Skip to content

Commit

Permalink
Add ContextCompat trait for porting from anyhow (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
yaahc authored May 7, 2020
1 parent d525c1f commit 6daf46c
Show file tree
Hide file tree
Showing 9 changed files with 394 additions and 68 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ rustversion = "1.0"
thiserror = "1.0"
trybuild = { version = "1.0.19", features = ["diff"] }
backtrace = "0.3.46"
anyhow = "1.0.28"

[dependencies]
indenter = "0.1.3"
indenter = "0.2.0"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
Expand Down
60 changes: 52 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Eyre
eyre
====

[![Build Status][actions-badge]][actions-url]
Expand Down Expand Up @@ -158,27 +158,71 @@ This crate does its best to be usable as a drop in replacement of `anyhow` and
vice-versa by `re-exporting` all of the renamed APIs with the names used in
`anyhow`.

It is not 100% compatible because there are some cases where `eyre` encounters
type inference errors but it should mostly work as a drop in replacement.
Specifically, the following works in anyhow:
There are two main incompatibilities that you might encounter when porting a
codebase from `anyhow` to `eyre`:

- type inference errors when using `eyre!`
- `.context` not being implemented for `Option`

#### Type Inference Errors

The type inference issue is caused by the generic parameter, which isn't
present in `anyhow::Error`. Specifically, the following works in anyhow:

```rust
use anyhow::anyhow;

// Works
let val = get_optional_val.ok_or_else(|| anyhow!("failed to get value")).unwrap();
let val = get_optional_val().ok_or_else(|| anyhow!("failed to get value")).unwrap_err();
```

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
```rust,compile_fail
use eyre::eyre;
// Broken
let val = get_optional_val.ok_or_else(|| eyre!("failed to get value")).unwrap();
let val = get_optional_val().ok_or_else(|| eyre!("failed to get value")).unwrap();
// Works
let val: Report = get_optional_val.ok_or_else(|| eyre!("failed to get value")).unwrap();
let val: Report = get_optional_val().ok_or_else(|| eyre!("failed to get value")).unwrap();
```

#### `Context` and `Option`

As part of renaming `Context` to `WrapErr` we also intentionally do not
implement `WrapErr` for `Option`. This decision was made because `wrap_err`
implies that you're creating a new error that saves the old error as its
`source`. With `Option` there is no source error to wrap, so `wrap_err` ends up
being somewhat meaningless.

Instead `eyre` intends for users to use the combinator functions provided by
`std` for converting `Option`s to `Result`s. So where you would write this with
anyhow:

```rust
use anyhow::Context;

let opt: Option<()> = None;
let result = opt.context("new error message");
```

With `eyre` we want users to write:

```rust
use eyre::{eyre, Result};

let opt: Option<()> = None;
let result: Result<()> = opt.ok_or_else(|| eyre!("new error message"));
```

However, to help with porting we do provide a `ContextCompat` trait which
implements `context` for options which you can import to make existing
`.context` calls compile.

[Report]: https://docs.rs/eyre/*/eyre/struct.Report.html
[`eyre::EyreContext`]: https://docs.rs/eyre/*/eyre/trait.EyreContext.html
[`eyre::WrapErr`]: https://docs.rs/eyre/*/eyre/trait.WrapErr.html
Expand Down
20 changes: 20 additions & 0 deletions src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ pub(crate) enum ChainState<'a> {
}

impl<'a> Chain<'a> {
/// Construct an iterator over a chain of errors via the `source` method
///
/// # Example
///
/// ```rust
/// use std::error::Error;
/// use std::fmt::{self, Write};
/// use eyre::Chain;
/// use indenter::indented;
///
/// fn report(error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// let mut errors = Chain::new(error).enumerate();
/// for (i, error) in errors {
/// writeln!(f)?;
/// write!(indented(f).ind(i), "{}", error)?;
/// }
///
/// Ok(())
/// }
/// ```
pub fn new(head: &'a (dyn StdError + 'static)) -> Self {
Chain {
state: ChainState::Linked { next: Some(head) },
Expand Down
44 changes: 40 additions & 4 deletions src/context.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::error::ContextError;
use crate::{EyreContext, Report, StdError, WrapErr};
use crate::{ContextCompat, EyreContext, Report, StdError, WrapErr};
use core::fmt::{self, Debug, Display, Write};

#[cfg(backtrace)]
Expand Down Expand Up @@ -80,12 +80,47 @@ where
}
}

