/*! `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. ## Features `no-fileline` : completely turn off storing filename and line `display-cause` : turn on printing a backtrace of the errors in `Display` `no-debug-cause` : turn off printing a backtrace of the errors in `Debug` # Tutorial Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) # 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() { #[cfg(not(windows))] 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); } #[cfg(not(windows))] 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)> { self.iter().last() } /** 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> { self.iter().filter_map(Error::downcast_ref::).next() } /** 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> { self.iter() .filter_map(Error::downcast_ref::>) .next() } // FIXME: naming fn find_chain_or_cause(&self) -> Option<&U> { self.iter() .filter_map(|e| { e.downcast_ref::>().map(|e| e.kind()) .or_else(|| e.downcast_ref::()) }) .next() } /** 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(&self) -> &T { &self.kind } pub fn iter(&self) -> impl Iterator { ErrorIter { current: Some(self), } } } struct ErrorIter<'a> { current: Option<&'a (dyn Error + 'static)>, } impl<'a> Iterator for ErrorIter<'a> { type Item = &'a (dyn Error + 'static); fn next(&mut self) -> Option { let current = self.current; self.current = self.current.and_then(Error::source); current } } /** 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::() { #[allow(clippy::cast_ptr_alignment)] 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::() { #[allow(clippy::cast_ptr_alignment)] 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)> { self.error_cause.as_ref().map(|e| e.as_ref()) } } impl Error for &ChainError { fn source(&self) -> Option<&(dyn Error + 'static)> { self.error_cause.as_ref().map(|e| e.as_ref()) } } impl Error for &mut ChainError { fn source(&self) -> Option<&(dyn Error + 'static)> { self.error_cause.as_ref().map(|e| e.as_ref()) } } 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(()) } } pub trait ChainErrorFrom: Sized { fn chain_error_from(_: T, line_filename: Option<(u32, &'static str)>) -> ChainError; } pub trait IntoChainError: Sized { fn into_chain_error(self, line_filename: Option<(u32, &'static str)>) -> ChainError; } impl IntoChainError for T where U: ChainErrorFrom, { fn into_chain_error(self, line_filename: Option<(u32, &'static str)>) -> ChainError { U::chain_error_from(self, line_filename) } } impl ChainErrorFrom for U where T: Into, U: 'static + Display + Debug, { fn chain_error_from(t: T, line_filename: Option<(u32, &'static str)>) -> ChainError { let e: U = t.into(); ChainError::<_>::new(e, None, line_filename) } } #[macro_export] macro_rules! minto_cherr { ( ) => { |e| e.into_chain_error(Some((line!(), file!()))) }; } #[macro_export] macro_rules! into_cherr { ( $t:expr ) => { $t.into_chain_error(Some((line!(), file!()))) }; } /** 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!()))) }); ( None, $k:expr ) => ({ ChainError::<_>::new($k, None, Some((line!(), file!()))) }); ( None, $fmt:expr, $($arg:tt)+ ) => ({ cherr!(None, format!($fmt, $($arg)+ )) }); ( None, $fmt:expr, $($arg:tt)+ ) => ({ cherr!(None, format!($fmt, $($arg)+ )) }); ( $e:ident, $k:expr ) => ({ ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) }); ( $e:path, $k:expr ) => ({ ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) }); ( $e:ident, $fmt:expr, $($arg:tt)+ ) => ({ cherr!($e, format!($fmt, $($arg)+ )) }); ( $e:path, $fmt:expr, $($arg:tt)+ ) => ({ cherr!($e, format!($fmt, $($arg)+ )) }); } /** 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() { # #[cfg(not(windows))] # 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, $msg:expr ) => ({ |e| cherr!(e, $t ($msg.to_string())) }); ( $t:path, $msg:expr ) => ({ |e| cherr!(e, $t ($msg.to_string())) }); ( $t:ident, $msg:expr, ) => ({ |e| cherr!(e, $t ($msg.to_string())) }); ( $t:path, $msg:expr, ) => ({ |e| cherr!(e, $t ($msg.to_string())) }); ( $t:ident, $fmt:expr, $($arg:tt)+ ) => ({ |e| cherr!(e, $t (format!($fmt, $($arg)+ ))) }); ( $t:path, $fmt:expr, $($arg:tt)+ ) => ({ |e| cherr!(e, $t (format!($fmt, $($arg)+ ))) }); ($msg:expr) => ({ |e| cherr!(e, $msg.to_string()) }); ($msg:expr, ) => ({ |e| cherr!(e, $msg.to_string()) }); ($fmt:expr, $($arg:tt)+) => ({ |e| cherr!(e, format!($fmt, $($arg)+ )) }); } /** convenience macro for cherr!(T(format!(…))) where T(String) ~~~rust # use crate::chainerror::*; # use std::error::Error; # use std::result::Result; # derive_str_cherr!(Func2Error); fn func2() -> ChainResult<(), Func2Error> { let filename = "foo.txt"; Err(strerr!(Func2Error, "Error reading '{}'", filename)) } 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! strerr { ( $t:ident, $msg:expr ) => ({ cherr!($t ($msg.to_string())) }); ( $t:path, $msg:expr ) => ({ cherr!($t ($msg.to_string())) }); ( $t:ident, $msg:expr, ) => ({ cherr!($t ($msg.to_string())) }); ( $t:path, $msg:expr, ) => ({ cherr!($t ($msg.to_string())) }); ( $t:ident, $fmt:expr, $($arg:tt)+ ) => ({ cherr!($t (format!($fmt, $($arg)+ ))) }); ( $t:path, $fmt:expr, $($arg:tt)+ ) => ({ cherr!($t (format!($fmt, $($arg)+ ))) }); ($msg:expr) => ({ cherr!($msg.to_string()) }); ($msg:expr, ) => ({ cherr!($msg.to_string()) }); ($fmt:expr, $($arg:tt)+) => ({ cherr!(format!($fmt, $($arg)+ )) }); } /** 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) => { pub struct $e(pub 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 {} }; }