3
3
import static cloud .eppo .Constants .DEFAULT_JITTER_INTERVAL_RATIO ;
4
4
import static cloud .eppo .Constants .DEFAULT_POLLING_INTERVAL_MILLIS ;
5
5
import static cloud .eppo .Utils .throwIfEmptyOrNull ;
6
+ import static cloud .eppo .Utils .throwIfNull ;
7
+ import static cloud .eppo .ValuedFlagEvaluationResultType .BAD_VALUE_TYPE ;
8
+ import static cloud .eppo .ValuedFlagEvaluationResultType .BAD_VARIATION_TYPE ;
9
+ import static cloud .eppo .ValuedFlagEvaluationResultType .FLAG_DISABLED ;
10
+ import static cloud .eppo .ValuedFlagEvaluationResultType .NO_ALLOCATION ;
11
+ import static cloud .eppo .ValuedFlagEvaluationResultType .NO_FLAG_CONFIG ;
12
+ import static cloud .eppo .ValuedFlagEvaluationResultType .OK ;
6
13
7
14
import cloud .eppo .api .*;
8
15
import cloud .eppo .cache .AssignmentCacheEntry ;
@@ -190,28 +197,33 @@ protected CompletableFuture<Void> loadConfigurationAsync() {
190
197
return future ;
191
198
}
192
199
193
- protected EppoValue getTypedAssignment (
194
- String flagKey ,
195
- String subjectKey ,
196
- Attributes subjectAttributes ,
197
- EppoValue defaultValue ,
198
- VariationType expectedType ) {
199
-
200
+ @ NotNull
201
+ protected ValuedFlagEvaluationResult getTypedAssignmentResult (
202
+ @ NotNull String flagKey ,
203
+ @ NotNull String subjectKey ,
204
+ @ NotNull Attributes subjectAttributes ,
205
+ @ NotNull EppoValue defaultValue ,
206
+ @ NotNull VariationType expectedType ) {
200
207
throwIfEmptyOrNull (flagKey , "flagKey must not be empty" );
201
208
throwIfEmptyOrNull (subjectKey , "subjectKey must not be empty" );
209
+ throwIfNull (subjectAttributes , "subjectAttributes must not be empty" );
210
+ throwIfNull (defaultValue , "defaultValue must not be empty" );
211
+ throwIfNull (expectedType , "expectedType must not be empty" );
202
212
203
- Configuration config = getConfiguration ();
213
+ @ NotNull final Configuration config = getConfiguration ();
204
214
205
- FlagConfig flag = config .getFlag (flagKey );
206
- if (flag == null ) {
215
+ @ Nullable final FlagConfig maybeFlag = config .getFlag (flagKey );
216
+ if (maybeFlag == null ) {
207
217
log .warn ("no configuration found for key: {}" , flagKey );
208
- return defaultValue ;
218
+ return new ValuedFlagEvaluationResult ( defaultValue , null , NO_FLAG_CONFIG ) ;
209
219
}
210
220
221
+ @ NotNull final FlagConfig flag = maybeFlag ;
222
+
211
223
if (!flag .isEnabled ()) {
212
224
log .info (
213
225
"no assigned variation because the experiment or feature flag is disabled: {}" , flagKey );
214
- return defaultValue ;
226
+ return new ValuedFlagEvaluationResult ( defaultValue , null , FLAG_DISABLED ) ;
215
227
}
216
228
217
229
if (flag .getVariationType () != expectedType ) {
@@ -220,68 +232,88 @@ protected EppoValue getTypedAssignment(
220
232
flagKey ,
221
233
flag .getVariationType (),
222
234
expectedType );
223
- return defaultValue ;
235
+ return new ValuedFlagEvaluationResult ( defaultValue , null , BAD_VARIATION_TYPE ) ;
224
236
}
225
237
226
- FlagEvaluationResult evaluationResult =
238
+ @ NotNull final FlagEvaluationResult evaluationResult =
227
239
FlagEvaluator .evaluateFlag (
228
240
flag , flagKey , subjectKey , subjectAttributes , config .isConfigObfuscated ());
229
- EppoValue assignedValue =
230
- evaluationResult .getVariation () != null ? evaluationResult .getVariation ().getValue () : null ;
231
-
232
- if (assignedValue != null && !valueTypeMatchesExpected (expectedType , assignedValue )) {
233
- log .warn (
241
+ @ Nullable final FlagEvaluationAllocationKeyAndVariation allocationKeyAndVariation =
242
+ evaluationResult .getAllocationKeyAndVariation ();
243
+
244
+ @ NotNull final ValuedFlagEvaluationResult valuedEvaluationResult ;
245
+ if (allocationKeyAndVariation == null ) {
246
+ valuedEvaluationResult = new ValuedFlagEvaluationResult (defaultValue , evaluationResult , NO_ALLOCATION );
247
+ } else {
248
+ @ NotNull final EppoValue assignedValue = allocationKeyAndVariation .getVariation ().getValue ();
249
+ if (!valueTypeMatchesExpected (expectedType , assignedValue )) {
250
+ log .warn (
234
251
"no assigned variation because the flag type doesn't match the variation type: {} has type {}, variation value is {}" ,
235
252
flagKey ,
236
253
flag .getVariationType (),
237
254
assignedValue );
238
- return defaultValue ;
239
- }
255
+ return new ValuedFlagEvaluationResult (defaultValue , evaluationResult , BAD_VALUE_TYPE );
256
+ } else {
257
+ valuedEvaluationResult = new ValuedFlagEvaluationResult (assignedValue , evaluationResult , OK );
258
+ if (assignmentLogger != null && evaluationResult .doLog ()) {
240
259
241
- if (assignedValue != null && assignmentLogger != null && evaluationResult .doLog ()) {
242
-
243
- try {
244
- String allocationKey = evaluationResult .getAllocationKey ();
245
- String experimentKey =
246
- flagKey
247
- + '-'
248
- + allocationKey ; // Our experiment key is derived by hyphenating the flag key and
249
- // allocation key
250
- String variationKey = evaluationResult .getVariation ().getKey ();
251
- Map <String , String > extraLogging = evaluationResult .getExtraLogging ();
252
- Map <String , String > metaData = buildLogMetaData (config .isConfigObfuscated ());
253
-
254
- Assignment assignment =
255
- new Assignment (
256
- experimentKey ,
257
- flagKey ,
258
- allocationKey ,
259
- variationKey ,
260
- subjectKey ,
261
- subjectAttributes ,
262
- extraLogging ,
263
- metaData );
264
-
265
- // Deduplication of assignment logging is possible by providing an `IAssignmentCache`.
266
- // Default to true, only avoid logging if there's a cache hit.
267
- boolean logAssignment = true ;
268
- AssignmentCacheEntry cacheEntry = AssignmentCacheEntry .fromVariationAssignment (assignment );
269
- if (assignmentCache != null ) {
270
- logAssignment = assignmentCache .putIfAbsent (cacheEntry );
271
- }
260
+ try {
261
+ @ NotNull final String allocationKey = allocationKeyAndVariation .getAllocationKey ();
262
+ @ NotNull final String experimentKey =
263
+ flagKey
264
+ + '-'
265
+ + allocationKey ; // Our experiment key is derived by hyphenating the flag key and
266
+ // allocation key
267
+ @ NotNull final String variationKey = allocationKeyAndVariation .getVariation ().getKey ();
268
+ @ NotNull final Map <String , String > extraLogging = evaluationResult .getExtraLogging ();
269
+ @ NotNull final Map <String , String > metaData = buildLogMetaData (config .isConfigObfuscated ());
270
+
271
+ @ NotNull final Assignment assignment =
272
+ new Assignment (
273
+ experimentKey ,
274
+ flagKey ,
275
+ allocationKey ,
276
+ variationKey ,
277
+ subjectKey ,
278
+ subjectAttributes ,
279
+ extraLogging ,
280
+ metaData );
281
+ final boolean logAssignment ;
282
+ @ NotNull final AssignmentCacheEntry cacheEntry = AssignmentCacheEntry .fromVariationAssignment (assignment );
283
+ if (assignmentCache != null ) {
284
+ logAssignment = assignmentCache .putIfAbsent (cacheEntry );
285
+ } else {
286
+ // Deduplication of assignment logging is possible by providing an `IAssignmentCache`.
287
+ // Default to true, only avoid logging if there's a cache hit.
288
+ logAssignment = true ;
289
+ }
272
290
273
- if (logAssignment ) {
274
- assignmentLogger .logAssignment (assignment );
291
+ if (logAssignment ) {
292
+ assignmentLogger .logAssignment (assignment );
293
+ }
294
+ } catch (Exception e ) {
295
+ log .error ("Error logging assignment: {}" , e .getMessage (), e );
296
+ }
275
297
}
276
-
277
- } catch (Exception e ) {
278
- log .error ("Error logging assignment: {}" , e .getMessage (), e );
279
298
}
280
299
}
281
- return assignedValue != null ? assignedValue : defaultValue ;
300
+ return valuedEvaluationResult ;
301
+ }
302
+
303
+ @ NotNull
304
+ protected EppoValue getTypedAssignment (
305
+ @ NotNull String flagKey ,
306
+ @ NotNull String subjectKey ,
307
+ @ NotNull Attributes subjectAttributes ,
308
+ @ NotNull EppoValue defaultValue ,
309
+ @ NotNull VariationType expectedType ) {
310
+
311
+ @ NotNull final ValuedFlagEvaluationResult valuedEvaluationResult = getTypedAssignmentResult (
312
+ flagKey , subjectKey , subjectAttributes , defaultValue , expectedType );
313
+ return valuedEvaluationResult .getValue ();
282
314
}
283
315
284
- private boolean valueTypeMatchesExpected (VariationType expectedType , EppoValue value ) {
316
+ private boolean valueTypeMatchesExpected (@ NotNull VariationType expectedType , @ NotNull EppoValue value ) {
285
317
boolean typeMatch ;
286
318
switch (expectedType ) {
287
319
case BOOLEAN :
@@ -312,14 +344,14 @@ private boolean valueTypeMatchesExpected(VariationType expectedType, EppoValue v
312
344
return typeMatch ;
313
345
}
314
346
315
- public boolean getBooleanAssignment (String flagKey , String subjectKey , boolean defaultValue ) {
347
+ public boolean getBooleanAssignment (@ NotNull String flagKey , @ NotNull String subjectKey , boolean defaultValue ) {
316
348
return this .getBooleanAssignment (flagKey , subjectKey , new Attributes (), defaultValue );
317
349
}
318
350
319
351
public boolean getBooleanAssignment (
320
- String flagKey , String subjectKey , Attributes subjectAttributes , boolean defaultValue ) {
352
+ @ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull Attributes subjectAttributes , boolean defaultValue ) {
321
353
try {
322
- EppoValue value =
354
+ @ NotNull final EppoValue value =
323
355
this .getTypedAssignment (
324
356
flagKey ,
325
357
subjectKey ,
@@ -332,14 +364,14 @@ public boolean getBooleanAssignment(
332
364
}
333
365
}
334
366
335
- public int getIntegerAssignment (String flagKey , String subjectKey , int defaultValue ) {
367
+ public int getIntegerAssignment (@ NotNull String flagKey , @ NotNull String subjectKey , int defaultValue ) {
336
368
return getIntegerAssignment (flagKey , subjectKey , new Attributes (), defaultValue );
337
369
}
338
370
339
371
public int getIntegerAssignment (
340
- String flagKey , String subjectKey , Attributes subjectAttributes , int defaultValue ) {
372
+ @ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull Attributes subjectAttributes , int defaultValue ) {
341
373
try {
342
- EppoValue value =
374
+ @ NotNull final EppoValue value =
343
375
this .getTypedAssignment (
344
376
flagKey ,
345
377
subjectKey ,
@@ -352,14 +384,16 @@ public int getIntegerAssignment(
352
384
}
353
385
}
354
386
355
- public Double getDoubleAssignment (String flagKey , String subjectKey , double defaultValue ) {
387
+ @ NotNull
388
+ public Double getDoubleAssignment (@ NotNull String flagKey , @ NotNull String subjectKey , double defaultValue ) {
356
389
return getDoubleAssignment (flagKey , subjectKey , new Attributes (), defaultValue );
357
390
}
358
391
392
+ @ NotNull
359
393
public Double getDoubleAssignment (
360
- String flagKey , String subjectKey , Attributes subjectAttributes , double defaultValue ) {
394
+ @ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull Attributes subjectAttributes , double defaultValue ) {
361
395
try {
362
- EppoValue value =
396
+ @ NotNull final EppoValue value =
363
397
this .getTypedAssignment (
364
398
flagKey ,
365
399
subjectKey ,
@@ -372,14 +406,16 @@ public Double getDoubleAssignment(
372
406
}
373
407
}
374
408
375
- public String getStringAssignment (String flagKey , String subjectKey , String defaultValue ) {
409
+ @ NotNull
410
+ public String getStringAssignment (@ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull String defaultValue ) {
376
411
return this .getStringAssignment (flagKey , subjectKey , new Attributes (), defaultValue );
377
412
}
378
413
414
+ @ NotNull
379
415
public String getStringAssignment (
380
- String flagKey , String subjectKey , Attributes subjectAttributes , String defaultValue ) {
416
+ @ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull Attributes subjectAttributes , @ NotNull String defaultValue ) {
381
417
try {
382
- EppoValue value =
418
+ @ NotNull final EppoValue value =
383
419
this .getTypedAssignment (
384
420
flagKey ,
385
421
subjectKey ,
@@ -402,7 +438,8 @@ public String getStringAssignment(
402
438
* @param defaultValue the default value to return if the flag is not found
403
439
* @return the JSON string value of the assignment
404
440
*/
405
- public JsonNode getJSONAssignment (String flagKey , String subjectKey , JsonNode defaultValue ) {
441
+ @ NotNull
442
+ public JsonNode getJSONAssignment (@ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull JsonNode defaultValue ) {
406
443
return getJSONAssignment (flagKey , subjectKey , new Attributes (), defaultValue );
407
444
}
408
445
@@ -416,17 +453,19 @@ public JsonNode getJSONAssignment(String flagKey, String subjectKey, JsonNode de
416
453
* @param defaultValue the default value to return if the flag is not found
417
454
* @return the JSON string value of the assignment
418
455
*/
456
+ @ NotNull
419
457
public JsonNode getJSONAssignment (
420
- String flagKey , String subjectKey , Attributes subjectAttributes , JsonNode defaultValue ) {
458
+ @ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull Attributes subjectAttributes , @ NotNull JsonNode defaultValue ) {
421
459
try {
422
- EppoValue value =
460
+ @ NotNull final EppoValue value =
423
461
this .getTypedAssignment (
424
462
flagKey ,
425
463
subjectKey ,
426
464
subjectAttributes ,
427
465
EppoValue .valueOf (defaultValue .toString ()),
428
466
VariationType .JSON );
429
- return parseJsonString (value .stringValue ());
467
+ @ Nullable final JsonNode jsonValue = parseJsonString (value .stringValue ());
468
+ return jsonValue != null ? jsonValue : defaultValue ;
430
469
} catch (Exception e ) {
431
470
return throwIfNotGraceful (e , defaultValue );
432
471
}
@@ -442,10 +481,11 @@ public JsonNode getJSONAssignment(
442
481
* @param defaultValue the default value to return if the flag is not found
443
482
* @return the JSON string value of the assignment
444
483
*/
484
+ @ NotNull
445
485
public String getJSONStringAssignment (
446
- String flagKey , String subjectKey , Attributes subjectAttributes , String defaultValue ) {
486
+ @ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull Attributes subjectAttributes , @ NotNull String defaultValue ) {
447
487
try {
448
- EppoValue value =
488
+ @ NotNull final EppoValue value =
449
489
this .getTypedAssignment (
450
490
flagKey ,
451
491
subjectKey ,
@@ -468,11 +508,13 @@ public String getJSONStringAssignment(
468
508
* @param defaultValue the default value to return if the flag is not found
469
509
* @return the JSON string value of the assignment
470
510
*/
471
- public String getJSONStringAssignment (String flagKey , String subjectKey , String defaultValue ) {
511
+ @ NotNull
512
+ public String getJSONStringAssignment (@ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull String defaultValue ) {
472
513
return this .getJSONStringAssignment (flagKey , subjectKey , new Attributes (), defaultValue );
473
514
}
474
515
475
- private JsonNode parseJsonString (String jsonString ) {
516
+ @ Nullable
517
+ private JsonNode parseJsonString (@ NotNull String jsonString ) {
476
518
try {
477
519
return mapper .readTree (jsonString );
478
520
} catch (JsonProcessingException e ) {
@@ -551,15 +593,17 @@ public BanditResult getBanditAction(
551
593
}
552
594
}
553
595
596
+ @ NotNull
554
597
private Map <String , String > buildLogMetaData (boolean isConfigObfuscated ) {
555
- HashMap <String , String > metaData = new HashMap <>();
598
+ @ NotNull final HashMap <String , String > metaData = new HashMap <>();
556
599
metaData .put ("obfuscated" , Boolean .valueOf (isConfigObfuscated ).toString ());
557
600
metaData .put ("sdkLanguage" , sdkName );
558
601
metaData .put ("sdkLibVersion" , sdkVersion );
559
602
return metaData ;
560
603
}
561
604
562
- private <T > T throwIfNotGraceful (Exception e , T defaultValue ) {
605
+ @ NotNull
606
+ private <T > T throwIfNotGraceful (@ NotNull Exception e , @ NotNull T defaultValue ) {
563
607
if (this .isGracefulMode ) {
564
608
log .info ("error getting assignment value: {}" , e .getMessage ());
565
609
return defaultValue ;
0 commit comments