-
Notifications
You must be signed in to change notification settings - Fork 56
v2 Java changes for consistency with other SDKs #59
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.launchdarkly.client; | ||
|
|
||
| /** | ||
| * An error indicating an abnormal result from evaluating a feature | ||
| */ | ||
| class EvaluationException extends Exception { | ||
| public EvaluationException(String message) { | ||
| super(message); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,44 +50,33 @@ static Map<String, FeatureFlag> fromJsonMap(String json) { | |
| this.deleted = deleted; | ||
| } | ||
|
|
||
| Integer getOffVariation() { | ||
| return this.offVariation; | ||
| } | ||
|
|
||
| JsonElement getOffVariationValue() { | ||
| if (offVariation != null && offVariation < variations.size()) { | ||
| return variations.get(offVariation); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| EvalResult evaluate(LDUser user, FeatureStore featureStore) { | ||
| EvalResult evaluate(LDUser user, FeatureStore featureStore) throws EvaluationException { | ||
| if (user == null || user.getKey() == null) { | ||
| return null; | ||
| throw new EvaluationException("User or user key is null"); | ||
| } | ||
| List<FeatureRequestEvent> prereqEvents = new ArrayList<>(); | ||
| Set<String> visited = new HashSet<>(); | ||
| JsonElement value = evaluate(user, featureStore, prereqEvents, visited); | ||
| JsonElement value = evaluate(user, featureStore, prereqEvents); | ||
| return new EvalResult(value, prereqEvents); | ||
| } | ||
|
|
||
| // Returning either a JsonElement or null indicating prereq failure/error. | ||
| private JsonElement evaluate(LDUser user, FeatureStore featureStore, List<FeatureRequestEvent> events, Set<String> visited) { | ||
| private JsonElement evaluate(LDUser user, FeatureStore featureStore, List<FeatureRequestEvent> events) throws EvaluationException { | ||
| boolean prereqOk = true; | ||
| visited.add(key); | ||
| for (Prerequisite prereq : prerequisites) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed the broken cycle detection algorithm. |
||
| if (visited.contains(prereq.getKey())) { | ||
| logger.error("Prerequisite cycle detected when evaluating feature flag: " + key); | ||
| return null; | ||
| } | ||
| FeatureFlag prereqFeatureFlag = featureStore.get(prereq.getKey()); | ||
| JsonElement prereqEvalResult = null; | ||
| if (prereqFeatureFlag == null) { | ||
| logger.error("Could not retrieve prerequisite flag: " + prereq.getKey() + " when evaluating: " + key); | ||
| return null; | ||
| } else if (prereqFeatureFlag.isOn()) { | ||
| prereqEvalResult = prereqFeatureFlag.evaluate(user, featureStore, events, visited); | ||
| if (prereqEvalResult == null || !prereqEvalResult.equals(prereqFeatureFlag.getVariation(prereq.getVariation()))) { | ||
| prereqEvalResult = prereqFeatureFlag.evaluate(user, featureStore, events); | ||
| try { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If prerequisite evaluation fails for whatever reason, we return the off variation, not an error. So we don't bubble up prereq evaluation exceptions. |
||
| JsonElement variation = prereqFeatureFlag.getVariation(prereq.getVariation()); | ||
| if (prereqEvalResult == null || variation == null || !prereqEvalResult.equals(variation)) { | ||
| prereqOk = false; | ||
| } | ||
| } catch (EvaluationException err) { | ||
| logger.warn("Error evaluating prerequisites: " + err.getMessage()); | ||
| prereqOk = false; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should log the exception here- at debug or warn.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. |
||
| } | ||
| } else { | ||
|
|
@@ -123,10 +112,30 @@ private Integer evaluateIndex(LDUser user) { | |
| return fallthrough.variationIndexForUser(user, key, salt); | ||
| } | ||
|
|
||
| private JsonElement getVariation(Integer index) { | ||
| if (index == null || index >= variations.size()) { | ||
| JsonElement getOffVariationValue() throws EvaluationException { | ||
| if (offVariation == null) { | ||
| return null; | ||
| } else { | ||
| } | ||
|
|
||
| if (offVariation >= variations.size()) { | ||
| throw new EvaluationException("Invalid off variation index"); | ||
| } | ||
|
|
||
| return variations.get(offVariation); | ||
| } | ||
|
|
||
| private JsonElement getVariation(Integer index) throws EvaluationException { | ||
| // If the supplied index is null, then rules didn't match, and we want to return | ||
| // the off variation | ||
| if (index == null) { | ||
| return null; | ||
| } | ||
| // If the index doesn't refer to a valid variation, that's an unexpected exception and we will | ||
| // return the default variation | ||
| else if (index >= variations.size()) { | ||
| throw new EvaluationException("Invalid index"); | ||
| } | ||
| else { | ||
| return variations.get(index); | ||
| } | ||
| } | ||
|
|
@@ -171,6 +180,8 @@ List<JsonElement> getVariations() { | |
| return variations; | ||
| } | ||
|
|
||
| Integer getOffVariation() { return offVariation; } | ||
|
|
||
| static class EvalResult { | ||
| private final JsonElement value; | ||
| private final List<FeatureRequestEvent> prerequisiteEvents; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -294,47 +294,36 @@ public JsonElement jsonVariation(String featureKey, LDUser user, JsonElement def | |
| return value; | ||
| } | ||
|
|
||
|
|
||
| private JsonElement evaluate(String featureKey, LDUser user, JsonElement defaultValue) { | ||
| if (!initialized()) { | ||
| return defaultValue; | ||
| } | ||
| try { | ||
| FeatureFlag featureFlag = config.featureStore.get(featureKey); | ||
| if (featureFlag != null) { | ||
| if (config.stream && config.debugStreaming) { | ||
| FeatureFlag pollingResult = requestor.makeRequest(featureKey, true); | ||
| if (!featureFlag.equals(pollingResult)) { | ||
| logger.warn("Mismatch between streaming and polling feature! Streaming: {} Polling: {}", featureFlag, pollingResult); | ||
| } | ||
| } | ||
| } else { | ||
| if (featureFlag == null) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🙏 |
||
| logger.warn("Unknown feature flag " + featureKey + "; returning default value: "); | ||
| return defaultValue; | ||
| } | ||
| if (featureFlag.isOn()) { | ||
| FeatureFlag.EvalResult evalResult = featureFlag.evaluate(user, config.featureStore); | ||
| if (evalResult != null) { | ||
| if (!isOffline()) { | ||
| for (FeatureRequestEvent event : evalResult.getPrerequisiteEvents()) { | ||
| eventProcessor.sendEvent(event); | ||
| } | ||
| } | ||
| if (evalResult.getValue() == null) { | ||
| return defaultValue; | ||
| } else { | ||
| if (evalResult.getValue() != null) { | ||
| return evalResult.getValue(); | ||
| } | ||
| } | ||
| } else { | ||
| JsonElement offVariation = featureFlag.getOffVariationValue(); | ||
| if (offVariation != null) { | ||
| return offVariation; | ||
| } | ||
| } | ||
| JsonElement offVariation = featureFlag.getOffVariationValue(); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return the |
||
| if (offVariation != null) { | ||
| return offVariation; | ||
| } | ||
| } catch (Exception e) { | ||
| logger.error("Encountered exception in LaunchDarkly client", e); | ||
| } | ||
|
|
||
|
|
||
| return defaultValue; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,13 +25,6 @@ public boolean apply(JsonPrimitive uValue, JsonPrimitive cValue) { | |
| if (uValue.isNumber() && cValue.isNumber()) { | ||
| return uValue.getAsDouble() == cValue.getAsDouble(); | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This cleans up the semantics of the |
||
| DateTime uDateTime = Util.jsonPrimitiveToDateTime(uValue); | ||
| if (uDateTime != null) { | ||
| DateTime cDateTime = Util.jsonPrimitiveToDateTime(cValue); | ||
| if (cDateTime != null) { | ||
| return uDateTime.getMillis() == cDateTime.getMillis(); | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| }, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case we should return the default.