diff --git a/examples/custom.rs b/examples/custom.rs new file mode 100644 index 0000000..62bde40 --- /dev/null +++ b/examples/custom.rs @@ -0,0 +1,55 @@ +extern crate failure; + +use std::fmt; + +use failure::{Fail, chain_display}; + +#[derive(Debug)] +struct InsideError; + +impl fmt::Display for InsideError { + fn fmt(&self, fmr: &mut fmt::Formatter) -> fmt::Result { + write!(fmr, "Inside Error") + } +} + +impl Fail for InsideError {} + +#[derive(Debug)] +struct OutsideError(InsideError); + +impl fmt::Display for OutsideError { + fn fmt(&self, fmr: &mut fmt::Formatter) -> fmt::Result { + write!(fmr, "Outside Error: {}", self.0) + } +} + +impl Fail for OutsideError { + fn cause(&self) -> Option<&Fail> { + Some(&self.0) + } +} + +fn bad_function() -> Result<(), failure::Error> { + Err(OutsideError(InsideError).into()) +} + +fn main() { + println!("——— line ({{}}) chain_display ———"); + if let Err(ref e) = bad_function() { + println!("{}", chain_display(e.as_fail())); + } + println!(); + + println!("——— block ({{:#}}) chain_display ———"); + if let Err(ref e) = bad_function() { + println!("{:#}", chain_display(e.as_fail())); + } + println!(); + + println!("——— block ({{:#?}}) (Debug) chain_display ———"); + if let Err(ref e) = bad_function() { + println!("{:#?}", chain_display(e.as_fail())); + } + println!(); +} diff --git a/examples/simple.rs b/examples/simple.rs index 35d25e1..1fe3ce1 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate failure; -use failure::Fail; +use failure::chain_display; #[derive(Debug, Fail)] #[fail(display = "my error")] @@ -16,7 +16,41 @@ fn bad_function() -> Result<(), WrappingError> { } fn main() { - for cause in Fail::iter_causes(&bad_function().unwrap_err()) { - println!("{}", cause); + println!("——— default fail(display = \"...\") ———"); + if let Err(ref e) = bad_function() { + println!("{}", e); + println!("{:#} (with {{:#}})", e); } + println!(); + + println!("——— default fmt::Debug ———"); + if let Err(ref e) = bad_function() { + println!("{:?} (with {{:?}}) ", e); + println!("{:#?} (with {{:#?}})", e); + } + println!(); + + println!("——— line ({{}}) chain_display ———"); + if let Err(ref e) = bad_function() { + println!("{}", chain_display(e)); + } + println!(); + + println!("——— block ({{:#}}) chain_display ———"); + if let Err(ref e) = bad_function() { + println!("{:#}", chain_display(e)); + } + println!(); + + println!("——— line ({{:?}}) fmt::Debug for chain_display ———"); + if let Err(ref e) = bad_function() { + println!("{:?}", chain_display(e)); + } + println!(); + + println!("——— block ({{:#?}}) fmt::Debug for chain_display ———"); + if let Err(ref e) = bad_function() { + println!("{:#?}", chain_display(e)); + } + println!(); } diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index 58f0477..17d264f 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -111,6 +111,11 @@ with_backtrace! { Backtrace { internal: InternalBacktrace::new() } } + /// Checks if the backtrace is empty. + pub fn is_empty(&self) -> bool { + self.internal.is_none() + } + pub(crate) fn none() -> Backtrace { Backtrace { internal: InternalBacktrace::none() } } diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..da78bc0 --- /dev/null +++ b/src/display.rs @@ -0,0 +1,86 @@ +use _core::fmt; + +use Fail; +use backtrace::Backtrace; + +/// A custom `Display` adapter for a `Fail` and an optional top-level +/// Backtrace. Displays the entire chain from the `Fail` through all causes. +/// Uses single line formatting with `{}` or multiple-lines incl. backtrace +/// via the (alternate) `{:#}` +pub struct ChainDisplay<'a>(pub(crate) &'a Fail, pub(crate) Option<&'a Backtrace>); + +impl<'a> fmt::Display for ChainDisplay<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut next = Some(self.0); + let mut idx = 0; + let mut was_backtrace = false; + + while let Some(fail) = next { + if was_backtrace { + writeln!(f)?; + } + was_backtrace = false; + if idx == 0 && f.alternate() { + write!(f, "error: {:#}", fail)?; + } else if idx == 0 { + write!(f, "error: {}", fail)?; + } else if f.alternate() { + write!(f, "\n caused by: {:#}", fail)?; + } else { + write!(f, "; caused by: {}", fail)?; + } + if f.alternate() { + let backtrace = if idx == 0 { + self.1.or_else(|| fail.backtrace()) + } else { + fail.backtrace() + }; + if let Some(backtrace) = backtrace { + write!(f, ", backtrace:\n{:#}", backtrace)?; + was_backtrace = true; + } + } + next = fail.cause(); + idx += 1; + } + Ok(()) + } +} + +impl<'a> fmt::Debug for ChainDisplay<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut next = Some(self.0); + let mut idx = 0; + let mut was_backtrace = false; + + while let Some(fail) = next { + if was_backtrace { + writeln!(f)?; + } + was_backtrace = false; + if idx == 0 && f.alternate() { + write!(f, "error: {:#?}", fail)?; + } else if idx == 0 { + write!(f, "error: {:?}", fail)?; + } else if f.alternate() { + write!(f, "\n caused by: {:#?}", fail)?; + } else { + write!(f, "; caused by: {:?}", fail)?; + } + if f.alternate() { + let backtrace = if idx == 0 { + self.1.or_else(|| fail.backtrace()) + } else { + fail.backtrace() + }; + if let Some(backtrace) = backtrace { + write!(f, ", backtrace:\n{:#?}", backtrace)?; + was_backtrace = true; + } + } + next = fail.cause(); + idx += 1; + } + Ok(()) + } +} diff --git a/src/error/mod.rs b/src/error/mod.rs index 04c0303..497f974 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -15,6 +15,7 @@ use self::error_impl::ErrorImpl; #[cfg(feature = "std")] use std::error::Error as StdError; +use display::ChainDisplay; /// The `Error` type, which can contain any failure. /// @@ -166,6 +167,11 @@ impl Error { self.imp.failure_mut().downcast_mut() } + /// Return a custom `Display` adapter which show the entire chain of causes. + pub fn chain_display(&self) -> ChainDisplay { + ChainDisplay(self.as_fail(), Some(self.backtrace())) + } + /// Deprecated alias to `find_root_cause`. #[deprecated(since = "0.1.2", note = "please use the 'find_root_cause()' method instead")] pub fn root_cause(&self) -> &Fail { diff --git a/src/lib.rs b/src/lib.rs index 1002e76..7d2e5a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,6 +39,7 @@ mod box_std; mod compat; mod context; mod result_ext; +mod display; use core::any::TypeId; use core::fmt::{Debug, Display}; @@ -47,6 +48,7 @@ pub use backtrace::Backtrace; pub use compat::Compat; pub use context::Context; pub use result_ext::ResultExt; +pub use display::ChainDisplay; #[cfg(feature = "failure_derive")] #[allow(unused_imports)] @@ -238,6 +240,11 @@ impl Fail { Causes { fail: Some(self) } } + /// Return a custom `Display` adapter which show the entire chain of causes. + pub fn chain_display(&self) -> ChainDisplay { + ChainDisplay(self, None) + } + /// Deprecated alias to `find_root_cause`. #[deprecated(since = "0.1.2", note = "please use the 'find_root_cause()' method instead")] pub fn root_cause(&self) -> &Fail { @@ -251,6 +258,14 @@ impl Fail { } } +/// Return a custom `Display` adapter which shows the entire chain from the +/// provided `Fail` and all causes. The adapter will use single line +/// formatting with `{}` or multiple-lines incl. backtrace via the (alternate) +/// `{:#}` +pub fn chain_display(fail: &Fail) -> ChainDisplay { + ChainDisplay(fail, None) +} + #[cfg(feature = "std")] impl Fail for E {}