Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ rustversion = "1.0"
thiserror = "1.0"
trybuild = { version = "1.0.19", features = ["diff"] }

[dependencies]
once_cell = "1.4.0"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
rustdoc-args = ["--cfg", "doc_cfg"]
21 changes: 21 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,13 @@ impl Error {
where
E: StdError + Send + Sync + 'static,
{
let handler =
crate::HOOK.get_or_init(|| Box::new(|_| Box::new(crate::DefaultHandler)))(&error);

let inner = Box::new(ErrorImpl {
vtable,
backtrace,
handler,
_object: error,
});
// Erase the concrete type of E from the compile-time type system. This
Expand Down Expand Up @@ -310,6 +314,14 @@ impl Error {
self.inner.backtrace()
}

pub fn handler(&self) -> &dyn crate::ReportHandler {
self.inner.handler()
}

pub fn handler_mut(&mut self) -> &mut dyn crate::ReportHandler {
self.inner.handler_mut()
}

/// An iterator of the chain of source errors contained by this Error.
///
/// This iterator will visit every error in the cause chain of this error
Expand Down Expand Up @@ -681,6 +693,7 @@ where
pub(crate) struct ErrorImpl<E> {
vtable: &'static ErrorVTable,
backtrace: Option<Backtrace>,
handler: Box<dyn crate::ReportHandler + Send + Sync + 'static>,
// NOTE: Don't use directly. Use only through vtable. Erased type may have
// different alignment.
_object: E,
Expand Down Expand Up @@ -728,6 +741,14 @@ impl ErrorImpl<()> {
.expect("backtrace capture failed")
}

pub(crate) fn handler(&self) -> &dyn crate::ReportHandler {
self.handler.as_ref()
}

pub(crate) fn handler_mut(&mut self) -> &mut dyn crate::ReportHandler {
self.handler.as_mut()
}

pub(crate) fn chain(&self) -> Chain {
Chain::new(self.error())
}
Expand Down
15 changes: 13 additions & 2 deletions src/fmt.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::chain::Chain;
use crate::error::ErrorImpl;
use core::fmt::{self, Debug, Write};
use core::fmt::{self, Write};

impl ErrorImpl<()> {
pub(crate) fn display(&self, f: &mut fmt::Formatter) -> fmt::Result {
Expand All @@ -17,9 +17,20 @@ impl ErrorImpl<()> {

pub(crate) fn debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
let error = self.error();
let handler = self.handler();

handler.report(error, f)
}
}

impl crate::ReportHandler for crate::DefaultHandler {
fn report(
&self,
error: &(dyn crate::StdError + 'static),
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
if f.alternate() {
return Debug::fmt(error, f);
return core::fmt::Debug::fmt(error, f);
}

write!(f, "{}", error)?;
Expand Down
25 changes: 25 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ trait StdError: Debug + Display {
}

pub use anyhow as format_err;
use once_cell::sync::OnceCell;

/// The `Error` type, a wrapper around a dynamic error type.
///
Expand Down Expand Up @@ -584,6 +585,30 @@ pub trait Context<T, E>: context::private::Sealed {
F: FnOnce() -> C;
}

static HOOK: OnceCell<ErrorHook> = OnceCell::new();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

lmk if you want me to move this all to a handler.rs file or something


pub trait ReportHandler: core::any::Any {
fn report(
&self,
error: &(dyn StdError + 'static),
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result;
}

type ErrorHook = Box<
dyn Fn(&(dyn StdError + 'static)) -> Box<dyn ReportHandler + Send + Sync + 'static>
+ Sync
+ Send
+ 'static,
>;

pub fn set_hook(hook: ErrorHook) -> Result<(), Box<dyn StdError + Send + Sync + 'static>> {
HOOK.set(hook)
.map_err(|_| "unable to set global hook".into())
}

struct DefaultHandler;

// Not public API. Referenced by macro-generated code.
#[doc(hidden)]
pub mod private {
Expand Down
44 changes: 44 additions & 0 deletions tests/test_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
struct CustomHandler {
msg: &'static str,
}

impl anyhow::ReportHandler for CustomHandler {
fn report(
&self,
_error: &(dyn std::error::Error + 'static),
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
write!(f, "{}", self.msg)
}
}

#[test]
fn test_custom_hook() {
let expected = "hook is set!";
anyhow::set_hook(Box::new(move |_error| {
Box::new(CustomHandler { msg: expected })
}))
.unwrap();

let report = anyhow::anyhow!("heres the message!");
let actual = format!("{:?}", report);

assert_eq!(expected, actual);
}

#[test]
fn test_mutable_hook() {
let fake_expected = "hook is set!";
let real_expected = "the context was modified!";

anyhow::set_hook(Box::new(move |_error| {
Box::new(CustomHandler { msg: fake_expected })
}))
.unwrap();

let report = anyhow::anyhow!("heres the message!");
let handler: &mut dyn core::any::Any = report.handler_mut() as _;
let actual = format!("{:?}", report);

assert_eq!(real_expected, actual);
}
1 change: 1 addition & 0 deletions tests/test_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ fn test_messages() {
}

#[test]
#[allow(clippy::eq_op)]
fn test_ensure() {
let f = || {
ensure!(1 + 1 == 2, "This is correct");
Expand Down