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