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
+ }
0 commit comments