Crate n0_error

Crate n0_error 

Source
Expand description

A library for ergonomic errors with call-site location data

This crate provides a StackError trait and stack_error proc macro to ergonomically work with enum or struct errors.

  • All errors that use the macro will implement the StackError trait, which exposes call-site metadata indicating where the error occurred. Its source method returns an ErrorRef, which is an enum over either a reference to a std::error::Error or another StackError. This allows retrieving error locations for the full error chain, as long as all errors are stack errors.

  • The proc macro can add a meta field to structs or enum variants. This field is the source for the call-site location accessed through the StackError trait. There is a simple declarative macro e! that provides an ergonomic way to construct errors with a meta field without having to spell it out everywhere.

  • This crate also provides an AnyError type, which is similar to anyhow. If constructed from an error that implements StackError, the call-site location is preserved. The AnyError type is generally recommended for applications or tests, whereas libraries should use concrete errors with the macro.

  • There are result extensions to convert StackErrors or std::error::Errors to AnyError while providing additional context.

  • While all errors using the derive macro from this crate have a From implementation for AnyError, regular std errors do not. Unfortunately, a blanket From implementation for all std errors would prevent a specialized implementation for stack errors, causing loss of location metadata upon conversion to AnyError. Therefore, you need to use the std_context or anyerr methods to convert results with std errors to AnyError. You should not use these methods on stack errors; instead, use context or simply forward with ?.

The call-site metadata in the meta field is collected only if the environment variable RUST_BACKTRACE=1 or RUST_ERROR_LOCATION=1 is set. Otherwise, it is not collected, as doing so has a small performance overhead.

