Skip to content

feat: long support; safe delegation to int#1985

Open
toddbaert wants to merge 2 commits into
mainfrom
feat/long
Open

feat: long support; safe delegation to int#1985
toddbaert wants to merge 2 commits into
mainfrom
feat/long

Conversation

@toddbaert

Copy link
Copy Markdown
Member
  • adds long support to client/provider
  • delegates to provider int resolver by default
    • widens, rejecting unsafe defaults
    • providers supporting Long should implement manually

Closes: #501

@toddbaert toddbaert requested review from a team as code owners July 3, 2026 17:47
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@toddbaert, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 26 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 15ab4776-425f-4a75-8bfe-5f749930f60f

📥 Commits

Reviewing files that changed from the base of the PR and between 835784e and 62f073f.

📒 Files selected for processing (15)
  • src/main/java/dev/openfeature/sdk/Client.java
  • src/main/java/dev/openfeature/sdk/FeatureProvider.java
  • src/main/java/dev/openfeature/sdk/Features.java
  • src/main/java/dev/openfeature/sdk/FlagValueType.java
  • src/main/java/dev/openfeature/sdk/LongHook.java
  • src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
  • src/main/java/dev/openfeature/sdk/Value.java
  • src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java
  • src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java
  • src/test/java/dev/openfeature/sdk/HookSupportTest.java
  • src/test/java/dev/openfeature/sdk/LongDefaultDelegationTest.java
  • src/test/java/dev/openfeature/sdk/LongHookTest.java
  • src/test/java/dev/openfeature/sdk/fixtures/HookFixtures.java
  • src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java
  • src/test/java/dev/openfeature/sdk/testutils/testProvider/TestProvider.java
📝 Walkthrough

Walkthrough

This PR adds LONG as a supported flag value type across the SDK. It introduces a FlagValueType.LONG constant, Value support for Long, a LongHook interface, Features/Client API additions (getLongValue/getLongDetails), a default getLongEvaluation in FeatureProvider derived from getDoubleEvaluation with safe-integer validation, OpenFeatureClient routing, and InMemoryProvider/TestProvider implementations with type-coercion helpers, plus corresponding tests.

Changes

Long Flag Value Type Support

Layer / File(s) Summary
LONG type, Value wrapping, and public API declarations
src/main/java/dev/openfeature/sdk/FlagValueType.java, src/main/java/dev/openfeature/sdk/Value.java, src/main/java/dev/openfeature/sdk/LongHook.java, src/main/java/dev/openfeature/sdk/Client.java, src/main/java/dev/openfeature/sdk/Features.java, src/test/java/dev/openfeature/sdk/fixtures/HookFixtures.java, src/test/java/dev/openfeature/sdk/LongHookTest.java
Adds LONG enum value, Value(Long) constructor/asLong()/objectToValue support, the LongHook interface, API-note Javadocs, new getLongValue/getLongDetails declarations, a mockLongHook() fixture, and tests verifying LongHook.supportsFlagValueType.
FeatureProvider default getLongEvaluation delegation
src/main/java/dev/openfeature/sdk/FeatureProvider.java, src/test/java/dev/openfeature/sdk/LongDefaultDelegationTest.java
Adds MAX_SAFE_INTEGER, a default getLongEvaluation that delegates to getDoubleEvaluation with safe-range/non-integral/non-finite validation and private error/range helpers, plus tests covering successful conversion, error propagation, bound violations, and non-integer doubles.
OpenFeatureClient LONG routing and accessor methods
src/main/java/dev/openfeature/sdk/OpenFeatureClient.java, src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java, src/test/java/dev/openfeature/sdk/HookSupportTest.java
Wires FlagValueType.LONG into createProviderEvaluation, adds getLongValue/getLongDetails overloads, and extends spec/hook-support tests to cover LONG flags.
InMemoryProvider/TestProvider Long support and type coercion
src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java, src/test/java/dev/openfeature/sdk/testutils/testProvider/TestProvider.java, src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java
Adds getLongEvaluation overrides and isAssignableTo/coerceVariant helpers to widen Integer to Long while rejecting incompatible types, with tests for native, widened, and mismatched type conversions.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant OpenFeatureClient
  participant FeatureProvider
  participant DoubleEvaluationSource
  Caller->>OpenFeatureClient: getLongValue(key, default, ctx, options)
  OpenFeatureClient->>OpenFeatureClient: evaluateFlag(FlagValueType.LONG, ...)
  OpenFeatureClient->>FeatureProvider: getLongEvaluation(key, default, ctx)
  alt provider overrides getLongEvaluation
    FeatureProvider-->>OpenFeatureClient: ProviderEvaluation<Long>
  else default delegation
    FeatureProvider->>DoubleEvaluationSource: getDoubleEvaluation(key, doubleDefault, ctx)
    DoubleEvaluationSource-->>FeatureProvider: ProviderEvaluation<Double>
    FeatureProvider->>FeatureProvider: validate finite/integral/safe-range
    FeatureProvider-->>OpenFeatureClient: ProviderEvaluation<Long>
  end
  OpenFeatureClient-->>Caller: Long value
