Debug for the ErrorKind
One small improvement is to fix the debug output of
Func1ErrorKind
. As you probably noticed, the output doesn't say much of the enum.
Debug Error:
src/main.rs:35: Func2
[…]
As a lazy shortcut, we implement Debug
by calling Display
and end up with
Debug Error:
src/main.rs:40: func1 error calling func2
[…}
which gives us a lot more detail.
To create your own Errors, you might find crates which create enum Display+Debug
via derive macros.
Also noteworthy is custom_error to define your custom errors,
which can then be used with chainerror
.
use chainerror::prelude::v1::*; use std::error::Error; use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } derive_str_context!(Func2Error); fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { let filename = "foo.txt"; do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; Ok(()) } enum Func1ErrorKind { Func2, IO(String), } 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), } } } impl ::std::fmt::Debug for Func1ErrorKind { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "{}", self) } } impl ::std::error::Error for Func1ErrorKind {} fn func1() -> ChainResult<(), Func1ErrorKind> { func2().context(Func1ErrorKind::Func2)?; let filename = String::from("bar.txt"); do_some_io().context(Func1ErrorKind::IO(filename))?; Ok(()) } fn main() -> Result<(), Box<dyn Error + Send + Sync>> { if let Err(e) = func1() { match e.kind() { Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), Func1ErrorKind::IO(filename) => { eprintln!("Main Error Report: func1 error reading '{}'", filename) } } if let Some(e) = e.find_chain_cause::<Func2Error>() { eprintln!("\nError reported by Func2Error: {}", e) } eprintln!("\nDebug Error:\n{:?}", e); std::process::exit(1); } Ok(()) } #[allow(dead_code)] mod chainerror { //! `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 `#[track_caller]` and `Location` 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<T>` struct, `chainerror` comes with some useful helper macros to save a lot of typing. //! //! Debug information is worth it! //! //! ## Features //! //! `display-cause` //! : turn on printing a backtrace of the errors in `Display` //! //! # Tutorial //! //! Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) //! //! # Examples //! //! examples/example.rs: //! ```rust,ignore //! // […] //! fn main() { //! if let Err(e) = func1() { //! eprintln!("\nDebug Error {{:?}}:\n{:?}", e); //! eprintln!("\nAlternative Debug Error {{:#?}}:\n{:#?}\n", e); //! // […] //! } //! } //! ``` //! //! ```console //! $ cargo run -q --example example //! Debug Error {:?}: //! examples/example.rs:46:13: func1 error calling func2 //! Caused by: //! examples/example.rs:21:13: Func2Error(func2 error: calling func3) //! Caused by: //! examples/example.rs:14:18: Error reading 'foo.txt' //! Caused by: //! Kind(NotFound) //! //! Alternative Debug Error {:#?}: //! ChainError<example::Func1Error> { //! occurrence: Some( //! "examples/example.rs:46:13", //! ), //! kind: func1 error calling func2, //! source: Some( //! ChainError<example::Func2Error> { //! occurrence: Some( //! "examples/example.rs:21:13", //! ), //! kind: Func2Error(func2 error: calling func3), //! source: Some( //! ChainError<alloc::string::String> { //! occurrence: Some( //! "examples/example.rs:14:18", //! ), //! kind: "Error reading \'foo.txt\'", //! source: Some( //! Kind( //! NotFound, //! ), //! ), //! }, //! ), //! }, //! ), //! } //! ``` //! //! ```rust //! use chainerror::prelude::v1::*; //! use std::error::Error; //! use std::io; //! use std::result::Result; //! //! fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { //! Err(io::Error::from(io::ErrorKind::NotFound))?; //! Ok(()) //! } //! //! fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { //! let filename = "foo.txt"; //! do_some_io().context(format!("Error reading '{}'", filename))?; //! Ok(()) //! } //! //! fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { //! func2().context("func1 error")?; //! Ok(()) //! } //! //! if let Err(e) = func1() { //! #[cfg(not(windows))] //! assert_eq!( //! format!("\n{:?}\n", e), //! r#" //! src/lib.rs:21:13: func1 error //! Caused by: //! src/lib.rs:16:18: Error reading 'foo.txt' //! Caused by: //! Kind(NotFound) //! "# //! ); //! } //! # else { //! # unreachable!(); //! # } //! ``` //! //! //! ```rust //! use chainerror::prelude::v1::*; //! use std::error::Error; //! use std::io; //! use std::result::Result; //! //! fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { //! Err(io::Error::from(io::ErrorKind::NotFound))?; //! Ok(()) //! } //! //! fn func3() -> Result<(), Box<dyn Error + Send + Sync>> { //! let filename = "foo.txt"; //! do_some_io().context(format!("Error reading '{}'", filename))?; //! Ok(()) //! } //! //! derive_str_context!(Func2Error); //! //! fn func2() -> ChainResult<(), Func2Error> { //! func3().context(Func2Error("func2 error: calling func3".into()))?; //! 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().context(Func1Error::Func2)?; //! let filename = String::from("bar.txt"); //! do_some_io().context(Func1Error::IO(filename))?; //! Ok(()) //! } //! //! 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::<Func2Error>().is_some()); //! //! if let Some(e) = e.find_chain_cause::<Func2Error>() { //! 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::<io::Error>().unwrap(); //! eprintln!("\nThe root cause was: std::io::Error: {:#?}", io_error); //! } //! //! #[cfg(not(windows))] //! assert_eq!( //! format!("\n{:?}\n", e), //! r#" //! src/lib.rs:48:13: func1 error calling func2 //! Caused by: //! src/lib.rs:23:13: Func2Error(func2 error: calling func3) //! Caused by: //! src/lib.rs:16:18: Error reading 'foo.txt' //! Caused by: //! Kind(NotFound) //! "# //! ); //! } //! # else { //! # unreachable!(); //! # } //! ``` #![deny(clippy::all)] #![deny(clippy::integer_arithmetic)] #![deny(missing_docs)] use std::any::TypeId; use std::error::Error; use std::fmt::{Debug, Display, Formatter, Result}; use std::panic::Location; pub mod prelude { //! convenience prelude pub mod v1 { //! convenience prelude pub use super::super::ChainErrorDown as _; pub use super::super::ResultTrait as _; pub use super::super::{ChainError, ChainResult}; pub use crate::{derive_err_kind, derive_str_context}; } } /// chains an inner error kind `T` with a causing error pub struct ChainError<T> { occurrence: Option<String>, kind: T, error_cause: Option<Box<dyn Error + 'static + Send + Sync>>, } /// convenience type alias pub type ChainResult<O, E> = std::result::Result<O, ChainError<E>>; impl<T: 'static + Display + Debug> ChainError<T> { /// Use the `context()` or `map_context()` Result methods instead of calling this directly #[inline] pub fn new( kind: T, error_cause: Option<Box<dyn Error + 'static + Send + Sync>>, occurrence: Option<String>, ) -> Self { Self { occurrence, 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 chainerror::prelude::v1::*; /// use std::error::Error; /// use std::io; /// /// fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { /// Err(io::Error::from(io::ErrorKind::NotFound))?; /// Ok(()) /// } /// /// derive_str_context!(Func2Error); /// /// fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { /// let filename = "foo.txt"; /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// /// derive_str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { /// func2().context(Func1Error("func1 error".into()))?; /// Ok(()) /// } /// /// if let Err(e) = func1() { /// if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { /// assert!(f1err.find_cause::<io::Error>().is_some()); /// /// assert!(f1err.find_chain_cause::<Func2Error>().is_some()); /// } /// # else { /// # panic!(); /// # } /// } /// # else { /// # unreachable!(); /// # } /// ``` #[inline] pub fn find_cause<U: Error + 'static>(&self) -> Option<&U> { self.iter().filter_map(Error::downcast_ref::<U>).next() } /// Find the first error cause of type `ChainError<U>`, if any exists /// /// Same as `find_cause`, but hides the `ChainError<U>` implementation internals /// /// # Examples /// /// ```rust /// # use chainerror::prelude::v1::*; /// # derive_str_context!(FooError); /// # let err = ChainError::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<ChainError<FooError>>(); /// /// // leave out the ChainError<FooError> implementation detail /// err.find_chain_cause::<FooError>(); /// ``` #[inline] pub fn find_chain_cause<U: Error + 'static>(&self) -> Option<&ChainError<U>> { self.iter() .filter_map(Error::downcast_ref::<ChainError<U>>) .next() } /// Find the first error cause of type `ChainError<U>` or `U`, if any exists and return `U` /// /// Same as `find_cause` and `find_chain_cause`, but hides the `ChainError<U>` implementation internals /// /// # Examples /// /// ```rust /// # use chainerror::prelude::v1::*; /// # derive_str_context!(FooErrorKind); /// # let err = ChainError::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<ChainError<FooErrorKind>>(); /// // and/or /// err.find_chain_cause::<FooErrorKind>(); /// // and/or /// err.find_cause::<FooErrorKind>(); /// /// // leave out the ChainError<FooErrorKind> implementation detail /// err.find_kind_or_cause::<FooErrorKind>(); /// ``` #[inline] pub fn find_kind_or_cause<U: Error + 'static>(&self) -> Option<&U> { self.iter() .filter_map(|e| { e.downcast_ref::<ChainError<U>>() .map(|e| e.kind()) .or_else(|| e.downcast_ref::<U>()) }) .next() } /// Return a reference to T of `ChainError<T>` /// /// # Examples /// /// ```rust /// use chainerror::prelude::v1::*; /// use std::error::Error; /// use std::io; /// /// fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { /// Err(io::Error::from(io::ErrorKind::NotFound))?; /// Ok(()) /// } /// /// derive_str_context!(Func2Error); /// /// fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { /// let filename = "foo.txt"; /// do_some_io().context(Func2Error(format!("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().context(Func1ErrorKind::Func2)?; /// do_some_io().context(Func1ErrorKind::IO("bar.txt".into()))?; /// Ok(()) /// } /// /// if let Err(e) = func1() { /// match e.kind() { /// Func1ErrorKind::Func2 => {} /// Func1ErrorKind::IO(filename) => panic!(), /// } /// } /// # else { /// # unreachable!(); /// # } /// ``` #[inline] pub fn kind(&self) -> &T { &self.kind } /// Returns an Iterator over all error causes/sources /// /// # Example #[inline] pub fn iter(&self) -> impl Iterator<Item = &(dyn Error + 'static)> { ErrorIter { current: Some(self), } } } /// Convenience methods for `Result<>` to turn the error into a decorated ChainError pub trait ResultTrait<O, E: Into<Box<dyn Error + 'static + Send + Sync>>> { /// Decorate the error with a `kind` of type `T` and the source `Location` fn context<T: 'static + Display + Debug>( self, kind: T, ) -> std::result::Result<O, ChainError<T>>; /// Decorate the `error` with a `kind` of type `T` produced with a `FnOnce(&error)` and the source `Location` fn map_context<T: 'static + Display + Debug, F: FnOnce(&E) -> T>( self, op: F, ) -> std::result::Result<O, ChainError<T>>; } impl<O, E: Into<Box<dyn Error + 'static + Send + Sync>>> ResultTrait<O, E> for std::result::Result<O, E> { #[track_caller] fn context<T: 'static + Display + Debug>( self, kind: T, ) -> std::result::Result<O, ChainError<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(ChainError::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] fn map_context<T: 'static + Display + Debug, F: FnOnce(&E) -> T>( self, op: F, ) -> std::result::Result<O, ChainError<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => { let kind = op(&error_cause); Err(ChainError::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), )) } } } } /// An iterator over all error causes/sources pub struct ErrorIter<'a> { current: Option<&'a (dyn Error + 'static)>, } impl<'a> Iterator for ErrorIter<'a> { type Item = &'a (dyn Error + 'static); #[inline] fn next(&mut self) -> Option<Self::Item> { let current = self.current; self.current = self.current.and_then(Error::source); current } } impl<T: 'static + Display + Debug> std::ops::Deref for ChainError<T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.kind } } /// Convenience trait to hide the `ChainError<T>` implementation internals pub trait ChainErrorDown { /// Test if of type `ChainError<T>` fn is_chain<T: 'static + Display + Debug>(&self) -> bool; /// Downcast to a reference of `ChainError<T>` fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>>; /// Downcast to a mutable reference of `ChainError<T>` fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>>; /// Downcast to T of `ChainError<T>` fn downcast_inner_ref<T: 'static + Error>(&self) -> Option<&T>; /// Downcast to T mutable reference of `ChainError<T>` fn downcast_inner_mut<T: 'static + Error>(&mut self) -> Option<&mut T>; } impl<U: 'static + Display + Debug> ChainErrorDown for ChainError<U> { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { TypeId::of::<T>() == TypeId::of::<U>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&*(self as *const dyn Error as *const &ChainError<T>)) } } else { None } } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&mut *(self as *mut dyn Error as *mut &mut ChainError<T>)) } } else { None } } #[inline] fn downcast_inner_ref<T: 'static + Error>(&self) -> Option<&T> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&(*(self as *const dyn Error as *const &ChainError<T>)).kind) } } else { None } } #[inline] fn downcast_inner_mut<T: 'static + Error>(&mut self) -> Option<&mut T> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&mut (*(self as *mut dyn Error as *mut &mut ChainError<T>)).kind) } } else { None } } } impl ChainErrorDown for dyn Error + 'static { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<ChainError<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { self.downcast_ref::<ChainError<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { self.downcast_mut::<ChainError<T>>() } #[inline] fn downcast_inner_ref<T: 'static + Error>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<ChainError<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + Error>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<ChainError<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ChainErrorDown for dyn Error + 'static + Send { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<ChainError<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { self.downcast_ref::<ChainError<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { self.downcast_mut::<ChainError<T>>() } #[inline] fn downcast_inner_ref<T: 'static + Error>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<ChainError<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + Error>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<ChainError<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ChainErrorDown for dyn Error + 'static + Send + Sync { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<ChainError<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { self.downcast_ref::<ChainError<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { self.downcast_mut::<ChainError<T>>() } #[inline] fn downcast_inner_ref<T: 'static + Error>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<ChainError<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + Error>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<ChainError<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl<T: 'static + Display + Debug> Error for ChainError<T> { #[inline] fn source(&self) -> Option<&(dyn Error + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn Error + 'static)) } } impl<T: 'static + Display + Debug> Error for &ChainError<T> { #[inline] fn source(&self) -> Option<&(dyn Error + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn Error + 'static)) } } impl<T: 'static + Display + Debug> Error for &mut ChainError<T> { #[inline] fn source(&self) -> Option<&(dyn Error + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn Error + 'static)) } } impl<T: 'static + Display + Debug> Display for ChainError<T> { #[inline] 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<T: 'static + Display + Debug> Debug for ChainError<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> Result { if f.alternate() { let mut f = f.debug_struct(&format!("ChainError<{}>", std::any::type_name::<T>())); let f = f .field("occurrence", &self.occurrence) .field("kind", &self.kind) .field("source", &self.source()); f.finish() } else { if let Some(ref o) = self.occurrence { write!(f, "{}: ", o)?; } if TypeId::of::<String>() == TypeId::of::<T>() || TypeId::of::<&str>() == TypeId::of::<T>() { Display::fmt(&self.kind, f)?; } else { Debug::fmt(&self.kind, f)?; } if let Some(e) = self.source() { writeln!(f, "\nCaused by:")?; Debug::fmt(&e, f)?; } Ok(()) } } } /// `ChainErrorFrom<T>` is similar to `From<T>` pub trait ChainErrorFrom<T>: Sized { /// similar to From<T>::from() fn chain_error_from(from: T, line_filename: Option<String>) -> ChainError<Self>; } /// `IntoChainError<T>` is similar to `Into<T>` pub trait IntoChainError<T>: Sized { /// similar to Into<T>::into() fn into_chain_error(self, line_filename: Option<String>) -> ChainError<T>; } impl<T, U> IntoChainError<U> for T where U: ChainErrorFrom<T>, { #[inline] fn into_chain_error(self, line_filename: Option<String>) -> ChainError<U> { U::chain_error_from(self, line_filename) } } impl<T, U> ChainErrorFrom<T> for U where T: Into<U>, U: 'static + Display + Debug, { #[inline] fn chain_error_from(t: T, line_filename: Option<String>) -> ChainError<Self> { let e: U = t.into(); ChainError::new(e, None, line_filename) } } /// 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<dyn Error + Send + Sync>> { /// # Err(io::Error::from(io::ErrorKind::NotFound))?; /// # Ok(()) /// # } /// derive_str_context!(Func2Error); /// /// fn func2() -> ChainResult<(), Func2Error> { /// let filename = "foo.txt"; /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// /// derive_str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error>> { /// func2().context(Func1Error("func1 error".into()))?; /// Ok(()) /// } /// # if let Err(e) = func1() { /// # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { /// # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some()); /// # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); /// # } else { /// # panic!(); /// # } /// # } else { /// # unreachable!(); /// # } /// ``` #[macro_export] macro_rules! derive_str_context { ($e:ident) => { #[derive(Clone)] 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 {} }; } /// Derive an Error for an ErrorKind, which wraps a `ChainError` and implements a `kind()` method /// /// It basically hides `ChainError` to the outside and only exposes the `kind()` /// method. /// /// Error::kind() returns the ErrorKind /// Error::source() returns the parent error /// /// # Examples /// /// ```rust /// use chainerror::prelude::v1::*; /// use std::io; /// /// fn do_some_io(_f: &str) -> std::result::Result<(), io::Error> { /// return Err(io::Error::from(io::ErrorKind::NotFound)); /// } /// /// #[derive(Debug, Clone)] /// pub enum ErrorKind { /// IO(String), /// FatalError(String), /// Unknown, /// } /// /// derive_err_kind!(Error, ErrorKind); /// /// impl std::fmt::Display for ErrorKind { /// fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result { /// match self { /// ErrorKind::FatalError(e) => write!(f, "fatal error {}", e), /// ErrorKind::Unknown => write!(f, "unknown error"), /// ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), /// } /// } /// } /// /// impl ErrorKind { /// fn from_io_error(e: &io::Error, f: String) -> Self { /// match e.kind() { /// io::ErrorKind::BrokenPipe => panic!("Should not happen"), /// io::ErrorKind::ConnectionReset => { /// ErrorKind::FatalError(format!("While reading `{}`: {}", f, e)) /// } /// _ => ErrorKind::IO(f), /// } /// } /// } /// /// impl From<&io::Error> for ErrorKind { /// fn from(e: &io::Error) -> Self { /// ErrorKind::IO(format!("{}", e)) /// } /// } /// /// pub fn func1() -> std::result::Result<(), Error> { /// let filename = "bar.txt"; /// /// do_some_io(filename).map_context(|e| ErrorKind::from_io_error(e, filename.into()))?; /// do_some_io(filename).map_context(|e| ErrorKind::IO(filename.into()))?; /// do_some_io(filename).map_context(|e| ErrorKind::from(e))?; /// Ok(()) /// } /// ``` #[macro_export] macro_rules! derive_err_kind { ($e:ident, $k:ident) => { pub struct $e($crate::ChainError<$k>); impl $e { pub fn kind(&self) -> &$k { self.0.kind() } } impl From<$k> for $e { fn from(e: $k) -> Self { $e($crate::ChainError::new(e, None, None)) } } impl From<ChainError<$k>> for $e { fn from(e: $crate::ChainError<$k>) -> Self { $e(e) } } impl From<&$e> for $k where $k: Clone, { fn from(e: &$e) -> Self { e.kind().clone() } } impl std::error::Error for $e { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() } } impl std::fmt::Display for $e { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { std::fmt::Display::fmt(&self.0, f) } } impl std::fmt::Debug for $e { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { std::fmt::Debug::fmt(&self.0, f) } } }; } }