Both AnyError and all errors that use the derive macro feature the following outputs:

  • Display impl ({error}) prints only the message of the outermost error failed to process input

  • Alternate display impl ({error:#}) prints the message for each error in the chain, in a single line failed to process input: invalid input: wanted 23 but got 13

  • Debug impl ({error:?}) prints the message and each source, on separate lines.

    failed to process input
    Caused by:
        invalid input
        wanted 23 but got 13

    If RUST_BACKTRACE or RUST_ERROR_LOCATION is set, this will also print the call site of each error.

    failed to process input (examples/basic.rs:61:17)
    Caused by:
         invalid input (examples/basic.rs:36:5)
         wanted 23 but got 13 (examples/basic.rs:48:13)
  • Alternate debug impl {error:#?}: An output similar to how the #[derive(Debug)] output looks.

§Feature flags

  • anyhow (off by default): Enables From<anyhow::Error> for AnyError

§Example

use n0_error::{Result, StackResultExt, StdResultExt, e, stack_error};

/// The `stack_error` macro controls how to turn our enum into a `StackError`.
///
/// * `add_meta` adds a field to all variants to track the call-site error location
/// * `derive` adds `#[derive(StackError)]`
/// * `from_sources` creates `From` impls for the error sources
#[stack_error(derive, add_meta, from_sources)]
enum MyError {
    /// We can define the error message with the `error` attribute
    /// It should not include the error source, those are printed in addition depending on the output format.
    #[error("invalid input")]
    InvalidInput { source: InvalidInput },
    /// Or we can define a variant as `transparent`, which forwards the Display impl to the error source
    #[error(transparent)]
    Io {
        /// For sources that do not implement `StackError`, we have to mark the source as `std_err`.
        #[error(std_err)]
        source: std::io::Error,
    },
}

/// We can use the [`stack_error`] macro on structs as well.
#[stack_error(derive, add_meta)]
#[error("wanted {expected} but got {actual}")]
struct InvalidInput {
    expected: u32,
    actual: u32,
}

fn validate_input(number: u32) -> Result<(), InvalidInput> {
    if number != 23 {
        // The `e` macro constructs a `StackError` while automatically adding the `meta` field.
        Err(e!(InvalidInput {
            actual: number,
            expected: 23
        }))
    } else {
        Ok(())
    }
}

fn fail_io() -> std::io::Result<()> {
    Err(std::io::Error::other("io failed"))
}

/// Some function that returns [`MyError`].
fn process(number: u32) -> Result<(), MyError> {
    // We have a `From` impl for `InvalidInput` on our error.
    validate_input(number)?;
    // We have a `From` impl for `std::io::Error` on our error.
    fail_io()?;
    // Without the From impl, we'd need to forward the error manually.
    // The `e` macro can assist here, so that we don't have to declare the `meta` field manually.
    fail_io().map_err(|source| e!(MyError::Io, source))?;
    Ok(())
}

// A main function that returns AnyError (via the crate's Result alias)
fn run(number: u32) -> Result<()> {
    // We can add context to errors via the result extensions.
    // The `context` function adds context to any `StackError`.
    process(number).context("failed to process input")?;
    // To add context to std errors, we have to use `std_context` from `StdResultExt`.
    fail_io().std_context("failed at fail_io")?;
    Ok(())
}

fn main() -> Result<()> {
    if let Err(err) = run(13) {
        println!("{err}");
        // failed to process input

        println!("{err:#}");
        // failed to process input: invalid input: wanted 23 but got 13

        println!("{err:?}");
        // failed to process input
        // Caused by:
        //     invalid input
        //     wanted 23 but got 13

        // and with RUST_BACKTRACE=1 or RUST_ERROR_LOCATION=1
        // failed to process input (examples/basic.rs:61:17)
        // Caused by:
        //     invalid input (examples/basic.rs:36:5)
        //     wanted 23 but got 13 (examples/basic.rs:48:13)

        println!("{err:#?}");
        // Stack(WithSource {
        //     message: "failed to process input",
        //     source: Stack(InvalidInput {
        //         source: BadNumber {
        //             expected: 23,
        //             actual: 13,
        //             meta: Meta(examples/basic.rs:48:13),
        //         },
        //         meta: Meta(examples/basic.rs:36:5),
        //     }),
        //     meta: Meta(examples/basic.rs:61:17),
        // })
    }
    Ok(())
}

/// You can also use the macros with tuple structs or enums.
/// In this case the meta field will be added as the last field.
#[stack_error(derive, add_meta)]
#[error("tuple fail ({_0})")]
struct TupleStruct(u32);

#[stack_error(derive, add_meta)]
#[allow(unused)]
enum TupleEnum {
    #[error("io failed")]
    Io(#[error(source, std_err)] std::io::Error),
}

Macros§

anyerr
Converts a value into AnyError.
bail
Returns an error result by constructing a StackError with e.
bail_any
Returns an error result by constructing an AnyError with anyerr.
e
Constructs an error enum/struct value while automatically filling meta: Meta.
ensure
Ensures a condition, otherwise returns the error constructed with e from the remaining args.
ensure_any
Ensures a condition, otherwise returns an AnyError.
try_or
Unwraps a result, returning in the error case while converting the error.
try_or_any
Unwraps a result, returning in the error case while adding context to the error.

Structs§

AnyError
Type-erased error that can wrap a StackError or any std::error::Error.
Chain
Iterator over the sources of an error.
Location
Wrapper around std::panic::Location used for display in reports.
Meta
Captured metadata for an error creation site.
NoneError
Error returned when converting Options to an error.
Report
A Report customizes how an error is displayed.

Enums§

ErrorRef
Reference to an error which can either be a std error or a stack error.
SourceFormat
Output style for rendering error sources in a Report.

Traits§

StackError
Trait implemented by errors produced by this crate.
StackErrorExt
Extension methods for StackErrors that are Sized.
StackResultExt
Provides extension methods to add context to StackErrors.
StdResultExt
Provides extension methods to add context to std errors.

Functions§

Ok
Returns a result with the error type set to AnyError.
meta
Creates new Meta capturing the caller location.

Type Aliases§

Result
Result type alias where the error type defaults to AnyError.

Attribute Macros§

stack_error
Attribute macro to expand error enums or structs.

Derive Macros§

StackError
Derive macro that implements StackError, Display, Debug and std::error::Error and generates From<T> impls for fields/variants configured via #[error(..)]. Derive macro for stack errors.