use std::error::Error; use std::fmt::{Debug, Display, Formatter, Result}; pub struct ChainError<T> { #[cfg(feature = "fileline")] occurrence: Option<(u32, &'static str)>, kind: T, error_cause: Option<Box<dyn Error + 'static>>, } impl<T: 'static + Display + Debug> ChainError<T> { #[cfg(feature = "fileline")] pub fn new( kind: T, error_cause: Option<Box<dyn Error + 'static>>, occurrence: Option<(u32, &'static str)>, ) -> Self { Self { occurrence, kind, error_cause, } } #[cfg(not(feature = "fileline"))] pub fn new( kind: T, error_cause: Option<Box<dyn Error + 'static>>, _occurrence: Option<(u32, &'static str)>, ) -> Self { Self { kind, error_cause } } pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> { let mut cause = self as &(dyn Error + 'static); while let Some(c) = cause.source() { cause = c; } Some(cause) } pub fn find_cause<U: Error + 'static>(&self) -> Option<&(dyn Error + 'static)> { let mut cause = self as &(dyn Error + 'static); loop { if cause.is::<U>() { return Some(cause); } match cause.source() { Some(c) => cause = c, None => return None, } } } pub fn find_kind<U: 'static + Display + Debug>(&self) -> Option<&ChainError<U>> { let mut cause = self as &(dyn Error + 'static); loop { if cause.is::<ChainError<U>>() { return cause.downcast_ref::<ChainError<U>>(); } match cause.source() { Some(c) => cause = c, None => return None, } } } pub fn kind<'a>(&'a self) -> &'a T { &self.kind } } pub trait ChainErrorDown { fn is_chain<T: 'static + Display + Debug>(&self) -> bool; fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>>; fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>>; } use std::any::TypeId; impl<U: 'static + Display + Debug> ChainErrorDown for ChainError<U> { fn is_chain<T: 'static + Display + Debug>(&self) -> bool { TypeId::of::<T>() == TypeId::of::<U>() } fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { if self.is_chain::<T>() { unsafe { Some(&*(self as *const dyn Error as *const &ChainError<T>)) } } else { None } } fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { if self.is_chain::<T>() { unsafe { Some(&mut *(self as *mut dyn Error as *mut &mut ChainError<T>)) } } else { None } } } impl ChainErrorDown for dyn Error + 'static { fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<ChainError<T>>() } fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { self.downcast_ref::<ChainError<T>>() } fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { self.downcast_mut::<ChainError<T>>() } } impl ChainErrorDown for dyn Error + 'static + Send { fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<ChainError<T>>() } fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { self.downcast_ref::<ChainError<T>>() } fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { self.downcast_mut::<ChainError<T>>() } } impl ChainErrorDown for dyn Error + 'static + Send + Sync { fn is_chain<T: 'static + Display + Debug>(&self) -> bool { self.is::<ChainError<T>>() } fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> { self.downcast_ref::<ChainError<T>>() } fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> { self.downcast_mut::<ChainError<T>>() } } impl<T: 'static + Display + Debug> Error for ChainError<T> { fn source(&self) -> Option<&(dyn Error + 'static)> { if let Some(ref e) = self.error_cause { Some(e.as_ref()) } else { None } } } impl<T: 'static + Display + Debug> Error for &ChainError<T> { fn source(&self) -> Option<&(dyn Error + 'static)> { if let Some(ref e) = self.error_cause { Some(e.as_ref()) } else { None } } } impl<T: 'static + Display + Debug> Error for &mut ChainError<T> { fn source(&self) -> Option<&(dyn Error + 'static)> { if let Some(ref e) = self.error_cause { Some(e.as_ref()) } else { None } } } impl<T: 'static + Display + Debug> Display for ChainError<T> { fn fmt(&self, f: &mut Formatter) -> Result { write!(f, "{}", self.kind)?; #[cfg(feature = "display-cause")] { if let Some(e) = self.source() { writeln!(f, "\nCaused by:")?; Display::fmt(&e, f)?; } } Ok(()) } } impl<T: 'static + Display + Debug> Debug for ChainError<T> { fn fmt(&self, f: &mut Formatter) -> Result { #[cfg(feature = "fileline")] { if let Some(o) = self.occurrence { write!(f, "{}:{}: ", o.1, o.0)?; } } Debug::fmt(&self.kind, f)?; #[cfg(feature = "debug-cause")] { if let Some(e) = self.source() { writeln!(f, "\nCaused by:")?; Debug::fmt(&e, f)?; } } Ok(()) } } #[macro_export] macro_rules! cherr { ( $k:expr ) => { ChainError::<_>::new($k, None, Some((line!(), file!()))) }; ( $e:expr, $k:expr ) => { ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) }; } #[macro_export] macro_rules! mstrerr { ( $t:ident, $v:expr $(, $more:expr)* ) => { |e| cherr!(e, $t (format!($v, $( $more , )* ))) }; ( $t:path, $v:expr $(, $more:expr)* ) => { |e| cherr!(e, $t (format!($v, $( $more , )* ))) }; } #[macro_export] macro_rules! derive_str_cherr { ($e:ident) => { struct $e(String); impl ::std::fmt::Display for $e { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "{}", self.0) } } impl ::std::fmt::Debug for $e { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "{}({})", stringify!($e), self.0) } } }; } pub mod prelude { pub use super::{cherr, derive_str_cherr, mstrerr, ChainError, ChainErrorDown}; } #[cfg(test)] mod tests { use std::error::Error; use std::io::Error as IoError; use std::io::ErrorKind as IoErrorKind; use std::path::Path; use crate::prelude::*; #[derive(Clone, PartialEq, Debug)] enum ParseError { InvalidValue(u32), InvalidParameter(String), NoOpen, NoClose, } impl ::std::fmt::Display for ParseError { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { match self { ParseError::InvalidValue(a) => write!(f, "InvalidValue: {}", a), ParseError::InvalidParameter(a) => write!(f, "InvalidParameter: '{}'", a), ParseError::NoOpen => write!(f, "No opening '{{' in config file"), ParseError::NoClose => write!(f, "No closing '}}' in config file"), } } } fn parse_config(c: String) -> Result<(), Box<Error>> { if !c.starts_with('{') { Err(cherr!(ParseError::NoOpen))?; } if !c.ends_with('}') { Err(cherr!(ParseError::NoClose))?; } let c = &c[1..(c.len() - 1)]; let v = c .parse::<u32>() .map_err(|e| cherr!(e, ParseError::InvalidParameter(c.into())))?; if v > 100 { Err(cherr!(ParseError::InvalidValue(v)))?; } Ok(()) } derive_str_cherr!(ConfigFileError); derive_str_cherr!(SeriousError); derive_str_cherr!(FileError); derive_str_cherr!(AppError); fn file_reader(filename: &Path) -> Result<(), Box<Error>> { Err(IoError::from(IoErrorKind::NotFound)).map_err(mstrerr!( FileError, "Can't find {:?}", filename ))?; Ok(()) } fn read_config(filename: &Path) -> Result<(), Box<Error>> { if filename.eq(Path::new("global.ini")) { // assume we got an IO error file_reader(filename).map_err(mstrerr!( ConfigFileError, "Can't find {:?}", filename ))?; } // assume we read some buffer if filename.eq(Path::new("local.ini")) { let buf = String::from("{1000}"); parse_config(buf)?; } if filename.eq(Path::new("user.ini")) { let buf = String::from("foo"); parse_config(buf)?; } if filename.eq(Path::new("user2.ini")) { let buf = String::from("{foo"); parse_config(buf)?; } if filename.eq(Path::new("user3.ini")) { let buf = String::from("{foo}"); parse_config(buf)?; } if filename.eq(Path::new("custom.ini")) { let buf = String::from("{10}"); parse_config(buf)?; } if filename.eq(Path::new("essential.ini")) { Err(cherr!(SeriousError("Something is really wrong".into())))?; } Ok(()) } fn read_verbose_config(p: &str) -> Result<(), Box<Error>> { eprintln!("Reading '{}' ... ", p); read_config(Path::new(p)).map_err(mstrerr!(AppError, "{}", p))?; eprintln!("Ok reading {}", p); Ok(()) } fn start_app(debug: bool) -> Result<(), Box<Error>> { for p in &[ "global.ini", "local.ini", "user.ini", "user2.ini", "user3.ini", "custom.ini", "essential.ini", ] { if let Err(e) = read_verbose_config(p) { assert!(e.is_chain::<AppError>()); let app_err = e.downcast_chain_ref::<AppError>().unwrap(); if app_err.find_kind::<SeriousError>().is_some() { // Bail out on SeriousError eprintln!("---> Serious Error:\n{:?}", e); Err(cherr!(e, AppError("Seriously".into())))?; } else if let Some(cfg_error) = app_err.find_kind::<ConfigFileError>() { if debug { eprintln!("{:?}\n", cfg_error); } else { // Deep Error handling if let Some(chioerror) = cfg_error.find_kind::<IoError>() { let ioerror = chioerror.kind(); match ioerror.kind() { IoErrorKind::NotFound => { eprint!("Ignoring missing file"); if let Some(root_cause) = cfg_error.root_cause() { eprint!(", because of: {}\n", root_cause); } eprintln!(); } _ => Err(cherr!(e, AppError("Unhandled IOError".into())))?, } } else { eprintln!("ConfigFileError for: {}", e); } } } else { if debug { eprintln!("Error reading:\n{:?}\n", e) } else { eprintln!("Error reading: {}\n", e) } } } eprintln!(); } Ok(()) } #[test] fn test_chain_error() { eprintln!("Display:\n"); let e = start_app(false).unwrap_err(); assert!(e.is_chain::<AppError>()); eprintln!("\n\n=================================="); eprintln!("==== Debug output"); eprintln!("==================================\n"); let r = start_app(true); assert!(r.unwrap_err().is_chain::<AppError>()); } }