import type { Express, Request, Response } from 'express'; import { createApiError } from '@open-design/contracts'; import { rawInput } from './parse.js'; import { sendApiError, sendJson, statusForError } from './response.js'; import { guardSameOrigin, type OriginContext } from './origin-guard.js'; import type { JsonRouteSpec } from './types.js'; export interface AdapterContext extends OriginContext {} /** * Identity function that pins a route spec's generic parameters at the * definition site so callers do not have to repeat them. The returned spec * is consumed by `mountJsonRoute` (live) and by tests (direct invocation of * `route.parse` / `route.handle`). */ export function defineJsonRoute( spec: JsonRouteSpec, ): JsonRouteSpec { return spec; } /** * Mounts one JsonRouteSpec on an Express app. The Adapter is the only code * here that knows about req/res; the route's parse and handle functions * operate on `RouteInputContext` and `Deps` respectively, so they are unit * testable without Express. */ export function mountJsonRoute( app: Express, spec: JsonRouteSpec, deps: Deps, adapter: AdapterContext, ): void { app[spec.method](spec.path, async (req: Request, res: Response) => { try { if (spec.requireSameOrigin) { const origin = guardSameOrigin(req, adapter); if (!origin.ok) { sendApiError(res, statusForError(origin.error), origin.error); return; } } const parsed = spec.parse(rawInput(req)); if (!parsed.ok) { sendApiError(res, statusForError(parsed.error), parsed.error); return; } const result = await spec.handle(parsed.value, deps); if (!result.ok) { sendApiError(res, statusForError(result.error), result.error); return; } sendJson(res, spec.successStatus ?? 200, result.value); } catch (e) { const message = e instanceof Error ? e.message : String(e); sendApiError(res, 500, createApiError('INTERNAL_ERROR', message)); } }); }