//! `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 io_error = e.downcast_ref::().unwrap(); //! eprintln!("\nThe root cause was: std::io::Error: {:#?}", io_error); //! } //! //! #[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() } /// Find the first error cause of type `ChainError` or `U`, if any exists and return `U` /// /// Same as `find_cause` and `find_chain_cause`, but hides the `ChainError` implementation internals /// /// # Examples /// /// ~~~rust,ignore /// // Instead of writing /// err.find_cause::>(); /// // and/or /// err.find_chain_cause::(); /// // and/or /// err.find_cause::(); /// /// // leave out the ChainError implementation detail /// err.find_chain_or_kind::(); /// ~~~ pub fn find_kind_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 } /// Returns an Iterator over all error causes/sources /// /// # Example /// /// 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 } } impl std::ops::Deref for ChainError { type Target = T; fn deref(&self) -> &Self::Target { &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::() { #[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:path, $k:expr ) => ({ ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) }); ( $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:path, $msg:expr ) => ({ |e| cherr!(e, $t ($msg.to_string())) }); ( $t:path, $msg:expr, ) => ({ |e| cherr!(e, $t ($msg.to_string())) }); ( $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)` /// /// # Examples /// /// ~~~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:path, $msg:expr ) => ({ cherr!($t ($msg.to_string())) }); ( $t:path, $msg:expr, ) => ({ cherr!($t ($msg.to_string())) }); ( $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 /// /// # 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() -> 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 {} }; }