1
1
using System ;
2
2
using System . Collections . Generic ;
3
+ using System . Collections . Immutable ;
3
4
using System . Globalization ;
4
5
using System . Net ;
5
6
using System . Net . Http ;
9
10
using System . Threading ;
10
11
using System . Threading . Tasks ;
11
12
using OpenFeature . Constant ;
13
+ using OpenFeature . Contrib . Providers . GOFeatureFlag . converters ;
12
14
using OpenFeature . Contrib . Providers . GOFeatureFlag . exception ;
15
+ using OpenFeature . Contrib . Providers . GOFeatureFlag . extensions ;
16
+ using OpenFeature . Contrib . Providers . GOFeatureFlag . hooks ;
17
+ using OpenFeature . Contrib . Providers . GOFeatureFlag . models ;
13
18
using OpenFeature . Model ;
14
19
15
20
namespace OpenFeature . Contrib . Providers . GOFeatureFlag
@@ -20,8 +25,8 @@ namespace OpenFeature.Contrib.Providers.GOFeatureFlag
20
25
public class GoFeatureFlagProvider : FeatureProvider
21
26
{
22
27
private const string ApplicationJson = "application/json" ;
28
+ private ExporterMetadata _exporterMetadata ;
23
29
private HttpClient _httpClient ;
24
- private JsonSerializerOptions _serializerOptions ;
25
30
26
31
/// <summary>
27
32
/// Constructor of the provider.
@@ -34,6 +39,17 @@ public GoFeatureFlagProvider(GoFeatureFlagProviderOptions options)
34
39
InitializeProvider ( options ) ;
35
40
}
36
41
42
+ /// <summary>
43
+ /// List of hooks to use for this provider
44
+ /// </summary>
45
+ /// <returns></returns>
46
+ public override IImmutableList < Hook > GetProviderHooks ( )
47
+ {
48
+ var hooks = ImmutableArray . CreateBuilder < Hook > ( ) ;
49
+ hooks . Add ( new EnrichEvaluationContextHook ( _exporterMetadata ) ) ;
50
+ return hooks . ToImmutable ( ) ;
51
+ }
52
+
37
53
/// <summary>
38
54
/// validateInputOptions is validating the different options provided when creating the provider.
39
55
/// </summary>
@@ -53,6 +69,10 @@ private void ValidateInputOptions(GoFeatureFlagProviderOptions options)
53
69
/// <param name="options">Options used while creating the provider</param>
54
70
private void InitializeProvider ( GoFeatureFlagProviderOptions options )
55
71
{
72
+ _exporterMetadata = options . ExporterMetadata ?? new ExporterMetadata ( ) ;
73
+ _exporterMetadata . Add ( "provider" , ".NET" ) ;
74
+ _exporterMetadata . Add ( "openfeature" , true ) ;
75
+
56
76
_httpClient = options . HttpMessageHandler != null
57
77
? new HttpClient ( options . HttpMessageHandler )
58
78
: new HttpClient
@@ -63,7 +83,6 @@ private void InitializeProvider(GoFeatureFlagProviderOptions options)
63
83
} ;
64
84
_httpClient . DefaultRequestHeaders . Accept . Add ( new MediaTypeWithQualityHeaderValue ( ApplicationJson ) ) ;
65
85
_httpClient . BaseAddress = new Uri ( options . Endpoint ) ;
66
- _serializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy . CamelCase } ;
67
86
68
87
if ( options . ApiKey != null )
69
88
_httpClient . DefaultRequestHeaders . Authorization =
@@ -96,8 +115,8 @@ public override async Task<ResolutionDetails<bool>> ResolveBooleanValueAsync(str
96
115
try
97
116
{
98
117
var resp = await CallApi ( flagKey , defaultValue , context ) ;
99
- return new ResolutionDetails < bool > ( flagKey , bool . Parse ( resp . value . ToString ( ) ) , ErrorType . None ,
100
- resp . reason , resp . variationType ) ;
118
+ return new ResolutionDetails < bool > ( flagKey , bool . Parse ( resp . Value . ToString ( ) ) , ErrorType . None ,
119
+ resp . Reason , resp . Variant , resp . ErrorDetails , resp . Metadata . ToImmutableMetadata ( ) ) ;
101
120
}
102
121
catch ( FormatException e )
103
122
{
@@ -121,16 +140,17 @@ public override async Task<ResolutionDetails<bool>> ResolveBooleanValueAsync(str
121
140
/// <exception cref="FlagNotFoundError">If the flag does not exists</exception>
122
141
/// <exception cref="GeneralError">If an unknown error happen</exception>
123
142
/// <exception cref="FlagDisabled">If the flag is disabled</exception>
124
- public override async Task < ResolutionDetails < string > > ResolveStringValueAsync ( string flagKey , string defaultValue ,
143
+ public override async Task < ResolutionDetails < string > > ResolveStringValueAsync ( string flagKey ,
144
+ string defaultValue ,
125
145
EvaluationContext context = null , CancellationToken cancellationToken = default )
126
146
{
127
147
try
128
148
{
129
149
var resp = await CallApi ( flagKey , defaultValue , context ) ;
130
- if ( ! ( resp . value is JsonElement element && element . ValueKind == JsonValueKind . String ) )
150
+ if ( ! ( resp . Value is JsonElement element && element . ValueKind == JsonValueKind . String ) )
131
151
throw new TypeMismatchError ( $ "flag value { flagKey } had unexpected type") ;
132
- return new ResolutionDetails < string > ( flagKey , resp . value . ToString ( ) , ErrorType . None , resp . reason ,
133
- resp . variationType ) ;
152
+ return new ResolutionDetails < string > ( flagKey , resp . Value . ToString ( ) , ErrorType . None , resp . Reason ,
153
+ resp . Variant , resp . ErrorDetails , resp . Metadata . ToImmutableMetadata ( ) ) ;
134
154
}
135
155
catch ( FormatException e )
136
156
{
@@ -160,8 +180,8 @@ public override async Task<ResolutionDetails<int>> ResolveIntegerValueAsync(stri
160
180
try
161
181
{
162
182
var resp = await CallApi ( flagKey , defaultValue , context ) ;
163
- return new ResolutionDetails < int > ( flagKey , int . Parse ( resp . value . ToString ( ) ) , ErrorType . None ,
164
- resp . reason , resp . variationType ) ;
183
+ return new ResolutionDetails < int > ( flagKey , int . Parse ( resp . Value . ToString ( ) ) , ErrorType . None ,
184
+ resp . Reason , resp . Variant , resp . ErrorDetails , resp . Metadata . ToImmutableMetadata ( ) ) ;
165
185
}
166
186
catch ( FormatException e )
167
187
{
@@ -185,15 +205,16 @@ public override async Task<ResolutionDetails<int>> ResolveIntegerValueAsync(stri
185
205
/// <exception cref="FlagNotFoundError">If the flag does not exists</exception>
186
206
/// <exception cref="GeneralError">If an unknown error happen</exception>
187
207
/// <exception cref="FlagDisabled">If the flag is disabled</exception>
188
- public override async Task < ResolutionDetails < double > > ResolveDoubleValueAsync ( string flagKey , double defaultValue ,
208
+ public override async Task < ResolutionDetails < double > > ResolveDoubleValueAsync ( string flagKey ,
209
+ double defaultValue ,
189
210
EvaluationContext context = null , CancellationToken cancellationToken = default )
190
211
{
191
212
try
192
213
{
193
214
var resp = await CallApi ( flagKey , defaultValue , context ) ;
194
215
return new ResolutionDetails < double > ( flagKey ,
195
- double . Parse ( resp . value . ToString ( ) , CultureInfo . InvariantCulture ) , ErrorType . None ,
196
- resp . reason , resp . variationType ) ;
216
+ double . Parse ( resp . Value . ToString ( ) , CultureInfo . InvariantCulture ) , ErrorType . None ,
217
+ resp . Reason , resp . Variant , resp . ErrorDetails , resp . Metadata . ToImmutableMetadata ( ) ) ;
197
218
}
198
219
catch ( FormatException e )
199
220
{
@@ -217,17 +238,18 @@ public override async Task<ResolutionDetails<double>> ResolveDoubleValueAsync(st
217
238
/// <exception cref="FlagNotFoundError">If the flag does not exists</exception>
218
239
/// <exception cref="GeneralError">If an unknown error happen</exception>
219
240
/// <exception cref="FlagDisabled">If the flag is disabled</exception>
220
- public override async Task < ResolutionDetails < Value > > ResolveStructureValueAsync ( string flagKey , Value defaultValue ,
241
+ public override async Task < ResolutionDetails < Value > > ResolveStructureValueAsync ( string flagKey ,
242
+ Value defaultValue ,
221
243
EvaluationContext context = null , CancellationToken cancellationToken = default )
222
244
{
223
245
try
224
246
{
225
247
var resp = await CallApi ( flagKey , defaultValue , context ) ;
226
- if ( resp . value is JsonElement )
248
+ if ( resp . Value is JsonElement )
227
249
{
228
- var value = ConvertValue ( ( JsonElement ) resp . value ) ;
229
- return new ResolutionDetails < Value > ( flagKey , value , ErrorType . None , resp . reason ,
230
- resp . variationType ) ;
250
+ var value = ConvertValue ( ( JsonElement ) resp . Value ) ;
251
+ return new ResolutionDetails < Value > ( flagKey , value , ErrorType . None , resp . Reason ,
252
+ resp . Variant , resp . ErrorDetails , resp . Metadata . ToImmutableMetadata ( ) ) ;
231
253
}
232
254
233
255
throw new TypeMismatchError ( $ "flag value { flagKey } had unexpected type") ;
@@ -253,39 +275,40 @@ public override async Task<ResolutionDetails<Value>> ResolveStructureValueAsync(
253
275
/// <exception cref="FlagNotFoundError">If the flag does not exists</exception>
254
276
/// <exception cref="GeneralError">If an unknown error happen</exception>
255
277
/// <exception cref="FlagDisabled">If the flag is disabled</exception>
256
- private async Task < GoFeatureFlagResponse > CallApi < T > ( string flagKey , T defaultValue ,
278
+ private async Task < OfrepResponse > CallApi < T > ( string flagKey , T defaultValue ,
257
279
EvaluationContext context = null )
258
280
{
259
- var request = new GOFeatureFlagRequest < T >
260
- {
261
- User = context ,
262
- DefaultValue = defaultValue
263
- } ;
264
- var goffRequest = JsonSerializer . Serialize ( request , _serializerOptions ) ;
265
-
266
- var response = await _httpClient . PostAsync ( $ "v1/feature/{ flagKey } /eval",
267
- new StringContent ( goffRequest , Encoding . UTF8 , ApplicationJson ) ) ;
281
+ var request = new OfrepRequest ( context ) ;
282
+ var response = await _httpClient . PostAsync ( $ "ofrep/v1/evaluate/flags/{ flagKey } ",
283
+ new StringContent ( request . AsJsonString ( ) , Encoding . UTF8 , ApplicationJson ) ) ;
268
284
269
285
if ( response . StatusCode == HttpStatusCode . NotFound )
270
286
throw new FlagNotFoundError ( $ "flag { flagKey } was not found in your configuration") ;
271
287
272
- if ( response . StatusCode == HttpStatusCode . Unauthorized )
288
+ if ( response . StatusCode == HttpStatusCode . Unauthorized || response . StatusCode == HttpStatusCode . Forbidden )
273
289
throw new UnauthorizedError ( "invalid token used to contact GO Feature Flag relay proxy instance" ) ;
274
290
275
291
if ( response . StatusCode >= HttpStatusCode . BadRequest )
276
292
throw new GeneralError ( "impossible to contact GO Feature Flag relay proxy instance" ) ;
277
293
278
294
var responseBody = await response . Content . ReadAsStringAsync ( ) ;
279
- var goffResp =
280
- JsonSerializer . Deserialize < GoFeatureFlagResponse > ( responseBody ) ;
295
+ var options = new JsonSerializerOptions
296
+ {
297
+ PropertyNameCaseInsensitive = true
298
+ } ;
299
+ var ofrepResp =
300
+ JsonSerializer . Deserialize < OfrepResponse > ( responseBody , options ) ;
281
301
282
- if ( goffResp != null && Reason . Disabled . Equals ( goffResp . reason ) )
302
+ if ( Reason . Disabled . Equals ( ofrepResp ? . Reason ) )
283
303
throw new FlagDisabled ( ) ;
284
304
285
- if ( "FLAG_NOT_FOUND" . Equals ( goffResp . errorCode ) )
305
+ if ( "FLAG_NOT_FOUND" . Equals ( ofrepResp ? . ErrorCode ) )
286
306
throw new FlagNotFoundError ( $ "flag { flagKey } was not found in your configuration") ;
287
307
288
- return goffResp ;
308
+ if ( ofrepResp ? . Metadata != null )
309
+ ofrepResp . Metadata = DictionaryConverter . ConvertDictionary ( ofrepResp . Metadata ) ;
310
+
311
+ return ofrepResp ;
289
312
}
290
313
291
314
/// <summary>
@@ -337,4 +360,4 @@ private Value ConvertValue(JsonElement value)
337
360
throw new ImpossibleToConvertTypeError ( $ "impossible to convert the object { value } ") ;
338
361
}
339
362
}
340
- }
363
+ }
0 commit comments