erract 0.1.2

Structured, context-aware error handling
Documentation
# erract - Structured Error Handling for Rust

[![Crates.io](https://img.shields.io/crates/v/erract.svg)](https://crates.io/crates/erract)
[![Documentation](https://docs.rs/erract/badge.svg)](https://docs.rs/erract)
[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](LICENSE)

**erract** provides production-ready error handling that solves:

- **Actionable errors**: Categorized by what callers can do (retry, fail, etc.)
- **Explicit retry semantics**: No guessing from error messages
- **Rich context**: Automatic location capture with zero overhead
- **Type safety**: Enforce context at module boundaries

## Philosophy

Two audiences, two needs:

- **Machines**: Need flat structures, clear error kinds, predictable codes
- **Humans**: Need rich context, call paths, business-level information

This library combines the best ideas from:

- [Apache OpenDAL's error design]https://github.com/apache/opendal/pull/977
- [The exn crate]https://github.com/fast/exn
- Years of production error handling experience

## Quick Start

```rust
use erract::prelude::*;

fn process_user(id: u32) -> erract::Result<String> {
    // Use static constructors for zero-allocation common errors
    let user = lookup_user(id)
        .or_raise(|| Error::not_found())?
        .ok_or_else(|| {
            Error::permanent(ErrorKind::NotFound, format!("user {} not found", id))
                .with_context("user_id", id.to_string())
                .raise()
        })?;

    Ok(user)
}

fn lookup_user(id: u32) -> erract::Result<Option<String>> {
    if id == 0 {
        // Static message - zero allocation
        return Err(Error::not_found().raise());
    }
    Ok(Some(format!("User{}", id)))
}
```

## Features

- **High-performance Context Arena**: Thread-local storage for dynamic context, making large-scale context addition up to 40% faster than alternatives.
- **Zero runtime overhead**: Uses `#[track_caller]` instead of expensive backtraces
- **Domain-specific errors**: HTTP, Database, and Storage error kinds
- **Error trees**: Support for multiple concurrent failures
- **Type safety**: Compiler enforces context at module boundaries
- **Zero-copy static strings**: Use `Cow<'static, str>` for static messages
- **Optimized serialization**: Fast JSON and machine-readable output

## API Highlights

### Static Constructors (Zero Allocation)

```rust
// Common errors with pre-defined static messages
let err = Error::not_found();           // "not found"
let err = Error::timeout();             // "operation timed out"
let err = Error::permission_denied();   // "permission denied"
let err = Error::validation_failed();   // "validation failed"
let err = Error::unexpected();          // "unexpected error"
```

### Context Methods

```rust
// For string context (zero-copy for static strings)
error.with_context("key", "value")

// For non-string values (uses ToString)
error.with_context_value("count", 42)
```

### Serialization

```rust
let error = Error::not_found()
    .with_context("resource", "user")
    .with_operation("fetch");

// Human-readable
println!("{}", error);  // "not found (operation: fetch) [resource: user]"

// Machine-readable
error.to_machine_string()  // "kind=not_found;status=permanent;message=not found;..."

// JSON (optimized, ~220ns)
error.to_json()  // {"kind":"not_found","status":"permanent",...}
```

## Comparison with anyhow

erract and [anyhow](https://github.com/dtolnay/anyhow) serve different use cases:

| Aspect                | erract                                        | anyhow                              |
| --------------------- | --------------------------------------------- | ----------------------------------- |
| **Design Goal**       | Production services with structured errors    | Quick prototyping, application code |
| **Retry Semantics**   | Explicit (`is_retryable()`, `is_permanent()`) | None (manual parsing)               |
| **Error Kind**        | Structured enum (`ErrorKind`)                 | Dynamic (downcast required)         |
| **Context Model**     | Key-value pairs                               | String messages                     |
| **Machine Output**    | Built-in JSON, machine strings                | Manual formatting                   |
| **Location Tracking** | `#[track_caller]` (zero cost)                 | Backtrace (optional overhead)       |

### Performance Benchmarks

Run `cargo bench` to reproduce. Results on Linux x86_64:

| Benchmark              | erract | anyhow | Winner        |
| ---------------------- | ------ | ------ | ------------- |
| Basic error creation   | 22 ns  | 30 ns  | erract (+27%) |
| Formatted error        | 81 ns  | 106 ns | erract (+24%) |
| Single context         | 24 ns  | 58 ns  | erract (+59%) |
| Triple context         | 110 ns | 115 ns | erract (+4%)  |
| Large context (100)    | 13.5 µs| 22.9 µs| erract (+41%) |
| to_string              | 268 ns | 52 ns  | anyhow        |
| to_json                | 221 ns | N/A    | erract only   |
| `is_retryable()` check | <1 ns  | N/A    | erract only   |

**Key Insights:**

- **erract excels** at error creation, high-pressure context addition (via Arena), and structured queries
- **anyhow excels** at deep error propagation and human-only display formatting
- **Memory**: erract::Error is 112 bytes (structured), anyhow::Error is 8 bytes (pointer)
- **Trade-off**: erract uses more stack space and thread-local memory to achieve superior performance in context-heavy scenarios

### When to Choose

**Choose erract when:**

- Building production services with retry logic
- Need machine-parseable errors for alerting/metrics
- Want explicit error semantics at compile time
- Multiple teams consume your error data

**Choose anyhow when:**

- Building CLI tools or quick prototypes
- Error handling is human-only (logs, not machines)
- Minimal boilerplate is priority
- Don't need structured error classification

See [benches/README.md](benches/README.md) for detailed benchmark methodology.

## License

This project is licensed under either of

- Apache License, Version 2.0, ([LICENSE-APACHE]LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT]LICENSE-MIT or http://opensource.org/licenses/MIT)

at your option.