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

Support converting between anyhow::Error and eyre::Report automatically with ? #178

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ indenter = "0.3.0"
once_cell = "1.18.0"
owo-colors = "4.0"
autocfg = "1.0"
anyhow = "1.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's put this behind a feature?


[profile.dev.package.backtrace]
opt-level = 3
Expand Down
6 changes: 3 additions & 3 deletions eyre/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,26 @@ readme = { workspace = true }
rust-version = { workspace = true }

[features]
default = ["anyhow", "auto-install", "track-caller"]
anyhow = []
default = [ "auto-install", "track-caller"]
auto-install = []
track-caller = []

[dependencies]
indenter = { workspace = true }
once_cell = { workspace = true }
pyo3 = { version = "0.20", optional = true, default-features = false }
anyhow = { workspace = true, optional = true, default-features = false }

[build-dependencies]
autocfg = { workspace = true }

[dev-dependencies]
anyhow = { workspace = true, default-features = true }
futures = { version = "0.3", default-features = false }
rustversion = "1.0"
thiserror = "1.0"
trybuild = { version = "=1.0.89", features = ["diff"] } # pinned due to MSRV
backtrace = "0.3.46"
anyhow = "1.0.28"
syn = { version = "2.0", features = ["full"] }
pyo3 = { version = "0.20", default-features = false, features = ["auto-initialize"] }

Expand Down
7 changes: 5 additions & 2 deletions eyre/src/backtrace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ macro_rules! capture_backtrace {
None
};
}

/// Capture a backtrace iff there is not already a backtrace in the error chain
#[cfg(generic_member_access)]
macro_rules! backtrace_if_absent {
($err:expr) => {
match std::error::request_ref::<std::backtrace::Backtrace>($err as &dyn std::error::Error) {
Some(_) => None,
None => capture_backtrace!(),
Some(v) => None,
None => {
capture_backtrace!()
}
}
};
}
Expand Down
5 changes: 5 additions & 0 deletions eyre/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ impl<D> StdError for ContextError<D, Report>
where
D: Display,
{
#[cfg(generic_member_access)]
fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) {
self.error.provide(request)
}

fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(ErrorImpl::error(self.error.inner.as_ref()))
}
Expand Down
23 changes: 23 additions & 0 deletions eyre/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use core::ptr::{self, NonNull};

use core::ops::{Deref, DerefMut};
use std::any::Any;

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Check (stable)

unused import: `std::any::Any`

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Test Suite (nightly, --features pyo3)

unused import: `std::any::Any`

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Test Suite (nightly)

unused import: `std::any::Any`

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Test Suite (beta, --features pyo3)

unused import: `std::any::Any`

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Test Suite (beta, --no-default-features --features auto-install)

unused import: `std::any::Any`

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Test Suite (beta, --no-default-features)

unused import: `std::any::Any`

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Test Suite (nightly, --no-default-features --features track-caller)

unused import: `std::any::Any`

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Test Suite (nightly, --no-default-features --features auto-install)

unused import: `std::any::Any`

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Test Suite (beta)

unused import: `std::any::Any`

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Test Suite (beta, --no-default-features --features track-caller)

unused import: `std::any::Any`

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Miri

unused import: `std::any::Any`

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Test Suite (macOS-latest)

unused import: `std::any::Any`

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Test Suite (ubuntu-latest)

unused import: `std::any::Any`

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Test Suite (windows-latest)

unused import: `std::any::Any`

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Test Suite (--no-default-features)

unused import: `std::any::Any`

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Test Suite (--no-default-features --features track-caller)

unused import: `std::any::Any`

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Test Suite (--no-default-features --features auto-install)

unused import: `std::any::Any`

Check warning on line 11 in eyre/src/error.rs

View workflow job for this annotation

GitHub Actions / Test Suite

unused import: `std::any::Any`

impl Report {
/// Create a new error object from any error type.
Expand Down Expand Up @@ -488,6 +489,7 @@
}
}

#[cfg(not(feature = "anyhow"))]
impl<E> From<E> for Report
where
E: StdError + Send + Sync + 'static,
Expand All @@ -498,6 +500,27 @@
}
}

