Skip to content

Commit

Permalink
Merge pull request #1 from yaahc/testing
Browse files Browse the repository at this point in the history
Merge customization and test via PR
  • Loading branch information
yaahc authored Feb 28, 2020
2 parents bac602c + 3b26dac commit 403f525
Show file tree
Hide file tree
Showing 26 changed files with 815 additions and 572 deletions.
85 changes: 85 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
on: [push, pull_request]

name: Continuous integration

jobs:
check:
name: Check
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
- 1.34.0
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- uses: actions-rs/cargo@v1
with:
command: check

test:
name: Test Suite
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
- nightly
- 1.34.0
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- uses: actions-rs/cargo@v1
with:
command: test
# - uses: actions-rs/cargo@v1
# with:
# command: test
# args: --no-default-features

fmt:
name: Rustfmt
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
- 1.34.0
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check

clippy:
name: Clippy
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
- 1.34.0
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- run: rustup component add clippy
- uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
22 changes: 0 additions & 22 deletions .travis.yml

This file was deleted.

15 changes: 6 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
[package]
name = "anyhow"
version = "1.0.26" # remember to update html_root_url
authors = ["David Tolnay <dtolnay@gmail.com>"]
name = "eyre"
version = "0.2.0" # remember to update html_root_url
authors = ["David Tolnay <dtolnay@gmail.com>", "Jane Lusby <jlusby42@gmail.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
description = "Flexible concrete Error type built on std::error::Error"
repository = "https://github.com/dtolnay/anyhow"
documentation = "https://docs.rs/anyhow"
description = "Flexible concrete Error Handling and Reporting type built on std::error::Error"
repository = "https://github.com/yaahc/eyre"
documentation = "https://docs.rs/eyre"
readme = "README.md"
categories = ["rust-patterns"]

[badges]
travis-ci = { repository = "dtolnay/anyhow" }

[features]
default = ["std"]
std = []
Expand Down
170 changes: 149 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,142 @@
Anyhow&ensp;¯\\\_(ツ)\_
Eyre&ensp;¯\\\_(ツ)\_
=========================

[![Build Status](https://api.travis-ci.com/dtolnay/anyhow.svg?branch=master)](https://travis-ci.com/dtolnay/anyhow)
[![Latest Version](https://img.shields.io/crates/v/anyhow.svg)](https://crates.io/crates/anyhow)
[![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/anyhow)
[![Build Status][actions-badge]][actions-url]
[![Latest Version](https://img.shields.io/crates/v/eyre.svg)](https://crates.io/crates/eyre)
[![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/eyre)

This library provides [`eyre::ErrReport`][ErrReport], a trait object based
error handling type for easy idiomatic error handling and reporting in Rust
applications.

First and foremost, this crate is a fork of `anyhow` by @dtolnay. My goal in
writing this crate is to explore new directions of handling context and to
explore new ways to communicate the intended usage of this crate via changes to
the API.

The main changes this crate brings to anyhow are

* Addition of the [`eyre::EyreContext`] trait and a type parameter on the core error
handling type which users can use to insert custom forms of context into
their general error type.
* Rebranding the type as principally for error reporting, rather than
describing it as an error type in its own right. This type is not an error,
it contains errors that it masqerades as, and provides helpers for creating
new errors to wrap those errors and for displaying those chains of errors,
and the included context, to the end user. The goal is to make it obvious
that this type is meant to be used when the only way you expect to handle
errors is to print them.
* Changing the [`anyhow::Context`] trait to [`eyre::WrapErr`] to make it clear
that it is unrelated to the [`eyre::EyreContext`] and the context member, and
is only for inserting new errors into the chain of errors.
* Addition of a new `context` function on [`eyre::ErrReport`] to assist with
extracting members from the inner Context, which is used by
[`eyre::ErrReport`] to extract [`std::backtrace::Backtrace`]'s from generic
contexts types.

These changes were made in order to facilitate the usage of
[`tracing::SpanTrace`] with anyhow, which is a Backtrace-like type for
rendering custom defined runtime context.

**Note**: The way the `eyre!` macro works in practice differs from how
`anyhow!` works due to the addition of the generic type parameter. In anyhow
the following is valid.

```rust
let val = get_optional_val.ok_or_else(|| anyhow!("failed to get value)).unwrap();
```
Where as with `eyre!` this will fail due to being unable to infer the type for
the Context parameter. The solution to this problem, should you encounter it,
is to give the compiler a hint for what type it should be resolving to, either
via your return type or a type annotation.
```rust
// Will work fine
let val: ErrReport = get_optional_val.ok_or_else(|| eyre!("failed to get value)).unwrap();
```

[ErrReport]: https://docs.rs/eyre/1.0/eyre/struct.ErrReport.html
[actions-badge]: https://github.com/yaahc/eyre/workflows/ci/badge.svg
[actions-url]:https://github.com/yaahc/eyre/actions?query=workflow%3Aci

## Customization

In order to insert your own custom context type you must first implement the
`eyre::EyreContext` trait for said type, which has four required methods.

* `fn default(error: &Error) -> Self` - For constructing default context while
allowing special case handling depending on the content of the error you're
wrapping.

This is essentially `Default::default` but more flexible, for example, the
`eyre::DefaultContext` type provide by this crate uses this to only capture a
`Backtrace` if the inner `Error` does not already have one.

```rust
fn default(error: &(dyn StdError + 'static)) -> Self {
let backtrace = backtrace_if_absent!(error);

Self { backtrace }
}
```

* `fn context_raw(&self, typeid TypeID) -> Option<&dyn Any>` - For extracting
arbitrary members from a context based on their type.

This library provides [`anyhow::Error`][Error], a trait object based error type
for easy idiomatic error handling in Rust applications.
This method is like a flexible version of the `fn backtrace(&self)` method on
the `Error` trait. In the future we will likely support extracting `Backtrace`s
and `SpanTrace`s by default by relying on the implementation of `context_raw`
provided by the user.

[Error]: https://docs.rs/anyhow/1.0/anyhow/struct.Error.html

Here is how the `eyre::DefaultContext` type uses this to return `Backtrace`s.

```rust
fn context_raw(&self, typeid: TypeId) -> Option<&dyn Any> {
if typeid == TypeId::of::<Backtrace>() {
self.backtrace.as_ref().map(|b| b as &dyn Any)
} else {
None
}
}
```

* `fn debug(&self, error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt Result`
it's companion `display` version. - For formatting the entire error chain and
the user provided context.

When overriding the context it no longer makes sense for `eyre::ErrReport` to
provide the `Display` and `Debug` implementations for the user, becase we
cannot predict what forms of context you will need to display along side your
chain of errors. Instead we forward the implementations of `Display` and
`Debug` to these methods on the inner `EyreContext` type.

This crate does provide a few helpers to assist in writing display
implementations, specifically the `Chain` type, for treating an error and its
sources like an iterator, and the `Indented` type, for indenting multi line
errors consistently without using heap allocations.

**Note**: best practices for printing errors suggest that `{}` should only
print the current error and none of its sources, and that the primary method of
displaying an error, its sources, and its context should be handled by the
`Debug` implementation, which is what is used to print errors that are returned
from `main`. For examples on how to implement this please refer to the
implementations of `display` and `debug` on `eyre::DefaultContext`

Once you've defined a custom Context type you can use it throughout your
application by defining a type alias.

```rust
type ErrReport = eyre::ErrReport<MyContext>;

// And optionally...
type Result<T, E = eyre::ErrReport<MyContext>> = core::result::Result<T, E>;
```

```toml
[dependencies]
anyhow = "1.0"
eyre = "0.2"
```

*Compiler support: requires rustc 1.34+*
Expand All @@ -21,14 +145,14 @@ anyhow = "1.0"

## Details

- Use `Result<T, anyhow::Error>`, or equivalently `anyhow::Result<T>`, as the
- Use `Result<T, eyre::ErrReport>`, or equivalently `eyre::Result<T>`, as the
return type of any fallible function.

Within the function, use `?` to easily propagate any error that implements the
`std::error::Error` trait.

```rust
use anyhow::Result;
use eyre::Result;

fn get_cluster_info() -> Result<ClusterMap> {
let config = std::fs::read_to_string("cluster.json")?;
Expand All @@ -43,7 +167,7 @@ anyhow = "1.0"
application was in the middle of.

```rust
use anyhow::{Context, Result};
use eyre::{WrapErr, Result};

fn main() -> Result<()> {
...
Expand Down Expand Up @@ -78,7 +202,7 @@ anyhow = "1.0"
type does not already provide its own. In order to see backtraces, the
`RUST_LIB_BACKTRACE=1` environment variable must be defined.

- Anyhow works with any error type that has an impl of `std::error::Error`,
- Eyre works with any error type that has an impl of `std::error::Error`,
including ones defined in your crate. We do not bundle a `derive(Error)` macro
but you can write the impls yourself or use a standalone macro like
[thiserror].
Expand All @@ -98,36 +222,40 @@ anyhow = "1.0"
}
```

- One-off error messages can be constructed using the `anyhow!` macro, which
supports string interpolation and produces an `anyhow::Error`.
- One-off error messages can be constructed using the `eyre!` macro, which
supports string interpolation and produces an `eyre::ErrReport`.

```rust
return Err(anyhow!("Missing attribute: {}", missing));
return Err(eyre!("Missing attribute: {}", missing));
```

<br>

## No-std support

**NOTE**: tests are currently broken for `no_std` so I cannot guaruntee that
everything works still. I'm waiting for upstream fixes to be merged rather than
fixing them myself, so bear with me.

In no_std mode, the same API is almost all available and works the same way. To
depend on Anyhow in no_std mode, disable our default enabled "std" feature in
depend on Eyre in no_std mode, disable our default enabled "std" feature in
Cargo.toml. A global allocator is required.

```toml
[dependencies]
anyhow = { version = "1.0", default-features = false }
eyre = { version = "0.2", default-features = false }
```

Since the `?`-based error conversions would normally rely on the
`std::error::Error` trait which is only available through std, no_std mode will
require an explicit `.map_err(Error::msg)` when working with a non-Anyhow error
type inside a function that returns Anyhow's error type.
require an explicit `.map_err(ErrReport::msg)` when working with a non-Eyre error
type inside a function that returns Eyre's error type.

<br>

## Comparison to failure

The `anyhow::Error` type works something like `failure::Error`, but unlike
The `eyre::ErrReport` type works something like `failure::Error`, but unlike
failure ours is built around the standard library's `std::error::Error` trait
rather than a separate trait `failure::Fail`. The standard library has adopted
the necessary improvements for this to be possible as part of [RFC 2504].
Expand All @@ -138,7 +266,7 @@ the necessary improvements for this to be possible as part of [RFC 2504].

## Comparison to thiserror

Use Anyhow if you don't care what error type your functions return, you just
Use Eyre if you don't care what error type your functions return, you just
want it to be easy. This is common in application code. Use [thiserror] if you
are a library that wants to design your own dedicated error type(s) so that on
failures the caller gets exactly the information that you choose.
Expand Down
4 changes: 2 additions & 2 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::process::{Command, ExitStatus};

// This code exercises the surface area that we expect of the std Backtrace
// type. If the current toolchain is able to compile it, we go ahead and use
// backtrace in anyhow.
// backtrace in eyre.
const PROBE: &str = r#"
#![feature(backtrace)]
#![allow(dead_code)]
Expand Down Expand Up @@ -51,7 +51,7 @@ fn compile_probe() -> Option<ExitStatus> {
fs::write(&probefile, PROBE).ok()?;
Command::new(rustc)
.arg("--edition=2018")
.arg("--crate-name=anyhow_build")
.arg("--crate-name=eyre_build")
.arg("--crate-type=lib")
.arg("--emit=metadata")
.arg("--out-dir")
Expand Down
Loading

0 comments on commit 403f525

Please sign in to comment.