Skip to content

Commit bfd150d

Browse files
committed
feat: add evaluation details to finally hook
Signed-off-by: Michael Beemer <[email protected]>
1 parent 1ba149d commit bfd150d

File tree

5 files changed

+84
-59
lines changed

5 files changed

+84
-59
lines changed

packages/server/src/client/internal/open-feature-client.ts

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import type {
1010
Logger,
1111
TrackingEventDetails,
1212
OpenFeatureError,
13-
ResolutionDetails} from '@openfeature/core';
13+
FlagMetadata,
14+
ResolutionDetails,
15+
} from '@openfeature/core';
1416
import {
1517
ErrorCode,
1618
ProviderFatalError,
@@ -24,7 +26,7 @@ import type { FlagEvaluationOptions } from '../../evaluation';
2426
import type { ProviderEvents } from '../../events';
2527
import type { InternalEventEmitter } from '../../events/internal/internal-event-emitter';
2628
import type { Hook } from '../../hooks';
27-
import type { Provider} from '../../provider';
29+
import type { Provider } from '../../provider';
2830
import { ProviderStatus } from '../../provider';
2931
import type { Client } from './../client';
3032

@@ -279,35 +281,38 @@ export class OpenFeatureClient implements Client {
279281
logger: this._logger,
280282
};
281283

282-
try {
283-
const frozenContext = await this.beforeHooks(allHooks, hookContext, options);
284+
const evaluationDetails = await(async () => {
285+
try {
286+
const frozenContext = await this.beforeHooks(allHooks, hookContext, options);
284287

285-
this.shortCircuitIfNotReady();
288+
this.shortCircuitIfNotReady();
286289

287-
// run the referenced resolver, binding the provider.
288-
const resolution = await resolver.call(this._provider, flagKey, defaultValue, frozenContext, this._logger);
290+
// run the referenced resolver, binding the provider.
291+
const resolution = await resolver.call(this._provider, flagKey, defaultValue, frozenContext, this._logger);
292+
293+
const resolutionDetails = {
294+
...resolution,
295+
flagMetadata: Object.freeze(resolution.flagMetadata ?? {}),
296+
flagKey,
297+
};
298+
299+
if (resolutionDetails.errorCode) {
300+
const err = instantiateErrorByErrorCode(resolutionDetails.errorCode);
301+
await this.errorHooks(allHooksReversed, hookContext, err, options);
302+
return this.getErrorEvaluationDetails(flagKey, defaultValue, err, resolutionDetails.flagMetadata);
303+
}
289304

290-
const evaluationDetails = {
291-
...resolution,
292-
flagMetadata: Object.freeze(resolution.flagMetadata ?? {}),
293-
flagKey,
294-
};
305+
await this.afterHooks(allHooksReversed, hookContext, resolutionDetails, options);
295306

296-
if (evaluationDetails.errorCode) {
297-
const err = instantiateErrorByErrorCode(evaluationDetails.errorCode);
307+
return resolutionDetails;
308+
} catch (err: unknown) {
298309
await this.errorHooks(allHooksReversed, hookContext, err, options);
299310
return this.getErrorEvaluationDetails(flagKey, defaultValue, err);
300311
}
312+
})();
301313

302-
await this.afterHooks(allHooksReversed, hookContext, evaluationDetails, options);
303-
304-
return evaluationDetails;
305-
} catch (err: unknown) {
306-
await this.errorHooks(allHooksReversed, hookContext, err, options);
307-
return this.getErrorEvaluationDetails(flagKey, defaultValue, err);
308-
} finally {
309-
await this.finallyHooks(allHooksReversed, hookContext, options);
310-
}
314+
await this.finallyHooks(allHooksReversed, hookContext, evaluationDetails, options);
315+
return evaluationDetails;
311316
}
312317

313318
private async beforeHooks(hooks: Hook[], hookContext: HookContext, options: FlagEvaluationOptions) {
@@ -353,11 +358,16 @@ export class OpenFeatureClient implements Client {
353358
}
354359
}
355360

356-
private async finallyHooks(hooks: Hook[], hookContext: HookContext, options: FlagEvaluationOptions) {
361+
private async finallyHooks(
362+
hooks: Hook[],
363+
hookContext: HookContext,
364+
evaluationDetails: EvaluationDetails<FlagValue>,
365+
options: FlagEvaluationOptions,
366+
) {
357367
// run "finally" hooks sequentially
358368
for (const hook of hooks) {
359369
try {
360-
await hook?.finally?.(hookContext, options.hookHints);
370+
await hook?.finally?.(hookContext, evaluationDetails, options.hookHints);
361371
} catch (err) {
362372
this._logger.error(`Unhandled error during 'finally' hook: ${err}`);
363373
if (err instanceof Error) {
@@ -403,6 +413,7 @@ export class OpenFeatureClient implements Client {
403413
flagKey: string,
404414
defaultValue: T,
405415
err: unknown,
416+
flagMetadata: FlagMetadata = {},
406417
): EvaluationDetails<T> {
407418
const errorMessage: string = (err as Error)?.message;
408419
const errorCode: ErrorCode = (err as OpenFeatureError)?.code || ErrorCode.GENERAL;
@@ -412,7 +423,7 @@ export class OpenFeatureClient implements Client {
412423
errorMessage,
413424
value: defaultValue,
414425
reason: StandardResolutionReasons.ERROR,
415-
flagMetadata: Object.freeze({}),
426+
flagMetadata: Object.freeze(flagMetadata),
416427
flagKey,
417428
};
418429
}

packages/server/test/hooks.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -922,14 +922,14 @@ describe('Hooks', () => {
922922
done(err);
923923
}
924924
},
925-
after: (_hookContext, _evaluationDetils, hookHints) => {
925+
after: (_hookContext, _evaluationDetails, hookHints) => {
926926
try {
927927
expect(hookHints?.hint).toBeTruthy();
928928
} catch (err) {
929929
done(err);
930930
}
931931
},
932-
finally: (_, hookHints) => {
932+
finally: (_, _evaluationDetails, hookHints) => {
933933
try {
934934
expect(hookHints?.hint).toBeTruthy();
935935
done();

packages/shared/src/hooks/hook.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,13 @@ export interface BaseHook<T extends FlagValue = FlagValue, BeforeHookReturn = un
3232

3333
/**
3434
* Runs after all other hook stages, regardless of success or error.
35-
* Errors thrown here are unhandled by the client and will surface in application code.
3635
* @param hookContext
36+
* @param evaluationDetails
3737
* @param hookHints
3838
*/
39-
finally?(hookContext: Readonly<HookContext<T>>, hookHints?: HookHints): HooksReturn;
39+
finally?(
40+
hookContext: Readonly<HookContext<T>>,
41+
evaluationDetails: EvaluationDetails<T>,
42+
hookHints?: HookHints,
43+
): HooksReturn;
4044
}

packages/web/src/client/internal/open-feature-client.ts

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import type {
1010
Logger,
1111
TrackingEventDetails,
1212
OpenFeatureError,
13-
ResolutionDetails } from '@openfeature/core';
13+
FlagMetadata,
14+
ResolutionDetails,
15+
} from '@openfeature/core';
1416
import {
1517
ErrorCode,
1618
ProviderFatalError,
@@ -24,7 +26,7 @@ import type { FlagEvaluationOptions } from '../../evaluation';
2426
import type { ProviderEvents } from '../../events';
2527
import type { InternalEventEmitter } from '../../events/internal/internal-event-emitter';
2628
import type { Hook } from '../../hooks';
27-
import type { Provider} from '../../provider';
29+
import type { Provider } from '../../provider';
2830
import { ProviderStatus } from '../../provider';
2931
import type { Client } from './../client';
3032

@@ -234,35 +236,37 @@ export class OpenFeatureClient implements Client {
234236
logger: this._logger,
235237
};
236238

237-
try {
238-
this.beforeHooks(allHooks, hookContext, options);
239+
const evaluationDetails = (() => {
240+
try {
241+
this.beforeHooks(allHooks, hookContext, options);
239242

240-
this.shortCircuitIfNotReady();
243+
this.shortCircuitIfNotReady();
241244

242-
// run the referenced resolver, binding the provider.
243-
const resolution = resolver.call(this._provider, flagKey, defaultValue, context, this._logger);
245+
// run the referenced resolver, binding the provider.
246+
const resolution = resolver.call(this._provider, flagKey, defaultValue, context, this._logger);
244247

245-
const evaluationDetails = {
246-
...resolution,
247-
flagMetadata: Object.freeze(resolution.flagMetadata ?? {}),
248-
flagKey,
249-
};
248+
const evaluationDetails = {
249+
...resolution,
250+
flagMetadata: Object.freeze(resolution.flagMetadata ?? {}),
251+
flagKey,
252+
};
250253

251-
if (evaluationDetails.errorCode) {
252-
const err = instantiateErrorByErrorCode(evaluationDetails.errorCode);
254+
if (evaluationDetails.errorCode) {
255+
const err = instantiateErrorByErrorCode(evaluationDetails.errorCode);
256+
this.errorHooks(allHooksReversed, hookContext, err, options);
257+
return this.getErrorEvaluationDetails(flagKey, defaultValue, err, evaluationDetails.flagMetadata);
258+
}
259+
260+
this.afterHooks(allHooksReversed, hookContext, evaluationDetails, options);
261+
262+
return evaluationDetails;
263+
} catch (err: unknown) {
253264
this.errorHooks(allHooksReversed, hookContext, err, options);
254265
return this.getErrorEvaluationDetails(flagKey, defaultValue, err);
255266
}
256-
257-
this.afterHooks(allHooksReversed, hookContext, evaluationDetails, options);
258-
259-
return evaluationDetails;
260-
} catch (err: unknown) {
261-
this.errorHooks(allHooksReversed, hookContext, err, options);
262-
return this.getErrorEvaluationDetails(flagKey, defaultValue, err);
263-
} finally {
264-
this.finallyHooks(allHooksReversed, hookContext, options);
265-
}
267+
})();
268+
this.finallyHooks(allHooksReversed, hookContext, evaluationDetails, options);
269+
return evaluationDetails;
266270
}
267271

268272
private beforeHooks(hooks: Hook[], hookContext: HookContext, options: FlagEvaluationOptions) {
@@ -301,11 +305,16 @@ export class OpenFeatureClient implements Client {
301305
}
302306
}
303307

304-
private finallyHooks(hooks: Hook[], hookContext: HookContext, options: FlagEvaluationOptions) {
308+
private finallyHooks(
309+
hooks: Hook[],
310+
hookContext: HookContext,
311+
evaluationDetails: EvaluationDetails<FlagValue>,
312+
options: FlagEvaluationOptions,
313+
) {
305314
// run "finally" hooks sequentially
306315
for (const hook of hooks) {
307316
try {
308-
hook?.finally?.(hookContext, options.hookHints);
317+
hook?.finally?.(hookContext, evaluationDetails, options.hookHints);
309318
} catch (err) {
310319
this._logger.error(`Unhandled error during 'finally' hook: ${err}`);
311320
if (err instanceof Error) {
@@ -337,6 +346,7 @@ export class OpenFeatureClient implements Client {
337346
flagKey: string,
338347
defaultValue: T,
339348
err: unknown,
349+
flagMetadata: FlagMetadata = {},
340350
): EvaluationDetails<T> {
341351
const errorMessage: string = (err as Error)?.message;
342352
const errorCode: ErrorCode = (err as OpenFeatureError)?.code || ErrorCode.GENERAL;
@@ -346,7 +356,7 @@ export class OpenFeatureClient implements Client {
346356
errorMessage,
347357
value: defaultValue,
348358
reason: StandardResolutionReasons.ERROR,
349-
flagMetadata: Object.freeze({}),
359+
flagMetadata: Object.freeze(flagMetadata),
350360
flagKey,
351361
};
352362
}

packages/web/test/hooks.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -759,14 +759,14 @@ describe('Hooks', () => {
759759
done(err);
760760
}
761761
},
762-
after: (_hookContext, _evaluationDetils, hookHints) => {
762+
after: (_hookContext, _evaluationDetails, hookHints) => {
763763
try {
764764
expect(hookHints?.hint).toBeTruthy();
765765
} catch (err) {
766766
done(err);
767767
}
768768
},
769-
finally: (_, hookHints) => {
769+
finally: (_, _evaluationDetails, hookHints) => {
770770
try {
771771
expect(hookHints?.hint).toBeTruthy();
772772
done();

0 commit comments

Comments
 (0)