error-info 0.3.1

Centralized error information ready for internationalization
Documentation
# ErrorInfo

_Centralized error information ready for internationalization._

`error-info` is designed for applications that need to separate **Error Definitions** (catalogs of codes, statuses, and
user-facing messages) from **Runtime Error Handling** (stack traces, internal logs, and dynamic properties).

It is particularly useful for Web APIs where you want to:

1. **Decouple Error Definitions:** Create multiple error enums across different libraries or services
   (e.g., `AuthErrors`, `PaymentErrors`) and manage them centrally.
2. **Auto-generate Contracts:** Generate a "schema" of all possible errors from all your crates for your localization teams.
3. **Optimize Production:** Keep your production binaries lean by only enabling the reflection features during
   build/maintenance tasks.

## 📦 Installation

```toml
[dependencies]
error-info = "0.3"
# Optional: Enable 'summary' ONLY in your xtask/CLI tool, not in your main service
error-info = { version = "0.3", features = ["summary"] }
```

## 🚀 Usage

### 1. Define Error Catalogs

You can define multiple enums across different crates or modules. `ErrorInfo` works seamlessly with this distributed approach.

```rust
// Crate: my-auth-lib
#[derive(Debug, Clone, ErrorInfo)]
pub enum AuthError {
    #[error(status = StatusCode::BAD_REQUEST, message = "Invalid username: {username}")]
    InvalidUsername { username: &'static str },
}

// Crate: my-billing-lib
#[derive(Debug, Clone, ErrorInfo)]
pub enum BillingError {
    #[error(status = StatusCode::PAYMENT_REQUIRED, message = "Quota exceeded")]
    QuotaExceeded,
}
```

### 2. Access Error Metadata

You can retrieve the structured data (codes, fields, status) regardless of the specific enum variant.

```rust
let error = AuthError::InvalidUsername { username: "admin" };

// Technical Code (stable contract for frontend logic)
assert_eq!(error.code(), "INVALID_USERNAME");

// HTTP Status (useful for implementing `IntoResponse`)
assert_eq!(error.status(), StatusCode::BAD_REQUEST);

// I18n Data (frontend uses the 'code' to look up the translation and interpolates the 'fields')
let fields = error.fields();
assert_eq!(fields.get("username"), Some(&"admin".to_string()));

// Primarily serves as the reference text when generating the summary export
assert_eq!(error.raw_message(), "Invalid username: {username}");

// Fallback Message (server-side interpolation to trace, or if the frontend cannot localize the error)
assert_eq!(error.message(), "Invalid username: admin");
```

## 🏗️ The "Rich Error" Pattern

Unlike standard libraries like `thiserror`, `ErrorInfo` is **not** intended to be your top-level `Error` type. It does not implement `std::error::Error` by design.

Instead, it is meant to be the **payload** inside a rich application error wrapper. This allows you to attach context (stack traces, span IDs) without polluting your clean error definitions.

```rust
/// Your application's main error type
#[derive(Clone)]
pub struct AppError {
    /// The specific error contract (e.g., AuthError::InvalidUsername)
    pub info: Arc<dyn ErrorInfo + Send + Sync + 'static>,

    /// Technical details for internal logs (not sent to users)
    pub cause: Option<String>,

    /// Runtime context (e.g., tracing spans, request IDs)
    pub context: serde_json::Value,
}
```

## 🌍 Internationalization (I18n) & CI Integration

The `summary` feature allows you to iterate over _every error defined in your dependency tree_ at runtime.

### Strategy: The _xtask_ Pattern

For the best performance, **do not enable the `summary` feature in your production services**.
It involves linking magic (`linkme`) that is unnecessary at runtime.

Instead, enable it only in a separate `cargo xtask` or maintenance binary. This tool imports all your library crates,
generates the JSON contract, and saves it for your frontend team.

**Example xtask:**

```rust
fn generate_frontend_contract() -> Result<(), Box<dyn std::error::Error>> {
    // This collects AuthError, BillingError, and any other ErrorInfo enum linked in this binary.
    let all_errors = error_info::summary();

    // Write to a file that your frontend/i18n team can consume
    std::fs::write(
        "../frontend/src/locales/errors.json",
        serde_json::to_string_pretty(&all_errors)?
    )?;
    Ok(())
}
```

**Output Example (`errors.json`):**

```json
[
  {
    "code": "INVALID_USERNAME",
    "status": 400,
    "raw_message": "Invalid username: {username}",
  },
  {
    "code": "QUOTA_EXCEEDED",
    "status": 402,
    "raw_message": "Quota exceeded"
  }
]
```

You can customize the output format to match your specific internationalization requirements.

## License

This project is licensed under the [Apache-2.0](LICENSE) license.