#[cfg(feature = "anyhow")]
impl<E> From<E> for Report
where
E: 'static + Into<anyhow::Error>,
Result<(), E>: anyhow::Context<(), E>,
{
#[cfg_attr(track_caller, track_caller)]
fn from(value: E) -> Self {
let mut value = Some(value);
let e = &mut value as &mut dyn Any;

if let Some(e) = e.downcast_mut::<Option<anyhow::Error>>() {
let e: Box<dyn StdError + Send + Sync> = e.take().unwrap().into();
Report::from_boxed(e)
} else {
let e: Box<dyn StdError + Send + Sync> = value.take().unwrap().into().into();
Report::from_boxed(e)
}
}
}

impl Deref for Report {
type Target = dyn StdError + Send + Sync + 'static;

Expand Down
8 changes: 6 additions & 2 deletions eyre/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@
use crate::error::ErrorImpl;
use core::fmt::{Debug, Display};

use std::error::Error as StdError;
use std::{any::Any, error::Error as StdError};

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Check (stable)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Clippy (stable)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite (nightly, --features pyo3)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite (nightly)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite (beta, --features pyo3)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite (nightly, --all-features)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite (beta, --no-default-features --features auto-install)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite (beta, --all-features)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite (beta, --no-default-features)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite (nightly, --no-default-features --features track-caller)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite (nightly, --no-default-features --features auto-install)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite (beta)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite (beta, --no-default-features --features track-caller)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Miri

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite (macOS-latest)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite (ubuntu-latest)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite (windows-latest)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite (--no-default-features)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite (--no-default-features --features track-caller)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite (--no-default-features --features auto-install)

unused import: `any::Any`

Check warning on line 384 in eyre/src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite

unused import: `any::Any`

pub use eyre as format_err;
/// Compatibility re-export of `eyre` for interop with `anyhow`
Expand Down Expand Up @@ -779,6 +779,7 @@
#[cfg_attr(not(feature = "auto-install"), allow(dead_code))]
pub fn default_with(error: &(dyn StdError + 'static)) -> Box<dyn EyreHandler> {
// Capture the backtrace if the source error did not already capture one
eprintln!("checking backtrace");
let backtrace = backtrace_if_absent!(error);

Box::new(Self {
Expand Down Expand Up @@ -848,7 +849,10 @@
let backtrace = self
.backtrace
.as_ref()
.or_else(|| std::error::request_ref::<Backtrace>(error))
.or_else(|| {
eprintln!("Requesting backtrace from underlying type");
std::error::request_ref::<std::backtrace::Backtrace>(error)
})
.expect("backtrace capture failed");

if let BacktraceStatus::Captured = backtrace.status() {
Expand Down
59 changes: 59 additions & 0 deletions eyre/tests/test_anyhow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#![cfg(generic_member_access)]
#![feature(error_generic_member_access)]

use eyre::Report;
use std::fmt::Display;

#[derive(Debug)]
struct RootError;

impl Display for RootError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "RootError")
}
}

impl std::error::Error for RootError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}

fn this_function_fails() -> anyhow::Result<()> {
use anyhow::Context;

Err(RootError).context("Ouch!").context("Anyhow context A")
}

fn test_failure() -> eyre::Result<()> {
use anyhow::Context;
this_function_fails().context("Anyhow context B")?;

Check failure on line 30 in eyre/tests/test_anyhow.rs

View workflow job for this annotation

GitHub Actions / Test Suite (nightly, --features pyo3)

the trait bound `anyhow::Error: std::error::Error` is not satisfied

Check failure on line 30 in eyre/tests/test_anyhow.rs

View workflow job for this annotation

GitHub Actions / Test Suite (nightly)

the trait bound `anyhow::Error: std::error::Error` is not satisfied

Check failure on line 30 in eyre/tests/test_anyhow.rs

View workflow job for this annotation

GitHub Actions / Miri

the trait bound `anyhow::Error: std::error::Error` is not satisfied

Ok(())
}

#[test]
fn anyhow_conversion() {
use eyre::WrapErr;
let error: Report = test_failure().wrap_err("Eyre context").unwrap_err();

eprintln!("Error: {:?}", error);

let chain = error.chain().map(ToString::to_string).collect::<Vec<_>>();
assert_eq!(
chain,
[
"Eyre context",
// Anyhow context
"Anyhow context B",
"Anyhow context A",
// Anyhow error
"Ouch!",
// Original concrete error, shows up in chain too
"RootError"
]
);

let backtrace = std::error::request_ref::<std::backtrace::Backtrace>(&*error).unwrap();
dbg!(backtrace);
}
Loading