error-envelope
A tiny Rust crate for consistent HTTP error responses across services.
This is a Rust port of err-envelope (Go), providing feature parity with the Go implementation.
Overview
Consistent error format: One predictable JSON structure for all HTTP errors
Typed error codes: 18 standard codes as a type-safe enum
Axum integration: Implements IntoResponse for seamless API error handling
anyhow support: Optional feature for Fromanyhow::Error conversion
Traceability: Built-in support for trace IDs and retry hints
Minimal dependencies: Framework-agnostic core with opt-in integrations
// Real-world Axum handler
async
Table of Contents
- Why
- What You Get
- Installation
- Crate Features
- Quick Start
- Anyhow Integration
- Framework Integration
- API Reference
- Error Codes
- Examples
- Testing
- Design Principles
Why
Without a standard, every endpoint returns errors differently:
{"error": "bad request"}{"message": "invalid email"}{"code": "E123", "details": {...}}
This forces clients to handle each endpoint specially. error-envelope provides a single, predictable error shape.
What You Get
Every field has a purpose: stable codes for logic, messages for humans, details for context, trace IDs for debugging, and retry signals for resilience.
Rate limiting example:
The retry_after field (human-readable duration) appears when with_retry_after() is used.
Installation
[]
= "0.2"
With optional features:
[]
= { = "0.2", = ["axum-support", "anyhow-support"] }
You can enable either or both features depending on your use case.
đź“– Full API documentation: docs.rs/error-envelope
Crate Features
| Feature | Description |
|---|---|
default |
Core error envelope with no framework dependencies |
axum-support |
Adds IntoResponse implementation for Axum framework integration |
anyhow-support |
Enables From<anyhow::Error> conversion for seamless interop with anyhow |
Quick Start
use Error;
Anyhow Integration
With the anyhow-support feature, anyhow::Error automatically converts to error_envelope::Error:
use Error;
async
This makes error-envelope a drop-in replacement for anyhow at HTTP boundaries:
use ;
use Error;
async
Framework Integration
Axum
With the axum-support feature, Error implements IntoResponse:
use ;
use Error;
async
// Error automatically converts to HTTP response with:
// - Correct status code
// - JSON body with error envelope
// - X-Request-ID header (if trace_id set)
// - Retry-After header (if retry_after set)
API Reference
Common Constructors
use Error;
// Generic errors
internal; // 500
bad_request; // 400
// Auth errors
unauthorized; // 401
forbidden; // 403
// Resource errors
not_found; // 404
method_not_allowed; // 405
request_timeout; // 408
conflict; // 409
gone; // 410
payload_too_large; // 413
unprocessable_entity; // 422
// Infrastructure errors
rate_limited; // 429
unavailable; // 503
timeout; // 504
// Downstream errors
downstream; // 502
downstream_timeout; // 504
Formatted Constructors
Use the format! macro for dynamic error messages:
use ;
// Using format! macro
let user_id = 123;
let err = not_foundf;
let db_name = "postgres";
let err = internalf;
Custom Errors
use ;
use Duration;
// Low-level constructor
let err = new;
// Add details
let err = err.with_details;
// Add trace ID
let err = err.with_trace_id;
// Override retryable
let err = err.with_retryable;
// Set retry-after duration
let err = err.with_retry_after;
Builder Pattern
All with_* methods consume and return Self, enabling fluent chaining:
let err = rate_limited
.with_details
.with_trace_id
.with_retry_after;
The builder pattern is immutable by default in Rust (unlike the Go version which had to implement copy-on-modify).
Error Codes
| Code | HTTP Status | Retryable | Use Case |
|---|---|---|---|
Internal |
500 | No | Unexpected server errors |
BadRequest |
400 | No | Malformed requests |
ValidationFailed |
400 | No | Invalid input data |
Unauthorized |
401 | No | Missing/invalid auth |
Forbidden |
403 | No | Insufficient permissions |
NotFound |
404 | No | Resource doesn't exist |
MethodNotAllowed |
405 | No | Invalid HTTP method |
RequestTimeout |
408 | Yes | Client timeout |
Conflict |
409 | No | State conflict (duplicate) |
Gone |
410 | No | Resource permanently deleted |
PayloadTooLarge |
413 | No | Request body too large |
UnprocessableEntity |
422 | No | Semantic validation failed |
RateLimited |
429 | Yes | Too many requests |
Canceled |
499 | No | Client canceled request |
Unavailable |
503 | Yes | Service temporarily down |
Timeout |
504 | Yes | Gateway timeout |
DownstreamError |
502 | Yes | Upstream service failed |
DownstreamTimeout |
504 | Yes | Upstream service timeout |
Design Principles
Minimal: ~500 lines of code, no unnecessary dependencies.
Framework-agnostic: Works standalone; integrations are opt-in via features.
Predictable: Error codes are stable and semantically meaningful.
Observable: Built-in trace IDs and structured details for debugging and logging.
Examples
See examples/axum_server.rs for a complete Axum server demonstrating:
- Validation errors with field details
- Rate limiting with retry-after
- Downstream error handling
- Trace ID propagation
Run it:
Test endpoints:
Testing
License
MIT