Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ContextCompat trait for porting from anyhow #15

Merged
merged 4 commits into from
May 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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