From 7b75d612db8258f413bc18cf2791e2bde7381d3c Mon Sep 17 00:00:00 2001 From: Kir_Antipov Date: Mon, 5 Dec 2022 08:03:07 +0000 Subject: [PATCH] Now it's possible to create middlewares --- src/utils/functions/middleware.ts | 88 +++++++++++++++++++ tests/unit/utils/functions/middleware.spec.ts | 84 ++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 src/utils/functions/middleware.ts create mode 100644 tests/unit/utils/functions/middleware.spec.ts diff --git a/src/utils/functions/middleware.ts b/src/utils/functions/middleware.ts new file mode 100644 index 0000000..c6bb8b0 --- /dev/null +++ b/src/utils/functions/middleware.ts @@ -0,0 +1,88 @@ +import { Func } from "./func"; + +/** + * Represents a middleware function for a function of type T. + * + * Middleware functions can intercept, modify, or add behavior to the original function. + * + * @template T - The type of the function. + */ +export type Middleware = T extends Func ? (...args: [...args: P, next: (...args: P) => R]) => R : never; + +/** + * A class that manages middleware functions for a given function of type `T`. + * + * It allows adding middleware functions to intercept, modify, or add behavior to the original function. + * + * @template T - The type of the function. + */ +export class MiddlewareHandler { + /** + * The target function that the middleware functions will be applied to. + */ + private readonly _target: T; + + /** + * A list of middleware functions that will be executed in the order they were added. + */ + private readonly _delegates: Middleware[]; + + /** + * Constructs a new {@link MiddlewareHandler} instance. + * + * @param target - The target function that the middleware functions will be applied to. + */ + constructor(target: T) { + this._target = target; + this._delegates = []; + } + + /** + * Adds a middleware function to the {@link MiddlewareHandler}. + * + * Middleware functions are executed in the order they were added. + * + * @param middleware - The middleware function to add. + * + * @returns `this` instance, allowing for method chaining. + */ + use(middleware: Middleware): this { + this._delegates.push(middleware); + return this; + } + + /** + * Executes the middleware chain and the target function with the provided arguments. + * + * The middleware functions are called in the order they were added. + * + * @param args - The arguments to pass to the middleware functions and the target function. + * + * @returns The result of the target function after applying all middleware functions. + */ + execute(...args: Parameters): ReturnType { + return this.asFunction()(...args) as ReturnType; + } + + /** + * Returns the composed target function with the middleware chain applied. + * + * This function can be called directly, and it will execute the middleware chain and the target function. + * + * @returns The composed target function. + */ + asFunction(): T { + if (!this._delegates.length) { + return this._target; + } + + const target = this._target; + const delegates = [...this._delegates]; + + const apply = (i: number) => (...args: Parameters) => i < delegates.length + ? delegates[i](...args, apply(i + 1)) + : target(...args); + + return apply(0) as T; + } +} diff --git a/tests/unit/utils/functions/middleware.spec.ts b/tests/unit/utils/functions/middleware.spec.ts new file mode 100644 index 0000000..93d9301 --- /dev/null +++ b/tests/unit/utils/functions/middleware.spec.ts @@ -0,0 +1,84 @@ +import { Middleware, MiddlewareHandler } from "@/utils/functions/middleware"; + +describe("MiddlewareHandler", () => { + describe("constructor", () => { + test("creates middleware handler with target function", () => { + const func = (a: number, b: number) => a + b; + + const handler = new MiddlewareHandler(func); + + expect(handler.execute(1, 2)).toBe(3); + }); + }); + + describe("use", () => { + test("adds middleware function to the handler", () => { + const func = (a: number, b: number) => a + b; + const middleware: Middleware = (a, b, next) => { + return next(a * 2, b * 3); + }; + + const handler = new MiddlewareHandler(func).use(middleware); + + expect(handler.execute(1, 2)).toBe(8); + }); + + test("adds multiple middleware functions to the handler", () => { + const func = (a: number, b: number) => a + b; + const middleware1: Middleware = (a, b, next) => { + return next(a * 2, b * 3); + }; + const middleware2: Middleware = (a, b, next) => { + return next(a + 1, b + 2); + }; + + const handler = new MiddlewareHandler(func).use(middleware1).use(middleware2); + + expect(handler.execute(1, 2)).toBe(11); + }); + }); + + describe("execute", () => { + test("executes middleware chain and target function", () => { + const func = (a: number, b: number) => a + b; + const middleware: Middleware = (a, b, next) => { + return next(a * 2, b * 3); + }; + + const handler = new MiddlewareHandler(func).use(middleware); + + expect(handler.execute(1, 2)).toBe(8); + }); + }); + + describe("asFunction", () => { + test("returns a callable function", () => { + const func = (a: number, b: number) => a + b; + const middleware: Middleware = (a, b, next) => { + return next(a * 2, b * 3); + }; + + const handler = new MiddlewareHandler(func).use(middleware); + const callable = handler.asFunction(); + + expect(callable(1, 2)).toBe(8); + }); + + test("returned function is not affected by subsequent mutations", () => { + const func = (a: number, b: number) => a + b; + const middleware1: Middleware = (a, b, next) => { + return next(a * 2, b * 3); + }; + const middleware2: Middleware = (a, b, next) => { + return next(a + 1, b + 2); + }; + + const handler = new MiddlewareHandler(func).use(middleware1); + const callable = handler.asFunction(); + handler.use(middleware2); + + // If callable was affected, the result would be 11 + expect(callable(1, 2)).toBe(8); + }); + }); +});