diff --git a/packages/context/src/__tests__/unit/interceptor.unit.ts b/packages/context/src/__tests__/unit/interceptor.unit.ts index 4206ac799882..a67ec30e15cf 100644 --- a/packages/context/src/__tests__/unit/interceptor.unit.ts +++ b/packages/context/src/__tests__/unit/interceptor.unit.ts @@ -266,7 +266,7 @@ describe('globalInterceptors', () => { }); it('generates binding key for the interceptor function', () => { - const binding = registerInterceptor(ctx, () => {}); + const binding = registerInterceptor(ctx, () => undefined); expect(binding.key).to.match( new RegExp(`interceptors.${UUID_PATTERN.source}`, 'i'), ); diff --git a/packages/context/src/interceptor-chain.ts b/packages/context/src/interceptor-chain.ts index 4d5887b8d48a..562ff7d8fee8 100644 --- a/packages/context/src/interceptor-chain.ts +++ b/packages/context/src/interceptor-chain.ts @@ -12,28 +12,55 @@ import {InvocationResult} from './invocation'; import {transformValueOrPromise, ValueOrPromise} from './value-promise'; const debug = debugFactory('loopback:context:interceptor-chain'); +/** + * Any type except `void`. We use this type to enforce that interceptor functions + * always return a value (including undefined or null). + */ +export type NonVoid = string | number | boolean | null | undefined | object; + /** * The `next` function that can be used to invoke next generic interceptor in * the chain */ -export type Next = () => ValueOrPromise; +export type Next = () => ValueOrPromise; /** * An interceptor function to be invoked in a chain for the given context. * It serves as the base interface for various types of interceptors, such * as method invocation interceptor or request/response processing interceptor. * + * We choose `NonVoid` as the return type to avoid possible bugs that an + * interceptor forgets to return the value from `next()`. For example, the code + * below will fail to compile. + * + * ```ts + * const myInterceptor: Interceptor = async (ctx, next) { + * // preprocessing + * // ... + * + * // There is a subtle bug that the result from `next()` is not further + * // returned back to the upstream interceptors + * const result = await next(); + * + * // postprocessing + * // ... + * // We must have `return ...` here + * // either return `result` or another value if the interceptor decides to + * // have its own response + * } + * ``` + * * @typeParam C - `Context` class or a subclass of `Context` * @param context - Context object * @param next - A function to proceed with downstream interceptors or the * target operation * - * @returns The invocation result as a value (sync) or promise (async) + * @returns The invocation result as a value (sync) or promise (async). */ export type GenericInterceptor = ( context: C, next: Next, -) => ValueOrPromise; +) => ValueOrPromise; /** * Interceptor function or a binding key that resolves a generic interceptor diff --git a/packages/core/src/__tests__/unit/application.unit.ts b/packages/core/src/__tests__/unit/application.unit.ts index 212747171c9b..61e76b5285e9 100644 --- a/packages/core/src/__tests__/unit/application.unit.ts +++ b/packages/core/src/__tests__/unit/application.unit.ts @@ -441,7 +441,9 @@ describe('Application', () => { }); }); - function logInterceptor(ctx: InvocationContext, next: Next) {} + function logInterceptor(ctx: InvocationContext, next: Next) { + return undefined; + } @bind(asGlobalInterceptor()) class LogInterceptorProvider implements Provider {