Skip to content

Commit 5d5e18c

Browse files
committed
feat: convert between anyhow::Error and eyre::Result
1 parent df42dc4 commit 5d5e18c

File tree

8 files changed

+101
-11
lines changed

8 files changed

+101
-11
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ indenter = "0.3.0"
1818
once_cell = "1.18.0"
1919
owo-colors = "4.0"
2020
autocfg = "1.0"
21+
anyhow = "1.0"
2122

2223
[profile.dev.package.backtrace]
2324
opt-level = 3

eyre/Cargo.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,26 @@ readme = { workspace = true }
1313
rust-version = { workspace = true }
1414

1515
[features]
16-
default = ["anyhow", "auto-install", "track-caller"]
17-
anyhow = []
16+
default = [ "auto-install", "track-caller"]
1817
auto-install = []
1918
track-caller = []
2019

2120
[dependencies]
2221
indenter = { workspace = true }
2322
once_cell = { workspace = true }
2423
pyo3 = { version = "0.20", optional = true, default-features = false }
24+
anyhow = { workspace = true, optional = true, default-features = false }
2525

2626
[build-dependencies]
2727
autocfg = { workspace = true }
2828

2929
[dev-dependencies]
30+
anyhow = { workspace = true, default-features = true }
3031
futures = { version = "0.3", default-features = false }
3132
rustversion = "1.0"
3233
thiserror = "1.0"
3334
trybuild = { version = "=1.0.89", features = ["diff"] } # pinned due to MSRV
3435
backtrace = "0.3.46"
35-
anyhow = "1.0.28"
3636
syn = { version = "2.0", features = ["full"] }
3737
pyo3 = { version = "0.20", default-features = false, features = ["auto-initialize"] }
3838

eyre/src/backtrace.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,31 @@ macro_rules! capture_backtrace {
1717
None
1818
};
1919
}
20+
2021
/// Capture a backtrace iff there is not already a backtrace in the error chain
2122
#[cfg(generic_member_access)]
2223
macro_rules! backtrace_if_absent {
2324
($err:expr) => {
2425
match std::error::request_ref::<std::backtrace::Backtrace>($err as &dyn std::error::Error) {
25-
Some(_) => None,
26-
None => capture_backtrace!(),
26+
Some(v) => {
27+
eprintln!(
28+
"BAcktrace present: {v:?} {:?}",
29+
($err as &dyn std::error::Error).type_id()
30+
);
31+
None
32+
}
33+
None => {
34+
eprintln!("No backtrace");
35+
capture_backtrace!()
36+
}
2737
}
2838
};
2939
}
3040

3141
#[cfg(not(generic_member_access))]
3242
macro_rules! backtrace_if_absent {
3343
($err:expr) => {
44+
eprintln!("capturing stable backtrace");
3445
capture_backtrace!()
3546
};
3647
}

eyre/src/context.rs

+5
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,11 @@ impl<D> StdError for ContextError<D, Report>
150150
where
151151
D: Display,
152152
{
153+
#[cfg(generic_member_access)]
154+
fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) {
155+
self.error.provide(request)
156+
}
157+
153158
fn source(&self) -> Option<&(dyn StdError + 'static)> {
154159
Some(ErrorImpl::error(self.error.inner.as_ref()))
155160
}

eyre/src/error.rs

+14-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use core::mem::{self, ManuallyDrop};
88
use core::ptr::{self, NonNull};
99

1010
use core::ops::{Deref, DerefMut};
11+
use std::any::Any;
1112

1213
impl Report {
1314
/// Create a new error object from any error type.
@@ -490,11 +491,20 @@ impl Report {
490491

491492
impl<E> From<E> for Report
492493
where
493-
E: StdError + Send + Sync + 'static,
494+
E: 'static + Into<anyhow::Error>,
495+
Result<(), E>: anyhow::Context<(), E>,
494496
{
495-
#[cfg_attr(track_caller, track_caller)]
496-
fn from(error: E) -> Self {
497-
Report::from_std(error)
497+
fn from(value: E) -> Self {
498+
let mut value = Some(value);
499+
let e = &mut value as &mut dyn Any;
500+
501+
if let Some(e) = e.downcast_mut::<Option<anyhow::Error>>() {
502+
let e: Box<dyn StdError + Send + Sync> = e.take().unwrap().into();
503+
Report::from_boxed(e)
504+
} else {
505+
let e: Box<dyn StdError + Send + Sync> = value.take().unwrap().into().into();
506+
Report::from_boxed(e)
507+
}
498508
}
499509
}
500510

eyre/src/lib.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ use crate::backtrace::Backtrace;
381381
use crate::error::ErrorImpl;
382382
use core::fmt::{Debug, Display};
383383

384-
use std::error::Error as StdError;
384+
use std::{any::Any, error::Error as StdError};
385385

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

784785
Box::new(Self {
@@ -848,7 +849,10 @@ impl EyreHandler for DefaultHandler {
848849
let backtrace = self
849850
.backtrace
850851
.as_ref()
851-
.or_else(|| std::error::request_ref::<Backtrace>(error))
852+
.or_else(|| {
853+
eprintln!("Requesting backtrace from underlying type");
854+
std::error::request_ref::<std::backtrace::Backtrace>(error)
855+
})
852856
.expect("backtrace capture failed");
853857

854858
if let BacktraceStatus::Captured = backtrace.status() {

eyre/tests/test_anyhow.rs

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#![feature(error_generic_member_access)]
2+
3+
use eyre::Report;
4+
use std::fmt::Display;
5+
6+
#[derive(Debug)]
7+
struct RootError;
8+
9+
impl Display for RootError {
10+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11+
write!(f, "RootError")
12+
}
13+
}
14+
15+
impl std::error::Error for RootError {
16+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
17+
None
18+
}
19+
}
20+
21+
fn this_function_fails() -> anyhow::Result<()> {
22+
use anyhow::Context;
23+
24+
Err(RootError).context("Ouch!").context("Anyhow context A")
25+
}
26+
27+
fn test_failure() -> eyre::Result<()> {
28+
use anyhow::Context;
29+
this_function_fails().context("Anyhow context B")?;
30+
31+
Ok(())
32+
}
33+
34+
#[test]
35+
fn anyhow_conversion() {
36+
use eyre::WrapErr;
37+
let error: Report = test_failure().wrap_err("Eyre context").unwrap_err();
38+
39+
eprintln!("Error: {:?}", error);
40+
41+
let chain = error.chain().map(ToString::to_string).collect::<Vec<_>>();
42+
assert_eq!(
43+
chain,
44+
[
45+
"Eyre context",
46+
// Anyhow context
47+
"Anyhow context B",
48+
"Anyhow context A",
49+
// Anyhow error
50+
"Ouch!",
51+
// Original concrete error, shows up in chain too
52+
"RootError"
53+
]
54+
);
55+
56+
let backtrace = std::error::request_ref::<std::backtrace::Backtrace>(&*error).unwrap();
57+
dbg!(backtrace);
58+
}

0 commit comments

Comments
 (0)