pub trait ChainError: ::std::error::Error + Sized { fn new( line: u32, filename: &'static str, description: Option, error_cause: Option>, ) -> Self; fn root_cause(&self) -> Option<&(dyn std::error::Error + 'static)>; fn find_cause( &self, ) -> Option<&(dyn std::error::Error + 'static)>; } pub trait ChainErrorFrom: ChainError { fn chain_error_from(_: T, _: u32, _: &'static str, _: Option) -> Self; } pub trait IntoChainError: Sized { fn into_chain_error(self, line: u32, filename: &'static str, description: Option) -> T; } impl IntoChainError for T where U: ChainErrorFrom + ChainError, { fn into_chain_error(self, line: u32, filename: &'static str, description: Option) -> U { U::chain_error_from(self, line, filename, description) } } #[macro_export] macro_rules! chain_error_fn { ( $t:ident, $v:expr $(, $more:expr)* ) => { |e| <$t> :: new(line!(), file!(), Some(format!($v, $( $more , )* )), Some(e.into())) }; ( $t:path, $v:expr $(, $more:expr)* ) => { |e| <$t> :: new(line!(), file!(), Some(format!($v, $( $more , )* )), Some(e.into())) }; ( $t:ident) => { |e| <$t> :: new(line!(), file!(), None, Some(e.into())) }; ( $t:path) => { |e| <$t> :: new(line!(), file!(), None, Some(e.into())) }; } #[macro_export] macro_rules! into_boxed_chain_error_fn { ( $v:expr $(, $more:expr)* ) => { |e| Box::::from(e).into_chain_error(line!(), file!(), Some(format!($v, $( $more , )* ))) }; ( ) => { |e| Box::::from(e).into_chain_error(line!(), file!(), None) }; } #[macro_export] macro_rules! chain { ( $v:expr $(, $more:expr)* ) => { |e| Box::::from(e).into_chain_error(line!(), file!(), Some(format!($v, $( $more , )* ))) }; ( ) => { |e| Box::::from(e).into_chain_error(line!(), file!(), None) }; } #[macro_export] macro_rules! into_chain_error_fn { ( $v:expr $(, $more:expr)* ) => { |e| e.into_chain_error(line!(), file!(), Some(format!($v, $( $more , )* ))) }; ( ) => { |e| e.into_chain_error(line!(), file!(), None) }; } #[macro_export] macro_rules! chain_error_from_fn { ( $t:expr, $v:expr $(, $more:expr)* ) => { |e| ($t).into().chain_error_from(e, line!(), file!(), Some(format!($v, $( $more , )* ))) }; ( $t:expr ) => { |e| ($t).into().chain_error_from(e, line!(), file!(), None) }; } #[macro_export] macro_rules! chain_error { ( $t:ident, $v:expr $(, $more:expr)* ) => { <$t> :: new(line!(), file!(), Some(format!($v, $( $more , )*)), None) }; ( $t:path, $v:expr $(, $more:expr)* ) => { <$t> :: new(line!(), file!(), Some(format!($v, $( $more , )*)), None) }; } #[macro_export] macro_rules! into_chain_error { ( $t:expr, $v:expr $(, $more:expr)* ) => { $t . into_chain_error(line!(), file!(), Some(format!($v, $( $more , )*))) }; ( $t:expr ) => { $t . into_chain_error(line!(), file!(), None) }; } #[macro_export] macro_rules! derive_chain_error { ($e:ident) => { pub struct $e { line: u32, filename: &'static str, description: Option, error_cause: Option>, } impl ChainError for $e { fn new( line: u32, filename: &'static str, description: Option, error_cause: Option>, ) -> Self { $e { line, filename, description, error_cause, } } fn root_cause(&self) -> Option<&(dyn std::error::Error + 'static)> { let mut cause = self as &(dyn std::error::Error + 'static); while let Some(c) = cause.source() { cause = c; } Some(cause) } fn find_cause( &self, ) -> Option<&(dyn std::error::Error + 'static)> { let mut cause = self as &(dyn std::error::Error + 'static); loop { if cause.is::() { return Some(cause); } match cause.source() { Some(c) => cause = c, None => return None, } } } } impl ::std::error::Error for $e { fn description(&self) -> &str { if let Some(ref d) = self.description { d.as_ref() } else { "" } } fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { if let Some(ref e) = self.error_cause { Some(e.as_ref()) } else { None } } } impl ::std::fmt::Display for $e { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { writeln!(f, "{}", self.description())?; if let Some(e) = self.source() { writeln!(f, "\nCaused by:")?; ::std::fmt::Display::fmt(&e, f)?; } Ok(()) } } impl ::std::fmt::Debug for $e { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { writeln!(f, "{}:{}: {}", self.filename, self.line, self.description())?; if let Some(e) = self.source() { writeln!(f, "\nCaused by:")?; ::std::fmt::Debug::fmt(&e, f)?; } Ok(()) } } }; } pub mod prelude { pub use super::{ chain_error, chain_error_fn, chain_error_from_fn, derive_chain_error, into_chain_error, into_chain_error_fn, ChainError, ChainErrorFrom, IntoChainError, }; } #[cfg(test)] mod tests { use std::error::Error; use crate::prelude::*; derive_chain_error!(MyError); derive_chain_error!(MyMainError); impl ChainErrorFrom> for MyMainError { fn chain_error_from( e: Box, line: u32, filename: &'static str, description: Option, ) -> Self { MyMainError { line, filename, description, error_cause: Some(e), } } } fn throw_error() -> Result<(), MyError> { let directory = String::from("ldfhgdfkgjdf"); ::std::fs::remove_dir(&directory).map_err(chain_error_fn!( MyError, "Could not remove directory '{}'{}", &directory, "!" ))?; Ok(()) } #[test] fn test_chain_error_fn() -> Result<(), MyMainError> { let res = throw_error().map_err(chain_error_fn!(MyMainError, "I has an error.")); if let Err(my_err) = res { if let Some(source) = my_err.source() { assert!(source.is::()); } println!("\nRoot cause is {:#?}\n", my_err.root_cause()); assert!(my_err.root_cause().unwrap().is::<::std::io::Error>()); assert!(my_err.find_cause::<::std::io::Error>().is_some()); if my_err.find_cause::<::std::io::Error>().is_some() { println!("Has cause io::Error"); } if my_err.find_cause::().is_some() { println!("Has cause MyError"); } println!("-----------"); println!("Display Error:\n{}", my_err); println!("-----------"); println!("Debug Error: \n{:#?}", my_err); println!("-----------"); }; //res?; Ok(()) } #[test] fn test_into_chain_error_fn() -> Result<(), MyMainError> { let res: Result<(), MyMainError> = throw_error().map_err(into_boxed_chain_error_fn!("I has an error.")); if let Err(my_err) = res { if let Some(source) = my_err.source() { assert!(source.is::()); } println!("\nRoot cause is {:#?}\n", my_err.root_cause()); assert!(my_err.root_cause().unwrap().is::<::std::io::Error>()); assert!(my_err.find_cause::<::std::io::Error>().is_some()); if my_err.find_cause::<::std::io::Error>().is_some() { println!("Has cause io::Error"); } if my_err.find_cause::().is_some() { println!("Has cause MyError"); } println!("-----------"); println!("Display Error:\n{}", my_err); println!("-----------"); println!("Debug Error: \n{:#?}", my_err); println!("-----------"); }; //res?; Ok(()) } #[test] fn test_map_chain_err() -> Result<(), MyMainError> { let res: Result<(), MyMainError> = throw_error().map_err(chain!()); if let Err(my_err) = res { if let Some(source) = my_err.source() { assert!(source.is::()); } println!("\nRoot cause is {:#?}\n", my_err.root_cause()); assert!(my_err.root_cause().unwrap().is::<::std::io::Error>()); assert!(my_err.find_cause::<::std::io::Error>().is_some()); if my_err.find_cause::<::std::io::Error>().is_some() { println!("Has cause io::Error"); } if my_err.find_cause::().is_some() { println!("Has cause MyError"); } println!("-----------"); println!("Display Error:\n{}", my_err); println!("-----------"); println!("Debug Error: \n{:#?}", my_err); println!("-----------"); }; //res?; Ok(()) } }