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. <Mail@MPassarello.de>
This commit is contained in:
2025-05-07 12:35:41 +02:00
parent a236fa7c97
commit ba7aa79f56
3 changed files with 47 additions and 16 deletions

View File

@@ -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;
}
}

View File

@@ -24,7 +24,7 @@ export class HttpKernel<TContext extends IContext = IContext>
/** /**
* The list of internally registered routes, each with method, matcher, middleware, and handler. * The list of internally registered routes, each with method, matcher, middleware, and handler.
*/ */
private routes: IInternalRoute[] = []; private routes: IInternalRoute<TContext>[] = [];
/** /**
* Creates a new instance of the `HttpKernel`. * Creates a new instance of the `HttpKernel`.
@@ -71,10 +71,10 @@ export class HttpKernel<TContext extends IContext = IContext>
query: parseQuery(url.searchParams), query: parseQuery(url.searchParams),
state: {}, state: {},
} as _TContext; } as _TContext;
return await this.executePipeline( return await this.executePipeline<_TContext>(
ctx, ctx,
route.middlewares, route.middlewares as unknown as IMiddleware<_TContext>[],
route.handler, route.handler as unknown as IHandler<_TContext>,
); );
} }
} }
@@ -89,8 +89,10 @@ export class HttpKernel<TContext extends IContext = IContext>
* *
* @param route - The fully constructed route including matcher, middlewares, and handler. * @param route - The fully constructed route including matcher, middlewares, and handler.
*/ */
private registerRoute(route: IInternalRoute): void { private registerRoute<_TContext extends IContext = TContext>(
this.routes.push(route); route: IInternalRoute<_TContext>,
): void {
this.routes.push(route as unknown as IInternalRoute<TContext>);
} }
/** /**
@@ -107,23 +109,22 @@ export class HttpKernel<TContext extends IContext = IContext>
* @param handler - The final request handler to invoke at the end of the pipeline. * @param handler - The final request handler to invoke at the end of the pipeline.
* @returns The final HTTP response after middleware and decoration. * @returns The final HTTP response after middleware and decoration.
*/ */
private async executePipeline( private async executePipeline<_TContext extends IContext = TContext>(
ctx: IContext, ctx: _TContext,
middleware: IMiddleware[], middleware: IMiddleware<_TContext>[],
handler: IHandler, handler: IHandler<_TContext>,
): Promise<Response> { ): Promise<Response> {
let i = -1; let i = -1;
const dispatch = async (index: number): Promise<Response> => { const dispatch = async (index: number): Promise<Response> => {
if (index <= i) throw new Error('next() called multiple times'); if (index <= i) throw new Error('next() called multiple times');
i = index; i = index;
const fn: IMiddleware | IHandler = index < middleware.length const fn: IMiddleware<_TContext> | IHandler<_TContext> =
? middleware[index] index < middleware.length ? middleware[index] : handler;
: handler;
if (!fn) return new Response('Internal error', { status: 500 }); if (!fn) return new Response('Internal error', { status: 500 });
return index < middleware.length return index < middleware.length
? await fn(ctx, () => dispatch(index + 1)) ? 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);
} }
} }

View File

@@ -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. * 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<TContext extends IContext = IContext> = (
res: Response,
ctx: TContext,
) => Response;