rust-rfc7807
RFC 7807 Problem Details for HTTP APIs in Rust.
Overview
RFC 7807 defines application/problem+json, a standard JSON
format for HTTP API error responses. Instead of every service inventing its own error shape, clients
get a consistent, predictable structure they can parse and act on.
rust-rfc7807 provides a lightweight Rust implementation with an ergonomic builder API,
safe defaults that prevent leaking internal details in 500 responses, and first-class Axum integration.
Features
- RFC 7807 compliant
application/problem+jsonresponses - Builder API with constructors for common HTTP status codes
- Structured validation errors with field-level detail
- Stable error codes for frontend/client consumption
- Internal cause storage that is never serialized to JSON
- Safe 500 defaults -- generic public message, no secret leaks
- Trace and request ID correlation
- Axum
IntoResponseintegration (optional feature) - Minimal dependencies: core crate requires only
serdeandserde_json
Workspace
| Crate | Description |
|---|---|
rust-rfc7807 |
Core Problem type, builder, traits, serialization |
rust-rfc7807-axum |
Axum IntoResponse, ApiError wrapper, trace helpers |
Example Responses
404 Not Found
422 Validation Error
500 Internal Server Error
Internal details (database errors, stack traces, credentials) are never included in 500 responses.
Quick Start (Axum)
Add to your Cargo.toml:
[]
= "0.1"
Define a handler that returns Result<T, ApiError>:
use ;
use Problem;
use ApiError;
async
async
The response will have status 404, content type application/problem+json, and a structured JSON body.
Creating Problems
Use status constructors and the builder API:
use Problem;
// 404 with all fields
let problem = not_found
.type_
.title
.detail
.instance
.code
.trace_id;
// Available constructors
let _ = new; // Any status
let _ = bad_request; // 400
let _ = unauthorized; // 401
let _ = forbidden; // 403
let _ = not_found; // 404
let _ = conflict; // 409
let _ = unprocessable_entity;// 422
let _ = validation; // 422 with defaults
let _ = too_many_requests; // 429
let _ = internal_server_error; // 500 with safe defaults
Mapping Domain Errors
Implement the IntoProblem trait on your application's error types:
use ;
Validation Errors
Use Problem::validation() with push_error and push_error_code:
use Problem;
let problem = validation
.push_error_code
.push_error_code
.push_error
.code;
Each error in the "errors" array includes:
| Field | Type | Description |
|---|---|---|
field |
String |
Field path (e.g. "email", "address.zip") |
message |
String |
Human-readable description |
code |
Option<String> |
Machine-readable code for client logic |
Frontends can map field directly to form inputs and use code for i18n or conditional UI.
Security Notes
500 errors are the most dangerous for information leakage. Database connection strings, stack traces, and internal identifiers can end up in API responses if error handling is careless.
This crate prevents that by design:
Problem::internal_server_error()defaults to a generic public message..with_cause()stores the diagnostic error in a field marked#[serde(skip)]-- it never appears in JSON output.ApiError::internal()wraps any error into a safe 500 response automatically.
use Problem;
let problem = internal_server_error
.with_cause;
// Safe for clients
let json = to_string.unwrap;
assert!;
assert!;
// Available for server-side logging
let cause = problem.internal_cause.unwrap;
assert!;
Integrations
Axum (available now)
rust-rfc7807-axum provides:
IntoResponseforProblem(sets status code + content type)ApiErrorenum for use inResult<T, ApiError>handler return typesattach_trace()helper for trace IDs- Optional
tracingfeature for span-based trace ID extraction
Planned
- Actix Web integration
utoipa/ OpenAPI schema generationvalidatorcrate bridge for automatic field error extraction
Design Philosophy
- Standards first. Follow RFC 7807 faithfully. Extensions are additive, not breaking.
- Minimal dependencies. Core crate depends only on
serdeandserde_json. Framework integrations are separate crates. - Explicit over magic. No global error registries. No derive macros that hide behavior. You implement
IntoProblem, you see exactly what maps to what. - Secure by default. 500 errors produce generic messages. Internal causes must be opted into and are never serialized.
- Predictable output.
Nonefields are omitted. Empty extensions produce no extra keys. What you build is what gets serialized.
Roadmap
- Actix Web integration crate
-
#[derive(IntoProblem)]macro for enum error types -
utoipa/ OpenAPI schema generation forProblem -
validatorcrate bridge for automatic field error extraction
MSRV
Minimum supported Rust version: 1.75.
License
Licensed under the MIT License.