#derive-error #error #trace #tracing #error-handling #derive #error-chain

nightly no-std errore

Library for error handling and tracing

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

Download history 273/week @ 2024-10-23

277 downloads per month

MIT/Apache

120KB
2.5K SLoC

Errore

crates.io docs.rs build status rustc version

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 and Formatter interface
  • Support for user attached data with Extensions at subscriber
  • Partial API compatibility with thiserror that allows to optionally enable errore in public distributed libraries.
    See example
  • Usable in application and library code
  • no-std support & wasmcompatible

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 of std::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 if errore 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 the ctor and inventory 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 at no-std projects on build failures.
  • debug-no-std: Enables internal debug logging with the defmt crate.
  • debug-std: Enables internal debug logging with the log crate.
  • std: Enables standard library support. If the std feature is not enabled, the alloc 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