Loading

Related issues: #501 — requests a way to fetch Long-typed flag values, addressed by the new getLongValue/getLongDetails APIs.

Suggested labels: enhancement, api

Suggested reviewers: none identified from provided context

🐰 A long-tailed rabbit hops with glee,
from double bits it counts so free,
safe ranges checked, no NaN in sight,
a brand new flag type takes its flight!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding Long support with safe delegation to integer resolution.
Description check ✅ Passed The description matches the PR and mentions Long support, safe widening, and manual provider handling.
Linked Issues check ✅ Passed The PR adds Long retrieval support requested by #501 through new client and provider APIs.
Out of Scope Changes check ✅ Passed All changes stay within Long support, delegation, and related tests/docs; no unrelated scope stands out.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java (1)

159-176: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate the context-evaluator result before returning it. The targeting path still returns flag.getContextEvaluator().evaluate(...) directly, so an Integer can escape as the value of a ProviderEvaluation<Long> without the isAssignableTo/coerceVariant widening used on the default-variant path. Apply the same check/coercion here or fail with TypeMismatchError.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java`
around lines 159 - 176, The targeting branch in InMemoryProvider’s evaluation
logic returns the raw result from flag.getContextEvaluator().evaluate(...)
without validating or widening it like the default-variant path. Update the
context-evaluator flow to run the same isAssignableTo check and coerceVariant
conversion before assigning value, or throw TypeMismatchError when the evaluated
variant cannot satisfy the expected type. Use the evaluate helper path and the
existing type-handling logic in InMemoryProvider to keep behavior consistent
across targeting and default resolution.
🧹 Nitpick comments (1)
src/main/java/dev/openfeature/sdk/FeatureProvider.java (1)

130-155: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor duplication: reuse isWithinSafeRange and consolidate longError overloads.

Math.abs(value) > MAX_SAFE_INTEGER re-implements the same range check as isWithinSafeRange(long) (once value is confirmed integral, (long) value is safe to pass). Also, the two longError overloads duplicate most builder fields, differing only in variant/flagMetadata.

♻️ Optional consolidation
-            if (Math.abs(value) > MAX_SAFE_INTEGER) {
+            if (!isWithinSafeRange((long) value)) {
                 return longError(
                         defaultValue,
                         "Value " + value + " exceeds safe integer range [-(2^53 - 1), 2^53 - 1] for long",
                         result);
             }
...
-    private static ProviderEvaluation<Long> longError(Long defaultValue, String message) {
-        return ProviderEvaluation.<Long>builder()
-                .value(defaultValue)
-                .reason(Reason.ERROR.toString())
-                .errorCode(ErrorCode.TYPE_MISMATCH)
-                .errorMessage(message)
-                .build();
-    }
-
-    // preserve upstream metadata/variant; override with type error
-    private static ProviderEvaluation<Long> longError(
-            Long defaultValue, String message, ProviderEvaluation<Double> upstream) {
-        return ProviderEvaluation.<Long>builder()
-                .value(defaultValue)
-                .reason(Reason.ERROR.toString())
-                .errorCode(ErrorCode.TYPE_MISMATCH)
-                .errorMessage(message)
-                .variant(upstream.getVariant())
-                .flagMetadata(upstream.getFlagMetadata())
-                .build();
-    }
+    private static ProviderEvaluation<Long> longError(Long defaultValue, String message) {
+        return longError(defaultValue, message, null);
+    }
+
+    // preserve upstream metadata/variant when available; override with type error
+    private static ProviderEvaluation<Long> longError(
+            Long defaultValue, String message, ProviderEvaluation<Double> upstream) {
+        var builder = ProviderEvaluation.<Long>builder()
+                .value(defaultValue)
+                .reason(Reason.ERROR.toString())
+                .errorCode(ErrorCode.TYPE_MISMATCH)
+                .errorMessage(message);
+        if (upstream != null) {
+            builder.variant(upstream.getVariant()).flagMetadata(upstream.getFlagMetadata());
+        }
+        return builder.build();
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/dev/openfeature/sdk/FeatureProvider.java` around lines 130 -
155, The `FeatureProvider` helpers duplicate range and error-building logic.
Update the integral conversion path to call `isWithinSafeRange(long)` instead of
rechecking with `Math.abs`, and consolidate the two `longError` overloads by
sharing the common `ProviderEvaluation` builder setup in one helper while
preserving the optional `variant` and `flagMetadata` from the upstream value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java`:
- Line 173: The error message in InMemoryProvider’s type mismatch path is
missing a space before “is”, causing malformed output. Update the
TypeMismatchError message in the relevant flag-checking logic so the
concatenated text reads naturally, using the same key variable and error
construction in InMemoryProvider.

---

Outside diff comments:
In `@src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java`:
- Around line 159-176: The targeting branch in InMemoryProvider’s evaluation
logic returns the raw result from flag.getContextEvaluator().evaluate(...)
without validating or widening it like the default-variant path. Update the
context-evaluator flow to run the same isAssignableTo check and coerceVariant
conversion before assigning value, or throw TypeMismatchError when the evaluated
variant cannot satisfy the expected type. Use the evaluate helper path and the
existing type-handling logic in InMemoryProvider to keep behavior consistent
across targeting and default resolution.

---

Nitpick comments:
In `@src/main/java/dev/openfeature/sdk/FeatureProvider.java`:
- Around line 130-155: The `FeatureProvider` helpers duplicate range and
error-building logic. Update the integral conversion path to call
`isWithinSafeRange(long)` instead of rechecking with `Math.abs`, and consolidate
the two `longError` overloads by sharing the common `ProviderEvaluation` builder
setup in one helper while preserving the optional `variant` and `flagMetadata`
from the upstream value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 56880d00-46b7-4979-b108-be72f986954a

📥 Commits

Reviewing files that changed from the base of the PR and between a7d4e8a and 835784e.

📒 Files selected for processing (15)
  • src/main/java/dev/openfeature/sdk/Client.java
  • src/main/java/dev/openfeature/sdk/FeatureProvider.java
  • src/main/java/dev/openfeature/sdk/Features.java
  • src/main/java/dev/openfeature/sdk/FlagValueType.java
  • src/main/java/dev/openfeature/sdk/LongHook.java
  • src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
  • src/main/java/dev/openfeature/sdk/Value.java
  • src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java
  • src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java
  • src/test/java/dev/openfeature/sdk/HookSupportTest.java
  • src/test/java/dev/openfeature/sdk/LongDefaultDelegationTest.java
  • src/test/java/dev/openfeature/sdk/LongHookTest.java
  • src/test/java/dev/openfeature/sdk/fixtures/HookFixtures.java
  • src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java
  • src/test/java/dev/openfeature/sdk/testutils/testProvider/TestProvider.java

Comment thread src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java Outdated
Comment thread src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java Outdated
@toddbaert

Copy link
Copy Markdown
Member Author

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)

src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java (1)> 159-176: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate the context-evaluator result before returning it. The targeting path still returns flag.getContextEvaluator().evaluate(...) directly, so an Integer can escape as the value of a ProviderEvaluation<Long> without the isAssignableTo/coerceVariant widening used on the default-variant path. Apply the same check/coercion here or fail with TypeMismatchError.

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java`
around lines 159 - 176, The targeting branch in InMemoryProvider’s evaluation
logic returns the raw result from flag.getContextEvaluator().evaluate(...)
without validating or widening it like the default-variant path. Update the
context-evaluator flow to run the same isAssignableTo check and coerceVariant
conversion before assigning value, or throw TypeMismatchError when the evaluated
variant cannot satisfy the expected type. Use the evaluate helper path and the
existing type-handling logic in InMemoryProvider to keep behavior consistent
across targeting and default resolution.

