Error Forge is a comprehensive, zero-dependency error management framework for Rust applications. It simplifies error handling through expressive macros, automatic trait implementations, and extensible error hooks for seamless integration with external logging systems.
Features
- Rich Error Types: Define expressive error types with minimal boilerplate
- ForgeError Trait: Unified interface for all error types with contextual metadata
- Declarative Macros: Generate complete error enums with the
define_errors!
macro
- Error Composition: Combine errors from multiple modules with the
group!
macro
- Derive Macros: Quickly implement errors with
#[derive(ModError)]
- Console Formatting: ANSI color formatting for terminal output with
ConsoleTheme
- Error Hooks: Thread-safe error hook system using
OnceLock
- Structured Context: Error wrapping with context via
context()
and with_context()
methods
- Error Registry: Support for error codes and documentation URLs
- Non-fatal Error Collection: Collect and process multiple errors with
ErrorCollector
- Logging Integration: Optional integration with
log
and tracing
crates
- Cross-Platform: Full support for Linux, macOS, and Windows
- Zero External Dependencies: Core functionality has no third-party dependencies
Installation
Add the following to your Cargo.toml
file:
[dependencies]
error-forge = "0.9.0"
Usage
Basic Error Definition
use error_forge::define_errors;
define_errors! {
pub enum DatabaseError {
#[error(display = "Database connection failed: {}", message)]
ConnectionFailed { message: String },
#[error(display = "Query execution failed: {}", message)]
QueryFailed { message: String, query: String },
#[error(display = "Record not found with ID: {}", id)]
RecordNotFound { id: String },
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let error = DatabaseError::connection_failed("Timeout after 30 seconds");
println!("Error kind: {}", error.kind());
println!("Caption: {}", error.caption());
println!("Status code: {}", error.status_code());
Err(Box::new(error))
}
Error Composition with the group!
Macro
use error_forge::{define_errors, group};
define_errors! {
pub enum ApiError {
#[error(display = "Invalid API key")]
InvalidApiKey,
#[error(display = "Rate limit exceeded")]
RateLimitExceeded,
}
}
define_errors! {
pub enum ValidationError {
#[error(display = "Required field {} is missing", field)]
MissingField { field: String },
#[error(display = "Field {} has invalid format", field)]
InvalidFormat { field: String },
}
}
group! {
pub enum AppError {
Api(ApiError),
Validation(ValidationError),
}
}
fn validate_request() -> Result<(), AppError> {
let error = ValidationError::missing_field("username");
Err(error.into())
}
Using Derive Macro
use error_forge::ModError;
#[derive(Debug, ModError)]
#[module_error(kind = "AuthError")]
pub enum AuthError {
#[error(display = "Invalid credentials")]
InvalidCredentials,
#[error(display = "Account locked: {}", reason)]
AccountLocked { reason: String },
#[error(display = "Session expired")]
#[http_status(401)]
SessionExpired,
}
fn login() -> Result<(), AuthError> {
Err(AuthError::InvalidCredentials)
}
Error Hooks for Logging Integration
use error_forge::{AppError, macros::{register_error_hook, ErrorLevel, ErrorContext}};
fn main() {
register_error_hook(|ctx| {
match ctx.level {
ErrorLevel::Info => println!("INFO: {} [{}]", ctx.caption, ctx.kind),
ErrorLevel::Warning => println!("WARN: {} [{}]", ctx.caption, ctx.kind),
ErrorLevel::Error => println!("ERROR: {} [{}]", ctx.caption, ctx.kind),
ErrorLevel::Critical => {
println!("CRITICAL: {} [{}]", ctx.caption, ctx.kind);
if ctx.is_fatal {
send_notification("Critical error occurred", ctx.caption);
}
}
}
});
let _error = AppError::config("Configuration file not found");
}
fn send_notification(level: &str, message: &str) {
println!("Notification sent: {} - {}", level, message);
}
Console Formatting
use error_forge::{AppError, ConsoleTheme};
fn main() {
let error = AppError::config("Database configuration missing");
let theme = ConsoleTheme::new();
println!("{}", theme.format_error(&error));
theme.install_panic_hook();
panic!("Something went wrong!");
}
Structured Context Support
use error_forge::{define_errors, context::ContextError};
use std::fs::File;
define_errors! {
pub enum FileError {
#[error(display = "Failed to open file")]
OpenFailed,
#[error(display = "Failed to read file")]
ReadFailed,
}
}
fn read_config_file(path: &str) -> Result<String, ContextError<FileError>> {
let mut file = File::open(path)
.map_err(|_| FileError::OpenFailed)
.with_context(format!("Opening config file: {}", path))?;
let mut contents = String::new();
file.read_to_string(&mut contents)
.map_err(|_| FileError::ReadFailed)
.context("Reading configuration data")?;
Ok(contents)
}
fn main() {
match read_config_file("/etc/app/config.json") {
Ok(config) => println!("Config loaded: {} bytes", config.len()),
Err(e) => {
println!("Error: {}", e);
println!("Original error: {}", e.source());
}
}
}
Error Collection
use error_forge::{define_errors, collector::ErrorCollector};
define_errors! {
pub enum ValidationError {
#[error(display = "Field '{}' is required", field)]
Required { field: String },
#[error(display = "Field '{}' must be a valid email", field)]
InvalidEmail { field: String },
}
}
fn validate_form(data: &FormData) -> Result<(), ValidationError> {
let mut collector = ErrorCollector::new();
if data.name.is_empty() {
collector.push(ValidationError::required("name"));
}
if data.email.is_empty() {
collector.push(ValidationError::required("email"));
} else if !is_valid_email(&data.email) {
collector.push(ValidationError::invalid_email("email"));
}
collector.into_result()
}
Advanced Usage
For more detailed documentation and advanced usage examples, refer to the API Documentation.