Skip to content

Commit 37aa55c

Browse files
committed
Add Flagsmith provider
1 parent d8cac7f commit 37aa55c

File tree

9 files changed

+690
-11
lines changed

9 files changed

+690
-11
lines changed

.release-please-manifest.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"src/OpenFeature.Contrib.Hooks.Otel": "0.1.1",
33
"src/OpenFeature.Contrib.Providers.Flagd": "0.1.7",
4-
"src/OpenFeature.Contrib.Providers.GOFeatureFlag": "0.1.4"
4+
"src/OpenFeature.Contrib.Providers.GOFeatureFlag": "0.1.4",
5+
"src/OpenFeature.Contrib.Providers.Flagsmith": "0.1.0"
56
}

DotnetSdkContrib.sln

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,29 @@ VisualStudioVersion = 17.0.31903.59
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0E563821-BD08-4B7F-BF9D-395CAD80F026}"
77
EndProject
8-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.Flagd", "src\OpenFeature.Contrib.Providers.Flagd\OpenFeature.Contrib.Providers.Flagd.csproj", "{6F8FF25A-F22B-4083-B3F9-B4B9BB6FB699}"
8+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Contrib.Providers.Flagd", "src\OpenFeature.Contrib.Providers.Flagd\OpenFeature.Contrib.Providers.Flagd.csproj", "{6F8FF25A-F22B-4083-B3F9-B4B9BB6FB699}"
99
EndProject
10-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Hooks.Otel", "src\OpenFeature.Contrib.Hooks.Otel\OpenFeature.Contrib.Hooks.Otel.csproj", "{82D10BAE-F1EE-432A-BD5D-DECAD07A84FE}"
10+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Contrib.Hooks.Otel", "src\OpenFeature.Contrib.Hooks.Otel\OpenFeature.Contrib.Hooks.Otel.csproj", "{82D10BAE-F1EE-432A-BD5D-DECAD07A84FE}"
1111
EndProject
1212
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B6D3230B-5E4D-4FF1-868E-2F4E325C84FE}"
1313
EndProject
14-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Hooks.Otel.Test", "test\OpenFeature.Contrib.Hooks.Otel.Test\OpenFeature.Contrib.Hooks.Otel.Test.csproj", "{199FA48A-06EF-4E15-8206-C095D1455A99}"
14+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Contrib.Hooks.Otel.Test", "test\OpenFeature.Contrib.Hooks.Otel.Test\OpenFeature.Contrib.Hooks.Otel.Test.csproj", "{199FA48A-06EF-4E15-8206-C095D1455A99}"
1515
EndProject
16-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.Flagd.Test", "test\OpenFeature.Contrib.Providers.Flagd.Test\OpenFeature.Contrib.Providers.Flagd.Test.csproj", "{206323A0-7334-4723-8394-C31C150B95DC}"
16+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Contrib.Providers.Flagd.Test", "test\OpenFeature.Contrib.Providers.Flagd.Test\OpenFeature.Contrib.Providers.Flagd.Test.csproj", "{206323A0-7334-4723-8394-C31C150B95DC}"
1717
EndProject
18-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.GOFeatureFlag", "src\OpenFeature.Contrib.Providers.GOFeatureFlag\OpenFeature.Contrib.Providers.GOFeatureFlag.csproj", "{F7BE205B-0375-4EC5-9B18-FAFEF7A78D71}"
18+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Contrib.Providers.GOFeatureFlag", "src\OpenFeature.Contrib.Providers.GOFeatureFlag\OpenFeature.Contrib.Providers.GOFeatureFlag.csproj", "{F7BE205B-0375-4EC5-9B18-FAFEF7A78D71}"
1919
EndProject
20-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.GOFeatureFlag.Test", "test\OpenFeature.Contrib.Providers.GOFeatureFlag.Test\OpenFeature.Contrib.Providers.GOFeatureFlag.Test.csproj", "{4041B63F-9CF6-4886-8FC7-BD1A7E45F859}"
20+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Contrib.Providers.GOFeatureFlag.Test", "test\OpenFeature.Contrib.Providers.GOFeatureFlag.Test\OpenFeature.Contrib.Providers.GOFeatureFlag.Test.csproj", "{4041B63F-9CF6-4886-8FC7-BD1A7E45F859}"
21+
EndProject
22+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Contrib.Providers.Flagsmith", "src\OpenFeature.Contrib.Providers.Flagsmith\OpenFeature.Contrib.Providers.Flagsmith.csproj", "{47008BEE-7888-4B9B-8884-712A922C3F9B}"
23+
EndProject
24+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.Flagsmith.Test", "test\OpenFeature.Contrib.Providers.Flagsmith.Test\OpenFeature.Contrib.Providers.Flagsmith.Test.csproj", "{C3BA23C2-BEC3-4683-A64A-C914C3D8037E}"
2125
EndProject
2226
Global
2327
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2428
Debug|Any CPU = Debug|Any CPU
2529
Release|Any CPU = Release|Any CPU
2630
EndGlobalSection
27-
GlobalSection(SolutionProperties) = preSolution
28-
HideSolutionNode = FALSE
29-
EndGlobalSection
3031
GlobalSection(ProjectConfigurationPlatforms) = postSolution
3132
{6F8FF25A-F22B-4083-B3F9-B4B9BB6FB699}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
3233
{6F8FF25A-F22B-4083-B3F9-B4B9BB6FB699}.Debug|Any CPU.Build.0 = Debug|Any CPU
@@ -52,6 +53,17 @@ Global
5253
{4041B63F-9CF6-4886-8FC7-BD1A7E45F859}.Debug|Any CPU.Build.0 = Debug|Any CPU
5354
{4041B63F-9CF6-4886-8FC7-BD1A7E45F859}.Release|Any CPU.ActiveCfg = Release|Any CPU
5455
{4041B63F-9CF6-4886-8FC7-BD1A7E45F859}.Release|Any CPU.Build.0 = Release|Any CPU
56+
{47008BEE-7888-4B9B-8884-712A922C3F9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
57+
{47008BEE-7888-4B9B-8884-712A922C3F9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
58+
{47008BEE-7888-4B9B-8884-712A922C3F9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
59+
{47008BEE-7888-4B9B-8884-712A922C3F9B}.Release|Any CPU.Build.0 = Release|Any CPU
60+
{C3BA23C2-BEC3-4683-A64A-C914C3D8037E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
61+
{C3BA23C2-BEC3-4683-A64A-C914C3D8037E}.Debug|Any CPU.Build.0 = Debug|Any CPU
62+
{C3BA23C2-BEC3-4683-A64A-C914C3D8037E}.Release|Any CPU.ActiveCfg = Release|Any CPU
63+
{C3BA23C2-BEC3-4683-A64A-C914C3D8037E}.Release|Any CPU.Build.0 = Release|Any CPU
64+
EndGlobalSection
65+
GlobalSection(SolutionProperties) = preSolution
66+
HideSolutionNode = FALSE
5567
EndGlobalSection
5668
GlobalSection(NestedProjects) = preSolution
5769
{6F8FF25A-F22B-4083-B3F9-B4B9BB6FB699} = {0E563821-BD08-4B7F-BF9D-395CAD80F026}
@@ -60,5 +72,7 @@ Global
6072
{206323A0-7334-4723-8394-C31C150B95DC} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE}
6173
{F7BE205B-0375-4EC5-9B18-FAFEF7A78D71} = {0E563821-BD08-4B7F-BF9D-395CAD80F026}
6274
{4041B63F-9CF6-4886-8FC7-BD1A7E45F859} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE}
75+
{47008BEE-7888-4B9B-8884-712A922C3F9B} = {0E563821-BD08-4B7F-BF9D-395CAD80F026}
76+
{C3BA23C2-BEC3-4683-A64A-C914C3D8037E} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE}
6377
EndGlobalSection
6478
EndGlobal

build/Common.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,6 @@
2525
-->
2626
<MicrosoftSourceLinkGitHubPkgVer>[1.0.0,2.0)</MicrosoftSourceLinkGitHubPkgVer>
2727
<!-- 0.5+ -->
28-
<OpenFeatureVer>[0.5,)</OpenFeatureVer>
28+
<OpenFeatureVer>[1.2,)</OpenFeatureVer>
2929
</PropertyGroup>
3030
</Project>

release-please-config.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@
3131
"extra-files": [
3232
"OpenFeature.Contrib.Providers.GOFeatureFlag.csproj"
3333
]
34+
},
35+
"src/OpenFeature.Contrib.Providers.Flagsmith": {
36+
"package-name": "OpenFeature.Contrib.Providers.Flagsmith",
37+
"release-type": "simple",
38+
"bump-minor-pre-major": true,
39+
"bump-patch-for-minor-pre-major": true,
40+
"versioning": "default",
41+
"extra-files": [
42+
"OpenFeature.Contrib.Providers.Flagsmith.csproj"
43+
]
3444
}
3545
},
3646
"changelog-sections": [
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
using Flagsmith;
2+
using OpenFeature.Constant;
3+
using OpenFeature.Model;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Net.Http;
7+
using System.Text.Json;
8+
using System.Text.Json.Nodes;
9+
using System.Threading.Tasks;
10+
using Trait = Flagsmith.Trait;
11+
using OpenFeature.Error;
12+
using System;
13+
14+
namespace OpenFeature.Contrib.Providers.Flagsmith
15+
{
16+
/// <summary>
17+
/// FlagsmithProvider is the .NET provider implementation for the feature flag solution Flagsmith.
18+
/// </summary>
19+
public class FlagsmithProvider : FeatureProvider
20+
{
21+
private readonly static Metadata Metadata = new("Flagsmith Provider");
22+
internal readonly IFlagsmithClient _flagsmithClient;
23+
/// <summary>
24+
/// Creates new instance of <see cref="FlagsmithProvider"/>
25+
/// </summary>
26+
/// <param name="options">Flagsmith client options. You can just use <see cref="FlagsmithConfiguration"/> class</param>
27+
public FlagsmithProvider(IFlagsmithConfiguration options)
28+
{
29+
_flagsmithClient = new FlagsmithClient(options);
30+
}
31+
32+
/// <summary>
33+
/// Creates new instance of <see cref="FlagsmithProvider"/>
34+
/// </summary>
35+
/// <param name="options">Flagsmith client options. You can just use <see cref="FlagsmithConfiguration"/> class</param>
36+
/// <param name="httpClient">Http client that will be used for flagsmith requests. You also can use it to register <see cref="FeatureProvider"/> as Typed HttpClient with <see cref="FeatureProvider"> as abstraction</see></param>
37+
public FlagsmithProvider(IFlagsmithConfiguration options, HttpClient httpClient)
38+
{
39+
_flagsmithClient = new FlagsmithClient(options, httpClient);
40+
}
41+
42+
43+
/// <summary>
44+
/// Creates new instance of <see cref="FlagsmithProvider"/>
45+
/// </summary>
46+
/// <param name="flagsmithClient">Precreated Flagsmith client. You can just use <see cref="FlagsmithClient"/> class.</param>
47+
public FlagsmithProvider(IFlagsmithClient flagsmithClient)
48+
{
49+
_flagsmithClient = flagsmithClient;
50+
}
51+
52+
private Task<IFlags> GetFlags(EvaluationContext ctx = null)
53+
{
54+
var key = ctx?.GetValue("targetingKey")?.AsString;
55+
return string.IsNullOrEmpty(key)
56+
? _flagsmithClient.GetEnvironmentFlags()
57+
: _flagsmithClient.GetIdentityFlags(key, ctx.AsDictionary().Select(x => new Trait(x.Key, x.Value.AsObject) as ITrait).ToList());
58+
}
59+
60+
61+
/// <inheritdoc/>
62+
public override Metadata GetMetadata() => Metadata;
63+
64+
/// <inheritdoc/>
65+
66+
public override async Task<ResolutionDetails<bool>> ResolveBooleanValue(string flagKey, bool defaultValue, EvaluationContext context = null)
67+
{
68+
var flags = await GetFlags(context);
69+
var isFlagEnabled = await flags.IsFeatureEnabled(flagKey);
70+
if (!isFlagEnabled)
71+
{
72+
return new ResolutionDetails<bool>(flagKey, defaultValue, reason: Reason.Disabled);
73+
}
74+
75+
var stringValue = await flags.GetFeatureValue(flagKey);
76+
if (bool.TryParse(stringValue, out var parsedValue))
77+
{
78+
return new ResolutionDetails<bool>(flagKey, parsedValue);
79+
}
80+
throw new TypeMismatchException("Failed to parse value in boolean type");
81+
82+
}
83+
84+
/// <inheritdoc/>
85+
86+
public override async Task<ResolutionDetails<string>> ResolveStringValue(string flagKey, string defaultValue, EvaluationContext context = null)
87+
{
88+
89+
var flags = await GetFlags(context);
90+
var isFlagEnabled = await flags.IsFeatureEnabled(flagKey);
91+
if (!isFlagEnabled)
92+
{
93+
return new ResolutionDetails<string>(flagKey, defaultValue, reason: Reason.Disabled);
94+
}
95+
96+
var stringValue = await flags.GetFeatureValue(flagKey);
97+
return new ResolutionDetails<string>(flagKey, stringValue);
98+
}
99+
100+
/// <inheritdoc/>
101+
public override async Task<ResolutionDetails<int>> ResolveIntegerValue(string flagKey, int defaultValue, EvaluationContext context = null)
102+
{
103+
104+
var flags = await GetFlags(context);
105+
var isFlagEnabled = await flags.IsFeatureEnabled(flagKey);
106+
if (!isFlagEnabled)
107+
{
108+
return new ResolutionDetails<int>(flagKey, defaultValue, reason: Reason.Disabled);
109+
}
110+
111+
var stringValue = await flags.GetFeatureValue(flagKey);
112+
if(int.TryParse(stringValue, out var parsedValue))
113+
{
114+
return new ResolutionDetails<int>(flagKey, parsedValue);
115+
}
116+
throw new TypeMismatchException("Failed to parse value in int type");
117+
}
118+
119+
/// <inheritdoc/>
120+
public override async Task<ResolutionDetails<double>> ResolveDoubleValue(string flagKey, double defaultValue, EvaluationContext context = null)
121+
{
122+
123+
var flags = await GetFlags(context);
124+
var isFlagEnabled = await flags.IsFeatureEnabled(flagKey);
125+
if (!isFlagEnabled)
126+
{
127+
return new ResolutionDetails<double>(flagKey, defaultValue, reason: Reason.Disabled);
128+
}
129+
130+
var stringValue = await flags.GetFeatureValue(flagKey);
131+
if (double.TryParse(stringValue, out var parsedValue))
132+
{
133+
return new ResolutionDetails<double>(flagKey, parsedValue);
134+
}
135+
throw new TypeMismatchException("Failed to parse value in double type");
136+
}
137+
138+
139+
/// <inheritdoc/>
140+
public override async Task<ResolutionDetails<Value>> ResolveStructureValue(string flagKey, Value defaultValue, EvaluationContext context = null)
141+
{
142+
143+
var flags = await GetFlags(context);
144+
var isFlagEnabled = await flags.IsFeatureEnabled(flagKey);
145+
if (!isFlagEnabled)
146+
{
147+
return new ResolutionDetails<Value>(flagKey, defaultValue, reason: Reason.Disabled);
148+
}
149+
var stringValue = await flags.GetFeatureValue(flagKey);
150+
151+
try
152+
{
153+
var mappedValue = JsonNode.Parse(stringValue);
154+
var value = ConvertValue(mappedValue);
155+
if (value is not null)
156+
{
157+
return new ResolutionDetails<Value>(flagKey, value);
158+
159+
}
160+
}
161+
catch(Exception ex)
162+
{
163+
throw new TypeMismatchException("Failed to parse value in structure type", ex);
164+
}
165+
throw new TypeMismatchException("Failed to parse value in structure type");
166+
}
167+
168+
/// <summary>
169+
/// convertValue is converting the object return by the proxy response in the right type.
170+
/// </summary>
171+
/// <param name="node">The value we have received</param>
172+
/// <returns>A converted object</returns>
173+
private Value ConvertValue(JsonNode node)
174+
{
175+
if(node == null)
176+
return null;
177+
if (node is JsonArray jsonArray)
178+
{
179+
var arr = new List<Value>();
180+
foreach (var item in jsonArray)
181+
{
182+
var convertedValue = ConvertValue(item);
183+
if (convertedValue != null) arr.Add(convertedValue);
184+
}
185+
return new Value(arr);
186+
}
187+
188+
if (node is JsonObject jsonObject)
189+
{
190+
var dict = jsonObject.ToDictionary(x => x.Key, x => ConvertValue(x.Value));
191+
192+
return new Value(new Structure(dict));
193+
}
194+
195+
if (node.AsValue().TryGetValue<JsonElement>(out var jsonElement))
196+
{
197+
if (jsonElement.ValueKind == JsonValueKind.False || jsonElement.ValueKind == JsonValueKind.True)
198+
return new Value(jsonElement.GetBoolean());
199+
if (jsonElement.ValueKind == JsonValueKind.Number)
200+
return new Value(jsonElement.GetDouble());
201+
202+
if (jsonElement.ValueKind == JsonValueKind.String)
203+
return new Value(jsonElement.ToString());
204+
}
205+
return null;
206+
}
207+
}
208+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>netstandard20</TargetFrameworks>
5+
<PackageId>OpenFeature.Contrib.Providers.Flagsmith</PackageId>
6+
<VersionNumber>0.1.0</VersionNumber>
7+
<!--x-release-please-version -->
8+
<Version>$(VersionNumber)</Version>
9+
<AssemblyVersion>$(VersionNumber)</AssemblyVersion>
10+
<FileVersion>$(VersionNumber)</FileVersion>
11+
<Description>Flagsmith provider for .NET</Description>
12+
<PackageProjectUrl>https://openfeature.dev</PackageProjectUrl>
13+
<RepositoryUrl>https://github.com/open-feature/dotnet-sdk-contrib</RepositoryUrl>
14+
<Authors>Vladimir Petrusevici</Authors>
15+
</PropertyGroup>
16+
17+
<ItemGroup>
18+
<!-- make the internal methods visble to our test project -->
19+
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
20+
<_Parameter1>$(MSBuildProjectName).Test</_Parameter1>
21+
</AssemblyAttribute>
22+
</ItemGroup>
23+
24+
<ItemGroup>
25+
<PackageReference Include="Flagsmith" Version="5.1.0" />
26+
<PackageReference Include="System.Text.Json" Version="7.0.3" />
27+
</ItemGroup>
28+
29+
<PropertyGroup>
30+
<LangVersion>latest</LangVersion>
31+
</PropertyGroup>
32+
</Project>
15.3 KB
Loading

0 commit comments

Comments
 (0)