impl<T, C> ContextCompat<T, C> for Option<T>
where
C: EyreContext,
{
fn wrap_err<D>(self, msg: D) -> Result<T, Report<C>>
where
D: Display + Send + Sync + 'static,
{
self.context(msg)
}

fn wrap_err_with<D, F>(self, msg: F) -> Result<T, Report<C>>
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D,
{
self.with_context(msg)
}

fn context<D>(self, msg: D) -> Result<T, Report<C>>
where
D: Display + Send + Sync + 'static,
{
self.ok_or_else(|| Report::from_display(msg))
}

fn with_context<D, F>(self, msg: F) -> Result<T, Report<C>>
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D,
{
self.ok_or_else(|| Report::from_display(msg()))
}
}

impl<D, E> Debug for ContextError<D, E>
where
D: Display,
E: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Error")
.field("msg", &Quoted(&self.msg))
.field("source", &self.error)
Expand All @@ -97,7 +132,7 @@ impl<D, E> Display for ContextError<D, E>
where
D: Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.msg, f)
}
}
Expand Down Expand Up @@ -133,7 +168,7 @@ impl<D> Debug for Quoted<D>
where
D: Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_char('"')?;
Quoted(&mut *formatter).write_fmt(format_args!("{}", self.0))?;
formatter.write_char('"')?;
Expand All @@ -153,4 +188,5 @@ pub(crate) mod private {
pub trait Sealed<C: EyreContext> {}

impl<T, E, C: EyreContext> Sealed<C> for Result<T, E> where E: ext::StdError<C> {}
impl<T, C: EyreContext> Sealed<C> for Option<T> {}
}
38 changes: 30 additions & 8 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,28 @@ where
unsafe { Report::construct(error, vtable, context) }
}

pub(crate) fn from_display<M>(message: M) -> Self
where
M: Display + Send + Sync + 'static,
{
use crate::wrapper::{DisplayError, NoneError};
let error: DisplayError<M> = DisplayError(message);
let vtable = &ErrorVTable {
object_drop: object_drop::<DisplayError<M>, C>,
object_ref: object_ref::<DisplayError<M>, C>,
#[cfg(feature = "std")]
object_mut: object_mut::<DisplayError<M>, C>,
object_boxed: object_boxed::<DisplayError<M>, C>,
object_downcast: object_downcast::<M, C>,
object_drop_rest: object_drop_front::<M, C>,
};

// Safety: DisplayError is repr(transparent) so it is okay for the
// vtable to allow casting the DisplayError<M> to M.
let context = Some(C::default(&NoneError));
unsafe { Report::construct(error, vtable, context) }
}

#[cfg(feature = "std")]
pub(crate) fn from_msg<D, E>(msg: D, error: E) -> Self
where
Expand Down Expand Up @@ -280,7 +302,7 @@ where
/// }
/// ```
#[cfg(feature = "std")]
pub fn chain(&self) -> Chain {
pub fn chain(&self) -> Chain<'_> {
self.inner.chain()
}

Expand Down Expand Up @@ -448,13 +470,13 @@ impl<C: EyreContext> DerefMut for Report<C> {
}

impl<C: EyreContext> Display for Report<C> {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.display(formatter)
}
}

impl<C: EyreContext> Debug for Report<C> {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.debug(formatter)
}
}
Expand Down Expand Up @@ -674,8 +696,8 @@ where
// ContextError<ManuallyDrop<D>, E> and ContextError<D, ManuallyDrop<E>>.
#[repr(C)]
pub(crate) struct ContextError<D, E> {
pub msg: D,
pub error: E,
pub(crate) msg: D,
pub(crate) error: E,
}

impl<E, C> ErrorImpl<E, C>
Expand Down Expand Up @@ -707,7 +729,7 @@ where
unsafe { &mut *(self.vtable.object_mut)(self) }
}

pub(crate) fn chain(&self) -> Chain {
pub(crate) fn chain(&self) -> Chain<'_> {
Chain::new(self.error())
}
}
Expand All @@ -727,7 +749,7 @@ where
C: EyreContext,
E: Debug,
{
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.erase().debug(formatter)
}
}
Expand All @@ -737,7 +759,7 @@ where
C: EyreContext,
E: Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.erase().error(), formatter)
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ impl<C> ErrorImpl<(), C>
where
C: EyreContext,
{
pub(crate) fn display(&self, f: &mut fmt::Formatter) -> fmt::Result {
pub(crate) fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.context
.as_ref()
.map(|context| context.display(self.error(), f))
.unwrap_or_else(|| std::fmt::Display::fmt(self.error(), f))
}

pub(crate) fn debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
pub(crate) fn debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.context
.as_ref()
.map(|context| context.debug(self.error(), f))
Expand Down
1 change: 1 addition & 0 deletions src/kind.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![allow(missing_debug_implementations, missing_docs)]
// Tagged dispatch mechanism for resolving the behavior of `eyre!($expr)`.
//
// When eyre! is given a single expr argument to turn into eyre::Report, we
Expand Down
Loading

0 comments on commit 6daf46c

Please sign in to comment.