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

[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.

// 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.

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.

/// 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:

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):

[
  {
    "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.