6.4 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |
|---|---|---|---|---|---|---|---|---|
| http/gateway/error-mapping | Implement CallError-to-HTTP-status error mapping (ADR-023) | completed |
|
narrow | low | component | implementation |
Description
Implement the CallError code → HTTP status code mapping in
src/gateway/error.rs. This is the error-mapping table the HTTP server's
gateway endpoints use to translate call-protocol CallError codes into
HTTP response status codes (ADR-023). The mapping is a two-way-door
default (the exact status for ambiguous codes can be refined
additively); the one-way constraint is that protocol-level and
operation-level codes are distinct (ADR-023) and from_openapi-imported
codes are prefixed HTTP_<status> to avoid collision with protocol
codes.
The mapping table (http-server.md §"Error Mapping")
Call code |
HTTP status | Notes |
|---|---|---|
NOT_FOUND (operation not registered, or Internal op) |
404 |
|
FORBIDDEN (insufficient scopes, or unauthenticated) |
401 (no token) / 403 (token present) |
|
INVALID_INPUT (schema mismatch) |
422 |
|
TIMEOUT |
504 |
retryable: true |
INTERNAL |
500 |
|
Operation-level domain code with http_status (ADR-023) |
the declared http_status |
from_openapi-imported ops carry the original status |
Operation-level domain code without http_status |
500 |
The HTTP_<status> prefix rule (ADR-023 §5)
from_openapi maps OpenAPI non-2xx response status codes to
ErrorDefinitions with codes prefixed HTTP_ + the status number:
// OpenAPI: 404: { schema: NotFoundError }
// → ErrorDefinition { code: "HTTP_404", http_status: Some(404), schema: NotFoundError }
The normative rule (review #002 W20): from_openapi must not produce
error codes that collide with the five protocol-level codes (NOT_FOUND,
FORBIDDEN, INVALID_INPUT, INTERNAL, TIMEOUT). The HTTP_<status>
prefix enforces this.
retryable → Retry-After hint
The retryable field from CallError maps to an HTTP Retry-After hint
for 503/429-class errors (operation-level codes with http_status in
that range). The hint is optional; if the operation-level error does not
carry a retry-after value, no header is added.
API
/// Map a CallError to an HTTP status code (ADR-023).
pub fn call_error_to_http_status(error: &CallError) -> u16;
/// Map a CallError to an HTTP response, including the Retry-After hint
/// when applicable. The body is the serialized CallError (or its
/// `details` field).
pub fn call_error_to_http_response(error: &CallError) -> axum::response::Response;
The FORBIDDEN case needs the caller's identity state to distinguish
401 (no token) from 403 (token present but insufficient scopes). The
mapping function takes an Option<Identity> (or a flag) so the gateway
endpoint can pass the resolved identity through:
/// Map a CallError to an HTTP status code, considering whether the caller
/// was authenticated (FORBIDDEN → 401 if no identity, 403 if identity
/// present but insufficient scopes).
pub fn call_error_to_http_status_with_identity(
error: &CallError,
identity: Option<&Identity>,
) -> u16;
What this task does NOT do
- No
to_openapierror projection.to_openapiprojectserror_schemasto the gateway endpoint's response definitions (the OpenAPI doc'sresponsesblock). That is theto-openapitask, not this one. This task is the runtime HTTP response mapping. - No
from_openapierror import.from_openapibuildsErrorDefinitions from OpenAPI non-2xx responses with theHTTP_<status>prefix. That is thefrom-openapitask. This task consumes the resultingCallErrorcodes at runtime.
Acceptance Criteria
call_error_to_http_status(error: &CallError) -> u16implementedNOT_FOUND→ 404FORBIDDEN→ 401 (no identity) / 403 (identity present)INVALID_INPUT→ 422TIMEOUT→ 504INTERNAL→ 500- Operation-level code with
http_status→ declared status - Operation-level code without
http_status→ 500 HTTP_<status>-prefixed codes (fromfrom_openapi) → the status numbercall_error_to_http_response(error)builds anaxum::response::Responsewith the status + JSON bodyretryable: trueon503/429-class errors →Retry-Afterheader (when value present)call_error_to_http_status_with_identity(error, identity)for the 401/403 split- Unit test: each protocol code maps to the correct status
- Unit test: operation-level code with
http_statusmaps to declared status - Unit test: operation-level code without
http_statusmaps to 500 - Unit test:
HTTP_404code maps to 404 (not collided with protocolNOT_FOUND) - Unit test:
FORBIDDENwithNoneidentity → 401 - Unit test:
FORBIDDENwithSome(identity)→ 403 cargo test -p alknet-httpsucceedscargo clippy -p alknet-http --all-targetssucceeds with no warnings
References
- docs/architecture/crates/http/http-server.md — Error Mapping table (§"Error Mapping")
- docs/architecture/crates/http/http-adapters.md — Error Fidelity (§"Error Fidelity (ADR-023)")
- docs/architecture/decisions/023-operation-error-schemas.md — ADR-023 (protocol/operation codes distinct, HTTP_ prefix)
Notes
The mapping is a two-way-door default (the exact status for ambiguous codes can be refined additively); the one-way constraint is that protocol-level and operation-level codes are distinct (ADR-023) and from_openapi-imported codes are prefixed HTTP_. The FORBIDDEN case needs the caller's identity state to distinguish 401 (no token) from 403 (token present but insufficient scopes). This task is the runtime HTTP response mapping; the to_openapi doc-level error projection is the to-openapi task, and the from_openapi error import is the from-openapi task.
Summary
Implemented call_error_to_http_status, call_error_to_http_status_with_identity (FORBIDDEN→401 no identity / 403 identity present), and call_error_to_http_response (JSON body + Retry-After for retryable 503/429 with details.retry_after) in src/gateway/error.rs. Five protocol codes map to fixed statuses. HTTP_- prefixed operation-level codes parse status from prefix (no collision). Unknown operation-level codes default to 500. 21 unit tests. Build/clippy/test all clean.