4 releases (2 breaking)
new 0.3.0 | Oct 29, 2024 |
---|---|
0.2.0 | Oct 29, 2024 |
0.1.1 | Oct 27, 2024 |
0.1.0 | Oct 27, 2024 |
#788 in Rust patterns
277 downloads per month
120KB
2.5K
SLoC
Errore
This library provides a framework to handle and trace errors across modules and crates.
At the moment errore is in development and breaking changes are to be expected.
Example
auth.rs
use std::{fs, path::PathBuf};
// if 'errore::result::Result' is not needed, a simple wildcard import can be used:
// use errore::*;
use errore::prelude::*;
/// Errors for any failed authentication.
#[derive(Error, Debug)]
pub enum Error {
#[error("Invalid email or password")]
ReadPassword(#[from] std::io::Error),
#[error("Invalid email or password")]
InvalidCredentials,
}
// Automatically generated:
// pub struct Ec(pub Span<Error>)
fn read_password(email: &str) -> Result<String, Ec> {
Ok(fs::read_to_string(PathBuf::from(email))?)
}
pub fn verify(email: &str, password: &str) -> Result<(), Ec> {
if read_password(email)? != password {
return err!(Error::InvalidCredentials);
}
Ok(())
}
account.rs
use errore::prelude::*;
use crate::auth;
/// Errors for account related operations.
#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
Authentication(#[from] auth::Ec),
#[error("Submitted captcha '{hash}' is wrong")]
WrongCaptcha { hash: String },
#[error("Captcha session '{session}' was not found or is expired")]
InvalidCaptcha { session: String },
}
// Automatically generated:
// pub struct Ec(pub Span<Error>)
pub fn login(email: &str, password: &str) -> Result<(), Ec> {
auth::verify(email, password)?;
// errors can also be defined without err!() macro
Err(Ec::new(Error::WrongCaptcha {
hash: "abc123".into(),
}))
}
main.rs
mod account;
mod auth;
use errore::prelude::*;
fn main() {
env_logger::builder().format_timestamp(None).init();
if let Err(ec) = account::login("root@errore.dev", "123") {
// print formatted error chain
println!("{}", ec.trace());
// print trace records
println!("\nTrace records:");
for tr in &ec {
println!("{}", tr);
}
// print the origin of the error
// (the deepest 'Display' trait implementation will be used)
println!("\nError display:\n{}", ec);
// error extraction with 'match':
// useful for handling multiple errors
match ec.error() {
account::Error::Authentication(ec) => match ec.error() {
auth::Error::ReadPassword(error) => {
println!(
"\nError extraction with 'match':\nOS error code {}: {}",
error.raw_os_error().unwrap_or_default(),
error.kind()
)
}
_ => {}
},
_ => {}
}
// error extraction with 'get()':
// useful for deeply nested errors
if let Some(auth_error) = ec.get::<auth::Error>() {
match &*auth_error {
auth::Error::ReadPassword(error) => println!(
"\nError extraction with 'get()':\nOS error code {}: {}",
error.raw_os_error().unwrap_or_default(),
error.kind()
),
_ => {}
}
}
}
}
Examplary error output:
Error: example_basic::account::Authentication
├─▶ <example_basic::auth::ReadPassword> Invalid email or password
│ ├╴ examples/basic/src/auth.rs:20:8
│ ╰╴ examples/basic/src/auth.rs:24:8
│
╰─▶ <example_basic::account::Authentication>
╰╴ examples/basic/src/account.rs:20:5
Trace records:
<example_basic::auth::ReadPassword> Invalid email or password at examples/basic/src/auth.rs:20:8
<example_basic::auth::ReadPassword> Invalid email or password at examples/basic/src/auth.rs:24:8
<example_basic::account::Authentication> Invalid email or password at examples/basic/src/account.rs:20:5
Error display:
example_basic::account::Authentication: Invalid email or password
at examples/basic/src/auth.rs:20:8
Error extraction with 'match':
OS error code 2: entity not found
Error extraction with 'get()':
OS error code 2: entity not found
For more examples please see here.
Features
- Tracing capability with rich metadata such as file location and line number without
backtrace
- Generates trait implementations for
metadata
and error conversion - Customizable
Subscriber
andFormatter
interface - Support for user attached data with
Extensions
at subscriber - Partial API compatibility with
thiserror
that allows to optionally enableerrore
in public distributed libraries.
Seeexample
- Usable in application and library code
no-std
support &wasm
compatible
Limitations & Disadvantages
- Invasive code changes with
Result
instrumentation are required - Nightly compiler is required
- Only one error per module can be defined
- No recursive or self-referencing fields
- Error conversion with attribute macro
#from
requires a trait implementation ofstd::error::Error
for the type - Generics with traits in error fields need to be declared with the
where
keyword - Some edge cases cannot be expressed with generics (for e.g. nesting)
- No
anyhow
support (shouldn't be a problem iferrore
is used)
Recommendations
- For public libraries an optional feature flag for errore is advisable.
For the best results
thiserror
should be used.
See Example - For private libraries
errore
can be used as is. Errors are best declared on a per module basis.
See Example - For general best-practices with
errore
the various examples can serve as a good foundation
Feature flags
ctor
: Utilizes link_sections provided by thector
andinventory
crates to offer a better implementation of the metadata and subscriber relevant code. The fallback implementation is based on lazy static variables. This feature can be disabled atno-std
projects on build failures.debug-no-std
: Enables internal debug logging with thedefmt
crate.debug-std
: Enables internal debug logging with thelog
crate.std
: Enables standard library support. If thestd
feature is not enabled, thealloc
crate is required.
Thanks to
- @dtolnay - Maintainer of several great crates including
thiserror
which is used as errore`s foundation - tracing / error-stack / error_set maintainers & contributors for the inspiring codebase and ideas
Dependencies
~2.4–3.5MB
~62K SLoC