/*! `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your binaries, you still have the error backtrace. `chainerror` has no dependencies! `chainerror` uses `.source()` of `std::error::Error` along with `line()!` and `file()!` to provide a nice debug error backtrace. It encapsulates all types, which have `Display + Debug` and can store the error cause internally. Along with the `ChainError` struct, `chainerror` comes with some useful helper macros to save a lot of typing. # Examples ~~~rust use chainerror::*; use std::error::Error; use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } fn func2() -> Result<(), Box> { let filename = "foo.txt"; do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; Ok(()) } fn func1() -> Result<(), Box> { func2().map_err(mstrerr!("func1 error"))?; Ok(()) } fn main() { if let Err(e) = func1() { assert_eq!( format!("\n{:?}\n", e), r#" src/lib.rs:20: func1 error Caused by: src/lib.rs:15: Error reading 'foo.txt' Caused by: Kind(NotFound) "# ); } # else { # unreachable!(); # } } ~~~ ~~~rust use chainerror::*; use std::error::Error; use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } fn func3() -> Result<(), Box> { let filename = "foo.txt"; do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; Ok(()) } derive_str_cherr!(Func2Error); fn func2() -> ChainResult<(), Func2Error> { func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; Ok(()) } enum Func1Error { Func2, IO(String), } impl ::std::fmt::Display for Func1Error { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { match self { Func1Error::Func2 => write!(f, "func1 error calling func2"), Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), } } } impl ::std::fmt::Debug for Func1Error { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "{}", self) } } fn func1() -> ChainResult<(), Func1Error> { func2().map_err(|e| cherr!(e, Func1Error::Func2))?; let filename = String::from("bar.txt"); do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; Ok(()) } fn main() { if let Err(e) = func1() { assert!( match e.kind() { Func1Error::Func2 => { eprintln!("Main Error Report: func1 error calling func2"); true } Func1Error::IO(filename) => { eprintln!("Main Error Report: func1 error reading '{}'", filename); false } } ); assert!(e.find_chain_cause::().is_some()); if let Some(e) = e.find_chain_cause::() { eprintln!("\nError reported by Func2Error: {}", e) } assert!(e.root_cause().is_some()); if let Some(e) = e.root_cause() { let ioerror = e.downcast_ref::().unwrap(); eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); } assert_eq!( format!("\n{:?}\n", e), r#" src/lib.rs:47: func1 error calling func2 Caused by: src/lib.rs:22: Func2Error(func2 error: calling func3) Caused by: src/lib.rs:15: Error reading 'foo.txt' Caused by: Kind(NotFound) "# ); } # else { # unreachable!(); # } } ~~~ !*/ use std::any::TypeId; use std::error::Error; use std::fmt::{Debug, Display, Formatter, Result}; /** chains an inner error kind `T` with a causing error **/ pub struct ChainError { #[cfg(not(feature = "no-fileline"))] occurrence: Option<(u32, &'static str)>, kind: T, error_cause: Option>, } /// convenience type alias pub type ChainResult = std::result::Result>; impl ChainError { #[cfg(not(feature = "no-fileline"))] /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly pub fn new( kind: T, error_cause: Option>, occurrence: Option<(u32, &'static str)>, ) -> Self { Self { occurrence, kind, error_cause, } } #[cfg(feature = "no-fileline")] /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly pub fn new( kind: T, error_cause: Option>, _occurrence: Option<(u32, &'static str)>, ) -> Self { Self { kind, error_cause } } /// return the root cause of the error chain, if any exists pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> { let mut cause = self as &(dyn Error + 'static); while let Some(c) = cause.source() { cause = c; } Some(cause) } /** find the first error cause of type U, if any exists # Examples ~~~rust # use crate::chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } derive_str_cherr!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; Ok(()) } derive_str_cherr!(Func1Error); fn func1() -> Result<(), Box> { func2().map_err(mstrerr!(Func1Error, "func1 error"))?; Ok(()) } fn main() { if let Err(e) = func1() { if let Some(f1err) = e.downcast_chain_ref::() { assert!(f1err.find_cause::().is_some()); assert!(f1err.find_chain_cause::().is_some()); } # else { # panic!(); # } } # else { # unreachable!(); # } } ~~~ **/ pub fn find_cause(&self) -> Option<&U> { let mut cause = self as &(dyn Error + 'static); loop { if cause.is::() { return cause.downcast_ref::(); } match cause.source() { Some(c) => cause = c, None => return None, } } } /** find the first error cause of type ChainError, if any exists Same as `find_cause`, but hides the `ChainError` implementation internals # Examples ~~~rust,ignore /// Instead of writing err.find_cause::>(); /// leave out the ChainError implementation detail err.find_chain_cause::(); ~~~ **/ pub fn find_chain_cause(&self) -> Option<&ChainError> { let mut cause = self as &(dyn Error + 'static); loop { if cause.is::>() { return cause.downcast_ref::>(); } match cause.source() { Some(c) => cause = c, None => return None, } } } /** return a reference to T of `ChainError` # Examples ~~~rust # use crate::chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } derive_str_cherr!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; Ok(()) } #[derive(Debug)] enum Func1ErrorKind { Func2, IO(String), } // impl ::std::fmt::Display for Func1ErrorKind {…} # impl ::std::fmt::Display for Func1ErrorKind { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # match self { # Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), # Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), # } # } # } fn func1() -> ChainResult<(), Func1ErrorKind> { func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO("bar.txt".into())))?; Ok(()) } fn main() { if let Err(e) = func1() { match e.kind() { Func1ErrorKind::Func2 => {}, Func1ErrorKind::IO(filename) => panic!(), } } # else { # unreachable!(); # } } ~~~ **/ pub fn kind<'a>(&'a self) -> &'a T { &self.kind } } /** convenience trait to hide the `ChainError` implementation internals **/ pub trait ChainErrorDown { /** test if of type `ChainError` **/ fn is_chain(&self) -> bool; /** downcast to a reference of `ChainError` **/ fn downcast_chain_ref(&self) -> Option<&ChainError>; /** downcast to a mutable reference of `ChainError` **/ fn downcast_chain_mut(&mut self) -> Option<&mut ChainError>; } impl ChainErrorDown for ChainError { fn is_chain(&self) -> bool { TypeId::of::() == TypeId::of::() } fn downcast_chain_ref(&self) -> Option<&ChainError> { if self.is_chain::() { unsafe { Some(&*(self as *const dyn Error as *const &ChainError)) } } else { None } } fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { if self.is_chain::() { unsafe { Some(&mut *(self as *mut dyn Error as *mut &mut ChainError)) } } else { None } } } impl ChainErrorDown for dyn Error + 'static { fn is_chain(&self) -> bool { self.is::>() } fn downcast_chain_ref(&self) -> Option<&ChainError> { self.downcast_ref::>() } fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { self.downcast_mut::>() } } impl ChainErrorDown for dyn Error + 'static + Send { fn is_chain(&self) -> bool { self.is::>() } fn downcast_chain_ref(&self) -> Option<&ChainError> { self.downcast_ref::>() } fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { self.downcast_mut::>() } } impl ChainErrorDown for dyn Error + 'static + Send + Sync { fn is_chain(&self) -> bool { self.is::>() } fn downcast_chain_ref(&self) -> Option<&ChainError> { self.downcast_ref::>() } fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { self.downcast_mut::>() } } impl Error for ChainError { fn source(&self) -> Option<&(dyn Error + 'static)> { if let Some(ref e) = self.error_cause { Some(e.as_ref()) } else { None } } } impl Error for &ChainError { fn source(&self) -> Option<&(dyn Error + 'static)> { if let Some(ref e) = self.error_cause { Some(e.as_ref()) } else { None } } } impl Error for &mut ChainError { fn source(&self) -> Option<&(dyn Error + 'static)> { if let Some(ref e) = self.error_cause { Some(e.as_ref()) } else { None } } } impl Display for ChainError { fn fmt(&self, f: &mut Formatter) -> Result { write!(f, "{}", self.kind)?; #[cfg(feature = "display-cause")] { if let Some(e) = self.source() { writeln!(f, "\nCaused by:")?; Display::fmt(&e, f)?; } } Ok(()) } } impl Debug for ChainError { fn fmt(&self, f: &mut Formatter) -> Result { #[cfg(not(feature = "no-fileline"))] { if let Some(o) = self.occurrence { write!(f, "{}:{}: ", o.1, o.0)?; } } if self.is_chain::() { Display::fmt(&self.kind, f)?; } else { Debug::fmt(&self.kind, f)?; } #[cfg(not(feature = "no-debug-cause"))] { if let Some(e) = self.source() { writeln!(f, "\nCaused by:")?; Debug::fmt(&e, f)?; } } Ok(()) } } /** creates a new `ChainError` # Examples Create a new ChainError, where `FooError` must implement `Display` and `Debug`. ~~~rust # use chainerror::*; # # #[derive(Debug)] enum FooError { Bar, Baz(&'static str), } # # impl ::std::fmt::Display for FooError { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # match self { # FooError::Bar => write!(f, "Bar Error"), # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # } # } # } // impl ::std::fmt::Display for FooError fn do_some_stuff() -> bool { false } fn func() -> ChainResult<(), FooError> { if ! do_some_stuff() { Err(cherr!(FooError::Baz("Error")))?; } Ok(()) } # # pub fn main() { # match func().unwrap_err().kind() { # FooError::Baz(s) if s == &"Error" => {}, # _ => panic!(), # } # } ~~~ Additionally an error cause can be added. ~~~rust # use chainerror::*; # use std::io; # use std::error::Error; # # #[derive(Debug)] # enum FooError { # Bar, # Baz(&'static str), # } # # impl ::std::fmt::Display for FooError { # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { # match self { # FooError::Bar => write!(f, "Bar Error"), # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), # } # } # } # fn do_some_stuff() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } fn func() -> ChainResult<(), FooError> { do_some_stuff().map_err( |e| cherr!(e, FooError::Baz("Error")) )?; Ok(()) } # # pub fn main() { # match func().unwrap_err().kind() { # FooError::Baz(s) if s == &"Error" => {}, # _ => panic!(), # } # } ~~~ **/ #[macro_export] macro_rules! cherr { ( $k:expr ) => { ChainError::<_>::new($k, None, Some((line!(), file!()))) }; ( $e:expr, $k:expr ) => { ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) }; } /** convenience macro for |e| cherr!(e, format!(…)) # Examples ~~~rust # use crate::chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # fn func2() -> Result<(), Box> { let filename = "foo.txt"; do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; Ok(()) } fn func1() -> Result<(), Box> { func2().map_err(mstrerr!("func1 error"))?; Ok(()) } # fn main() { # if let Err(e) = func1() { # assert_eq!( # format!("\n{:?}\n", e), r#" # src/lib.rs:20: func1 error # Caused by: # src/lib.rs:15: Error reading 'foo.txt' # Caused by: # Kind(NotFound) # "# # ); # } else { # unreachable!(); # } # } ~~~ `mstrerr!()` can also be used to map a new `ChainError`, where T was defined with `derive_str_cherr!(T)` ~~~rust # use crate::chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # derive_str_cherr!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; Ok(()) } derive_str_cherr!(Func1Error); fn func1() -> Result<(), Box> { func2().map_err(mstrerr!(Func1Error, "func1 error"))?; Ok(()) } # # fn main() { # if let Err(e) = func1() { # if let Some(f1err) = e.downcast_chain_ref::() { # assert!(f1err.find_cause::>().is_some()); # assert!(f1err.find_chain_cause::().is_some()); # } else { # panic!(); # } # } else { # unreachable!(); # } # } ~~~ **/ #[macro_export] macro_rules! mstrerr { ( $t:ident, $v:expr $(, $more:expr)* ) => { |e| cherr!(e, $t (format!($v, $( $more , )* ))) }; ( $t:path, $v:expr $(, $more:expr)* ) => { |e| cherr!(e, $t (format!($v, $( $more , )* ))) }; ( $v:expr $(, $more:expr)* ) => { |e| cherr!(e, format!($v, $( $more , )* )) }; } /** convenience macro to create a "new type" T(String) and implement Display + Debug for T ~~~rust # use crate::chainerror::*; # use std::error::Error; # use std::io; # use std::result::Result; # # fn do_some_io() -> Result<(), Box> { # Err(io::Error::from(io::ErrorKind::NotFound))?; # Ok(()) # } # derive_str_cherr!(Func2Error); fn func2() -> ChainResult<(), Func2Error> { let filename = "foo.txt"; do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; Ok(()) } derive_str_cherr!(Func1Error); fn func1() -> Result<(), Box> { func2().map_err(mstrerr!(Func1Error, "func1 error"))?; Ok(()) } # # fn main() { # if let Err(e) = func1() { # if let Some(f1err) = e.downcast_chain_ref::() { # assert!(f1err.find_cause::>().is_some()); # assert!(f1err.find_chain_cause::().is_some()); # } else { # panic!(); # } # } else { # unreachable!(); # } # } ~~~ **/ #[macro_export] macro_rules! derive_str_cherr { ($e:ident) => { struct $e(String); impl ::std::fmt::Display for $e { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "{}", self.0) } } impl ::std::fmt::Debug for $e { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "{}({})", stringify!($e), self.0) } } impl ::std::error::Error for $e {} }; }