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

Structural conversion from anyhow errors #127

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
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
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ rust-version = "1.65.0"
[workspace.dependencies]
indenter = "0.3.0"
once_cell = "1.18.0"
# For use with anyhow-compat
anyhow = { version = "1.0", features = ["backtrace"] }
3 changes: 3 additions & 0 deletions eyre/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ rust-version = { workspace = true }
default = ["auto-install", "track-caller"]
auto-install = []
track-caller = []
anyhow-compat = ["anyhow"]
anyhow-compat-backtrace = ["anyhow-compat", "anyhow/backtrace"]

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

[dev-dependencies]
futures = { version = "0.3", default-features = false }
Expand Down
4 changes: 3 additions & 1 deletion eyre/examples/custom_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ fn install() -> Result<(), impl Error> {

let hook = Hook { capture_backtrace };

eyre::set_hook(Box::new(move |e| Box::new(hook.make_handler(e))))
eyre::set_hook(Box::new(move |e: &(dyn Error + 'static)| {
Box::new(hook.make_handler(e)) as Box<dyn EyreHandler>
}))
}

struct Hook {
Expand Down
7 changes: 4 additions & 3 deletions eyre/src/backtrace.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
#[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 {
($err:expr) => {
match $err.backtrace() {
Some(_) => None,
None => Some(Backtrace::capture()),
None => Some(Backtrace::capture().into()),
}
};
}
Expand Down
146 changes: 146 additions & 0 deletions eyre/src/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use crate::{
error::{context_downcast, context_downcast_mut, context_drop_rest, ContextError},
vtable::{
object_boxed, object_downcast, object_downcast_mut, object_drop, object_drop_front,
object_mut, object_ref, ErrorVTable,
},
HandlerBacktraceCompat, HookParams, Report, StdError,
};
use std::fmt::Display;

#[derive(Debug, Default)]
/// Used for incrementally constructing reports
pub struct ReportBuilder {
params: HookParams,
}

impl ReportBuilder {
/// Creates a new report builder with default parameters
pub fn new() -> Self {
Self::default()
}

/// Use the given backtrace for the error
pub fn with_backtrace(mut self, backtrace: impl Into<HandlerBacktraceCompat>) -> Self {
self.params.backtrace = Some(backtrace.into());
self
}

#[cfg_attr(track_caller, track_caller)]
/// Creates a report from the given error message
pub fn from_msg<M>(self, message: M) -> Report
where
M: Display + std::fmt::Debug + Send + Sync + 'static,
{
use crate::wrapper::MessageError;
let error: MessageError<M> = MessageError(message);
let vtable = &ErrorVTable {
object_drop: object_drop::<MessageError<M>>,
object_ref: object_ref::<MessageError<M>>,
object_mut: object_mut::<MessageError<M>>,
object_boxed: object_boxed::<MessageError<M>>,
object_downcast: object_downcast::<M>,
object_downcast_mut: object_downcast_mut::<M>,
object_drop_rest: object_drop_front::<M>,
};

// Safety: MessageError is repr(transparent) so it is okay for the
// vtable to allow casting the MessageError<M> to M.
let handler = Some(crate::capture_handler(&error, self.params));

unsafe { Report::construct(error, vtable, handler) }
}

#[cfg_attr(track_caller, track_caller)]
/// Creates a report from the following error
pub fn from_stderr<E>(self, error: E) -> Report
where
E: StdError + Send + Sync + 'static,
{
let vtable = &ErrorVTable {
object_drop: object_drop::<E>,
object_ref: object_ref::<E>,
object_mut: object_mut::<E>,
object_boxed: object_boxed::<E>,
object_downcast: object_downcast::<E>,
object_downcast_mut: object_downcast_mut::<E>,
object_drop_rest: object_drop_front::<E>,
};

// Safety: passing vtable that operates on the right type E.
let handler = Some(crate::capture_handler(&error, self.params));

unsafe { Report::construct(error, vtable, handler) }
}

#[cfg_attr(track_caller, track_caller)]
/// Creates a report from the following boxed error
pub fn from_boxed(self, error: Box<dyn StdError + Send + Sync>) -> Report {
use crate::wrapper::BoxedError;
let error = BoxedError(error);
let handler = Some(crate::capture_handler(&error, self.params));

let vtable = &ErrorVTable {
object_drop: object_drop::<BoxedError>,
object_ref: object_ref::<BoxedError>,
object_mut: object_mut::<BoxedError>,
object_boxed: object_boxed::<BoxedError>,
object_downcast: object_downcast::<Box<dyn StdError + Send + Sync>>,
object_downcast_mut: object_downcast_mut::<Box<dyn StdError + Send + Sync>>,
object_drop_rest: object_drop_front::<Box<dyn StdError + Send + Sync>>,
};

// Safety: BoxedError is repr(transparent) so it is okay for the vtable
// to allow casting to Box<dyn StdError + Send + Sync>.
unsafe { Report::construct(error, vtable, handler) }
}

#[cfg_attr(track_caller, track_caller)]
/// Wraps a source error with a message
pub fn wrap_with_msg<D, E>(self, msg: D, error: E) -> Report
where
D: Display + Send + Sync + 'static,
E: StdError + Send + Sync + 'static,
{
let error: ContextError<D, E> = ContextError { msg, error };

let vtable = &ErrorVTable {
object_drop: object_drop::<ContextError<D, E>>,
object_ref: object_ref::<ContextError<D, E>>,
object_mut: object_mut::<ContextError<D, E>>,
object_boxed: object_boxed::<ContextError<D, E>>,
object_downcast: context_downcast::<D, E>,
object_downcast_mut: context_downcast_mut::<D, E>,
object_drop_rest: context_drop_rest::<D, E>,
};

// Safety: passing vtable that operates on the right type.
let handler = Some(crate::capture_handler(&error, self.params));

unsafe { Report::construct(error, vtable, handler) }
}

#[cfg_attr(track_caller, track_caller)]
pub(crate) fn from_display<M>(self, message: M) -> Report
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>>,
object_ref: object_ref::<DisplayError<M>>,
object_mut: object_mut::<DisplayError<M>>,
object_boxed: object_boxed::<DisplayError<M>>,
object_downcast: object_downcast::<M>,
object_downcast_mut: object_downcast_mut::<M>,
object_drop_rest: object_drop_front::<M>,
};

