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