2
2
using System . Collections . Generic ;
3
3
using System . Collections . Immutable ;
4
4
using System . Linq ;
5
+ using System . Text . RegularExpressions ;
5
6
using JsonLogic . Net ;
6
7
using Newtonsoft . Json ;
7
8
using Newtonsoft . Json . Linq ;
8
9
using OpenFeature . Constant ;
9
10
using OpenFeature . Contrib . Providers . Flagd . Resolver . InProcess . CustomEvaluators ;
10
11
using OpenFeature . Error ;
11
12
using OpenFeature . Model ;
12
- using System . Text . RegularExpressions ;
13
13
14
14
namespace OpenFeature . Contrib . Providers . Flagd . Resolver . InProcess
15
15
{
16
-
17
16
internal class FlagConfiguration
18
17
{
19
- [ JsonProperty ( "state" ) ]
20
- internal string State { get ; set ; }
21
- [ JsonProperty ( "defaultVariant" ) ]
22
- internal string DefaultVariant { get ; set ; }
23
- [ JsonProperty ( "variants" ) ]
24
- internal Dictionary < string , object > Variants { get ; set ; }
25
- [ JsonProperty ( "targeting" ) ]
26
- internal object Targeting { get ; set ; }
27
- [ JsonProperty ( "source" ) ]
28
- internal string Source { get ; set ; }
18
+ [ JsonProperty ( "state" ) ] internal string State { get ; set ; }
19
+ [ JsonProperty ( "defaultVariant" ) ] internal string DefaultVariant { get ; set ; }
20
+ [ JsonProperty ( "variants" ) ] internal Dictionary < string , object > Variants { get ; set ; }
21
+ [ JsonProperty ( "targeting" ) ] internal object Targeting { get ; set ; }
22
+ [ JsonProperty ( "source" ) ] internal string Source { get ; set ; }
23
+ [ JsonProperty ( "metadata" ) ] internal Dictionary < string , object > Metadata { get ; set ; }
29
24
}
30
25
31
26
internal class FlagSyncData
32
27
{
33
- [ JsonProperty ( "flags" ) ]
34
- internal Dictionary < string , FlagConfiguration > Flags { get ; set ; }
35
- [ JsonProperty ( "$evaluators" ) ]
36
- internal Dictionary < string , object > Evaluators { get ; set ; }
28
+ [ JsonProperty ( "flags" ) ] internal Dictionary < string , FlagConfiguration > Flags { get ; set ; }
29
+ [ JsonProperty ( "$evaluators" ) ] internal Dictionary < string , object > Evaluators { get ; set ; }
30
+ [ JsonProperty ( "metadata" ) ] internal Dictionary < string , object > Metadata { get ; set ; }
37
31
}
38
32
39
33
internal class FlagConfigurationSync
@@ -53,6 +47,7 @@ internal enum FlagConfigurationUpdateType
53
47
internal class JsonEvaluator
54
48
{
55
49
private Dictionary < string , FlagConfiguration > _flags = new Dictionary < string , FlagConfiguration > ( ) ;
50
+ private Dictionary < string , object > _flagSetMetadata = new Dictionary < string , object > ( ) ;
56
51
57
52
private string _selector ;
58
53
@@ -88,7 +83,57 @@ internal FlagSyncData Parse(string flagConfigurations)
88
83
} ) ;
89
84
}
90
85
91
- return JsonConvert . DeserializeObject < FlagSyncData > ( transformed ) ;
86
+
87
+ var data = JsonConvert . DeserializeObject < FlagSyncData > ( transformed ) ;
88
+ if ( data . Metadata == null )
89
+ {
90
+ data . Metadata = new Dictionary < string , object > ( ) ;
91
+ }
92
+ else
93
+ {
94
+ foreach ( var key in new List < string > ( data . Metadata . Keys ) )
95
+ {
96
+ var value = data . Metadata [ key ] ;
97
+ if ( value is long longValue )
98
+ {
99
+ value = data . Metadata [ key ] = ( int ) longValue ;
100
+ }
101
+
102
+ VerifyMetadataValue ( key , value ) ;
103
+ }
104
+ }
105
+
106
+ foreach ( var flagConfig in data . Flags )
107
+ {
108
+ if ( flagConfig . Value . Metadata == null )
109
+ {
110
+ continue ;
111
+ }
112
+
113
+ foreach ( var key in new List < string > ( flagConfig . Value . Metadata . Keys ) )
114
+ {
115
+ var value = flagConfig . Value . Metadata [ key ] ;
116
+ if ( value is long longValue )
117
+ {
118
+ value = flagConfig . Value . Metadata [ key ] = ( int ) longValue ;
119
+ }
120
+
121
+ VerifyMetadataValue ( key , value ) ;
122
+ }
123
+ }
124
+
125
+ return data ;
126
+ }
127
+
128
+ private static void VerifyMetadataValue ( string key , object value )
129
+ {
130
+ if ( value is int || value is double || value is string || value is bool )
131
+ {
132
+ return ;
133
+ }
134
+
135
+ throw new ParseErrorException ( "Metadata entry for key " + key + " and value " + value +
136
+ " is of unknown type" ) ;
92
137
}
93
138
94
139
internal void Sync ( FlagConfigurationUpdateType updateType , string flagConfigurations )
@@ -99,71 +144,100 @@ internal void Sync(FlagConfigurationUpdateType updateType, string flagConfigurat
99
144
{
100
145
case FlagConfigurationUpdateType . ALL :
101
146
_flags = flagConfigsMap . Flags ;
147
+ _flagSetMetadata = flagConfigsMap . Metadata ;
148
+
102
149
break ;
103
150
case FlagConfigurationUpdateType . ADD :
151
+ case FlagConfigurationUpdateType . UPDATE :
104
152
foreach ( var keyAndValue in flagConfigsMap . Flags )
105
153
{
106
154
_flags [ keyAndValue . Key ] = keyAndValue . Value ;
107
155
}
108
- break ;
109
- case FlagConfigurationUpdateType . UPDATE :
110
- foreach ( var keyAndValue in flagConfigsMap . Flags )
156
+
157
+ foreach ( var metadata in flagConfigsMap . Metadata )
111
158
{
112
- _flags [ keyAndValue . Key ] = keyAndValue . Value ;
159
+ _flagSetMetadata [ metadata . Key ] = metadata . Value ;
113
160
}
161
+
114
162
break ;
115
163
case FlagConfigurationUpdateType . DELETE :
116
164
foreach ( var keyAndValue in flagConfigsMap . Flags )
117
165
{
118
166
_flags . Remove ( keyAndValue . Key ) ;
119
167
}
120
- break ;
121
168
169
+ foreach ( var keyValuePair in flagConfigsMap . Metadata )
170
+ {
171
+ _flagSetMetadata . Remove ( keyValuePair . Key ) ;
172
+ }
173
+
174
+ break ;
122
175
}
123
176
}
124
177
125
- public ResolutionDetails < bool > ResolveBooleanValueAsync ( string flagKey , bool defaultValue , EvaluationContext context = null )
178
+ public ResolutionDetails < bool > ResolveBooleanValueAsync ( string flagKey , bool defaultValue ,
179
+ EvaluationContext context = null )
126
180
{
127
181
return ResolveValue ( flagKey , defaultValue , context ) ;
128
182
}
129
183
130
- public ResolutionDetails < string > ResolveStringValueAsync ( string flagKey , string defaultValue , EvaluationContext context = null )
184
+ public ResolutionDetails < string > ResolveStringValueAsync ( string flagKey , string defaultValue ,
185
+ EvaluationContext context = null )
131
186
{
132
187
return ResolveValue ( flagKey , defaultValue , context ) ;
133
188
}
134
189
135
- public ResolutionDetails < int > ResolveIntegerValueAsync ( string flagKey , int defaultValue , EvaluationContext context = null )
190
+ public ResolutionDetails < int > ResolveIntegerValueAsync ( string flagKey , int defaultValue ,
191
+ EvaluationContext context = null )
136
192
{
137
193
return ResolveValue ( flagKey , defaultValue , context ) ;
138
194
}
139
195
140
- public ResolutionDetails < double > ResolveDoubleValueAsync ( string flagKey , double defaultValue , EvaluationContext context = null )
196
+ public ResolutionDetails < double > ResolveDoubleValueAsync ( string flagKey , double defaultValue ,
197
+ EvaluationContext context = null )
141
198
{
142
199
return ResolveValue ( flagKey , defaultValue , context ) ;
143
200
}
144
201
145
- public ResolutionDetails < Value > ResolveStructureValueAsync ( string flagKey , Value defaultValue , EvaluationContext context = null )
202
+ public ResolutionDetails < Value > ResolveStructureValueAsync ( string flagKey , Value defaultValue ,
203
+ EvaluationContext context = null )
146
204
{
147
205
return ResolveValue ( flagKey , defaultValue , context ) ;
148
206
}
149
207
150
- private ResolutionDetails < T > ResolveValue < T > ( string flagKey , T defaultValue , EvaluationContext context = null )
208
+ private ResolutionDetails < T > ResolveValue < T > ( string flagKey , T defaultValue ,
209
+ EvaluationContext context = null )
151
210
{
152
211
// check if we find the flag key
153
212
var reason = Reason . Static ;
154
213
if ( _flags . TryGetValue ( flagKey , out var flagConfiguration ) )
155
214
{
156
215
if ( "DISABLED" == flagConfiguration . State )
157
216
{
158
- throw new FeatureProviderException ( ErrorType . FlagNotFound , "FLAG_NOT_FOUND: flag '" + flagKey + "' is disabled" ) ;
217
+ throw new FeatureProviderException ( ErrorType . FlagNotFound ,
218
+ "FLAG_NOT_FOUND: flag '" + flagKey + "' is disabled" ) ;
219
+ }
220
+
221
+ Dictionary < string , object > combinedMetadata = new Dictionary < string , object > ( _flagSetMetadata ) ;
222
+ if ( flagConfiguration . Metadata != null )
223
+ {
224
+ foreach ( var metadataEntry in flagConfiguration . Metadata )
225
+ {
226
+ combinedMetadata [ metadataEntry . Key ] = metadataEntry . Value ;
227
+ }
159
228
}
229
+
230
+ var flagMetadata = new ImmutableMetadata ( combinedMetadata ) ;
160
231
var variant = flagConfiguration . DefaultVariant ;
161
- if ( flagConfiguration . Targeting != null && ! String . IsNullOrEmpty ( flagConfiguration . Targeting . ToString ( ) ) && flagConfiguration . Targeting . ToString ( ) != "{}" )
232
+ if ( flagConfiguration . Targeting != null &&
233
+ ! String . IsNullOrEmpty ( flagConfiguration . Targeting . ToString ( ) ) &&
234
+ flagConfiguration . Targeting . ToString ( ) != "{}" )
162
235
{
163
236
reason = Reason . TargetingMatch ;
164
237
var flagdProperties = new Dictionary < string , Value > ( ) ;
165
238
flagdProperties . Add ( FlagdProperties . FlagKeyKey , new Value ( flagKey ) ) ;
166
- flagdProperties . Add ( FlagdProperties . TimestampKey , new Value ( DateTimeOffset . UtcNow . ToUnixTimeSeconds ( ) ) ) ;
239
+ flagdProperties . Add ( FlagdProperties . TimestampKey ,
240
+ new Value ( DateTimeOffset . UtcNow . ToUnixTimeSeconds ( ) ) ) ;
167
241
168
242
if ( context == null )
169
243
{
@@ -173,7 +247,7 @@ private ResolutionDetails<T> ResolveValue<T>(string flagKey, T defaultValue, Eva
173
247
var targetingContext = context . AsDictionary ( ) . Add (
174
248
FlagdProperties . FlagdPropertiesKey ,
175
249
new Value ( new Structure ( flagdProperties ) )
176
- ) ;
250
+ ) ;
177
251
178
252
var targetingString = flagConfiguration . Targeting . ToString ( ) ;
179
253
// Parse json into hierarchical structure
@@ -202,32 +276,39 @@ private ResolutionDetails<T> ResolveValue<T>(string flagKey, T defaultValue, Eva
202
276
{
203
277
// if variant is null, revert to default
204
278
reason = Reason . Default ;
205
- flagConfiguration . Variants . TryGetValue ( flagConfiguration . DefaultVariant , out var defaultVariantValue ) ;
279
+ flagConfiguration . Variants . TryGetValue ( flagConfiguration . DefaultVariant ,
280
+ out var defaultVariantValue ) ;
206
281
if ( defaultVariantValue == null )
207
282
{
208
- throw new FeatureProviderException ( ErrorType . ParseError , "PARSE_ERROR: flag '" + flagKey + "' has missing or invalid defaultVariant." ) ;
283
+ throw new FeatureProviderException ( ErrorType . ParseError ,
284
+ "PARSE_ERROR: flag '" + flagKey + "' has missing or invalid defaultVariant." ) ;
209
285
}
286
+
210
287
var value = ExtractFoundVariant < T > ( defaultVariantValue , flagKey ) ;
211
288
return new ResolutionDetails < T > (
212
- flagKey : flagKey ,
213
- value ,
214
- reason : reason ,
215
- variant : variant
216
- ) ;
289
+ flagKey : flagKey ,
290
+ value ,
291
+ reason : reason ,
292
+ variant : variant ,
293
+ flagMetadata : flagMetadata
294
+ ) ;
217
295
}
218
296
else if ( flagConfiguration . Variants . TryGetValue ( variant , out var foundVariantValue ) )
219
297
{
220
298
// if variant can be found, return it - this could be TARGETING_MATCH or STATIC.
221
299
var value = ExtractFoundVariant < T > ( foundVariantValue , flagKey ) ;
222
300
return new ResolutionDetails < T > (
223
- flagKey : flagKey ,
224
- value ,
225
- reason : reason ,
226
- variant : variant
227
- ) ;
301
+ flagKey : flagKey ,
302
+ value ,
303
+ reason : reason ,
304
+ variant : variant ,
305
+ flagMetadata : flagMetadata
306
+ ) ;
228
307
}
229
308
}
230
- throw new FeatureProviderException ( ErrorType . FlagNotFound , "FLAG_NOT_FOUND: flag '" + flagKey + "' not found" ) ;
309
+
310
+ throw new FeatureProviderException ( ErrorType . FlagNotFound ,
311
+ "FLAG_NOT_FOUND: flag '" + flagKey + "' not found" ) ;
231
312
}
232
313
233
314
static T ExtractFoundVariant < T > ( object foundVariantValue , string flagKey )
@@ -236,6 +317,7 @@ static T ExtractFoundVariant<T>(object foundVariantValue, string flagKey)
236
317
{
237
318
foundVariantValue = Convert . ToInt32 ( foundVariantValue ) ;
238
319
}
320
+
239
321
if ( typeof ( T ) == typeof ( double ) )
240
322
{
241
323
foundVariantValue = Convert . ToDouble ( foundVariantValue ) ;
@@ -244,11 +326,14 @@ static T ExtractFoundVariant<T>(object foundVariantValue, string flagKey)
244
326
{
245
327
foundVariantValue = ConvertJObjectToOpenFeatureValue ( value ) ;
246
328
}
329
+
247
330
if ( foundVariantValue is T castValue )
248
331
{
249
332
return castValue ;
250
333
}
251
- throw new FeatureProviderException ( ErrorType . TypeMismatch , "TYPE_MISMATCH: flag '" + flagKey + "' does not match the expected type" ) ;
334
+
335
+ throw new FeatureProviderException ( ErrorType . TypeMismatch ,
336
+ "TYPE_MISMATCH: flag '" + flagKey + "' does not match the expected type" ) ;
252
337
}
253
338
254
339
static dynamic ConvertToDynamicObject ( IImmutableDictionary < string , Value > dictionary )
@@ -259,7 +344,9 @@ static dynamic ConvertToDynamicObject(IImmutableDictionary<string, Value> dictio
259
344
foreach ( var kvp in dictionary )
260
345
{
261
346
expandoDict . Add ( kvp . Key ,
262
- kvp . Value . IsStructure ? ConvertToDynamicObject ( kvp . Value . AsStructure . AsDictionary ( ) ) : kvp . Value . AsObject ) ;
347
+ kvp . Value . IsStructure
348
+ ? ConvertToDynamicObject ( kvp . Value . AsStructure . AsDictionary ( ) )
349
+ : kvp . Value . AsObject ) ;
263
350
}
264
351
265
352
return expandoObject ;
@@ -302,4 +389,4 @@ static Value ConvertJObjectToOpenFeatureValue(JObject jsonValue)
302
389
return new Value ( new Structure ( result ) ) ;
303
390
}
304
391
}
305
- }
392
+ }
0 commit comments