import { findLongestAndNearestMatches } from "./_matcher.ts";import { ServeHandler, ServerRequest } from "./server.ts";import { RoutingError } from "./error.ts";import { acceptable, acceptWebSocket, WebSocket,} from "./vendor/https/deno.land/std/ws/mod.ts";import { assert } from "./vendor/https/deno.land/std/testing/asserts.ts";
export interface RouteHandler { (req: ServerRequest): | void | Promise<void>;}
export interface WebSocketHandler { ( sock: WebSocket, req: ServerRequest, ): void | Promise<void>;}
export interface ErrorHandler { ( e: any | RoutingError, req: ServerRequest, ): void | Promise<void>;}
export interface Route { handleRoute(prefix: string, req: ServerRequest): Promise<void>;}
function isRoute(x: any): x is Route { return typeof x?.handleRoute === "function";}
export interface Router extends Route { use(...handlers: ServeHandler[]): void;
handle(pattern: string | RegExp, ...handlers: RouteHandler[]): void;
route(prefix: string, ...handlers: (RouteHandler | Router)[]): void;
get(pattern: string | RegExp, ...handlers: RouteHandler[]): void;
post(pattern: string | RegExp, ...handlers: RouteHandler[]): void;
put(pattern: string | RegExp, ...handlers: RouteHandler[]): void;
delete(pattern: string | RegExp, ...handlers: RouteHandler[]): void;
options(pattern: string | RegExp, ...handlers: RouteHandler[]): void;
ws(pattern: string | RegExp, handler: WebSocketHandler): void; ws( pattern: string | RegExp, handlers: RouteHandler[], handler: WebSocketHandler, ): void;
catch(handler: ErrorHandler): void;
finally(handler: ServeHandler): void;}
export function createRouter(): Router { const middlewareList: ServeHandler[] = []; const routes: { pattern: string | RegExp; methods?: string[]; handlers: RouteHandler[]; wsHandler?: WebSocketHandler; }[] = []; const prefixers: { prefix: string; handlers: RouteHandler[]; }[] = [];
let errorHandler: ErrorHandler | undefined; let finalHandler: ServeHandler | undefined;
function handle(pattern: string | RegExp, ...handlers: RouteHandler[]) { routes.push({ pattern, handlers }); }
function route(prefix: string, ...handlers: RouteHandler[]) { prefixers.push({ prefix, handlers }); }
function get(pattern: string | RegExp, ...handlers: RouteHandler[]) { routes.push({ pattern, methods: ["GET", "HEAD"], handlers, }); }
function post(pattern: string | RegExp, ...handlers: RouteHandler[]) { routes.push({ pattern, methods: ["POST"], handlers }); }
function put(pattern: string | RegExp, ...handlers: RouteHandler[]) { routes.push({ pattern, methods: ["PUT"], handlers }); }
function _delete(pattern: string | RegExp, ...handlers: RouteHandler[]) { routes.push({ pattern, methods: ["DELETE"], handlers }); }
function options(pattern: string | RegExp, ...handlers: RouteHandler[]) { routes.push({ pattern, methods: ["OPTIONS"], handlers }); }
function use(...handlers: ServeHandler[]) { middlewareList.push(...handlers); }
function ws(pattern: string | RegExp, ...args: any[]) { if (Array.isArray(args[0])) { routes.push({ pattern, handlers: args[0], wsHandler: args[1] }); } else if (typeof args[0] === "function") { routes.push({ pattern, handlers: [], wsHandler: args[0] }); } else { throw new Error("invalid function arguments"); } }
function _catch(handler: ErrorHandler) { errorHandler = handler; }
function _finally(handler: ServeHandler) { finalHandler = handler; }
async function chainRoutes( prefix: string, req: ServerRequest, handlers: (RouteHandler | Router)[], ): Promise<boolean> { for (const handler of handlers) { if (isRoute(handler)) { await handler.handleRoute(prefix, req); } else { await handler(req); } if (req.isResponded()) { return true; } } return false; } async function handleRouteInternal( parentMatch: string, req: ServerRequest, ): Promise<void> { for (const handler of middlewareList) { await handler(req); if (req.isResponded()) { return; } } const subpath = req.path.slice(parentMatch.length) || "/"; for (const { prefix, handlers } of prefixers) { if (subpath.startsWith(prefix)) { const match = subpath.match(new RegExp(`^${prefix}`)); assert(match != null); req.match = match; if (await chainRoutes(parentMatch + prefix, req, handlers)) { return; } } } const matches = findLongestAndNearestMatches( subpath, routes.map((v) => v.pattern), ); if (matches.length > 0) { for (const [i, match] of matches) { const { methods, handlers, wsHandler } = routes[i]; if (methods && !methods.includes(req.method)) { continue; } req.match = match; if (await chainRoutes(parentMatch + match, req, handlers)) { return; } if (wsHandler && acceptable(req)) { const sock = await acceptWebSocket(req); req.markAsResponded(101); wsHandler(sock, req); } } if (!req.isResponded()) { throw new RoutingError(404); } } else { throw new RoutingError(404); } } const handleRoute = async (parentMatch: string, req: ServerRequest) => { try { await handleRouteInternal(parentMatch, req); } catch (e) { if (errorHandler) { await errorHandler(e, req); if (!req.isResponded()) { throw e; } } else { throw e; } } finally { finalHandler?.(req); } }; return { handleRoute, use, handle, route, get, post, options, put, delete: _delete, ws, catch: _catch, finally: _finally, };}