From f8788c8d5d2e9475dbc41ae425e578c8b09f51d7 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 28 Jul 2018 16:55:48 +0200 Subject: [PATCH 1/7] Added support for rendering errors with causes Also: Remove std dependency --- examples/simple.rs | 4 +--- src/backtrace/mod.rs | 5 +++++ src/display.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ src/error/mod.rs | 7 +++++++ src/lib.rs | 7 +++++++ 5 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 src/display.rs diff --git a/examples/simple.rs b/examples/simple.rs index 35d25e1..e25ba8e 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -16,7 +16,5 @@ fn bad_function() -> Result<(), WrappingError> { } fn main() { - for cause in Fail::iter_causes(&bad_function().unwrap_err()) { - println!("{}", cause); - } + println!("{}", Fail::display(&bad_function().unwrap_err())); } 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..e0f9037 --- /dev/null +++ b/src/display.rs @@ -0,0 +1,43 @@ +use _core::fmt; + +use Fail; +use backtrace::Backtrace; + + +/// Renders a fail with all causes. +pub struct FailDisplay<'a>(pub(crate) &'a Fail, pub(crate) Option<&'a Backtrace>); + +impl<'a> fmt::Display for FailDisplay<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut ptr = Some(self.0); + let mut idx = 0; + let mut was_backtrace = false; + + while let Some(fail) = ptr { + if was_backtrace { + write!(f, "\n")?; + } + was_backtrace = false; + if idx == 0 { + write!(f, "error: {}", fail)?; + } else { + write!(f, "\n caused by: {}", fail)?; + } + if f.alternate() { + let backtrace = if idx == 0 && self.1.is_some() { + Some(self.1.unwrap()) + } else { + fail.backtrace() + }; + if let Some(backtrace) = backtrace { + write!(f, "\nbacktrace:\n{}", backtrace)?; + was_backtrace = true; + } + } + ptr = fail.cause(); + idx += 1; + } + + Ok(()) + } +} diff --git a/src/error/mod.rs b/src/error/mod.rs index 04c0303..3032d91 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -15,6 +15,8 @@ use self::error_impl::ErrorImpl; #[cfg(feature = "std")] use std::error::Error as StdError; +use display::FailDisplay; + /// The `Error` type, which can contain any failure. /// @@ -166,6 +168,11 @@ impl Error { self.imp.failure_mut().downcast_mut() } + /// Displays the error. + pub fn display(&self) -> FailDisplay { + FailDisplay(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..4fb5fe6 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::FailDisplay; #[cfg(feature = "failure_derive")] #[allow(unused_imports)] @@ -238,6 +240,11 @@ impl Fail { Causes { fail: Some(self) } } + /// Displays the failure. + pub fn display(&self) -> FailDisplay { + FailDisplay(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 { From c207e594860d949db558a0d6a8116f67a815e751 Mon Sep 17 00:00:00 2001 From: David Kellum Date: Wed, 15 Aug 2018 11:14:04 -0700 Subject: [PATCH 2/7] Call it ChainDisplay, via chain_display --- examples/simple.rs | 21 +++++++++++++++++++-- src/display.rs | 35 +++++++++++++++++++++-------------- src/error/mod.rs | 9 ++++----- src/lib.rs | 16 ++++++++++++---- 4 files changed, 56 insertions(+), 25 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index e25ba8e..a3cd396 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,5 +16,22 @@ fn bad_function() -> Result<(), WrappingError> { } fn main() { - println!("{}", Fail::display(&bad_function().unwrap_err())); + println!("### default fail(display = \"...\") ###"); + if let Err(ref e) = bad_function() { + println!("{}", 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!(); } diff --git a/src/display.rs b/src/display.rs index e0f9037..429ae7c 100644 --- a/src/display.rs +++ b/src/display.rs @@ -3,41 +3,48 @@ 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>); -/// Renders a fail with all causes. -pub struct FailDisplay<'a>(pub(crate) &'a Fail, pub(crate) Option<&'a Backtrace>); - -impl<'a> fmt::Display for FailDisplay<'a> { +impl<'a> fmt::Display for ChainDisplay<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut ptr = Some(self.0); + let mut next = Some(self.0); let mut idx = 0; let mut was_backtrace = false; - while let Some(fail) = ptr { + while let Some(fail) = next { if was_backtrace { - write!(f, "\n")?; + writeln!(f)?; } was_backtrace = false; if idx == 0 { - write!(f, "error: {}", fail)?; + if f.alternate() { + write!(f, "error: {:#}", fail)?; + } else { + write!(f, "error: {}", fail)?; + } + } else if f.alternate() { + write!(f, "\n caused by: {:#}", fail)?; } else { - write!(f, "\n caused by: {}", fail)?; + write!(f, "; caused by: {}", fail)?; } if f.alternate() { - let backtrace = if idx == 0 && self.1.is_some() { - Some(self.1.unwrap()) + let backtrace = if idx == 0 { + self.1.or_else(|| fail.backtrace()) } else { fail.backtrace() }; if let Some(backtrace) = backtrace { - write!(f, "\nbacktrace:\n{}", backtrace)?; + write!(f, ", backtrace:\n{:#}", backtrace)?; was_backtrace = true; } } - ptr = fail.cause(); + next = fail.cause(); idx += 1; } - Ok(()) } } diff --git a/src/error/mod.rs b/src/error/mod.rs index 3032d91..497f974 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -15,8 +15,7 @@ use self::error_impl::ErrorImpl; #[cfg(feature = "std")] use std::error::Error as StdError; -use display::FailDisplay; - +use display::ChainDisplay; /// The `Error` type, which can contain any failure. /// @@ -168,9 +167,9 @@ impl Error { self.imp.failure_mut().downcast_mut() } - /// Displays the error. - pub fn display(&self) -> FailDisplay { - FailDisplay(self.as_fail(), Some(self.backtrace())) + /// 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`. diff --git a/src/lib.rs b/src/lib.rs index 4fb5fe6..7d2e5a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,7 @@ pub use backtrace::Backtrace; pub use compat::Compat; pub use context::Context; pub use result_ext::ResultExt; -pub use display::FailDisplay; +pub use display::ChainDisplay; #[cfg(feature = "failure_derive")] #[allow(unused_imports)] @@ -240,9 +240,9 @@ impl Fail { Causes { fail: Some(self) } } - /// Displays the failure. - pub fn display(&self) -> FailDisplay { - FailDisplay(self, None) + /// 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`. @@ -258,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 {} From 894111ef0a7e113aadbb790fcab0f5710cd2287c Mon Sep 17 00:00:00 2001 From: David Kellum Date: Wed, 15 Aug 2018 11:15:01 -0700 Subject: [PATCH 3/7] Add a custom Fail impl example --- examples/custom.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 examples/custom.rs diff --git a/examples/custom.rs b/examples/custom.rs new file mode 100644 index 0000000..15d9682 --- /dev/null +++ b/examples/custom.rs @@ -0,0 +1,48 @@ +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<(), OutsideError> { + Err(OutsideError(InsideError)) +} + +fn main() { + 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!(); +} From a3e1abfd7cc0484741ce10bea48fc13e7142a0b4 Mon Sep 17 00:00:00 2001 From: David Kellum Date: Thu, 16 Aug 2018 10:46:24 -0700 Subject: [PATCH 4/7] Refactor display conditions --- src/display.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/display.rs b/src/display.rs index 429ae7c..9d222c4 100644 --- a/src/display.rs +++ b/src/display.rs @@ -20,12 +20,10 @@ impl<'a> fmt::Display for ChainDisplay<'a> { writeln!(f)?; } was_backtrace = false; - if idx == 0 { - if f.alternate() { - write!(f, "error: {:#}", fail)?; - } else { - write!(f, "error: {}", fail)?; - } + 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 { From 016c50c7b923c35fc3c2fdec0627a23eeb6a41fb Mon Sep 17 00:00:00 2001 From: David Kellum Date: Wed, 15 Aug 2018 13:53:00 -0700 Subject: [PATCH 5/7] Add failure::Error to mix in custom example --- examples/custom.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/custom.rs b/examples/custom.rs index 15d9682..de2091d 100644 --- a/examples/custom.rs +++ b/examples/custom.rs @@ -1,6 +1,7 @@ extern crate failure; use std::fmt; + use failure::{Fail, chain_display}; #[derive(Debug)] @@ -29,20 +30,20 @@ impl Fail for OutsideError { } } -fn bad_function() -> Result<(), OutsideError> { - Err(OutsideError(InsideError)) +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)); + println!("{}", chain_display(e.as_fail())); } println!(); println!("### block ({{:#}}) chain_display ###"); if let Err(ref e) = bad_function() { - println!("{:#}", chain_display(e)); + println!("{:#}", chain_display(e.as_fail())); } println!(); } From 37dafed55f8fcc30bd53d6f7a4ce334ae6b5736e Mon Sep 17 00:00:00 2001 From: David Kellum Date: Thu, 16 Aug 2018 10:59:11 -0700 Subject: [PATCH 6/7] impl Debug for ChainDisplay as well --- examples/custom.rs | 6 ++++++ examples/simple.rs | 19 +++++++++++++++++++ src/display.rs | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/examples/custom.rs b/examples/custom.rs index de2091d..4c576e8 100644 --- a/examples/custom.rs +++ b/examples/custom.rs @@ -46,4 +46,10 @@ fn main() { 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 a3cd396..0804e4b 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -23,6 +23,13 @@ fn main() { } 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)); @@ -34,4 +41,16 @@ fn main() { 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/display.rs b/src/display.rs index 9d222c4..da78bc0 100644 --- a/src/display.rs +++ b/src/display.rs @@ -46,3 +46,41 @@ impl<'a> fmt::Display for ChainDisplay<'a> { 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(()) + } +} From 3feec026732d5e91b533956cca4936c662bba6eb Mon Sep 17 00:00:00 2001 From: David Kellum Date: Thu, 16 Aug 2018 11:11:46 -0700 Subject: [PATCH 7/7] Improve sample output formatting (with U+2014 EM DASH) --- examples/custom.rs | 6 +++--- examples/simple.rs | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/custom.rs b/examples/custom.rs index 4c576e8..62bde40 100644 --- a/examples/custom.rs +++ b/examples/custom.rs @@ -35,19 +35,19 @@ fn bad_function() -> Result<(), failure::Error> { } fn main() { - println!("### line ({{}}) chain_display ###"); + println!("——— line ({{}}) chain_display ———"); if let Err(ref e) = bad_function() { println!("{}", chain_display(e.as_fail())); } println!(); - println!("### block ({{:#}}) chain_display ###"); + println!("——— block ({{:#}}) chain_display ———"); if let Err(ref e) = bad_function() { println!("{:#}", chain_display(e.as_fail())); } println!(); - println!("### block ({{:#?}}) (Debug) chain_display ###"); + println!("——— block ({{:#?}}) (Debug) chain_display ———"); if let Err(ref e) = bad_function() { println!("{:#?}", chain_display(e.as_fail())); } diff --git a/examples/simple.rs b/examples/simple.rs index 0804e4b..1fe3ce1 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -16,39 +16,39 @@ fn bad_function() -> Result<(), WrappingError> { } fn main() { - println!("### default fail(display = \"...\") ###"); + println!("——— default fail(display = \"...\") ———"); if let Err(ref e) = bad_function() { println!("{}", e); println!("{:#} (with {{:#}})", e); } println!(); - println!("### default fmt::Debug ###"); + println!("——— default fmt::Debug ———"); if let Err(ref e) = bad_function() { println!("{:?} (with {{:?}}) ", e); println!("{:#?} (with {{:#?}})", e); } println!(); - println!("### line ({{}}) chain_display ###"); + println!("——— line ({{}}) chain_display ———"); if let Err(ref e) = bad_function() { println!("{}", chain_display(e)); } println!(); - println!("### block ({{:#}}) chain_display ###"); + println!("——— block ({{:#}}) chain_display ———"); if let Err(ref e) = bad_function() { println!("{:#}", chain_display(e)); } println!(); - println!("### line ({{:?}}) fmt::Debug for chain_display ###"); + 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 ###"); + println!("——— block ({{:#?}}) fmt::Debug for chain_display ———"); if let Err(ref e) = bad_function() { println!("{:#?}", chain_display(e)); }