From 8ad6eaceac251ae78756f5cbf8fd4d812fa2bd22 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 21 Dec 2018 13:50:08 +0100 Subject: [PATCH] lots of documentation --- Cargo.toml | 4 +- README.md | 112 ++++++++- book.toml | 5 +- booksrc/README.md | 1 + booksrc/SUMMARY.md | 26 +- booksrc/end.md | 5 + booksrc/tutorial1.md | 22 +- booksrc/tutorial10.md | 16 +- booksrc/tutorial11.md | 19 +- booksrc/tutorial2.md | 14 +- booksrc/tutorial5.md | 4 +- booksrc/tutorial6.md | 10 +- booksrc/tutorial7.md | 26 +- booksrc/tutorial8.md | 22 +- booksrc/tutorial9.md | 26 +- examples/example.rs | 71 ++++++ examples/tutorial1.rs | 4 +- examples/tutorial10.rs | 20 +- examples/tutorial11.rs | 22 +- examples/tutorial2.rs | 4 +- examples/tutorial3.rs | 4 +- examples/tutorial4.rs | 3 +- examples/tutorial9.rs | 2 +- src/lib.rs | 539 +++++++++++++++++++++++++++++++++++++++-- 24 files changed, 892 insertions(+), 89 deletions(-) create mode 120000 booksrc/README.md create mode 100644 booksrc/end.md create mode 100644 examples/example.rs diff --git a/Cargo.toml b/Cargo.toml index ac13816..71fd263 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "chainerror" -version = "0.1.0" +version = "0.1.1" authors = ["Harald Hoyer "] edition = "2018" -license = "MIT" +license = "MIT OR Apache-2.0" documentation = "https://docs.rs/chainerror" homepage = "https://haraldh.github.io/chainerror/" repository = "https://github.com/haraldh/chainerror" diff --git a/README.md b/README.md index 3b91c23..014a843 100644 --- a/README.md +++ b/README.md @@ -12,4 +12,114 @@ Along with the `ChainError` struct, `chainerror` comes with some useful helpe Debug information is worth it! -For an introduction read the [Tutorial](https://haraldh.github.io/chainerror/) +Now continue reading the +[Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) + +## Example: +Output: + +~~~ +$ cargo run -q --example example +Main Error Report: func1 error calling func2 + +Error reported by Func2Error: func2 error: calling func3 + +The root cause was: std::io::Error: Kind( + NotFound +) + +Debug Error: +examples/example.rs:45: func1 error calling func2 +Caused by: +examples/example.rs:20: Func2Error(func2 error: calling func3) +Caused by: +examples/example.rs:13: Error reading 'foo.txt' +Caused by: +Kind(NotFound) +~~~ + +~~~rust,ignore +use chainerror::*; +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 func3() -> Result<(), Box> { + let filename = "foo.txt"; + do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + Ok(()) +} + +derive_str_cherr!(Func2Error); + +fn func2() -> ChainResult<(), Func2Error> { + func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; + 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() { + 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!("\nError reported by Func2Error: {}", e) + } + + if let Some(e) = e.root_cause() { + let ioerror = e.downcast_ref::().unwrap(); + eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); + } + + eprintln!("\nDebug Error:\n{:?}", e); + } +} + +~~~ + +## Features + +`no-fileline` +: completely turn off storing filename and line + +`display-cause` +: turn on printing a backtrace of the errors in `Display` + +`no-debug-cause` +: turn off printing a backtrace of the errors in `Debug` \ No newline at end of file diff --git a/book.toml b/book.toml index 53f7f2a..be4dacb 100644 --- a/book.toml +++ b/book.toml @@ -3,11 +3,8 @@ authors = ["Harald Hoyer"] multilingual = false src = "booksrc" title = "chainerror" - -[output.html.playpen] -editable = true +description = "A tutorial for the chainerror rust crate." [build] build-dir = "book" create-missing = false - diff --git a/booksrc/README.md b/booksrc/README.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/booksrc/README.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/booksrc/SUMMARY.md b/booksrc/SUMMARY.md index 8c48ac4..ac070ee 100644 --- a/booksrc/SUMMARY.md +++ b/booksrc/SUMMARY.md @@ -1,13 +1,17 @@ # Summary -- [Chapter 1](./tutorial1.md) -- [Chapter 2](./tutorial2.md) -- [Chapter 3](./tutorial3.md) -- [Chapter 4](./tutorial4.md) -- [Chapter 5](./tutorial5.md) -- [Chapter 6](./tutorial6.md) -- [Chapter 7](./tutorial7.md) -- [Chapter 8](./tutorial8.md) -- [Chapter 9](./tutorial9.md) -- [Chapter 10](./tutorial10.md) -- [Chapter 11](./tutorial11.md) +[chainerror](README.md) + +- [Simple String Errors](tutorial1.md) +- [Simple Chained String Errors](tutorial2.md) +- [Mapping Errors](tutorial3.md) +- [Saving coding chars](tutorial4.md) +- [The source() of Errors](tutorial5.md) +- [Downcast the Errors](tutorial6.md) +- [The root cause of all Errors](tutorial7.md) +- [Finding an Error cause](tutorial8.md) +- [Selective Error Handling](tutorial9.md) +- [ErrorKind to the rescue](tutorial10.md) +- [Debug for the ErrorKind](tutorial11.md) + +[The End](end.md) \ No newline at end of file diff --git a/booksrc/end.md b/booksrc/end.md new file mode 100644 index 0000000..13ffe89 --- /dev/null +++ b/booksrc/end.md @@ -0,0 +1,5 @@ +# The End + +That's it for now… + +Happy error handling! diff --git a/booksrc/tutorial1.md b/booksrc/tutorial1.md index 4a0ac91..cf1d9d0 100644 --- a/booksrc/tutorial1.md +++ b/booksrc/tutorial1.md @@ -1,12 +1,26 @@ # Simple String Errors -The most simplest of doing error handling in rust is by returning `String` as a `Box`. +An easy way of doing error handling in rust is by returning `String` as a `Box`. -As you can see by running the example (the "Play" button in upper right of the code block), this only +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`. -If the rust `main` function returns an Err(), this Err() will be displayed with `std::fmt::Debug`. +~~~ +Error: StringError("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](https://github.com/haraldh/chainerror). +~~~ +$ cargo run -q --example tutorial1 +~~~ ~~~rust -{{#include ../examples/tutorial1.rs:2:}} +{{#include ../examples/tutorial1.rs}} ~~~ \ No newline at end of file diff --git a/booksrc/tutorial10.md b/booksrc/tutorial10.md index 09b08aa..7feaf6e 100644 --- a/booksrc/tutorial10.md +++ b/booksrc/tutorial10.md @@ -1,6 +1,20 @@ # ErrorKind to the rescue -[TBD] +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`. + +Not using `String` errors anymore, the `cherr!()` macro seen in the beginning of +the tutorial has to be used again. + +Only returning `Func1ErrorKind` in `func1()` now let us get rid of `Result<(), Box>` and we can +use `ChainResult<(), Func1ErrorKind>`. + +In `main` we can now directly use the methods of `ChainError` without downcasting the error first. + +Also a nice `match` on `ChainError.kind()` is now possible, which returns `&T`, meaning +`&Func1ErrorKind` here. ~~~rust use crate::chainerror::*; diff --git a/booksrc/tutorial11.md b/booksrc/tutorial11.md index d7df420..3fce6fb 100644 --- a/booksrc/tutorial11.md +++ b/booksrc/tutorial11.md @@ -1,6 +1,23 @@ # Debug for the ErrorKind -[TBD] +One small improvement at the end of the tutorial 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. ~~~rust use crate::chainerror::*; diff --git a/booksrc/tutorial2.md b/booksrc/tutorial2.md index 933589a..86e2726 100644 --- a/booksrc/tutorial2.md +++ b/booksrc/tutorial2.md @@ -1,6 +1,7 @@ # Simple Chained String Errors -Now with the help of the `chainerror` crate, we can have a nicer output. +With relatively small changes and the help of the `cherr!` macro of the `chainerror` crate +the `String` errors are now chained together. Press the play button in the upper right corner and see the nice debug output. @@ -16,16 +17,17 @@ use crate::chainerror::*; ### What did we do here? ~~~rust,ignore -{{#include ../examples/tutorial2.rs:11:13}} +{{#include ../examples/tutorial2.rs:13:15}} ~~~ -The macro `cherr!(cause, newerror)` stores `cause` as the source/cause of `newerror` and returns -`newerror`, along with the filename (`file!()`) and line number (`line!()`). +The macro `cherr!(olderror, newerror)` stores `olderror` as the source/cause of `newerror` +along with the filename (`file!()`) and line number (`line!()`) +and returns `newerror`. -`Err(e)?` then returns the error `e` applying `e.into()`, so that we +`Err()?` then returns the inner error applying `.into()`, so that we again have a `Err(Box)` as a result. The `Debug` implementation of `ChainError` (which is returned by `cherr!()`) prints the `Debug` of `T` prefixed with the stored filename and line number. -`ChainError` is in our case `ChainError`. \ No newline at end of file +`ChainError` in our case is `ChainError`. \ No newline at end of file diff --git a/booksrc/tutorial5.md b/booksrc/tutorial5.md index e8c6302..d98f09e 100644 --- a/booksrc/tutorial5.md +++ b/booksrc/tutorial5.md @@ -12,8 +12,8 @@ use crate::chainerror::*; # } ~~~ -Note, that we changed the output of the error in `main()` from `Debug` to `Display`, so we don't see -the error backtrace with filename and line number. +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 enable the `Display` backtrace, you have to enable the feature `display-cause` for `chainerror`. \ No newline at end of file diff --git a/booksrc/tutorial6.md b/booksrc/tutorial6.md index aab9395..8154874 100644 --- a/booksrc/tutorial6.md +++ b/booksrc/tutorial6.md @@ -1,6 +1,14 @@ # Downcast the Errors -[TBD] +`std::error::Error` comes with some helper methods to get to the original object of the +`&(dyn Error + 'static)` returned by `.source()`. + +~~~rust,ignore +pub fn downcast_ref(&self) -> Option<&T> +pub fn downcast_mut(&mut self) -> Option<&mut T> +~~~ + +This is how it looks like, when using those: ~~~rust use crate::chainerror::*; diff --git a/booksrc/tutorial7.md b/booksrc/tutorial7.md index 0ada232..4ddd17f 100644 --- a/booksrc/tutorial7.md +++ b/booksrc/tutorial7.md @@ -1,6 +1,30 @@ # The root cause of all Errors -[TBD] +`chainerror` also has some helper methods: + +~~~rust,ignore +fn is_chain(&self) -> bool +fn downcast_chain_ref(&self) -> Option<&ChainError> +fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> +fn root_cause(&self) -> Option<&(dyn Error + 'static)> +fn find_cause(&self) -> Option<&U> +fn find_chain_cause(&self) -> Option<&ChainError> +fn kind<'a>(&'a self) -> &'a T +~~~ + +Using `downcast_chain_ref::()` gives a `ChainError`, which can be used +to call `.find_cause::()`. + +~~~rust,ignore + if let Some(s) = e.downcast_chain_ref::() { + if let Some(ioerror) = s.find_cause::() { +~~~ + +or to use `.root_cause()`, which of course can be of any type implementing `std::error::Error`. + +~~~rust,ignore + if let Some(e) = s.root_cause() { +~~~ ~~~rust use crate::chainerror::*; diff --git a/booksrc/tutorial8.md b/booksrc/tutorial8.md index ecd8f64..ce123e1 100644 --- a/booksrc/tutorial8.md +++ b/booksrc/tutorial8.md @@ -1,6 +1,26 @@ # Finding an Error cause -[TBD] +To distinguish the errors occuring in various places, we can define named string errors with the +"new type" pattern. + +~~~rust,ignore +derive_str_cherr!(Func2Error); +derive_str_cherr!(Func1Error); +~~~ + +Instead of `ChainError` we now have `struct Func1Error(String)` and `ChainError`. + +In the `main` function you can see, how we can match the different errors. + +Also see: +~~~rust,ignore + if let Some(f2err) = f1err.find_chain_cause::() { +~~~ +as a shortcut to +~~~rust,ignore + if let Some(f2err) = f1err.find_cause::>() { +~~~ +hiding the `ChainError` implementation detail. ~~~rust use crate::chainerror::*; diff --git a/booksrc/tutorial9.md b/booksrc/tutorial9.md index 060de6e..e9107ca 100644 --- a/booksrc/tutorial9.md +++ b/booksrc/tutorial9.md @@ -1,6 +1,28 @@ # Selective Error Handling -[TBD] +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: + +~~~rust,ignore +fn main() -> Result<(), Box> { + match func1() { + Err(e) if let Some(s) = e.downcast_chain_ref::() => + eprintln!("Func1ErrorIO:\n{:?}", s), + + Err(e) if let Some(s) = e.downcast_chain_ref::() => + 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. ~~~rust use crate::chainerror::*; @@ -9,4 +31,4 @@ use crate::chainerror::*; # mod chainerror { {{#includecomment ../src/lib.rs}} # } -~~~ \ No newline at end of file +~~~ diff --git a/examples/example.rs b/examples/example.rs new file mode 100644 index 0000000..3d97f70 --- /dev/null +++ b/examples/example.rs @@ -0,0 +1,71 @@ +use chainerror::*; +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 func3() -> Result<(), Box> { + let filename = "foo.txt"; + do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + Ok(()) +} + +derive_str_cherr!(Func2Error); + +fn func2() -> ChainResult<(), Func2Error> { + func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; + 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() { + 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!("\nError reported by Func2Error: {}", e) + } + + if let Some(e) = e.root_cause() { + let ioerror = e.downcast_ref::().unwrap(); + eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); + } + + eprintln!("\nDebug Error:\n{:?}", e); + } +} diff --git a/examples/tutorial1.rs b/examples/tutorial1.rs index 4149773..87778d9 100644 --- a/examples/tutorial1.rs +++ b/examples/tutorial1.rs @@ -1,9 +1,9 @@ - use std::error::Error; +use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box> { - Err("do_some_io error")?; + Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } diff --git a/examples/tutorial10.rs b/examples/tutorial10.rs index 72e703f..5d1aa1e 100644 --- a/examples/tutorial10.rs +++ b/examples/tutorial10.rs @@ -17,38 +17,38 @@ fn func2() -> Result<(), Box> { } #[derive(Debug)] -enum Func1Error { +enum Func1ErrorKind { Func2, IO(String), } -impl ::std::fmt::Display for Func1Error { +impl ::std::fmt::Display for Func1ErrorKind { 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), + Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), + Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), } } } -fn func1() -> ChainResult<(), Func1Error> { - func2().map_err(|e| cherr!(e, Func1Error::Func2))?; +fn func1() -> ChainResult<(), Func1ErrorKind> { + func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; let filename = String::from("bar.txt"); - do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; + do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::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) => { + 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::() { - eprintln!("Error reported by Func2Error: {}", e) + eprintln!("\nError reported by Func2Error: {}", e) } eprintln!("\nDebug Error:\n{:?}", e); diff --git a/examples/tutorial11.rs b/examples/tutorial11.rs index 62cabaa..336b7fb 100644 --- a/examples/tutorial11.rs +++ b/examples/tutorial11.rs @@ -16,44 +16,44 @@ fn func2() -> Result<(), Box> { Ok(()) } -enum Func1Error { +enum Func1ErrorKind { Func2, IO(String), } -impl ::std::fmt::Display for Func1Error { +impl ::std::fmt::Display for Func1ErrorKind { 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), + Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), + Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), } } } -impl ::std::fmt::Debug for Func1Error { +impl ::std::fmt::Debug for Func1ErrorKind { 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))?; +fn func1() -> ChainResult<(), Func1ErrorKind> { + func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; let filename = String::from("bar.txt"); - do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; + do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::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) => { + 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::() { - eprintln!("Error reported by Func2Error: {}", e) + eprintln!("\nError reported by Func2Error: {}", e) } eprintln!("\nDebug Error:\n{:?}", e); diff --git a/examples/tutorial2.rs b/examples/tutorial2.rs index 2cf855d..d75f0a4 100644 --- a/examples/tutorial2.rs +++ b/examples/tutorial2.rs @@ -1,9 +1,11 @@ use chainerror::*; + use std::error::Error; +use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box> { - Err("do_some_io error")?; + Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } diff --git a/examples/tutorial3.rs b/examples/tutorial3.rs index 876f5a9..6915a62 100644 --- a/examples/tutorial3.rs +++ b/examples/tutorial3.rs @@ -1,9 +1,11 @@ use chainerror::*; + use std::error::Error; +use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box> { - Err("do_some_io error")?; + Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } diff --git a/examples/tutorial4.rs b/examples/tutorial4.rs index f6d8cba..de51745 100644 --- a/examples/tutorial4.rs +++ b/examples/tutorial4.rs @@ -1,9 +1,10 @@ use chainerror::*; use std::error::Error; +use std::io; use std::result::Result; fn do_some_io() -> Result<(), Box> { - Err("do_some_io error")?; + Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } diff --git a/examples/tutorial9.rs b/examples/tutorial9.rs index df4a1e9..f1954d3 100644 --- a/examples/tutorial9.rs +++ b/examples/tutorial9.rs @@ -32,7 +32,7 @@ fn main() -> Result<(), Box> { eprintln!("Func1ErrorIO:\n{:?}", s); } - if let Some(s) = try_cherr_ref!(e, Func1ErrorFunc2) { + if let Some(s) = e.downcast_chain_ref::() { eprintln!("Func1ErrorFunc2:\n{:?}", s); } } diff --git a/src/lib.rs b/src/lib.rs index 0987e34..3f9ccfc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,164 @@ +/*! + +`chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your +binaries, you still have the error backtrace. + +`chainerror` has no dependencies! + +`chainerror` uses `.source()` of `std::error::Error` along with `line()!` and `file()!` 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 `ChainError` struct, `chainerror` comes with some useful helper macros to save a lot of typing. + +# Examples + +~~~rust +use chainerror::*; +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() { + if let Err(e) = func1() { + assert_eq!( + format!("\n{:?}\n", e), r#" +src/lib.rs:20: func1 error +Caused by: +src/lib.rs:15: Error reading 'foo.txt' +Caused by: +Kind(NotFound) +"# + ); + } +# else { +# unreachable!(); +# } +} +~~~ + + +~~~rust +use chainerror::*; +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 func3() -> Result<(), Box> { + let filename = "foo.txt"; + do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + Ok(()) +} + +derive_str_cherr!(Func2Error); + +fn func2() -> ChainResult<(), Func2Error> { + func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; + 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() { + if let Err(e) = func1() { + assert!( + match e.kind() { + Func1Error::Func2 => { + eprintln!("Main Error Report: func1 error calling func2"); + true + } + Func1Error::IO(filename) => { + eprintln!("Main Error Report: func1 error reading '{}'", filename); + false + } + } + ); + + assert!(e.find_chain_cause::().is_some()); + + if let Some(e) = e.find_chain_cause::() { + eprintln!("\nError reported by Func2Error: {}", e) + } + + + assert!(e.root_cause().is_some()); + + if let Some(e) = e.root_cause() { + let ioerror = e.downcast_ref::().unwrap(); + eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); + } + + assert_eq!( + format!("\n{:?}\n", e), r#" +src/lib.rs:47: func1 error calling func2 +Caused by: +src/lib.rs:22: Func2Error(func2 error: calling func3) +Caused by: +src/lib.rs:15: Error reading 'foo.txt' +Caused by: +Kind(NotFound) +"# + ); + } +# else { +# unreachable!(); +# } +} +~~~ + +!*/ + +use std::any::TypeId; use std::error::Error; use std::fmt::{Debug, Display, Formatter, Result}; -use std::result::Result as StdResult; +/** chains an inner error kind `T` with a causing error +**/ pub struct ChainError { #[cfg(not(feature = "no-fileline"))] occurrence: Option<(u32, &'static str)>, @@ -9,10 +166,12 @@ pub struct ChainError { error_cause: Option>, } -pub type ChainResult = StdResult>; +/// convenience type alias +pub type ChainResult = std::result::Result>; impl ChainError { #[cfg(not(feature = "no-fileline"))] + /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly pub fn new( kind: T, error_cause: Option>, @@ -26,6 +185,7 @@ impl ChainError { } #[cfg(feature = "no-fileline")] + /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly pub fn new( kind: T, error_cause: Option>, @@ -34,6 +194,7 @@ impl ChainError { Self { kind, error_cause } } + /// return the root cause of the error chain, if any exists pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> { let mut cause = self as &(dyn Error + 'static); while let Some(c) = cause.source() { @@ -42,6 +203,54 @@ impl ChainError { Some(cause) } + /** find the first error cause of type U, if any exists + + # Examples + + ~~~rust + # use crate::chainerror::*; + # 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() { + if let Err(e) = func1() { + if let Some(f1err) = e.downcast_chain_ref::() { + + assert!(f1err.find_cause::().is_some()); + + assert!(f1err.find_chain_cause::().is_some()); + } + # else { + # panic!(); + # } + } + # else { + # unreachable!(); + # } + } + ~~~ + **/ pub fn find_cause(&self) -> Option<&U> { let mut cause = self as &(dyn Error + 'static); loop { @@ -56,6 +265,21 @@ impl ChainError { } } + /** find the first error cause of type ChainError, if any exists + + Same as `find_cause`, but hides the `ChainError` implementation internals + + # Examples + + ~~~rust,ignore + /// Instead of writing + err.find_cause::>(); + + /// leave out the ChainError implementation detail + err.find_chain_cause::(); + ~~~ + + **/ pub fn find_chain_cause(&self) -> Option<&ChainError> { let mut cause = self as &(dyn Error + 'static); loop { @@ -70,19 +294,84 @@ impl ChainError { } } + /** return a reference to T of `ChainError` + + # Examples + + ~~~rust + # use crate::chainerror::*; + # 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 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() -> ChainResult<(), Func1ErrorKind> { + func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; + do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO("bar.txt".into())))?; + Ok(()) + } + + fn main() { + if let Err(e) = func1() { + match e.kind() { + Func1ErrorKind::Func2 => {}, + Func1ErrorKind::IO(filename) => panic!(), + } + } + # else { + # unreachable!(); + # } + } + ~~~ + + **/ pub fn kind<'a>(&'a self) -> &'a T { &self.kind } } +/** convenience trait to hide the `ChainError` implementation internals +**/ pub trait ChainErrorDown { + /** test if of type `ChainError` + **/ fn is_chain(&self) -> bool; + /** downcast to a reference of `ChainError` + **/ fn downcast_chain_ref(&self) -> Option<&ChainError>; + /** downcast to a mutable reference of `ChainError` + **/ fn downcast_chain_mut(&mut self) -> Option<&mut ChainError>; } -use std::any::TypeId; - impl ChainErrorDown for ChainError { fn is_chain(&self) -> bool { TypeId::of::() == TypeId::of::() @@ -201,7 +490,11 @@ impl Debug for ChainError { } } - Debug::fmt(&self.kind, f)?; + if self.is_chain::() { + Display::fmt(&self.kind, f)?; + } else { + Debug::fmt(&self.kind, f)?; + } #[cfg(not(feature = "no-debug-cause"))] { @@ -214,6 +507,93 @@ impl Debug for ChainError { } } +/** creates a new `ChainError` + +# Examples + +Create a new ChainError, where `FooError` must implement `Display` and `Debug`. +~~~rust +# use chainerror::*; +# +# #[derive(Debug)] +enum FooError { + Bar, + Baz(&'static str), +} +# +# impl ::std::fmt::Display for FooError { +# fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { +# match self { +# FooError::Bar => write!(f, "Bar Error"), +# FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), +# } +# } +# } + +// impl ::std::fmt::Display for FooError + +fn do_some_stuff() -> bool { + false +} + +fn func() -> ChainResult<(), FooError> { + if ! do_some_stuff() { + Err(cherr!(FooError::Baz("Error")))?; + } + Ok(()) +} +# +# pub fn main() { +# match func().unwrap_err().kind() { +# FooError::Baz(s) if s == &"Error" => {}, +# _ => panic!(), +# } +# } +~~~ + +Additionally an error cause can be added. + +~~~rust +# use chainerror::*; +# use std::io; +# use std::error::Error; +# +# #[derive(Debug)] +# enum FooError { +# Bar, +# Baz(&'static str), +# } +# +# impl ::std::fmt::Display for FooError { +# fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { +# match self { +# FooError::Bar => write!(f, "Bar Error"), +# FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), +# } +# } +# } +# +fn do_some_stuff() -> Result<(), Box> { + Err(io::Error::from(io::ErrorKind::NotFound))?; + Ok(()) +} + +fn func() -> ChainResult<(), FooError> { + do_some_stuff().map_err( + |e| cherr!(e, FooError::Baz("Error")) + )?; + Ok(()) +} +# +# pub fn main() { +# match func().unwrap_err().kind() { +# FooError::Baz(s) if s == &"Error" => {}, +# _ => panic!(), +# } +# } +~~~ + +**/ #[macro_export] macro_rules! cherr { ( $k:expr ) => { @@ -224,6 +604,92 @@ macro_rules! cherr { }; } +/** convenience macro for |e| cherr!(e, format!(…)) + +# Examples + +~~~rust +# use crate::chainerror::*; +# 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() { +# if let Err(e) = func1() { +# assert_eq!( +# format!("\n{:?}\n", e), r#" +# src/lib.rs:20: func1 error +# Caused by: +# src/lib.rs:15: Error reading 'foo.txt' +# Caused by: +# Kind(NotFound) +# "# +# ); +# } else { +# unreachable!(); +# } +# } +~~~ + +`mstrerr!()` can also be used to map a new `ChainError`, where T was defined with +`derive_str_cherr!(T)` + +~~~rust +# use crate::chainerror::*; +# 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() { +# if let Err(e) = func1() { +# if let Some(f1err) = e.downcast_chain_ref::() { +# assert!(f1err.find_cause::>().is_some()); +# assert!(f1err.find_chain_cause::().is_some()); +# } else { +# panic!(); +# } +# } else { +# unreachable!(); +# } +# } +~~~ +**/ #[macro_export] macro_rules! mstrerr { ( $t:ident, $v:expr $(, $more:expr)* ) => { @@ -237,6 +703,49 @@ macro_rules! mstrerr { }; } +/** convenience macro to create a "new type" T(String) and implement Display + Debug for T + +~~~rust +# use crate::chainerror::*; +# 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() -> ChainResult<(), Func2Error> { + 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() { +# if let Err(e) = func1() { +# if let Some(f1err) = e.downcast_chain_ref::() { +# assert!(f1err.find_cause::>().is_some()); +# assert!(f1err.find_chain_cause::().is_some()); +# } else { +# panic!(); +# } +# } else { +# unreachable!(); +# } +# } +~~~ + +**/ #[macro_export] macro_rules! derive_str_cherr { ($e:ident) => { @@ -254,23 +763,3 @@ macro_rules! derive_str_cherr { 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::>() - }; -}