-
Notifications
You must be signed in to change notification settings - Fork 0
Use new evaluations endpoint #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fc413e5
ddd25c3
4e4df5c
c641846
4337999
4a8ad09
582f3a9
5427a51
3d8f62e
36a7806
d9a7acb
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 |
|---|---|---|
| @@ -1,7 +1,6 @@ | ||
| package com.octopus.openfeature.provider; | ||
|
|
||
| import com.fasterxml.jackson.core.type.TypeReference; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import java.net.MalformedURLException; | ||
| import java.net.URI; | ||
| import java.net.URISyntaxException; | ||
|
|
@@ -65,7 +64,7 @@ FeatureToggles getFeatureToggleEvaluationManifest() | |
| logger.log(System.Logger.Level.WARNING,String.format("Feature toggle response from %s did not contain expected ContentHash header", manifestURI.toString())); | ||
| return null; | ||
| } | ||
| List<FeatureToggleEvaluation> evaluations = OctopusObjectMapper.INSTANCE.readValue(httpResponse.body(), new TypeReference<List<FeatureToggleEvaluation>>(){}); | ||
| List<FeatureToggleEvaluation> evaluations = OctopusObjectMapper.INSTANCE.readValue(httpResponse.body(), new TypeReference<>(){}); | ||
|
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. Have not seen this before but Claude says you're just telling the deserializer to put the response into a list of
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. Yeah, this is just a bit of syntax clean up. Because the type of the variable |
||
| return new FeatureToggles(evaluations, Base64.getDecoder().decode(contentHashHeader.get())); | ||
| } catch (Exception e) { | ||
| logger.log(System.Logger.Level.WARNING, "Unable to query Octopus Feature Toggle service", e); | ||
|
|
@@ -83,16 +82,12 @@ private URI getCheckURI() { | |
|
|
||
| private URI getManifestURI() { | ||
| try { | ||
| return new URL(config.getServerUri().toURL(), "/api/featuretoggles/v3/").toURI(); | ||
| return new URL(config.getServerUri().toURL(), "/api/toggles/evaluations/v3/").toURI(); | ||
| } catch (MalformedURLException | URISyntaxException ignored) // we know this URL is well-formed | ||
| { } | ||
| return null; | ||
| } | ||
|
|
||
| private Boolean isSuccessStatusCode(int statusCode) { | ||
| return statusCode >= 200 && statusCode < 300; | ||
| } | ||
|
|
||
| // This class needs to be static to allow deserialization | ||
| private static class FeatureToggleCheckResponse { | ||
| public byte[] contentHash; | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,9 +2,9 @@ | |||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import dev.openfeature.sdk.*; | ||||||||||||||||||||||||||||||
| import dev.openfeature.sdk.exceptions.FlagNotFoundError; | ||||||||||||||||||||||||||||||
| import dev.openfeature.sdk.exceptions.ParseError; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import static java.util.stream.Collectors.groupingBy; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
@@ -20,35 +20,51 @@ class OctopusContext { | |||||||||||||||||||||||||||||
| static OctopusContext empty() { | ||||||||||||||||||||||||||||||
| return new OctopusContext(new FeatureToggles(List.of(), new byte[0])); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| byte[] getContentHash() { return featureToggles.getContentHash(); } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| byte[] getContentHash() { | ||||||||||||||||||||||||||||||
| return featureToggles.getContentHash(); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| ProviderEvaluation<Boolean> evaluate(String slug, Boolean defaultValue, EvaluationContext evaluationContext) { | ||||||||||||||||||||||||||||||
| // find the feature toggle matching the slug | ||||||||||||||||||||||||||||||
| var toggleValue = featureToggles.getEvaluations().stream().filter(f -> f.getSlug().equalsIgnoreCase(slug)).findFirst().orElse(null); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // this exception will be handled by OpenFeature, and the default value will be used | ||||||||||||||||||||||||||||||
| if (toggleValue == null) { | ||||||||||||||||||||||||||||||
| throw new FlagNotFoundError(); | ||||||||||||||||||||||||||||||
| throw new FlagNotFoundError(); | ||||||||||||||||||||||||||||||
|
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. Interesting. Java implementation throws rather than returning an error response?
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. The specification test seems to confirm this for the parse error case.
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. Of note: these exception types also exist in the .NET SDK: https://github.com/open-feature/dotnet-sdk/tree/main/src/OpenFeature/Error We may want to align the clients in one direction or the other at some point. |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (missingRequiredPropertiesForClientSideEvaluation(toggleValue)) { | ||||||||||||||||||||||||||||||
| throw new ParseError("Feature toggle " + toggleValue.getSlug() + " is missing necessary information for client-side evaluation."); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+37
to
39
|
||||||||||||||||||||||||||||||
| // if the toggle is disabled, or if it has no segments, then we don't need to evaluate dynamically | ||||||||||||||||||||||||||||||
| if (!toggleValue.isEnabled() || toggleValue.getSegments().isEmpty()) { | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // if the toggle is disabled, or if it has no segments, then we don't need to evaluate dynamically | ||||||||||||||||||||||||||||||
| if (!toggleValue.isEnabled() || !toggleValue.hasSegments()) { | ||||||||||||||||||||||||||||||
| return ProviderEvaluation.<Boolean>builder() | ||||||||||||||||||||||||||||||
| .value(toggleValue.isEnabled()) | ||||||||||||||||||||||||||||||
| .reason(Reason.DEFAULT.toString()) | ||||||||||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| // If the toggle is enabled and has segments configured, then we need to evaluate dynamically, | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // If the toggle is enabled and has segments configured, then we need to evaluate dynamically, | ||||||||||||||||||||||||||||||
| // checking the context matches the segments | ||||||||||||||||||||||||||||||
| return ProviderEvaluation.<Boolean>builder() | ||||||||||||||||||||||||||||||
| .value(MatchesSegment(evaluationContext, toggleValue.getSegments())) | ||||||||||||||||||||||||||||||
| .value(matchesSegment(evaluationContext, toggleValue.getSegments().orElseThrow())) // checked in hasSegments | ||||||||||||||||||||||||||||||
| .reason(Reason.TARGETING_MATCH.toString()) | ||||||||||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| private Boolean MatchesSegment(EvaluationContext evaluationContext, List<Segment> segments) { | ||||||||||||||||||||||||||||||
| private boolean missingRequiredPropertiesForClientSideEvaluation(FeatureToggleEvaluation evaluation) { | ||||||||||||||||||||||||||||||
| if (!evaluation.isEnabled()) { | ||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return evaluation.getClientRolloutPercentage().isEmpty() | ||||||||||||||||||||||||||||||
| || evaluation.getEvaluationKey().isEmpty() | ||||||||||||||||||||||||||||||
| || evaluation.getSegments().isEmpty(); | ||||||||||||||||||||||||||||||
|
Comment on lines
+58
to
+64
|
||||||||||||||||||||||||||||||
| if (!evaluation.isEnabled()) { | |
| return false; | |
| } | |
| return evaluation.getClientRolloutPercentage().isEmpty() | |
| || evaluation.getEvaluationKey().isEmpty() | |
| || evaluation.getSegments().isEmpty(); | |
| // Client-side evaluation is only applicable when the toggle is enabled and has segments | |
| if (!evaluation.isEnabled() || !evaluation.hasSegments()) { | |
| return false; | |
| } | |
| return evaluation.getClientRolloutPercentage().isEmpty() | |
| || evaluation.getEvaluationKey().isEmpty(); |
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.
I think this might be correct. Will give it more thought tomorrow.
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.
This matches what the .NET client is doing and passes the missing-client-validation-fields specification tests.
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -7,7 +7,7 @@ | |||
|
|
||||
| import java.io.InputStream; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Optional; | ||||
|
||||
| import java.util.Optional; |
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.
If we throw an error here, is it possible to include a message about why?
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.
Same question below.
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.
I could add an explicit check and throw my own exception, but I think it should be clear if it were ever to throw at this line? You can basically think of .orElseThrow() as the same as C#'s !, except it throws right away rather than when you try to reference the variable.
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.
This is my first time seeing this Optional class, but it sounds right 👍