Skip to main content

Module canonical_error_layer

Module canonical_error_layer 

Source
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 / instance on canonical Problem responses and logs at warn! (4xx) / error! (5xx); wraps any other error-status response into a minimal RFC 9457 Problem.