#error #context #rich #backtrace #kinds #error-message #wrapping

anyerr

Dynamic error library with rich error wrapping and context support

1 unstable release

0.1.1 Feb 17, 2025

#262 in Debugging

Download history 96/week @ 2025-02-12 23/week @ 2025-02-19 14/week @ 2025-02-26 79/week @ 2025-03-05 28/week @ 2025-03-12

162 downloads per month

MIT license

120KB
2K SLoC

anyerr

GitHub | Crates.io | Docs.rs

anyerr is a flexible and powerful error-handling library for Rust. It provides a highly customizable error type called AnyError, allowing you to capture and manage rich error information, including custom error kinds, backtraces, and contextual data.

Features

  • Error Composition: Combine and wrap errors while preserving original information.
  • Customizable Error Kinds: Use predefined error kinds or create your own.
  • Contextual Data Support: Attach rich, structured context to your errors for better debugging.
  • Backtrace Support: Automatically captures backtraces to simplify error diagnosis.
  • Error Reporting: Customize and write formated and detailed error messages to stdout, loggers and so on.

Installation

Add anyerr to your Cargo.toml:

[dependencies]
anyerr = "0.1.1"

Getting Started

Defining a Custom Error Type

AnyError is the core type of this library. It works similarly to Box<dyn Error>, but with added capabilities for handling contexts and custom error kinds. You can define your own error type with AnyError by choosing appropriate components.

Here's an example:

// Make this module accessible to your whole crate.
mod err {
    use anyerr::AnyError as AnyErrorTemplate;
    use anyerr::context::LiteralKeyStringMapContext;

    pub use anyerr::{Intermediate, Overlay};
    pub use anyerr::kind::DefaultErrorKind as ErrKind;
    pub use anyerr::Report;

    pub type AnyError = AnyErrorTemplate<LiteralKeyStringMapContext, ErrKind>;
    pub type AnyResult<T> = Result<T, AnyError>;
}

Creating and Using Errors

You can create errors with different levels of detail, depending on your needs.

use err::*;

fn fail() -> AnyResult<()> {
    // Use `AnyError::minimal()` to create a simple [`String`]-based error.
    Err(AnyError::minimal("this function always fails"))
}

fn check_positive(x: i32) -> AnyResult<()> {
    if x > 0 {
        return Ok(());
    }
    // Use `AnyError::quick()` to quickly create an error with an error
    // message and an error kind.
    Err(AnyError::quick(
        "expects `x` to be a positive number",
        ErrKind::ValueValidation
    ))
}

fn try_add_username(
    usernames: &mut Vec<String>,
    new_username: String
) -> AnyResult<usize> {
    let res = usernames.iter()
        .enumerate()
        .find(|(_, username)| **username == new_username)
        .map(|(index, _)| index);
    if let Some(index) = res {
        // Use `AnyError::builder()` to create an error with all essential
        // context you'll need.
        let err = AnyError::builder()
            .message("the username already exists")
            .kind(ErrKind::RuleViolation)
            .context("new_username", new_username)
            .context("index", index)
            .build();
        Err(err)
    } else {
        usernames.push(new_username);
        Ok(usernames.len() - 1)
    }
}

fn parse_i32(input: &str) -> AnyResult<i32> {
    // Use `AnyError::wrap()` to wrap any other error type.
    input.parse::<i32>().map_err(AnyError::wrap)
}

Using AnyError is also rather straightforward.

use err::*;

fn main() {
    let mut usernames = Vec::new();

    let res = try_add_username(&mut usernames, "foo").unwrap();
    assert_eq!(res, 0);

    let err = try_add_username(&mut usernames, "foo").unwrap_err();
    assert_eq!(err.to_string(), "the username already exists"); // Or `err.message()`.
    assert_eq!(err.kind(), ErrKind::RuleViolation);
    assert_eq!(err.get("new_username"), Some("\"foo\""));
    assert_eq!(err.get("index"), Some("0"));
}

Chaining Errors

AnyError allows attaching chaining errors for better logging and easier debugging.

use err::*;

struct UserRepository {
    conn: Arc<Connection>,
}

impl UserRepository {
    pub fn find_by_username(&self, username: &str) -> AnyResult<User> {
        // Don't build SQL statements yourself in practice.
        let statement = format!("SELECT * FROM users WHERE users.username = '{username}'");
        let data = self.conn.query(&statement)
            .overlay(("could not get a `User` due to SQL execution error", ErrKind::EntityAbsence))
            .context("username", username)
            .context("statement", statement)?;
        let entity = User::try_from(data)
            .overlay(("could not get a `User` due to serialization error", Errkind::EntityAbsence))
            .context("username", username)?;
        Ok(entity)
    }
}

Error Reporting

Use Report to display a formated error message with details:

use err::*;

fn source_error() -> AnyResult<()> {
    let err = AnyError::builder()
        .message("the source error is here")
        .kind(ErrKind::InfrastructureFailure)
        .context("key1", "value1")
        .context("key2", "value2")
        .build();
    Err(err)
}

fn intermediate_error() -> AnyResult<()> {
    source_error()
        .overlay("the intermediate error is here")
        .context("key3", "value3")?;
    Ok(())
}

fn toplevel_error() -> AnyResult<()> {
    intermediate_error()
        .overlay("the toplevel error is here")?;
    Ok(())
}

fn main() -> impl Termination {
    Report::capture(|| {
        toplevel_error()?;
        Ok(())
    })
}

The expected output is

Error:
    (Unknown) the toplevel error is here
Caused by:
    (Unknown) the intermediate error is here
    [key3 = "value3"]
Caused by:
    (InfrastructureFailure) the source error is here
    [key1 = "value1", key2 = "value2"]

Stack backtrace:
  0: anyerr::core::data::ErrorDataBuilder<C,K>::build
            at ./src/core/data.rs:210:28
  1: anyerr::core::AnyErrorBuilder<C,K>::build
            at ./src/core.rs:415:24
  2: anyerr::source_error
            at ./src/main.rs:18:15
  3: anyerr::intermediate_error
            at ./src/main.rs:28:5
  4: anyerr::toplevel_error
            at ./src/main.rs:35:5
  5: anyerr::main::{{closure}}
            at ./src/main.rs:40:43
  6: anyerr::report::Report<C,K>::capture
            at ./src/report.rs:52:15
  7: anyerr::main
            at ./src/main.rs:40:5
   ...

Advanced Usage

See API documentation for more features and advanced usages of different types in this crate.

Examples

Please refer to example codes in the examples directory.

License

Copyright (C) 2025 Justin Chen

This project is licensed under the MIT License.

No runtime deps