diff --git a/Cargo.toml b/Cargo.toml index 1ca8330..d07ba7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chainerror" -version = "0.4.3" +version = "0.4.4" authors = ["Harald Hoyer "] edition = "2018" license = "MIT/Apache-2.0" diff --git a/booksrc/SUMMARY.md b/booksrc/SUMMARY.md index 9c8d13a..91ef467 100644 --- a/booksrc/SUMMARY.md +++ b/booksrc/SUMMARY.md @@ -15,5 +15,6 @@ - [Debug for the ErrorKind](tutorial11.md) - [Deref for the ErrorKind](tutorial12.md) - [Writing a library](tutorial13.md) +- [Going back to std](tutorial14.md) [The End](end.md) \ No newline at end of file diff --git a/booksrc/tutorial14.md b/booksrc/tutorial14.md new file mode 100644 index 0000000..1bdb97b --- /dev/null +++ b/booksrc/tutorial14.md @@ -0,0 +1,9 @@ +# Going back to std + +Not using `chainerror` and going full `std` would look like this: + +Btw, the code size is bigger than using `chainerror` :-) + +~~~rust +{{#include ../examples/tutorial14.rs}} +~~~ diff --git a/examples/tutorial13.rs b/examples/tutorial13.rs index 6a06694..6c30358 100644 --- a/examples/tutorial13.rs +++ b/examples/tutorial13.rs @@ -21,11 +21,12 @@ pub mod mycrate { IO(String), } - pub type Error = ChainError; + derive_err_kind!(Error, ErrorKind); + pub type Result = std::result::Result; - impl ::std::fmt::Display for ErrorKind { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + impl std::fmt::Display for ErrorKind { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result { match self { ErrorKind::Func2 => write!(f, "func1 error calling func2"), ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), diff --git a/examples/tutorial14.rs b/examples/tutorial14.rs new file mode 100644 index 0000000..5ccd7ce --- /dev/null +++ b/examples/tutorial14.rs @@ -0,0 +1,217 @@ +pub mod mycrate { + use std::error::Error as StdError; + + use func2mod::{do_some_io, func2}; + + pub mod func2mod { + use std::error::Error as StdError; + use std::io; + + pub enum ErrorKind { + IO(String), + } + + impl std::fmt::Display for ErrorKind { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result { + match self { + ErrorKind::IO(s) => std::fmt::Display::fmt(s, f), + } + } + } + + impl std::fmt::Debug for ErrorKind { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result { + match self { + ErrorKind::IO(s) => std::fmt::Display::fmt(s, f), + } + } + } + + macro_rules! mcherr { + ( $k:expr ) => {{ + |e| { + Error( + $k, + Some(Box::from(e)), + Some(concat!(file!(), ":", line!(), ": ")), + ) + } + }}; + } + + pub struct Error( + ErrorKind, + Option>, + Option<&'static str>, + ); + + impl Error { + pub fn kind(&self) -> &ErrorKind { + &self.0 + } + } + + impl From for Error { + fn from(e: ErrorKind) -> Self { + Error(e, None, None) + } + } + + impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.1.as_ref().map(|e| e.as_ref()) + } + } + + impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } + } + + impl std::fmt::Debug for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if let Some(ref o) = self.2 { + std::fmt::Display::fmt(o, f)?; + } + + std::fmt::Debug::fmt(&self.0, f)?; + + if let Some(e) = self.source() { + std::fmt::Display::fmt("\nCaused by:\n", f)?; + std::fmt::Debug::fmt(&e, f)?; + } + Ok(()) + } + } + + pub fn do_some_io() -> std::result::Result<(), Box> { + Err(io::Error::from(io::ErrorKind::NotFound))?; + Ok(()) + } + + pub fn func2() -> std::result::Result<(), Error> { + let filename = "foo.txt"; + do_some_io().map_err(mcherr!(ErrorKind::IO(format!( + "Error reading '{}'", + filename + ))))?; + Ok(()) + } + } + + #[derive(Debug)] + pub enum ErrorKind { + Func2, + IO(String), + } + + impl std::fmt::Display for ErrorKind { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result { + match self { + ErrorKind::Func2 => write!(f, "func1 error calling func2"), + ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), + } + } + } + + macro_rules! mcherr { + ( $k:expr ) => {{ + |e| { + Error( + $k, + Some(Box::from(e)), + Some(concat!(file!(), ":", line!(), ": ")), + ) + } + }}; + } + + pub struct Error( + ErrorKind, + Option>, + Option<&'static str>, + ); + + impl Error { + pub fn kind(&self) -> &ErrorKind { + &self.0 + } + } + + impl From for Error { + fn from(e: ErrorKind) -> Self { + Error(e, None, None) + } + } + + impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.1.as_ref().map(|e| e.as_ref()) + } + } + + impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } + } + + impl std::fmt::Debug for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if let Some(ref o) = self.2 { + std::fmt::Display::fmt(o, f)?; + } + + std::fmt::Debug::fmt(&self.0, f)?; + if let Some(e) = self.source() { + std::fmt::Display::fmt("\nCaused by:\n", f)?; + std::fmt::Debug::fmt(&e, f)?; + } + Ok(()) + } + } + + pub type Result = std::result::Result; + + pub fn func1() -> Result<()> { + func2().map_err(mcherr!(ErrorKind::Func2))?; + let filename = String::from("bar.txt"); + do_some_io().map_err(mcherr!(ErrorKind::IO(filename)))?; + Ok(()) + } +} + +fn main() -> Result<(), Box> { + use mycrate::func1; + use mycrate::ErrorKind; + use std::error::Error; + use std::io; + + if let Err(e) = func1() { + match e.kind() { + ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), + ErrorKind::IO(ref filename) => { + eprintln!("Main Error Report: func1 error reading '{}'", filename) + } + } + + eprintln!(); + let mut s: &Error = &e; + while let Some(c) = s.source() { + if let Some(ioerror) = c.downcast_ref::() { + eprintln!("caused by: std::io::Error: {}", ioerror); + match ioerror.kind() { + io::ErrorKind::NotFound => eprintln!("of kind: std::io::ErrorKind::NotFound"), + _ => {} + } + } else { + eprintln!("caused by: {}", c); + } + s = c; + } + + eprintln!("\nDebug Error:\n{:?}", e); + } + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 1ba8dcb..dd1471c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -199,7 +199,7 @@ 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)>, + occurrence: Option<&'static str>, kind: T, error_cause: Option>, } @@ -210,10 +210,11 @@ pub type ChainResult = std::result::Result>; impl ChainError { #[cfg(not(feature = "no-fileline"))] /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly + #[inline] pub fn new( kind: T, error_cause: Option>, - occurrence: Option<(u32, &'static str)>, + occurrence: Option<&'static str>, ) -> Self { Self { occurrence, @@ -224,10 +225,11 @@ impl ChainError { #[cfg(feature = "no-fileline")] /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly + #[inline] pub fn new( kind: T, error_cause: Option>, - _occurrence: Option<(u32, &'static str)>, + _occurrence: Option<&'static str>, ) -> Self { Self { kind, error_cause } } @@ -283,6 +285,7 @@ impl ChainError { /// # } /// } /// ``` + #[inline] pub fn find_cause(&self) -> Option<&U> { self.iter().filter_map(Error::downcast_ref::).next() } @@ -303,6 +306,7 @@ impl ChainError { /// // leave out the ChainError implementation detail /// err.find_chain_cause::(); /// ``` + #[inline] pub fn find_chain_cause(&self) -> Option<&ChainError> { self.iter() .filter_map(Error::downcast_ref::>) @@ -329,6 +333,7 @@ impl ChainError { /// // leave out the ChainError implementation detail /// err.find_kind_or_cause::(); /// ``` + #[inline] pub fn find_kind_or_cause(&self) -> Option<&U> { self.iter() .filter_map(|e| { @@ -395,6 +400,7 @@ impl ChainError { /// # } /// } /// ``` + #[inline] pub fn kind(&self) -> &T { &self.kind } @@ -404,6 +410,7 @@ impl ChainError { /// # Example /// /// + #[inline] pub fn iter(&self) -> impl Iterator { ErrorIter { current: Some(self), @@ -418,6 +425,7 @@ struct ErrorIter<'a> { impl<'a> Iterator for ErrorIter<'a> { type Item = &'a (dyn Error + 'static); + #[inline] fn next(&mut self) -> Option { let current = self.current; self.current = self.current.and_then(Error::source); @@ -428,6 +436,7 @@ impl<'a> Iterator for ErrorIter<'a> { impl std::ops::Deref for ChainError { type Target = T; + #[inline] fn deref(&self) -> &Self::Target { &self.kind } @@ -444,10 +453,12 @@ pub trait ChainErrorDown { } impl ChainErrorDown for ChainError { + #[inline] fn is_chain(&self) -> bool { TypeId::of::() == TypeId::of::() } + #[inline] fn downcast_chain_ref(&self) -> Option<&ChainError> { if self.is_chain::() { #[allow(clippy::cast_ptr_alignment)] @@ -460,6 +471,7 @@ impl ChainErrorDown for ChainError { } } + #[inline] fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { if self.is_chain::() { #[allow(clippy::cast_ptr_alignment)] @@ -474,66 +486,79 @@ impl ChainErrorDown for ChainError { } impl ChainErrorDown for dyn Error + 'static { + #[inline] fn is_chain(&self) -> bool { self.is::>() } + #[inline] fn downcast_chain_ref(&self) -> Option<&ChainError> { self.downcast_ref::>() } + #[inline] fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { self.downcast_mut::>() } } impl ChainErrorDown for dyn Error + 'static + Send { + #[inline] fn is_chain(&self) -> bool { self.is::>() } + #[inline] fn downcast_chain_ref(&self) -> Option<&ChainError> { self.downcast_ref::>() } + #[inline] fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { self.downcast_mut::>() } } impl ChainErrorDown for dyn Error + 'static + Send + Sync { + #[inline] fn is_chain(&self) -> bool { self.is::>() } + #[inline] fn downcast_chain_ref(&self) -> Option<&ChainError> { self.downcast_ref::>() } + #[inline] fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { self.downcast_mut::>() } } impl Error for ChainError { + #[inline] fn source(&self) -> Option<&(dyn Error + 'static)> { self.error_cause.as_ref().map(|e| e.as_ref()) } } impl Error for &ChainError { + #[inline] fn source(&self) -> Option<&(dyn Error + 'static)> { self.error_cause.as_ref().map(|e| e.as_ref()) } } impl Error for &mut ChainError { + #[inline] fn source(&self) -> Option<&(dyn Error + 'static)> { self.error_cause.as_ref().map(|e| e.as_ref()) } } impl Display for ChainError { + #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> Result { write!(f, "{}", self.kind)?; @@ -549,11 +574,12 @@ impl Display for ChainError { } impl Debug for ChainError { + #[inline] 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 let Some(ref o) = self.occurrence { + Display::fmt(o, f)?; } } @@ -577,20 +603,21 @@ impl Debug for ChainError { /// `ChainErrorFrom` is similar to `From` pub trait ChainErrorFrom: Sized { /// similar to From::from() - fn chain_error_from(_: T, line_filename: Option<(u32, &'static str)>) -> ChainError; + fn chain_error_from(from: T, line_filename: Option<&'static str>) -> ChainError; } /// `IntoChainError` is similar to `Into` pub trait IntoChainError: Sized { /// similar to Into::into() - fn into_chain_error(self, line_filename: Option<(u32, &'static str)>) -> ChainError; + fn into_chain_error(self, line_filename: Option<&'static str>) -> ChainError; } impl IntoChainError for T where U: ChainErrorFrom, { - fn into_chain_error(self, line_filename: Option<(u32, &'static str)>) -> ChainError { + #[inline] + fn into_chain_error(self, line_filename: Option<&'static str>) -> ChainError { U::chain_error_from(self, line_filename) } } @@ -600,7 +627,8 @@ where T: Into, U: 'static + Display + Debug, { - fn chain_error_from(t: T, line_filename: Option<(u32, &'static str)>) -> ChainError { + #[inline] + fn chain_error_from(t: T, line_filename: Option<&'static str>) -> ChainError { let e: U = t.into(); ChainError::<_>::new(e, None, line_filename) } @@ -612,7 +640,7 @@ where #[macro_export] macro_rules! minto_cherr { ( ) => { - |e| e.into_chain_error(Some((line!(), file!()))) + |e| ChainErrorFrom::chain_error_from(e, Some(concat!(file!(), ":", line!(), ": "))) }; } @@ -622,7 +650,7 @@ macro_rules! minto_cherr { #[macro_export] macro_rules! into_cherr { ( $t:expr ) => { - $t.into_chain_error(Some((line!(), file!()))) + ChainErrorFrom::chain_error_from($t, Some(concat!(file!(), ":", line!(), ": "))) }; } @@ -707,10 +735,10 @@ macro_rules! into_cherr { #[macro_export] macro_rules! cherr { ( $k:expr ) => ({ - ChainError::<_>::new($k, None, Some((line!(), file!()))) + ChainError::new($k, None, Some(concat!(file!(), ":", line!(), ": "))) }); ( None, $k:expr ) => ({ - ChainError::<_>::new($k, None, Some((line!(), file!()))) + ChainError::new($k, None, Some(concat!(file!(), ":", line!(), ": "))) }); ( None, $fmt:expr, $($arg:tt)+ ) => ({ cherr!(None, format!($fmt, $($arg)+ )) @@ -719,7 +747,7 @@ macro_rules! cherr { cherr!(None, format!($fmt, $($arg)+ )) }); ( $e:path, $k:expr ) => ({ - ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) + ChainError::new($k, Some(Box::from($e)), Some(concat!(file!(), ":", line!(), ": "))) }); ( $e:path, $fmt:expr, $($arg:tt)+ ) => ({ cherr!($e, format!($fmt, $($arg)+ )) @@ -727,6 +755,14 @@ macro_rules! cherr { } +/// shortcut for |e| cherr!(e, $k) +#[macro_export] +macro_rules! mcherr { + ( $k:expr ) => {{ + |e| cherr!(e, $k) + }}; +} + /// Convenience macro for `|e| cherr!(e, format!(…))` /// /// # Examples @@ -756,9 +792,9 @@ macro_rules! cherr { /// # #[cfg(not(windows))] /// # assert_eq!( /// # format!("\n{:?}\n", e), r#" -/// # src/lib.rs:20: func1 error +/// # src/lib.rs:18: func1 error /// # Caused by: -/// # src/lib.rs:15: Error reading 'foo.txt' +/// # src/lib.rs:13: Error reading 'foo.txt' /// # Caused by: /// # Kind(NotFound) /// # "# @@ -929,6 +965,7 @@ macro_rules! strerr { #[macro_export] macro_rules! derive_str_cherr { ($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 { @@ -943,3 +980,49 @@ macro_rules! derive_str_cherr { impl ::std::error::Error for $e {} }; } + +/// Derive an Error, which wraps ChainError and implements a kind() method +/// +/// e.kind() returns the kind +#[macro_export] +macro_rules! derive_err_kind { + ($e:ident, $k:ident) => { + pub struct $e(ChainError<$k>); + + impl $e { + pub fn kind(&self) -> &$k { + self.0.kind() + } + } + + impl From<$k> for $e { + fn from(e: $k) -> Self { + $e(ChainError::new(e, None, None)) + } + } + + impl From> for $e { + fn from(e: ChainError<$k>) -> Self { + $e(e) + } + } + + 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) + } + } + }; +}