From b5c7dd595bf813f2fa5fa8c90cfb773e93f5dbb3 Mon Sep 17 00:00:00 2001 From: VgwizardX Date: Wed, 19 Jun 2024 19:52:10 -0400 Subject: [PATCH] feat: Add CSharp SDK [FLI-783 - C# Server Side SDK](https://github.com/flipt-io/flipt-server-sdks/issues/86) - Add Authentication - Add Evaluation (Boolean, Variant, and Batch) Signed-off-by: vgwizardx <1323841+vgwizardx@users.noreply.github.com> --- .gitignore | 29 ++++ flipt-csharp/.editorconfig | 84 ++++++++++ flipt-csharp/flipt-csharp.sln | 27 +++ .../ClientTokenAuthenticationStrategy.cs | 18 ++ .../Authentication/IAuthenticationStrategy.cs | 9 + .../JWTAuthenticationStrategy.cs | 19 +++ flipt-csharp/src/Clients/Evaluation.cs | 155 ++++++++++++++++++ .../src/DTOs/BatchEvaluationRequest.cs | 19 +++ .../src/DTOs/BatchEvaluationResponse.cs | 37 +++++ .../src/DTOs/BooleanEvaluationResponse.cs | 23 +++ flipt-csharp/src/DTOs/EvaluationRequest.cs | 32 ++++ .../src/DTOs/VariantEvaluationResponse.cs | 29 ++++ flipt-csharp/src/Example/Program.cs | 37 +++++ flipt-csharp/src/FliptCSharp.csproj | 13 ++ flipt-csharp/src/Models/Reason.cs | 21 +++ flipt-csharp/src/Models/ResponseType.cs | 18 ++ flipt-csharp/src/Utilities/FliptClient.cs | 116 +++++++++++++ .../Controllers/EvaluationController.cs | 75 +++++++++ .../srcWrong/DTOs/BatchEvaluationRequest.cs | 12 ++ .../srcWrong/DTOs/BatchEvaluationResponse.cs | 33 ++++ .../DTOs/BooleanEvaluationResponse.cs | 20 +++ .../srcWrong/DTOs/EvaluationRequest.cs | 22 +++ .../DTOs/VariantEvaluationResponse.cs | 16 ++ flipt-csharp/srcWrong/FliptCsharpWrong.csproj | 15 ++ flipt-csharp/srcWrong/Models/Reason.cs | 18 ++ flipt-csharp/srcWrong/Models/ResponseType.cs | 16 ++ flipt-csharp/srcWrong/Program.cs | 25 +++ .../srcWrong/appsettings.Development.json | 8 + flipt-csharp/srcWrong/appsettings.json | 9 + flipt-csharp/srcWrong/flipt-csharp.http | 6 + 30 files changed, 961 insertions(+) create mode 100644 flipt-csharp/.editorconfig create mode 100644 flipt-csharp/flipt-csharp.sln create mode 100644 flipt-csharp/src/Authentication/ClientTokenAuthenticationStrategy.cs create mode 100644 flipt-csharp/src/Authentication/IAuthenticationStrategy.cs create mode 100644 flipt-csharp/src/Authentication/JWTAuthenticationStrategy.cs create mode 100644 flipt-csharp/src/Clients/Evaluation.cs create mode 100644 flipt-csharp/src/DTOs/BatchEvaluationRequest.cs create mode 100644 flipt-csharp/src/DTOs/BatchEvaluationResponse.cs create mode 100644 flipt-csharp/src/DTOs/BooleanEvaluationResponse.cs create mode 100644 flipt-csharp/src/DTOs/EvaluationRequest.cs create mode 100644 flipt-csharp/src/DTOs/VariantEvaluationResponse.cs create mode 100644 flipt-csharp/src/Example/Program.cs create mode 100644 flipt-csharp/src/FliptCSharp.csproj create mode 100644 flipt-csharp/src/Models/Reason.cs create mode 100644 flipt-csharp/src/Models/ResponseType.cs create mode 100644 flipt-csharp/src/Utilities/FliptClient.cs create mode 100644 flipt-csharp/srcWrong/Controllers/EvaluationController.cs create mode 100644 flipt-csharp/srcWrong/DTOs/BatchEvaluationRequest.cs create mode 100644 flipt-csharp/srcWrong/DTOs/BatchEvaluationResponse.cs create mode 100644 flipt-csharp/srcWrong/DTOs/BooleanEvaluationResponse.cs create mode 100644 flipt-csharp/srcWrong/DTOs/EvaluationRequest.cs create mode 100644 flipt-csharp/srcWrong/DTOs/VariantEvaluationResponse.cs create mode 100644 flipt-csharp/srcWrong/FliptCsharpWrong.csproj create mode 100644 flipt-csharp/srcWrong/Models/Reason.cs create mode 100644 flipt-csharp/srcWrong/Models/ResponseType.cs create mode 100644 flipt-csharp/srcWrong/Program.cs create mode 100644 flipt-csharp/srcWrong/appsettings.Development.json create mode 100644 flipt-csharp/srcWrong/appsettings.json create mode 100644 flipt-csharp/srcWrong/flipt-csharp.http diff --git a/.gitignore b/.gitignore index 5ff2583..b849b3f 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ \ No newline at end of file diff --git a/flipt-csharp/.editorconfig b/flipt-csharp/.editorconfig new file mode 100644 index 0000000..c69706b --- /dev/null +++ b/flipt-csharp/.editorconfig @@ -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 diff --git a/flipt-csharp/flipt-csharp.sln b/flipt-csharp/flipt-csharp.sln new file mode 100644 index 0000000..bb9a0b6 --- /dev/null +++ b/flipt-csharp/flipt-csharp.sln @@ -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 diff --git a/flipt-csharp/src/Authentication/ClientTokenAuthenticationStrategy.cs b/flipt-csharp/src/Authentication/ClientTokenAuthenticationStrategy.cs new file mode 100644 index 0000000..0e4ada7 --- /dev/null +++ b/flipt-csharp/src/Authentication/ClientTokenAuthenticationStrategy.cs @@ -0,0 +1,18 @@ +namespace FliptCSharp.Authentication; + +/// +/// This class is responsible for providing the client token authentication strategy. +/// +public class ClientTokenAuthenticationStrategy : IAuthenticationStrategy +{ + private readonly string _clientToken; + + public ClientTokenAuthenticationStrategy(string clientToken) + { + _clientToken = clientToken; + } + public string GetAuthorizationHeader() + { + return $"Bearer {_clientToken}"; + } +} \ No newline at end of file diff --git a/flipt-csharp/src/Authentication/IAuthenticationStrategy.cs b/flipt-csharp/src/Authentication/IAuthenticationStrategy.cs new file mode 100644 index 0000000..988f68c --- /dev/null +++ b/flipt-csharp/src/Authentication/IAuthenticationStrategy.cs @@ -0,0 +1,9 @@ +namespace FliptCSharp.Authentication; + +/// +/// This interface is responsible for providing the authentication strategy. +/// +public interface IAuthenticationStrategy +{ + string GetAuthorizationHeader(); +} \ No newline at end of file diff --git a/flipt-csharp/src/Authentication/JWTAuthenticationStrategy.cs b/flipt-csharp/src/Authentication/JWTAuthenticationStrategy.cs new file mode 100644 index 0000000..1a5e747 --- /dev/null +++ b/flipt-csharp/src/Authentication/JWTAuthenticationStrategy.cs @@ -0,0 +1,19 @@ +namespace FliptCSharp.Authentication; + +/// +/// This class is responsible for providing the JWT authentication strategy. +/// +public class JWTAuthenticationStrategy : IAuthenticationStrategy +{ + private readonly string _jwtToken; + + public JWTAuthenticationStrategy(string jwtToken) + { + _jwtToken = jwtToken; + } + + public string GetAuthorizationHeader() + { + return $"JWT {_jwtToken}"; + } +} \ No newline at end of file diff --git a/flipt-csharp/src/Clients/Evaluation.cs b/flipt-csharp/src/Clients/Evaluation.cs new file mode 100644 index 0000000..cb1ae2f --- /dev/null +++ b/flipt-csharp/src/Clients/Evaluation.cs @@ -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; +/// +/// This class is responsible for making requests to the Flipt server to evaluate flags. +/// +public class Evaluation +{ + private readonly HttpClient _httpClient; + private readonly string _baseUrl; + private readonly IAuthenticationStrategy? _authenticationStrategy; + private readonly IDictionary? _headers; + + /// + /// This method creates a new instance of the Evaluation class. + /// + /// + private Evaluation(EvaluationBuilder builder) + { + _httpClient = builder.HttpClient; + _baseUrl = builder.BaseUrl; + _authenticationStrategy = builder.AuthenticationStrategy; + _headers = builder.Headers; + } + + /// + /// This method returns a new instance of the EvaluationBuilder class. + /// + /// + public static EvaluationBuilder Builder() + { + return new EvaluationBuilder(); + } + + /// + /// This class is a builder for the Evaluation class. + /// + 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? 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? 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); + } + } + + /// + /// This method evaluates a variant for a given flag key and entity id. + /// + /// + /// + public async Task EvaluateVariantAsync(EvaluationRequest request) + { + return await EvaluateAsync("/evaluate/v1/variant", request); + } + + /// + /// This method evaluates a boolean flag for a given flag key and entity id. + /// + /// + /// + public async Task EvaluateBooleanAsync(EvaluationRequest request) + { + return await EvaluateAsync("/evaluate/v1/boolean", request); + } + + /// + /// This method evaluates a batch of flags for a given flag key and entity id. + /// + /// + /// + public async Task EvaluateBatchAsync(BatchEvaluationRequest request) + { + return await EvaluateAsync("/evaluate/v1/batch", request); + } + + /// + /// This method evaluates a flag for a given flag key and entity id. + /// + /// + /// + /// + /// + private async Task EvaluateAsync(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(responseContent); + } + +} \ No newline at end of file diff --git a/flipt-csharp/src/DTOs/BatchEvaluationRequest.cs b/flipt-csharp/src/DTOs/BatchEvaluationRequest.cs new file mode 100644 index 0000000..bd6c0de --- /dev/null +++ b/flipt-csharp/src/DTOs/BatchEvaluationRequest.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; + +namespace FliptCSharp.DTOs; + +/// +/// Represents a batch evaluation request. +/// +public class BatchEvaluationRequest +{ + public BatchEvaluationRequest(List evaluationRequests) + { + EvaluationRequests = evaluationRequests; + } + public string? RequestId { get; set; } + + [Required] + public List EvaluationRequests { get; set; } + public string? Reference { get; set; } +} \ No newline at end of file diff --git a/flipt-csharp/src/DTOs/BatchEvaluationResponse.cs b/flipt-csharp/src/DTOs/BatchEvaluationResponse.cs new file mode 100644 index 0000000..d53d0ef --- /dev/null +++ b/flipt-csharp/src/DTOs/BatchEvaluationResponse.cs @@ -0,0 +1,37 @@ +using System.ComponentModel.DataAnnotations; +using FliptCSharp.Models; + +namespace FliptCSharp.DTOs; + +/// +/// Represents a batch evaluation response. +/// +public class BatchEvaluationResponse +{ + [Required] + public required string RequestId { get; set; } + + [Required] + public required Response[] Responses { get; set; } + + [Required] + public int RequestDurationMillis { get; set; } +} + + +public class Response +{ + [Required] + public ResponseType Type { get; set; } + public BooleanEvaluationResponse? BooleanResponse { get; set; } + public VariantEvaluationResponse? VariantResponse { get; set; } + public ErrorEvaluationResponse? ErrorResponse { get; set; } +} + + +public class ErrorEvaluationResponse +{ + public string? FlagKey { get; set; } + public string? NamespaceKey { get; set; } + public string? Reason { get; set; } +} diff --git a/flipt-csharp/src/DTOs/BooleanEvaluationResponse.cs b/flipt-csharp/src/DTOs/BooleanEvaluationResponse.cs new file mode 100644 index 0000000..0146e1e --- /dev/null +++ b/flipt-csharp/src/DTOs/BooleanEvaluationResponse.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; +using FliptCSharp.Models; + +namespace FliptCSharp.DTOs; + +/// +/// Represents a boolean evaluation response. +/// +public class BooleanEvaluationResponse +{ + [Required] + public required string RequestId { get; set; } + [Required] + public required string FlagKey { get; set; } + [Required] + public bool Enabled { get; set; } + [Required] + public DateTime Timestamp { get; set; } + [Required] + public int RequestDurationMillis { get; set; } + [Required] + public Reason Reason { get; set; } +} diff --git a/flipt-csharp/src/DTOs/EvaluationRequest.cs b/flipt-csharp/src/DTOs/EvaluationRequest.cs new file mode 100644 index 0000000..8c46796 --- /dev/null +++ b/flipt-csharp/src/DTOs/EvaluationRequest.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; + +namespace FliptCSharp.DTOs; + +/// +/// Represents an evaluation request. +/// +public class EvaluationRequest +{ + public EvaluationRequest(string namespaceKey, string flagKey, string entityId, Dictionary context) + { + NamespaceKey = namespaceKey; + FlagKey = flagKey; + EntityId = entityId; + Context = context; + } + public string? RequestId { get; set; } + + [Required] + public string NamespaceKey { get; set; } + + [Required] + public string FlagKey { get; set; } + + [Required] + public string EntityId { get; set; } + + [Required] + public Dictionary Context { get; set; } + + public string? Reference { get; set; } +} \ No newline at end of file diff --git a/flipt-csharp/src/DTOs/VariantEvaluationResponse.cs b/flipt-csharp/src/DTOs/VariantEvaluationResponse.cs new file mode 100644 index 0000000..dbb4b97 --- /dev/null +++ b/flipt-csharp/src/DTOs/VariantEvaluationResponse.cs @@ -0,0 +1,29 @@ +using System.ComponentModel.DataAnnotations; +using FliptCSharp.Models; + +namespace FliptCSharp.DTOs; + +/// +/// Represents a variant evaluation response. +/// +public class VariantEvaluationResponse +{ + [Required] + public required string RequestId { get; set; } + [Required] + public bool Match { get; set; } + [Required] + public required string FlagKey { get; set; } + [Required] + public required List SegmentKeys { get; set; } + [Required] + public required string VariantKey { get; set; } + [Required] + public required string VariantAttachment { get; set; } + [Required] + public DateTime Timestamp { get; set; } + [Required] + public int RequestDurationMillis { get; set; } + [Required] + public Reason Reason { get; set; } +} diff --git a/flipt-csharp/src/Example/Program.cs b/flipt-csharp/src/Example/Program.cs new file mode 100644 index 0000000..908b589 --- /dev/null +++ b/flipt-csharp/src/Example/Program.cs @@ -0,0 +1,37 @@ +using FliptCSharp.Authentication; +using FliptCSharp.DTOs; +using FliptCSharp.Utilities; + +namespace FliptCSharp.Example; + +public class Program +{ + public static async Task Main() + { + var fliptClient = FliptClient.Builder() + .WithUrl("http://localhost:8080") + .WithAuthentication(new ClientTokenAuthenticationStrategy("Client-Token")) + .WithTimeout(30) + .Build(); + + Dictionary context = new() { { "fizz", "buzz" } }; + + var evaluation = fliptClient.Evaluation; + + var variant = new EvaluationRequest("default", "flag1", "entity", context); + var variantEvaluationResponse = await evaluation.EvaluateVariantAsync(variant); + + var boolEvaluation = new EvaluationRequest("default", "bool_flag", "entity", context); + var boolEvaluationResponse = await evaluation.EvaluateBooleanAsync(boolEvaluation); + + var list = new List + { + variant, + boolEvaluation + }; + var batchEvaluationRequest = new BatchEvaluationRequest(list); + var batchEvaluationResponse = await evaluation.EvaluateBatchAsync(batchEvaluationRequest); + } + + +} \ No newline at end of file diff --git a/flipt-csharp/src/FliptCSharp.csproj b/flipt-csharp/src/FliptCSharp.csproj new file mode 100644 index 0000000..88c3a39 --- /dev/null +++ b/flipt-csharp/src/FliptCSharp.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/flipt-csharp/src/Models/Reason.cs b/flipt-csharp/src/Models/Reason.cs new file mode 100644 index 0000000..18a5580 --- /dev/null +++ b/flipt-csharp/src/Models/Reason.cs @@ -0,0 +1,21 @@ +using System.Runtime.Serialization; + +namespace FliptCSharp.Models; + +/// +/// Represents the reason for the evaluation. +/// +public enum Reason +{ + [EnumMember(Value = "UNKNOWN_EVALUATION_REASON")] + UnknownEvaluationReason, + + [EnumMember(Value = "FLAG_DISABLED_EVALUATION_REASON")] + FlagDisabledEvaluationReason, + + [EnumMember(Value = "MATCH_EVALUATION_REASON")] + MatchEvaluationReason, + + [EnumMember(Value = "DEFAULT_EVALUATION_REASON")] + DefaultEvaluationReason +} \ No newline at end of file diff --git a/flipt-csharp/src/Models/ResponseType.cs b/flipt-csharp/src/Models/ResponseType.cs new file mode 100644 index 0000000..51adbaa --- /dev/null +++ b/flipt-csharp/src/Models/ResponseType.cs @@ -0,0 +1,18 @@ +using System.Runtime.Serialization; + +namespace FliptCSharp.Models; + +/// +/// Represents the response type. +/// +public enum ResponseType +{ + [EnumMember(Value = "VARIANT_EVALUATION_RESPONSE_TYPE")] + VariantEvaluationResponseType, + + [EnumMember(Value = "BOOLEAN_EVALUATION_RESPONSE_TYPE")] + BooleanEvaluationResponseType, + + [EnumMember(Value = "ERROR_EVALUATION_RESPONSE_TYPE")] + ErrorEvaluationResponseType +} diff --git a/flipt-csharp/src/Utilities/FliptClient.cs b/flipt-csharp/src/Utilities/FliptClient.cs new file mode 100644 index 0000000..dd9aa68 --- /dev/null +++ b/flipt-csharp/src/Utilities/FliptClient.cs @@ -0,0 +1,116 @@ +using FliptCSharp.Authentication; +using FliptCSharp.Clients; + +namespace FliptCSharp.Utilities; + +/// +/// This class is a wrapper around the Evaluation class. It provides a builder pattern to create an instance of the Evaluation class. +/// +public class FliptClient +{ + /// + /// This method returns a new instance of the FliptClientBuilder class. + /// + /// + private FliptClient(FliptClientBuilder builder) + { + + var httpClient = new HttpClient() + { + Timeout = builder.Timeout + }; + + Evaluation = Evaluation.Builder() + .WithHttpClient(httpClient) + .WithBaseUrl(builder.BaseUrl) + .WithAuthenticationStrategy(builder.AuthenticationStrategy) + .WithHeaders(builder.Headers) + .Build(); + } + + public Evaluation Evaluation { get; } + + /// + /// This method returns a new instance of the FliptClientBuilder class. + /// + /// + public static FliptClientBuilder Builder() + { + return new FliptClientBuilder(); + } + + /// + /// This class is a builder for the FliptClient class. + /// + public class FliptClientBuilder + { + public string BaseUrl { get; private set; } = "http://localhost:8080"; + public IAuthenticationStrategy? AuthenticationStrategy { get; private set; } + public IDictionary? Headers { get; private set; } = new Dictionary(); + public TimeSpan Timeout { get; private set; } = TimeSpan.FromSeconds(60); + + /// + /// This method sets the base URL for the FliptClient. + /// + /// + /// + public FliptClientBuilder WithUrl(string url) + { + BaseUrl = url; + return this; + } + + /// + /// This method sets the authentication strategy for the FliptClient. + /// + /// + /// + public FliptClientBuilder WithAuthentication(IAuthenticationStrategy authenticationStrategy) + { + AuthenticationStrategy = authenticationStrategy; + return this; + } + + /// + /// This method sets the headers for the FliptClient. + /// + /// + /// + public FliptClientBuilder WithHeaders(IDictionary headers) + { + Headers = headers; + return this; + } + + /// + /// This method sets the timeout for the FliptClient. + /// + /// + /// + public FliptClientBuilder WithTimeout(TimeSpan timeout) + { + Timeout = timeout; + return this; + } + + /// + /// This method sets the timeout in seconds for the FliptClient. + /// + /// + /// + public FliptClientBuilder WithTimeout(int timeoutInSeconds) + { + Timeout = TimeSpan.FromSeconds(timeoutInSeconds); + return this; + } + + /// + /// This method builds a new instance of the FliptClient class. + /// + /// + public FliptClient Build() + { + return new FliptClient(this); + } + } +} \ No newline at end of file diff --git a/flipt-csharp/srcWrong/Controllers/EvaluationController.cs b/flipt-csharp/srcWrong/Controllers/EvaluationController.cs new file mode 100644 index 0000000..d1d34c1 --- /dev/null +++ b/flipt-csharp/srcWrong/Controllers/EvaluationController.cs @@ -0,0 +1,75 @@ +using System.Text.Json; +using Asp.Versioning; +using FliptCsharp.DTOs; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace FliptCsharp.Controllers; + +[Route("api/evaluate/v{version:apiVersion}")] +[ApiVersion("1")] +[ApiController] +public class EvaluationController : ControllerBase +{ + private readonly HttpClient _client; + private readonly string _version; + public EvaluationController(IHttpClientFactory clientFactory) + { + _client = clientFactory.CreateClient("FliptApi"); + _version = HttpContext.GetRequestedApiVersion()?.ToString() ?? "1"; + } + + [HttpPost] + [Route("boolean")] + public async Task> BooleanEvaluation([FromBody] EvaluationRequest request) + + { + // Get the API version as a string + var url = $"evaluate/v{_version}/boolean"; + var response = await _client.PostAsJsonAsync(url, request); + response.EnsureSuccessStatusCode(); + var jsonResponse = await response.Content.ReadAsStringAsync(); + var evaluationResponse = JsonSerializer.Deserialize(jsonResponse); + if (evaluationResponse == null) + { + return BadRequest(); + } + return evaluationResponse; + } + + [HttpPost] + [Route("boolean")] + public async Task> VariantEvaluation([FromBody] EvaluationRequest request) + + { + // Get the API version as a string + var url = $"evaluate/v{_version}/variant"; + var response = await _client.PostAsJsonAsync(url, request); + response.EnsureSuccessStatusCode(); + var jsonResponse = await response.Content.ReadAsStringAsync(); + var evaluationResponse = JsonSerializer.Deserialize(jsonResponse); + if (evaluationResponse == null) + { + return BadRequest(); + } + return evaluationResponse; + } + + [HttpPost] + [Route("batch")] + public async Task> BatchEvaluation([FromBody] BatchEvaluationRequest request) + + { + // Get the API version as a string + var url = $"evaluate/v{_version}/batch"; + var response = await _client.PostAsJsonAsync(url, request); + response.EnsureSuccessStatusCode(); + var jsonResponse = await response.Content.ReadAsStringAsync(); + var evaluationResponse = JsonSerializer.Deserialize(jsonResponse); + if (evaluationResponse == null) + { + return BadRequest(); + } + return evaluationResponse; + } +} \ No newline at end of file diff --git a/flipt-csharp/srcWrong/DTOs/BatchEvaluationRequest.cs b/flipt-csharp/srcWrong/DTOs/BatchEvaluationRequest.cs new file mode 100644 index 0000000..2c75f05 --- /dev/null +++ b/flipt-csharp/srcWrong/DTOs/BatchEvaluationRequest.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace FliptCsharp.DTOs; + +public class BatchEvaluationRequest +{ + public string requestId { get; set; } + + [Required] + public List evaluationRequests { get; set; } + public string reference { get; set; } +} \ No newline at end of file diff --git a/flipt-csharp/srcWrong/DTOs/BatchEvaluationResponse.cs b/flipt-csharp/srcWrong/DTOs/BatchEvaluationResponse.cs new file mode 100644 index 0000000..cf2f6b3 --- /dev/null +++ b/flipt-csharp/srcWrong/DTOs/BatchEvaluationResponse.cs @@ -0,0 +1,33 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; +using FliptCsharp.Models; + +namespace FliptCsharp.DTOs; + +public class BatchEvaluationResponse +{ + [Required] + public string requestId { get; set; } + [Required] + public Response[] responses { get; set; } + [Required] + public int requestDurationMillis { get; set; } +} + + +public class Response +{ + [Required] + public ResponseType type { get; set; } + public BooleanEvaluationResponse booleanResponse { get; set; } + public VariantEvaluationResponse variantResponse { get; set; } + public ErrorEvaluationResponse errorResponse { get; set; } +} + + +public class ErrorEvaluationResponse +{ + public string flagKey { get; set; } + public string namespaceKey { get; set; } + public string reason { get; set; } +} diff --git a/flipt-csharp/srcWrong/DTOs/BooleanEvaluationResponse.cs b/flipt-csharp/srcWrong/DTOs/BooleanEvaluationResponse.cs new file mode 100644 index 0000000..dfd0177 --- /dev/null +++ b/flipt-csharp/srcWrong/DTOs/BooleanEvaluationResponse.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations; +using FliptCsharp.Models; + +namespace FliptCsharp.DTOs; + +public class BooleanEvaluationResponse +{ + [Required] + public string requestId { get; set; } + [Required] + public string flagKey { get; set; } + [Required] + public bool enabled { get; set; } + [Required] + public DateTime timestamp { get; set; } + [Required] + public int requestDurationMillis { get; set; } + [Required] + public Reason reason { get; set; } +} diff --git a/flipt-csharp/srcWrong/DTOs/EvaluationRequest.cs b/flipt-csharp/srcWrong/DTOs/EvaluationRequest.cs new file mode 100644 index 0000000..ea82c69 --- /dev/null +++ b/flipt-csharp/srcWrong/DTOs/EvaluationRequest.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations; + +namespace FliptCsharp.DTOs; + +public class EvaluationRequest +{ + public string requestId { get; set; } + + [Required] + public string namespaceKey { get; set; } + + [Required] + public string flagKey { get; set; } + + [Required] + public string entityId { get; set; } + + [Required] + public Dictionary context { get; set; } + + public string reference { get; set; } +} \ No newline at end of file diff --git a/flipt-csharp/srcWrong/DTOs/VariantEvaluationResponse.cs b/flipt-csharp/srcWrong/DTOs/VariantEvaluationResponse.cs new file mode 100644 index 0000000..ae3e50e --- /dev/null +++ b/flipt-csharp/srcWrong/DTOs/VariantEvaluationResponse.cs @@ -0,0 +1,16 @@ +using FliptCsharp.Models; + +namespace FliptCsharp.DTOs; + +public class VariantEvaluationResponse +{ + public string requestId { get; set; } + public bool match { get; set; } + public string flagKey { get; set; } + public List segmentKeys { get; set; } + public string variantKey { get; set; } + public string variantAttachment { get; set; } + public DateTime timestamp { get; set; } + public int requestDurationMillis { get; set; } + public Reason reason { get; set; } +} diff --git a/flipt-csharp/srcWrong/FliptCsharpWrong.csproj b/flipt-csharp/srcWrong/FliptCsharpWrong.csproj new file mode 100644 index 0000000..a7e5dfd --- /dev/null +++ b/flipt-csharp/srcWrong/FliptCsharpWrong.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + true + + + + + + + + diff --git a/flipt-csharp/srcWrong/Models/Reason.cs b/flipt-csharp/srcWrong/Models/Reason.cs new file mode 100644 index 0000000..baff266 --- /dev/null +++ b/flipt-csharp/srcWrong/Models/Reason.cs @@ -0,0 +1,18 @@ +using System.Runtime.Serialization; + +namespace FliptCsharp.Models; + +public enum Reason +{ + [EnumMember(Value = "UNKNOWN_EVALUATION_REASON")] + UNKNOWN_EVALUATION_REASON, + + [EnumMember(Value = "FLAG_DISABLED_EVALUATION_REASON")] + FLAG_DISABLED_EVALUATION_REASON, + + [EnumMember(Value = "MATCH_EVALUATION_REASON")] + MATCH_EVALUATION_REASON, + + [EnumMember(Value = "DEFAULT_EVALUATION_REASON")] + DEFAULT_EVALUATION_REASON +} \ No newline at end of file diff --git a/flipt-csharp/srcWrong/Models/ResponseType.cs b/flipt-csharp/srcWrong/Models/ResponseType.cs new file mode 100644 index 0000000..97a18aa --- /dev/null +++ b/flipt-csharp/srcWrong/Models/ResponseType.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace FliptCsharp.Models; +using System.Runtime.Serialization; + +public enum ResponseType +{ + [EnumMember(Value = "VARIANT_EVALUATION_RESPONSE_TYPE")] + VARIANT_EVALUATION_RESPONSE_TYPE, + + [EnumMember(Value = "BOOLEAN_EVALUATION_RESPONSE_TYPE")] + BOOLEAN_EVALUATION_RESPONSE_TYPE, + + [EnumMember(Value = "ERROR_EVALUATION_RESPONSE_TYPE")] + ERROR_EVALUATION_RESPONSE_TYPE +} diff --git a/flipt-csharp/srcWrong/Program.cs b/flipt-csharp/srcWrong/Program.cs new file mode 100644 index 0000000..48863a6 --- /dev/null +++ b/flipt-csharp/srcWrong/Program.cs @@ -0,0 +1,25 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/flipt-csharp/srcWrong/appsettings.Development.json b/flipt-csharp/srcWrong/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/flipt-csharp/srcWrong/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/flipt-csharp/srcWrong/appsettings.json b/flipt-csharp/srcWrong/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/flipt-csharp/srcWrong/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/flipt-csharp/srcWrong/flipt-csharp.http b/flipt-csharp/srcWrong/flipt-csharp.http new file mode 100644 index 0000000..e1f587b --- /dev/null +++ b/flipt-csharp/srcWrong/flipt-csharp.http @@ -0,0 +1,6 @@ +@src_HostAddress = http://localhost:5033 + +GET {{src_HostAddress}}/weatherforecast/ +Accept: application/json + +###