# standard-error
`standard-error` is a Rust crate designed to simplify the handling of error messages for various locales. It reads error messages from YAML and other sources, providing a structured and efficient way to manage errors in your applications.
## Features
- **Locale-Specific Error Messages**: Automatically fetches error messages for the specified locale.
- **Flexible Data Sources**: Reads error messages from YAML files and other formats.
- **Easy Integration**: Designed to work seamlessly with Rust web frameworks like `Axum`.
## Usage
Below is an example of how to use `standard-error` in a web application using the `Axum` framework:
```rust
use axum::{
extract::Query,
Json,
http::StatusCode,
};
use serde_json::{json, Value};
use std::collections::HashMap;
use standard_error::StandardError;
pub async fn my_handler(Query(params): Query<HashMap<String, String>>) -> Result<Json<Value>, StandardError> {
let group = match params.get("name") {
Some(g) => g,
None => {
return Err(StandardError::new("ER-0037").code(StatusCode::BAD_REQUEST));
}
};
Ok(Json(json!({"message": "success"})))
}
```
## Explanation
- **Error Handling**: In the example, StandardError is used to handle cases where a required query parameter is missing. If the group parameter is not provided, the function returns a StandardError with the appropriate error code and HTTP status.
- **Error Code**: The error code "ER-0037" is used to look up the relevant error message based on the locale.
## Scenarios
### Basic Error Handling with Default Status Code
If a parsing error occurs (e.g., trying to convert a string to an integer), a StandardError can be returned with a default status code (`500 INTERNAL_SERVER_ERROR`):
```rust
async fn parse_int(a: &str) -> Result<i32, StandardError> {
a.parse().map_err(|_| StandardError::new("ER-0004"))
}
// Example usage
let res = parse_int("abc").await;
// This will return an error with code "ER-0004" and status code 500.
```
### Setting a Custom Status Code
You can customize the status code to something other than the default. For example, returning `400 BAD_REQUEST`:
```rust
async fn parse_int_custom(a: &str) -> Result<i32, StandardError> {
a.parse().map_err(|_| StandardError::new("ER-0004").code(StatusCode::BAD_REQUEST))
}
// Example usage
let res = parse_int_custom("abc").await;
// This will return an error with code "ER-0004" and status code 400.
```
### Interpolating Error Details
`StandardError` supports error message interpolation. For example, you can include the specific error details when returning the error:
```rust
async fn parse_with_error_interpolation(a: &str) -> Result<i32, StandardError> {
a.parse().map_err(|e| StandardError::new("ER-0005").interpolate_err(e.to_string()))
}
// Example usage
let res = parse_with_error_interpolation("abc").await;
// This will return an error with code "ER-0005" and message: "Should be an integer: invalid digit found in string".
```
### Interpolating Values
You can interpolate additional values into the error message, such as user-specific data:
```rust
async fn parse_with_value_interpolation(a: &str) -> Result<i32, StandardError> {
a.parse().map_err(|_| {
let mut values = HashMap::new();
values.insert("fname".to_string(), "ashu".to_string());
values.insert("lname".to_string(), "pednekar".to_string());
StandardError::new("ER-0006").interpolate_values(values)
})
}
// Example usage
let res = parse_with_value_interpolation("abc").await;
### Chaining Status Code and Interpolations
You can chain multiple methods on `StandardError`, like setting a custom status code and interpolating both error details and values:
```rust
async fn parse_with_chained_errors(a: &str) -> Result<i32, StandardError> {
a.parse().map_err(|e| {
let mut values = HashMap::new();
values.insert("fname".to_string(), "ashu".to_string());
values.insert("lname".to_string(), "pednekar".to_string());
StandardError::new("ER-0007")
.code(StatusCode::IM_A_TEAPOT)
.interpolate_values(values)
.interpolate_err(e.to_string())
})
}
// Example usage
let res = parse_with_chained_errors("abc").await;
// This will return an error with code "ER-0007", status code 418, and message:
### Rendering askama templates
Make sure you have the `askama` feature enabled, then you can use the `HtmlRes` trait by calling `.template` and pass in the rendered template, here's an example auth middleware use case
> note: this feature is only available in version `0.1.8` onwards
```rust
use askama::Template;
use axum::{
extract::{Request, State},
http::{HeaderMap, StatusCode, header::COOKIE},
middleware::Next,
response::Response,
};
use axum_extra::extract::CookieJar;
use sqlx::query;
use standard_error::{HtmlRes, StandardError, Status};
use crate::prelude::Result;
#[derive(Template)]
#[template(path = "verify.html")]
pub struct Verify {}
pub async fn authenticate(
request: Request,
next: Next,
) -> Result<Response> {
match CookieJar::from_headers(&headers)
.get("_Host_lwsuser")
.filter(|c| !c.value().is_empty())
{
Some(token) => {
// verify and proceed
}
None => {
return Err(StandardError::new("ERR-AUTH-001")
.code(StatusCode::UNAUTHORIZED)
.template(Verify {}.render()?));
}
};
Ok(next.run(request).await)
}
```
## Installation
Add standard-error to your Cargo.toml:
```toml
[dependencies]
standard-error = "0.1"
```
or with cargo
```bash
cargo add standard-error
```
> Note: Add features `diesel` or `git` to auto-magically handle errors raised by these diesel and git2 crates respectively
## Configuration
To configure standard-error, you can provide YAML files containing error messages for different locales. The crate will automatically load the correct message based on the locale specified in your application.
Example YAML structure:
```yaml
errors:
- code: ER-0004
detail_en_US: "Should be an integer"
detail_hi_IN: "एक पूर्णांक होना चाहिए"
- code: ER-0005
detail_en_US: "Should be an integer: [err]"
detail_hi_IN: "एक पूर्णांक होना चाहिए: [err]"
- code: ER-0006
detail_en_US: "Should be an integer - fname: [fname] | lname: [lname]"
detail_hi_IN: "एक पूर्णांक होना चाहिए - fname: [fname] | lname: [lname]"
- code: ER-0007
detail_en_US: "Should be an integer - fname: [fname] | lname: [lname] - [err]"
detail_hi_IN: "एक पूर्णांक होना चाहिए - fname: [fname] | lname: [lname] - [err]"
```
> Keep this yaml file (`errors.yaml`) at the root of your directory, outside `src`.
Or you can keep it wherever you please and set the `ERROR_YAML_FILE_PATH` environment variable.
> As for the locale configuration, by default, the crate picks up the default value from the `DEFAULT_LOCALE` env, which is set to `en_US` by default.
> - You can change this env to any value you like, provided the corresponding keys are present in yout errors yaml file.
> If you wish to dynamically change the locale programmatically at any given point, you can call the `standard_error::set_current_locale` function like so
```rust
use standard_error::set_current_locale;
fn my_business_logic(){
//...
set_current_locale("hi_IN");
//...
}
```
> This sets a thread local refcell variable that'll persist throught the thread. Since it's a `RefCell` value, and not something like `Arc`, you don't have to worry about it leaking into your other threads/requests.