3.2 KiB
RFC 7807 problem+json renderer
(no submodules)
Purpose
Render CmsError (and arbitrary throwables) as RFC 7807 problem+json
HTTP responses without requiring @suaveplan/api-plugin-cms to depend
on any HTTP framework. The renderer accepts a small structural
HttpResponseLike interface that is compatible with Hono, Fastify,
Bun's stdlib Response, and any framework whose response object
exposes header(name, value) plus body(data, status). Status
mapping is encoded inline as a Record<string, number> lookup table
that the package-level spec-conformance test asserts is exhaustive
over CMS_CODES.
Features
- Pure
formatProblemJson(err)returns the JSON body for logging or side-channel use. respondProblemJson(res, err)writes header + body and returns the framework'sResponseforreturn res.body(...)style handlers.PROBLEM_JSON_CONTENT_TYPEconstant —application/problem+jsonper RFC 7807.- Local
ProblemDetailsinterface matching the IETF spec, with an extension-index signature so callers can add fields per RFC §3.2. - Status table:
CMS.CONTENT_NOT_FOUND/CMS.MODEL_NOT_FOUND→ 404CMS.INVALID_TRANSITION/CMS.DUPLICATE_SLUG/CMS.MODEL_IN_USE→ 409CMS.VALIDATION_FAILED→ 400CMS.INVALID_INPUT→ 500
- Generic
SuaveplanErrorinstances render with HTTP 500 but with the original code so log scrapers still see structure. - Non-error throwables render
{ type: "about:blank", status: 500 }.
Usage: Basic
import { Hono } from "hono";
import { respondProblemJson } from "@suaveplan/api-plugin-cms";
const app = new Hono();
app.onError((err, c) => respondProblemJson(c, err));
Usage: Advanced
Side-channel logging without writing to a response (e.g. forwarding the body to an audit pipeline):
import { formatProblemJson } from "@suaveplan/api-plugin-cms";
try {
engine.transitionStatus(id, target);
} catch (err) {
const body = formatProblemJson(err);
await audit.log({
type: "cms.transition.failed",
severity: body.status >= 500 ? "error" : "warning",
...body,
});
throw err;
}
API Reference
| Symbol | Description |
|---|---|
PROBLEM_JSON_CONTENT_TYPE |
"application/problem+json". |
interface ProblemDetails |
RFC 7807 body shape with an extension index. |
interface HttpResponseLike |
{ header(n, v); body(d, s): Response }. |
respondProblemJson(res, err) |
Write header + body; return Response. |
formatProblemJson(err) |
Pure variant. |
Implementation Notes
The status lookup is a Readonly<Record<string, number>> keyed by the
fully-qualified CMS.* code. If a CmsError is constructed with a
code not in the table (unreachable in normal use; possible only in
tests that bypass defineCodes), the renderer falls back to HTTP 500.
Generic SuaveplanError instances render with HTTP 500 + the
original code field so the response is still grep-friendly. The
HttpResponseLike shape was chosen over Context (Hono) to keep
this package free of framework dependencies — see
@suaveplan/api-plugin-database's routes/problem-json.ts for the
same pattern.