mirror of
https://github.com/haraldh/chainerror.git
synced 2025-01-30 16:46:42 +01:00
lots of documentation
This commit is contained in:
parent
5145b950b5
commit
8ad6eaceac
|
@ -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
112
README.md
|
@ -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`
|
|
@ -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
1
booksrc/README.md
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../README.md
|
|
@ -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
5
booksrc/end.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# The End
|
||||||
|
|
||||||
|
That's it for now…
|
||||||
|
|
||||||
|
Happy error handling!
|
|
@ -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}}
|
||||||
~~~
|
~~~
|
|
@ -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::*;
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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>`.
|
|
@ -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`.
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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
71
examples/example.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
539
src/lib.rs
539
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<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>>()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue