{"id":30188610,"url":"https://github.com/jamesgober/error-forge","last_synced_at":"2026-04-02T12:30:35.070Z","repository":{"id":307104973,"uuid":"1028385919","full_name":"jamesgober/error-forge","owner":"jamesgober","description":"Error Forge is a flexible, high-performance Rust framework for rich, structured errors. Define typed errors with macros/derives, add contextual metadata \u0026 severities, wire sync/async hooks for logging/telemetry, and use recovery helpers (retry/fallback/backoff) that keep your code clean and resilient.","archived":false,"fork":false,"pushed_at":"2026-03-25T00:03:40.000Z","size":178,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-26T05:23:00.243Z","etag":null,"topics":["asynchronous","cross-platform","custom-errors","derive","error","error-handling","error-recovery","framework","hppp","macros","rhpl","rust","thread-safe"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamesgober.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-29T13:02:23.000Z","updated_at":"2026-03-24T23:57:45.000Z","dependencies_parsed_at":"2025-08-01T06:50:49.095Z","dependency_job_id":null,"html_url":"https://github.com/jamesgober/error-forge","commit_stats":null,"previous_names":["jamesgober/error-forge"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/jamesgober/error-forge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Ferror-forge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Ferror-forge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Ferror-forge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Ferror-forge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamesgober","download_url":"https://codeload.github.com/jamesgober/error-forge/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Ferror-forge/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31306052,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T09:48:21.550Z","status":"ssl_error","status_checked_at":"2026-04-02T09:48:19.196Z","response_time":89,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asynchronous","cross-platform","custom-errors","derive","error","error-handling","error-recovery","framework","hppp","macros","rhpl","rust","thread-safe"],"created_at":"2025-08-12T17:08:31.349Z","updated_at":"2026-04-02T12:30:35.056Z","avatar_url":"https://github.com/jamesgober.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg width=\"120\" height=\"auto\" src=\"https://raw.githubusercontent.com/jamesgober/jamesgober/main/media/icons/hexagon-3.svg\" alt=\"Error Forge logo\"\u003e\n  \u003ch1\u003e\u003cstrong\u003eError Forge\u003c/strong\u003e\u003c/h1\u003e\n  \u003cp\u003ePragmatic error modeling, contextual diagnostics, and resilience helpers for Rust.\u003c/p\u003e\n\u003c/div\u003e\n\nError Forge is a Rust error-handling crate built around a few simple ideas:\n\n- Errors should carry stable metadata such as kind, retryability, status code, and exit code.\n- Application code should be able to add context without destroying the original cause chain.\n- Operational tooling should have clear hooks for logging, formatting, codes, and recovery policies.\n- Feature-gated integrations should stay optional so the core remains lightweight.\n\nIt 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.\n\n## Installation\n\n```toml\n[dependencies]\nerror-forge = \"0.9.7\"\n```\n\nCommon optional features:\n\n- `derive`: enables `#[derive(ModError)]`\n- `async`: enables `AsyncForgeError`\n- `serde`: enables serialization support where compatible\n- `log`: enables the `log` adapter\n- `tracing`: enables the `tracing` adapter\n\n## Quick Start\n\n### Built-in `AppError`\n\n```rust\nuse error_forge::{AppError, ForgeError};\n\nfn load_config() -\u003e Result\u003c(), AppError\u003e {\n    Err(AppError::config(\"Missing DATABASE_URL\").with_fatal(true))\n}\n\nfn main() {\n    let error = load_config().unwrap_err();\n\n    assert_eq!(error.kind(), \"Config\");\n    assert!(error.is_fatal());\n    println!(\"{}\", error);\n}\n```\n\n### Defining Custom Errors With `define_errors!`\n\n`define_errors!` is the lowest-friction way to create a custom error enum with generated constructors and `ForgeError` metadata.\n\n```rust\nuse error_forge::{define_errors, ForgeError};\nuse std::io;\n\ndefine_errors! {\n    pub enum ServiceError {\n        #[error(display = \"Configuration is invalid: {message}\", message)]\n        #[kind(Config, status = 500)]\n        Config { message: String },\n\n        #[error(display = \"Request to {endpoint} failed\", endpoint)]\n        #[kind(Network, retryable = true, status = 503)]\n        Network { endpoint: String, source: Option\u003cBox\u003cdyn std::error::Error + Send + Sync\u003e\u003e },\n\n        #[error(display = \"Could not read {path}\", path)]\n        #[kind(Filesystem, status = 500)]\n        Filesystem { path: String, source: io::Error },\n    }\n}\n\nfn main() {\n    let error = ServiceError::config(\"Missing API token\".to_string());\n    assert_eq!(error.kind(), \"Config\");\n    assert_eq!(error.status_code(), 500);\n}\n```\n\nNotes:\n\n- `#[kind(...)]` is required for each variant.\n- Constructors are generated from the lowercase variant name, such as `ServiceError::config(...)`.\n- A field named `source` participates in `std::error::Error::source()` chaining.\n- For custom `source` field types, implement `error_forge::macros::ErrorSource` in your crate.\n- With the `serde` feature enabled, source fields must themselves be serializable if you want to derive serialization through the macro-generated enum.\n\n### Adding Context Without Losing the Original Error\n\n```rust\nuse error_forge::{AppError, ResultExt};\n\nfn connect() -\u003e Result\u003c(), AppError\u003e {\n    Err(AppError::network(\"db.internal\", None))\n}\n\nfn main() {\n    let error = connect()\n        .with_context(|| \"opening primary database connection\".to_string())\n        .unwrap_err();\n\n    println!(\"{}\", error);\n}\n```\n\n### Collecting Multiple Errors\n\n```rust\nuse error_forge::{AppError, ErrorCollector};\n\nfn main() {\n    let mut collector = ErrorCollector::new();\n    collector.push(AppError::config(\"missing host\"));\n    collector.push(AppError::other(\"invalid timeout\"));\n\n    assert_eq!(collector.len(), 2);\n    println!(\"{}\", collector.summary());\n}\n```\n\n## Derive Macro\n\nEnable the `derive` feature to use `#[derive(ModError)]`.\n\n```rust\nuse error_forge::{ForgeError, ModError};\n\n#[derive(Debug, ModError)]\n#[error_prefix(\"Database\")]\nenum DbError {\n    #[error_display(\"Connection failed: {0}\")]\n    #[error_retryable]\n    #[error_http_status(503)]\n    ConnectionFailed(String),\n\n    #[error_display(\"Query failed for {query}\")]\n    QueryFailed { query: String },\n\n    #[error_display(\"Permission denied\")]\n    #[error_fatal]\n    PermissionDenied,\n}\n\nfn main() {\n    let error = DbError::ConnectionFailed(\"primary\".to_string());\n    assert!(error.is_retryable());\n    assert_eq!(error.status_code(), 503);\n}\n```\n\nSupported derive attributes:\n\n- `error_prefix`\n- `error_display`\n- `error_kind`\n- `error_caption`\n- `error_retryable`\n- `error_http_status`\n- `error_exit_code`\n- `error_fatal`\n\nBoth list-style and name-value forms are supported for `error_prefix`.\n\n## Recovery and Resilience\n\nThe recovery module is intentionally synchronous today. It is designed for blocking code, worker threads, and service wrappers where a small sleep is acceptable.\n\n```rust\nuse error_forge::recovery::{CircuitBreaker, RetryPolicy};\n\nfn main() {\n    let breaker = CircuitBreaker::new(\"inventory-service\");\n    let policy = RetryPolicy::new_fixed(25).with_max_retries(3);\n\n    let value: Result\u003cu32, std::io::Error\u003e = breaker.execute(|| {\n        policy.retry(|| Ok(42))\n    });\n\n    assert_eq!(value.unwrap(), 42);\n}\n```\n\nIf you need async retries, keep Error Forge for modeling and classification, then wrap retry behavior with your async runtime of choice.\n\n## Hooks, Logging, and Formatting\n\n### Error Hooks\n\n```rust\nuse error_forge::{\n    AppError,\n    macros::{try_register_error_hook, ErrorLevel},\n};\n\nfn main() {\n    let _ = try_register_error_hook(|ctx| {\n        if matches!(ctx.level, ErrorLevel::Critical | ErrorLevel::Error) {\n            eprintln!(\"{} [{}]\", ctx.caption, ctx.kind);\n        }\n    });\n\n    let _ = AppError::config(\"Missing environment variable\");\n}\n```\n\n### Logging Adapters\n\n- `logging::register_logger(...)` installs a custom logger once.\n- `logging::log_impl::init()` is available with the `log` feature.\n- `logging::tracing_impl::init()` is available with the `tracing` feature.\n\n### Console Output\n\n```rust\nuse error_forge::{console_theme::print_error, AppError};\n\nfn main() {\n    let error = AppError::filesystem(\"config.toml\", None);\n    print_error(\u0026error);\n}\n```\n\n## Error Codes\n\nAttach stable codes to errors when you want machine-readable identifiers or documentation links.\n\n```rust\nuse error_forge::{register_error_code, AppError, ForgeError};\n\nfn main() {\n    let _ = register_error_code(\n        \"AUTH-001\",\n        \"Authentication failed\",\n        Some(\"https://example.com/errors/AUTH-001\"),\n        false,\n    );\n\n    let error = AppError::config(\"Invalid credentials\")\n        .with_code(\"AUTH-001\")\n        .with_status(401);\n\n    assert_eq!(error.status_code(), 401);\n    println!(\"{}\", error.dev_message());\n}\n```\n\n## Quality Bar\n\nThe crate is validated with:\n\n- `cargo test --all-features`\n- `cargo clippy --all-targets --all-features -- -D warnings`\n- targeted examples and feature-gated regression coverage\n\n## Documentation\n\n- API reference: `docs/API.md`\n- Examples: `examples/`\n- Crate documentation: https://docs.rs/error-forge\n\n## License\n\nLicensed under Apache-2.0.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Ferror-forge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesgober%2Ferror-forge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Ferror-forge/lists"}