From 46e2c78aa830c5ede7eccf612d84f38daf2ce5e3 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Thu, 20 Dec 2018 10:08:54 +0100 Subject: [PATCH] add tutorial --- examples/configapp.rs | 169 ----------------------------------------- examples/tutorial1.rs | 34 +++++++++ examples/tutorial10.rs | 74 ++++++++++++++++++ examples/tutorial11.rs | 79 +++++++++++++++++++ examples/tutorial2.rs | 39 ++++++++++ examples/tutorial3.rs | 38 +++++++++ examples/tutorial4.rs | 38 +++++++++ examples/tutorial5.rs | 42 ++++++++++ examples/tutorial6.rs | 52 +++++++++++++ examples/tutorial7.rs | 55 ++++++++++++++ examples/tutorial8.rs | 55 ++++++++++++++ examples/tutorial9.rs | 54 +++++++++++++ src/lib.rs | 163 +++++++++------------------------------ 13 files changed, 594 insertions(+), 298 deletions(-) delete mode 100644 examples/configapp.rs create mode 100644 examples/tutorial1.rs create mode 100644 examples/tutorial10.rs create mode 100644 examples/tutorial11.rs create mode 100644 examples/tutorial2.rs create mode 100644 examples/tutorial3.rs create mode 100644 examples/tutorial4.rs create mode 100644 examples/tutorial5.rs create mode 100644 examples/tutorial6.rs create mode 100644 examples/tutorial7.rs create mode 100644 examples/tutorial8.rs create mode 100644 examples/tutorial9.rs diff --git a/examples/configapp.rs b/examples/configapp.rs deleted file mode 100644 index a7b147a..0000000 --- a/examples/configapp.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::error::Error; -use std::io::Error as IoError; -use std::io::ErrorKind as IoErrorKind; -use std::path::Path; - -use chainerror::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> { - 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, "File reader error"))?; - Ok(()) -} - -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, - "Error reading file {:?}", - 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> { - 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> { - 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) - } - } - } - eprintln!(); - } - Ok(()) -} - - -fn main() -> Result<(), Box> { - 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); - - eprintln!("\n\n=================================="); - eprintln!("==== Main return output"); - eprintln!("==================================\n"); - r -} diff --git a/examples/tutorial1.rs b/examples/tutorial1.rs new file mode 100644 index 0000000..17d5534 --- /dev/null +++ b/examples/tutorial1.rs @@ -0,0 +1,34 @@ +/*! + +~~~bash +$ cargo run -q --example tutorial1 +Error: StringError("func1 error") +~~~ + +!*/ + +use std::error::Error; +use std::result::Result; + +fn do_some_io() -> Result<(), Box> { + Err("do_some_io error")?; + Ok(()) +} + +fn func2() -> Result<(), Box> { + if let Err(_) = do_some_io() { + Err("func2 error")?; + } + Ok(()) +} + +fn func1() -> Result<(), Box> { + if let Err(_) = func2() { + Err("func1 error")?; + } + Ok(()) +} + +fn main() -> Result<(), Box> { + func1() +} diff --git a/examples/tutorial10.rs b/examples/tutorial10.rs new file mode 100644 index 0000000..282fbb6 --- /dev/null +++ b/examples/tutorial10.rs @@ -0,0 +1,74 @@ +/*! + +~~~bash +$ cargo run -q --example tutorial10 +Main Error Report: func1 error calling func2 +Error reported by Func2Error: Error reading 'foo.txt' + +Debug Error: +examples/tutorial10.rs:49: Func2 +Caused by: +examples/tutorial10.rs:29: Func2Error(Error reading 'foo.txt') +Caused by: +Kind(NotFound) +~~~ + +!*/ + +use chainerror::prelude::*; +use std::error::Error; +use std::io; +use std::result::Result; + +fn do_some_io() -> Result<(), Box> { + Err(io::Error::from(io::ErrorKind::NotFound))?; + Ok(()) +} + +derive_str_cherr!(Func2Error); + +fn func2() -> Result<(), Box> { + let filename = "foo.txt"; + do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + Ok(()) +} + +#[derive(Debug)] +enum Func1Error { + Func2, + IO(String), +} + +impl ::std::fmt::Display for Func1Error { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match self { + Func1Error::Func2 => write!(f, "func1 error calling func2"), + Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), + } + } +} + +fn func1() -> ChainResult<(), Func1Error> { + func2().map_err(|e| cherr!(e, Func1Error::Func2))?; + let filename = String::from("bar.txt"); + do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; + Ok(()) +} + +fn main() -> Result<(), Box> { + if let Err(e) = func1() { + match e.kind() { + Func1Error::Func2 => eprintln!("Main Error Report: func1 error calling func2"), + Func1Error::IO(filename) => { + eprintln!("Main Error Report: func1 error reading '{}'", filename) + } + } + + if let Some(e) = e.find_chain_cause::() { + eprintln!("Error reported by Func2Error: {}", e) + } + + eprintln!("\nDebug Error:\n{:?}", e); + } + Ok(()) +} diff --git a/examples/tutorial11.rs b/examples/tutorial11.rs new file mode 100644 index 0000000..dd80482 --- /dev/null +++ b/examples/tutorial11.rs @@ -0,0 +1,79 @@ +/*! + +~~~bash +$ cargo run -q --example tutorial11 +Main Error Report: func1 error calling func2 +Error reported by Func2Error: Error reading 'foo.txt' + +Debug Error: +examples/tutorial11.rs:57: func1 error calling func2 +Caused by: +examples/tutorial11.rs:32: Func2Error(Error reading 'foo.txt') +Caused by: +Kind(NotFound) +~~~ + +!*/ + +use chainerror::prelude::*; +use std::error::Error; +use std::io; +use std::result::Result; + +fn do_some_io() -> Result<(), Box> { + Err(io::Error::from(io::ErrorKind::NotFound))?; + Ok(()) +} + +derive_str_cherr!(Func2Error); + +fn func2() -> Result<(), Box> { + let filename = "foo.txt"; + do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + Ok(()) +} + +enum Func1Error { + Func2, + IO(String), +} + +impl ::std::fmt::Display for Func1Error { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match self { + Func1Error::Func2 => write!(f, "func1 error calling func2"), + Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), + } + } +} + +impl ::std::fmt::Debug for Func1Error { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "{}", self) + } +} + +fn func1() -> ChainResult<(), Func1Error> { + func2().map_err(|e| cherr!(e, Func1Error::Func2))?; + let filename = String::from("bar.txt"); + do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; + Ok(()) +} + +fn main() -> Result<(), Box> { + if let Err(e) = func1() { + match e.kind() { + Func1Error::Func2 => eprintln!("Main Error Report: func1 error calling func2"), + Func1Error::IO(filename) => { + eprintln!("Main Error Report: func1 error reading '{}'", filename) + } + } + + if let Some(e) = e.find_chain_cause::() { + eprintln!("Error reported by Func2Error: {}", e) + } + + eprintln!("\nDebug Error:\n{:?}", e); + } + Ok(()) +} diff --git a/examples/tutorial2.rs b/examples/tutorial2.rs new file mode 100644 index 0000000..2e076c4 --- /dev/null +++ b/examples/tutorial2.rs @@ -0,0 +1,39 @@ +/*! + +~~~bash +$ cargo run -q --example tutorial2 +Error: examples/tutorial2.rs:28: "func1 error" +Caused by: +examples/tutorial2.rs:21: "func2 error" +Caused by: +StringError("do_some_io error") +~~~ + +!*/ + +use chainerror::prelude::*; +use std::error::Error; +use std::result::Result; + +fn do_some_io() -> Result<(), Box> { + Err("do_some_io error")?; + Ok(()) +} + +fn func2() -> Result<(), Box> { + if let Err(e) = do_some_io() { + Err(cherr!(e, "func2 error"))?; + } + Ok(()) +} + +fn func1() -> Result<(), Box> { + if let Err(e) = func2() { + Err(cherr!(e, "func1 error"))?; + } + Ok(()) +} + +fn main() -> Result<(), Box> { + func1() +} diff --git a/examples/tutorial3.rs b/examples/tutorial3.rs new file mode 100644 index 0000000..1742cc5 --- /dev/null +++ b/examples/tutorial3.rs @@ -0,0 +1,38 @@ +/*! + +~~~bash +$ cargo run -q --example tutorial3 +examples/tutorial3.rs:30: "func1 error" +Caused by: +examples/tutorial3.rs:25: "func2 error" +Caused by: +StringError("do_some_io error") +~~~ + +!*/ + +use chainerror::prelude::*; +use std::error::Error; +use std::result::Result; + +fn do_some_io() -> Result<(), Box> { + Err("do_some_io error")?; + Ok(()) +} + +fn func2() -> Result<(), Box> { + do_some_io().map_err(|e| cherr!(e, "func2 error"))?; + Ok(()) +} + +fn func1() -> Result<(), Box> { + func2().map_err(|e| cherr!(e, "func1 error"))?; + Ok(()) +} + +fn main() -> Result<(), Box> { + if let Err(e) = func1() { + eprintln!("{:?}", e); + } + Ok(()) +} diff --git a/examples/tutorial4.rs b/examples/tutorial4.rs new file mode 100644 index 0000000..67ddc55 --- /dev/null +++ b/examples/tutorial4.rs @@ -0,0 +1,38 @@ +/*! + +~~~bash +$ cargo run -q --example tutorial4 +examples/tutorial4.rs:29: "func1 error" +Caused by: +examples/tutorial4.rs:24: "func2 error" +Caused by: +StringError("do_some_io error") +~~~ + +!*/ + +use chainerror::prelude::*; +use std::error::Error; +use std::result::Result; + +fn do_some_io() -> Result<(), Box> { + Err("do_some_io error")?; + Ok(()) +} + +fn func2() -> Result<(), Box> { + do_some_io().map_err(mstrerr!("func2 error"))?; + Ok(()) +} + +fn func1() -> Result<(), Box> { + func2().map_err(mstrerr!("func1 error"))?; + Ok(()) +} + +fn main() -> Result<(), Box> { + if let Err(e) = func1() { + eprintln!("{:?}", e); + } + Ok(()) +} diff --git a/examples/tutorial5.rs b/examples/tutorial5.rs new file mode 100644 index 0000000..5b7cb90 --- /dev/null +++ b/examples/tutorial5.rs @@ -0,0 +1,42 @@ +/*! + +~~~bash +$ cargo run -q --example tutorial5 +Func2 failed because of 'entity not found' +func1 error +~~~ + +!*/ + +use chainerror::prelude::*; +use std::error::Error; +use std::io; +use std::result::Result; + +fn do_some_io() -> Result<(), Box> { + Err(io::Error::from(io::ErrorKind::NotFound))?; + Ok(()) +} + +fn func2() -> Result<(), Box> { + let filename = "foo.txt"; + do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + Ok(()) +} + +fn func1() -> Result<(), Box> { + if let Err(e) = func2() { + if let Some(s) = e.source() { + eprintln!("func2 failed because of '{}'", s); + Err(e).map_err(mstrerr!("func1 error"))?; + } + } + Ok(()) +} + +fn main() -> Result<(), Box> { + if let Err(e) = func1() { + eprintln!("{}", e); + } + Ok(()) +} diff --git a/examples/tutorial6.rs b/examples/tutorial6.rs new file mode 100644 index 0000000..b2b3b2a --- /dev/null +++ b/examples/tutorial6.rs @@ -0,0 +1,52 @@ +/*! + +~~~bash +$ cargo run -q --example tutorial6 +Error: func1 error +caused by: Error reading 'foo.txt' +caused by: std::io::Error: entity not found +of kind: std::io::ErrorKind::NotFound +~~~ + +!*/ + +use chainerror::prelude::*; +use std::error::Error; +use std::io; +use std::result::Result; + +fn do_some_io() -> Result<(), Box> { + Err(io::Error::from(io::ErrorKind::NotFound))?; + Ok(()) +} + +fn func2() -> Result<(), Box> { + let filename = "foo.txt"; + do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + Ok(()) +} + +fn func1() -> Result<(), Box> { + func2().map_err(mstrerr!("func1 error"))?; + Ok(()) +} + +fn main() -> Result<(), Box> { + if let Err(e) = func1() { + eprintln!("Error: {}", e); + let mut s = e.as_ref(); + while let Some(c) = s.source() { + if let Some(ioerror) = c.downcast_ref::() { + eprintln!("caused by: std::io::Error: {}", ioerror); + match ioerror.kind() { + io::ErrorKind::NotFound => eprintln!("of kind: std::io::ErrorKind::NotFound"), + _ => {} + } + } else { + eprintln!("caused by: {}", c); + } + s = c; + } + } + Ok(()) +} diff --git a/examples/tutorial7.rs b/examples/tutorial7.rs new file mode 100644 index 0000000..89e002b --- /dev/null +++ b/examples/tutorial7.rs @@ -0,0 +1,55 @@ +/*! + +~~~bash +$ cargo run -q --example tutorial7 +Error: func1 error +caused by: std::io::Error: entity not found +of kind: std::io::ErrorKind::NotFound +The root cause was: std::io::Error: Kind( + NotFound +) +~~~ + +!*/ + +use chainerror::prelude::*; +use std::error::Error; +use std::io; +use std::result::Result; + +fn do_some_io() -> Result<(), Box> { + Err(io::Error::from(io::ErrorKind::NotFound))?; + Ok(()) +} + +fn func2() -> Result<(), Box> { + let filename = "foo.txt"; + do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + Ok(()) +} + +fn func1() -> Result<(), Box> { + func2().map_err(mstrerr!("func1 error"))?; + Ok(()) +} + +fn main() -> Result<(), Box> { + if let Err(e) = func1() { + eprintln!("Error: {}", e); + if let Some(s) = e.downcast_chain_ref::() { + if let Some(ioerror) = s.find_cause::() { + 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::().unwrap(); + eprintln!("The root cause was: std::io::Error: {:#?}", ioerror); + } + } + } + Ok(()) +} diff --git a/examples/tutorial8.rs b/examples/tutorial8.rs new file mode 100644 index 0000000..65e04d1 --- /dev/null +++ b/examples/tutorial8.rs @@ -0,0 +1,55 @@ +/*! + +~~~bash +$ cargo run -q --example tutorial8 +Func1Error: func1 error +Func2Error: Error reading 'foo.txt' +Debug Func2Error: +examples/tutorial8.rs:27: Func2Error(Error reading 'foo.txt') +Caused by: +Kind(NotFound) +~~~ + +!*/ + +use chainerror::prelude::*; +use std::error::Error; +use std::io; +use std::result::Result; + +fn do_some_io() -> Result<(), Box> { + Err(io::Error::from(io::ErrorKind::NotFound))?; + Ok(()) +} + +derive_str_cherr!(Func2Error); + +fn func2() -> Result<(), Box> { + let filename = "foo.txt"; + do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + Ok(()) +} + +derive_str_cherr!(Func1Error); + +fn func1() -> Result<(), Box> { + func2().map_err(mstrerr!(Func1Error, "func1 error"))?; + Ok(()) +} + +fn main() -> Result<(), Box> { + if let Err(e) = func1() { + if let Some(f1err) = e.downcast_chain_ref::() { + eprintln!("Func1Error: {}", f1err); + + if let Some(f2err) = f1err.find_cause::>() { + eprintln!("Func2Error: {}", f2err); + } + + if let Some(f2err) = f1err.find_chain_cause::() { + eprintln!("Debug Func2Error:\n{:?}", f2err); + } + } + } + Ok(()) +} diff --git a/examples/tutorial9.rs b/examples/tutorial9.rs new file mode 100644 index 0000000..b818e35 --- /dev/null +++ b/examples/tutorial9.rs @@ -0,0 +1,54 @@ +/*! + +~~~bash +$ cargo run -q --example tutorial9 +Func1ErrorFunc2: +examples/tutorial9.rs:37: Func1ErrorFunc2(func1 error calling func2) +Caused by: +examples/tutorial9.rs:29: Func2Error(Error reading 'foo.txt') +Caused by: +Kind(NotFound) +~~~ + +!*/ + +use chainerror::prelude::*; +use std::error::Error; +use std::io; +use std::result::Result; + +fn do_some_io() -> Result<(), Box> { + Err(io::Error::from(io::ErrorKind::NotFound))?; + Ok(()) +} + +derive_str_cherr!(Func2Error); + +fn func2() -> Result<(), Box> { + let filename = "foo.txt"; + do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + Ok(()) +} + +derive_str_cherr!(Func1ErrorFunc2); +derive_str_cherr!(Func1ErrorIO); + +fn func1() -> Result<(), Box> { + func2().map_err(mstrerr!(Func1ErrorFunc2, "func1 error calling func2"))?; + let filename = "bar.txt"; + do_some_io().map_err(mstrerr!(Func1ErrorIO, "Error reading '{}'", filename))?; + Ok(()) +} + +fn main() -> Result<(), Box> { + if let Err(e) = func1() { + if let Some(s) = e.downcast_ref::>() { + eprintln!("Func1ErrorIO:\n{:?}", s); + } + + if let Some(s) = try_cherr_ref!(e, Func1ErrorFunc2) { + eprintln!("Func1ErrorFunc2:\n{:?}", s); + } + } + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index e621fcb..f3ec61d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ use std::error::Error; use std::fmt::{Debug, Display, Formatter, Result}; +use std::result::Result as StdResult; pub struct ChainError { #[cfg(feature = "fileline")] @@ -8,6 +9,8 @@ pub struct ChainError { error_cause: Option>, } +pub type ChainResult = StdResult>; + impl ChainError { #[cfg(feature = "fileline")] pub fn new( @@ -39,11 +42,11 @@ impl ChainError { Some(cause) } - pub fn find_cause(&self) -> Option<&(dyn Error + 'static)> { + pub fn find_cause(&self) -> Option<&U> { let mut cause = self as &(dyn Error + 'static); loop { if cause.is::() { - return Some(cause); + return cause.downcast_ref::(); } match cause.source() { @@ -53,7 +56,7 @@ impl ChainError { } } - pub fn find_kind(&self) -> Option<&ChainError> { + pub fn find_chain_cause(&self) -> Option<&ChainError> { let mut cause = self as &(dyn Error + 'static); loop { if cause.is::>() { @@ -229,6 +232,9 @@ macro_rules! mstrerr { ( $t:path, $v:expr $(, $more:expr)* ) => { |e| cherr!(e, $t (format!($v, $( $more , )* ))) }; + ( $v:expr $(, $more:expr)* ) => { + |e| cherr!(e, format!($v, $( $more , )* )) + }; } #[macro_export] @@ -245,138 +251,37 @@ macro_rules! derive_str_cherr { write!(f, "{}({})", stringify!($e), self.0) } } + impl ::std::error::Error for $e {} + }; +} + +#[macro_export] +macro_rules! try_cherr_ref { + ( $e:expr, $t:ident ) => { + $e.downcast_ref::>() + }; + ( $e:expr, $t:path ) => { + $e.downcast_ref::>() + }; +} + +#[macro_export] +macro_rules! try_cherr_mut { + ( $e:expr, $t:ident ) => { + $e.downcast_mut::>() + }; + ( $e:expr, $t:path ) => { + $e.downcast_mut::>() }; } pub mod prelude { - pub use super::{cherr, derive_str_cherr, mstrerr, ChainError, ChainErrorDown}; + pub use super::{ + cherr, derive_str_cherr, mstrerr, try_cherr_mut, try_cherr_ref, ChainError, ChainErrorDown, + ChainResult, + }; } #[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> { - 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, "File reader error"))?; - Ok(()) - } - - 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, - "Error reading file {:?}", - 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_config_pre(p: &str) -> Result<(), Box> { - read_config(Path::new(p)).map_err(mstrerr!(AppError, "{}", p))?; - Ok(()) - } - - #[test] - fn test_chain_error() { - for p in &[ - "global.ini", - "local.ini", - "user.ini", - "user2.ini", - "user3.ini", - "custom.ini", - "essential.ini", - ] { - if let Err(e) = read_config_pre(p) { - let app_err = e.downcast_chain_ref::().unwrap(); - - match p { - &"global.ini" => { - assert!(app_err.find_kind::().is_some()); - assert!(app_err.root_cause().unwrap().is::()); - }, - _ => {} - } - } - } - } }