first commit
This commit is contained in:
90
docs/api-stack/api/api-plugin-cms/src/routes/problem-json.md
Normal file
90
docs/api-stack/api/api-plugin-cms/src/routes/problem-json.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# RFC 7807 problem+json renderer
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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's `Response` for `return res.body(...)` style handlers.
|
||||
- `PROBLEM_JSON_CONTENT_TYPE` constant — `application/problem+json` per
|
||||
RFC 7807.
|
||||
- Local `ProblemDetails` interface 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` → 404
|
||||
- `CMS.INVALID_TRANSITION` / `CMS.DUPLICATE_SLUG` / `CMS.MODEL_IN_USE` → 409
|
||||
- `CMS.VALIDATION_FAILED` → 400
|
||||
- `CMS.INVALID_INPUT` → 500
|
||||
- Generic `SuaveplanError` instances 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
|
||||
|
||||
```ts
|
||||
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):
|
||||
|
||||
```ts
|
||||
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.
|
||||
Reference in New Issue
Block a user