Initial Commit
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
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",
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user