chainerror
chainerror
provides an error backtrace without doing a real backtrace, so even after you strip
your
binaries, you still have the error backtrace.
Having nested function returning errors, the output doesn't tell where the error originates from.
use std::path::PathBuf; type BoxedError = Box<dyn std::error::Error + Send + Sync>; fn read_config_file(path: PathBuf) -> Result<(), BoxedError> { // do stuff, return other errors let _buf = std::fs::read_to_string(&path)?; // do stuff, return other errors Ok(()) } fn process_config_file() -> Result<(), BoxedError> { // do stuff, return other errors let _buf = read_config_file("foo.txt".into())?; // do stuff, return other errors Ok(()) } fn main() { if let Err(e) = process_config_file() { eprintln!("Error:\n{:?}", e); } }
This gives the output:
Error:
Os { code: 2, kind: NotFound, message: "No such file or directory" }
and you have no idea where it comes from.
With chainerror
, you can supply a context and get a nice error backtrace:
use chainerror::Context as _; use std::path::PathBuf; type BoxedError = Box<dyn std::error::Error + Send + Sync>; fn read_config_file(path: PathBuf) -> Result<(), BoxedError> { // do stuff, return other errors let _buf = std::fs::read_to_string(&path).context(format!("Reading file: {:?}", &path))?; // do stuff, return other errors Ok(()) } fn process_config_file() -> Result<(), BoxedError> { // do stuff, return other errors let _buf = read_config_file("foo.txt".into()).context("read the config file")?; // do stuff, return other errors Ok(()) } fn main() { if let Err(e) = process_config_file() { eprintln!("Error:\n{:?}", e); } }
with the output:
Error:
examples/simple.rs:14:51: read the config file
Caused by:
examples/simple.rs:7:47: Reading file: "foo.txt"
Caused by:
Os { code: 2, kind: NotFound, message: "No such file or directory" }
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 Error<T>
struct, chainerror
comes with some useful helper macros to save a lot of typing.
chainerror
has no dependencies!
Debug information is worth it!
Multiple Output Formats
chainerror
supports multiple output formats, which can be selected with the different format specifiers:
{}
: Display
func1 error calling func2
{:#}
: Alternative Display
func1 error calling func2
Caused by:
func2 error: calling func3
Caused by:
(passed error)
Caused by:
Error reading 'foo.txt'
Caused by:
entity not found
{:?}
: Debug
examples/example.rs:50:13: func1 error calling func2
Caused by:
examples/example.rs:25:13: Func2Error(func2 error: calling func3)
Caused by:
examples/example.rs:18:13: (passed error)
Caused by:
examples/example.rs:13:18: Error reading 'foo.txt'
Caused by:
Kind(NotFound)
{:#?}
: Alternative Debug
Error<example::Func1Error> {
occurrence: Some(
"examples/example.rs:50:13",
),
kind: func1 error calling func2,
source: Some(
Error<example::Func2Error> {
occurrence: Some(
"examples/example.rs:25:13",
),
kind: Func2Error(func2 error: calling func3),
source: Some(
Error<chainerror::AnnotatedError> {
occurrence: Some(
"examples/example.rs:18:13",
),
kind: (passed error),
source: Some(
Error<alloc::string::String> {
occurrence: Some(
"examples/example.rs:13:18",
),
kind: "Error reading 'foo.txt'",
source: Some(
Kind(
NotFound,
),
),
},
),
},
),
},
),
}
Tutorial
Read the Tutorial
License
Licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)
at your option.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Simple String Errors
An easy way of doing error handling in rust is by returning String
as a Box<std::error::Error>
.
If the rust main
function returns an Err()
, this Err()
will be displayed with std::fmt::Debug
.
As you can see by running the example (by pressing the "Play" button in upper right of the code block),
this only
prints out the last Error
.
Error: "func1 error"
The next chapters of this tutorial show how chainerror
adds more information
and improves inspecting the sources of an error.
You can also run the tutorial examples in the checked out chainerror git repo.
$ cargo run -q --example tutorial1
#![allow(clippy::single_match)] #![allow(clippy::redundant_pattern_matching)] 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(()) } fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { if let Err(_) = do_some_io() { Err("func2 error")?; } Ok(()) } fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { if let Err(_) = func2() { Err("func1 error")?; } Ok(()) } fn main() -> Result<(), Box<dyn Error + Send + Sync>> { func1() }
Simple Chained String Errors
With relatively small changes and the help of the context()
method of the chainerror
crate
the &str
errors are now chained together.
Press the play button in the upper right corner and see the nice debug output.
use chainerror::Context as _; 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(()) } fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { if let Err(e) = do_some_io() { Err(e).context("func2 error")?; } Ok(()) } fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { if let Err(e) = func2() { Err(e).context("func1 error")?; } Ok(()) } fn main() -> Result<(), Box<dyn Error + Send + Sync>> { func1() } #[allow(dead_code)] mod chainerror { #![doc = include_str!("../README.md")] #![deny(clippy::all)] #![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] use std::any::TypeId; use std::error::Error as StdError; use std::fmt::{Debug, Display, Formatter}; use std::panic::Location; /// chains an inner error kind `T` with a causing error pub struct Error<T> { occurrence: Option<String>, kind: T, error_cause: Option<Box<dyn StdError + 'static + Send + Sync>>, } /// convenience type alias pub type Result<O, E> = std::result::Result<O, Error<E>>; impl<T: 'static + Display + Debug> Error<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 StdError + '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 StdError + 'static)> { self.iter().last() } /// Find the first error cause of type U, if any exists /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// use chainerror::ErrorDown as _; /// 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(()) /// } /// /// chainerror::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(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { /// func2().context(Func1Error::new("func1 error"))?; /// 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: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<U>) .next() } /// Find the first error cause of type [`Error<U>`](Error), if any exists /// /// Same as `find_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooError); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooError>>(); /// /// // leave out the chainerror::Error<FooError> implementation detail /// err.find_chain_cause::<FooError>(); /// ``` #[inline] pub fn find_chain_cause<U: StdError + 'static>(&self) -> Option<&Error<U>> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<Error<U>>) .next() } /// Find the first error cause of type [`Error<U>`](Error) or `U`, if any exists and return `U` /// /// Same as `find_cause` and `find_chain_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooErrorKind); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooErrorKind>>(); /// // and/or /// err.find_chain_cause::<FooErrorKind>(); /// // and/or /// err.find_cause::<FooErrorKind>(); /// /// // leave out the chainerror::Error<FooErrorKind> implementation detail /// err.find_kind_or_cause::<FooErrorKind>(); /// ``` #[inline] pub fn find_kind_or_cause<U: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(|e| { e.downcast_ref::<Error<U>>() .map(|e| e.kind()) .or_else(|| e.downcast_ref::<U>()) }) .next() } /// Return a reference to T of [`Error<T>`](Error) /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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(()) /// } /// /// chainerror::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() -> chainerror::Result<(), 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 StdError + 'static)> { ErrorIter { current: Some(self), } } } /// Convenience methods for `Result<>` to turn the error into a decorated [`Error`](Error) pub trait Context<O, E: Into<Box<dyn StdError + '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, Error<T>>; /// Decorate the error just with the source `Location` fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>>; /// 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, Error<T>>; } /// Convenience type to just decorate the error with the source `Location` pub struct AnnotatedError(()); impl Display for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl Debug for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> Context<O, E> for std::result::Result<O, E> { #[track_caller] #[inline] fn context<T: 'static + Display + Debug>(self, kind: T) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( AnnotatedError(()), Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn map_context<T: 'static + Display + Debug, F: FnOnce(&E) -> T>( self, op: F, ) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => { let kind = op(&error_cause); Err(Error::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 StdError + 'static)>, } impl<'a> Iterator for ErrorIter<'a> { type Item = &'a (dyn StdError + 'static); #[inline] fn next(&mut self) -> Option<Self::Item> { let current = self.current; self.current = self.current.and_then(StdError::source); current } } impl<T: 'static + Display + Debug> std::ops::Deref for Error<T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.kind } } /// Convenience trait to hide the [`Error<T>`](Error) implementation internals pub trait ErrorDown { /// Test if of type `Error<T>` fn is_chain<T: 'static + Display + Debug>(&self) -> bool; /// Downcast to a reference of `Error<T>` fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>>; /// Downcast to a mutable reference of `Error<T>` fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>>; /// Downcast to T of `Error<T>` fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T>; /// Downcast to T mutable reference of `Error<T>` fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T>; } impl<U: 'static + Display + Debug> ErrorDown for Error<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<&Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(*(self as *const dyn StdError as *const &Error<T>)) } } else { None } } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&mut *(self as *mut dyn StdError as *mut &mut Error<T>)) } } else { None } } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&(*(self as *const dyn StdError as *const &Error<T>)).kind) } } else { None } } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&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 StdError as *mut &mut Error<T>)).kind) } } else { None } } } impl ErrorDown for dyn StdError + 'static { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send + Sync { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl<T: 'static + Display + Debug> StdError for Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> StdError for &mut Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> Display for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.kind)?; if f.alternate() { if let Some(e) = self.source() { write!(f, "\nCaused by:\n {:#}", &e)?; } } Ok(()) } } impl<T: 'static + Display + Debug> Debug for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if f.alternate() { let mut f = f.debug_struct(&format!("Error<{}>", 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() { write!(f, "\nCaused by:\n{:?}", &e)?; } Ok(()) } } } impl<T> From<T> for Error<T> where T: 'static + Display + Debug, { #[track_caller] #[inline] fn from(e: T) -> Error<T> { Error::new(e, None, Some(Location::caller().to_string())) } } /// Convenience macro to create a "new type" T(String) and implement Display + Debug for T /// /// # Examples /// /// ```rust /// # use chainerror::Context as _; /// # use chainerror::ErrorDown as _; /// # 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(()) /// # } /// chainerror::str_context!(Func2Error); /// /// fn func2() -> chainerror::Result<(), Func2Error> { /// let filename = "foo.txt"; /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error>> { /// func2().context(Func1Error::new("func1 error"))?; /// Ok(()) /// } /// # if let Err(e) = func1() { /// # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { /// # assert!(f1err.find_cause::<chainerror::Error<Func2Error>>().is_some()); /// # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); /// # } else { /// # panic!(); /// # } /// # } else { /// # unreachable!(); /// # } /// ``` #[macro_export] macro_rules! str_context { ($e:ident) => { #[derive(Clone)] pub struct $e(pub String); impl $e { pub fn new<S: Into<String>>(s: S) -> Self { $e(s.into()) } } 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 [`Error`](Error) and implements a `kind()` method /// /// It basically hides [`Error`](Error) to the outside and only exposes the [`kind()`](Error::kind) /// method. /// /// Error::kind() returns the ErrorKind /// Error::source() returns the parent error /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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, /// } /// /// chainerror::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! err_kind { ($e:ident, $k:ident) => { pub struct $e($crate::Error<$k>); impl $e { pub fn kind(&self) -> &$k { self.0.kind() } } impl From<$k> for $e { fn from(e: $k) -> Self { $e($crate::Error::new(e, None, None)) } } impl From<$crate::Error<$k>> for $e { fn from(e: $crate::Error<$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) } } }; } }
What did we do here?
Err(e).context("func2 error")?;
}
Ok(())
The function context(newerror)
stores olderror
as the source/cause of newerror
along with the Location
of the context()
call and returns Err(newerror)
.
?
then returns the inner error applying .into()
, so that we
again have a Err(Box<Error + Send + Sync>)
as a result.
The Debug
implementation of chainerror::Error<T>
(which is returned by context()
)
prints the Debug
of T
prefixed with the stored filename and line number.
chainerror::Error<T>
in our case is chainerror::Error<&str>
.
Mapping Errors
Now let's get more rust idiomatic by using .context()
directly on the previous Result
.
use chainerror::Context as _; 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(()) } fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { do_some_io().context("func2 error")?; Ok(()) } fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { func2().context("func1 error")?; Ok(()) } fn main() -> Result<(), Box<dyn Error + Send + Sync>> { if let Err(e) = func1() { eprintln!("{:?}", e); std::process::exit(1); } Ok(()) } #[allow(dead_code)] mod chainerror { #![doc = include_str!("../README.md")] #![deny(clippy::all)] #![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] use std::any::TypeId; use std::error::Error as StdError; use std::fmt::{Debug, Display, Formatter}; use std::panic::Location; /// chains an inner error kind `T` with a causing error pub struct Error<T> { occurrence: Option<String>, kind: T, error_cause: Option<Box<dyn StdError + 'static + Send + Sync>>, } /// convenience type alias pub type Result<O, E> = std::result::Result<O, Error<E>>; impl<T: 'static + Display + Debug> Error<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 StdError + '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 StdError + 'static)> { self.iter().last() } /// Find the first error cause of type U, if any exists /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// use chainerror::ErrorDown as _; /// 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(()) /// } /// /// chainerror::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(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { /// func2().context(Func1Error::new("func1 error"))?; /// 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: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<U>) .next() } /// Find the first error cause of type [`Error<U>`](Error), if any exists /// /// Same as `find_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooError); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooError>>(); /// /// // leave out the chainerror::Error<FooError> implementation detail /// err.find_chain_cause::<FooError>(); /// ``` #[inline] pub fn find_chain_cause<U: StdError + 'static>(&self) -> Option<&Error<U>> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<Error<U>>) .next() } /// Find the first error cause of type [`Error<U>`](Error) or `U`, if any exists and return `U` /// /// Same as `find_cause` and `find_chain_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooErrorKind); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooErrorKind>>(); /// // and/or /// err.find_chain_cause::<FooErrorKind>(); /// // and/or /// err.find_cause::<FooErrorKind>(); /// /// // leave out the chainerror::Error<FooErrorKind> implementation detail /// err.find_kind_or_cause::<FooErrorKind>(); /// ``` #[inline] pub fn find_kind_or_cause<U: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(|e| { e.downcast_ref::<Error<U>>() .map(|e| e.kind()) .or_else(|| e.downcast_ref::<U>()) }) .next() } /// Return a reference to T of [`Error<T>`](Error) /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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(()) /// } /// /// chainerror::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() -> chainerror::Result<(), 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 StdError + 'static)> { ErrorIter { current: Some(self), } } } /// Convenience methods for `Result<>` to turn the error into a decorated [`Error`](Error) pub trait Context<O, E: Into<Box<dyn StdError + '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, Error<T>>; /// Decorate the error just with the source `Location` fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>>; /// 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, Error<T>>; } /// Convenience type to just decorate the error with the source `Location` pub struct AnnotatedError(()); impl Display for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl Debug for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> Context<O, E> for std::result::Result<O, E> { #[track_caller] #[inline] fn context<T: 'static + Display + Debug>(self, kind: T) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( AnnotatedError(()), Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn map_context<T: 'static + Display + Debug, F: FnOnce(&E) -> T>( self, op: F, ) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => { let kind = op(&error_cause); Err(Error::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 StdError + 'static)>, } impl<'a> Iterator for ErrorIter<'a> { type Item = &'a (dyn StdError + 'static); #[inline] fn next(&mut self) -> Option<Self::Item> { let current = self.current; self.current = self.current.and_then(StdError::source); current } } impl<T: 'static + Display + Debug> std::ops::Deref for Error<T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.kind } } /// Convenience trait to hide the [`Error<T>`](Error) implementation internals pub trait ErrorDown { /// Test if of type `Error<T>` fn is_chain<T: 'static + Display + Debug>(&self) -> bool; /// Downcast to a reference of `Error<T>` fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>>; /// Downcast to a mutable reference of `Error<T>` fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>>; /// Downcast to T of `Error<T>` fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T>; /// Downcast to T mutable reference of `Error<T>` fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T>; } impl<U: 'static + Display + Debug> ErrorDown for Error<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<&Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(*(self as *const dyn StdError as *const &Error<T>)) } } else { None } } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&mut *(self as *mut dyn StdError as *mut &mut Error<T>)) } } else { None } } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&(*(self as *const dyn StdError as *const &Error<T>)).kind) } } else { None } } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&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 StdError as *mut &mut Error<T>)).kind) } } else { None } } } impl ErrorDown for dyn StdError + 'static { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send + Sync { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl<T: 'static + Display + Debug> StdError for Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> StdError for &mut Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> Display for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.kind)?; if f.alternate() { if let Some(e) = self.source() { write!(f, "\nCaused by:\n {:#}", &e)?; } } Ok(()) } } impl<T: 'static + Display + Debug> Debug for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if f.alternate() { let mut f = f.debug_struct(&format!("Error<{}>", 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() { write!(f, "\nCaused by:\n{:?}", &e)?; } Ok(()) } } } impl<T> From<T> for Error<T> where T: 'static + Display + Debug, { #[track_caller] #[inline] fn from(e: T) -> Error<T> { Error::new(e, None, Some(Location::caller().to_string())) } } /// Convenience macro to create a "new type" T(String) and implement Display + Debug for T /// /// # Examples /// /// ```rust /// # use chainerror::Context as _; /// # use chainerror::ErrorDown as _; /// # 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(()) /// # } /// chainerror::str_context!(Func2Error); /// /// fn func2() -> chainerror::Result<(), Func2Error> { /// let filename = "foo.txt"; /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error>> { /// func2().context(Func1Error::new("func1 error"))?; /// Ok(()) /// } /// # if let Err(e) = func1() { /// # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { /// # assert!(f1err.find_cause::<chainerror::Error<Func2Error>>().is_some()); /// # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); /// # } else { /// # panic!(); /// # } /// # } else { /// # unreachable!(); /// # } /// ``` #[macro_export] macro_rules! str_context { ($e:ident) => { #[derive(Clone)] pub struct $e(pub String); impl $e { pub fn new<S: Into<String>>(s: S) -> Self { $e(s.into()) } } 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 [`Error`](Error) and implements a `kind()` method /// /// It basically hides [`Error`](Error) to the outside and only exposes the [`kind()`](Error::kind) /// method. /// /// Error::kind() returns the ErrorKind /// Error::source() returns the parent error /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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, /// } /// /// chainerror::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! err_kind { ($e:ident, $k:ident) => { pub struct $e($crate::Error<$k>); impl $e { pub fn kind(&self) -> &$k { self.0.kind() } } impl From<$k> for $e { fn from(e: $k) -> Self { $e($crate::Error::new(e, None, None)) } } impl From<$crate::Error<$k>> for $e { fn from(e: $crate::Error<$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) } } }; } }
If you compare the output to the previous example, you will see, that:
Error: examples/tutorial2.rs:20:16: func1 error
changed to just:
examples/tutorial3.rs:17:13: func1 error
This is, because we caught the error of func1()
in main()
and print it out ourselves.
We can now control, whether to output in Debug
or Display
mode.
Maybe depending on --debug
as a CLI argument.
More information
To give more context to the error, you want to use format!
to extend the information in the context string.
use chainerror::Context as _; 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(()) } 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(()) } fn main() -> Result<(), Box<dyn Error + Send + Sync>> { if let Err(e) = func1() { eprintln!("{:?}", e); std::process::exit(1); } Ok(()) } #[allow(dead_code)] mod chainerror { #![doc = include_str!("../README.md")] #![deny(clippy::all)] #![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] use std::any::TypeId; use std::error::Error as StdError; use std::fmt::{Debug, Display, Formatter}; use std::panic::Location; /// chains an inner error kind `T` with a causing error pub struct Error<T> { occurrence: Option<String>, kind: T, error_cause: Option<Box<dyn StdError + 'static + Send + Sync>>, } /// convenience type alias pub type Result<O, E> = std::result::Result<O, Error<E>>; impl<T: 'static + Display + Debug> Error<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 StdError + '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 StdError + 'static)> { self.iter().last() } /// Find the first error cause of type U, if any exists /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// use chainerror::ErrorDown as _; /// 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(()) /// } /// /// chainerror::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(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { /// func2().context(Func1Error::new("func1 error"))?; /// 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: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<U>) .next() } /// Find the first error cause of type [`Error<U>`](Error), if any exists /// /// Same as `find_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooError); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooError>>(); /// /// // leave out the chainerror::Error<FooError> implementation detail /// err.find_chain_cause::<FooError>(); /// ``` #[inline] pub fn find_chain_cause<U: StdError + 'static>(&self) -> Option<&Error<U>> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<Error<U>>) .next() } /// Find the first error cause of type [`Error<U>`](Error) or `U`, if any exists and return `U` /// /// Same as `find_cause` and `find_chain_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooErrorKind); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooErrorKind>>(); /// // and/or /// err.find_chain_cause::<FooErrorKind>(); /// // and/or /// err.find_cause::<FooErrorKind>(); /// /// // leave out the chainerror::Error<FooErrorKind> implementation detail /// err.find_kind_or_cause::<FooErrorKind>(); /// ``` #[inline] pub fn find_kind_or_cause<U: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(|e| { e.downcast_ref::<Error<U>>() .map(|e| e.kind()) .or_else(|| e.downcast_ref::<U>()) }) .next() } /// Return a reference to T of [`Error<T>`](Error) /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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(()) /// } /// /// chainerror::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() -> chainerror::Result<(), 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 StdError + 'static)> { ErrorIter { current: Some(self), } } } /// Convenience methods for `Result<>` to turn the error into a decorated [`Error`](Error) pub trait Context<O, E: Into<Box<dyn StdError + '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, Error<T>>; /// Decorate the error just with the source `Location` fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>>; /// 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, Error<T>>; } /// Convenience type to just decorate the error with the source `Location` pub struct AnnotatedError(()); impl Display for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl Debug for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> Context<O, E> for std::result::Result<O, E> { #[track_caller] #[inline] fn context<T: 'static + Display + Debug>(self, kind: T) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( AnnotatedError(()), Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn map_context<T: 'static + Display + Debug, F: FnOnce(&E) -> T>( self, op: F, ) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => { let kind = op(&error_cause); Err(Error::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 StdError + 'static)>, } impl<'a> Iterator for ErrorIter<'a> { type Item = &'a (dyn StdError + 'static); #[inline] fn next(&mut self) -> Option<Self::Item> { let current = self.current; self.current = self.current.and_then(StdError::source); current } } impl<T: 'static + Display + Debug> std::ops::Deref for Error<T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.kind } } /// Convenience trait to hide the [`Error<T>`](Error) implementation internals pub trait ErrorDown { /// Test if of type `Error<T>` fn is_chain<T: 'static + Display + Debug>(&self) -> bool; /// Downcast to a reference of `Error<T>` fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>>; /// Downcast to a mutable reference of `Error<T>` fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>>; /// Downcast to T of `Error<T>` fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T>; /// Downcast to T mutable reference of `Error<T>` fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T>; } impl<U: 'static + Display + Debug> ErrorDown for Error<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<&Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(*(self as *const dyn StdError as *const &Error<T>)) } } else { None } } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&mut *(self as *mut dyn StdError as *mut &mut Error<T>)) } } else { None } } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&(*(self as *const dyn StdError as *const &Error<T>)).kind) } } else { None } } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&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 StdError as *mut &mut Error<T>)).kind) } } else { None } } } impl ErrorDown for dyn StdError + 'static { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send + Sync { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl<T: 'static + Display + Debug> StdError for Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> StdError for &mut Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> Display for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.kind)?; if f.alternate() { if let Some(e) = self.source() { write!(f, "\nCaused by:\n {:#}", &e)?; } } Ok(()) } } impl<T: 'static + Display + Debug> Debug for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if f.alternate() { let mut f = f.debug_struct(&format!("Error<{}>", 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() { write!(f, "\nCaused by:\n{:?}", &e)?; } Ok(()) } } } impl<T> From<T> for Error<T> where T: 'static + Display + Debug, { #[track_caller] #[inline] fn from(e: T) -> Error<T> { Error::new(e, None, Some(Location::caller().to_string())) } } /// Convenience macro to create a "new type" T(String) and implement Display + Debug for T /// /// # Examples /// /// ```rust /// # use chainerror::Context as _; /// # use chainerror::ErrorDown as _; /// # 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(()) /// # } /// chainerror::str_context!(Func2Error); /// /// fn func2() -> chainerror::Result<(), Func2Error> { /// let filename = "foo.txt"; /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error>> { /// func2().context(Func1Error::new("func1 error"))?; /// Ok(()) /// } /// # if let Err(e) = func1() { /// # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { /// # assert!(f1err.find_cause::<chainerror::Error<Func2Error>>().is_some()); /// # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); /// # } else { /// # panic!(); /// # } /// # } else { /// # unreachable!(); /// # } /// ``` #[macro_export] macro_rules! str_context { ($e:ident) => { #[derive(Clone)] pub struct $e(pub String); impl $e { pub fn new<S: Into<String>>(s: S) -> Self { $e(s.into()) } } 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 [`Error`](Error) and implements a `kind()` method /// /// It basically hides [`Error`](Error) to the outside and only exposes the [`kind()`](Error::kind) /// method. /// /// Error::kind() returns the ErrorKind /// Error::source() returns the parent error /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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, /// } /// /// chainerror::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! err_kind { ($e:ident, $k:ident) => { pub struct $e($crate::Error<$k>); impl $e { pub fn kind(&self) -> &$k { self.0.kind() } } impl From<$k> for $e { fn from(e: $k) -> Self { $e($crate::Error::new(e, None, None)) } } impl From<$crate::Error<$k>> for $e { fn from(e: $crate::Error<$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) } } }; } }
The source() of Errors
Sometimes you want to inspect the source()
of an Error
.
chainerror
implements std::error::Error::source()
, so you can get the cause of an error.
use chainerror::Context as _; 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(()) } 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>> { if let Err(e) = func2() { if let Some(s) = e.source() { eprintln!("func2 failed because of '{}'", s); Err(e).context("func1 error")?; } } Ok(()) } fn main() -> Result<(), Box<dyn Error + Send + Sync>> { if let Err(e) = func1() { eprintln!("{}", e); std::process::exit(1); } Ok(()) } #[allow(dead_code)] mod chainerror { #![doc = include_str!("../README.md")] #![deny(clippy::all)] #![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] use std::any::TypeId; use std::error::Error as StdError; use std::fmt::{Debug, Display, Formatter}; use std::panic::Location; /// chains an inner error kind `T` with a causing error pub struct Error<T> { occurrence: Option<String>, kind: T, error_cause: Option<Box<dyn StdError + 'static + Send + Sync>>, } /// convenience type alias pub type Result<O, E> = std::result::Result<O, Error<E>>; impl<T: 'static + Display + Debug> Error<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 StdError + '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 StdError + 'static)> { self.iter().last() } /// Find the first error cause of type U, if any exists /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// use chainerror::ErrorDown as _; /// 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(()) /// } /// /// chainerror::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(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { /// func2().context(Func1Error::new("func1 error"))?; /// 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: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<U>) .next() } /// Find the first error cause of type [`Error<U>`](Error), if any exists /// /// Same as `find_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooError); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooError>>(); /// /// // leave out the chainerror::Error<FooError> implementation detail /// err.find_chain_cause::<FooError>(); /// ``` #[inline] pub fn find_chain_cause<U: StdError + 'static>(&self) -> Option<&Error<U>> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<Error<U>>) .next() } /// Find the first error cause of type [`Error<U>`](Error) or `U`, if any exists and return `U` /// /// Same as `find_cause` and `find_chain_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooErrorKind); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooErrorKind>>(); /// // and/or /// err.find_chain_cause::<FooErrorKind>(); /// // and/or /// err.find_cause::<FooErrorKind>(); /// /// // leave out the chainerror::Error<FooErrorKind> implementation detail /// err.find_kind_or_cause::<FooErrorKind>(); /// ``` #[inline] pub fn find_kind_or_cause<U: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(|e| { e.downcast_ref::<Error<U>>() .map(|e| e.kind()) .or_else(|| e.downcast_ref::<U>()) }) .next() } /// Return a reference to T of [`Error<T>`](Error) /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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(()) /// } /// /// chainerror::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() -> chainerror::Result<(), 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 StdError + 'static)> { ErrorIter { current: Some(self), } } } /// Convenience methods for `Result<>` to turn the error into a decorated [`Error`](Error) pub trait Context<O, E: Into<Box<dyn StdError + '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, Error<T>>; /// Decorate the error just with the source `Location` fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>>; /// 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, Error<T>>; } /// Convenience type to just decorate the error with the source `Location` pub struct AnnotatedError(()); impl Display for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl Debug for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> Context<O, E> for std::result::Result<O, E> { #[track_caller] #[inline] fn context<T: 'static + Display + Debug>(self, kind: T) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( AnnotatedError(()), Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn map_context<T: 'static + Display + Debug, F: FnOnce(&E) -> T>( self, op: F, ) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => { let kind = op(&error_cause); Err(Error::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 StdError + 'static)>, } impl<'a> Iterator for ErrorIter<'a> { type Item = &'a (dyn StdError + 'static); #[inline] fn next(&mut self) -> Option<Self::Item> { let current = self.current; self.current = self.current.and_then(StdError::source); current } } impl<T: 'static + Display + Debug> std::ops::Deref for Error<T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.kind } } /// Convenience trait to hide the [`Error<T>`](Error) implementation internals pub trait ErrorDown { /// Test if of type `Error<T>` fn is_chain<T: 'static + Display + Debug>(&self) -> bool; /// Downcast to a reference of `Error<T>` fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>>; /// Downcast to a mutable reference of `Error<T>` fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>>; /// Downcast to T of `Error<T>` fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T>; /// Downcast to T mutable reference of `Error<T>` fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T>; } impl<U: 'static + Display + Debug> ErrorDown for Error<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<&Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(*(self as *const dyn StdError as *const &Error<T>)) } } else { None } } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&mut *(self as *mut dyn StdError as *mut &mut Error<T>)) } } else { None } } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&(*(self as *const dyn StdError as *const &Error<T>)).kind) } } else { None } } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&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 StdError as *mut &mut Error<T>)).kind) } } else { None } } } impl ErrorDown for dyn StdError + 'static { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send + Sync { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl<T: 'static + Display + Debug> StdError for Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> StdError for &mut Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> Display for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.kind)?; if f.alternate() { if let Some(e) = self.source() { write!(f, "\nCaused by:\n {:#}", &e)?; } } Ok(()) } } impl<T: 'static + Display + Debug> Debug for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if f.alternate() { let mut f = f.debug_struct(&format!("Error<{}>", 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() { write!(f, "\nCaused by:\n{:?}", &e)?; } Ok(()) } } } impl<T> From<T> for Error<T> where T: 'static + Display + Debug, { #[track_caller] #[inline] fn from(e: T) -> Error<T> { Error::new(e, None, Some(Location::caller().to_string())) } } /// Convenience macro to create a "new type" T(String) and implement Display + Debug for T /// /// # Examples /// /// ```rust /// # use chainerror::Context as _; /// # use chainerror::ErrorDown as _; /// # 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(()) /// # } /// chainerror::str_context!(Func2Error); /// /// fn func2() -> chainerror::Result<(), Func2Error> { /// let filename = "foo.txt"; /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error>> { /// func2().context(Func1Error::new("func1 error"))?; /// Ok(()) /// } /// # if let Err(e) = func1() { /// # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { /// # assert!(f1err.find_cause::<chainerror::Error<Func2Error>>().is_some()); /// # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); /// # } else { /// # panic!(); /// # } /// # } else { /// # unreachable!(); /// # } /// ``` #[macro_export] macro_rules! str_context { ($e:ident) => { #[derive(Clone)] pub struct $e(pub String); impl $e { pub fn new<S: Into<String>>(s: S) -> Self { $e(s.into()) } } 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 [`Error`](Error) and implements a `kind()` method /// /// It basically hides [`Error`](Error) to the outside and only exposes the [`kind()`](Error::kind) /// method. /// /// Error::kind() returns the ErrorKind /// Error::source() returns the parent error /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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, /// } /// /// chainerror::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! err_kind { ($e:ident, $k:ident) => { pub struct $e($crate::Error<$k>); impl $e { pub fn kind(&self) -> &$k { self.0.kind() } } impl From<$k> for $e { fn from(e: $k) -> Self { $e($crate::Error::new(e, None, None)) } } impl From<$crate::Error<$k>> for $e { fn from(e: $crate::Error<$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) } } }; } }
Note, that because we changed the output of the error in main()
from
Debug
to Display
, we don't see the error backtrace with filename and line number.
To use the Display
backtrace, you have to use the alternative display format output {:#}
.
Downcast the Errors
std::error::Error
comes with some helper methods to get to the original object of the
&(dyn Error + 'static)
returned by .source()
.
pub fn downcast_ref<T: Error + 'static>(&self) -> Option<&T>
pub fn downcast_mut<T: Error + 'static>(&mut self) -> Option<&mut T>
This is how it looks like, when using those:
#![allow(clippy::single_match)] #![allow(clippy::redundant_pattern_matching)] use chainerror::Context as _; 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(()) } 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(()) } fn main() -> Result<(), Box<dyn Error + Send + Sync>> { if let Err(e) = func1() { eprintln!("Error: {}", e); let mut s: &(dyn Error) = e.as_ref(); while let Some(c) = s.source() { if let Some(ioerror) = c.downcast_ref::<io::Error>() { 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; } std::process::exit(1); } Ok(()) } #[allow(dead_code)] mod chainerror { #![doc = include_str!("../README.md")] #![deny(clippy::all)] #![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] use std::any::TypeId; use std::error::Error as StdError; use std::fmt::{Debug, Display, Formatter}; use std::panic::Location; /// chains an inner error kind `T` with a causing error pub struct Error<T> { occurrence: Option<String>, kind: T, error_cause: Option<Box<dyn StdError + 'static + Send + Sync>>, } /// convenience type alias pub type Result<O, E> = std::result::Result<O, Error<E>>; impl<T: 'static + Display + Debug> Error<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 StdError + '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 StdError + 'static)> { self.iter().last() } /// Find the first error cause of type U, if any exists /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// use chainerror::ErrorDown as _; /// 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(()) /// } /// /// chainerror::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(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { /// func2().context(Func1Error::new("func1 error"))?; /// 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: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<U>) .next() } /// Find the first error cause of type [`Error<U>`](Error), if any exists /// /// Same as `find_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooError); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooError>>(); /// /// // leave out the chainerror::Error<FooError> implementation detail /// err.find_chain_cause::<FooError>(); /// ``` #[inline] pub fn find_chain_cause<U: StdError + 'static>(&self) -> Option<&Error<U>> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<Error<U>>) .next() } /// Find the first error cause of type [`Error<U>`](Error) or `U`, if any exists and return `U` /// /// Same as `find_cause` and `find_chain_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooErrorKind); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooErrorKind>>(); /// // and/or /// err.find_chain_cause::<FooErrorKind>(); /// // and/or /// err.find_cause::<FooErrorKind>(); /// /// // leave out the chainerror::Error<FooErrorKind> implementation detail /// err.find_kind_or_cause::<FooErrorKind>(); /// ``` #[inline] pub fn find_kind_or_cause<U: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(|e| { e.downcast_ref::<Error<U>>() .map(|e| e.kind()) .or_else(|| e.downcast_ref::<U>()) }) .next() } /// Return a reference to T of [`Error<T>`](Error) /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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(()) /// } /// /// chainerror::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() -> chainerror::Result<(), 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 StdError + 'static)> { ErrorIter { current: Some(self), } } } /// Convenience methods for `Result<>` to turn the error into a decorated [`Error`](Error) pub trait Context<O, E: Into<Box<dyn StdError + '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, Error<T>>; /// Decorate the error just with the source `Location` fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>>; /// 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, Error<T>>; } /// Convenience type to just decorate the error with the source `Location` pub struct AnnotatedError(()); impl Display for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl Debug for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> Context<O, E> for std::result::Result<O, E> { #[track_caller] #[inline] fn context<T: 'static + Display + Debug>(self, kind: T) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( AnnotatedError(()), Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn map_context<T: 'static + Display + Debug, F: FnOnce(&E) -> T>( self, op: F, ) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => { let kind = op(&error_cause); Err(Error::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 StdError + 'static)>, } impl<'a> Iterator for ErrorIter<'a> { type Item = &'a (dyn StdError + 'static); #[inline] fn next(&mut self) -> Option<Self::Item> { let current = self.current; self.current = self.current.and_then(StdError::source); current } } impl<T: 'static + Display + Debug> std::ops::Deref for Error<T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.kind } } /// Convenience trait to hide the [`Error<T>`](Error) implementation internals pub trait ErrorDown { /// Test if of type `Error<T>` fn is_chain<T: 'static + Display + Debug>(&self) -> bool; /// Downcast to a reference of `Error<T>` fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>>; /// Downcast to a mutable reference of `Error<T>` fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>>; /// Downcast to T of `Error<T>` fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T>; /// Downcast to T mutable reference of `Error<T>` fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T>; } impl<U: 'static + Display + Debug> ErrorDown for Error<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<&Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(*(self as *const dyn StdError as *const &Error<T>)) } } else { None } } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&mut *(self as *mut dyn StdError as *mut &mut Error<T>)) } } else { None } } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&(*(self as *const dyn StdError as *const &Error<T>)).kind) } } else { None } } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&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 StdError as *mut &mut Error<T>)).kind) } } else { None } } } impl ErrorDown for dyn StdError + 'static { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send + Sync { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl<T: 'static + Display + Debug> StdError for Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> StdError for &mut Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> Display for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.kind)?; if f.alternate() { if let Some(e) = self.source() { write!(f, "\nCaused by:\n {:#}", &e)?; } } Ok(()) } } impl<T: 'static + Display + Debug> Debug for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if f.alternate() { let mut f = f.debug_struct(&format!("Error<{}>", 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() { write!(f, "\nCaused by:\n{:?}", &e)?; } Ok(()) } } } impl<T> From<T> for Error<T> where T: 'static + Display + Debug, { #[track_caller] #[inline] fn from(e: T) -> Error<T> { Error::new(e, None, Some(Location::caller().to_string())) } } /// Convenience macro to create a "new type" T(String) and implement Display + Debug for T /// /// # Examples /// /// ```rust /// # use chainerror::Context as _; /// # use chainerror::ErrorDown as _; /// # 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(()) /// # } /// chainerror::str_context!(Func2Error); /// /// fn func2() -> chainerror::Result<(), Func2Error> { /// let filename = "foo.txt"; /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error>> { /// func2().context(Func1Error::new("func1 error"))?; /// Ok(()) /// } /// # if let Err(e) = func1() { /// # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { /// # assert!(f1err.find_cause::<chainerror::Error<Func2Error>>().is_some()); /// # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); /// # } else { /// # panic!(); /// # } /// # } else { /// # unreachable!(); /// # } /// ``` #[macro_export] macro_rules! str_context { ($e:ident) => { #[derive(Clone)] pub struct $e(pub String); impl $e { pub fn new<S: Into<String>>(s: S) -> Self { $e(s.into()) } } 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 [`Error`](Error) and implements a `kind()` method /// /// It basically hides [`Error`](Error) to the outside and only exposes the [`kind()`](Error::kind) /// method. /// /// Error::kind() returns the ErrorKind /// Error::source() returns the parent error /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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, /// } /// /// chainerror::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! err_kind { ($e:ident, $k:ident) => { pub struct $e($crate::Error<$k>); impl $e { pub fn kind(&self) -> &$k { self.0.kind() } } impl From<$k> for $e { fn from(e: $k) -> Self { $e($crate::Error::new(e, None, None)) } } impl From<$crate::Error<$k>> for $e { fn from(e: $crate::Error<$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) } } }; } }
The root cause of all Errors
chainerror
also has some helper methods:
fn is_chain<T: 'static + Display + Debug>(&self) -> bool
fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&chainerror::Error<T>>
fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut chainerror::Error<T>>
fn root_cause(&self) -> Option<&(dyn Error + 'static)>
fn find_cause<U: Error + 'static>(&self) -> Option<&U>
fn find_chain_cause<U: Error + 'static>(&self) -> Option<&chainerror::Error<U>>
fn kind<'a>(&'a self) -> &'a T
Using downcast_chain_ref::<String>()
gives a chainerror::Error<String>
, which can be used
to call .find_cause::<io::Error>()
.
if let Some(s) = e.downcast_chain_ref::<String>() {
if let Some(ioerror) = s.find_cause::<io::Error>() {
or to use .root_cause()
, which of course can be of any type implementing std::error::Error
.
if let Some(e) = s.root_cause() {
#![allow(clippy::single_match)] #![allow(clippy::redundant_pattern_matching)] use chainerror::{Context as _, ErrorDown as _}; 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(()) } 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(()) } fn main() -> Result<(), Box<dyn Error + Send + Sync>> { if let Err(e) = func1() { eprintln!("Error: {}", e); if let Some(s) = e.downcast_chain_ref::<String>() { if let Some(ioerror) = s.find_cause::<io::Error>() { eprintln!("caused by: std::io::Error: {}", ioerror); match ioerror.kind() { io::ErrorKind::NotFound => eprintln!("of kind: std::io::ErrorKind::NotFound"), _ => {} } } if let Some(e) = s.root_cause() { let ioerror = e.downcast_ref::<io::Error>().unwrap(); eprintln!("The root cause was: std::io::Error: {:#?}", ioerror); } } std::process::exit(1); } Ok(()) } #[allow(dead_code)] mod chainerror { #![doc = include_str!("../README.md")] #![deny(clippy::all)] #![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] use std::any::TypeId; use std::error::Error as StdError; use std::fmt::{Debug, Display, Formatter}; use std::panic::Location; /// chains an inner error kind `T` with a causing error pub struct Error<T> { occurrence: Option<String>, kind: T, error_cause: Option<Box<dyn StdError + 'static + Send + Sync>>, } /// convenience type alias pub type Result<O, E> = std::result::Result<O, Error<E>>; impl<T: 'static + Display + Debug> Error<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 StdError + '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 StdError + 'static)> { self.iter().last() } /// Find the first error cause of type U, if any exists /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// use chainerror::ErrorDown as _; /// 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(()) /// } /// /// chainerror::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(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { /// func2().context(Func1Error::new("func1 error"))?; /// 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: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<U>) .next() } /// Find the first error cause of type [`Error<U>`](Error), if any exists /// /// Same as `find_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooError); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooError>>(); /// /// // leave out the chainerror::Error<FooError> implementation detail /// err.find_chain_cause::<FooError>(); /// ``` #[inline] pub fn find_chain_cause<U: StdError + 'static>(&self) -> Option<&Error<U>> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<Error<U>>) .next() } /// Find the first error cause of type [`Error<U>`](Error) or `U`, if any exists and return `U` /// /// Same as `find_cause` and `find_chain_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooErrorKind); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooErrorKind>>(); /// // and/or /// err.find_chain_cause::<FooErrorKind>(); /// // and/or /// err.find_cause::<FooErrorKind>(); /// /// // leave out the chainerror::Error<FooErrorKind> implementation detail /// err.find_kind_or_cause::<FooErrorKind>(); /// ``` #[inline] pub fn find_kind_or_cause<U: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(|e| { e.downcast_ref::<Error<U>>() .map(|e| e.kind()) .or_else(|| e.downcast_ref::<U>()) }) .next() } /// Return a reference to T of [`Error<T>`](Error) /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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(()) /// } /// /// chainerror::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() -> chainerror::Result<(), 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 StdError + 'static)> { ErrorIter { current: Some(self), } } } /// Convenience methods for `Result<>` to turn the error into a decorated [`Error`](Error) pub trait Context<O, E: Into<Box<dyn StdError + '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, Error<T>>; /// Decorate the error just with the source `Location` fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>>; /// 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, Error<T>>; } /// Convenience type to just decorate the error with the source `Location` pub struct AnnotatedError(()); impl Display for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl Debug for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> Context<O, E> for std::result::Result<O, E> { #[track_caller] #[inline] fn context<T: 'static + Display + Debug>(self, kind: T) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( AnnotatedError(()), Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn map_context<T: 'static + Display + Debug, F: FnOnce(&E) -> T>( self, op: F, ) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => { let kind = op(&error_cause); Err(Error::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 StdError + 'static)>, } impl<'a> Iterator for ErrorIter<'a> { type Item = &'a (dyn StdError + 'static); #[inline] fn next(&mut self) -> Option<Self::Item> { let current = self.current; self.current = self.current.and_then(StdError::source); current } } impl<T: 'static + Display + Debug> std::ops::Deref for Error<T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.kind } } /// Convenience trait to hide the [`Error<T>`](Error) implementation internals pub trait ErrorDown { /// Test if of type `Error<T>` fn is_chain<T: 'static + Display + Debug>(&self) -> bool; /// Downcast to a reference of `Error<T>` fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>>; /// Downcast to a mutable reference of `Error<T>` fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>>; /// Downcast to T of `Error<T>` fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T>; /// Downcast to T mutable reference of `Error<T>` fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T>; } impl<U: 'static + Display + Debug> ErrorDown for Error<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<&Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(*(self as *const dyn StdError as *const &Error<T>)) } } else { None } } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&mut *(self as *mut dyn StdError as *mut &mut Error<T>)) } } else { None } } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&(*(self as *const dyn StdError as *const &Error<T>)).kind) } } else { None } } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&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 StdError as *mut &mut Error<T>)).kind) } } else { None } } } impl ErrorDown for dyn StdError + 'static { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send + Sync { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl<T: 'static + Display + Debug> StdError for Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> StdError for &mut Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> Display for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.kind)?; if f.alternate() { if let Some(e) = self.source() { write!(f, "\nCaused by:\n {:#}", &e)?; } } Ok(()) } } impl<T: 'static + Display + Debug> Debug for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if f.alternate() { let mut f = f.debug_struct(&format!("Error<{}>", 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() { write!(f, "\nCaused by:\n{:?}", &e)?; } Ok(()) } } } impl<T> From<T> for Error<T> where T: 'static + Display + Debug, { #[track_caller] #[inline] fn from(e: T) -> Error<T> { Error::new(e, None, Some(Location::caller().to_string())) } } /// Convenience macro to create a "new type" T(String) and implement Display + Debug for T /// /// # Examples /// /// ```rust /// # use chainerror::Context as _; /// # use chainerror::ErrorDown as _; /// # 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(()) /// # } /// chainerror::str_context!(Func2Error); /// /// fn func2() -> chainerror::Result<(), Func2Error> { /// let filename = "foo.txt"; /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error>> { /// func2().context(Func1Error::new("func1 error"))?; /// Ok(()) /// } /// # if let Err(e) = func1() { /// # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { /// # assert!(f1err.find_cause::<chainerror::Error<Func2Error>>().is_some()); /// # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); /// # } else { /// # panic!(); /// # } /// # } else { /// # unreachable!(); /// # } /// ``` #[macro_export] macro_rules! str_context { ($e:ident) => { #[derive(Clone)] pub struct $e(pub String); impl $e { pub fn new<S: Into<String>>(s: S) -> Self { $e(s.into()) } } 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 [`Error`](Error) and implements a `kind()` method /// /// It basically hides [`Error`](Error) to the outside and only exposes the [`kind()`](Error::kind) /// method. /// /// Error::kind() returns the ErrorKind /// Error::source() returns the parent error /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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, /// } /// /// chainerror::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! err_kind { ($e:ident, $k:ident) => { pub struct $e($crate::Error<$k>); impl $e { pub fn kind(&self) -> &$k { self.0.kind() } } impl From<$k> for $e { fn from(e: $k) -> Self { $e($crate::Error::new(e, None, None)) } } impl From<$crate::Error<$k>> for $e { fn from(e: $crate::Error<$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) } } }; } }
Finding an Error cause
To distinguish the errors occurring in various places, we can define named string errors with the "new type" pattern.
chainerror::str_context!(Func2Error);
chainerror::str_context!(Func1Error);
Instead of chainerror::Error<String>
we now have struct Func1Error(String)
and chainerror::Error<Func1Error>
.
In the main
function you can see, how we can match the different errors.
Also see:
if let Some(f2err) = f1err.find_chain_cause::<Func2Error>() {
as a shortcut to
if let Some(f2err) = f1err.find_cause::<chainerror::Error<Func2Error>>() {
hiding the chainerror::Error<T>
implementation detail.
use chainerror::{Context as _, ErrorDown as _}; 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(()) } chainerror::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(()) } chainerror::str_context!(Func1Error); fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { func2().context(Func1Error::new("func1 error"))?; Ok(()) } fn main() -> Result<(), Box<dyn Error + Send + Sync>> { if let Err(e) = func1() { if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { eprintln!("Func1Error: {}", f1err); if let Some(f2err) = f1err.find_cause::<chainerror::Error<Func2Error>>() { eprintln!("Func2Error: {}", f2err); } if let Some(f2err) = f1err.find_chain_cause::<Func2Error>() { eprintln!("Debug Func2Error:\n{:?}", f2err); } } std::process::exit(1); } Ok(()) } #[allow(dead_code)] mod chainerror { #![doc = include_str!("../README.md")] #![deny(clippy::all)] #![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] use std::any::TypeId; use std::error::Error as StdError; use std::fmt::{Debug, Display, Formatter}; use std::panic::Location; /// chains an inner error kind `T` with a causing error pub struct Error<T> { occurrence: Option<String>, kind: T, error_cause: Option<Box<dyn StdError + 'static + Send + Sync>>, } /// convenience type alias pub type Result<O, E> = std::result::Result<O, Error<E>>; impl<T: 'static + Display + Debug> Error<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 StdError + '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 StdError + 'static)> { self.iter().last() } /// Find the first error cause of type U, if any exists /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// use chainerror::ErrorDown as _; /// 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(()) /// } /// /// chainerror::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(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { /// func2().context(Func1Error::new("func1 error"))?; /// 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: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<U>) .next() } /// Find the first error cause of type [`Error<U>`](Error), if any exists /// /// Same as `find_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooError); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooError>>(); /// /// // leave out the chainerror::Error<FooError> implementation detail /// err.find_chain_cause::<FooError>(); /// ``` #[inline] pub fn find_chain_cause<U: StdError + 'static>(&self) -> Option<&Error<U>> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<Error<U>>) .next() } /// Find the first error cause of type [`Error<U>`](Error) or `U`, if any exists and return `U` /// /// Same as `find_cause` and `find_chain_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooErrorKind); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooErrorKind>>(); /// // and/or /// err.find_chain_cause::<FooErrorKind>(); /// // and/or /// err.find_cause::<FooErrorKind>(); /// /// // leave out the chainerror::Error<FooErrorKind> implementation detail /// err.find_kind_or_cause::<FooErrorKind>(); /// ``` #[inline] pub fn find_kind_or_cause<U: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(|e| { e.downcast_ref::<Error<U>>() .map(|e| e.kind()) .or_else(|| e.downcast_ref::<U>()) }) .next() } /// Return a reference to T of [`Error<T>`](Error) /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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(()) /// } /// /// chainerror::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() -> chainerror::Result<(), 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 StdError + 'static)> { ErrorIter { current: Some(self), } } } /// Convenience methods for `Result<>` to turn the error into a decorated [`Error`](Error) pub trait Context<O, E: Into<Box<dyn StdError + '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, Error<T>>; /// Decorate the error just with the source `Location` fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>>; /// 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, Error<T>>; } /// Convenience type to just decorate the error with the source `Location` pub struct AnnotatedError(()); impl Display for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl Debug for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> Context<O, E> for std::result::Result<O, E> { #[track_caller] #[inline] fn context<T: 'static + Display + Debug>(self, kind: T) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( AnnotatedError(()), Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn map_context<T: 'static + Display + Debug, F: FnOnce(&E) -> T>( self, op: F, ) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => { let kind = op(&error_cause); Err(Error::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 StdError + 'static)>, } impl<'a> Iterator for ErrorIter<'a> { type Item = &'a (dyn StdError + 'static); #[inline] fn next(&mut self) -> Option<Self::Item> { let current = self.current; self.current = self.current.and_then(StdError::source); current } } impl<T: 'static + Display + Debug> std::ops::Deref for Error<T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.kind } } /// Convenience trait to hide the [`Error<T>`](Error) implementation internals pub trait ErrorDown { /// Test if of type `Error<T>` fn is_chain<T: 'static + Display + Debug>(&self) -> bool; /// Downcast to a reference of `Error<T>` fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>>; /// Downcast to a mutable reference of `Error<T>` fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>>; /// Downcast to T of `Error<T>` fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T>; /// Downcast to T mutable reference of `Error<T>` fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T>; } impl<U: 'static + Display + Debug> ErrorDown for Error<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<&Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(*(self as *const dyn StdError as *const &Error<T>)) } } else { None } } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&mut *(self as *mut dyn StdError as *mut &mut Error<T>)) } } else { None } } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&(*(self as *const dyn StdError as *const &Error<T>)).kind) } } else { None } } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&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 StdError as *mut &mut Error<T>)).kind) } } else { None } } } impl ErrorDown for dyn StdError + 'static { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send + Sync { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl<T: 'static + Display + Debug> StdError for Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> StdError for &mut Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> Display for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.kind)?; if f.alternate() { if let Some(e) = self.source() { write!(f, "\nCaused by:\n {:#}", &e)?; } } Ok(()) } } impl<T: 'static + Display + Debug> Debug for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if f.alternate() { let mut f = f.debug_struct(&format!("Error<{}>", 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() { write!(f, "\nCaused by:\n{:?}", &e)?; } Ok(()) } } } impl<T> From<T> for Error<T> where T: 'static + Display + Debug, { #[track_caller] #[inline] fn from(e: T) -> Error<T> { Error::new(e, None, Some(Location::caller().to_string())) } } /// Convenience macro to create a "new type" T(String) and implement Display + Debug for T /// /// # Examples /// /// ```rust /// # use chainerror::Context as _; /// # use chainerror::ErrorDown as _; /// # 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(()) /// # } /// chainerror::str_context!(Func2Error); /// /// fn func2() -> chainerror::Result<(), Func2Error> { /// let filename = "foo.txt"; /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error>> { /// func2().context(Func1Error::new("func1 error"))?; /// Ok(()) /// } /// # if let Err(e) = func1() { /// # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { /// # assert!(f1err.find_cause::<chainerror::Error<Func2Error>>().is_some()); /// # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); /// # } else { /// # panic!(); /// # } /// # } else { /// # unreachable!(); /// # } /// ``` #[macro_export] macro_rules! str_context { ($e:ident) => { #[derive(Clone)] pub struct $e(pub String); impl $e { pub fn new<S: Into<String>>(s: S) -> Self { $e(s.into()) } } 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 [`Error`](Error) and implements a `kind()` method /// /// It basically hides [`Error`](Error) to the outside and only exposes the [`kind()`](Error::kind) /// method. /// /// Error::kind() returns the ErrorKind /// Error::source() returns the parent error /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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, /// } /// /// chainerror::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! err_kind { ($e:ident, $k:ident) => { pub struct $e($crate::Error<$k>); impl $e { pub fn kind(&self) -> &$k { self.0.kind() } } impl From<$k> for $e { fn from(e: $k) -> Self { $e($crate::Error::new(e, None, None)) } } impl From<$crate::Error<$k>> for $e { fn from(e: $crate::Error<$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) } } }; } }
Selective Error Handling
What about functions returning different Error types?
In this example func1()
can return either Func1ErrorFunc2
or Func1ErrorIO
.
We might want to match
on func1()
with something like:
fn main() -> Result<(), Box<Error + Send + Sync>> {
match func1() {
Err(e) if let Some(s) = e.downcast_chain_ref::<Func1ErrorIO>() =>
eprintln!("Func1ErrorIO:\n{:?}", s),
Err(e) if let Some(s) = e.downcast_chain_ref::<Func1ErrorFunc2>() =>
eprintln!("Func1ErrorFunc2:\n{:?}", s),
Ok(_) => {},
}
Ok(())
}
but this is not valid rust code, so we end up doing it the hard way. In the next chapter, we will see, how to solve this more elegantly.
use chainerror::{Context as _, ErrorDown}; 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(()) } chainerror::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(()) } chainerror::str_context!(Func1ErrorFunc2); chainerror::str_context!(Func1ErrorIO); fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { func2().context(Func1ErrorFunc2::new("func1 error calling func2"))?; let filename = "bar.txt"; do_some_io().context(Func1ErrorIO(format!("Error reading '{}'", filename)))?; Ok(()) } fn main() -> Result<(), Box<dyn Error + Send + Sync>> { if let Err(e) = func1() { if let Some(s) = e.downcast_ref::<chainerror::Error<Func1ErrorIO>>() { eprintln!("Func1ErrorIO:\n{:?}", s); } if let Some(s) = e.downcast_chain_ref::<Func1ErrorFunc2>() { eprintln!("Func1ErrorFunc2:\n{:?}", s); } std::process::exit(1); } Ok(()) } #[allow(dead_code)] mod chainerror { #![doc = include_str!("../README.md")] #![deny(clippy::all)] #![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] use std::any::TypeId; use std::error::Error as StdError; use std::fmt::{Debug, Display, Formatter}; use std::panic::Location; /// chains an inner error kind `T` with a causing error pub struct Error<T> { occurrence: Option<String>, kind: T, error_cause: Option<Box<dyn StdError + 'static + Send + Sync>>, } /// convenience type alias pub type Result<O, E> = std::result::Result<O, Error<E>>; impl<T: 'static + Display + Debug> Error<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 StdError + '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 StdError + 'static)> { self.iter().last() } /// Find the first error cause of type U, if any exists /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// use chainerror::ErrorDown as _; /// 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(()) /// } /// /// chainerror::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(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { /// func2().context(Func1Error::new("func1 error"))?; /// 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: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<U>) .next() } /// Find the first error cause of type [`Error<U>`](Error), if any exists /// /// Same as `find_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooError); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooError>>(); /// /// // leave out the chainerror::Error<FooError> implementation detail /// err.find_chain_cause::<FooError>(); /// ``` #[inline] pub fn find_chain_cause<U: StdError + 'static>(&self) -> Option<&Error<U>> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<Error<U>>) .next() } /// Find the first error cause of type [`Error<U>`](Error) or `U`, if any exists and return `U` /// /// Same as `find_cause` and `find_chain_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooErrorKind); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooErrorKind>>(); /// // and/or /// err.find_chain_cause::<FooErrorKind>(); /// // and/or /// err.find_cause::<FooErrorKind>(); /// /// // leave out the chainerror::Error<FooErrorKind> implementation detail /// err.find_kind_or_cause::<FooErrorKind>(); /// ``` #[inline] pub fn find_kind_or_cause<U: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(|e| { e.downcast_ref::<Error<U>>() .map(|e| e.kind()) .or_else(|| e.downcast_ref::<U>()) }) .next() } /// Return a reference to T of [`Error<T>`](Error) /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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(()) /// } /// /// chainerror::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() -> chainerror::Result<(), 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 StdError + 'static)> { ErrorIter { current: Some(self), } } } /// Convenience methods for `Result<>` to turn the error into a decorated [`Error`](Error) pub trait Context<O, E: Into<Box<dyn StdError + '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, Error<T>>; /// Decorate the error just with the source `Location` fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>>; /// 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, Error<T>>; } /// Convenience type to just decorate the error with the source `Location` pub struct AnnotatedError(()); impl Display for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl Debug for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> Context<O, E> for std::result::Result<O, E> { #[track_caller] #[inline] fn context<T: 'static + Display + Debug>(self, kind: T) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( AnnotatedError(()), Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn map_context<T: 'static + Display + Debug, F: FnOnce(&E) -> T>( self, op: F, ) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => { let kind = op(&error_cause); Err(Error::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 StdError + 'static)>, } impl<'a> Iterator for ErrorIter<'a> { type Item = &'a (dyn StdError + 'static); #[inline] fn next(&mut self) -> Option<Self::Item> { let current = self.current; self.current = self.current.and_then(StdError::source); current } } impl<T: 'static + Display + Debug> std::ops::Deref for Error<T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.kind } } /// Convenience trait to hide the [`Error<T>`](Error) implementation internals pub trait ErrorDown { /// Test if of type `Error<T>` fn is_chain<T: 'static + Display + Debug>(&self) -> bool; /// Downcast to a reference of `Error<T>` fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>>; /// Downcast to a mutable reference of `Error<T>` fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>>; /// Downcast to T of `Error<T>` fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T>; /// Downcast to T mutable reference of `Error<T>` fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T>; } impl<U: 'static + Display + Debug> ErrorDown for Error<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<&Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(*(self as *const dyn StdError as *const &Error<T>)) } } else { None } } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&mut *(self as *mut dyn StdError as *mut &mut Error<T>)) } } else { None } } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&(*(self as *const dyn StdError as *const &Error<T>)).kind) } } else { None } } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&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 StdError as *mut &mut Error<T>)).kind) } } else { None } } } impl ErrorDown for dyn StdError + 'static { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send + Sync { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl<T: 'static + Display + Debug> StdError for Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> StdError for &mut Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> Display for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.kind)?; if f.alternate() { if let Some(e) = self.source() { write!(f, "\nCaused by:\n {:#}", &e)?; } } Ok(()) } } impl<T: 'static + Display + Debug> Debug for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if f.alternate() { let mut f = f.debug_struct(&format!("Error<{}>", 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() { write!(f, "\nCaused by:\n{:?}", &e)?; } Ok(()) } } } impl<T> From<T> for Error<T> where T: 'static + Display + Debug, { #[track_caller] #[inline] fn from(e: T) -> Error<T> { Error::new(e, None, Some(Location::caller().to_string())) } } /// Convenience macro to create a "new type" T(String) and implement Display + Debug for T /// /// # Examples /// /// ```rust /// # use chainerror::Context as _; /// # use chainerror::ErrorDown as _; /// # 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(()) /// # } /// chainerror::str_context!(Func2Error); /// /// fn func2() -> chainerror::Result<(), Func2Error> { /// let filename = "foo.txt"; /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error>> { /// func2().context(Func1Error::new("func1 error"))?; /// Ok(()) /// } /// # if let Err(e) = func1() { /// # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { /// # assert!(f1err.find_cause::<chainerror::Error<Func2Error>>().is_some()); /// # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); /// # } else { /// # panic!(); /// # } /// # } else { /// # unreachable!(); /// # } /// ``` #[macro_export] macro_rules! str_context { ($e:ident) => { #[derive(Clone)] pub struct $e(pub String); impl $e { pub fn new<S: Into<String>>(s: S) -> Self { $e(s.into()) } } 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 [`Error`](Error) and implements a `kind()` method /// /// It basically hides [`Error`](Error) to the outside and only exposes the [`kind()`](Error::kind) /// method. /// /// Error::kind() returns the ErrorKind /// Error::source() returns the parent error /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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, /// } /// /// chainerror::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! err_kind { ($e:ident, $k:ident) => { pub struct $e($crate::Error<$k>); impl $e { pub fn kind(&self) -> &$k { self.0.kind() } } impl From<$k> for $e { fn from(e: $k) -> Self { $e($crate::Error::new(e, None, None)) } } impl From<$crate::Error<$k>> for $e { fn from(e: $crate::Error<$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) } } }; } }
ErrorKind to the rescue
To cope with different kind of errors, we introduce the kind
of an error Func1ErrorKind
with an enum.
Because we derive Debug
and implement Display
our Func1ErrorKind
enum, this enum can be used as
a std::error::Error
.
Only returning Func1ErrorKind
in func1()
now let us get rid of Result<(), Box<Error + Send + Sync>>
and we can
use ChainResult<(), Func1ErrorKind>
.
In main
we can now directly use the methods of chainerror::Error<T>
without downcasting the error first.
Also, a nice match
on chainerror::Error<T>.kind()
is now possible, which returns &T
, meaning &Func1ErrorKind
here.
use chainerror::Context as _; 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(()) } chainerror::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 { 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::error::Error for Func1ErrorKind {} fn func1() -> chainerror::Result<(), 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 { #![doc = include_str!("../README.md")] #![deny(clippy::all)] #![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] use std::any::TypeId; use std::error::Error as StdError; use std::fmt::{Debug, Display, Formatter}; use std::panic::Location; /// chains an inner error kind `T` with a causing error pub struct Error<T> { occurrence: Option<String>, kind: T, error_cause: Option<Box<dyn StdError + 'static + Send + Sync>>, } /// convenience type alias pub type Result<O, E> = std::result::Result<O, Error<E>>; impl<T: 'static + Display + Debug> Error<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 StdError + '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 StdError + 'static)> { self.iter().last() } /// Find the first error cause of type U, if any exists /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// use chainerror::ErrorDown as _; /// 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(()) /// } /// /// chainerror::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(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { /// func2().context(Func1Error::new("func1 error"))?; /// 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: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<U>) .next() } /// Find the first error cause of type [`Error<U>`](Error), if any exists /// /// Same as `find_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooError); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooError>>(); /// /// // leave out the chainerror::Error<FooError> implementation detail /// err.find_chain_cause::<FooError>(); /// ``` #[inline] pub fn find_chain_cause<U: StdError + 'static>(&self) -> Option<&Error<U>> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<Error<U>>) .next() } /// Find the first error cause of type [`Error<U>`](Error) or `U`, if any exists and return `U` /// /// Same as `find_cause` and `find_chain_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooErrorKind); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooErrorKind>>(); /// // and/or /// err.find_chain_cause::<FooErrorKind>(); /// // and/or /// err.find_cause::<FooErrorKind>(); /// /// // leave out the chainerror::Error<FooErrorKind> implementation detail /// err.find_kind_or_cause::<FooErrorKind>(); /// ``` #[inline] pub fn find_kind_or_cause<U: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(|e| { e.downcast_ref::<Error<U>>() .map(|e| e.kind()) .or_else(|| e.downcast_ref::<U>()) }) .next() } /// Return a reference to T of [`Error<T>`](Error) /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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(()) /// } /// /// chainerror::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() -> chainerror::Result<(), 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 StdError + 'static)> { ErrorIter { current: Some(self), } } } /// Convenience methods for `Result<>` to turn the error into a decorated [`Error`](Error) pub trait Context<O, E: Into<Box<dyn StdError + '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, Error<T>>; /// Decorate the error just with the source `Location` fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>>; /// 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, Error<T>>; } /// Convenience type to just decorate the error with the source `Location` pub struct AnnotatedError(()); impl Display for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl Debug for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> Context<O, E> for std::result::Result<O, E> { #[track_caller] #[inline] fn context<T: 'static + Display + Debug>(self, kind: T) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( AnnotatedError(()), Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn map_context<T: 'static + Display + Debug, F: FnOnce(&E) -> T>( self, op: F, ) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => { let kind = op(&error_cause); Err(Error::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 StdError + 'static)>, } impl<'a> Iterator for ErrorIter<'a> { type Item = &'a (dyn StdError + 'static); #[inline] fn next(&mut self) -> Option<Self::Item> { let current = self.current; self.current = self.current.and_then(StdError::source); current } } impl<T: 'static + Display + Debug> std::ops::Deref for Error<T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.kind } } /// Convenience trait to hide the [`Error<T>`](Error) implementation internals pub trait ErrorDown { /// Test if of type `Error<T>` fn is_chain<T: 'static + Display + Debug>(&self) -> bool; /// Downcast to a reference of `Error<T>` fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>>; /// Downcast to a mutable reference of `Error<T>` fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>>; /// Downcast to T of `Error<T>` fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T>; /// Downcast to T mutable reference of `Error<T>` fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T>; } impl<U: 'static + Display + Debug> ErrorDown for Error<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<&Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(*(self as *const dyn StdError as *const &Error<T>)) } } else { None } } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&mut *(self as *mut dyn StdError as *mut &mut Error<T>)) } } else { None } } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&(*(self as *const dyn StdError as *const &Error<T>)).kind) } } else { None } } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&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 StdError as *mut &mut Error<T>)).kind) } } else { None } } } impl ErrorDown for dyn StdError + 'static { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send + Sync { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl<T: 'static + Display + Debug> StdError for Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> StdError for &mut Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> Display for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.kind)?; if f.alternate() { if let Some(e) = self.source() { write!(f, "\nCaused by:\n {:#}", &e)?; } } Ok(()) } } impl<T: 'static + Display + Debug> Debug for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if f.alternate() { let mut f = f.debug_struct(&format!("Error<{}>", 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() { write!(f, "\nCaused by:\n{:?}", &e)?; } Ok(()) } } } impl<T> From<T> for Error<T> where T: 'static + Display + Debug, { #[track_caller] #[inline] fn from(e: T) -> Error<T> { Error::new(e, None, Some(Location::caller().to_string())) } } /// Convenience macro to create a "new type" T(String) and implement Display + Debug for T /// /// # Examples /// /// ```rust /// # use chainerror::Context as _; /// # use chainerror::ErrorDown as _; /// # 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(()) /// # } /// chainerror::str_context!(Func2Error); /// /// fn func2() -> chainerror::Result<(), Func2Error> { /// let filename = "foo.txt"; /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error>> { /// func2().context(Func1Error::new("func1 error"))?; /// Ok(()) /// } /// # if let Err(e) = func1() { /// # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { /// # assert!(f1err.find_cause::<chainerror::Error<Func2Error>>().is_some()); /// # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); /// # } else { /// # panic!(); /// # } /// # } else { /// # unreachable!(); /// # } /// ``` #[macro_export] macro_rules! str_context { ($e:ident) => { #[derive(Clone)] pub struct $e(pub String); impl $e { pub fn new<S: Into<String>>(s: S) -> Self { $e(s.into()) } } 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 [`Error`](Error) and implements a `kind()` method /// /// It basically hides [`Error`](Error) to the outside and only exposes the [`kind()`](Error::kind) /// method. /// /// Error::kind() returns the ErrorKind /// Error::source() returns the parent error /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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, /// } /// /// chainerror::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! err_kind { ($e:ident, $k:ident) => { pub struct $e($crate::Error<$k>); impl $e { pub fn kind(&self) -> &$k { self.0.kind() } } impl From<$k> for $e { fn from(e: $k) -> Self { $e($crate::Error::new(e, None, None)) } } impl From<$crate::Error<$k>> for $e { fn from(e: $crate::Error<$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) } } }; } }
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::Context as _; 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(()) } chainerror::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() -> chainerror::Result<(), 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 { #![doc = include_str!("../README.md")] #![deny(clippy::all)] #![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] use std::any::TypeId; use std::error::Error as StdError; use std::fmt::{Debug, Display, Formatter}; use std::panic::Location; /// chains an inner error kind `T` with a causing error pub struct Error<T> { occurrence: Option<String>, kind: T, error_cause: Option<Box<dyn StdError + 'static + Send + Sync>>, } /// convenience type alias pub type Result<O, E> = std::result::Result<O, Error<E>>; impl<T: 'static + Display + Debug> Error<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 StdError + '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 StdError + 'static)> { self.iter().last() } /// Find the first error cause of type U, if any exists /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// use chainerror::ErrorDown as _; /// 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(()) /// } /// /// chainerror::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(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { /// func2().context(Func1Error::new("func1 error"))?; /// 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: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<U>) .next() } /// Find the first error cause of type [`Error<U>`](Error), if any exists /// /// Same as `find_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooError); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooError>>(); /// /// // leave out the chainerror::Error<FooError> implementation detail /// err.find_chain_cause::<FooError>(); /// ``` #[inline] pub fn find_chain_cause<U: StdError + 'static>(&self) -> Option<&Error<U>> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<Error<U>>) .next() } /// Find the first error cause of type [`Error<U>`](Error) or `U`, if any exists and return `U` /// /// Same as `find_cause` and `find_chain_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooErrorKind); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooErrorKind>>(); /// // and/or /// err.find_chain_cause::<FooErrorKind>(); /// // and/or /// err.find_cause::<FooErrorKind>(); /// /// // leave out the chainerror::Error<FooErrorKind> implementation detail /// err.find_kind_or_cause::<FooErrorKind>(); /// ``` #[inline] pub fn find_kind_or_cause<U: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(|e| { e.downcast_ref::<Error<U>>() .map(|e| e.kind()) .or_else(|| e.downcast_ref::<U>()) }) .next() } /// Return a reference to T of [`Error<T>`](Error) /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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(()) /// } /// /// chainerror::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() -> chainerror::Result<(), 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 StdError + 'static)> { ErrorIter { current: Some(self), } } } /// Convenience methods for `Result<>` to turn the error into a decorated [`Error`](Error) pub trait Context<O, E: Into<Box<dyn StdError + '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, Error<T>>; /// Decorate the error just with the source `Location` fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>>; /// 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, Error<T>>; } /// Convenience type to just decorate the error with the source `Location` pub struct AnnotatedError(()); impl Display for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl Debug for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> Context<O, E> for std::result::Result<O, E> { #[track_caller] #[inline] fn context<T: 'static + Display + Debug>(self, kind: T) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( AnnotatedError(()), Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn map_context<T: 'static + Display + Debug, F: FnOnce(&E) -> T>( self, op: F, ) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => { let kind = op(&error_cause); Err(Error::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 StdError + 'static)>, } impl<'a> Iterator for ErrorIter<'a> { type Item = &'a (dyn StdError + 'static); #[inline] fn next(&mut self) -> Option<Self::Item> { let current = self.current; self.current = self.current.and_then(StdError::source); current } } impl<T: 'static + Display + Debug> std::ops::Deref for Error<T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.kind } } /// Convenience trait to hide the [`Error<T>`](Error) implementation internals pub trait ErrorDown { /// Test if of type `Error<T>` fn is_chain<T: 'static + Display + Debug>(&self) -> bool; /// Downcast to a reference of `Error<T>` fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>>; /// Downcast to a mutable reference of `Error<T>` fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>>; /// Downcast to T of `Error<T>` fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T>; /// Downcast to T mutable reference of `Error<T>` fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T>; } impl<U: 'static + Display + Debug> ErrorDown for Error<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<&Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(*(self as *const dyn StdError as *const &Error<T>)) } } else { None } } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&mut *(self as *mut dyn StdError as *mut &mut Error<T>)) } } else { None } } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&(*(self as *const dyn StdError as *const &Error<T>)).kind) } } else { None } } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&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 StdError as *mut &mut Error<T>)).kind) } } else { None } } } impl ErrorDown for dyn StdError + 'static { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send + Sync { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl<T: 'static + Display + Debug> StdError for Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> StdError for &mut Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> Display for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.kind)?; if f.alternate() { if let Some(e) = self.source() { write!(f, "\nCaused by:\n {:#}", &e)?; } } Ok(()) } } impl<T: 'static + Display + Debug> Debug for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if f.alternate() { let mut f = f.debug_struct(&format!("Error<{}>", 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() { write!(f, "\nCaused by:\n{:?}", &e)?; } Ok(()) } } } impl<T> From<T> for Error<T> where T: 'static + Display + Debug, { #[track_caller] #[inline] fn from(e: T) -> Error<T> { Error::new(e, None, Some(Location::caller().to_string())) } } /// Convenience macro to create a "new type" T(String) and implement Display + Debug for T /// /// # Examples /// /// ```rust /// # use chainerror::Context as _; /// # use chainerror::ErrorDown as _; /// # 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(()) /// # } /// chainerror::str_context!(Func2Error); /// /// fn func2() -> chainerror::Result<(), Func2Error> { /// let filename = "foo.txt"; /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error>> { /// func2().context(Func1Error::new("func1 error"))?; /// Ok(()) /// } /// # if let Err(e) = func1() { /// # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { /// # assert!(f1err.find_cause::<chainerror::Error<Func2Error>>().is_some()); /// # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); /// # } else { /// # panic!(); /// # } /// # } else { /// # unreachable!(); /// # } /// ``` #[macro_export] macro_rules! str_context { ($e:ident) => { #[derive(Clone)] pub struct $e(pub String); impl $e { pub fn new<S: Into<String>>(s: S) -> Self { $e(s.into()) } } 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 [`Error`](Error) and implements a `kind()` method /// /// It basically hides [`Error`](Error) to the outside and only exposes the [`kind()`](Error::kind) /// method. /// /// Error::kind() returns the ErrorKind /// Error::source() returns the parent error /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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, /// } /// /// chainerror::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! err_kind { ($e:ident, $k:ident) => { pub struct $e($crate::Error<$k>); impl $e { pub fn kind(&self) -> &$k { self.0.kind() } } impl From<$k> for $e { fn from(e: $k) -> Self { $e($crate::Error::new(e, None, None)) } } impl From<$crate::Error<$k>> for $e { fn from(e: $crate::Error<$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) } } }; } }
Deref for the ErrorKind
Because chainerror::Error*e
instead of e.kind()
or call a function with &e
use chainerror::Context as _; 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(()) } chainerror::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() -> chainerror::Result<(), Func1ErrorKind> { func2().context(Func1ErrorKind::Func2)?; let filename = String::from("bar.txt"); do_some_io().context(Func1ErrorKind::IO(filename))?; Ok(()) } fn handle_func1errorkind(e: &Func1ErrorKind) { match e { Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), Func1ErrorKind::IO(ref filename) => { eprintln!("Main Error Report: func1 error reading '{}'", filename) } } } fn main() -> Result<(), Box<dyn Error + Send + Sync>> { if let Err(e) = func1() { match *e { Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), Func1ErrorKind::IO(ref filename) => { eprintln!("Main Error Report: func1 error reading '{}'", filename) } } handle_func1errorkind(&e); 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 { #![doc = include_str!("../README.md")] #![deny(clippy::all)] #![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] use std::any::TypeId; use std::error::Error as StdError; use std::fmt::{Debug, Display, Formatter}; use std::panic::Location; /// chains an inner error kind `T` with a causing error pub struct Error<T> { occurrence: Option<String>, kind: T, error_cause: Option<Box<dyn StdError + 'static + Send + Sync>>, } /// convenience type alias pub type Result<O, E> = std::result::Result<O, Error<E>>; impl<T: 'static + Display + Debug> Error<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 StdError + '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 StdError + 'static)> { self.iter().last() } /// Find the first error cause of type U, if any exists /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// use chainerror::ErrorDown as _; /// 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(()) /// } /// /// chainerror::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(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { /// func2().context(Func1Error::new("func1 error"))?; /// 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: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<U>) .next() } /// Find the first error cause of type [`Error<U>`](Error), if any exists /// /// Same as `find_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooError); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooError>>(); /// /// // leave out the chainerror::Error<FooError> implementation detail /// err.find_chain_cause::<FooError>(); /// ``` #[inline] pub fn find_chain_cause<U: StdError + 'static>(&self) -> Option<&Error<U>> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<Error<U>>) .next() } /// Find the first error cause of type [`Error<U>`](Error) or `U`, if any exists and return `U` /// /// Same as `find_cause` and `find_chain_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooErrorKind); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooErrorKind>>(); /// // and/or /// err.find_chain_cause::<FooErrorKind>(); /// // and/or /// err.find_cause::<FooErrorKind>(); /// /// // leave out the chainerror::Error<FooErrorKind> implementation detail /// err.find_kind_or_cause::<FooErrorKind>(); /// ``` #[inline] pub fn find_kind_or_cause<U: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(|e| { e.downcast_ref::<Error<U>>() .map(|e| e.kind()) .or_else(|| e.downcast_ref::<U>()) }) .next() } /// Return a reference to T of [`Error<T>`](Error) /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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(()) /// } /// /// chainerror::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() -> chainerror::Result<(), 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 StdError + 'static)> { ErrorIter { current: Some(self), } } } /// Convenience methods for `Result<>` to turn the error into a decorated [`Error`](Error) pub trait Context<O, E: Into<Box<dyn StdError + '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, Error<T>>; /// Decorate the error just with the source `Location` fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>>; /// 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, Error<T>>; } /// Convenience type to just decorate the error with the source `Location` pub struct AnnotatedError(()); impl Display for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl Debug for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> Context<O, E> for std::result::Result<O, E> { #[track_caller] #[inline] fn context<T: 'static + Display + Debug>(self, kind: T) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( AnnotatedError(()), Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn map_context<T: 'static + Display + Debug, F: FnOnce(&E) -> T>( self, op: F, ) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => { let kind = op(&error_cause); Err(Error::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 StdError + 'static)>, } impl<'a> Iterator for ErrorIter<'a> { type Item = &'a (dyn StdError + 'static); #[inline] fn next(&mut self) -> Option<Self::Item> { let current = self.current; self.current = self.current.and_then(StdError::source); current } } impl<T: 'static + Display + Debug> std::ops::Deref for Error<T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.kind } } /// Convenience trait to hide the [`Error<T>`](Error) implementation internals pub trait ErrorDown { /// Test if of type `Error<T>` fn is_chain<T: 'static + Display + Debug>(&self) -> bool; /// Downcast to a reference of `Error<T>` fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>>; /// Downcast to a mutable reference of `Error<T>` fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>>; /// Downcast to T of `Error<T>` fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T>; /// Downcast to T mutable reference of `Error<T>` fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T>; } impl<U: 'static + Display + Debug> ErrorDown for Error<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<&Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(*(self as *const dyn StdError as *const &Error<T>)) } } else { None } } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&mut *(self as *mut dyn StdError as *mut &mut Error<T>)) } } else { None } } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&(*(self as *const dyn StdError as *const &Error<T>)).kind) } } else { None } } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&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 StdError as *mut &mut Error<T>)).kind) } } else { None } } } impl ErrorDown for dyn StdError + 'static { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send + Sync { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl<T: 'static + Display + Debug> StdError for Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> StdError for &mut Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> Display for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.kind)?; if f.alternate() { if let Some(e) = self.source() { write!(f, "\nCaused by:\n {:#}", &e)?; } } Ok(()) } } impl<T: 'static + Display + Debug> Debug for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if f.alternate() { let mut f = f.debug_struct(&format!("Error<{}>", 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() { write!(f, "\nCaused by:\n{:?}", &e)?; } Ok(()) } } } impl<T> From<T> for Error<T> where T: 'static + Display + Debug, { #[track_caller] #[inline] fn from(e: T) -> Error<T> { Error::new(e, None, Some(Location::caller().to_string())) } } /// Convenience macro to create a "new type" T(String) and implement Display + Debug for T /// /// # Examples /// /// ```rust /// # use chainerror::Context as _; /// # use chainerror::ErrorDown as _; /// # 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(()) /// # } /// chainerror::str_context!(Func2Error); /// /// fn func2() -> chainerror::Result<(), Func2Error> { /// let filename = "foo.txt"; /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error>> { /// func2().context(Func1Error::new("func1 error"))?; /// Ok(()) /// } /// # if let Err(e) = func1() { /// # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { /// # assert!(f1err.find_cause::<chainerror::Error<Func2Error>>().is_some()); /// # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); /// # } else { /// # panic!(); /// # } /// # } else { /// # unreachable!(); /// # } /// ``` #[macro_export] macro_rules! str_context { ($e:ident) => { #[derive(Clone)] pub struct $e(pub String); impl $e { pub fn new<S: Into<String>>(s: S) -> Self { $e(s.into()) } } 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 [`Error`](Error) and implements a `kind()` method /// /// It basically hides [`Error`](Error) to the outside and only exposes the [`kind()`](Error::kind) /// method. /// /// Error::kind() returns the ErrorKind /// Error::source() returns the parent error /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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, /// } /// /// chainerror::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! err_kind { ($e:ident, $k:ident) => { pub struct $e($crate::Error<$k>); impl $e { pub fn kind(&self) -> &$k { self.0.kind() } } impl From<$k> for $e { fn from(e: $k) -> Self { $e($crate::Error::new(e, None, None)) } } impl From<$crate::Error<$k>> for $e { fn from(e: $crate::Error<$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) } } }; } }
Writing a library
I would advise to only expose an mycrate::ErrorKind
and type alias mycrate::Error
to chainerror::Error<mycrate::ErrorKind>
so you can tell your library users to use the .kind()
method as std::io::Error
does.
If you later decide to make your own Error
implementation, your library users don't
have to change much or anything.
#[allow(dead_code)] #[macro_use] pub mod chainerror { #![doc = include_str!("../README.md")] #![deny(clippy::all)] #![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] use std::any::TypeId; use std::error::Error as StdError; use std::fmt::{Debug, Display, Formatter}; use std::panic::Location; /// chains an inner error kind `T` with a causing error pub struct Error<T> { occurrence: Option<String>, kind: T, error_cause: Option<Box<dyn StdError + 'static + Send + Sync>>, } /// convenience type alias pub type Result<O, E> = std::result::Result<O, Error<E>>; impl<T: 'static + Display + Debug> Error<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 StdError + '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 StdError + 'static)> { self.iter().last() } /// Find the first error cause of type U, if any exists /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// use chainerror::ErrorDown as _; /// 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(()) /// } /// /// chainerror::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(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { /// func2().context(Func1Error::new("func1 error"))?; /// 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: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<U>) .next() } /// Find the first error cause of type [`Error<U>`](Error), if any exists /// /// Same as `find_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooError); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooError>>(); /// /// // leave out the chainerror::Error<FooError> implementation detail /// err.find_chain_cause::<FooError>(); /// ``` #[inline] pub fn find_chain_cause<U: StdError + 'static>(&self) -> Option<&Error<U>> { self.iter() .filter_map(<dyn StdError>::downcast_ref::<Error<U>>) .next() } /// Find the first error cause of type [`Error<U>`](Error) or `U`, if any exists and return `U` /// /// Same as `find_cause` and `find_chain_cause`, but hides the [`Error<U>`](Error) implementation internals /// /// # Examples /// /// ```rust /// # chainerror::str_context!(FooErrorKind); /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::<chainerror::Error<FooErrorKind>>(); /// // and/or /// err.find_chain_cause::<FooErrorKind>(); /// // and/or /// err.find_cause::<FooErrorKind>(); /// /// // leave out the chainerror::Error<FooErrorKind> implementation detail /// err.find_kind_or_cause::<FooErrorKind>(); /// ``` #[inline] pub fn find_kind_or_cause<U: StdError + 'static>(&self) -> Option<&U> { self.iter() .filter_map(|e| { e.downcast_ref::<Error<U>>() .map(|e| e.kind()) .or_else(|| e.downcast_ref::<U>()) }) .next() } /// Return a reference to T of [`Error<T>`](Error) /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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(()) /// } /// /// chainerror::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() -> chainerror::Result<(), 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 StdError + 'static)> { ErrorIter { current: Some(self), } } } /// Convenience methods for `Result<>` to turn the error into a decorated [`Error`](Error) pub trait Context<O, E: Into<Box<dyn StdError + '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, Error<T>>; /// Decorate the error just with the source `Location` fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>>; /// 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, Error<T>>; } /// Convenience type to just decorate the error with the source `Location` pub struct AnnotatedError(()); impl Display for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl Debug for AnnotatedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "(passed error)") } } impl<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> Context<O, E> for std::result::Result<O, E> { #[track_caller] #[inline] fn context<T: 'static + Display + Debug>(self, kind: T) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>> { match self { Ok(t) => Ok(t), Err(error_cause) => Err(Error::new( AnnotatedError(()), Some(error_cause.into()), Some(Location::caller().to_string()), )), } } #[track_caller] #[inline] fn map_context<T: 'static + Display + Debug, F: FnOnce(&E) -> T>( self, op: F, ) -> std::result::Result<O, Error<T>> { match self { Ok(t) => Ok(t), Err(error_cause) => { let kind = op(&error_cause); Err(Error::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 StdError + 'static)>, } impl<'a> Iterator for ErrorIter<'a> { type Item = &'a (dyn StdError + 'static); #[inline] fn next(&mut self) -> Option<Self::Item> { let current = self.current; self.current = self.current.and_then(StdError::source); current } } impl<T: 'static + Display + Debug> std::ops::Deref for Error<T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.kind } } /// Convenience trait to hide the [`Error<T>`](Error) implementation internals pub trait ErrorDown { /// Test if of type `Error<T>` fn is_chain<T: 'static + Display + Debug>(&self) -> bool; /// Downcast to a reference of `Error<T>` fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>>; /// Downcast to a mutable reference of `Error<T>` fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>>; /// Downcast to T of `Error<T>` fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T>; /// Downcast to T mutable reference of `Error<T>` fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T>; } impl<U: 'static + Display + Debug> ErrorDown for Error<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<&Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(*(self as *const dyn StdError as *const &Error<T>)) } } else { None } } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&mut *(self as *mut dyn StdError as *mut &mut Error<T>)) } } else { None } } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { if self.is_chain::<T>() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] Some(&(*(self as *const dyn StdError as *const &Error<T>)).kind) } } else { None } } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&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 StdError as *mut &mut Error<T>)).kind) } } else { None } } } impl ErrorDown for dyn StdError + 'static { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl ErrorDown for dyn StdError + 'static + Send + Sync { #[inline] fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<Error<T>>() } #[inline] fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> { self.downcast_ref::<Error<T>>() } #[inline] fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> { self.downcast_mut::<Error<T>>() } #[inline] fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> { self.downcast_ref::<T>() .or_else(|| self.downcast_ref::<Error<T>>().map(|e| e.kind())) } #[inline] fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> { if self.is::<T>() { return self.downcast_mut::<T>(); } self.downcast_mut::<Error<T>>() .and_then(|e| e.downcast_inner_mut::<T>()) } } impl<T: 'static + Display + Debug> StdError for Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> StdError for &mut Error<T> { #[inline] fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } impl<T: 'static + Display + Debug> Display for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.kind)?; if f.alternate() { if let Some(e) = self.source() { write!(f, "\nCaused by:\n {:#}", &e)?; } } Ok(()) } } impl<T: 'static + Display + Debug> Debug for Error<T> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if f.alternate() { let mut f = f.debug_struct(&format!("Error<{}>", 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() { write!(f, "\nCaused by:\n{:?}", &e)?; } Ok(()) } } } impl<T> From<T> for Error<T> where T: 'static + Display + Debug, { #[track_caller] #[inline] fn from(e: T) -> Error<T> { Error::new(e, None, Some(Location::caller().to_string())) } } /// Convenience macro to create a "new type" T(String) and implement Display + Debug for T /// /// # Examples /// /// ```rust /// # use chainerror::Context as _; /// # use chainerror::ErrorDown as _; /// # 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(()) /// # } /// chainerror::str_context!(Func2Error); /// /// fn func2() -> chainerror::Result<(), Func2Error> { /// let filename = "foo.txt"; /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box<dyn Error>> { /// func2().context(Func1Error::new("func1 error"))?; /// Ok(()) /// } /// # if let Err(e) = func1() { /// # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { /// # assert!(f1err.find_cause::<chainerror::Error<Func2Error>>().is_some()); /// # assert!(f1err.find_chain_cause::<Func2Error>().is_some()); /// # } else { /// # panic!(); /// # } /// # } else { /// # unreachable!(); /// # } /// ``` #[macro_export] macro_rules! str_context { ($e:ident) => { #[derive(Clone)] pub struct $e(pub String); impl $e { pub fn new<S: Into<String>>(s: S) -> Self { $e(s.into()) } } 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 [`Error`](Error) and implements a `kind()` method /// /// It basically hides [`Error`](Error) to the outside and only exposes the [`kind()`](Error::kind) /// method. /// /// Error::kind() returns the ErrorKind /// Error::source() returns the parent error /// /// # Examples /// /// ```rust /// use chainerror::Context as _; /// 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, /// } /// /// chainerror::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! err_kind { ($e:ident, $k:ident) => { pub struct $e($crate::Error<$k>); impl $e { pub fn kind(&self) -> &$k { self.0.kind() } } impl From<$k> for $e { fn from(e: $k) -> Self { $e($crate::Error::new(e, None, None)) } } impl From<$crate::Error<$k>> for $e { fn from(e: $crate::Error<$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) } } }; } } pub mod mycrate { use crate::chainerror::*; // omit the `crate::` part pub mod mycrate { use chainerror::Context as _; use std::io; fn do_some_io() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } chainerror::str_context!(Func2Error); fn func2() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> { let filename = "foo.txt"; do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; Ok(()) } #[derive(Debug, Clone)] pub enum ErrorKind { Func2, IO(String), } chainerror::err_kind!(Error, ErrorKind); pub type Result<T> = std::result::Result<T, Error>; 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), } } } pub fn func1() -> Result<()> { func2().context(ErrorKind::Func2)?; let filename = String::from("bar.txt"); do_some_io().context(ErrorKind::IO(filename))?; Ok(()) } } fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 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: &dyn Error = &e; while let Some(c) = s.source() { if let Some(ioerror) = c.downcast_ref::<io::Error>() { 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); std::process::exit(1); } Ok(()) }
Going back to std
Not using chainerror
and going full std
would look like this:
Btw, the code size is bigger than using chainerror
:-)
#![allow(clippy::single_match)] #![allow(clippy::redundant_pattern_matching)] pub mod mycrate { use std::error::Error as StdError; use self::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! mcontext { ( $k:expr ) => {{ |e| { Error( $k, Some(Box::from(e)), Some(concat!(file!(), ":", line!(), ": ")), ) } }}; } pub struct Error( ErrorKind, Option<Box<dyn std::error::Error + 'static>>, Option<&'static str>, ); impl Error { pub fn kind(&self) -> &ErrorKind { &self.0 } } impl From<ErrorKind> 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<dyn std::error::Error>> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } pub fn func2() -> std::result::Result<(), Error> { let filename = "foo.txt"; do_some_io().map_err(mcontext!(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! mcontext { ( $k:expr ) => {{ |e| { Error( $k, Some(Box::from(e)), Some(concat!(file!(), ":", line!(), ": ")), ) } }}; } pub struct Error( ErrorKind, Option<Box<dyn std::error::Error + 'static>>, Option<&'static str>, ); impl Error { pub fn kind(&self) -> &ErrorKind { &self.0 } } impl From<ErrorKind> 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<T> = std::result::Result<T, Error>; pub fn func1() -> Result<()> { func2().map_err(mcontext!(ErrorKind::Func2))?; let filename = String::from("bar.txt"); do_some_io().map_err(mcontext!(ErrorKind::IO(filename)))?; Ok(()) } } fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 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: &dyn Error = &e; while let Some(c) = s.source() { if let Some(ioerror) = c.downcast_ref::<io::Error>() { 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); std::process::exit(1); } Ok(()) }
The End
That's it for now…
Happy error handling!
To report issues, submit pull request or for the source code, examples and the book source, visit the Git Repo.