-
Notifications
You must be signed in to change notification settings - Fork 58
feat: long support; safe delegation to int #1985
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
base: main
Are you sure you want to change the base?
Changes from all commits
3f1d869
62f073f
20653c2
bdbe524
c931153
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 |
|---|---|---|
|
|
@@ -9,49 +9,189 @@ | |
| * should extend {@link EventProvider} | ||
| */ | ||
| public interface FeatureProvider { | ||
|
|
||
| /** Maximum 64 bit integer losslessly representable as an IEEE-754 double: 2^53 - 1. */ | ||
| long MAX_SAFE_INTEGER = 9_007_199_254_740_991L; | ||
|
|
||
| /** | ||
| * Returns provider-identifying metadata (typically the provider name). | ||
| * | ||
| * @return provider metadata | ||
| */ | ||
| Metadata getMetadata(); | ||
|
|
||
| /** | ||
| * Returns provider-defined hooks that run alongside API/client/invocation hooks during | ||
| * flag evaluation. Provider hooks are managed by the provider, not the application author. | ||
| * | ||
| * @return list of provider hooks; empty by default | ||
| */ | ||
| default List<Hook> getProviderHooks() { | ||
| return new ArrayList<>(); | ||
| } | ||
|
|
||
| /** | ||
| * Resolves a boolean flag value. | ||
| * | ||
| * @param key flag key | ||
| * @param defaultValue value to return in the {@link ProviderEvaluation} if resolution fails | ||
| * @param ctx merged evaluation context (may be empty, never {@code null}) | ||
| * @return provider evaluation containing the resolved value or an error | ||
| */ | ||
| ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx); | ||
|
|
||
| /** | ||
| * Resolves a string flag value. | ||
| * | ||
| * @param key flag key | ||
| * @param defaultValue value to return in the {@link ProviderEvaluation} if resolution fails | ||
| * @param ctx merged evaluation context (may be empty, never {@code null}) | ||
| * @return provider evaluation containing the resolved value or an error | ||
| */ | ||
| ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx); | ||
|
|
||
| /** | ||
| * Resolves a 32-bit integer flag value. For flags whose values may exceed | ||
| * {@link Integer#MAX_VALUE}, use {@link #getLongEvaluation} instead. | ||
| * | ||
| * @param key flag key | ||
| * @param defaultValue value to return in the {@link ProviderEvaluation} if resolution fails | ||
| * @param ctx merged evaluation context (may be empty, never {@code null}) | ||
| * @return provider evaluation containing the resolved value or an error | ||
| */ | ||
| ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx); | ||
|
|
||
| /** | ||
| * Resolves a double-precision floating-point flag value. | ||
| * | ||
| * @param key flag key | ||
| * @param defaultValue value to return in the {@link ProviderEvaluation} if resolution fails | ||
| * @param ctx merged evaluation context (may be empty, never {@code null}) | ||
| * @return provider evaluation containing the resolved value or an error | ||
| */ | ||
| ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx); | ||
|
|
||
| /** | ||
| * Resolves a 64-bit integer (Long) flag value. | ||
| * | ||
| * <p>The default implementation delegates to {@link #getDoubleEvaluation} and returns a | ||
| * {@link ProviderEvaluation} with {@link ErrorCode#TYPE_MISMATCH} for values outside the | ||
| * safe-integer range ({@code [-(2^53 - 1), 2^53 - 1]}) or non-integral doubles (NaN, | ||
| * +/-Infinity, fractional). Providers that natively support 64-bit integer flags should | ||
| * override this method. | ||
| * | ||
| * @param key flag key | ||
| * @param defaultValue value to return in the {@link ProviderEvaluation} if resolution fails | ||
| * @param ctx merged evaluation context (may be empty, never {@code null}) | ||
| * @return provider evaluation containing the resolved value or an error | ||
| */ | ||
| default ProviderEvaluation<Long> getLongEvaluation(String key, Long defaultValue, EvaluationContext ctx) { | ||
| if (defaultValue != null && !isWithinSafeRange(defaultValue)) { | ||
| return longError( | ||
| defaultValue, | ||
| "Default value " + defaultValue | ||
| + " exceeds safe integer range [-(2^53 - 1), 2^53 - 1] for double-backed long evaluation"); | ||
| } | ||
|
|
||
| Double doubleDefault = defaultValue == null ? null : (double) defaultValue; | ||
| ProviderEvaluation<Double> result = getDoubleEvaluation(key, doubleDefault, ctx); | ||
|
|
||
| Double boxed = result.getValue(); | ||
| Long longValue; | ||
| if (boxed == null) { | ||
| longValue = defaultValue; | ||
| } else { | ||
| double value = boxed; | ||
| if (Double.isNaN(value) || Double.isInfinite(value)) { | ||
| return longError(defaultValue, "Cannot convert " + value + " to long", result); | ||
| } | ||
| if (value != Math.floor(value)) { | ||
| return longError(defaultValue, "Cannot convert fractional value " + value + " to long", result); | ||
| } | ||
| if (!isWithinSafeRange(value)) { | ||
| return longError( | ||
| defaultValue, | ||
| "Value " + value + " exceeds safe integer range [-(2^53 - 1), 2^53 - 1] for long", | ||
| result); | ||
| } | ||
| longValue = (long) value; | ||
| } | ||
|
|
||
| return ProviderEvaluation.<Long>builder() | ||
| .value(longValue) | ||
| .reason(result.getReason()) | ||
| .variant(result.getVariant()) | ||
| .errorCode(result.getErrorCode()) | ||
| .errorMessage(result.getErrorMessage()) | ||
| .flagMetadata(result.getFlagMetadata()) | ||
| .build(); | ||
| } | ||
|
|
||
| // avoid Math.abs; Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE (two's-complement overflow) | ||
| private static boolean isWithinSafeRange(long value) { | ||
| return value >= -MAX_SAFE_INTEGER && value <= MAX_SAFE_INTEGER; | ||
| } | ||
|
|
||
| private static boolean isWithinSafeRange(double value) { | ||
| return value >= -(double) MAX_SAFE_INTEGER && value <= (double) MAX_SAFE_INTEGER; | ||
|
Check warning on line 136 in src/main/java/dev/openfeature/sdk/FeatureProvider.java
|
||
| } | ||
|
|
||
| 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(); | ||
| } | ||
|
|
||
| /** | ||
| * Resolves a structured (object) flag value. Values are wrapped in {@link Value} which can | ||
| * carry booleans, strings, numbers, structures, and lists. | ||
| * | ||
| * @param key flag key | ||
| * @param defaultValue value to return in the {@link ProviderEvaluation} if resolution fails | ||
| * @param ctx merged evaluation context (may be empty, never {@code null}) | ||
| * @return provider evaluation containing the resolved value or an error | ||
| */ | ||
| ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx); | ||
|
|
||
| /** | ||
| * This method is called before a provider is used to evaluate flags. Providers | ||
| * can overwrite this method, | ||
| * if they have special initialization needed prior being called for flag | ||
| * evaluation. | ||
| * Called once before a provider is used to evaluate flags. Providers can override this method | ||
| * if they have special initialization needed prior to being called for flag evaluation. | ||
| * | ||
| * <p>It is ok if the method is expensive; it is executed in the background. All runtime | ||
| * exceptions will be caught and logged. | ||
| * | ||
| * <p> | ||
| * It is ok if the method is expensive as it is executed in the background. All | ||
| * runtime exceptions will be | ||
| * caught and logged. | ||
| * </p> | ||
| * @param evaluationContext the API-level evaluation context at the time of initialization | ||
| * @throws Exception any exception thrown here transitions the provider to | ||
| * {@link ProviderState#ERROR} (or {@link ProviderState#FATAL} for | ||
| * {@link dev.openfeature.sdk.exceptions.FatalError}) | ||
| */ | ||
| default void initialize(EvaluationContext evaluationContext) throws Exception { | ||
| // Intentionally left blank | ||
| } | ||
|
|
||
| /** | ||
| * This method is called when a new provider is about to be used to evaluate | ||
| * flags, or the SDK is shut down. | ||
| * Providers can overwrite this method, if they have special shutdown actions | ||
| * needed. | ||
| * Called when a provider is about to be replaced or the SDK is shutting down. Providers can | ||
|
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. Why do we rewrite all the docs?
Member
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 noticed a lot of the Java-docs were missing, even for public APIs (for example here) so I took this chance to update them. I can revert it if you want, or do it in a separate PR. |
||
| * override this method if they have resources to release (background threads, connections, | ||
| * caches, etc.). | ||
| * | ||
| * <p> | ||
| * It is ok if the method is expensive as it is executed in the background. All | ||
| * runtime exceptions will be | ||
| * caught and logged. | ||
| * </p> | ||
| * <p>It is ok if the method is expensive; it is executed in the background. All runtime | ||
| * exceptions will be caught and logged. | ||
| */ | ||
| default void shutdown() { | ||
| // Intentionally left blank | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
| public enum FlagValueType { | ||
| STRING, | ||
| INTEGER, | ||
| LONG, | ||
| DOUBLE, | ||
| OBJECT, | ||
| BOOLEAN; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package dev.openfeature.sdk; | ||
|
|
||
| /** | ||
| * An extension point which can run around flag resolution. They are intended to be used as a way to add custom logic | ||
| * to the lifecycle of flag evaluation. | ||
| * | ||
| * @see Hook | ||
| */ | ||
| public interface LongHook extends Hook<Long> { | ||
|
|
||
| @Override | ||
| default boolean supportsFlagValueType(FlagValueType flagValueType) { | ||
| return FlagValueType.LONG == flagValueType; | ||
| } | ||
| } |
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.
Why do we add all that Java Doc?
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 as #1985 (comment)