Skip to content

feat: add v4 evaluation response types to provider library - #52

Merged
lourens-octopus merged 3 commits into
mainfrom
lourens/bmbb-747
Aug 10, 2026
Merged

feat: add v4 evaluation response types to provider library#52
lourens-octopus merged 3 commits into
mainfrom
lourens/bmbb-747

Conversation

@lourens-octopus

@lourens-octopus lourens-octopus commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

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 of ServerSideEvaluation); OctoToggle resolves the server-side conditions itself and sends only the client-side ones, keyed by a camelCase type discriminator.

New in src/main/java/com/octopus/openfeature/provider/v4/: ServerSideEvaluation (server-resolved value/reason, or deferred evaluationKey/rules), ClientSideRule, the polymorphic ClientSideCondition and its three variants, UnknownCondition, and ConditionTypeNames.

Decisions

  • Types are package-private in a v4 sub-package — Java's nearest equivalent to internal. The public API is unchanged: still just OctopusProvider, OctopusConfiguration and ProductMetadata.

    Worth knowing for the follow-up, since Java package access isn't hierarchical: …provider cannot see these types. This assumes the v4 evaluation logic will live in the v4 package 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 UnknownCondition rather 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, @JsonTypeInfo is not enough — Jackson coerces "type": 123 to "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 conditions sub-package as .NET has them: package access is not hierarchical, so package-private conditions there would be invisible to ClientSideRule, and that layout would force every condition public. Noted in ClientSideCondition's javadoc.

  • TestObjectMapper is a test-only shim exposing the package-private OctopusObjectMapper, 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.

Unrelated to this PR: the POM doesn't pin maven-surefire-plugin, so mvn test can silently run zero tests on Maven builds whose default surefire can't discover JUnit 5. Worth pinning separately.

🤖 Generated with Claude Code

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 liamhughes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved with comments. 👍

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we need to worry about being strict around capitalisation of JSON property names? This test and the next.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

lourens-octopus and others added 2 commits August 11, 2026 07:37
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>
@lourens-octopus
lourens-octopus merged commit 7e12fa7 into main Aug 10, 2026
8 checks passed
@lourens-octopus
lourens-octopus deleted the lourens/bmbb-747 branch August 10, 2026 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants