[][src]Crate err_context

Lightweight error context manipulation.

Oftentimes, when error is being reported to the user, the lowest-level cause of the original error is not very interesting (eg. „No such file or directory“). It is not clear what file it should have been, why the program needed that file and what the consequences of failing to find it are.

This library allows to wrap the low-level error into layers of context easily and examine such multi-layer libraries. Depending on preferences of the developer, the story of the whole error might then be found either in the outer layer, or the whole chain may be needed.

There are other libraries with similar aim, though none seemed to fit very well. Most of them have unergonomic API. This API is modeled after the failure crate (which has simple and powerful API), but uses the Error trait in standard library.

Compatibility

By using the trait and AnyError type alias, the library is compatible with any future or past version of self or any other error handling library that operates on the Error trait. To avoid dependencies on specific version of this library, downstream crates are advised to not reexport types from here with the sole exception of the AnyError alias (as it is alias, it can be re-created independently by as many libraries and is compatible). Downstream libraries are, of course, free to expose their own errors.

Using the library

Besides the AnyError alias, users of the library probably don't want to use any of the present types here directly. Instead, certain traits can be imported and all errors, boxed errors and results containing them get additional methods.

The methods can be found on the ErrorExt and ResultExt traits.

Producing errors

The lowest level error comes from somewhere else ‒ it may be type provided by some other crate, an error implemented manually or generated with help of some other crate (the err-derive crate offers derive macro similar to the one of failure, but for standard errors ‒ combination of these two crates provide almost all the functionality of failure).

Then, as the error bubbles up, it can be enriched by additional information using the .context and .with_context methods.

use std::error::Error;
use std::io::Read;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::fs::File;
use std::path::Path;

use err_context::AnyError;
use err_context::prelude::*;

// An example error. You'd implement it manually like this, or use something else, like
// err-derive, to generate it.
#[derive(Debug)]
struct BrokenImage;

impl Display for BrokenImage {
    fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
        write!(fmt, "The image is broken")
    }
}

impl Error for BrokenImage {}

#[derive(Clone, Debug)]
struct Image;

impl Image {
    fn parse(_: Vec<u8>) -> Result<Self, BrokenImage> {
        // This is an example. We didn't really implement this.
        Err(BrokenImage)
    }
}

fn read_image<P: AsRef<Path>>(path: P) -> Result<Image, AnyError> {
    let mut file = File::open(&path)
        .with_context(|_| format!("Couldn't open file {}", path.as_ref().display()))?;
    let mut data = Vec::new();
    file.read_to_end(&mut data)
        .with_context(|_| format!("File {} couldn't be read", path.as_ref().display()))?;
    let image = Image::parse(data)
        .with_context(|_| format!("Image in file {} is corrupt", path.as_ref().display()))?;
    Ok(image)
}

Note that the type of the error produced by any of these methods doesn't carry any useful information. Therefore this library should be used only at places where the error is meant for printing out to user or some other handling in uniform manner. Libraries providing building blocks might want to implement their own typed errors, with possibly usefully typed layers.

Consuming the errors

These kinds of errors are usually meant for the user. The outer layer's Display contains only its own context, eg:

let inner = std::io::Error::last_os_error();
let outer = inner.context("Some error");
assert_eq!("Some error", outer.to_string());

If you're interested in all the layers, they can be iterated (this simply calls the source method until it gets to the bottom).

let inner = std::io::Error::last_os_error();
let outer = inner.context("Some error");
// This will print
//   Some error
//   success (or whatever the last io error was)
for e in outer.chain() {
    println!("{}", e);
}

Or, more conveniently can be printed as a single string:

let inner = std::io::Error::last_os_error();
let outer = inner.context("Some error");
// Will print something like:
// Some error: success
println!("{}", outer.display(", "));

Despite being mostly aimed for human output, the chain still can be examined to an extend. In particular, it is possible to look for the outermost error in the chain of specific type. This will find the inner error.

let inner = std::io::Error::last_os_error();
let outer = inner.context("Some error");
assert!(outer.find_source::<std::io::Error>().is_some());

Modules

prelude

A module with reexports to wildcard-import all relevant traits.

Structs

BoxContext

Additional context for boxed errors.

Chain

An iterator through the error chain.

Context

Additional level of context, wrapping some error inside itself.

DisplayChain

A display implementation for formatting separated layers of an error.

Traits

BoxedErrorExt

An equivalent of ErrorExt for boxed errors.

BoxedResultExt

A ResultExt equivalent for boxed errors.

ErrorExt

Extension trait for the Error trait.

ResultExt

Extension traits for results.

Type Definitions

AnyError

A type alias for boxed errors.