feat: basic backtrace propagation

This commit is contained in:
Freja Roberts 2023-11-29 12:54:20 +01:00
parent 29aec58099
commit b52dba1323
3 changed files with 69 additions and 25 deletions

View File

@ -1,8 +1,9 @@
#[cfg(backtrace)]
pub(crate) use std::backtrace::Backtrace;
pub use std::backtrace::Backtrace;
#[cfg(not(backtrace))]
pub(crate) enum Backtrace {}
#[derive(Debug)]
pub enum Backtrace {}
#[cfg(backtrace)]
macro_rules! backtrace_if_absent {

View File

@ -1,4 +1,4 @@
use crate::Report;
use crate::{builder::ReportBuilder, Report};
/// Convert this result into an eyre [`Report`](crate::Report) result
///
@ -6,12 +6,7 @@ use crate::Report;
/// [`Error`](std::error::Error) is not yet available.
pub trait IntoEyre<T> {
/// Convert this result into an eyre [`Report`](crate::Report) result
fn into_eyre(self) -> crate::Result<T>
where
Self: Sized,
{
todo!()
}
fn into_eyre(self) -> crate::Result<T>;
}
/// See: [`IntoEyre`]
@ -19,25 +14,27 @@ pub trait IntoEyre<T> {
/// Error type automatically implements `into_eyre` for `Result<T, E>`
pub trait IntoEyreReport {
/// Convert this error into an eyre [`Report`](crate::Report)
fn into_eyre_report(self) -> Report
where
Self: Sized,
{
todo!()
}
#[track_caller]
fn into_eyre_report(self) -> Report;
}
impl<T, E> IntoEyre<T> for Result<T, E>
where
E: IntoEyreReport,
{
#[track_caller]
fn into_eyre(self) -> crate::Result<T> {
self.map_err(E::into_eyre_report)
// Use a manual match to keep backtrace
match self {
Ok(v) => Ok(v),
Err(err) => Err(err.into_eyre_report()),
}
}
}
#[cfg(feature = "anyhow-compat")]
impl IntoEyreReport for anyhow::Error {
#[track_caller]
fn into_eyre_report(self) -> Report
where
Self: Sized,
@ -60,7 +57,13 @@ impl IntoEyreReport for anyhow::Error {
.next()
.expect("Error chain contains at least one error");
let report = Report::msg(head.to_string());
#[cfg(backtrace)]
let report = ReportBuilder::default()
.with_backtrace(self.backtrace())
.msg(head.to_string());
#[cfg(not(backtrace))]
let report = ReportBuilder::default().msg(head.to_string());
// chai
// eprintln!("{:?}", chain.map(|v| v.to_string()).collect::<Vec<_>>());

View File

@ -1,24 +1,64 @@
use anyhow::Context;
use eyre::WrapErr;
use std::fmt::Display;
use eyre::{compat::IntoEyre, Report};
fn this_function_fails() -> anyhow::Result<()> {
anyhow::bail!("Ouch!")
#[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
}
}
#[cfg(feature = "anyhow-compat")]
fn this_function_fails() -> anyhow::Result<()> {
use anyhow::Context;
Err(RootError).context("Ouch!").context("Anyhow context A")
}
#[cfg(feature = "anyhow-compat")]
fn bubble() -> eyre::Result<()> {
use anyhow::Context;
use eyre::WrapErr;
this_function_fails()
.context("Anyhow::context")
.context("Anyhow context B")
.into_eyre()
.wrap_err("Eyre::wrap_err")?;
.wrap_err("Eyre context A")?;
Ok(())
}
#[cfg(feature = "anyhow-compat")]
#[test]
fn anyhow_conversion() {
let error: Report = bubble().unwrap_err();
// let error = Report::msg("A").wrap_err("B").wrap_err("C");
use eyre::WrapErr;
let error: Report = bubble().wrap_err("Eyre context B").unwrap_err();
eprintln!("{error:?}");
let chain = error.chain().map(ToString::to_string).collect::<Vec<_>>();
assert_eq!(
chain,
[
"Eyre context B",
"Eyre context A",
// Anyhow context
"Anyhow context B",
"Anyhow context A",
// Anyhow error
"Ouch!",
// Original concrete error, shows up in chain too
"RootError"
]
);
// let error = Report::msg("A").wrap_err("B").wrap_err("C");
}