From 5d5e18ce5482c57654ef5bb8fb4ffc3080e38741 Mon Sep 17 00:00:00 2001 From: Tei Leelo Roberts Date: Fri, 21 Jun 2024 19:58:28 +0200 Subject: [PATCH] feat: convert between anyhow::Error and eyre::Result --- Cargo.lock | 1 + Cargo.toml | 1 + eyre/Cargo.toml | 6 ++-- eyre/src/backtrace.rs | 15 ++++++++-- eyre/src/context.rs | 5 ++++ eyre/src/error.rs | 18 +++++++++--- eyre/src/lib.rs | 8 ++++-- eyre/tests/test_anyhow.rs | 58 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 eyre/tests/test_anyhow.rs diff --git a/Cargo.lock b/Cargo.lock index c98d750..70e678f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -184,6 +184,7 @@ name = "eyre" version = "1.0.0" dependencies = [ "anyhow", + "autocfg", "backtrace", "futures", "indenter", diff --git a/Cargo.toml b/Cargo.toml index c1651b2..68ac32e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ indenter = "0.3.0" once_cell = "1.18.0" owo-colors = "4.0" autocfg = "1.0" +anyhow = "1.0" [profile.dev.package.backtrace] opt-level = 3 diff --git a/eyre/Cargo.toml b/eyre/Cargo.toml index 3c779ef..c69e6e7 100644 --- a/eyre/Cargo.toml +++ b/eyre/Cargo.toml @@ -13,8 +13,7 @@ readme = { workspace = true } rust-version = { workspace = true } [features] -default = ["anyhow", "auto-install", "track-caller"] -anyhow = [] +default = [ "auto-install", "track-caller"] auto-install = [] track-caller = [] @@ -22,17 +21,18 @@ track-caller = [] 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"] } diff --git a/eyre/src/backtrace.rs b/eyre/src/backtrace.rs index b1b378b..bcf0338 100644 --- a/eyre/src/backtrace.rs +++ b/eyre/src/backtrace.rs @@ -17,13 +17,23 @@ 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::($err as &dyn std::error::Error) { - Some(_) => None, - None => capture_backtrace!(), + Some(v) => { + eprintln!( + "BAcktrace present: {v:?} {:?}", + ($err as &dyn std::error::Error).type_id() + ); + None + } + None => { + eprintln!("No backtrace"); + capture_backtrace!() + } } }; } @@ -31,6 +41,7 @@ macro_rules! backtrace_if_absent { #[cfg(not(generic_member_access))] macro_rules! backtrace_if_absent { ($err:expr) => { + eprintln!("capturing stable backtrace"); capture_backtrace!() }; } diff --git a/eyre/src/context.rs b/eyre/src/context.rs index debaab0..d4ad7aa 100644 --- a/eyre/src/context.rs +++ b/eyre/src/context.rs @@ -150,6 +150,11 @@ impl StdError for ContextError 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())) } diff --git a/eyre/src/error.rs b/eyre/src/error.rs index 511b4c4..93eb2a0 100644 --- a/eyre/src/error.rs +++ b/eyre/src/error.rs @@ -8,6 +8,7 @@ use core::mem::{self, ManuallyDrop}; use core::ptr::{self, NonNull}; use core::ops::{Deref, DerefMut}; +use std::any::Any; impl Report { /// Create a new error object from any error type. @@ -490,11 +491,20 @@ impl Report { impl From for Report where - E: StdError + Send + Sync + 'static, + E: 'static + Into, + Result<(), E>: anyhow::Context<(), E>, { - #[cfg_attr(track_caller, track_caller)] - fn from(error: E) -> Self { - Report::from_std(error) + 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::>() { + let e: Box = e.take().unwrap().into(); + Report::from_boxed(e) + } else { + let e: Box = value.take().unwrap().into().into(); + Report::from_boxed(e) + } } } diff --git a/eyre/src/lib.rs b/eyre/src/lib.rs index 5fafac3..9b9dab2 100644 --- a/eyre/src/lib.rs +++ b/eyre/src/lib.rs @@ -381,7 +381,7 @@ use crate::backtrace::Backtrace; use crate::error::ErrorImpl; use core::fmt::{Debug, Display}; -use std::error::Error as StdError; +use std::{any::Any, error::Error as StdError}; pub use eyre as format_err; /// Compatibility re-export of `eyre` for interop with `anyhow` @@ -779,6 +779,7 @@ impl DefaultHandler { #[cfg_attr(not(feature = "auto-install"), allow(dead_code))] pub fn default_with(error: &(dyn StdError + 'static)) -> Box { // Capture the backtrace if the source error did not already capture one + eprintln!("checking backtrace"); let backtrace = backtrace_if_absent!(error); Box::new(Self { @@ -848,7 +849,10 @@ impl EyreHandler for DefaultHandler { let backtrace = self .backtrace .as_ref() - .or_else(|| std::error::request_ref::(error)) + .or_else(|| { + eprintln!("Requesting backtrace from underlying type"); + std::error::request_ref::(error) + }) .expect("backtrace capture failed"); if let BacktraceStatus::Captured = backtrace.status() { diff --git a/eyre/tests/test_anyhow.rs b/eyre/tests/test_anyhow.rs new file mode 100644 index 0000000..d88ded9 --- /dev/null +++ b/eyre/tests/test_anyhow.rs @@ -0,0 +1,58 @@ +#![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")?; + + 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::>(); + 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::(&*error).unwrap(); + dbg!(backtrace); +}