axum-anyhow
A library for ergonomic error handling in Axum applications using anyhow.
This crate provides extension traits and utilities to easily convert Result and Option types into HTTP error responses with proper status codes, titles, and details.
[!WARNING] This project is new and under active development. The API is still in flux and may have breaking changes between releases. While we follow semantic versioning to prevent changes from breaking your build, you may need to perform manual migration steps when upgrading to new versions. Please review the CHANGELOG when updating.
Features
- Convert
anyhow::Resultto anApiErrorwith custom HTTP status codes. - Convert
Optionto anApiErrorwhenNoneis encountered. - Returns JSON responses in RFC 9457 format.
- Optional environment variable to expose error details in development mode.
Installation
Add this to your Cargo.toml:
[]
= "1.0"
= "0.8"
= "0" # Use the latest 0.x version from crates.io
= "1.0"
= { = "1.48", = ["full"] }
Quick Start
use Result;
use ;
use ;
use HashMap;
async
async
// Mock database
Usage Examples
Working with Results
Use the ResultExt trait to convert any anyhow::Result into an HTTP error response:
use ;
use Result;
async
Working with Options
Use the OptionExt trait to convert Option into an HTTP error response:
use ;
async
Available Status Codes
The library provides helper methods for common HTTP status codes:
| Method | Status Code | Use Case |
|---|---|---|
context_bad_request |
400 | Invalid client input |
context_unauthorized |
401 | Authentication required |
context_forbidden |
403 | Insufficient permissions |
context_not_found |
404 | Resource doesn't exist |
context_method_not_allowed |
405 | HTTP method not supported |
context_conflict |
409 | Resource conflict |
context_unprocessable_entity |
422 | Validation errors |
context_too_many_requests |
429 | Rate limit exceeded |
context_internal |
500 | Server errors |
context_bad_gateway |
502 | Invalid upstream response |
context_service_unavailable |
503 | Service temporarily unavailable |
context_gateway_timeout |
504 | Upstream timeout |
context_status |
Custom | Any custom status code |
Creating Errors Directly
You can also create errors directly without Results or Options:
use ;
use StatusCode;
// Using helper functions for common status codes
let error = bad_request;
let error = unauthorized;
let error = forbidden;
let error = not_found;
let error = method_not_allowed;
let error = conflict;
let error = unprocessable_entity;
let error = too_many_requests;
let error = internal_error;
let error = bad_gateway;
let error = service_unavailable;
let error = gateway_timeout;
// Using the builder for custom status codes
let error = builder
.status
.title
.detail
.build;
Error Response Format
All errors are serialized as JSON with the following structure:
Adding Metadata to Errors
You can include custom metadata in error responses using the meta field. This is useful for adding request IDs, trace information, timestamps, or other contextual data:
use StatusCode;
use ApiError;
use json;
let error = builder
.status
.title
.detail
.meta
.build;
This produces a JSON response like:
The meta field is omitted from the response if not set, keeping responses clean when metadata isn't needed.
Development Features
Exposing Error Details
By default, when an anyhow::Error is automatically converted to an ApiError (via the From trait), the error detail is set to the generic message "Something went wrong". This protects against accidentally leaking sensitive information in production.
However, during development, it can be helpful to see the actual error messages. You can enable this in two ways:
Programmatically (Recommended)
use set_expose_errors;
// Enable for development
set_expose_errors;
// Disable for production
set_expose_errors;
This is especially useful in tests or when you want fine-grained control:
use set_expose_errors;
set_expose_errors;
Via Environment Variable
You can also set the AXUM_ANYHOW_EXPOSE_ERRORS environment variable:
AXUM_ANYHOW_EXPOSE_ERRORS=1
# or
AXUM_ANYHOW_EXPOSE_ERRORS=true
With this enabled:
use anyhow;
use ApiError;
// Without expose_errors: detail = "Something went wrong"
// With expose_errors: detail = "Database connection failed"
let error: ApiError = anyhow!.into;
[!WARNING] Error messages may contain sensitive information like file paths, database details, or internal system information that should not be exposed to end users in production.
Error Hook
You can set a global hook that will be called whenever an ApiError is created. This is useful for logging, monitoring, or debugging errors in your application.
use on_error;
// Set up error logging
on_error;
The hook receives a reference to the ApiError and will be called automatically whenever an error is built, whether through the builder pattern, helper functions, or automatic conversions:
use ;
use anyhow;
use StatusCode;
// Set up the hook once at application startup
on_error;
// The hook will be called for all of these:
let error1: ApiError = bad_request;
let result: ApiError = anyhow!
.context_internal;
let error2 = builder
.status
.title
.detail
.build;
Common use cases for error hooks:
- Logging: Send errors to your logging system (e.g.,
tracing,log,slog) - Monitoring: Report errors to monitoring services (e.g., Sentry, Datadog, New Relic)
- Metrics: Increment error counters for observability
- Debugging: Print detailed error information during development
[!TIP] The error hook is global and thread-safe. You can call
on_errormultiple times to replace the hook, but only one hook can be active at a time.
Motivation
Without axum-anyhow, the code in our quick start example would look like this:
use Result;
use Path;
use StatusCode;
use ;
use HashMap;
async
async
// Mock database
Axum encourages you to create your own error types and conversion logic to reduce this boilerplate. axum-anyhow does this for you, providing extension traits and helper functions to convert standard Rust types (Result and Option) into properly formatted HTTP error responses.
axum-anyhow is designed for REST APIs and returns errors formatted according to RFC 9457. If you need more flexibility, please file an issue or copy the code into your project and modify it as needed.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.