Skip to content

Commit

Permalink
feat: Introduce flipt provider for dotnet (#285)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrei de la Cruz <[email protected]>
  • Loading branch information
jeandreidc authored Oct 14, 2024
1 parent cbe61a9 commit 5cc83a1
Show file tree
Hide file tree
Showing 21 changed files with 3,513 additions and 3 deletions.
6 changes: 6 additions & 0 deletions .github/component_owners.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ components:
src/OpenFeature.Contrib.Providers.Statsig:
- jenshenneberg
- lattenborough
src/OpenFeature.Contrib.Providers.Flipt:
- jeandreidc
- markphelps

# test/
test/OpenFeature.Contrib.Hooks.Otel.Test:
Expand All @@ -45,6 +48,9 @@ components:
test/src/OpenFeature.Contrib.Providers.Statsig.Test:
- jenshenneberg
- lattenborough
test/src/OpenFeature.Contrib.Providers.Flipt.Test:
- jeandreidc
- markphelps

ignored-authors:
- renovate-bot
3 changes: 2 additions & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
"src/OpenFeature.Contrib.Providers.Flagsmith": "0.2.0",
"src/OpenFeature.Contrib.Providers.ConfigCat": "0.1.1",
"src/OpenFeature.Contrib.Providers.FeatureManagement": "0.1.0",
"src/OpenFeature.Contrib.Providers.Statsig": "0.1.0"
"src/OpenFeature.Contrib.Providers.Statsig": "0.1.0",
"src/OpenFeature.Contrib.Providers.Flipt": "0.0.1"
}
14 changes: 14 additions & 0 deletions DotnetSdkContrib.sln
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Provide
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.Statsig.Test", "test\OpenFeature.Contrib.Providers.Statsig.Test\OpenFeature.Contrib.Providers.Statsig.Test.csproj", "{F3080350-B0AB-4D59-B416-50CC38C99087}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.Flipt", "src\OpenFeature.Contrib.Providers.Flipt\OpenFeature.Contrib.Providers.Flipt.csproj", "{5ECF7DBF-FE64-40A2-BF39-239DE173DA4B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.Flipt.Test", "test\OpenFeature.Contrib.Providers.Flipt.Test\OpenFeature.Contrib.Providers.Flipt.Test.csproj", "{B446D481-B5A3-4509-8933-C4CF6DA9B147}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -115,6 +119,14 @@ Global
{F3080350-B0AB-4D59-B416-50CC38C99087}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3080350-B0AB-4D59-B416-50CC38C99087}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3080350-B0AB-4D59-B416-50CC38C99087}.Release|Any CPU.Build.0 = Release|Any CPU
{5ECF7DBF-FE64-40A2-BF39-239DE173DA4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5ECF7DBF-FE64-40A2-BF39-239DE173DA4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5ECF7DBF-FE64-40A2-BF39-239DE173DA4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5ECF7DBF-FE64-40A2-BF39-239DE173DA4B}.Release|Any CPU.Build.0 = Release|Any CPU
{B446D481-B5A3-4509-8933-C4CF6DA9B147}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B446D481-B5A3-4509-8933-C4CF6DA9B147}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B446D481-B5A3-4509-8933-C4CF6DA9B147}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B446D481-B5A3-4509-8933-C4CF6DA9B147}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -137,5 +149,7 @@ Global
{B8C5376B-BAFE-48B8-ABC1-111A93C033F2} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE}
{4C7C0E2D-6ECC-4D17-BC5D-18F6BA6F872A} = {0E563821-BD08-4B7F-BF9D-395CAD80F026}
{F3080350-B0AB-4D59-B416-50CC38C99087} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE}
{5ECF7DBF-FE64-40A2-BF39-239DE173DA4B} = {0E563821-BD08-4B7F-BF9D-395CAD80F026}
{B446D481-B5A3-4509-8933-C4CF6DA9B147} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE}
EndGlobalSection
EndGlobal
4 changes: 2 additions & 2 deletions nuget.config
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>

<configuration>

<packageSources>
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
<add key="github-open-feature" value="https://nuget.pkg.github.com/open-feature/index.json" />
<add key="github-open-feature" value="https://nuget.pkg.github.com/open-feature/index.json"/>
</packageSources>

