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:
25
src/Errors/InvalidHttpMethodError.ts
Normal file
25
src/Errors/InvalidHttpMethodError.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
Reference in New Issue
Block a user