// Safety: DisplayError is repr(transparent) so it is okay for the
// vtable to allow casting the DisplayError<M> to M.
let handler = Some(crate::capture_handler(&NoneError, Default::default()));

unsafe { Report::construct(error, vtable, handler) }
}
}
48 changes: 48 additions & 0 deletions eyre/src/compat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use crate::{builder::ReportBuilder, Report};

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

GitHub Actions / Check (stable)

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

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

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

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

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

GitHub Actions / Test Suite (beta)

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

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

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

GitHub Actions / Test Suite (stable, --features auto-install)

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

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

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

GitHub Actions / Test Suite (stable)

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

GitHub Actions / Test Suite (nightly)

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

GitHub Actions / Test Suite (nightly)

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

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

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

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

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

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

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

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

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

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

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

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

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

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

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

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

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

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

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

GitHub Actions / Test Suite (stable, --features track-caller)

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

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

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

GitHub Actions / Miri

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

GitHub Actions / Miri

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

GitHub Actions / Test Suite (ubuntu-latest)

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

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

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

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

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

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

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

GitHub Actions / Test Suite

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

GitHub Actions / Test Suite (macOS-latest)

unused import: `builder::ReportBuilder`

Check warning on line 1 in eyre/src/compat.rs

View workflow job for this annotation

GitHub Actions / Test Suite (windows-latest)

unused import: `builder::ReportBuilder`

/// Convert this result into an eyre [`Report`](crate::Report) result
///
/// This trait can also be used to provide conversions to eyre in `no-std` environments where
/// [`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>;
}

/// See: [`IntoEyre`]
/// This is for crate authors to implement on their custom error types. Implementing this for your
/// Error type automatically implements `into_eyre` for `Result<T, E>`
pub trait IntoEyreReport {
/// Convert this error into an eyre [`Report`](crate::Report)
#[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> {
// 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,
{
let report = ReportBuilder::default()
.with_backtrace(self.backtrace())
.from_boxed(self.into());

report
}
}
3 changes: 2 additions & 1 deletion eyre/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::error::{ContextError, ErrorImpl};
use crate::error::ContextError;
use crate::vtable::ErrorImpl;
use crate::{ContextCompat, Report, StdError, WrapErr};
use core::fmt::{self, Debug, Display, Write};

Expand Down
Loading
Loading