33import static cloud .eppo .Constants .DEFAULT_JITTER_INTERVAL_RATIO ;
44import static cloud .eppo .Constants .DEFAULT_POLLING_INTERVAL_MILLIS ;
55import 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 ;
613
714import cloud .eppo .api .*;
815import cloud .eppo .cache .AssignmentCacheEntry ;
@@ -190,28 +197,33 @@ protected CompletableFuture<Void> loadConfigurationAsync() {
190197 return future ;
191198 }
192199
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 ) {
200207 throwIfEmptyOrNull (flagKey , "flagKey must not be empty" );
201208 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" );
202212
203- Configuration config = getConfiguration ();
213+ @ NotNull final Configuration config = getConfiguration ();
204214
205- FlagConfig flag = config .getFlag (flagKey );
206- if (flag == null ) {
215+ @ Nullable final FlagConfig maybeFlag = config .getFlag (flagKey );
216+ if (maybeFlag == null ) {
207217 log .warn ("no configuration found for key: {}" , flagKey );
208- return defaultValue ;
218+ return new ValuedFlagEvaluationResult ( defaultValue , null , NO_FLAG_CONFIG ) ;
209219 }
210220
221+ @ NotNull final FlagConfig flag = maybeFlag ;
222+
211223 if (!flag .isEnabled ()) {
212224 log .info (
213225 "no assigned variation because the experiment or feature flag is disabled: {}" , flagKey );
214- return defaultValue ;
226+ return new ValuedFlagEvaluationResult ( defaultValue , null , FLAG_DISABLED ) ;
215227 }
216228
217229 if (flag .getVariationType () != expectedType ) {
@@ -220,74 +232,94 @@ protected EppoValue getTypedAssignment(
220232 flagKey ,
221233 flag .getVariationType (),
222234 expectedType );
223- return defaultValue ;
235+ return new ValuedFlagEvaluationResult ( defaultValue , null , BAD_VARIATION_TYPE ) ;
224236 }
225237
226- FlagEvaluationResult evaluationResult =
238+ @ NotNull final FlagEvaluationResult evaluationResult =
227239 FlagEvaluator .evaluateFlag (
228240 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 (
234251 "no assigned variation because the flag type doesn't match the variation type: {} has type {}, variation value is {}" ,
235252 flagKey ,
236253 flag .getVariationType (),
237254 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 ()) {
240259
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- if (assignmentCache .hasEntry (cacheEntry )) {
271- logAssignment = false ;
272- }
273- }
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+
282+ // Deduplication of assignment logging is possible by providing an `IAssignmentCache`.
283+ // Default to true, only avoid logging if there's a cache hit.
284+ boolean logAssignment = true ;
285+ @ NotNull final AssignmentCacheEntry cacheEntry = AssignmentCacheEntry .fromVariationAssignment (assignment );
286+ if (assignmentCache != null ) {
287+ if (assignmentCache .hasEntry (cacheEntry )) {
288+ logAssignment = false ;
289+ }
290+ }
274291
275- if (logAssignment ) {
276- assignmentLogger .logAssignment (assignment );
292+ if (logAssignment ) {
293+ assignmentLogger .logAssignment (assignment );
277294
278- if (assignmentCache != null ) {
279- assignmentCache .put (cacheEntry );
295+ if (assignmentCache != null ) {
296+ assignmentCache .put (cacheEntry );
297+ }
298+ }
299+
300+ } catch (Exception e ) {
301+ log .error ("Error logging assignment: {}" , e .getMessage (), e );
280302 }
281303 }
282-
283- } catch (Exception e ) {
284- log .error ("Error logging assignment: {}" , e .getMessage (), e );
285304 }
286305 }
287- return assignedValue != null ? assignedValue : defaultValue ;
306+ return valuedEvaluationResult ;
307+ }
308+
309+ @ NotNull
310+ protected EppoValue getTypedAssignment (
311+ @ NotNull String flagKey ,
312+ @ NotNull String subjectKey ,
313+ @ NotNull Attributes subjectAttributes ,
314+ @ NotNull EppoValue defaultValue ,
315+ @ NotNull VariationType expectedType ) {
316+
317+ @ NotNull final ValuedFlagEvaluationResult valuedEvaluationResult = getTypedAssignmentResult (
318+ flagKey , subjectKey , subjectAttributes , defaultValue , expectedType );
319+ return valuedEvaluationResult .getValue ();
288320 }
289321
290- private boolean valueTypeMatchesExpected (VariationType expectedType , EppoValue value ) {
322+ private boolean valueTypeMatchesExpected (@ NotNull VariationType expectedType , @ NotNull EppoValue value ) {
291323 boolean typeMatch ;
292324 switch (expectedType ) {
293325 case BOOLEAN :
@@ -318,14 +350,14 @@ private boolean valueTypeMatchesExpected(VariationType expectedType, EppoValue v
318350 return typeMatch ;
319351 }
320352
321- public boolean getBooleanAssignment (String flagKey , String subjectKey , boolean defaultValue ) {
353+ public boolean getBooleanAssignment (@ NotNull String flagKey , @ NotNull String subjectKey , boolean defaultValue ) {
322354 return this .getBooleanAssignment (flagKey , subjectKey , new Attributes (), defaultValue );
323355 }
324356
325357 public boolean getBooleanAssignment (
326- String flagKey , String subjectKey , Attributes subjectAttributes , boolean defaultValue ) {
358+ @ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull Attributes subjectAttributes , boolean defaultValue ) {
327359 try {
328- EppoValue value =
360+ @ NotNull final EppoValue value =
329361 this .getTypedAssignment (
330362 flagKey ,
331363 subjectKey ,
@@ -338,14 +370,14 @@ public boolean getBooleanAssignment(
338370 }
339371 }
340372
341- public int getIntegerAssignment (String flagKey , String subjectKey , int defaultValue ) {
373+ public int getIntegerAssignment (@ NotNull String flagKey , @ NotNull String subjectKey , int defaultValue ) {
342374 return getIntegerAssignment (flagKey , subjectKey , new Attributes (), defaultValue );
343375 }
344376
345377 public int getIntegerAssignment (
346- String flagKey , String subjectKey , Attributes subjectAttributes , int defaultValue ) {
378+ @ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull Attributes subjectAttributes , int defaultValue ) {
347379 try {
348- EppoValue value =
380+ @ NotNull final EppoValue value =
349381 this .getTypedAssignment (
350382 flagKey ,
351383 subjectKey ,
@@ -358,14 +390,16 @@ public int getIntegerAssignment(
358390 }
359391 }
360392
361- public Double getDoubleAssignment (String flagKey , String subjectKey , double defaultValue ) {
393+ @ NotNull
394+ public Double getDoubleAssignment (@ NotNull String flagKey , @ NotNull String subjectKey , double defaultValue ) {
362395 return getDoubleAssignment (flagKey , subjectKey , new Attributes (), defaultValue );
363396 }
364397
398+ @ NotNull
365399 public Double getDoubleAssignment (
366- String flagKey , String subjectKey , Attributes subjectAttributes , double defaultValue ) {
400+ @ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull Attributes subjectAttributes , double defaultValue ) {
367401 try {
368- EppoValue value =
402+ @ NotNull final EppoValue value =
369403 this .getTypedAssignment (
370404 flagKey ,
371405 subjectKey ,
@@ -378,14 +412,16 @@ public Double getDoubleAssignment(
378412 }
379413 }
380414
381- public String getStringAssignment (String flagKey , String subjectKey , String defaultValue ) {
415+ @ NotNull
416+ public String getStringAssignment (@ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull String defaultValue ) {
382417 return this .getStringAssignment (flagKey , subjectKey , new Attributes (), defaultValue );
383418 }
384419
420+ @ NotNull
385421 public String getStringAssignment (
386- String flagKey , String subjectKey , Attributes subjectAttributes , String defaultValue ) {
422+ @ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull Attributes subjectAttributes , @ NotNull String defaultValue ) {
387423 try {
388- EppoValue value =
424+ @ NotNull final EppoValue value =
389425 this .getTypedAssignment (
390426 flagKey ,
391427 subjectKey ,
@@ -408,7 +444,8 @@ public String getStringAssignment(
408444 * @param defaultValue the default value to return if the flag is not found
409445 * @return the JSON string value of the assignment
410446 */
411- public JsonNode getJSONAssignment (String flagKey , String subjectKey , JsonNode defaultValue ) {
447+ @ NotNull
448+ public JsonNode getJSONAssignment (@ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull JsonNode defaultValue ) {
412449 return getJSONAssignment (flagKey , subjectKey , new Attributes (), defaultValue );
413450 }
414451
@@ -422,17 +459,19 @@ public JsonNode getJSONAssignment(String flagKey, String subjectKey, JsonNode de
422459 * @param defaultValue the default value to return if the flag is not found
423460 * @return the JSON string value of the assignment
424461 */
462+ @ NotNull
425463 public JsonNode getJSONAssignment (
426- String flagKey , String subjectKey , Attributes subjectAttributes , JsonNode defaultValue ) {
464+ @ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull Attributes subjectAttributes , @ NotNull JsonNode defaultValue ) {
427465 try {
428- EppoValue value =
466+ @ NotNull final EppoValue value =
429467 this .getTypedAssignment (
430468 flagKey ,
431469 subjectKey ,
432470 subjectAttributes ,
433471 EppoValue .valueOf (defaultValue .toString ()),
434472 VariationType .JSON );
435- return parseJsonString (value .stringValue ());
473+ @ Nullable final JsonNode jsonValue = parseJsonString (value .stringValue ());
474+ return jsonValue != null ? jsonValue : defaultValue ;
436475 } catch (Exception e ) {
437476 return throwIfNotGraceful (e , defaultValue );
438477 }
@@ -448,10 +487,11 @@ public JsonNode getJSONAssignment(
448487 * @param defaultValue the default value to return if the flag is not found
449488 * @return the JSON string value of the assignment
450489 */
490+ @ NotNull
451491 public String getJSONStringAssignment (
452- String flagKey , String subjectKey , Attributes subjectAttributes , String defaultValue ) {
492+ @ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull Attributes subjectAttributes , @ NotNull String defaultValue ) {
453493 try {
454- EppoValue value =
494+ @ NotNull final EppoValue value =
455495 this .getTypedAssignment (
456496 flagKey ,
457497 subjectKey ,
@@ -474,11 +514,13 @@ public String getJSONStringAssignment(
474514 * @param defaultValue the default value to return if the flag is not found
475515 * @return the JSON string value of the assignment
476516 */
477- public String getJSONStringAssignment (String flagKey , String subjectKey , String defaultValue ) {
517+ @ NotNull
518+ public String getJSONStringAssignment (@ NotNull String flagKey , @ NotNull String subjectKey , @ NotNull String defaultValue ) {
478519 return this .getJSONStringAssignment (flagKey , subjectKey , new Attributes (), defaultValue );
479520 }
480521
481- private JsonNode parseJsonString (String jsonString ) {
522+ @ Nullable
523+ private JsonNode parseJsonString (@ NotNull String jsonString ) {
482524 try {
483525 return mapper .readTree (jsonString );
484526 } catch (JsonProcessingException e ) {
@@ -557,15 +599,17 @@ public BanditResult getBanditAction(
557599 }
558600 }
559601
602+ @ NotNull
560603 private Map <String , String > buildLogMetaData (boolean isConfigObfuscated ) {
561- HashMap <String , String > metaData = new HashMap <>();
604+ @ NotNull final HashMap <String , String > metaData = new HashMap <>();
562605 metaData .put ("obfuscated" , Boolean .valueOf (isConfigObfuscated ).toString ());
563606 metaData .put ("sdkLanguage" , sdkName );
564607 metaData .put ("sdkLibVersion" , sdkVersion );
565608 return metaData ;
566609 }
567610
568- private <T > T throwIfNotGraceful (Exception e , T defaultValue ) {
611+ @ NotNull
612+ private <T > T throwIfNotGraceful (@ NotNull Exception e , @ NotNull T defaultValue ) {
569613 if (this .isGracefulMode ) {
570614 log .info ("error getting assignment value: {}" , e .getMessage ());
571615 return defaultValue ;
0 commit comments