feat: add v4 evaluation response types to provider library - #52
Conversation
Adds the OctoToggle v4 evaluation-response contract as package-private types in a v4 sub-package, without hooking them up to anything. The existing v3 evaluation path is untouched. Unrecognised (or absent) client-side condition discriminators deserialize to UnknownCondition rather than failing the whole response, so a condition type introduced by a newer server degrades safely on an older client. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
liamhughes
left a comment
There was a problem hiding this comment.
Approved with comments. 👍
There was a problem hiding this comment.
Not sure it's strictly necessary, but might be helpful to follow the same directory structure between the libraries. e.g. src/main/java/com/octopus/openfeature/provider/v4/Conditions/ClientSideCondition.java
There was a problem hiding this comment.
The package private setup of java makes this tricky. Doing that looks like it forces us to make it public and we'd rather not now to make things easier to update. Updated javadoc of ClientSideCondition with the explanation
| * Matches when the OpenFeature targeting key falls within the {@code percentage}% rollout. | ||
| */ | ||
| @JsonIgnoreProperties("type") // The discriminator is visible to subtypes; this type does not model it. | ||
| final class PercentageByContextCondition extends ClientSideCondition { |
There was a problem hiding this comment.
The C# equivalent for this condition has a nullable Percentage so that we can determine if it has been excluded at evaluation time rather than deserialisation time. Will this work the same way?
By the way, happy to defer that conversation to one of the following PRs. I think the specification tests should catch this if needed.
There was a problem hiding this comment.
haha yeah, next PR changes it. This looks like a follow on from my prompt. I gave it the equivalent dotnet PR and it did the same in that one, so both sets of PRs match in that respect now
| } | ||
|
|
||
| @Test | ||
| void shouldDeserializeConditionWhenDiscriminatorPropertyUsesDifferentCapitalisation() throws Exception { |
There was a problem hiding this comment.
I'm not sure we need to worry about being strict around capitalisation of JSON property names? This test and the next.
These pinned Jackson's configured case-insensitive property matching rather than anything the provider does, and committed us to tolerating casing the server never sends. Removed along with their fixtures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Records the constraint that rules out mirroring the .NET provider's Conditions sub-namespace: package access is not hierarchical, so that layout would force every condition public. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resolves BMBB-747. Java counterpart of openfeature-provider-dotnet#92, which resolved the .NET equivalent, BMBB-674.
What
Adds the OctoToggle v4 evaluation-response contract as types only — nothing is hooked up, and the v3 path is untouched. The provider only ever deserializes this response (
GET api/toggles/evaluations/v4→ array ofServerSideEvaluation); OctoToggle resolves the server-side conditions itself and sends only the client-side ones, keyed by a camelCasetypediscriminator.New in
src/main/java/com/octopus/openfeature/provider/v4/:ServerSideEvaluation(server-resolvedvalue/reason, or deferredevaluationKey/rules),ClientSideRule, the polymorphicClientSideConditionand its three variants,UnknownCondition, andConditionTypeNames.Decisions
Types are package-private in a
v4sub-package — Java's nearest equivalent tointernal. The public API is unchanged: still justOctopusProvider,OctopusConfigurationandProductMetadata.Worth knowing for the follow-up, since Java package access isn't hierarchical:
…providercannot see these types. This assumes the v4 evaluation logic will live in thev4package too, exposing a single public entry point when it lands. Nothing needs one yet, so this PR adds none.Unknown conditions degrade gracefully. An unrecognised or absent discriminator deserializes to
UnknownConditionrather than failing the whole response, so a condition type introduced by a newer server is treated as "not met" by an older client. Jackson's@JsonTypeInfo(defaultImpl = …)covers both cases, so .NET's custom converter has no equivalent here.Superseded in the follow-up: once evaluation has to tell a malformed type from an unrecognised one,
@JsonTypeInfois not enough — Jackson coerces"type": 123to"123"— so it is replaced there by a hand-written deserializer. Worth knowing before weighing this decision on its own.Client-side condition evaluation is left unimplemented, per the ticket — it lands in the follow-up. The types are named for what they become there, so that PR is not a rename of everything introduced here.
Conditions are not in a
conditionssub-package as .NET has them: package access is not hierarchical, so package-private conditions there would be invisible toClientSideRule, and that layout would force every condition public. Noted inClientSideCondition's javadoc.TestObjectMapperis a test-only shim exposing the package-privateOctopusObjectMapper, so the v4 tests exercise the production mapper rather than a copy that could drift from it.Tests
ServerSideEvaluationDeserializationTests— 13 tests covering each condition type, mixed arrays, both response shapes, the unknown- and missing-discriminator fallbacks, a missing required property, extraneous properties, and list immutability.228 tests pass, 0 failures. Public API surface confirmed unchanged via
javap.🤖 Generated with Claude Code