Skip to content

Commit 24b477a

Browse files
committed
first match strategy will ignore thrown flag not found errors
Signed-off-by: Adam Wootton <[email protected]>
1 parent 5414635 commit 24b477a

File tree

5 files changed

+45
-14
lines changed

5 files changed

+45
-14
lines changed

libs/providers/multi-provider/src/lib/multi-provider.spec.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
DefaultLogger,
44
ErrorCode,
55
EvaluationContext,
6+
FlagNotFoundError,
67
FlagValue,
78
FlagValueType,
89
Hook,
@@ -11,8 +12,8 @@ import {
1112
OpenFeatureEventEmitter,
1213
Provider,
1314
ServerProviderEvents,
15+
ProviderMetadata,
1416
} from '@openfeature/server-sdk';
15-
import { ProviderMetadata } from '@openfeature/core';
1617
import { FirstMatchStrategy } from './strategies/FirstMatchStrategy';
1718
import { FirstSuccessfulStrategy } from './strategies/FirstSuccessfulStrategy';
1819
import { ComparisonStrategy } from './strategies/ComparisonStrategy';
@@ -542,6 +543,34 @@ describe('MultiProvider', () => {
542543
expect(provider2.resolveBooleanEvaluation).toHaveBeenCalled();
543544
expect(provider3.resolveBooleanEvaluation).not.toHaveBeenCalled();
544545
});
546+
547+
it('skips providers that throw flag not found until it gets a result, skipping any provider after', async () => {
548+
const provider1 = new TestProvider();
549+
const provider2 = new TestProvider();
550+
const provider3 = new TestProvider();
551+
provider1.resolveBooleanEvaluation.mockRejectedValue(new FlagNotFoundError('flag not found'));
552+
provider2.resolveBooleanEvaluation.mockResolvedValue({
553+
value: true,
554+
});
555+
const multiProvider = new MultiProvider(
556+
[
557+
{
558+
provider: provider1,
559+
},
560+
{
561+
provider: provider2,
562+
},
563+
{
564+
provider: provider3,
565+
},
566+
],
567+
new FirstMatchStrategy(),
568+
);
569+
const result = await callEvaluation(multiProvider, {}, logger);
570+
expect(result).toEqual({ value: true });
571+
expect(provider2.resolveBooleanEvaluation).toHaveBeenCalled();
572+
expect(provider3.resolveBooleanEvaluation).not.toHaveBeenCalled();
573+
});
545574
});
546575

547576
describe('first successful strategy', () => {

libs/providers/multi-provider/src/lib/multi-provider.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,6 @@ export class MultiProvider implements Provider {
192192
hookHints: HookHints,
193193
context: EvaluationContext,
194194
): Promise<[shouldEvaluateNext: boolean, ProviderResolutionResult<T> | null]> {
195-
let thrownError: unknown;
196195
let evaluationResult: ResolutionDetails<T> | undefined = undefined;
197196
const provider = providerEntry.provider;
198197
const strategyContext = {
@@ -222,12 +221,11 @@ export class MultiProvider implements Provider {
222221
provider: provider,
223222
providerName: providerEntry.name,
224223
};
225-
thrownError = error;
226224
}
227225

228226
return [
229227
this.evaluationStrategy.runMode === 'sequential'
230-
? this.evaluationStrategy.shouldEvaluateNextProvider(strategyContext, context, evaluationResult, thrownError)
228+
? this.evaluationStrategy.shouldEvaluateNextProvider(strategyContext, context, resolution)
231229
: true,
232230
resolution,
233231
];

libs/providers/multi-provider/src/lib/strategies/BaseEvaluationStrategy.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
EvaluationContext,
44
FlagValue,
55
FlagValueType,
6+
OpenFeatureError,
67
Provider,
78
ProviderStatus,
89
ResolutionDetails,
@@ -66,8 +67,7 @@ export abstract class BaseEvaluationStrategy {
6667
shouldEvaluateNextProvider<T extends FlagValue>(
6768
strategyContext: StrategyPerProviderContext,
6869
context: EvaluationContext,
69-
details?: ResolutionDetails<T>,
70-
thrownError?: unknown,
70+
result: ProviderResolutionResult<T>,
7171
): boolean {
7272
return true;
7373
}
@@ -86,6 +86,12 @@ export abstract class BaseEvaluationStrategy {
8686
return 'thrownError' in resolution || !!resolution.details.errorCode;
8787
}
8888

89+
protected hasErrorWithCode(resolution: ProviderResolutionResult<FlagValue>, code: ErrorCode): boolean {
90+
return 'thrownError' in resolution
91+
? (resolution.thrownError as OpenFeatureError)?.code === code
92+
: resolution.details.errorCode === code;
93+
}
94+
8995
protected collectProviderErrors<T extends FlagValue>(resolutions: ProviderResolutionResult<T>[]): FinalResult<T> {
9096
const errors: FinalResult<FlagValue>['errors'] = [];
9197
for (const resolution of resolutions) {

libs/providers/multi-provider/src/lib/strategies/FirstMatchStrategy.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
ProviderResolutionResult,
55
StrategyPerProviderContext,
66
} from './BaseEvaluationStrategy';
7-
import { ErrorCode, EvaluationContext, FlagValue, ResolutionDetails } from '@openfeature/server-sdk';
7+
import { ErrorCode, EvaluationContext, FlagValue, OpenFeatureError, ResolutionDetails } from '@openfeature/server-sdk';
88

99
/**
1010
* Return the first result that did not indicate "flag not found".
@@ -14,13 +14,12 @@ export class FirstMatchStrategy extends BaseEvaluationStrategy {
1414
override shouldEvaluateNextProvider<T extends FlagValue>(
1515
strategyContext: StrategyPerProviderContext,
1616
context: EvaluationContext,
17-
details?: ResolutionDetails<T>,
18-
thrownError?: unknown,
17+
result: ProviderResolutionResult<T>,
1918
): boolean {
20-
if (details?.errorCode === ErrorCode.FLAG_NOT_FOUND) {
19+
if (this.hasErrorWithCode(result, ErrorCode.FLAG_NOT_FOUND)) {
2120
return true;
2221
}
23-
if (details?.errorCode) {
22+
if (this.hasError(result)) {
2423
return false;
2524
}
2625
return false;

libs/providers/multi-provider/src/lib/strategies/FirstSuccessfulStrategy.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@ export class FirstSuccessfulStrategy extends BaseEvaluationStrategy {
1515
override shouldEvaluateNextProvider<T extends FlagValue>(
1616
strategyContext: StrategyPerProviderContext,
1717
context: EvaluationContext,
18-
details?: ResolutionDetails<T>,
19-
thrownError?: unknown,
18+
result: ProviderResolutionResult<T>,
2019
): boolean {
2120
// evaluate next only if there was an error
22-
return !!(thrownError || details?.errorCode);
21+
return this.hasError(result);
2322
}
2423

2524
override determineFinalResult<T extends FlagValue>(

0 commit comments

Comments
 (0)