Skip to content

Commit 295d752

Browse files
Added BaseEppoClient.getTypedAssignmentResult + nullability
Added minimum nullability necessary to add required fields. Also added `sortedVariationKeys` to `FlagConfig` to make finding the variation index cheaper
1 parent 33593d2 commit 295d752

24 files changed

+1338
-435
lines changed

src/main/java/cloud/eppo/BaseEppoClient.java

Lines changed: 125 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
import static cloud.eppo.Constants.DEFAULT_JITTER_INTERVAL_RATIO;
44
import static cloud.eppo.Constants.DEFAULT_POLLING_INTERVAL_MILLIS;
55
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;
613

714
import cloud.eppo.api.*;
815
import 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,68 +232,88 @@ 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-
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+
}
272290

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+
}
275297
}
276-
277-
} catch (Exception e) {
278-
log.error("Error logging assignment: {}", e.getMessage(), e);
279298
}
280299
}
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();
282314
}
283315

284-
private boolean valueTypeMatchesExpected(VariationType expectedType, EppoValue value) {
316+
private boolean valueTypeMatchesExpected(@NotNull VariationType expectedType, @NotNull EppoValue value) {
285317
boolean typeMatch;
286318
switch (expectedType) {
287319
case BOOLEAN:
@@ -312,14 +344,14 @@ private boolean valueTypeMatchesExpected(VariationType expectedType, EppoValue v
312344
return typeMatch;
313345
}
314346

315-
public boolean getBooleanAssignment(String flagKey, String subjectKey, boolean defaultValue) {
347+
public boolean getBooleanAssignment(@NotNull String flagKey, @NotNull String subjectKey, boolean defaultValue) {
316348
return this.getBooleanAssignment(flagKey, subjectKey, new Attributes(), defaultValue);
317349
}
318350

319351
public boolean getBooleanAssignment(
320-
String flagKey, String subjectKey, Attributes subjectAttributes, boolean defaultValue) {
352+
@NotNull String flagKey, @NotNull String subjectKey, @NotNull Attributes subjectAttributes, boolean defaultValue) {
321353
try {
322-
EppoValue value =
354+
@NotNull final EppoValue value =
323355
this.getTypedAssignment(
324356
flagKey,
325357
subjectKey,
@@ -332,14 +364,14 @@ public boolean getBooleanAssignment(
332364
}
333365
}
334366

335-
public int getIntegerAssignment(String flagKey, String subjectKey, int defaultValue) {
367+
public int getIntegerAssignment(@NotNull String flagKey, @NotNull String subjectKey, int defaultValue) {
336368
return getIntegerAssignment(flagKey, subjectKey, new Attributes(), defaultValue);
337369
}
338370

339371
public int getIntegerAssignment(
340-
String flagKey, String subjectKey, Attributes subjectAttributes, int defaultValue) {
372+
@NotNull String flagKey, @NotNull String subjectKey, @NotNull Attributes subjectAttributes, int defaultValue) {
341373
try {
342-
EppoValue value =
374+
@NotNull final EppoValue value =
343375
this.getTypedAssignment(
344376
flagKey,
345377
subjectKey,
@@ -352,14 +384,16 @@ public int getIntegerAssignment(
352384
}
353385
}
354386

355-
public Double getDoubleAssignment(String flagKey, String subjectKey, double defaultValue) {
387+
@NotNull
388+
public Double getDoubleAssignment(@NotNull String flagKey, @NotNull String subjectKey, double defaultValue) {
356389
return getDoubleAssignment(flagKey, subjectKey, new Attributes(), defaultValue);
357390
}
358391

392+
@NotNull
359393
public Double getDoubleAssignment(
360-
String flagKey, String subjectKey, Attributes subjectAttributes, double defaultValue) {
394+
@NotNull String flagKey, @NotNull String subjectKey, @NotNull Attributes subjectAttributes, double defaultValue) {
361395
try {
362-
EppoValue value =
396+
@NotNull final EppoValue value =
363397
this.getTypedAssignment(
364398
flagKey,
365399
subjectKey,
@@ -372,14 +406,16 @@ public Double getDoubleAssignment(
372406
}
373407
}
374408

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) {
376411
return this.getStringAssignment(flagKey, subjectKey, new Attributes(), defaultValue);
377412
}
378413

414+
@NotNull
379415
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) {
381417
try {
382-
EppoValue value =
418+
@NotNull final EppoValue value =
383419
this.getTypedAssignment(
384420
flagKey,
385421
subjectKey,
@@ -402,7 +438,8 @@ public String getStringAssignment(
402438
* @param defaultValue the default value to return if the flag is not found
403439
* @return the JSON string value of the assignment
404440
*/
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) {
406443
return getJSONAssignment(flagKey, subjectKey, new Attributes(), defaultValue);
407444
}
408445

@@ -416,17 +453,19 @@ public JsonNode getJSONAssignment(String flagKey, String subjectKey, JsonNode de
416453
* @param defaultValue the default value to return if the flag is not found
417454
* @return the JSON string value of the assignment
418455
*/
456+
@NotNull
419457
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) {
421459
try {
422-
EppoValue value =
460+
@NotNull final EppoValue value =
423461
this.getTypedAssignment(
424462
flagKey,
425463
subjectKey,
426464
subjectAttributes,
427465
EppoValue.valueOf(defaultValue.toString()),
428466
VariationType.JSON);
429-
return parseJsonString(value.stringValue());
467+
@Nullable final JsonNode jsonValue = parseJsonString(value.stringValue());
468+
return jsonValue != null ? jsonValue : defaultValue;
430469
} catch (Exception e) {
431470
return throwIfNotGraceful(e, defaultValue);
432471
}
@@ -442,10 +481,11 @@ public JsonNode getJSONAssignment(
442481
* @param defaultValue the default value to return if the flag is not found
443482
* @return the JSON string value of the assignment
444483
*/
484+
@NotNull
445485
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) {
447487
try {
448-
EppoValue value =
488+
@NotNull final EppoValue value =
449489
this.getTypedAssignment(
450490
flagKey,
451491
subjectKey,
@@ -468,11 +508,13 @@ public String getJSONStringAssignment(
468508
* @param defaultValue the default value to return if the flag is not found
469509
* @return the JSON string value of the assignment
470510
*/
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) {
472513
return this.getJSONStringAssignment(flagKey, subjectKey, new Attributes(), defaultValue);
473514
}
474515

475-
private JsonNode parseJsonString(String jsonString) {
516+
@Nullable
517+
private JsonNode parseJsonString(@NotNull String jsonString) {
476518
try {
477519
return mapper.readTree(jsonString);
478520
} catch (JsonProcessingException e) {
@@ -551,15 +593,17 @@ public BanditResult getBanditAction(
551593
}
552594
}
553595

596+
@NotNull
554597
private Map<String, String> buildLogMetaData(boolean isConfigObfuscated) {
555-
HashMap<String, String> metaData = new HashMap<>();
598+
@NotNull final HashMap<String, String> metaData = new HashMap<>();
556599
metaData.put("obfuscated", Boolean.valueOf(isConfigObfuscated).toString());
557600
metaData.put("sdkLanguage", sdkName);
558601
metaData.put("sdkLibVersion", sdkVersion);
559602
return metaData;
560603
}
561604

562-
private <T> T throwIfNotGraceful(Exception e, T defaultValue) {
605+
@NotNull
606+
private <T> T throwIfNotGraceful(@NotNull Exception e, @NotNull T defaultValue) {
563607
if (this.isGracefulMode) {
564608
log.info("error getting assignment value: {}", e.getMessage());
565609
return defaultValue;

0 commit comments

Comments
 (0)