🧹 Nitpick comments (1)

src/main/java/dev/openfeature/sdk/FeatureProvider.java (1)> 130-155: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor duplication: reuse isWithinSafeRange and consolidate longError overloads.
Math.abs(value) > MAX_SAFE_INTEGER re-implements the same range check as isWithinSafeRange(long) (once value is confirmed integral, (long) value is safe to pass). Also, the two longError overloads duplicate most builder fields, differing only in variant/flagMetadata.

♻️ Optional consolidation

-            if (Math.abs(value) > MAX_SAFE_INTEGER) {
+            if (!isWithinSafeRange((long) value)) {
                 return longError(
                         defaultValue,
                         "Value " + value + " exceeds safe integer range [-(2^53 - 1), 2^53 - 1] for long",
                         result);
             }
...
-    private static ProviderEvaluation<Long> longError(Long defaultValue, String message) {
-        return ProviderEvaluation.<Long>builder()
-                .value(defaultValue)
-                .reason(Reason.ERROR.toString())
-                .errorCode(ErrorCode.TYPE_MISMATCH)
-                .errorMessage(message)
-                .build();
-    }
-
-    // preserve upstream metadata/variant; override with type error
-    private static ProviderEvaluation<Long> longError(
-            Long defaultValue, String message, ProviderEvaluation<Double> upstream) {
-        return ProviderEvaluation.<Long>builder()
-                .value(defaultValue)
-                .reason(Reason.ERROR.toString())
-                .errorCode(ErrorCode.TYPE_MISMATCH)
-                .errorMessage(message)
-                .variant(upstream.getVariant())
-                .flagMetadata(upstream.getFlagMetadata())
-                .build();
-    }
+    private static ProviderEvaluation<Long> longError(Long defaultValue, String message) {
+        return longError(defaultValue, message, null);
+    }
+
+    // preserve upstream metadata/variant when available; override with type error
+    private static ProviderEvaluation<Long> longError(
+            Long defaultValue, String message, ProviderEvaluation<Double> upstream) {
+        var builder = ProviderEvaluation.<Long>builder()
+                .value(defaultValue)
+                .reason(Reason.ERROR.toString())
+                .errorCode(ErrorCode.TYPE_MISMATCH)
+                .errorMessage(message);
+        if (upstream != null) {
+            builder.variant(upstream.getVariant()).flagMetadata(upstream.getFlagMetadata());
+        }
+        return builder.build();
+    }

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/dev/openfeature/sdk/FeatureProvider.java` around lines 130 -
155, The `FeatureProvider` helpers duplicate range and error-building logic.
Update the integral conversion path to call `isWithinSafeRange(long)` instead of
rechecking with `Math.abs`, and consolidate the two `longError` overloads by
sharing the common `ProviderEvaluation` builder setup in one helper while
preserving the optional `variant` and `flagMetadata` from the upstream value.

🤖 Prompt for all review comments with AI agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java`:
- Line 173: The error message in InMemoryProvider’s type mismatch path is
missing a space before “is”, causing malformed output. Update the
TypeMismatchError message in the relevant flag-checking logic so the
concatenated text reads naturally, using the same key variable and error
construction in InMemoryProvider.

---

Outside diff comments:
In `@src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java`:
- Around line 159-176: The targeting branch in InMemoryProvider’s evaluation
logic returns the raw result from flag.getContextEvaluator().evaluate(...)
without validating or widening it like the default-variant path. Update the
context-evaluator flow to run the same isAssignableTo check and coerceVariant
conversion before assigning value, or throw TypeMismatchError when the evaluated
variant cannot satisfy the expected type. Use the evaluate helper path and the
existing type-handling logic in InMemoryProvider to keep behavior consistent
across targeting and default resolution.

---

Nitpick comments:
In `@src/main/java/dev/openfeature/sdk/FeatureProvider.java`:
- Around line 130-155: The `FeatureProvider` helpers duplicate range and
error-building logic. Update the integral conversion path to call
`isWithinSafeRange(long)` instead of rechecking with `Math.abs`, and consolidate
the two `longError` overloads by sharing the common `ProviderEvaluation` builder
setup in one helper while preserving the optional `variant` and `flagMetadata`
from the upstream value.

🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info

Added fix and related test.

toddbaert added 2 commits July 3, 2026 14:21
* adds long support to client/provider
* delegates to provider int resolver by default
  * widens, rejecting unsafe defaults
  * providers supporting Long should implement manually

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
@sonarqubecloud

sonarqubecloud Bot commented Jul 3, 2026

Copy link
Copy Markdown

@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.33333% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.84%. Comparing base (a7d4e8a) to head (62f073f).

Files with missing lines Patch % Lines
src/main/java/dev/openfeature/sdk/Value.java 0.00% 7 Missing and 1 partial ⚠️
...feature/sdk/providers/memory/InMemoryProvider.java 85.00% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #1985      +/-   ##
============================================
+ Coverage     92.23%   92.84%   +0.60%     
- Complexity      669      704      +35     
============================================
  Files            59       60       +1     
  Lines          1635     1704      +69     
  Branches        186      195       +9     
============================================
+ Hits           1508     1582      +74     
+ Misses           80       74       -6     
- Partials         47       48       +1     
Flag Coverage Δ
unittests 92.84% <85.33%> (+0.60%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

Why the SDK does not provide a way to fetch Long value?

1 participant