#error-message #web-framework #web-apps #axum #yaml #locale #structured

standard-error

simplifies returning meaningful errors for axum services

4 releases

0.1.5 Sep 12, 2024
0.1.4 Sep 7, 2024
0.1.1 Aug 17, 2024

#577 in HTTP server

31 downloads per month

MIT license

32KB
497 lines

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:

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

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:

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:

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:

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;
// This will return an error with code "ER-0006" and message: "Should be an integer - fname: ashu | lname: pednekar".

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:


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:
// "Should be an integer - fname: ashu | lname: pednekar - invalid digit found in string".

Installation

Add standard-error to your Cargo.toml:

[dependencies]
standard-error = "0.1"

or with cargo

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:

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

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.

Dependencies

~9–23MB
~354K SLoC