1
1
import {
2
- EvaluationFailureErrorCode ,
3
2
EvaluationRequest ,
4
3
EvaluationResponse ,
5
4
OFREPApi ,
6
5
OFREPApiFetchError ,
7
6
OFREPApiTooManyRequestsError ,
8
7
OFREPApiUnauthorizedError ,
9
8
OFREPForbiddenError ,
10
- handleEvaluationError ,
11
9
isEvaluationFailureResponse ,
12
10
isEvaluationSuccessResponse ,
13
11
} from '@openfeature/ofrep-core' ;
14
12
import {
15
13
ClientProviderEvents ,
14
+ ErrorCode ,
16
15
EvaluationContext ,
17
- FlagMetadata ,
18
- FlagNotFoundError ,
19
16
FlagValue ,
20
17
GeneralError ,
21
18
Hook ,
22
- InvalidContextError ,
23
19
JsonValue ,
24
20
Logger ,
25
21
OpenFeatureError ,
26
22
OpenFeatureEventEmitter ,
27
- ParseError ,
28
23
Provider ,
29
24
ProviderFatalError ,
30
25
ResolutionDetails ,
31
26
StandardResolutionReasons ,
32
- TargetingKeyMissingError ,
33
- TypeMismatchError ,
34
27
} from '@openfeature/web-sdk' ;
35
28
import { BulkEvaluationStatus , EvaluateFlagsResponse } from './model/evaluate-flags-response' ;
36
- import { FlagCache } from './model/in-memory-cache' ;
29
+ import { FlagCache , MetadataCache } from './model/in-memory-cache' ;
37
30
import { OFREPWebProviderOptions } from './model/ofrep-web-provider-options' ;
38
31
import { isResolutionError } from './model/resolution-error' ;
39
32
33
+ const ErrorMessageMap : { [ key in ErrorCode ] : string } = {
34
+ [ ErrorCode . FLAG_NOT_FOUND ] : 'Flag was not found' ,
35
+ [ ErrorCode . GENERAL ] : 'General error' ,
36
+ [ ErrorCode . INVALID_CONTEXT ] : 'Context is invalid or could be parsed' ,
37
+ [ ErrorCode . PARSE_ERROR ] : 'Flag or flag configuration could not be parsed' ,
38
+ [ ErrorCode . PROVIDER_FATAL ] : 'Provider is in a fatal error state' ,
39
+ [ ErrorCode . PROVIDER_NOT_READY ] : 'Provider is not yet ready' ,
40
+ [ ErrorCode . TARGETING_KEY_MISSING ] : 'Targeting key is missing' ,
41
+ [ ErrorCode . TYPE_MISMATCH ] : 'Flag is not of expected type' ,
42
+ } ;
43
+
40
44
export class OFREPWebProvider implements Provider {
41
45
DEFAULT_POLL_INTERVAL = 30000 ;
42
46
@@ -52,10 +56,11 @@ export class OFREPWebProvider implements Provider {
52
56
// _options is the options used to configure the provider.
53
57
private _options : OFREPWebProviderOptions ;
54
58
private _ofrepAPI : OFREPApi ;
55
- private _etag : string | null ;
59
+ private _etag : string | null | undefined ;
56
60
private _pollingInterval : number ;
57
61
private _retryPollingAfter : Date | undefined ;
58
62
private _flagCache : FlagCache = { } ;
63
+ private _flagSetMetadataCache : MetadataCache = { } ;
59
64
private _context : EvaluationContext | undefined ;
60
65
private _pollingIntervalId ?: number ;
61
66
@@ -81,7 +86,7 @@ export class OFREPWebProvider implements Provider {
81
86
async initialize ( context ?: EvaluationContext | undefined ) : Promise < void > {
82
87
try {
83
88
this . _context = context ;
84
- await this . _evaluateFlags ( context ) ;
89
+ await this . _fetchFlags ( context ) ;
85
90
86
91
if ( this . _pollingInterval > 0 ) {
87
92
this . startPolling ( ) ;
@@ -102,30 +107,29 @@ export class OFREPWebProvider implements Provider {
102
107
defaultValue : boolean ,
103
108
context : EvaluationContext ,
104
109
) : ResolutionDetails < boolean > {
105
- return this . evaluate ( flagKey , 'boolean' ) ;
110
+ return this . _resolve ( flagKey , 'boolean' , defaultValue ) ;
106
111
}
107
112
resolveStringEvaluation (
108
113
flagKey : string ,
109
114
defaultValue : string ,
110
115
context : EvaluationContext ,
111
116
) : ResolutionDetails < string > {
112
- return this . evaluate ( flagKey , 'string' ) ;
117
+ return this . _resolve ( flagKey , 'string' , defaultValue ) ;
113
118
}
114
119
resolveNumberEvaluation (
115
120
flagKey : string ,
116
121
defaultValue : number ,
117
122
context : EvaluationContext ,
118
123
) : ResolutionDetails < number > {
119
- return this . evaluate ( flagKey , 'number' ) ;
124
+ return this . _resolve ( flagKey , 'number' , defaultValue ) ;
120
125
}
121
126
resolveObjectEvaluation < T extends JsonValue > (
122
127
flagKey : string ,
123
128
defaultValue : T ,
124
129
context : EvaluationContext ,
125
130
) : ResolutionDetails < T > {
126
- return this . evaluate ( flagKey , 'object' ) ;
131
+ return this . _resolve ( flagKey , 'object' , defaultValue ) ;
127
132
}
128
- /* eslint-enable @typescript-eslint/no-unused-vars */
129
133
130
134
/**
131
135
* onContextChange is called when the context changes, it will re-evaluate the flags with the new context
@@ -143,7 +147,7 @@ export class OFREPWebProvider implements Provider {
143
147
return ;
144
148
}
145
149
146
- await this . _evaluateFlags ( newContext ) ;
150
+ await this . _fetchFlags ( newContext ) ;
147
151
} catch ( error ) {
148
152
if ( error instanceof OFREPApiTooManyRequestsError ) {
149
153
this . events ?. emit ( ClientProviderEvents . Stale , { message : `${ error . name } : ${ error . message } ` } ) ;
@@ -172,7 +176,7 @@ export class OFREPWebProvider implements Provider {
172
176
}
173
177
174
178
/**
175
- * _evaluateFlags is a function that will call the bulk evaluate flags endpoint to get the flags values.
179
+ * _fetchFlags is a function that will call the bulk evaluate flags endpoint to get the flags values.
176
180
* @param context - the context to use for the evaluation
177
181
* @private
178
182
* @returns EvaluationStatus if the evaluation the API returned a 304, 200.
@@ -181,7 +185,7 @@ export class OFREPWebProvider implements Provider {
181
185
* @throws ParseError if the API returned a 400 with the error code ParseError
182
186
* @throws GeneralError if the API returned a 400 with an unknown error code
183
187
*/
184
- private async _evaluateFlags ( context ?: EvaluationContext | undefined ) : Promise < EvaluateFlagsResponse > {
188
+ private async _fetchFlags ( context ?: EvaluationContext | undefined ) : Promise < EvaluateFlagsResponse > {
185
189
try {
186
190
const evalReq : EvaluationRequest = {
187
191
context,
@@ -194,34 +198,40 @@ export class OFREPWebProvider implements Provider {
194
198
}
195
199
196
200
if ( response . httpStatus !== 200 ) {
197
- handleEvaluationError ( response ) ;
201
+ throw new GeneralError ( `Failed OFREP bulk evaluation request, status: ${ response . httpStatus } ` ) ;
198
202
}
199
203
200
204
const bulkSuccessResp = response . value ;
201
205
const newCache : FlagCache = { } ;
202
206
203
- bulkSuccessResp . flags ?. forEach ( ( evalResp : EvaluationResponse ) => {
204
- if ( isEvaluationFailureResponse ( evalResp ) ) {
205
- newCache [ evalResp . key ] = {
206
- errorCode : evalResp . errorCode ,
207
- errorDetails : evalResp . errorDetails ,
208
- reason : StandardResolutionReasons . ERROR ,
209
- } ;
210
- }
207
+ if ( 'flags' in bulkSuccessResp && Array . isArray ( bulkSuccessResp . flags ) ) {
208
+ bulkSuccessResp . flags . forEach ( ( evalResp : EvaluationResponse ) => {
209
+ if ( isEvaluationFailureResponse ( evalResp ) ) {
210
+ newCache [ evalResp . key ] = {
211
+ reason : StandardResolutionReasons . ERROR ,
212
+ flagMetadata : evalResp . metadata ,
213
+ errorCode : evalResp . errorCode ,
214
+ errorDetails : evalResp . errorDetails ,
215
+ } ;
216
+ }
211
217
212
- if ( isEvaluationSuccessResponse ( evalResp ) && evalResp . key ) {
213
- newCache [ evalResp . key ] = {
214
- value : evalResp . value ,
215
- flagMetadata : evalResp . metadata as FlagMetadata ,
216
- reason : evalResp . reason ,
217
- variant : evalResp . variant ,
218
- } ;
219
- }
220
- } ) ;
221
- const listUpdatedFlags = this . _getListUpdatedFlags ( this . _flagCache , newCache ) ;
222
- this . _flagCache = newCache ;
223
- this . _etag = response . httpResponse ?. headers . get ( 'etag' ) ;
224
- return { status : BulkEvaluationStatus . SUCCESS_WITH_CHANGES , flags : listUpdatedFlags } ;
218
+ if ( isEvaluationSuccessResponse ( evalResp ) && evalResp . key ) {
219
+ newCache [ evalResp . key ] = {
220
+ value : evalResp . value ,
221
+ variant : evalResp . variant ,
222
+ reason : evalResp . reason ,
223
+ flagMetadata : evalResp . metadata ,
224
+ } ;
225
+ }
226
+ } ) ;
227
+ const listUpdatedFlags = this . _getListUpdatedFlags ( this . _flagCache , newCache ) ;
228
+ this . _flagCache = newCache ;
229
+ this . _etag = response . httpResponse ?. headers . get ( 'etag' ) ;
230
+ this . _flagSetMetadataCache = typeof bulkSuccessResp . metadata === 'object' ? bulkSuccessResp . metadata : { } ;
231
+ return { status : BulkEvaluationStatus . SUCCESS_WITH_CHANGES , flags : listUpdatedFlags } ;
232
+ } else {
233
+ throw new Error ( 'No flags in OFREP bulk evaluation response' ) ;
234
+ }
225
235
} catch ( error ) {
226
236
if ( error instanceof OFREPApiTooManyRequestsError && error . retryAfterDate !== null ) {
227
237
this . _retryPollingAfter = error . retryAfterDate ;
@@ -260,37 +270,41 @@ export class OFREPWebProvider implements Provider {
260
270
}
261
271
262
272
/**
263
- * Evaluate is a function retrieving the value from a flag in the cache.
273
+ * _resolve is a function retrieving the value from a flag in the cache.
264
274
* @param flagKey - name of the flag to retrieve
265
275
* @param type - type of the flag
276
+ * @param defaultValue - default value
266
277
* @private
267
278
*/
268
- private evaluate < T extends FlagValue > ( flagKey : string , type : string ) : ResolutionDetails < T > {
279
+ private _resolve < T extends FlagValue > ( flagKey : string , type : string , defaultValue : T ) : ResolutionDetails < T > {
269
280
const resolved = this . _flagCache [ flagKey ] ;
281
+
270
282
if ( ! resolved ) {
271
- throw new FlagNotFoundError ( `flag key ${ flagKey } not found in cache` ) ;
283
+ return {
284
+ value : defaultValue ,
285
+ flagMetadata : this . _flagSetMetadataCache ,
286
+ reason : StandardResolutionReasons . ERROR ,
287
+ errorCode : ErrorCode . FLAG_NOT_FOUND ,
288
+ errorMessage : ErrorMessageMap [ ErrorCode . FLAG_NOT_FOUND ] ,
289
+ } ;
272
290
}
273
291
274
292
if ( isResolutionError ( resolved ) ) {
275
- switch ( resolved . errorCode ) {
276
- case EvaluationFailureErrorCode . FlagNotFound :
277
- throw new FlagNotFoundError ( `flag key ${ flagKey } not found: ${ resolved . errorDetails } ` ) ;
278
- case EvaluationFailureErrorCode . TargetingKeyMissing :
279
- throw new TargetingKeyMissingError ( `targeting key missing for flag key ${ flagKey } : ${ resolved . errorDetails } ` ) ;
280
- case EvaluationFailureErrorCode . InvalidContext :
281
- throw new InvalidContextError ( `invalid context for flag key ${ flagKey } : ${ resolved . errorDetails } ` ) ;
282
- case EvaluationFailureErrorCode . ParseError :
283
- throw new ParseError ( `parse error for flag key ${ flagKey } : ${ resolved . errorDetails } ` ) ;
284
- case EvaluationFailureErrorCode . General :
285
- default :
286
- throw new GeneralError (
287
- `general error during flag evaluation for flag key ${ flagKey } : ${ resolved . errorDetails } ` ,
288
- ) ;
289
- }
293
+ return {
294
+ ...resolved ,
295
+ value : defaultValue ,
296
+ errorMessage : ErrorMessageMap [ resolved . errorCode ] ,
297
+ } ;
290
298
}
291
299
292
300
if ( typeof resolved . value !== type ) {
293
- throw new TypeMismatchError ( `flag key ${ flagKey } is not of type ${ type } ` ) ;
301
+ return {
302
+ value : defaultValue ,
303
+ flagMetadata : resolved . flagMetadata ,
304
+ reason : StandardResolutionReasons . ERROR ,
305
+ errorCode : ErrorCode . TYPE_MISMATCH ,
306
+ errorMessage : ErrorMessageMap [ ErrorCode . TYPE_MISMATCH ] ,
307
+ } ;
294
308
}
295
309
296
310
return {
@@ -314,7 +328,7 @@ export class OFREPWebProvider implements Provider {
314
328
if ( this . _retryPollingAfter !== undefined && this . _retryPollingAfter > now ) {
315
329
return ;
316
330
}
317
- const res = await this . _evaluateFlags ( this . _context ) ;
331
+ const res = await this . _fetchFlags ( this . _context ) ;
318
332
if ( res . status === BulkEvaluationStatus . SUCCESS_WITH_CHANGES ) {
319
333
this . events ?. emit ( ClientProviderEvents . ConfigurationChanged , {
320
334
message : 'Flags updated' ,
0 commit comments