import type { Context } from "@wrnexus/core"; import { can } from "@wrnexus/authz"; export class HttpError extends Error { constructor( public readonly status: number, message: string, public readonly details?: unknown, ) { super(message); this.name = "HttpError"; } } export function subjectId(ctx: Pick): string { return String((ctx.user as { id?: unknown } | undefined)?.id ?? "").trim(); } export function requireUser(ctx: Pick): { id: string; user: unknown } { const id = subjectId(ctx); if (!id) throw new HttpError(401, "Unauthorized"); return { id, user: ctx.user }; } export function requireParam( ctx: Pick, name: string, options: { integer?: boolean; positive?: boolean } = {}, ): string | number { const raw = String(ctx.params[name] ?? "").trim(); if (!raw) throw new HttpError(400, `Route parameter '${name}' is required.`); if (!options.integer) return raw; const value = Number(raw); if (!Number.isInteger(value) || (options.positive && value < 1)) { throw new HttpError(400, `Route parameter '${name}' must be a positive integer.`); } return value; } export async function requireJson>( ctx: Pick, ): Promise { try { return (await ctx.req.json()) as T; } catch { throw new HttpError(400, "Request body must be valid JSON."); } } export async function requirePermission( ctx: Context, permission: string, resource?: unknown, ): Promise { if (!(await can(ctx, permission, resource))) throw new HttpError(403, "Forbidden"); } export const json = { ok: (value: T) => Response.json(value), created: (value: T) => Response.json(value, { status: 201 }), noContent: () => new Response(null, { status: 204 }), error: (status: number, message: string, details?: unknown) => Response.json({ error: message, ...(details === undefined ? {} : { details }) }, { status }), }; export type ApiHandler = (ctx: Context) => Response | Promise; /** Convert thrown HttpError values into the framework's standard JSON error shape. */ export function defineApiRoute(handler: ApiHandler): ApiHandler { return async (ctx) => { try { return await handler(ctx); } catch (error) { if (error instanceof HttpError) return json.error(error.status, error.message, error.details); throw error; } }; } export function authorized( permission: string, handler: ApiHandler, resource?: (ctx: Context) => unknown | Promise, ): ApiHandler { return defineApiRoute(async (ctx) => { requireUser(ctx); await requirePermission(ctx, permission, await resource?.(ctx)); return handler(ctx); }); }