lots of documentation

This commit is contained in:
Harald Hoyer 2018-12-21 13:50:08 +01:00
parent 5145b950b5
commit 8ad6eaceac
24 changed files with 892 additions and 89 deletions

View file

@ -1,9 +1,9 @@
[package] [package]
name = "chainerror" name = "chainerror"
version = "0.1.0" version = "0.1.1"
authors = ["Harald Hoyer <harald@redhat.com>"] authors = ["Harald Hoyer <harald@redhat.com>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT OR Apache-2.0"
documentation = "https://docs.rs/chainerror" documentation = "https://docs.rs/chainerror"
homepage = "https://haraldh.github.io/chainerror/" homepage = "https://haraldh.github.io/chainerror/"
repository = "https://github.com/haraldh/chainerror" repository = "https://github.com/haraldh/chainerror"

112
README.md
View file

@ -12,4 +12,114 @@ Along with the `ChainError<T>` struct, `chainerror` comes with some useful helpe
Debug information is worth it! 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<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(())
}
fn func3() -> Result<(), Box<Error>> {
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::<Func2Error>() {
eprintln!("\nError reported by Func2Error: {}", e)
}
if let Some(e) = e.root_cause() {
let ioerror = e.downcast_ref::<io::Error>().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`

View file

@ -3,11 +3,8 @@ authors = ["Harald Hoyer"]
multilingual = false multilingual = false
src = "booksrc" src = "booksrc"
title = "chainerror" title = "chainerror"
description = "A tutorial for the chainerror rust crate."
[output.html.playpen]
editable = true
[build] [build]
build-dir = "book" build-dir = "book"
create-missing = false create-missing = false

1
booksrc/README.md Symbolic link
View file

@ -0,0 +1 @@
../README.md

View file

@ -1,13 +1,17 @@
# Summary # Summary
- [Chapter 1](./tutorial1.md) [chainerror](README.md)
- [Chapter 2](./tutorial2.md)
- [Chapter 3](./tutorial3.md) - [Simple String Errors](tutorial1.md)
- [Chapter 4](./tutorial4.md) - [Simple Chained String Errors](tutorial2.md)
- [Chapter 5](./tutorial5.md) - [Mapping Errors](tutorial3.md)
- [Chapter 6](./tutorial6.md) - [Saving coding chars](tutorial4.md)
- [Chapter 7](./tutorial7.md) - [The source() of Errors](tutorial5.md)
- [Chapter 8](./tutorial8.md) - [Downcast the Errors](tutorial6.md)
- [Chapter 9](./tutorial9.md) - [The root cause of all Errors](tutorial7.md)
- [Chapter 10](./tutorial10.md) - [Finding an Error cause](tutorial8.md)
- [Chapter 11](./tutorial11.md) - [Selective Error Handling](tutorial9.md)
- [ErrorKind to the rescue](tutorial10.md)
- [Debug for the ErrorKind](tutorial11.md)
[The End](end.md)

5
booksrc/end.md Normal file
View file

@ -0,0 +1,5 @@
# The End
That's it for now…
Happy error handling!

View file

@ -1,12 +1,26 @@
# Simple String Errors # Simple String Errors
The most simplest of doing error handling in rust is by returning `String` as a `Box<Error>`. An easy way of doing error handling in rust is by returning `String` as a `Box<std::error::Error>`.
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`. 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 ~~~rust
{{#include ../examples/tutorial1.rs:2:}} {{#include ../examples/tutorial1.rs}}
~~~ ~~~

View file

@ -1,6 +1,20 @@
# ErrorKind to the rescue # 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<Error>>` and we can
use `ChainResult<(), Func1ErrorKind>`.
In `main` we can now directly use the methods of `ChainError<T>` without downcasting the error first.
Also a nice `match` on `ChainError<T>.kind()` is now possible, which returns `&T`, meaning
`&Func1ErrorKind` here.
~~~rust ~~~rust
use crate::chainerror::*; use crate::chainerror::*;

View file

@ -1,6 +1,23 @@
# Debug for the ErrorKind # 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 ~~~rust
use crate::chainerror::*; use crate::chainerror::*;

View file

@ -1,6 +1,7 @@
# Simple Chained String Errors # 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. 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? ### What did we do here?
~~~rust,ignore ~~~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 The macro `cherr!(olderror, newerror)` stores `olderror` as the source/cause of `newerror`
`newerror`, along with the filename (`file!()`) and line number (`line!()`). 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<Error>)` as a result. again have a `Err(Box<Error>)` as a result.
The `Debug` implementation of `ChainError<T>` (which is returned by `cherr!()`) The `Debug` implementation of `ChainError<T>` (which is returned by `cherr!()`)
prints the `Debug` of `T` prefixed with the stored filename and line number. prints the `Debug` of `T` prefixed with the stored filename and line number.
`ChainError<T>` is in our case `ChainError<String>`. `ChainError<T>` in our case is `ChainError<String>`.

View file

@ -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 Note, that because we changed the output of the error in `main()` from
the error backtrace with filename and line number. `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`. To enable the `Display` backtrace, you have to enable the feature `display-cause` for `chainerror`.

View file

@ -1,6 +1,14 @@
# Downcast the Errors # 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<T: Error + 'static>(&self) -> Option<&T>
pub fn downcast_mut<T: Error + 'static>(&mut self) -> Option<&mut T>
~~~
This is how it looks like, when using those:
~~~rust ~~~rust
use crate::chainerror::*; use crate::chainerror::*;

View file

@ -1,6 +1,30 @@
# The root cause of all Errors # The root cause of all Errors
[TBD] `chainerror` also has some helper methods:
~~~rust,ignore
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>>
fn root_cause(&self) -> Option<&(dyn Error + 'static)>
fn find_cause<U: Error + 'static>(&self) -> Option<&U>
fn find_chain_cause<U: Error + 'static>(&self) -> Option<&ChainError<U>>
fn kind<'a>(&'a self) -> &'a T
~~~
Using `downcast_chain_ref::<String>()` gives a `ChainError<String>`, which can be used
to call `.find_cause::<io::Error>()`.
~~~rust,ignore
if let Some(s) = e.downcast_chain_ref::<String>() {
if let Some(ioerror) = s.find_cause::<io::Error>() {
~~~
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 ~~~rust
use crate::chainerror::*; use crate::chainerror::*;

View file

@ -1,6 +1,26 @@
# Finding an Error cause # 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<String>` we now have `struct Func1Error(String)` and `ChainError<Func1Error>`.
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::<Func2Error>() {
~~~
as a shortcut to
~~~rust,ignore
if let Some(f2err) = f1err.find_cause::<ChainError<Func2Error>>() {
~~~
hiding the `ChainError<T>` implementation detail.
~~~rust ~~~rust
use crate::chainerror::*; use crate::chainerror::*;

View file

@ -1,6 +1,28 @@
# Selective Error Handling # 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<Error>> {
match func1() {
Err(e) if let Some(s) = e.downcast_chain_ref::<Func1ErrorIO>() =>
eprintln!("Func1ErrorIO:\n{:?}", s),
Err(e) if let Some(s) = e.downcast_chain_ref::<Func1ErrorFunc2>() =>
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 ~~~rust
use crate::chainerror::*; use crate::chainerror::*;
@ -9,4 +31,4 @@ use crate::chainerror::*;
# mod chainerror { # mod chainerror {
{{#includecomment ../src/lib.rs}} {{#includecomment ../src/lib.rs}}
# } # }
~~~ ~~~

71
examples/example.rs Normal file
View file

@ -0,0 +1,71 @@
use chainerror::*;
use std::error::Error;
use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(())
}
fn func3() -> Result<(), Box<Error>> {
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::<Func2Error>() {
eprintln!("\nError reported by Func2Error: {}", e)
}
if let Some(e) = e.root_cause() {
let ioerror = e.downcast_ref::<io::Error>().unwrap();
eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror);
}
eprintln!("\nDebug Error:\n{:?}", e);
}
}

View file

@ -1,9 +1,9 @@
use std::error::Error; use std::error::Error;
use std::io;
use std::result::Result; use std::result::Result;
fn do_some_io() -> Result<(), Box<Error>> { fn do_some_io() -> Result<(), Box<Error>> {
Err("do_some_io error")?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }

View file

@ -17,38 +17,38 @@ fn func2() -> Result<(), Box<Error>> {
} }
#[derive(Debug)] #[derive(Debug)]
enum Func1Error { enum Func1ErrorKind {
Func2, Func2,
IO(String), 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 { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match self { match self {
Func1Error::Func2 => write!(f, "func1 error calling func2"), Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"),
Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename),
} }
} }
} }
fn func1() -> ChainResult<(), Func1Error> { fn func1() -> ChainResult<(), Func1ErrorKind> {
func2().map_err(|e| cherr!(e, Func1Error::Func2))?; func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?;
let filename = String::from("bar.txt"); 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(()) Ok(())
} }
fn main() -> Result<(), Box<Error>> { fn main() -> Result<(), Box<Error>> {
if let Err(e) = func1() { if let Err(e) = func1() {
match e.kind() { match e.kind() {
Func1Error::Func2 => eprintln!("Main Error Report: func1 error calling func2"), Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"),
Func1Error::IO(filename) => { Func1ErrorKind::IO(filename) => {
eprintln!("Main Error Report: func1 error reading '{}'", filename) eprintln!("Main Error Report: func1 error reading '{}'", filename)
} }
} }
if let Some(e) = e.find_chain_cause::<Func2Error>() { if let Some(e) = e.find_chain_cause::<Func2Error>() {
eprintln!("Error reported by Func2Error: {}", e) eprintln!("\nError reported by Func2Error: {}", e)
} }
eprintln!("\nDebug Error:\n{:?}", e); eprintln!("\nDebug Error:\n{:?}", e);

View file

@ -16,44 +16,44 @@ fn func2() -> Result<(), Box<Error>> {
Ok(()) Ok(())
} }
enum Func1Error { enum Func1ErrorKind {
Func2, Func2,
IO(String), 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 { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match self { match self {
Func1Error::Func2 => write!(f, "func1 error calling func2"), Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"),
Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), 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 { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
write!(f, "{}", self) write!(f, "{}", self)
} }
} }
fn func1() -> ChainResult<(), Func1Error> { fn func1() -> ChainResult<(), Func1ErrorKind> {
func2().map_err(|e| cherr!(e, Func1Error::Func2))?; func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?;
let filename = String::from("bar.txt"); 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(()) Ok(())
} }
fn main() -> Result<(), Box<Error>> { fn main() -> Result<(), Box<Error>> {
if let Err(e) = func1() { if let Err(e) = func1() {
match e.kind() { match e.kind() {
Func1Error::Func2 => eprintln!("Main Error Report: func1 error calling func2"), Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"),
Func1Error::IO(filename) => { Func1ErrorKind::IO(filename) => {
eprintln!("Main Error Report: func1 error reading '{}'", filename) eprintln!("Main Error Report: func1 error reading '{}'", filename)
} }
} }
if let Some(e) = e.find_chain_cause::<Func2Error>() { if let Some(e) = e.find_chain_cause::<Func2Error>() {
eprintln!("Error reported by Func2Error: {}", e) eprintln!("\nError reported by Func2Error: {}", e)
} }
eprintln!("\nDebug Error:\n{:?}", e); eprintln!("\nDebug Error:\n{:?}", e);

View file

@ -1,9 +1,11 @@
use chainerror::*; use chainerror::*;
use std::error::Error; use std::error::Error;
use std::io;
use std::result::Result; use std::result::Result;
fn do_some_io() -> Result<(), Box<Error>> { fn do_some_io() -> Result<(), Box<Error>> {
Err("do_some_io error")?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }

View file

@ -1,9 +1,11 @@
use chainerror::*; use chainerror::*;
use std::error::Error; use std::error::Error;
use std::io;
use std::result::Result; use std::result::Result;
fn do_some_io() -> Result<(), Box<Error>> { fn do_some_io() -> Result<(), Box<Error>> {
Err("do_some_io error")?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }

View file

@ -1,9 +1,10 @@
use chainerror::*; use chainerror::*;
use std::error::Error; use std::error::Error;
use std::io;
use std::result::Result; use std::result::Result;
fn do_some_io() -> Result<(), Box<Error>> { fn do_some_io() -> Result<(), Box<Error>> {
Err("do_some_io error")?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }

View file

@ -32,7 +32,7 @@ fn main() -> Result<(), Box<Error>> {
eprintln!("Func1ErrorIO:\n{:?}", s); eprintln!("Func1ErrorIO:\n{:?}", s);
} }
if let Some(s) = try_cherr_ref!(e, Func1ErrorFunc2) { if let Some(s) = e.downcast_chain_ref::<Func1ErrorFunc2>() {
eprintln!("Func1ErrorFunc2:\n{:?}", s); eprintln!("Func1ErrorFunc2:\n{:?}", s);
} }
} }

View file

@ -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<T>` 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<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(())
}
fn func2() -> Result<(), Box<Error>> {
let filename = "foo.txt";
do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?;
Ok(())
}
fn func1() -> Result<(), Box<Error>> {
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<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(())
}
fn func3() -> Result<(), Box<Error>> {
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::<Func2Error>().is_some());
if let Some(e) = e.find_chain_cause::<Func2Error>() {
eprintln!("\nError reported by Func2Error: {}", e)
}
assert!(e.root_cause().is_some());
if let Some(e) = e.root_cause() {
let ioerror = e.downcast_ref::<io::Error>().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::error::Error;
use std::fmt::{Debug, Display, Formatter, Result}; 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<T> { pub struct ChainError<T> {
#[cfg(not(feature = "no-fileline"))] #[cfg(not(feature = "no-fileline"))]
occurrence: Option<(u32, &'static str)>, occurrence: Option<(u32, &'static str)>,
@ -9,10 +166,12 @@ pub struct ChainError<T> {
error_cause: Option<Box<dyn Error + 'static>>, error_cause: Option<Box<dyn Error + 'static>>,
} }
pub type ChainResult<O, E> = StdResult<O, ChainError<E>>; /// convenience type alias
pub type ChainResult<O, E> = std::result::Result<O, ChainError<E>>;
impl<T: 'static + Display + Debug> ChainError<T> { impl<T: 'static + Display + Debug> ChainError<T> {
#[cfg(not(feature = "no-fileline"))] #[cfg(not(feature = "no-fileline"))]
/// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly
pub fn new( pub fn new(
kind: T, kind: T,
error_cause: Option<Box<dyn Error + 'static>>, error_cause: Option<Box<dyn Error + 'static>>,
@ -26,6 +185,7 @@ impl<T: 'static + Display + Debug> ChainError<T> {
} }
#[cfg(feature = "no-fileline")] #[cfg(feature = "no-fileline")]
/// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly
pub fn new( pub fn new(
kind: T, kind: T,
error_cause: Option<Box<dyn Error + 'static>>, error_cause: Option<Box<dyn Error + 'static>>,
@ -34,6 +194,7 @@ impl<T: 'static + Display + Debug> ChainError<T> {
Self { kind, error_cause } Self { kind, error_cause }
} }
/// return the root cause of the error chain, if any exists
pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> { pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> {
let mut cause = self as &(dyn Error + 'static); let mut cause = self as &(dyn Error + 'static);
while let Some(c) = cause.source() { while let Some(c) = cause.source() {
@ -42,6 +203,54 @@ impl<T: 'static + Display + Debug> ChainError<T> {
Some(cause) 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<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(())
}
derive_str_cherr!(Func2Error);
fn func2() -> Result<(), Box<Error>> {
let filename = "foo.txt";
do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?;
Ok(())
}
derive_str_cherr!(Func1Error);
fn func1() -> Result<(), Box<Error>> {
func2().map_err(mstrerr!(Func1Error, "func1 error"))?;
Ok(())
}
fn main() {
if let Err(e) = func1() {
if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() {
assert!(f1err.find_cause::<io::Error>().is_some());
assert!(f1err.find_chain_cause::<Func2Error>().is_some());
}
# else {
# panic!();
# }
}
# else {
# unreachable!();
# }
}
~~~
**/
pub fn find_cause<U: Error + 'static>(&self) -> Option<&U> { pub fn find_cause<U: Error + 'static>(&self) -> Option<&U> {
let mut cause = self as &(dyn Error + 'static); let mut cause = self as &(dyn Error + 'static);
loop { loop {
@ -56,6 +265,21 @@ impl<T: 'static + Display + Debug> ChainError<T> {
} }
} }
/** find the first error cause of type ChainError<U>, if any exists
Same as `find_cause`, but hides the `ChainError<U>` implementation internals
# Examples
~~~rust,ignore
/// Instead of writing
err.find_cause::<ChainError<FooError>>();
/// leave out the ChainError<T> implementation detail
err.find_chain_cause::<FooError>();
~~~
**/
pub fn find_chain_cause<U: Error + 'static>(&self) -> Option<&ChainError<U>> { pub fn find_chain_cause<U: Error + 'static>(&self) -> Option<&ChainError<U>> {
let mut cause = self as &(dyn Error + 'static); let mut cause = self as &(dyn Error + 'static);
loop { loop {
@ -70,19 +294,84 @@ impl<T: 'static + Display + Debug> ChainError<T> {
} }
} }
/** return a reference to T of `ChainError<T>`
# Examples
~~~rust
# use crate::chainerror::*;
# use std::error::Error;
# use std::io;
# use std::result::Result;
#
fn do_some_io() -> Result<(), Box<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(())
}
derive_str_cherr!(Func2Error);
fn func2() -> Result<(), Box<Error>> {
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 { pub fn kind<'a>(&'a self) -> &'a T {
&self.kind &self.kind
} }
} }
/** convenience trait to hide the `ChainError<T>` implementation internals
**/
pub trait ChainErrorDown { pub trait ChainErrorDown {
/** test if of type `ChainError<T>`
**/
fn is_chain<T: 'static + Display + Debug>(&self) -> bool; fn is_chain<T: 'static + Display + Debug>(&self) -> bool;
/** downcast to a reference of `ChainError<T>`
**/
fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>>; fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>>;
/** downcast to a mutable reference of `ChainError<T>`
**/
fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut 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> { impl<U: 'static + Display + Debug> ChainErrorDown for ChainError<U> {
fn is_chain<T: 'static + Display + Debug>(&self) -> bool { fn is_chain<T: 'static + Display + Debug>(&self) -> bool {
TypeId::of::<T>() == TypeId::of::<U>() TypeId::of::<T>() == TypeId::of::<U>()
@ -201,7 +490,11 @@ impl<T: 'static + Display + Debug> Debug for ChainError<T> {
} }
} }
Debug::fmt(&self.kind, f)?; if self.is_chain::<String>() {
Display::fmt(&self.kind, f)?;
} else {
Debug::fmt(&self.kind, f)?;
}
#[cfg(not(feature = "no-debug-cause"))] #[cfg(not(feature = "no-debug-cause"))]
{ {
@ -214,6 +507,93 @@ impl<T: 'static + Display + Debug> Debug for ChainError<T> {
} }
} }
/** creates a new `ChainError<T>`
# Examples
Create a new ChainError<FooError>, 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<Error>> {
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_export]
macro_rules! cherr { macro_rules! cherr {
( $k:expr ) => { ( $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<Error>> {
# Err(io::Error::from(io::ErrorKind::NotFound))?;
# Ok(())
# }
#
fn func2() -> Result<(), Box<Error>> {
let filename = "foo.txt";
do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?;
Ok(())
}
fn func1() -> Result<(), Box<Error>> {
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<T>`, 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<Error>> {
# Err(io::Error::from(io::ErrorKind::NotFound))?;
# Ok(())
# }
#
derive_str_cherr!(Func2Error);
fn func2() -> Result<(), Box<Error>> {
let filename = "foo.txt";
do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?;
Ok(())
}
derive_str_cherr!(Func1Error);
fn func1() -> Result<(), Box<Error>> {
func2().map_err(mstrerr!(Func1Error, "func1 error"))?;
Ok(())
}
#
# fn main() {
# if let Err(e) = func1() {
# if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() {
# assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some());
# assert!(f1err.find_chain_cause::<Func2Error>().is_some());
# } else {
# panic!();
# }
# } else {
# unreachable!();
# }
# }
~~~
**/
#[macro_export] #[macro_export]
macro_rules! mstrerr { macro_rules! mstrerr {
( $t:ident, $v:expr $(, $more:expr)* ) => { ( $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<Error>> {
# 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<Error>> {
func2().map_err(mstrerr!(Func1Error, "func1 error"))?;
Ok(())
}
#
# fn main() {
# if let Err(e) = func1() {
# if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() {
# assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some());
# assert!(f1err.find_chain_cause::<Func2Error>().is_some());
# } else {
# panic!();
# }
# } else {
# unreachable!();
# }
# }
~~~
**/
#[macro_export] #[macro_export]
macro_rules! derive_str_cherr { macro_rules! derive_str_cherr {
($e:ident) => { ($e:ident) => {
@ -254,23 +763,3 @@ macro_rules! derive_str_cherr {
impl ::std::error::Error for $e {} impl ::std::error::Error for $e {}
}; };
} }
#[macro_export]
macro_rules! try_cherr_ref {
( $e:expr, $t:ident ) => {
$e.downcast_ref::<ChainError<$t>>()
};
( $e:expr, $t:path ) => {
$e.downcast_ref::<ChainError<$t>>()
};
}
#[macro_export]
macro_rules! try_cherr_mut {
( $e:expr, $t:ident ) => {
$e.downcast_mut::<ChainError<$t>>()
};
( $e:expr, $t:path ) => {
$e.downcast_mut::<ChainError<$t>>()
};
}