-
Notifications
You must be signed in to change notification settings - Fork 61
Expand file tree
/
Copy pathFeatureProvider.java
More file actions
256 lines (232 loc) · 10.7 KB
/
Copy pathFeatureProvider.java
File metadata and controls
256 lines (232 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
package dev.openfeature.sdk;
import java.util.ArrayList;
import java.util.List;
/**
* The interface implemented by upstream flag providers to resolve flags for
* their service. If you want to support realtime events with your provider, you
* 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;
}
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);
/**
* 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.
*
* @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 before a provider is used to evaluate flags, with the
* bound domain supplied when the provider is registered to a named client.
*
* <p>
* The default provider is initialized with a {@code null} domain. Providers that
* maintain per-domain state (for example a persistent cache) should override this
* method and declare themselves {@linkplain #isDomainScoped() domain-scoped}.
* </p>
*
* @param evaluationContext the global evaluation context
* @param domain the bound domain, or {@code null} for the default provider
*/
default void initialize(EvaluationContext evaluationContext, String domain) throws Exception {
initialize(evaluationContext);
}
/**
* Returns whether this provider maintains state specific to a single domain that
* cannot be shared across domains.
*
* <p>
* Domain-scoped providers may only be bound to one domain within a single API
* instance.
* </p>
*
* @return {@code true} if this provider is domain-scoped
*/
default boolean isDomainScoped() {
return false;
}
/**
* Called when a provider is about to be replaced or the SDK is shutting down. Providers can
* override this method if they have resources to release (background threads, connections,
* caches, etc.).
*
* <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
}
/**
* Returns a representation of the current readiness of the provider.
* If the provider needs to be initialized, it should return {@link ProviderState#NOT_READY}.
* If the provider is in an error state, it should return {@link ProviderState#ERROR}.
* If the provider is functioning normally, it should return {@link ProviderState#READY}.
*
* <p><i>Providers which do not implement this method are assumed to be ready immediately.</i></p>
*
* @return ProviderState
* @deprecated The state is handled by the SDK internally. Query the state from the {@link Client} instead.
*/
@Deprecated
default ProviderState getState() {
return ProviderState.READY;
}
/**
* Feature provider implementations can opt in for to support Tracking by implementing this method.
*
* @param eventName The name of the tracking event
* @param context Evaluation context used in flag evaluation (Optional)
* @param details Data pertinent to a particular tracking event (Optional)
*/
default void track(String eventName, EvaluationContext context, TrackingEventDetails details) {}
}