Skip to content

Commit

Permalink
feat: Add CSharp SDK
Browse files Browse the repository at this point in the history
[FLI-783 - C# Server Side SDK](flipt-io#86)
- Add Authentication
- Add Evaluation (Boolean, Variant, and Batch)

Signed-off-by: vgwizardx <[email protected]>
  • Loading branch information
VgwizardX committed Jun 20, 2024
1 parent 87e9488 commit 816bce5
Show file tree
Hide file tree
Showing 30 changed files with 961 additions and 0 deletions.
29 changes: 29 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,32 @@ Cargo.lock
/flipt-python/.mypy_cache/
/flipt-python/.pytest_cache/
/flipt-python/.ruff_cache/

# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Visual Studio 2015 cache/options directory
.vs/

# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json

# VS Code
.vscode/
84 changes: 84 additions & 0 deletions flipt-csharp/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
[*.cs]

# IDE0290: Use primary constructor
dotnet_diagnostic.IDE0290.severity = none
csharp_indent_labels = one_less_than_current
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_prefer_braces = true:silent
csharp_style_namespace_declarations = block_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_primary_constructors = false:suggestion
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_space_around_binary_operators = before_and_after
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent
csharp_style_conditional_delegate_call = true:suggestion

[*.{cs,vb}]
#### Naming styles ####

# Naming rules

dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i

dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case

dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case

# Symbol specifications

dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =

dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =

dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =

# Naming styles

dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case

dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case

dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
indent_size = 4
end_of_line = crlf
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_prefer_collection_expression = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
dotnet_style_prefer_compound_assignment = true:suggestion
27 changes: 27 additions & 0 deletions flipt-csharp/flipt-csharp.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34316.72
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FliptCSharp", "src\FliptCSharp.csproj", "{5BAA5E95-A58B-4346-9C78-8076C81ED2D2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{13F95778-A052-43D1-88B2-4AF06F7E1AC2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5BAA5E95-A58B-4346-9C78-8076C81ED2D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5BAA5E95-A58B-4346-9C78-8076C81ED2D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5BAA5E95-A58B-4346-9C78-8076C81ED2D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5BAA5E95-A58B-4346-9C78-8076C81ED2D2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {35FFF068-2AD7-470B-88B0-A8254F952CD7}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace FliptCSharp.Authentication;

/// <summary>
/// This class is responsible for providing the client token authentication strategy.
/// </summary>
public class ClientTokenAuthenticationStrategy : IAuthenticationStrategy
{
private readonly string _clientToken;

public ClientTokenAuthenticationStrategy(string clientToken)
{
_clientToken = clientToken;
}
public string GetAuthorizationHeader()
{
return $"Bearer {_clientToken}";
}
}
9 changes: 9 additions & 0 deletions flipt-csharp/src/Authentication/IAuthenticationStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace FliptCSharp.Authentication;

/// <summary>
/// This interface is responsible for providing the authentication strategy.
/// </summary>
public interface IAuthenticationStrategy
{
string GetAuthorizationHeader();
}
19 changes: 19 additions & 0 deletions flipt-csharp/src/Authentication/JWTAuthenticationStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace FliptCSharp.Authentication;

/// <summary>
/// This class is responsible for providing the JWT authentication strategy.
/// </summary>
public class JWTAuthenticationStrategy : IAuthenticationStrategy
{
private readonly string _jwtToken;

public JWTAuthenticationStrategy(string jwtToken)
{
_jwtToken = jwtToken;
}

public string GetAuthorizationHeader()
{
return $"JWT {_jwtToken}";
}
}
155 changes: 155 additions & 0 deletions flipt-csharp/src/Clients/Evaluation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using FliptCSharp.Authentication;
using FliptCSharp.DTOs;

namespace FliptCSharp.Clients;
/// <summary>
/// This class is responsible for making requests to the Flipt server to evaluate flags.
/// </summary>
public class Evaluation
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl;
private readonly IAuthenticationStrategy? _authenticationStrategy;
private readonly IDictionary<string, string>? _headers;

/// <summary>
/// This method creates a new instance of the Evaluation class.
/// </summary>
/// <param name="builder"></param>
private Evaluation(EvaluationBuilder builder)
{
_httpClient = builder.HttpClient;
_baseUrl = builder.BaseUrl;
_authenticationStrategy = builder.AuthenticationStrategy;
_headers = builder.Headers;
}

/// <summary>
/// This method returns a new instance of the EvaluationBuilder class.
/// </summary>
/// <returns></returns>
public static EvaluationBuilder Builder()
{
return new EvaluationBuilder();
}

/// <summary>
/// This class is a builder for the Evaluation class.
/// </summary>
public class EvaluationBuilder
{
public HttpClient HttpClient { get; private set; } = null!;
public string BaseUrl { get; private set; } = null!;
public IAuthenticationStrategy? AuthenticationStrategy { get; private set; }
public IDictionary<string, string>? Headers { get; private set; }

public EvaluationBuilder WithHttpClient(HttpClient httpClient)
{
HttpClient = httpClient;
return this;
}

public EvaluationBuilder WithBaseUrl(string baseUrl)
{
BaseUrl = baseUrl;
return this;
}

public EvaluationBuilder WithAuthenticationStrategy(IAuthenticationStrategy? authenticationStrategy)
{
AuthenticationStrategy = authenticationStrategy;
return this;
}

public EvaluationBuilder WithHeaders(IDictionary<string, string>? headers)
{
Headers = headers;
return this;
}

public Evaluation Build()
{
if (HttpClient == null)
{
throw new InvalidOperationException("HttpClient must be provided.");
}

if (string.IsNullOrEmpty(BaseUrl))
{
throw new InvalidOperationException("BaseURL must be provided.");
}

return new Evaluation(this);
}
}

/// <summary>
/// This method evaluates a variant for a given flag key and entity id.
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<VariantEvaluationResponse?> EvaluateVariantAsync(EvaluationRequest request)
{
return await EvaluateAsync<VariantEvaluationResponse>("/evaluate/v1/variant", request);
}

/// <summary>
/// This method evaluates a boolean flag for a given flag key and entity id.
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<BooleanEvaluationResponse?> EvaluateBooleanAsync(EvaluationRequest request)
{
return await EvaluateAsync<BooleanEvaluationResponse>("/evaluate/v1/boolean", request);
}

/// <summary>
/// This method evaluates a batch of flags for a given flag key and entity id.
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<BatchEvaluationResponse?> EvaluateBatchAsync(BatchEvaluationRequest request)
{
return await EvaluateAsync<BatchEvaluationResponse>("/evaluate/v1/batch", request);
}

/// <summary>
/// This method evaluates a flag for a given flag key and entity id.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="path"></param>
/// <param name="request"></param>
/// <returns></returns>
private async Task<T?> EvaluateAsync<T>(string path, object request)
{
var url = new Uri(new Uri(_baseUrl), path);

var jsonContent = JsonSerializer.Serialize(request);
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");

var httpRequest = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = content
};

if (_headers != null)
foreach (var header in _headers)
{
httpRequest.Headers.Add(header.Key, header.Value);
}

if (_authenticationStrategy != null)
httpRequest.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", _authenticationStrategy.GetAuthorizationHeader());

var response = await _httpClient.SendAsync(httpRequest);
response.EnsureSuccessStatusCode();

var responseContent = await response.Content.ReadAsStringAsync();
return string.IsNullOrEmpty(responseContent) ? default : JsonSerializer.Deserialize<T>(responseContent);
}

}
19 changes: 19 additions & 0 deletions flipt-csharp/src/DTOs/BatchEvaluationRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;

namespace FliptCSharp.DTOs;

/// <summary>
/// Represents a batch evaluation request.
/// </summary>
public class BatchEvaluationRequest
{
public BatchEvaluationRequest(List<EvaluationRequest> evaluationRequests)
{
EvaluationRequests = evaluationRequests;
}
public string? RequestId { get; set; }

[Required]
public List<EvaluationRequest> EvaluationRequests { get; set; }
public string? Reference { get; set; }
}
Loading

0 comments on commit 816bce5

Please sign in to comment.