<packageSourceMapping>
Expand Down
10 changes: 10 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@
"extra-files": [
"OpenFeature.Contrib.Providers.Statsig.csproj"
]
},
"src/OpenFeature.Contrib.Providers.Flipt": {
"package-name": "OpenFeature.Contrib.Providers.Flipt",
"release-type": "simple",
"bump-minor-pre-major": true,
"bump-patch-for-minor-pre-major": true,
"versioning": "default",
"extra-files": [
"OpenFeature.Contrib.Providers.Flipt.csproj"
]
}
},
"changelog-sections": [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Flipt.Rest;

namespace OpenFeature.Contrib.Providers.Flipt.ClientWrapper;

/// <summary>
/// Wrapper for Flipt server sdk client for .net
/// </summary>
public class FliptClientWrapper : IFliptClientWrapper
{
private readonly FliptRestClient _fliptRestClient;

/// <summary>
/// </summary>
/// <param name="fliptUrl">Url of flipt instance</param>
/// <param name="clientToken">Authentication access token</param>
/// <param name="timeoutInSeconds">Timeout when calling flipt endpoints in seconds</param>
public FliptClientWrapper(string fliptUrl,
string clientToken = "",
int timeoutInSeconds = 30)
{
_fliptRestClient = BuildClient(fliptUrl, clientToken, timeoutInSeconds);
}

/// <inheritdoc />
public async Task<VariantEvaluationResponse> EvaluateVariantAsync(EvaluationRequest evaluationRequest)
{
return await _fliptRestClient.EvaluateV1VariantAsync(evaluationRequest);
}

/// <inheritdoc />
public async Task<BooleanEvaluationResponse> EvaluateBooleanAsync(EvaluationRequest evaluationRequest)
{
return await _fliptRestClient.EvaluateV1BooleanAsync(evaluationRequest);
}

private static FliptRestClient BuildClient(string fliptUrl, string clientToken, int timeoutInSeconds = 30)
{
var httpClient = new HttpClient
{
BaseAddress = new Uri(fliptUrl),
Timeout = TimeSpan.FromSeconds(timeoutInSeconds),
DefaultRequestHeaders = { { "Authorization", $"Bearer {clientToken}" } }
};
return new FliptRestClient(httpClient);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Threading.Tasks;
using Flipt.Rest;

namespace OpenFeature.Contrib.Providers.Flipt.ClientWrapper;

/// <summary>
/// </summary>
public interface IFliptClientWrapper
{
/// <summary>
/// Wrapper to Flipt.io/EvaluateVariantAsync method
/// </summary>
/// <param name="evaluationRequest"></param>
/// <returns></returns>
Task<VariantEvaluationResponse> EvaluateVariantAsync(EvaluationRequest evaluationRequest);

/// <summary>
/// Wrapper to Flipt.io/EvaluateBooleanAsync method
/// </summary>
/// <param name="evaluationRequest"></param>
/// <returns></returns>
Task<BooleanEvaluationResponse> EvaluateBooleanAsync(EvaluationRequest evaluationRequest);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Text.Json;

namespace OpenFeature.Contrib.Providers.Flipt.Converters;

/// <summary>
/// Extensions for default JsonConverter behavior
/// </summary>
public static class JsonConverterExtensions
{
/// <summary>
/// JsonConverter serializer settings for Flipt to OpenFeature model deserialization
/// </summary>
public static readonly JsonSerializerOptions DefaultSerializerSettings = new()
{
WriteIndented = true,
AllowTrailingCommas = true,
Converters =
{
new OpenFeatureStructureConverter(),
new OpenFeatureValueConverter()
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using OpenFeature.Model;

namespace OpenFeature.Contrib.Providers.Flipt.Converters;

/// <summary>
/// JsonConverter for OpenFeature Structure type
/// </summary>
public class OpenFeatureStructureConverter : JsonConverter<Structure>
{
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, Structure value, JsonSerializerOptions options)
{
var jsonDoc = JsonDocument.Parse(JsonSerializer.Serialize(value.AsDictionary(),
JsonConverterExtensions.DefaultSerializerSettings));
jsonDoc.WriteTo(writer);
}

/// <inheritdoc />
public override Structure Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using var jsonDocument = JsonDocument.ParseValue(ref reader);
var jsonText = jsonDocument.RootElement.GetRawText();
return new Structure(JsonSerializer.Deserialize<Dictionary<string, Value>>(jsonText,
JsonConverterExtensions.DefaultSerializerSettings));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using OpenFeature.Model;

namespace OpenFeature.Contrib.Providers.Flipt.Converters;

/// <summary>
/// OpenFeature Value type converter
/// </summary>
public class OpenFeatureValueConverter : JsonConverter<Value>
{
/// <inheritdoc />
public override Value Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var value = new Value();
switch (reader.TokenType)
{
case JsonTokenType.String:
return reader.TryGetDateTime(out var dateTimeValue)
? new Value(dateTimeValue)
: new Value(reader.GetString() ?? string.Empty);
case JsonTokenType.True:
case JsonTokenType.False:
return new Value(reader.GetBoolean());
case JsonTokenType.Number:
if (reader.TryGetInt32(out var intValue)) return new Value(intValue);
if (reader.TryGetDouble(out var dblValue)) return new Value(dblValue);
break;
case JsonTokenType.StartArray:
return new Value(GenerateValueArray(ref reader, typeToConvert, options));
case JsonTokenType.StartObject:
return new Value(GetStructure(ref reader, typeToConvert, options));
}

return value;
}

private Structure GetStructure(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var startDepth = reader.CurrentDepth;
var structureDictionary = new Dictionary<string, Value>();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.PropertyName)
{
var key = reader.GetString();
reader.Read();
var val = Read(ref reader, typeToConvert, options);
structureDictionary[key ?? string.Empty] = val;
}

if (reader.TokenType == JsonTokenType.EndObject && reader.CurrentDepth == startDepth) break;
}

return new Structure(structureDictionary);
}


private IList<Value> GenerateValueArray(ref Utf8JsonReader reader, Type typeToConvert,
JsonSerializerOptions options)
{
var valuesArray = new List<Value>();
var startDepth = reader.CurrentDepth;

while (reader.Read())
switch (reader.TokenType)
{
case JsonTokenType.EndArray when reader.CurrentDepth == startDepth:
return valuesArray;
default:
valuesArray.Add(Read(ref reader, typeToConvert, options));
break;
}

return valuesArray;
}

/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, Value value, JsonSerializerOptions options)
{
if (value.IsList)
{
writer.WriteStartArray();
foreach (var val in value.AsList!)
{
var jsonDoc = JsonDocument.Parse(JsonSerializer.Serialize(val.AsObject,
JsonConverterExtensions.DefaultSerializerSettings));
jsonDoc.WriteTo(writer);
}

writer.WriteEndArray();
}
else
{
var jsonDoc = JsonDocument.Parse(JsonSerializer.Serialize(value.AsObject,
JsonConverterExtensions.DefaultSerializerSettings));
jsonDoc.WriteTo(writer);
}
}
}
27 changes: 27 additions & 0 deletions src/OpenFeature.Contrib.Providers.Flipt/FliptExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using OpenFeature.Contrib.Providers.Flipt.Converters;
using OpenFeature.Model;

namespace OpenFeature.Contrib.Providers.Flipt;

/// <summary>
/// Extension helper methods
/// </summary>
public static class FliptExtensions
{
/// <summary>
/// Transforms openFeature EvaluationContext to a mutable Dictionary that flipt sdk accepts
/// </summary>
/// <param name="evaluationContext">OpenFeature EvaluationContext</param>
/// <returns></returns>
public static Dictionary<string, string> ToStringDictionary(this EvaluationContext evaluationContext)
{
return evaluationContext?.AsDictionary()
.ToDictionary(k => k.Key,
v => JsonSerializer.Serialize(v.Value.AsObject,
JsonConverterExtensions.DefaultSerializerSettings)) ??
[];
}
}
Loading

0 comments on commit 5cc83a1

Please sign in to comment.