11import {
2- EvaluationFailureErrorCode ,
32 EvaluationRequest ,
43 EvaluationResponse ,
54 OFREPApi ,
65 OFREPApiFetchError ,
76 OFREPApiTooManyRequestsError ,
87 OFREPApiUnauthorizedError ,
98 OFREPForbiddenError ,
10- handleEvaluationError ,
119 isEvaluationFailureResponse ,
1210 isEvaluationSuccessResponse ,
1311} from '@openfeature/ofrep-core' ;
1412import {
1513 ClientProviderEvents ,
14+ ErrorCode ,
1615 EvaluationContext ,
17- FlagMetadata ,
18- FlagNotFoundError ,
1916 FlagValue ,
20- GeneralError ,
2117 Hook ,
22- InvalidContextError ,
2318 JsonValue ,
2419 Logger ,
2520 OpenFeatureError ,
2621 OpenFeatureEventEmitter ,
27- ParseError ,
2822 Provider ,
2923 ProviderFatalError ,
3024 ResolutionDetails ,
3125 StandardResolutionReasons ,
32- TargetingKeyMissingError ,
33- TypeMismatchError ,
3426} from '@openfeature/web-sdk' ;
3527import { BulkEvaluationStatus , EvaluateFlagsResponse } from './model/evaluate-flags-response' ;
36- import { FlagCache } from './model/in-memory-cache' ;
28+ import { FlagCache , MetadataCache } from './model/in-memory-cache' ;
3729import { OFREPWebProviderOptions } from './model/ofrep-web-provider-options' ;
3830import { isResolutionError } from './model/resolution-error' ;
3931
32+ const ERROR_TO_MESSAGE : { [ key in ErrorCode ] : string } = {
33+ [ ErrorCode . FLAG_NOT_FOUND ] : 'Flag was not found' ,
34+ [ ErrorCode . GENERAL ] : 'General error' ,
35+ [ ErrorCode . INVALID_CONTEXT ] : 'Context is invalid or could be parsed' ,
36+ [ ErrorCode . PARSE_ERROR ] : 'Flag or flag configuration could not be parsed' ,
37+ [ ErrorCode . PROVIDER_FATAL ] : 'Provider is in a fatal error state' ,
38+ [ ErrorCode . PROVIDER_NOT_READY ] : 'Provider is not yet ready' ,
39+ [ ErrorCode . TARGETING_KEY_MISSING ] : 'Targeting key is missing' ,
40+ [ ErrorCode . TYPE_MISMATCH ] : 'Flag is not of expect type' ,
41+ } ;
42+
4043export class OFREPWebProvider implements Provider {
4144 DEFAULT_POLL_INTERVAL = 30000 ;
4245
@@ -52,10 +55,11 @@ export class OFREPWebProvider implements Provider {
5255 // _options is the options used to configure the provider.
5356 private _options : OFREPWebProviderOptions ;
5457 private _ofrepAPI : OFREPApi ;
55- private _etag : string | null ;
58+ private _etag : string | null | undefined ;
5659 private _pollingInterval : number ;
5760 private _retryPollingAfter : Date | undefined ;
5861 private _flagCache : FlagCache = { } ;
62+ private _flagSetMetadataCache : MetadataCache = { } ;
5963 private _context : EvaluationContext | undefined ;
6064 private _pollingIntervalId ?: number ;
6165
@@ -81,7 +85,7 @@ export class OFREPWebProvider implements Provider {
8185 async initialize ( context ?: EvaluationContext | undefined ) : Promise < void > {
8286 try {
8387 this . _context = context ;
84- await this . _evaluateFlags ( context ) ;
88+ await this . _fetchFlags ( context ) ;
8589
8690 if ( this . _pollingInterval > 0 ) {
8791 this . startPolling ( ) ;
@@ -102,28 +106,28 @@ export class OFREPWebProvider implements Provider {
102106 defaultValue : boolean ,
103107 context : EvaluationContext ,
104108 ) : ResolutionDetails < boolean > {
105- return this . evaluate ( flagKey , 'boolean' ) ;
109+ return this . _resolve ( flagKey , 'boolean' , defaultValue ) ;
106110 }
107111 resolveStringEvaluation (
108112 flagKey : string ,
109113 defaultValue : string ,
110114 context : EvaluationContext ,
111115 ) : ResolutionDetails < string > {
112- return this . evaluate ( flagKey , 'string' ) ;
116+ return this . _resolve ( flagKey , 'string' , defaultValue ) ;
113117 }
114118 resolveNumberEvaluation (
115119 flagKey : string ,
116120 defaultValue : number ,
117121 context : EvaluationContext ,
118122 ) : ResolutionDetails < number > {
119- return this . evaluate ( flagKey , 'number' ) ;
123+ return this . _resolve ( flagKey , 'number' , defaultValue ) ;
120124 }
121125 resolveObjectEvaluation < T extends JsonValue > (
122126 flagKey : string ,
123127 defaultValue : T ,
124128 context : EvaluationContext ,
125129 ) : ResolutionDetails < T > {
126- return this . evaluate ( flagKey , 'object' ) ;
130+ return this . _resolve ( flagKey , 'object' , defaultValue ) ;
127131 }
128132 /* eslint-enable @typescript-eslint/no-unused-vars */
129133
@@ -143,7 +147,7 @@ export class OFREPWebProvider implements Provider {
143147 return ;
144148 }
145149
146- await this . _evaluateFlags ( newContext ) ;
150+ await this . _fetchFlags ( newContext ) ;
147151 } catch ( error ) {
148152 if ( error instanceof OFREPApiTooManyRequestsError ) {
149153 this . events ?. emit ( ClientProviderEvents . Stale , { message : `${ error . name } : ${ error . message } ` } ) ;
@@ -172,7 +176,7 @@ export class OFREPWebProvider implements Provider {
172176 }
173177
174178 /**
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.
176180 * @param context - the context to use for the evaluation
177181 * @private
178182 * @returns EvaluationStatus if the evaluation the API returned a 304, 200.
@@ -181,7 +185,7 @@ export class OFREPWebProvider implements Provider {
181185 * @throws ParseError if the API returned a 400 with the error code ParseError
182186 * @throws GeneralError if the API returned a 400 with an unknown error code
183187 */
184- private async _evaluateFlags ( context ?: EvaluationContext | undefined ) : Promise < EvaluateFlagsResponse > {
188+ private async _fetchFlags ( context ?: EvaluationContext | undefined ) : Promise < EvaluateFlagsResponse > {
185189 try {
186190 const evalReq : EvaluationRequest = {
187191 context,
@@ -194,34 +198,42 @@ export class OFREPWebProvider implements Provider {
194198 }
195199
196200 if ( response . httpStatus !== 200 ) {
197- handleEvaluationError ( response ) ;
201+ throw new Error ( `Failed OFREP bulk evaluation request, status: ${ response . httpStatus } ` ) ;
198202 }
199203
200204 const bulkSuccessResp = response . value ;
201205 const newCache : FlagCache = { } ;
202206
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 ( 'metadata' in bulkSuccessResp && typeof bulkSuccessResp . flags === 'object' ) {
208+ this . _flagSetMetadataCache = bulkSuccessResp . metadata || { } ;
209+ }
211210
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 } ;
211+ if ( 'flags' in bulkSuccessResp && typeof bulkSuccessResp . flags === 'object' ) {
212+ bulkSuccessResp . flags ?. forEach ( ( evalResp : EvaluationResponse ) => {
213+ if ( isEvaluationFailureResponse ( evalResp ) ) {
214+ newCache [ evalResp . key ] = {
215+ errorCode : evalResp . errorCode ,
216+ errorDetails : evalResp . errorDetails ,
217+ reason : StandardResolutionReasons . ERROR ,
218+ } ;
219+ }
220+
221+ if ( isEvaluationSuccessResponse ( evalResp ) && evalResp . key ) {
222+ newCache [ evalResp . key ] = {
223+ value : evalResp . value ,
224+ flagMetadata : evalResp . metadata ,
225+ reason : evalResp . reason ,
226+ variant : evalResp . variant ,
227+ } ;
228+ }
229+ } ) ;
230+ const listUpdatedFlags = this . _getListUpdatedFlags ( this . _flagCache , newCache ) ;
231+ this . _flagCache = newCache ;
232+ this . _etag = response . httpResponse ?. headers . get ( 'etag' ) ;
233+ return { status : BulkEvaluationStatus . SUCCESS_WITH_CHANGES , flags : listUpdatedFlags } ;
234+ } else {
235+ throw new Error ( 'No flags in OFREP bulk evaluation response' ) ;
236+ }
225237 } catch ( error ) {
226238 if ( error instanceof OFREPApiTooManyRequestsError && error . retryAfterDate !== null ) {
227239 this . _retryPollingAfter = error . retryAfterDate ;
@@ -260,37 +272,41 @@ export class OFREPWebProvider implements Provider {
260272 }
261273
262274 /**
263- * Evaluate is a function retrieving the value from a flag in the cache.
275+ * _resolve is a function retrieving the value from a flag in the cache.
264276 * @param flagKey - name of the flag to retrieve
265277 * @param type - type of the flag
278+ * @param defaultValue - default value
266279 * @private
267280 */
268- private evaluate < T extends FlagValue > ( flagKey : string , type : string ) : ResolutionDetails < T > {
281+ private _resolve < T extends FlagValue > ( flagKey : string , type : string , defaultValue : T ) : ResolutionDetails < T > {
269282 const resolved = this . _flagCache [ flagKey ] ;
283+
270284 if ( ! resolved ) {
271- throw new FlagNotFoundError ( `flag key ${ flagKey } not found in cache` ) ;
285+ return {
286+ value : defaultValue ,
287+ flagMetadata : this . _flagSetMetadataCache ,
288+ reason : 'ERROR' ,
289+ errorCode : ErrorCode . FLAG_NOT_FOUND ,
290+ errorMessage : ERROR_TO_MESSAGE [ ErrorCode . FLAG_NOT_FOUND ] ,
291+ } ;
272292 }
273293
274294 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- }
295+ return {
296+ ...resolved ,
297+ value : defaultValue ,
298+ errorMessage : ERROR_TO_MESSAGE [ resolved . errorCode ] ,
299+ } ;
290300 }
291301
292302 if ( typeof resolved . value !== type ) {
293- throw new TypeMismatchError ( `flag key ${ flagKey } is not of type ${ type } ` ) ;
303+ return {
304+ value : defaultValue ,
305+ flagMetadata : this . _flagSetMetadataCache ,
306+ reason : 'ERROR' ,
307+ errorCode : ErrorCode . TYPE_MISMATCH ,
308+ errorMessage : ERROR_TO_MESSAGE [ ErrorCode . TYPE_MISMATCH ] ,
309+ } ;
294310 }
295311
296312 return {
@@ -314,7 +330,7 @@ export class OFREPWebProvider implements Provider {
314330 if ( this . _retryPollingAfter !== undefined && this . _retryPollingAfter > now ) {
315331 return ;
316332 }
317- const res = await this . _evaluateFlags ( this . _context ) ;
333+ const res = await this . _fetchFlags ( this . _context ) ;
318334 if ( res . status === BulkEvaluationStatus . SUCCESS_WITH_CHANGES ) {
319335 this . events ?. emit ( ClientProviderEvents . ConfigurationChanged , {
320336 message : 'Flags updated' ,
0 commit comments