Files
2026-05-27 23:29:53 +01:00

2.6 KiB

routes/problem-json — RFC 7807 problem+json helper

Purpose

Bridges thrown errors from the route handlers into RFC 7807 application/problem+json responses. Reads the HTTP status from the error's context.statusCode when present so the canonical ImportError raised by the service / plugin layer surfaces with the correct HTTP code without a code → status lookup table in the router.

Unknown throwables fall back to a generic 500 so the router never crashes — the renderer is the safety net for non-SuaveplanError exceptions that escape upstream code.

Features

  • respondProblemJson(c, err) — writes the Response to the Hono context with content-type: application/problem+json.
  • renderProblem(err) — pure render returning a ProblemDetails object (used by tests + alt transports).
  • PROBLEM_JSON_CONTENT_TYPE constant for downstream re-use.
  • Native Error paths preserve the name so debugging upstream failures stays readable.

Usage: Basic Example

import { respondProblemJson } from "@suaveplan/api-plugin-import";

app.get("/foo", (c) => {
    try {
        throw new ImportError(IMPORT_CODES.NOT_FOUND, "missing", {
            context: { statusCode: 404 },
        });
    } catch (err) {
        return respondProblemJson(c, err);
    }
});

Usage: Advanced Example

import { renderProblem } from "@suaveplan/api-plugin-import";

// Pure render for use in alternate transports (gRPC adapter, queue worker)
const body = renderProblem(new Error("boom"));
// body === { type: "urn:suaveplan:error:internal", title: "Error", status: 500, detail: "boom" }
const message = JSON.stringify(body);
publishToDeadLetterQueue(message);

API Reference

  • respondProblemJson(c: HonoLike, err: unknown): Response
  • renderProblem(err: unknown): ProblemDetails
  • interface HonoLike { header(name, value); body(data, status): Response }
  • interface ProblemDetails { type; title; status; detail; code?; [k: string]: unknown }
  • const PROBLEM_JSON_CONTENT_TYPE = "application/problem+json"

Implementation Notes

  • The type field is built as urn:suaveplan:error:<lowercased-code> so consumers can branch on the URN without parsing the body.
  • Status hints below 400 (or missing) collapse to 500 — clients should never receive a problem+json body with a 2xx status code.
  • The Error.name fallback ("InternalError") protects against thrown objects whose name was wiped by a downstream serializer.
  • renderProblem returns a ProblemDetails literal so consumers that need to merge additional fields (e.g. instance) can spread it.