Error Handling Strategy
Relevant source files
The following files were used as context for generating this wiki page:
Horizon utilizes a unified error handling infrastructure centered around the error crate. The strategy combines the thiserror and anyhow patterns to provide structured, strongly-typed errors for internal logic while ensuring consistent JSON responses for external API consumers.
The system is designed to provide high-level visibility through structured logging via the tracing crate and automated error propagation using Rust's ? operator. It encompasses custom error variants, HTTP middleware for response conversion, and a standardized API response wrapper that includes metadata and pagination.
Sources: AGENTS.md:88-100, crates/error/src/lib.rs:1-15, IMPLEMENTATION_PLAN.md:204-209
Core Error Architecture
The error handling architecture is built on a hierarchy of traits and enums that categorize failures into client-side, server-side, and domain-specific categories.
AppError Enum
The AppError enum is the primary error type used across the workspace. It derives thiserror::Error for automatic string representation and provides convenience methods for common error scenarios.
| Variant | HTTP Status | Description |
|---|---|---|
NotFound | 404 | Resource does not exist. |
BadRequest | 400 | Invalid input or malformed request. |
Unauthorized | 401 | Authentication failed or missing. |
Forbidden | 403 | Authenticated user lacks permissions. |
Conflict | 409 | Resource state conflict (e.g., duplicate). |
Validation | 422 | Data failed validation rules. |
RateLimit | 429 | Too many requests; includes retry_after. |
Internal | 500 | Generic server-side failure. |
Database | 500 | Database-specific failures (Sea-ORM/SQLx). |
Io | 500 | Input/Output operation failures. |
Config | 500 | Configuration loading or parsing errors. |
Sources: crates/error/src/lib.rs:21-72, crates/error/src/lib.rs:188-219
Error Codes and Categories
For more granular control, the codes module defines the ErrorCode trait and specific structs for each error type. This allows the system to attach metadata such as whether an error should be logged or if details should be exposed to the end-user.
API Response Standardization
All API responses, whether successful or erroneous, are wrapped in the ApiResponse enum. This ensures that the frontend receives a predictable JSON structure.
Unified Response Format
The ApiResponse enum uses a tagged representation (status) and camelCase naming for compatibility with web frontends.
- Success: Returns
dataand optionalmeta(request ID, timestamp). - Error: Returns a standardized
code,message, and optionaldetails(e.g., specific validation failures).
Sources: crates/error/src/response.rs:107-142
Pagination Metadata
For list endpoints, the PaginationMeta struct provides consistent navigation data.
Sources: crates/error/src/response.rs:37-75
| Field | Type | Description |
|---|---|---|
page | u64 | Current 1-indexed page. |
per_page | u64 | Items per page. |
total_items | u64 | Total records found. |
total_pages | u64 | Total pages available. |
has_next | bool | True if a subsequent page exists. |
Middleware and Propagation
The strategy includes Axum middleware to intercept AppError instances and convert them into HTTP responses automatically.
ErrorHandler Middleware
The ErrorHandler determines the amount of information leaked to the client. If include_details is false, sensitive 500-level errors are masked with generic messages like "Internal server error," while 400-level errors remain descriptive.
Sources: crates/error/src/middleware.rs:23-74
Result Conversions
Horizon implements From traits to seamlessly convert external library errors into the internal AppError type:
- Anyhow: Converts
anyhow::ErrortoAppError::Internal. - IO: Converts
std::io::ErrortoAppError::Io. - Sea-ORM: Converts
sea_orm::DbErrtoAppError::Database.
Sources: crates/error/src/lib.rs:309-335
Implementation Example
Developers use the Result<T> alias (defaulting to AppError) and the Context trait to enrich errors as they propagate.
// crates/error/src/lib.rs:260-264
pub fn context(self, context: impl ToString) -> Self {
let context_msg = context.to_string();
match self {
AppError::NotFound { message } => {
Self::NotFound { message: format!("{}: {}", context_msg, message) }
},
// ... other variants
}
}
Sources: crates/error/src/lib.rs:18-19, crates/error/src/lib.rs:260-307
Summary of Components
| Component | File Path | Purpose |
|---|---|---|
AppError | crates/error/src/lib.rs | Main error enum defining project-wide error types. |
ErrorCode | crates/error/src/codes.rs | Trait for defining HTTP status and logging behavior per error. |
ErrorHandler | crates/error/src/middleware.rs | Axum middleware for converting errors to JSON responses. |
ApiResponse | crates/error/src/response.rs | Generic wrapper for all API success and error payloads. |
ResultExt | crates/error/src/traits.rs | Extension traits for adding context to Result types. |
The Horizon error handling strategy ensures that every failure is categorized, optionally logged with context, and returned to the requester in a standardized format that preserves system security by masking internal details when necessary.
Sources: crates/error/src/lib.rs, crates/error/src/middleware.rs, IMPLEMENTATION_PLAN.md:204-210