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 ,
2017 GeneralError ,
2118 Hook ,
22- InvalidContextError ,
2319 JsonValue ,
2420 Logger ,
2521 OpenFeatureError ,
2622 OpenFeatureEventEmitter ,
27- ParseError ,
2823 Provider ,
2924 ProviderFatalError ,
3025 ResolutionDetails ,
3126 StandardResolutionReasons ,
32- TargetingKeyMissingError ,
33- TypeMismatchError ,
3427} from '@openfeature/web-sdk' ;
3528import { BulkEvaluationStatus , EvaluateFlagsResponse } from './model/evaluate-flags-response' ;
36- import { FlagCache } from './model/in-memory-cache' ;
29+ import { FlagCache , MetadataCache } from './model/in-memory-cache' ;
3730import { OFREPWebProviderOptions } from './model/ofrep-web-provider-options' ;
3831import { isResolutionError } from './model/resolution-error' ;
3932
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+
4044export class OFREPWebProvider implements Provider {
4145 DEFAULT_POLL_INTERVAL = 30000 ;
4246
@@ -52,10 +56,11 @@ export class OFREPWebProvider implements Provider {
5256 // _options is the options used to configure the provider.
5357 private _options : OFREPWebProviderOptions ;
5458 private _ofrepAPI : OFREPApi ;
55- private _etag : string | null ;
59+ private _etag : string | null | undefined ;
5660 private _pollingInterval : number ;
5761 private _retryPollingAfter : Date | undefined ;
5862 private _flagCache : FlagCache = { } ;
63+ private _flagSetMetadataCache : MetadataCache = { } ;
5964 private _context : EvaluationContext | undefined ;
6065 private _pollingIntervalId ?: number ;
6166
@@ -81,7 +86,7 @@ export class OFREPWebProvider implements Provider {
8186 async initialize ( context ?: EvaluationContext | undefined ) : Promise < void > {
8287 try {
8388 this . _context = context ;
84- await this . _evaluateFlags ( context ) ;
89+ await this . _fetchFlags ( context ) ;
8590
8691 if ( this . _pollingInterval > 0 ) {
8792 this . startPolling ( ) ;
@@ -102,30 +107,29 @@ export class OFREPWebProvider implements Provider {
102107 defaultValue : boolean ,
103108 context : EvaluationContext ,
104109 ) : ResolutionDetails < boolean > {
105- return this . evaluate ( flagKey , 'boolean' ) ;
110+ return this . _resolve ( flagKey , 'boolean' , defaultValue ) ;
106111 }
107112 resolveStringEvaluation (
108113 flagKey : string ,
109114 defaultValue : string ,
110115 context : EvaluationContext ,
111116 ) : ResolutionDetails < string > {
112- return this . evaluate ( flagKey , 'string' ) ;
117+ return this . _resolve ( flagKey , 'string' , defaultValue ) ;
113118 }
114119 resolveNumberEvaluation (
115120 flagKey : string ,
116121 defaultValue : number ,
117122 context : EvaluationContext ,
118123 ) : ResolutionDetails < number > {
119- return this . evaluate ( flagKey , 'number' ) ;
124+ return this . _resolve ( flagKey , 'number' , defaultValue ) ;
120125 }
121126 resolveObjectEvaluation < T extends JsonValue > (
122127 flagKey : string ,
123128 defaultValue : T ,
124129 context : EvaluationContext ,
125130 ) : ResolutionDetails < T > {
126- return this . evaluate ( flagKey , 'object' ) ;
131+ return this . _resolve ( flagKey , 'object' , defaultValue ) ;
127132 }
128- /* eslint-enable @typescript-eslint/no-unused-vars */
129133
130134 /**
131135 * 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 {
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,40 @@ export class OFREPWebProvider implements Provider {
194198 }
195199
196200 if ( response . httpStatus !== 200 ) {
197- handleEvaluationError ( response ) ;
201+ throw new GeneralError ( `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 ( '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+ }
211217
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+ }
225235 } catch ( error ) {
226236 if ( error instanceof OFREPApiTooManyRequestsError && error . retryAfterDate !== null ) {
227237 this . _retryPollingAfter = error . retryAfterDate ;
@@ -260,37 +270,41 @@ export class OFREPWebProvider implements Provider {
260270 }
261271
262272 /**
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.
264274 * @param flagKey - name of the flag to retrieve
265275 * @param type - type of the flag
276+ * @param defaultValue - default value
266277 * @private
267278 */
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 > {
269280 const resolved = this . _flagCache [ flagKey ] ;
281+
270282 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+ } ;
272290 }
273291
274292 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+ } ;
290298 }
291299
292300 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+ } ;
294308 }
295309
296310 return {
@@ -314,7 +328,7 @@ export class OFREPWebProvider implements Provider {
314328 if ( this . _retryPollingAfter !== undefined && this . _retryPollingAfter > now ) {
315329 return ;
316330 }
317- const res = await this . _evaluateFlags ( this . _context ) ;
331+ const res = await this . _fetchFlags ( this . _context ) ;
318332 if ( res . status === BulkEvaluationStatus . SUCCESS_WITH_CHANGES ) {
319333 this . events ?. emit ( ClientProviderEvents . ConfigurationChanged , {
320334 message : 'Flags updated' ,
0 commit comments