Skip to content

Commit b297bd6

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 4fa7306 commit b297bd6

24 files changed

+1315
-393
lines changed

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

Lines changed: 129 additions & 85 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,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

Comments
 (0)