https://github.com/jamesgober/error-forge
Error Forge is a flexible, high-performance Rust framework for rich, structured errors. Define typed errors with macros/derives, add contextual metadata & severities, wire sync/async hooks for logging/telemetry, and use recovery helpers (retry/fallback/backoff) that keep your code clean and resilient.
https://github.com/jamesgober/error-forge
asynchronous cross-platform custom-errors derive error error-handling error-recovery framework hppp macros rhpl rust thread-safe
Last synced: about 4 hours ago
JSON representation
Error Forge is a flexible, high-performance Rust framework for rich, structured errors. Define typed errors with macros/derives, add contextual metadata & severities, wire sync/async hooks for logging/telemetry, and use recovery helpers (retry/fallback/backoff) that keep your code clean and resilient.
- Host: GitHub
- URL: https://github.com/jamesgober/error-forge
- Owner: jamesgober
- License: apache-2.0
- Created: 2025-07-29T13:02:23.000Z (8 months ago)
- Default Branch: main
- Last Pushed: 2026-03-25T00:03:40.000Z (9 days ago)
- Last Synced: 2026-03-26T05:23:00.243Z (7 days ago)
- Topics: asynchronous, cross-platform, custom-errors, derive, error, error-handling, error-recovery, framework, hppp, macros, rhpl, rust, thread-safe
- Language: Rust
- Homepage:
- Size: 174 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
Error Forge
Pragmatic error modeling, contextual diagnostics, and resilience helpers for Rust.
Error Forge is a Rust error-handling crate built around a few simple ideas:
- Errors should carry stable metadata such as kind, retryability, status code, and exit code.
- Application code should be able to add context without destroying the original cause chain.
- Operational tooling should have clear hooks for logging, formatting, codes, and recovery policies.
- Feature-gated integrations should stay optional so the core remains lightweight.
It ships with a built-in `AppError`, a declarative `define_errors!` macro, an optional `#[derive(ModError)]` proc macro, error collectors, registry support, console formatting, synchronous retry and circuit-breaker primitives, and async-specific traits behind feature flags.
## Installation
```toml
[dependencies]
error-forge = "0.9.7"
```
Common optional features:
- `derive`: enables `#[derive(ModError)]`
- `async`: enables `AsyncForgeError`
- `serde`: enables serialization support where compatible
- `log`: enables the `log` adapter
- `tracing`: enables the `tracing` adapter
## Quick Start
### Built-in `AppError`
```rust
use error_forge::{AppError, ForgeError};
fn load_config() -> Result<(), AppError> {
Err(AppError::config("Missing DATABASE_URL").with_fatal(true))
}
fn main() {
let error = load_config().unwrap_err();
assert_eq!(error.kind(), "Config");
assert!(error.is_fatal());
println!("{}", error);
}
```
### Defining Custom Errors With `define_errors!`
`define_errors!` is the lowest-friction way to create a custom error enum with generated constructors and `ForgeError` metadata.
```rust
use error_forge::{define_errors, ForgeError};
use std::io;
define_errors! {
pub enum ServiceError {
#[error(display = "Configuration is invalid: {message}", message)]
#[kind(Config, status = 500)]
Config { message: String },
#[error(display = "Request to {endpoint} failed", endpoint)]
#[kind(Network, retryable = true, status = 503)]
Network { endpoint: String, source: Option> },
#[error(display = "Could not read {path}", path)]
#[kind(Filesystem, status = 500)]
Filesystem { path: String, source: io::Error },
}
}
fn main() {
let error = ServiceError::config("Missing API token".to_string());
assert_eq!(error.kind(), "Config");
assert_eq!(error.status_code(), 500);
}
```
Notes:
- `#[kind(...)]` is required for each variant.
- Constructors are generated from the lowercase variant name, such as `ServiceError::config(...)`.
- A field named `source` participates in `std::error::Error::source()` chaining.
- For custom `source` field types, implement `error_forge::macros::ErrorSource` in your crate.
- With the `serde` feature enabled, source fields must themselves be serializable if you want to derive serialization through the macro-generated enum.
### Adding Context Without Losing the Original Error
```rust
use error_forge::{AppError, ResultExt};
fn connect() -> Result<(), AppError> {
Err(AppError::network("db.internal", None))
}
fn main() {
let error = connect()
.with_context(|| "opening primary database connection".to_string())
.unwrap_err();
println!("{}", error);
}
```
### Collecting Multiple Errors
```rust
use error_forge::{AppError, ErrorCollector};
fn main() {
let mut collector = ErrorCollector::new();
collector.push(AppError::config("missing host"));
collector.push(AppError::other("invalid timeout"));
assert_eq!(collector.len(), 2);
println!("{}", collector.summary());
}
```
## Derive Macro
Enable the `derive` feature to use `#[derive(ModError)]`.
```rust
use error_forge::{ForgeError, ModError};
#[derive(Debug, ModError)]
#[error_prefix("Database")]
enum DbError {
#[error_display("Connection failed: {0}")]
#[error_retryable]
#[error_http_status(503)]
ConnectionFailed(String),
#[error_display("Query failed for {query}")]
QueryFailed { query: String },
#[error_display("Permission denied")]
#[error_fatal]
PermissionDenied,
}
fn main() {
let error = DbError::ConnectionFailed("primary".to_string());
assert!(error.is_retryable());
assert_eq!(error.status_code(), 503);
}
```
Supported derive attributes:
- `error_prefix`
- `error_display`
- `error_kind`
- `error_caption`
- `error_retryable`
- `error_http_status`
- `error_exit_code`
- `error_fatal`
Both list-style and name-value forms are supported for `error_prefix`.
## Recovery and Resilience
The recovery module is intentionally synchronous today. It is designed for blocking code, worker threads, and service wrappers where a small sleep is acceptable.
```rust
use error_forge::recovery::{CircuitBreaker, RetryPolicy};
fn main() {
let breaker = CircuitBreaker::new("inventory-service");
let policy = RetryPolicy::new_fixed(25).with_max_retries(3);
let value: Result = breaker.execute(|| {
policy.retry(|| Ok(42))
});
assert_eq!(value.unwrap(), 42);
}
```
If you need async retries, keep Error Forge for modeling and classification, then wrap retry behavior with your async runtime of choice.
## Hooks, Logging, and Formatting
### Error Hooks
```rust
use error_forge::{
AppError,
macros::{try_register_error_hook, ErrorLevel},
};
fn main() {
let _ = try_register_error_hook(|ctx| {
if matches!(ctx.level, ErrorLevel::Critical | ErrorLevel::Error) {
eprintln!("{} [{}]", ctx.caption, ctx.kind);
}
});
let _ = AppError::config("Missing environment variable");
}
```
### Logging Adapters
- `logging::register_logger(...)` installs a custom logger once.
- `logging::log_impl::init()` is available with the `log` feature.
- `logging::tracing_impl::init()` is available with the `tracing` feature.
### Console Output
```rust
use error_forge::{console_theme::print_error, AppError};
fn main() {
let error = AppError::filesystem("config.toml", None);
print_error(&error);
}
```
## Error Codes
Attach stable codes to errors when you want machine-readable identifiers or documentation links.
```rust
use error_forge::{register_error_code, AppError, ForgeError};
fn main() {
let _ = register_error_code(
"AUTH-001",
"Authentication failed",
Some("https://example.com/errors/AUTH-001"),
false,
);
let error = AppError::config("Invalid credentials")
.with_code("AUTH-001")
.with_status(401);
assert_eq!(error.status_code(), 401);
println!("{}", error.dev_message());
}
```
## Quality Bar
The crate is validated with:
- `cargo test --all-features`
- `cargo clippy --all-targets --all-features -- -D warnings`
- targeted examples and feature-gated regression coverage
## Documentation
- API reference: `docs/API.md`
- Examples: `examples/`
- Crate documentation: https://docs.rs/error-forge
## License
Licensed under Apache-2.0.