11using System ;
22using System . Collections . Generic ;
3+ using System . Collections . Immutable ;
34using System . Globalization ;
45using System . Net ;
56using System . Net . Http ;
910using System . Threading ;
1011using System . Threading . Tasks ;
1112using OpenFeature . Constant ;
13+ using OpenFeature . Contrib . Providers . GOFeatureFlag . converters ;
1214using 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 ;
1318using OpenFeature . Model ;
1419
1520namespace OpenFeature . Contrib . Providers . GOFeatureFlag
@@ -20,8 +25,8 @@ namespace OpenFeature.Contrib.Providers.GOFeatureFlag
2025 public class GoFeatureFlagProvider : FeatureProvider
2126 {
2227 private const string ApplicationJson = "application/json" ;
28+ private ExporterMetadata _exporterMetadata ;
2329 private HttpClient _httpClient ;
24- private JsonSerializerOptions _serializerOptions ;
2530
2631 /// <summary>
2732 /// Constructor of the provider.
@@ -34,6 +39,17 @@ public GoFeatureFlagProvider(GoFeatureFlagProviderOptions options)
3439 InitializeProvider ( options ) ;
3540 }
3641
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+
3753 /// <summary>
3854 /// validateInputOptions is validating the different options provided when creating the provider.
3955 /// </summary>
@@ -53,6 +69,10 @@ private void ValidateInputOptions(GoFeatureFlagProviderOptions options)
5369 /// <param name="options">Options used while creating the provider</param>
5470 private void InitializeProvider ( GoFeatureFlagProviderOptions options )
5571 {
72+ _exporterMetadata = options . ExporterMetadata ?? new ExporterMetadata ( ) ;
73+ _exporterMetadata . Add ( "provider" , ".NET" ) ;
74+ _exporterMetadata . Add ( "openfeature" , true ) ;
75+
5676 _httpClient = options . HttpMessageHandler != null
5777 ? new HttpClient ( options . HttpMessageHandler )
5878 : new HttpClient
@@ -63,7 +83,6 @@ private void InitializeProvider(GoFeatureFlagProviderOptions options)
6383 } ;
6484 _httpClient . DefaultRequestHeaders . Accept . Add ( new MediaTypeWithQualityHeaderValue ( ApplicationJson ) ) ;
6585 _httpClient . BaseAddress = new Uri ( options . Endpoint ) ;
66- _serializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy . CamelCase } ;
6786
6887 if ( options . ApiKey != null )
6988 _httpClient . DefaultRequestHeaders . Authorization =
@@ -96,8 +115,8 @@ public override async Task<ResolutionDetails<bool>> ResolveBooleanValueAsync(str
96115 try
97116 {
98117 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 ( ) ) ;
101120 }
102121 catch ( FormatException e )
103122 {
@@ -121,16 +140,17 @@ public override async Task<ResolutionDetails<bool>> ResolveBooleanValueAsync(str
121140 /// <exception cref="FlagNotFoundError">If the flag does not exists</exception>
122141 /// <exception cref="GeneralError">If an unknown error happen</exception>
123142 /// <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 ,
125145 EvaluationContext context = null , CancellationToken cancellationToken = default )
126146 {
127147 try
128148 {
129149 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 ) )
131151 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 ( ) ) ;
134154 }
135155 catch ( FormatException e )
136156 {
@@ -160,8 +180,8 @@ public override async Task<ResolutionDetails<int>> ResolveIntegerValueAsync(stri
160180 try
161181 {
162182 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 ( ) ) ;
165185 }
166186 catch ( FormatException e )
167187 {
@@ -185,15 +205,16 @@ public override async Task<ResolutionDetails<int>> ResolveIntegerValueAsync(stri
185205 /// <exception cref="FlagNotFoundError">If the flag does not exists</exception>
186206 /// <exception cref="GeneralError">If an unknown error happen</exception>
187207 /// <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 ,
189210 EvaluationContext context = null , CancellationToken cancellationToken = default )
190211 {
191212 try
192213 {
193214 var resp = await CallApi ( flagKey , defaultValue , context ) ;
194215 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 ( ) ) ;
197218 }
198219 catch ( FormatException e )
199220 {
@@ -217,17 +238,18 @@ public override async Task<ResolutionDetails<double>> ResolveDoubleValueAsync(st
217238 /// <exception cref="FlagNotFoundError">If the flag does not exists</exception>
218239 /// <exception cref="GeneralError">If an unknown error happen</exception>
219240 /// <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 ,
221243 EvaluationContext context = null , CancellationToken cancellationToken = default )
222244 {
223245 try
224246 {
225247 var resp = await CallApi ( flagKey , defaultValue , context ) ;
226- if ( resp . value is JsonElement )
248+ if ( resp . Value is JsonElement )
227249 {
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 ( ) ) ;
231253 }
232254
233255 throw new TypeMismatchError ( $ "flag value { flagKey } had unexpected type") ;
@@ -253,39 +275,40 @@ public override async Task<ResolutionDetails<Value>> ResolveStructureValueAsync(
253275 /// <exception cref="FlagNotFoundError">If the flag does not exists</exception>
254276 /// <exception cref="GeneralError">If an unknown error happen</exception>
255277 /// <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 ,
257279 EvaluationContext context = null )
258280 {
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 ) ) ;
268284
269285 if ( response . StatusCode == HttpStatusCode . NotFound )
270286 throw new FlagNotFoundError ( $ "flag { flagKey } was not found in your configuration") ;
271287
272- if ( response . StatusCode == HttpStatusCode . Unauthorized )
288+ if ( response . StatusCode == HttpStatusCode . Unauthorized || response . StatusCode == HttpStatusCode . Forbidden )
273289 throw new UnauthorizedError ( "invalid token used to contact GO Feature Flag relay proxy instance" ) ;
274290
275291 if ( response . StatusCode >= HttpStatusCode . BadRequest )
276292 throw new GeneralError ( "impossible to contact GO Feature Flag relay proxy instance" ) ;
277293
278294 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 ) ;
281301
282- if ( goffResp != null && Reason . Disabled . Equals ( goffResp . reason ) )
302+ if ( Reason . Disabled . Equals ( ofrepResp ? . Reason ) )
283303 throw new FlagDisabled ( ) ;
284304
285- if ( "FLAG_NOT_FOUND" . Equals ( goffResp . errorCode ) )
305+ if ( "FLAG_NOT_FOUND" . Equals ( ofrepResp ? . ErrorCode ) )
286306 throw new FlagNotFoundError ( $ "flag { flagKey } was not found in your configuration") ;
287307
288- return goffResp ;
308+ if ( ofrepResp ? . Metadata != null )
309+ ofrepResp . Metadata = DictionaryConverter . ConvertDictionary ( ofrepResp . Metadata ) ;
310+
311+ return ofrepResp ;
289312 }
290313
291314 /// <summary>
@@ -337,4 +360,4 @@ private Value ConvertValue(JsonElement value)
337360 throw new ImpossibleToConvertTypeError ( $ "impossible to convert the object { value } ") ;
338361 }
339362 }
340- }
363+ }
0 commit comments