π¨ Centralized Error Handling & Exception Filters
Robust applications require predictable, strongly typed error handling. In Ferrox, ferrox-errors provides a centralized AppError enum that implements Axum's IntoResponse trait.
This guarantees that unhandled exceptions, database errors, validation failures, and authorization checks automatically map to structured JSON responses with correct HTTP status codes.
1. The AppError Enumβ
The core error type is AppError:
use thiserror::Error;
#[derive(Debug, Error)]
pub enum AppError {
#[error("Not Found: {0}")]
NotFound(String),
#[error("Validation Error: {0}")]
ValidationError(String),
#[error("Unauthorized: {0}")]
Unauthorized(String),
#[error("Internal Server Error")]
InternalServerError(#[source] Box<dyn std::error::Error + Send + Sync>),
#[error("Database Error: {0}")]
DatabaseError(String),
}
2. Standardized JSON Error Payload Formatβ
When an AppError is returned from a controller handler, Ferrox converts it into a standardized JSON response:
{
"status": 404,
"message": "Not Found: User with ID 42 does not exist"
}
HTTP Status Code Mappingsβ
AppError Variant | HTTP Status Code | Description |
|---|---|---|
AppError::NotFound(msg) | 404 Not Found | Requested entity or resource missing |
AppError::ValidationError(msg) | 400 Bad Request | Malformed JSON or DTO validation failure |
AppError::Unauthorized(msg) | 401 Unauthorized | Missing or invalid PASETO JWT token |
AppError::DatabaseError(msg) | 500 Internal Server Error | Database query or connection pool error |
AppError::InternalServerError(err) | 500 Internal Server Error | Unexpected server panic or system error |
3. Returning Errors from Controllersβ
Controllers return Result<T, AppError> and use Rust's ? operator for clean error propagation:
use axum::{extract::{Path, State}, Json};
use ferrox_errors::AppError;
pub async fn get_user_by_id(
Path(id): Path<u64>,
State(service): State<UserService>,
) -> Result<Json<UserDto>, AppError> {
let user = service.find_by_id(id).await?
.ok_or_else(|| AppError::NotFound(format!("User {}", id)))?;
Ok(Json(user.into()))
}
4. Security & Error Sanitizationβ
[!IMPORTANT] To prevent Information Disclosure vulnerabilities,
AppError::InternalServerErrorandAppError::DatabaseErrorprint full diagnostic stack traces to server console logs, but only return generic"Internal Server Error"messages to HTTP clients.
5. β Best Practicesβ
- Never
unwrap()inside controllers or services: Always propagate errors with?or map custom errors intoAppError. - Use domain-specific error messages: Provide human-readable details for
AppError::ValidationErrorso frontend clients can render form field hints.
6. β Anti-Patternsβ
- β Exposing database connection strings or raw SQL tracebacks: Raw database errors contain schema details that attackers exploit. Use
AppError::DatabaseErrorto sanitize responses.