| Crates.io | anyerr |
| lib.rs | anyerr |
| version | 0.1.1 |
| created_at | 2025-02-17 01:26:53.840595+00 |
| updated_at | 2025-02-17 01:26:53.840595+00 |
| description | Dynamic error library with rich error wrapping and context support |
| homepage | |
| repository | https://github.com/oosquare/anyerr |
| max_upload_size | |
| id | 1558417 |
| size | 126,435 |
anyerranyerr 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.
stdout, loggers and so on.Add anyerr to your Cargo.toml:
[dependencies]
anyerr = "0.1.1"
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>;
}
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"));
}
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)
}
}
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
...
See API documentation for more features and advanced usages of different types in this crate.
Please refer to example codes in the examples directory.
Copyright (C) 2025 Justin Chen
This project is licensed under the MIT License.