From ba7aa79f56772213bf73b62bc6bf8810f3871127 Mon Sep 17 00:00:00 2001 From: "Max P." Date: Wed, 7 May 2025 12:35:41 +0200 Subject: [PATCH] feat(http): add error handling for invalid HTTP methods - Introduce `InvalidHttpMethodError` for unrecognized HTTP methods. - Enhance type safety in `HttpKernel` by using generic contexts. - Update `ResponseDecorator` to accept context for enriched responses. Signed-off-by: Max P. --- src/Errors/InvalidHttpMethodError.ts | 25 ++++++++++++++++++++++ src/HttpKernel.ts | 31 ++++++++++++++-------------- src/Types/ResponseDecorator.ts | 7 ++++++- 3 files changed, 47 insertions(+), 16 deletions(-) create mode 100644 src/Errors/InvalidHttpMethodError.ts diff --git a/src/Errors/InvalidHttpMethodError.ts b/src/Errors/InvalidHttpMethodError.ts new file mode 100644 index 0000000..8d9047f --- /dev/null +++ b/src/Errors/InvalidHttpMethodError.ts @@ -0,0 +1,25 @@ +/** + * Represents an error thrown when an incoming HTTP method + * is not among the recognized set of valid HTTP methods. + * + * This is typically used in routers or request dispatchers + * to enforce allowed methods and produce 405-like behavior. + */ +export class InvalidHttpMethodError extends Error { + /** + * The invalid method that triggered this error. + */ + public readonly method: unknown; + + /** + * A fixed HTTP status code representing "Method Not Allowed". + */ + public readonly status: number = 405; + + constructor(method: unknown) { + const label = typeof method === 'string' ? method : '[non-string]'; + super(`Unsupported HTTP method: ${label}`); + this.name = 'InvalidHttpMethodError'; + this.method = method; + } +} diff --git a/src/HttpKernel.ts b/src/HttpKernel.ts index ed00acc..ae8db14 100644 --- a/src/HttpKernel.ts +++ b/src/HttpKernel.ts @@ -24,7 +24,7 @@ export class HttpKernel /** * The list of internally registered routes, each with method, matcher, middleware, and handler. */ - private routes: IInternalRoute[] = []; + private routes: IInternalRoute[] = []; /** * Creates a new instance of the `HttpKernel`. @@ -71,10 +71,10 @@ export class HttpKernel query: parseQuery(url.searchParams), state: {}, } as _TContext; - return await this.executePipeline( + return await this.executePipeline<_TContext>( ctx, - route.middlewares, - route.handler, + route.middlewares as unknown as IMiddleware<_TContext>[], + route.handler as unknown as IHandler<_TContext>, ); } } @@ -89,8 +89,10 @@ export class HttpKernel * * @param route - The fully constructed route including matcher, middlewares, and handler. */ - private registerRoute(route: IInternalRoute): void { - this.routes.push(route); + private registerRoute<_TContext extends IContext = TContext>( + route: IInternalRoute<_TContext>, + ): void { + this.routes.push(route as unknown as IInternalRoute); } /** @@ -107,23 +109,22 @@ export class HttpKernel * @param handler - The final request handler to invoke at the end of the pipeline. * @returns The final HTTP response after middleware and decoration. */ - private async executePipeline( - ctx: IContext, - middleware: IMiddleware[], - handler: IHandler, + private async executePipeline<_TContext extends IContext = TContext>( + ctx: _TContext, + middleware: IMiddleware<_TContext>[], + handler: IHandler<_TContext>, ): Promise { let i = -1; const dispatch = async (index: number): Promise => { if (index <= i) throw new Error('next() called multiple times'); i = index; - const fn: IMiddleware | IHandler = index < middleware.length - ? middleware[index] - : handler; + const fn: IMiddleware<_TContext> | IHandler<_TContext> = + index < middleware.length ? middleware[index] : handler; if (!fn) return new Response('Internal error', { status: 500 }); return index < middleware.length ? await fn(ctx, () => dispatch(index + 1)) - : await (fn as IHandler)(ctx); + : await (fn as IHandler<_TContext>)(ctx); }; - return this.decorateResponse(await dispatch(0)); + return this.decorateResponse(await dispatch(0), ctx); } } diff --git a/src/Types/ResponseDecorator.ts b/src/Types/ResponseDecorator.ts index c0cd438..a027f4f 100644 --- a/src/Types/ResponseDecorator.ts +++ b/src/Types/ResponseDecorator.ts @@ -1,3 +1,5 @@ +import { IContext } from '../Interfaces/mod.ts'; + /** * A function that modifies or enriches an outgoing HTTP response before it is returned to the client. * @@ -22,4 +24,7 @@ * }; * ``` */ -export type ResponseDecorator = (res: Response) => Response; +export type ResponseDecorator = ( + res: Response, + ctx: TContext, +) => Response;