Expand description
§Api Error
A Rust crate for easily defining API-friendly error types with HTTP status codes and user-facing error messages.
§Usage
§Basic Enum Example
use api_error::ApiError;
use http::StatusCode;
#[derive(Debug, thiserror::Error, ApiError)]
enum MyError {
#[error("Invalid input")]
#[api_error(status_code = 400, message = "The provided input is invalid")]
InvalidInput,
#[error("Resource not found")]
#[api_error(status_code = 404, message = "The requested resource was not found")]
NotFound,
#[error("Internal error")]
#[api_error(status_code = StatusCode::INTERNAL_SERVER_ERROR)]
Internal,
}
let err = MyError::InvalidInput;
assert_eq!(err.status_code(), StatusCode::BAD_REQUEST);
assert_eq!(err.message().as_ref(), "The provided input is invalid");
assert_eq!(err.to_string(), "Invalid input"); // From thiserror§Enum with Fields and Formatting
use api_error::ApiError;
#[derive(Debug, thiserror::Error, ApiError)]
enum AppError {
// Unnamed fields with positional formatting
#[error("Database error: {0}")]
#[api_error(status_code = 500, message = "Database operation failed: {0}")]
Database(String),
// Named fields with named formatting
#[error("Validation failed on {field}")]
#[api_error(status_code = 422, message = "Field `{field}` has invalid value")]
Validation { field: String, value: String },
}
let err = AppError::Database("Connection timeout".to_string());
assert_eq!(err.status_code().as_u16(), 500);
assert_eq!(err.message().as_ref(), "Database operation failed: Connection timeout");
let err = AppError::Validation {
field: "email".to_string(),
value: "invalid".to_string(),
};
assert_eq!(err.status_code().as_u16(), 422);
assert_eq!(err.message().as_ref(), "Field `email` has invalid value");§Struct Example
use api_error::ApiError;
#[derive(Debug, thiserror::Error, ApiError)]
#[error("Authentication failed: {reason}")]
#[api_error(status_code = 401, message = "Authentication failed")]
struct AuthError {
reason: String,
}
let err = AuthError {
reason: "Invalid token".to_string(),
};
assert_eq!(err.status_code().as_u16(), 401);
assert_eq!(err.message().as_ref(), "Authentication failed");§Message Inheritance
Use message(inherit) to use the Display implementation as the user-facing message:
use api_error::ApiError;
#[derive(Debug, thiserror::Error, ApiError)]
enum MyError {
#[error("User-friendly error message")]
#[api_error(message(inherit), status_code = 400)]
BadRequest,
}
let err = MyError::BadRequest;
assert_eq!(err.message().as_ref(), "User-friendly error message");§Transparent Forwarding
Forward both status code and message from an inner error:
use api_error::ApiError;
#[derive(Debug, thiserror::Error, ApiError)]
#[error("Database error")]
#[api_error(status_code = 503, message = "Service temporarily unavailable")]
struct DatabaseError;
#[derive(Debug, thiserror::Error, ApiError)]
enum AppError {
#[error(transparent)]
#[api_error(transparent)]
Database(DatabaseError),
#[error("Other error")]
#[api_error(status_code = 500, message = "Internal error")]
Other,
}
let err = AppError::Database(DatabaseError);
assert_eq!(err.status_code().as_u16(), 503); // Forwarded from DatabaseError
assert_eq!(err.message().as_ref(), "Service temporarily unavailable");§Axum Integration
With the axum feature enabled, ApiError types automatically implement IntoResponse:
use api_error::ApiError;
use axum::{Router, routing::get};
#[derive(Debug, thiserror::Error, ApiError)]
enum MyApiError {
#[error("Not found")]
#[api_error(status_code = 404, message = "Resource not found")]
NotFound,
}
async fn handler() -> Result<String, MyApiError> {
Err(MyApiError::NotFound)
}
let app: Router = Router::new().route("/", get(handler));
// Returns JSON response:
// Status: 404
// Body: {"message": "Resource not found"}Traits§
- ApiError
- An error that can be returned by a service API.