import { RouterTree } from "@lib/routerTree.ts"; import { none, Option, some } from "@shared/utils/option.ts"; import { Context } from "@lib/context.ts"; type RequestHandler = ( c: Context, ) => Promise | Response; export type Middleware = ( c: Context, next: () => Promise, ) => Promise | Response | undefined; type MethodHandlers = Partial< Record> >; const DEFAULT_NOT_FOUND_HANDLER = () => new Response("404 Not found"); class HttpRouter { routerTree = new RouterTree>(); pathPreprocessor?: (path: string) => string; middlewareChain: Middleware[] = []; defaultNotFoundHandler: RequestHandler = DEFAULT_NOT_FOUND_HANDLER; setPathProcessor(processor: (path: string) => string) { this.pathPreprocessor = processor; } use(mw: Middleware): HttpRouter { this.middlewareChain.push(mw); return this; } add( path: S, method: string, handler: RequestHandler, ): HttpRouter; add( path: S[], method: string, handler: RequestHandler, ): HttpRouter; add( path: string | string[], method: string, handler: RequestHandler, ): HttpRouter { const paths = Array.isArray(path) ? path : [path]; for (const p of paths) { this.routerTree.getHandler(p).match( (mth) => { mth[method] = handler; }, () => { const mth: MethodHandlers = {}; mth[method] = handler; this.routerTree.add(p, mth); }, ); } return this; } // Overload signatures for 'get' get(path: S, handler: RequestHandler): HttpRouter; get( path: S[], handler: RequestHandler, ): HttpRouter; // Non-generic implementation for 'get' get(path: string | string[], handler: RequestHandler): HttpRouter { if (Array.isArray(path)) { return this.add(path, "GET", handler); } return this.add(path, "GET", handler); } post(path: S, handler: RequestHandler): HttpRouter; post( path: string[], handler: RequestHandler, ): HttpRouter; post(path: string | string[], handler: RequestHandler): HttpRouter { if (Array.isArray(path)) { return this.add(path, "POST", handler); } return this.add(path, "POST", handler); } async handleRequest( req: Request, connInfo: Deno.ServeHandlerInfo, ): Promise { const c = new Context(req, connInfo, {}); let i = 0; const mw = this.middlewareChain[i++]; const path = this.pathPreprocessor ? this.pathPreprocessor(c.path) : c.path; return await this.routerTree .find(path) .andThen((routeMatch) => { const { value, params } = routeMatch; const handler = value[req.method]; return handler ? some(handler(Context.setParams(c, params))) : none; }) .unwrapOrElse(() => this.defaultNotFoundHandler(c)); } } export type ExtractRouteParams = T extends string ? T extends `${infer _Start}:${infer Param}/${infer Rest}` ? Param | ExtractRouteParams : T extends `${infer _Start}:${infer Param}` ? Param : T extends `${infer _Start}*` ? "restOfThePath" : never : never; export type Params = { [K in Keys]: string; }; export default HttpRouter;