Expand description
Canonical error middleware (DESIGN.md §3.2 / §3.6 / §3.7).
Post-processes responses with Content-Type: application/problem+json,
filling missing trace_id (W3C traceparent → x-trace-id →
x-request-id → span-id fallback) and instance (request URI path).
Logs at warn! for 4xx / error! for 5xx with structured fields.
Any other error-status response whose body is genuinely unstructured (a
tower-layer short-circuit such as RequestBodyLimitLayer, an unmatched
route, or any other rejection never typed as CanonicalError - all of
which render as text/plain or omit Content-Type entirely, never a
real content type a handler chose on purpose) is wrapped into a minimal,
valid RFC 9457 Problem rather than passed through as-is - see
wrap_foreign_response and is_unstructured_error_body. A response with
any other Content-Type (application/json, text/html, …) was
deliberately shaped by the handler that returned it and is left alone -
this fallback exists to rescue responses nothing ever shaped on purpose,
not to overwrite one that was.
A foreign 5xx is mapped to the real internal canonical category
(DESIGN.md §2.1’s fail-safe fallback: “no error escapes the system
without a canonical category”) since it is, by definition, this
platform’s own fault, not the client’s. A foreign 4xx uses RFC 9457
§4.2.1’s "about:blank" convention instead - unlike a 5xx, a bare 4xx
genuinely doesn’t determine one canonical category over another (e.g.
invalid_argument vs. failed_precondition vs. out_of_range), so
about:blank is the honest “no more specific type than the status code”
rather than a guess.
crate::api::rest::extract (Json, Query, Path) is the precise,
per-extractor counterpart to this generic fallback: prefer it wherever
the failure’s shape is known ahead of time. See
docs/arch/errors/ADR/0006-cpt-cf-adr-error-middleware-catchall.md for
the decision record. Panics remain out of scope here - CatchPanicLayer
handles those before this middleware ever sees a response.
This middleware always renders JSON, gear-wide, with no content
negotiation, for the responses it actually touches. Every wrapped
Problem (this fallback’s, and every CanonicalError’s IntoResponse)
is application/problem+json unconditionally - there is no
Accept-header check anywhere in this pipeline. Since this middleware
wraps a gear’s entire router (every gear, via toolkit’s OoP
bootstrap), it would be equally unconditional about which responses it
rewrites if it matched on status alone - which is exactly the bug
is_unstructured_error_body exists to close: a gear route that
deliberately returns a non-CanonicalError, non-JSON, or custom-shaped
JSON error body (server-rendered HTML, a legacy hand-rolled health
check, …) keeps that body exactly as returned, on any status. Only a
response with no shape at all - because nothing ever gave it one - is
this fallback’s business.
Functions§
- canonical_
error_ middleware - Tower middleware function that fills
trace_id/instanceon canonical Problem responses and logs atwarn!(4xx) /error!(5xx); wraps any other error-status response into a minimal RFC 9457Problem.