import "../core/metadata.ts"; import { defineMemberMetadata, getAllMemberMetadata, } from "../core/metadata.ts"; import type { HttpMethod } from "../contracts/typegen/http/method.ts"; import { normalizeMethod } from "../contracts/typegen/http/method.ts"; import type { StatusCode } from "../contracts/typegen/http/respcode.ts"; /** * `@Route()` binds a controller method to an HTTP route. */ export const ROUTE_METADATA_KEY = "muddle:route"; export interface RouteOptions { /** HTTP method(s) handled by the route, defaults to `GET`. */ method?: HttpMethod | HttpMethod[]; /** Status code used when the handler returns a plain value. */ status?: StatusCode; /** Route name, used by the fluent view helpers. */ name?: string; /** Human-readable route description. */ description?: string; /** Parameter description by parameter name. */ params?: Record; } export interface RouteMetadata { /** Path relative to the controller prefix. */ path: string; method: HttpMethod; options: RouteOptions; /** Name of the decorated method. */ propertyKey: string; } function normalizeMethods(method: RouteOptions["method"]): HttpMethod[] { if (method === undefined) return ["GET"]; const list = Array.isArray(method) ? method : [method]; return list.map((entry) => normalizeMethod(entry)); } /** Declares one or more routes for a controller method. */ export function Route( path: string, options: RouteOptions = {}, ): MethodDecorator { return (target, propertyKey, descriptor) => { for (const httpMethod of normalizeMethods(options.method)) { defineMemberMetadata( ROUTE_METADATA_KEY, { path, method: httpMethod, options, propertyKey: String(propertyKey), } satisfies RouteMetadata, target as object, propertyKey as string, ); } return descriptor; }; } /** * Reads every route declared on a controller class. * * When a property key is given, only the routes of that method are returned. */ export function getRoutes( target: object, propertyKey?: string, ): RouteMetadata[] { return getAllMemberMetadata(ROUTE_METADATA_KEY, target) .filter( (entry) => propertyKey === undefined || entry.propertyKey === propertyKey, ) .map((entry) => entry.value as RouteMetadata); }