import { SuaveplanError } from "@suaveplan/error"; /** Standard RFC 7807 content-type. */ export const PROBLEM_JSON_CONTENT_TYPE = "application/problem+json"; /** * Minimal structural Hono context surface relied on by * {@link respondProblemJson}. Declared as an interface so the renderer * does not couple to a specific Hono version. */ export interface HonoLike { header(name: string, value: string): void; body(data: string, status: number): Response; } /** RFC 7807 problem-details body. */ export interface ProblemDetails { readonly type: string; readonly title: string; readonly status: number; readonly detail: string; readonly code?: string; readonly [key: string]: unknown; } /** * Convert an arbitrary error into an RFC 7807 problem-details body and * write it to the supplied Hono context. `SuaveplanError` subclasses * surface their `code` and read `context.statusCode` for the HTTP code; * plain `Error` and unknown throwables fall through to a generic * `500 Internal Server Error`. */ export function respondProblemJson(c: HonoLike, err: unknown): Response { const body = renderProblem(err); c.header("content-type", PROBLEM_JSON_CONTENT_TYPE); return c.body(JSON.stringify(body), body.status); } /** * Pure render — kept separate from {@link respondProblemJson} so the * shape can be asserted in tests that do not construct a Hono context. */ export function renderProblem(err: unknown): ProblemDetails { if (err instanceof SuaveplanError) { const ctx = err.context as { statusCode?: number } | undefined; const status = typeof ctx?.statusCode === "number" && ctx.statusCode >= 400 ? ctx.statusCode : 500; return { type: `urn:suaveplan:error:${err.code.toLowerCase()}`, title: err.name, status, detail: err.message, code: err.code, }; } if (err instanceof Error) { return { type: "urn:suaveplan:error:internal", title: err.name || "InternalError", status: 500, detail: err.message || "Internal Server Error", }; } return { type: "urn:suaveplan:error:internal", title: "InternalError", status: 500, detail: "Internal Server Error", }; }