diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
deleted file mode 100644
index accd0a4..0000000
--- a/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index a55e7a1..0000000
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index 43136c6..5dda0f1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,3 +4,8 @@ version = "0.1.0"
authors = ["Harald Hoyer "]
edition = "2018"
+[features]
+default = [ "debug-cause", "fileline" ]
+fileline = []
+display-cause = []
+debug-cause = []
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index 27f435d..e404343 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,326 +1,426 @@
-pub trait ChainError: ::std::error::Error + Sized {
- fn new(
- line: u32,
- filename: &'static str,
- description: Option,
- error_cause: Option>,
- ) -> Self;
+use std::error::Error;
+use std::fmt::{Debug, Display, Formatter, Result};
- fn root_cause(&self) -> Option<&(dyn std::error::Error + 'static)>;
- fn find_cause(
- &self,
- ) -> Option<&(dyn std::error::Error + 'static)>;
+pub struct ChainError {
+ #[cfg(feature = "fileline")]
+ occurrence: Option<(u32, &'static str)>,
+ kind: T,
+ error_cause: Option>,
}
-pub trait ChainErrorFrom: ChainError {
- fn chain_error_from(_: T, _: u32, _: &'static str, _: Option) -> Self;
+impl ChainError {
+ #[cfg(feature = "fileline")]
+ pub fn new(
+ kind: T,
+ error_cause: Option>,
+ occurrence: Option<(u32, &'static str)>,
+ ) -> Self {
+ Self {
+ occurrence,
+ kind,
+ error_cause,
+ }
+ }
+
+ #[cfg(not(feature = "fileline"))]
+ pub fn new(
+ kind: T,
+ error_cause: Option>,
+ _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(&self) -> Option<&(dyn Error + 'static)> {
+ let mut cause = self as &(dyn Error + 'static);
+ loop {
+ if cause.is::() {
+ return Some(cause);
+ }
+
+ match cause.source() {
+ Some(c) => cause = c,
+ None => return None,
+ }
+ }
+ }
+
+ pub fn find_kind(&self) -> Option<&ChainError> {
+ let mut cause = self as &(dyn Error + 'static);
+ loop {
+ if cause.is::>() {
+ return cause.downcast_ref::>();
+ }
+
+ match cause.source() {
+ Some(c) => cause = c,
+ None => return None,
+ }
+ }
+ }
+
+ pub fn kind<'a>(&'a self) -> &'a T {
+ &self.kind
+ }
}
-pub trait IntoChainError: Sized {
- fn into_chain_error(self, line: u32, filename: &'static str, description: Option) -> T;
+pub trait ChainErrorDown {
+ fn is_chain(&self) -> bool;
+ fn downcast_chain_ref(&self) -> Option<&ChainError>;
+ fn downcast_chain_mut(&mut self) -> Option<&mut ChainError>;
}
-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)
+use std::any::TypeId;
+
+impl ChainErrorDown for ChainError {
+ fn is_chain(&self) -> bool {
+ TypeId::of::() == TypeId::of::()
+ }
+
+ fn downcast_chain_ref(&self) -> Option<&ChainError> {
+ if self.is_chain::() {
+ unsafe { Some(&*(self as *const dyn Error as *const &ChainError)) }
+ } else {
+ None
+ }
+ }
+
+ fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> {
+ if self.is_chain::() {
+ unsafe { Some(&mut *(self as *mut dyn Error as *mut &mut ChainError)) }
+ } else {
+ None
+ }
+ }
+}
+
+impl ChainErrorDown for dyn Error + 'static {
+ fn is_chain(&self) -> bool {
+ self.is::>()
+ }
+
+ fn downcast_chain_ref(&self) -> Option<&ChainError> {
+ self.downcast_ref::>()
+ }
+
+ fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> {
+ self.downcast_mut::>()
+ }
+}
+
+impl ChainErrorDown for dyn Error + 'static + Send {
+ fn is_chain(&self) -> bool {
+ self.is::>()
+ }
+
+ fn downcast_chain_ref(&self) -> Option<&ChainError> {
+ self.downcast_ref::>()
+ }
+
+ fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> {
+ self.downcast_mut::>()
+ }
+}
+
+impl ChainErrorDown for dyn Error + 'static + Send + Sync {
+ fn is_chain(&self) -> bool {
+ self.is::>()
+ }
+
+ fn downcast_chain_ref(&self) -> Option<&ChainError> {
+ self.downcast_ref::>()
+ }
+
+ fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> {
+ self.downcast_mut::>()
+ }
+}
+
+impl Error for ChainError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ if let Some(ref e) = self.error_cause {
+ Some(e.as_ref())
+ } else {
+ None
+ }
+ }
+}
+
+impl Error for &ChainError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ if let Some(ref e) = self.error_cause {
+ Some(e.as_ref())
+ } else {
+ None
+ }
+ }
+}
+
+impl Error for &mut ChainError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ if let Some(ref e) = self.error_cause {
+ Some(e.as_ref())
+ } else {
+ None
+ }
+ }
+}
+
+impl Display for ChainError {
+ 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 Debug for ChainError {
+ 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! chain_error_fn {
+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| <$t> :: new(line!(), file!(), Some(format!($v, $( $more , )* )), Some(e.into()))
+ |e| cherr!(e, $t (format!($v, $( $more , )* )))
};
( $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()))
+ |e| cherr!(e, $t (format!($v, $( $more , )* )))
};
}
#[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 {
+macro_rules! derive_str_cherr {
($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
- }
- }
- }
-
+ struct $e(String);
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(())
+ write!(f, "{}", self.0)
}
}
-
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(())
+ write!(f, "{}({})", stringify!($e), self.0)
}
}
};
}
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,
- };
+ 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_chain_error!(MyError);
- derive_chain_error!(MyMainError);
+ #[derive(Clone, PartialEq, Debug)]
+ enum ParseError {
+ InvalidValue(u32),
+ InvalidParameter(String),
+ NoOpen,
+ NoClose,
+ }
- 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),
+ 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 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,
- "!"
+ fn parse_config(c: String) -> Result<(), Box> {
+ 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::()
+ .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> {
+ Err(IoError::from(IoErrorKind::NotFound)).map_err(mstrerr!(
+ FileError,
+ "Can't find {:?}",
+ filename
))?;
Ok(())
}
- #[test]
- fn test_chain_error_fn() -> Result<(), MyMainError> {
- let res = throw_error().map_err(chain_error_fn!(MyMainError, "I has an error."));
+ fn read_config(filename: &Path) -> Result<(), Box> {
+ 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 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 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())))?;
+ }
- 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());
+ fn read_verbose_config(p: &str) -> Result<(), Box> {
+ eprintln!("Reading '{}' ... ", p);
+ read_config(Path::new(p)).map_err(mstrerr!(AppError, "{}", p))?;
+ eprintln!("Ok reading {}", p);
+ Ok(())
+ }
- if my_err.find_cause::<::std::io::Error>().is_some() {
- println!("Has cause io::Error");
+ fn start_app(debug: bool) -> Result<(), Box> {
+ 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::());
+ let app_err = e.downcast_chain_ref::().unwrap();
+
+ if app_err.find_kind::().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::() {
+ if debug {
+ eprintln!("{:?}\n", cfg_error);
+ } else {
+ // Deep Error handling
+ if let Some(chioerror) = cfg_error.find_kind::() {
+ 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)
+ }
+ }
}
- 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?;
+ eprintln!();
+ }
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(())
+ fn test_chain_error() {
+ eprintln!("Display:\n");
+ let e = start_app(false).unwrap_err();
+ assert!(e.is_chain::());
+ eprintln!("\n\n==================================");
+ eprintln!("==== Debug output");
+ eprintln!("==================================\n");
+ let r = start_app(true);
+ assert!(r.unwrap_err().is_chain::());
}
}