78 lines
1.7 KiB
TypeScript
78 lines
1.7 KiB
TypeScript
import { STATUS_CODE } from '@std/http';
|
|
|
|
import { OrPromise } from './util.ts';
|
|
|
|
export type Method = string;
|
|
|
|
export const Method = {
|
|
GET: 'GET',
|
|
POST: 'POST',
|
|
PUT: 'PUT',
|
|
PATCH: 'PATCH',
|
|
Any: '___INTERNAL_ANY',
|
|
};
|
|
|
|
export type Handler = (
|
|
request: Request,
|
|
pathParams: Record<string, string>,
|
|
) => OrPromise<Response>;
|
|
|
|
export type Match = string | URLPattern;
|
|
export class Router {
|
|
#pathnameCache: Map<string, URLPattern>;
|
|
#routes: Map<URLPattern, Map<Method, Handler>>;
|
|
|
|
constructor() {
|
|
this.#routes = new Map();
|
|
this.#pathnameCache = new Map();
|
|
}
|
|
|
|
register(method: Method, match: Match, handler: Handler) {
|
|
const urlPattern = (match instanceof URLPattern) ? match : this.#cachePathname(match);
|
|
if (!this.#routes.has(urlPattern)) {
|
|
this.#routes.set(urlPattern, new Map());
|
|
}
|
|
|
|
const methodMap = this.#routes.get(urlPattern)!;
|
|
method = method.toUpperCase();
|
|
methodMap.set(method, handler);
|
|
}
|
|
|
|
#cachePathname(pathname: string) {
|
|
const cached = this.#pathnameCache.get(pathname);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
|
|
const pattern = new URLPattern({ pathname });
|
|
this.#pathnameCache.set(pathname, pattern);
|
|
|
|
return pattern;
|
|
}
|
|
|
|
async handle(req: Request): Promise<Response> {
|
|
const method = req.method.toUpperCase();
|
|
|
|
for (const [pattern, methodMap] of this.#routes.entries()) {
|
|
const match = pattern.exec(req.url);
|
|
if (!match) {
|
|
continue;
|
|
}
|
|
|
|
let handler = methodMap.get(method);
|
|
if (!handler) {
|
|
handler = methodMap.get(Method.Any);
|
|
}
|
|
if (!handler) {
|
|
return new Response('Method not allowed', {
|
|
status: STATUS_CODE.MethodNotAllowed,
|
|
});
|
|
}
|
|
|
|
return handler(req, match.pathname.groups as Record<string, string>);
|
|
}
|
|
|
|
return new Response('Not found', { status: STATUS_CODE.NotFound });
|
|
}
|
|
}
|