Compare commits

..

No commits in common. "master" and "v0.4.2" have entirely different histories.

44 changed files with 1677 additions and 1874 deletions

View file

@ -1,7 +0,0 @@
version: 2
updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10

View file

@ -1,38 +0,0 @@
name: coverage
on:
# Trigger the workflow on push or pull request,
# but only for the master branch
push:
branches:
- master
pull_request:
branches:
- master
release:
types:
- created
jobs:
coverage:
runs-on: ubuntu-latest
env:
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
target: x86_64-unknown-linux-gnu
toolchain: nightly
components: llvm-tools-preview
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Generate code coverage
run: cargo +nightly llvm-cov --all-features --workspace --codecov --doctests --output-path codecov.json
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
files: codecov.json
fail_ci_if_error: true

View file

@ -1,30 +0,0 @@
name: github pages
on:
push:
tags:
- '*'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build mdbook
run: cargo install mdbook
- name: Build cargo-readme
run: cargo install cargo-readme
- name: Build README.md
run: cargo readme > README.md
- name: Build
run: mdbook build
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
publish_dir: ./book

View file

@ -1,72 +0,0 @@
name: Rust
on:
# Trigger the workflow on push or pull request,
# but only for the master branch
push:
branches:
- master
pull_request:
branches:
- master
release:
types:
- created
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
version:
- 1.54.0
- stable
- beta
- nightly
steps:
- uses: actions/checkout@v1
- name: Install toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.version }}
profile: minimal
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- name: Build --all-features
run: cargo build --verbose --all-features
- name: Run tests --all-features
run: cargo test --verbose --all-features
fmt:
name: cargo fmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@master
with:
components: rustfmt
toolchain: stable
profile: minimal
override: true
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
name: cargo clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@master
with:
components: clippy
toolchain: stable
profile: minimal
override: true
- uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings

22
.travis.yml Normal file
View file

@ -0,0 +1,22 @@
language: rust
branches:
except:
- gh-pages
rust:
- stable
- nightly
os:
- linux
- windows
script:
- cargo build --all
- cargo test --all
matrix:
allow_failures:
- rust: nightly
fast_finish: true

View file

@ -1,9 +1,9 @@
[package] [package]
name = "chainerror" name = "chainerror"
version = "1.0.0" version = "0.4.2"
authors = ["Harald Hoyer <harald@redhat.com>"] authors = ["Harald Hoyer <harald@redhat.com>"]
edition = "2018" edition = "2018"
license = "MIT OR Apache-2.0" license = "MIT/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"
@ -16,11 +16,10 @@ exclude = [ ".gitignore", "examples/*", "booksrc/*", "book.toml",
"theme/*", "git-deploy-branch.sh", ".travis.yml" ] "theme/*", "git-deploy-branch.sh", ".travis.yml" ]
[badges] [badges]
# See https://doc.rust-lang.org/cargo/reference/manifest.html#the-badges-section travis-ci = { repository = "haraldh/chainerror" }
github = { repository = "haraldh/chainerror", workflow = "Rust" }
maintenance = { status = "actively-developed" }
is-it-maintained-issue-resolution = { repository = "haraldh/chainerror" }
is-it-maintained-open-issues = { repository = "haraldh/chainerror" }
[package.metadata.docs.rs] [features]
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] default = [ ]
no-fileline = []
display-cause = []
no-debug-cause = []

261
README.md
View file

@ -1,186 +1,129 @@
[![Crate](https://img.shields.io/crates/v/chainerror.svg)](https://crates.io/crates/chainerror)
[![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/chainerror/)
[![Coverage Status](https://codecov.io/gh/haraldh/chainerror/branch/master/graph/badge.svg?token=HGLJFGA11B)](https://codecov.io/gh/haraldh/chainerror)
![Maintenance](https://img.shields.io/badge/maintenance-activly--developed-brightgreen.svg)
# chainerror # chainerror
`chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your [![Build Status](https://travis-ci.org/haraldh/chainerror.svg?branch=master)](https://travis-ci.org/haraldh/chainerror)
[![Crate](https://img.shields.io/crates/v/chainerror.svg)](https://crates.io/crates/chainerror)
[![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/chainerror/)
`chainerror` provides an error backtrace like `failure` without doing a real backtrace, so even after you `strip` your
binaries, you still have the error backtrace. binaries, you still have the error backtrace.
Having nested function returning errors, the output doesn't tell where the error originates from.
```rust
use std::path::PathBuf;
type BoxedError = Box<dyn std::error::Error + Send + Sync>;
fn read_config_file(path: PathBuf) -> Result<(), BoxedError> {
// do stuff, return other errors
let _buf = std::fs::read_to_string(&path)?;
// do stuff, return other errors
Ok(())
}
fn process_config_file() -> Result<(), BoxedError> {
// do stuff, return other errors
let _buf = read_config_file("foo.txt".into())?;
// do stuff, return other errors
Ok(())
}
fn main() {
if let Err(e) = process_config_file() {
eprintln!("Error:\n{:?}", e);
}
}
```
This gives the output:
```console
Error:
Os { code: 2, kind: NotFound, message: "No such file or directory" }
```
and you have no idea where it comes from.
With `chainerror`, you can supply a context and get a nice error backtrace:
```rust
use chainerror::Context as _;
use std::path::PathBuf;
type BoxedError = Box<dyn std::error::Error + Send + Sync>;
fn read_config_file(path: PathBuf) -> Result<(), BoxedError> {
// do stuff, return other errors
let _buf = std::fs::read_to_string(&path).context(format!("Reading file: {:?}", &path))?;
// do stuff, return other errors
Ok(())
}
fn process_config_file() -> Result<(), BoxedError> {
// do stuff, return other errors
let _buf = read_config_file("foo.txt".into()).context("read the config file")?;
// do stuff, return other errors
Ok(())
}
fn main() {
if let Err(e) = process_config_file() {
eprintln!("Error:\n{:?}", e);
}
}
```
with the output:
```console
Error:
examples/simple.rs:14:51: read the config file
Caused by:
examples/simple.rs:7:47: Reading file: "foo.txt"
Caused by:
Os { code: 2, kind: NotFound, message: "No such file or directory" }
```
`chainerror` uses `.source()` of `std::error::Error` along with `#[track_caller]` and `Location` 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 `Error<T>` struct, `chainerror` comes with some useful helper macros to save a lot of typing.
`chainerror` has no dependencies! `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.
Debug information is worth it! Debug information is worth it!
## Multiple Output Formats Now continue reading the
[Tutorial](https://haraldh.github.io/chainerror/tutorial1.html)
`chainerror` supports multiple output formats, which can be selected with the different format specifiers: ## Example:
Output:
* `{}`: Display ~~~
```console $ cargo run -q --example example
func1 error calling func2 Main Error Report: func1 error calling func2
```
* `{:#}`: Alternative Display Error reported by Func2Error: func2 error: calling func3
```console
func1 error calling func2
Caused by:
func2 error: calling func3
Caused by:
(passed error)
Caused by:
Error reading 'foo.txt'
Caused by:
entity not found
```
* `{:?}`: Debug The root cause was: std::io::Error: Kind(
```console NotFound
examples/example.rs:50:13: func1 error calling func2 )
Debug Error:
examples/example.rs:45: func1 error calling func2
Caused by: Caused by:
examples/example.rs:25:13: Func2Error(func2 error: calling func3) examples/example.rs:20: Func2Error(func2 error: calling func3)
Caused by: Caused by:
examples/example.rs:18:13: (passed error) examples/example.rs:13: Error reading 'foo.txt'
Caused by:
examples/example.rs:13:18: Error reading 'foo.txt'
Caused by: Caused by:
Kind(NotFound) Kind(NotFound)
~~~
``` ~~~rust,ignore
use chainerror::*;
use std::error::Error;
use std::io;
use std::result::Result;
* `{:#?}`: Alternative Debug fn do_some_io() -> Result<(), Box<Error>> {
```console Err(io::Error::from(io::ErrorKind::NotFound))?;
Error<example::Func1Error> { Ok(())
occurrence: Some(
"examples/example.rs:50:13",
),
kind: func1 error calling func2,
source: Some(
Error<example::Func2Error> {
occurrence: Some(
"examples/example.rs:25:13",
),
kind: Func2Error(func2 error: calling func3),
source: Some(
Error<chainerror::AnnotatedError> {
occurrence: Some(
"examples/example.rs:18:13",
),
kind: (passed error),
source: Some(
Error<alloc::string::String> {
occurrence: Some(
"examples/example.rs:13:18",
),
kind: "Error reading 'foo.txt'",
source: Some(
Kind(
NotFound,
),
),
},
),
},
),
},
),
} }
```
## Tutorial fn func3() -> Result<(), Box<Error>> {
let filename = "foo.txt";
do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?;
Ok(())
}
Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) derive_str_cherr!(Func2Error);
## License fn func2() -> ChainResult<(), Func2Error> {
func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?;
Ok(())
}
Licensed under either of enum Func1Error {
Func2,
IO(String),
}
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or <https://www.apache.org/licenses/LICENSE-2.0>) impl ::std::fmt::Display for Func1Error {
* MIT license ([LICENSE-MIT](LICENSE-MIT) or <https://opensource.org/licenses/MIT>) 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),
}
}
}
at your option. impl ::std::fmt::Debug for Func1Error {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
write!(f, "{}", self)
}
}
### Contribution 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(())
}
Unless you explicitly state otherwise, any contribution intentionally fn main() {
submitted for inclusion in the work by you, as defined in the Apache-2.0 if let Err(e) = func1() {
license, shall be dual licensed as above, without any additional terms or match e.kind() {
conditions. 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

@ -1 +0,0 @@
../LICENSE-APACHE

View file

@ -1 +0,0 @@
../LICENSE-MIT

View file

@ -5,7 +5,7 @@
- [Simple String Errors](tutorial1.md) - [Simple String Errors](tutorial1.md)
- [Simple Chained String Errors](tutorial2.md) - [Simple Chained String Errors](tutorial2.md)
- [Mapping Errors](tutorial3.md) - [Mapping Errors](tutorial3.md)
- [More Information](tutorial4.md) - [Saving coding chars](tutorial4.md)
- [The source() of Errors](tutorial5.md) - [The source() of Errors](tutorial5.md)
- [Downcast the Errors](tutorial6.md) - [Downcast the Errors](tutorial6.md)
- [The root cause of all Errors](tutorial7.md) - [The root cause of all Errors](tutorial7.md)
@ -14,7 +14,5 @@
- [ErrorKind to the rescue](tutorial10.md) - [ErrorKind to the rescue](tutorial10.md)
- [Debug for the ErrorKind](tutorial11.md) - [Debug for the ErrorKind](tutorial11.md)
- [Deref for the ErrorKind](tutorial12.md) - [Deref for the ErrorKind](tutorial12.md)
- [Writing a library](tutorial13.md)
- [Going back to std](tutorial14.md)
[The End](end.md) [The End](end.md)

View file

@ -9,7 +9,7 @@ this only
prints out the last `Error`. prints out the last `Error`.
~~~ ~~~
Error: "func1 error" Error: StringError("func1 error")
~~~ ~~~
The next chapters of this tutorial show how `chainerror` adds more information The next chapters of this tutorial show how `chainerror` adds more information
@ -17,7 +17,7 @@ and improves inspecting the sources of an error.
You can also run the tutorial examples in the checked out You can also run the tutorial examples in the checked out
[chainerror git repo](https://github.com/haraldh/chainerror). [chainerror git repo](https://github.com/haraldh/chainerror).
~~~console ~~~
$ cargo run -q --example tutorial1 $ cargo run -q --example tutorial1
~~~ ~~~

View file

@ -1,21 +1,26 @@
# ErrorKind to the rescue # ErrorKind to the rescue
To cope with different kind of errors, we introduce the `kind` of an error `Func1ErrorKind` with an enum. 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 Because we derive `Debug` and implement `Display` our `Func1ErrorKind` enum, this enum can be used as
a `std::error::Error`. a `std::error::Error`.
Only returning `Func1ErrorKind` in `func1()` now let us get rid of `Result<(), Box<Error + Send + Sync>>` and we can 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>`. use `ChainResult<(), Func1ErrorKind>`.
In `main` we can now directly use the methods of `chainerror::Error<T>` without downcasting the error first. In `main` we can now directly use the methods of `ChainError<T>` without downcasting the error first.
Also, a nice `match` on `chainerror::Error<T>.kind()` is now possible, which returns `&T`, meaning `&Func1ErrorKind` here. Also a nice `match` on `ChainError<T>.kind()` is now possible, which returns `&T`, meaning
`&Func1ErrorKind` here.
~~~rust ~~~rust
{{#include ../examples/tutorial10.rs}} use crate::chainerror::*;
{{#include ../examples/tutorial10.rs:2:}}
# #[allow(dead_code)] # #[allow(dead_code)]
# mod chainerror { # mod chainerror {
{{#rustdoc_include ../src/lib.rs:-1}} {{#includecomment ../src/lib.rs}}
# } # }
~~~ ~~~

View file

@ -1,6 +1,6 @@
# Debug for the ErrorKind # Debug for the ErrorKind
One small improvement is to fix the debug output of 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. `Func1ErrorKind`. As you probably noticed, the output doesn't say much of the enum.
~~~ ~~~
@ -21,13 +21,14 @@ which gives us a lot more detail.
To create your own Errors, you might find [crates](https://crates.io) which create enum `Display+Debug` via derive macros. To create your own Errors, you might find [crates](https://crates.io) which create enum `Display+Debug` via derive macros.
Also, noteworthy is [custom_error](https://crates.io/crates/custom_error) to define your custom errors, Also noteworthy is [custom_error](https://crates.io/crates/custom_error) to define your custom errors,
which can then be used with `chainerror`. which can then be used with `chainerror`.
~~~rust ~~~rust
{{#include ../examples/tutorial11.rs}} use crate::chainerror::*;
{{#include ../examples/tutorial11.rs:2:}}
# #[allow(dead_code)] # #[allow(dead_code)]
# mod chainerror { # mod chainerror {
{{#rustdoc_include ../src/lib.rs:-1}} {{#includecomment ../src/lib.rs}}
# } # }
~~~ ~~~

View file

@ -1,11 +1,12 @@
# Deref for the ErrorKind # Deref for the ErrorKind
Because chainerror::Error<T> implements Deref to &T, we can also match on `*e` instead of `e.kind()` Because ChainError<T> implements Deref to &T, we can also match on `*e` instead of `e.kind()`
or call a function with `&e` or call a function with `&e`
~~~rust ~~~rust
{{#include ../examples/tutorial12.rs}} use crate::chainerror::*;
{{#include ../examples/tutorial12.rs:2:}}
# #[allow(dead_code)] # #[allow(dead_code)]
# mod chainerror { # mod chainerror {
{{#rustdoc_include ../src/lib.rs:-1}} {{#includecomment ../src/lib.rs}}
# } # }
~~~ ~~~

View file

@ -1,18 +0,0 @@
# Writing a library
I would advise to only expose an `mycrate::ErrorKind` and type alias `mycrate::Error` to `chainerror::Error<mycrate::ErrorKind>`
so you can tell your library users to use the `.kind()` method as `std::io::Error` does.
If you later decide to make your own `Error` implementation, your library users don't
have to change much or anything.
~~~rust
# #[allow(dead_code)]
# #[macro_use]
# pub mod chainerror {
{{#rustdoc_include ../src/lib.rs:-1}}
# }
pub mod mycrate {
use crate::chainerror::*; // omit the `crate::` part
{{#include ../examples/tutorial13.rs:3:}}
~~~

View file

@ -1,9 +0,0 @@
# Going back to std
Not using `chainerror` and going full `std` would look like this:
Btw, the code size is bigger than using `chainerror` :-)
~~~rust
{{#include ../examples/tutorial14.rs}}
~~~

View file

@ -1,15 +1,16 @@
# Simple Chained String Errors # Simple Chained String Errors
With relatively small changes and the help of the `context()` method of the `chainerror` crate With relatively small changes and the help of the `cherr!` macro of the `chainerror` crate
the `&str` errors are now chained together. 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.
~~~rust ~~~rust
{{#include ../examples/tutorial2.rs}} use crate::chainerror::*;
{{#include ../examples/tutorial2.rs:2:}}
# #[allow(dead_code)] # #[allow(dead_code)]
# mod chainerror { # mod chainerror {
{{#rustdoc_include ../src/lib.rs:-1}} {{#includecomment ../src/lib.rs}}
# } # }
~~~ ~~~
@ -19,13 +20,14 @@ Press the play button in the upper right corner and see the nice debug output.
{{#include ../examples/tutorial2.rs:13:15}} {{#include ../examples/tutorial2.rs:13:15}}
~~~ ~~~
The function `context(newerror)` stores `olderror` as the source/cause of `newerror` The macro `cherr!(olderror, newerror)` stores `olderror` as the source/cause of `newerror`
along with the `Location` of the `context()` call and returns `Err(newerror)`. along with the filename (`file!()`) and line number (`line!()`)
and returns `newerror`.
`?` then returns the inner error applying `.into()`, so that we `Err()?` then returns the inner error applying `.into()`, so that we
again have a `Err(Box<Error + Send + Sync>)` as a result. again have a `Err(Box<Error>)` as a result.
The `Debug` implementation of `chainerror::Error<T>` (which is returned by `context()`) 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::Error<T>` in our case is `chainerror::Error<&str>`. `ChainError<T>` in our case is `ChainError<String>`.

View file

@ -1,12 +1,13 @@
# Mapping Errors # Mapping Errors
Now let's get more rust idiomatic by using `.context()` directly on the previous `Result`. Now let's get more rust idiomatic by using `.map_err()`.
~~~rust ~~~rust
{{#include ../examples/tutorial3.rs}} use crate::chainerror::*;
{{#include ../examples/tutorial3.rs:2:}}
# #[allow(dead_code)] # #[allow(dead_code)]
# mod chainerror { # mod chainerror {
{{#rustdoc_include ../src/lib.rs:-1}} {{#includecomment ../src/lib.rs}}
# } # }
~~~ ~~~
@ -14,13 +15,13 @@ If you compare the output to the previous example, you will see,
that: that:
~~~ ~~~
Error: examples/tutorial2.rs:20:16: func1 error Error: src/main.rs:19: "func1 error"
~~~ ~~~
changed to just: changed to just:
~~~ ~~~
examples/tutorial3.rs:17:13: func1 error src/main.rs:16: "func1 error"
~~~ ~~~
This is, because we caught the error of `func1()` in `main()` and print it out ourselves. This is, because we caught the error of `func1()` in `main()` and print it out ourselves.

View file

@ -1,12 +1,18 @@
# More information # Saving coding chars
To give more context to the error, you want to use `format!` Because decorating an error with more information should not
to extend the information in the context string. let you jump through hoops, `chainerror` has a quick macro for that.
`mstrerror!()` fits right into `.map_err()` letting you quickly add
more debug strings.
`mstrerror!()` even understands `format!()` syntax like `println!()`.
~~~rust ~~~rust
{{#include ../examples/tutorial4.rs}} use crate::chainerror::*;
{{#include ../examples/tutorial4.rs:2:}}
# #[allow(dead_code)] # #[allow(dead_code)]
# mod chainerror { # mod chainerror {
{{#rustdoc_include ../src/lib.rs:-1}} {{#includecomment ../src/lib.rs}}
# } # }
~~~ ~~~

View file

@ -4,14 +4,16 @@ Sometimes you want to inspect the `source()` of an `Error`.
`chainerror` implements `std::error::Error::source()`, so you can get the cause of an error. `chainerror` implements `std::error::Error::source()`, so you can get the cause of an error.
~~~rust ~~~rust
{{#include ../examples/tutorial5.rs}} use crate::chainerror::*;
{{#include ../examples/tutorial5.rs:2:}}
# #[allow(dead_code)] # #[allow(dead_code)]
# mod chainerror { # mod chainerror {
{{#rustdoc_include ../src/lib.rs:-1}} {{#includecomment ../src/lib.rs}}
# } # }
~~~ ~~~
Note, that because we changed the output of the error in `main()` from 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. `Debug` to `Display`, we don't see the error backtrace with filename and line number.
To use the `Display` backtrace, you have to use the alternative display format output `{:#}`. To enable the `Display` backtrace, you have to enable the feature `display-cause` for `chainerror`.

View file

@ -11,9 +11,10 @@ pub fn downcast_mut<T: Error + 'static>(&mut self) -> Option<&mut T>
This is how it looks like, when using those: This is how it looks like, when using those:
~~~rust ~~~rust
{{#include ../examples/tutorial6.rs}} use crate::chainerror::*;
{{#include ../examples/tutorial6.rs:2:}}
# #[allow(dead_code)] # #[allow(dead_code)]
# mod chainerror { # mod chainerror {
{{#rustdoc_include ../src/lib.rs:-1}} {{#includecomment ../src/lib.rs}}
# } # }
~~~ ~~~

View file

@ -4,15 +4,15 @@
~~~rust,ignore ~~~rust,ignore
fn is_chain<T: 'static + Display + Debug>(&self) -> bool fn is_chain<T: 'static + Display + Debug>(&self) -> bool
fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&chainerror::Error<T>> 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::Error<T>> fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>>
fn root_cause(&self) -> Option<&(dyn Error + 'static)> fn root_cause(&self) -> Option<&(dyn Error + 'static)>
fn find_cause<U: Error + 'static>(&self) -> Option<&U> fn find_cause<U: Error + 'static>(&self) -> Option<&U>
fn find_chain_cause<U: Error + 'static>(&self) -> Option<&chainerror::Error<U>> fn find_chain_cause<U: Error + 'static>(&self) -> Option<&ChainError<U>>
fn kind<'a>(&'a self) -> &'a T fn kind<'a>(&'a self) -> &'a T
~~~ ~~~
Using `downcast_chain_ref::<String>()` gives a `chainerror::Error<String>`, which can be used Using `downcast_chain_ref::<String>()` gives a `ChainError<String>`, which can be used
to call `.find_cause::<io::Error>()`. to call `.find_cause::<io::Error>()`.
~~~rust,ignore ~~~rust,ignore
@ -27,9 +27,10 @@ or to use `.root_cause()`, which of course can be of any type implementing `std:
~~~ ~~~
~~~rust ~~~rust
{{#include ../examples/tutorial7.rs}} use crate::chainerror::*;
{{#include ../examples/tutorial7.rs:2:}}
# #[allow(dead_code)] # #[allow(dead_code)]
# mod chainerror { # mod chainerror {
{{#rustdoc_include ../src/lib.rs:-1}} {{#includecomment ../src/lib.rs}}
# } # }
~~~ ~~~

View file

@ -1,14 +1,14 @@
# Finding an Error cause # Finding an Error cause
To distinguish the errors occurring in various places, we can define named string errors with the To distinguish the errors occuring in various places, we can define named string errors with the
"new type" pattern. "new type" pattern.
~~~rust,ignore ~~~rust,ignore
chainerror::str_context!(Func2Error); derive_str_cherr!(Func2Error);
chainerror::str_context!(Func1Error); derive_str_cherr!(Func1Error);
~~~ ~~~
Instead of `chainerror::Error<String>` we now have `struct Func1Error(String)` and `chainerror::Error<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. In the `main` function you can see, how we can match the different errors.
@ -18,14 +18,15 @@ Also see:
~~~ ~~~
as a shortcut to as a shortcut to
~~~rust,ignore ~~~rust,ignore
if let Some(f2err) = f1err.find_cause::<chainerror::Error<Func2Error>>() { if let Some(f2err) = f1err.find_cause::<ChainError<Func2Error>>() {
~~~ ~~~
hiding the `chainerror::Error<T>` implementation detail. hiding the `ChainError<T>` implementation detail.
~~~rust ~~~rust
{{#include ../examples/tutorial8.rs}} use crate::chainerror::*;
{{#include ../examples/tutorial8.rs:2:}}
# #[allow(dead_code)] # #[allow(dead_code)]
# mod chainerror { # mod chainerror {
{{#rustdoc_include ../src/lib.rs:-1}} {{#includecomment ../src/lib.rs}}
# } # }
~~~ ~~~

View file

@ -7,7 +7,7 @@ In this example `func1()` can return either `Func1ErrorFunc2` or `Func1ErrorIO`.
We might want to `match` on `func1()` with something like: We might want to `match` on `func1()` with something like:
~~~rust,ignore ~~~rust,ignore
fn main() -> Result<(), Box<Error + Send + Sync>> { fn main() -> Result<(), Box<Error>> {
match func1() { match func1() {
Err(e) if let Some(s) = e.downcast_chain_ref::<Func1ErrorIO>() => Err(e) if let Some(s) = e.downcast_chain_ref::<Func1ErrorIO>() =>
eprintln!("Func1ErrorIO:\n{:?}", s), eprintln!("Func1ErrorIO:\n{:?}", s),
@ -25,9 +25,10 @@ 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. In the next chapter, we will see, how to solve this more elegantly.
~~~rust ~~~rust
{{#include ../examples/tutorial9.rs}} use crate::chainerror::*;
{{#include ../examples/tutorial9.rs:2:}}
# #[allow(dead_code)] # #[allow(dead_code)]
# mod chainerror { # mod chainerror {
{{#rustdoc_include ../src/lib.rs:-1}} {{#includecomment ../src/lib.rs}}
# } # }
~~~ ~~~

View file

@ -1,28 +1,23 @@
use chainerror::Context as _; use chainerror::*;
use std::error::Error; use std::error::Error;
use std::fmt;
use std::io; use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { fn do_some_io() -> Result<(), Box<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }
fn func4() -> Result<(), Box<dyn Error + Send + Sync>> { fn func3() -> Result<(), Box<Error>> {
let filename = "foo.txt"; let filename = "foo.txt";
do_some_io().context(format!("Error reading '{}'", filename))?; do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?;
Ok(()) Ok(())
} }
fn func3() -> Result<(), Box<dyn Error + Send + Sync>> { derive_str_cherr!(Func2Error);
func4().annotate()?;
Ok(())
}
chainerror::str_context!(Func2Error); fn func2() -> ChainResult<(), Func2Error> {
func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?;
fn func2() -> chainerror::Result<(), Func2Error> {
func3().context(Func2Error::new("func2 error: calling func3"))?;
Ok(()) Ok(())
} }
@ -31,8 +26,8 @@ enum Func1Error {
IO(String), IO(String),
} }
impl fmt::Display for Func1Error { impl ::std::fmt::Display for Func1Error {
fn fmt(&self, f: &mut fmt::Formatter) -> 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"), Func1Error::Func2 => write!(f, "func1 error calling func2"),
Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename),
@ -40,29 +35,21 @@ impl fmt::Display for Func1Error {
} }
} }
impl fmt::Debug for Func1Error { impl ::std::fmt::Debug for Func1Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
write!(f, "{}", self) write!(f, "{}", self)
} }
} }
fn func1() -> chainerror::Result<(), Func1Error> { fn func1() -> ChainResult<(), Func1Error> {
func2().context(Func1Error::Func2)?; func2().map_err(|e| cherr!(e, Func1Error::Func2))?;
let filename = String::from("bar.txt"); let filename = String::from("bar.txt");
do_some_io().context(Func1Error::IO(filename))?; do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?;
Ok(()) Ok(())
} }
fn main() { fn main() {
if let Err(e) = func1() { if let Err(e) = func1() {
eprintln!("\nDisplay Error {{}}:\n{}", e);
eprintln!("\nAlternative Display Error {{:#}}:\n{:#}", e);
eprintln!("\nDebug Error {{:?}}:\n{:?}", e);
eprintln!("\nAlternative Debug Error {{:#?}}:\n{:#?}\n", e);
match e.kind() { match e.kind() {
Func1Error::Func2 => eprintln!("Main Error Report: func1 error calling func2"), Func1Error::Func2 => eprintln!("Main Error Report: func1 error calling func2"),
Func1Error::IO(filename) => { Func1Error::IO(filename) => {
@ -78,5 +65,7 @@ fn main() {
let ioerror = e.downcast_ref::<io::Error>().unwrap(); let ioerror = e.downcast_ref::<io::Error>().unwrap();
eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror);
} }
eprintln!("\nDebug Error:\n{:?}", e);
} }
} }

View file

@ -1,28 +1,26 @@
#![allow(clippy::single_match)]
#![allow(clippy::redundant_pattern_matching)]
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { fn do_some_io() -> Result<(), Box<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { fn func2() -> Result<(), Box<Error>> {
if let Err(_) = do_some_io() { if let Err(_) = do_some_io() {
Err("func2 error")?; Err("func2 error")?;
} }
Ok(()) Ok(())
} }
fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { fn func1() -> Result<(), Box<Error>> {
if let Err(_) = func2() { if let Err(_) = func2() {
Err("func1 error")?; Err("func1 error")?;
} }
Ok(()) Ok(())
} }
fn main() -> Result<(), Box<dyn Error + Send + Sync>> { fn main() -> Result<(), Box<Error>> {
func1() func1()
} }

View file

@ -1,18 +1,18 @@
use chainerror::Context as _; use chainerror::*;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { fn do_some_io() -> Result<(), Box<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }
chainerror::str_context!(Func2Error); derive_str_cherr!(Func2Error);
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { fn func2() -> Result<(), Box<Error>> {
let filename = "foo.txt"; let filename = "foo.txt";
do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?;
Ok(()) Ok(())
} }
@ -32,14 +32,14 @@ impl ::std::fmt::Display for Func1ErrorKind {
} }
impl ::std::error::Error for Func1ErrorKind {} impl ::std::error::Error for Func1ErrorKind {}
fn func1() -> chainerror::Result<(), Func1ErrorKind> { fn func1() -> ChainResult<(), Func1ErrorKind> {
func2().context(Func1ErrorKind::Func2)?; func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?;
let filename = String::from("bar.txt"); let filename = String::from("bar.txt");
do_some_io().context(Func1ErrorKind::IO(filename))?; do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO(filename)))?;
Ok(()) Ok(())
} }
fn main() -> Result<(), Box<dyn Error + Send + Sync>> { fn main() -> Result<(), Box<Error>> {
if let Err(e) = func1() { if let Err(e) = func1() {
match e.kind() { match e.kind() {
Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"),
@ -53,8 +53,6 @@ fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
} }
eprintln!("\nDebug Error:\n{:?}", e); eprintln!("\nDebug Error:\n{:?}", e);
std::process::exit(1);
} }
Ok(()) Ok(())
} }

View file

@ -1,18 +1,18 @@
use chainerror::Context as _; use chainerror::*;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { fn do_some_io() -> Result<(), Box<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }
chainerror::str_context!(Func2Error); derive_str_cherr!(Func2Error);
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { fn func2() -> Result<(), Box<Error>> {
let filename = "foo.txt"; let filename = "foo.txt";
do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?;
Ok(()) Ok(())
} }
@ -38,14 +38,14 @@ impl ::std::fmt::Debug for Func1ErrorKind {
impl ::std::error::Error for Func1ErrorKind {} impl ::std::error::Error for Func1ErrorKind {}
fn func1() -> chainerror::Result<(), Func1ErrorKind> { fn func1() -> ChainResult<(), Func1ErrorKind> {
func2().context(Func1ErrorKind::Func2)?; func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?;
let filename = String::from("bar.txt"); let filename = String::from("bar.txt");
do_some_io().context(Func1ErrorKind::IO(filename))?; do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO(filename)))?;
Ok(()) Ok(())
} }
fn main() -> Result<(), Box<dyn Error + Send + Sync>> { fn main() -> Result<(), Box<Error>> {
if let Err(e) = func1() { if let Err(e) = func1() {
match e.kind() { match e.kind() {
Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"),
@ -59,8 +59,6 @@ fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
} }
eprintln!("\nDebug Error:\n{:?}", e); eprintln!("\nDebug Error:\n{:?}", e);
std::process::exit(1);
} }
Ok(()) Ok(())
} }

View file

@ -1,18 +1,18 @@
use chainerror::Context as _; use chainerror::*;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { fn do_some_io() -> Result<(), Box<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }
chainerror::str_context!(Func2Error); derive_str_cherr!(Func2Error);
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { fn func2() -> Result<(), Box<Error>> {
let filename = "foo.txt"; let filename = "foo.txt";
do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?;
Ok(()) Ok(())
} }
@ -38,10 +38,10 @@ impl ::std::fmt::Debug for Func1ErrorKind {
impl ::std::error::Error for Func1ErrorKind {} impl ::std::error::Error for Func1ErrorKind {}
fn func1() -> chainerror::Result<(), Func1ErrorKind> { fn func1() -> ChainResult<(), Func1ErrorKind> {
func2().context(Func1ErrorKind::Func2)?; func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?;
let filename = String::from("bar.txt"); let filename = String::from("bar.txt");
do_some_io().context(Func1ErrorKind::IO(filename))?; do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO(filename)))?;
Ok(()) Ok(())
} }
@ -54,7 +54,7 @@ fn handle_func1errorkind(e: &Func1ErrorKind) {
} }
} }
fn main() -> Result<(), Box<dyn Error + Send + Sync>> { fn main() -> Result<(), Box<Error>> {
if let Err(e) = func1() { if let Err(e) = func1() {
match *e { match *e {
Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"),
@ -70,8 +70,6 @@ fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
} }
eprintln!("\nDebug Error:\n{:?}", e); eprintln!("\nDebug Error:\n{:?}", e);
std::process::exit(1);
} }
Ok(()) Ok(())
} }

View file

@ -1,83 +0,0 @@
#![allow(clippy::single_match)]
#![allow(clippy::redundant_pattern_matching)]
pub mod mycrate {
use chainerror::Context as _;
use std::io;
fn do_some_io() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(())
}
chainerror::str_context!(Func2Error);
fn func2() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
let filename = "foo.txt";
do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?;
Ok(())
}
#[derive(Debug, Clone)]
pub enum ErrorKind {
Func2,
IO(String),
}
chainerror::err_kind!(Error, ErrorKind);
pub type Result<T> = std::result::Result<T, Error>;
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result {
match self {
ErrorKind::Func2 => write!(f, "func1 error calling func2"),
ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename),
}
}
}
pub fn func1() -> Result<()> {
func2().context(ErrorKind::Func2)?;
let filename = String::from("bar.txt");
do_some_io().context(ErrorKind::IO(filename))?;
Ok(())
}
}
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
use mycrate::func1;
use mycrate::ErrorKind;
use std::error::Error;
use std::io;
if let Err(e) = func1() {
match e.kind() {
ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"),
ErrorKind::IO(ref filename) => {
eprintln!("Main Error Report: func1 error reading '{}'", filename)
}
}
eprintln!();
let mut s: &dyn Error = &e;
while let Some(c) = s.source() {
if let Some(ioerror) = c.downcast_ref::<io::Error>() {
eprintln!("caused by: std::io::Error: {}", ioerror);
match ioerror.kind() {
io::ErrorKind::NotFound => eprintln!("of kind: std::io::ErrorKind::NotFound"),
_ => {}
}
} else {
eprintln!("caused by: {}", c);
}
s = c;
}
eprintln!("\nDebug Error:\n{:?}", e);
std::process::exit(1);
}
Ok(())
}

View file

@ -1,222 +0,0 @@
#![allow(clippy::single_match)]
#![allow(clippy::redundant_pattern_matching)]
pub mod mycrate {
use std::error::Error as StdError;
use self::func2mod::{do_some_io, func2};
pub mod func2mod {
use std::error::Error as StdError;
use std::io;
pub enum ErrorKind {
IO(String),
}
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result {
match self {
ErrorKind::IO(s) => std::fmt::Display::fmt(s, f),
}
}
}
impl std::fmt::Debug for ErrorKind {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result {
match self {
ErrorKind::IO(s) => std::fmt::Display::fmt(s, f),
}
}
}
macro_rules! mcontext {
( $k:expr ) => {{
|e| {
Error(
$k,
Some(Box::from(e)),
Some(concat!(file!(), ":", line!(), ": ")),
)
}
}};
}
pub struct Error(
ErrorKind,
Option<Box<dyn std::error::Error + 'static>>,
Option<&'static str>,
);
impl Error {
pub fn kind(&self) -> &ErrorKind {
&self.0
}
}
impl From<ErrorKind> for Error {
fn from(e: ErrorKind) -> Self {
Error(e, None, None)
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.1.as_ref().map(|e| e.as_ref())
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if let Some(ref o) = self.2 {
std::fmt::Display::fmt(o, f)?;
}
std::fmt::Debug::fmt(&self.0, f)?;
if let Some(e) = self.source() {
std::fmt::Display::fmt("\nCaused by:\n", f)?;
std::fmt::Debug::fmt(&e, f)?;
}
Ok(())
}
}
pub fn do_some_io() -> std::result::Result<(), Box<dyn std::error::Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(())
}
pub fn func2() -> std::result::Result<(), Error> {
let filename = "foo.txt";
do_some_io().map_err(mcontext!(ErrorKind::IO(format!(
"Error reading '{}'",
filename
))))?;
Ok(())
}
}
#[derive(Debug)]
pub enum ErrorKind {
Func2,
IO(String),
}
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result {
match self {
ErrorKind::Func2 => write!(f, "func1 error calling func2"),
ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename),
}
}
}
macro_rules! mcontext {
( $k:expr ) => {{
|e| {
Error(
$k,
Some(Box::from(e)),
Some(concat!(file!(), ":", line!(), ": ")),
)
}
}};
}
pub struct Error(
ErrorKind,
Option<Box<dyn std::error::Error + 'static>>,
Option<&'static str>,
);
impl Error {
pub fn kind(&self) -> &ErrorKind {
&self.0
}
}
impl From<ErrorKind> for Error {
fn from(e: ErrorKind) -> Self {
Error(e, None, None)
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.1.as_ref().map(|e| e.as_ref())
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if let Some(ref o) = self.2 {
std::fmt::Display::fmt(o, f)?;
}
std::fmt::Debug::fmt(&self.0, f)?;
if let Some(e) = self.source() {
std::fmt::Display::fmt("\nCaused by:\n", f)?;
std::fmt::Debug::fmt(&e, f)?;
}
Ok(())
}
}
pub type Result<T> = std::result::Result<T, Error>;
pub fn func1() -> Result<()> {
func2().map_err(mcontext!(ErrorKind::Func2))?;
let filename = String::from("bar.txt");
do_some_io().map_err(mcontext!(ErrorKind::IO(filename)))?;
Ok(())
}
}
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
use mycrate::func1;
use mycrate::ErrorKind;
use std::error::Error;
use std::io;
if let Err(e) = func1() {
match e.kind() {
ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"),
ErrorKind::IO(ref filename) => {
eprintln!("Main Error Report: func1 error reading '{}'", filename)
}
}
eprintln!();
let mut s: &dyn Error = &e;
while let Some(c) = s.source() {
if let Some(ioerror) = c.downcast_ref::<io::Error>() {
eprintln!("caused by: std::io::Error: {}", ioerror);
match ioerror.kind() {
io::ErrorKind::NotFound => eprintln!("of kind: std::io::ErrorKind::NotFound"),
_ => {}
}
} else {
eprintln!("caused by: {}", c);
}
s = c;
}
eprintln!("\nDebug Error:\n{:?}", e);
std::process::exit(1);
}
Ok(())
}

View file

@ -1,140 +0,0 @@
#![allow(clippy::single_match)]
#![allow(clippy::redundant_pattern_matching)]
pub mod mycrate {
use chainerror::{Context as _, ErrorDown as _};
use std::io;
fn do_some_io(_f: &str) -> std::result::Result<(), io::Error> {
Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(())
}
chainerror::str_context!(Func2Error);
fn func2() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
let filename = "foo.txt";
do_some_io(filename).context(Func2Error(format!("Error reading '{}'", filename)))?;
Ok(())
}
#[derive(Debug, Clone)]
pub enum ErrorKind {
Func2,
IO(String),
FatalError(String),
Unknown,
}
chainerror::err_kind!(Error, ErrorKind);
pub type Result<T> = std::result::Result<T, Error>;
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result {
match self {
ErrorKind::FatalError(e) => write!(f, "fatal error {}", e),
ErrorKind::Unknown => write!(f, "unknown error"),
ErrorKind::Func2 => write!(f, "func1 error calling func2"),
ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename),
}
}
}
impl ErrorKind {
fn from_io_error(e: &io::Error, f: String) -> Self {
match e.kind() {
io::ErrorKind::BrokenPipe => panic!("Should not happen"),
io::ErrorKind::ConnectionReset => {
ErrorKind::FatalError(format!("While reading `{}`: {}", f, e))
}
_ => ErrorKind::IO(f),
}
}
}
impl From<&(dyn std::error::Error + 'static + Send + Sync)> for ErrorKind {
fn from(e: &(dyn std::error::Error + 'static + Send + Sync)) -> Self {
if let Some(_) = e.downcast_ref::<io::Error>() {
ErrorKind::IO(String::from("Unknown filename"))
} else if let Some(_) = e.downcast_inner_ref::<Func2Error>() {
ErrorKind::Func2
} else {
ErrorKind::Unknown
}
}
}
impl From<&std::boxed::Box<dyn std::error::Error + 'static + Send + Sync>> for ErrorKind {
fn from(e: &std::boxed::Box<dyn std::error::Error + 'static + Send + Sync>) -> Self {
Self::from(&**e)
}
}
impl From<&Func2Error> for ErrorKind {
fn from(_: &Func2Error) -> Self {
ErrorKind::Func2
}
}
impl From<&io::Error> for ErrorKind {
fn from(e: &io::Error) -> Self {
ErrorKind::IO(format!("{}", e))
}
}
pub fn func1() -> Result<()> {
func2().map_err(|e| ErrorKind::from(&e))?;
let filename = "bar.txt";
do_some_io(filename).map_context(|e| ErrorKind::from_io_error(e, filename.into()))?;
do_some_io(filename).map_context(|_| ErrorKind::IO(filename.into()))?;
do_some_io(filename).map_context(|e| ErrorKind::from(e))?;
Ok(())
}
pub fn super_func1() -> Result<()> {
func1().map_context(|e| ErrorKind::from(e))?;
Ok(())
}
}
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
use mycrate::super_func1;
use mycrate::ErrorKind;
use std::error::Error;
use std::io;
if let Err(e) = super_func1() {
match e.kind() {
ErrorKind::FatalError(f) => eprintln!("Main Error Report: Fatal Error: {}", f),
ErrorKind::Unknown => eprintln!("Main Error Report: Unknown error occurred"),
ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"),
ErrorKind::IO(ref filename) => {
eprintln!("Main Error Report: func1 error reading '{}'", filename)
}
}
eprintln!();
let mut s: &dyn Error = &e;
while let Some(c) = s.source() {
if let Some(ioerror) = c.downcast_ref::<io::Error>() {
eprintln!("caused by: std::io::Error: {}", ioerror);
match ioerror.kind() {
io::ErrorKind::NotFound => eprintln!("of kind: std::io::ErrorKind::NotFound"),
_ => {}
}
} else {
eprintln!("caused by: {}", c);
}
s = c;
}
eprintln!("\nDebug Error:\n{:?}", e);
std::process::exit(1);
}
Ok(())
}

View file

@ -1,27 +1,28 @@
use chainerror::Context as _; use chainerror::*;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { fn do_some_io() -> Result<(), Box<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { fn func2() -> Result<(), Box<Error>> {
if let Err(e) = do_some_io() { if let Err(e) = do_some_io() {
Err(e).context("func2 error")?; Err(cherr!(e, "func2 error"))?;
} }
Ok(()) Ok(())
} }
fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { fn func1() -> Result<(), Box<Error>> {
if let Err(e) = func2() { if let Err(e) = func2() {
Err(e).context("func1 error")?; Err(cherr!(e, "func1 error"))?;
} }
Ok(()) Ok(())
} }
fn main() -> Result<(), Box<dyn Error + Send + Sync>> { fn main() -> Result<(), Box<Error>> {
func1() func1()
} }

View file

@ -1,27 +1,27 @@
use chainerror::Context as _; use chainerror::*;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { fn do_some_io() -> Result<(), Box<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { fn func2() -> Result<(), Box<Error>> {
do_some_io().context("func2 error")?; do_some_io().map_err(|e| cherr!(e, "func2 error"))?;
Ok(()) Ok(())
} }
fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { fn func1() -> Result<(), Box<Error>> {
func2().context("func1 error")?; func2().map_err(|e| cherr!(e, "func1 error"))?;
Ok(()) Ok(())
} }
fn main() -> Result<(), Box<dyn Error + Send + Sync>> { fn main() -> Result<(), Box<Error>> {
if let Err(e) = func1() { if let Err(e) = func1() {
eprintln!("{:?}", e); eprintln!("{:?}", e);
std::process::exit(1);
} }
Ok(()) Ok(())
} }

View file

@ -1,28 +1,27 @@
use chainerror::Context as _; use chainerror::*;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { fn do_some_io() -> Result<(), Box<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { fn func2() -> Result<(), Box<Error>> {
let filename = "foo.txt"; let filename = "foo.txt";
do_some_io().context(format!("Error reading '{}'", filename))?; do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?;
Ok(()) Ok(())
} }
fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { fn func1() -> Result<(), Box<Error>> {
func2().context("func1 error")?; func2().map_err(mstrerr!("func1 error"))?;
Ok(()) Ok(())
} }
fn main() -> Result<(), Box<dyn Error + Send + Sync>> { fn main() -> Result<(), Box<Error>> {
if let Err(e) = func1() { if let Err(e) = func1() {
eprintln!("{:?}", e); eprintln!("{:?}", e);
std::process::exit(1);
} }
Ok(()) Ok(())
} }

View file

@ -1,33 +1,32 @@
use chainerror::Context as _; use chainerror::*;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { fn do_some_io() -> Result<(), Box<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { fn func2() -> Result<(), Box<Error>> {
let filename = "foo.txt"; let filename = "foo.txt";
do_some_io().context(format!("Error reading '{}'", filename))?; do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?;
Ok(()) Ok(())
} }
fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { fn func1() -> Result<(), Box<Error>> {
if let Err(e) = func2() { if let Err(e) = func2() {
if let Some(s) = e.source() { if let Some(s) = e.source() {
eprintln!("func2 failed because of '{}'", s); eprintln!("func2 failed because of '{}'", s);
Err(e).context("func1 error")?; Err(e).map_err(mstrerr!("func1 error"))?;
} }
} }
Ok(()) Ok(())
} }
fn main() -> Result<(), Box<dyn Error + Send + Sync>> { fn main() -> Result<(), Box<Error>> {
if let Err(e) = func1() { if let Err(e) = func1() {
eprintln!("{}", e); eprintln!("{}", e);
std::process::exit(1);
} }
Ok(()) Ok(())
} }

View file

@ -1,31 +1,28 @@
#![allow(clippy::single_match)] use chainerror::*;
#![allow(clippy::redundant_pattern_matching)]
use chainerror::Context as _;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { fn do_some_io() -> Result<(), Box<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { fn func2() -> Result<(), Box<Error>> {
let filename = "foo.txt"; let filename = "foo.txt";
do_some_io().context(format!("Error reading '{}'", filename))?; do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?;
Ok(()) Ok(())
} }
fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { fn func1() -> Result<(), Box<Error>> {
func2().context("func1 error")?; func2().map_err(mstrerr!("func1 error"))?;
Ok(()) Ok(())
} }
fn main() -> Result<(), Box<dyn Error + Send + Sync>> { fn main() -> Result<(), Box<Error>> {
if let Err(e) = func1() { if let Err(e) = func1() {
eprintln!("Error: {}", e); eprintln!("Error: {}", e);
let mut s: &(dyn Error) = e.as_ref(); let mut s = e.as_ref();
while let Some(c) = s.source() { while let Some(c) = s.source() {
if let Some(ioerror) = c.downcast_ref::<io::Error>() { if let Some(ioerror) = c.downcast_ref::<io::Error>() {
eprintln!("caused by: std::io::Error: {}", ioerror); eprintln!("caused by: std::io::Error: {}", ioerror);
@ -38,7 +35,6 @@ fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
} }
s = c; s = c;
} }
std::process::exit(1);
} }
Ok(()) Ok(())
} }

View file

@ -1,28 +1,25 @@
#![allow(clippy::single_match)] use chainerror::*;
#![allow(clippy::redundant_pattern_matching)]
use chainerror::{Context as _, ErrorDown as _};
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { fn do_some_io() -> Result<(), Box<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { fn func2() -> Result<(), Box<Error>> {
let filename = "foo.txt"; let filename = "foo.txt";
do_some_io().context(format!("Error reading '{}'", filename))?; do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?;
Ok(()) Ok(())
} }
fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { fn func1() -> Result<(), Box<Error>> {
func2().context("func1 error")?; func2().map_err(mstrerr!("func1 error"))?;
Ok(()) Ok(())
} }
fn main() -> Result<(), Box<dyn Error + Send + Sync>> { fn main() -> Result<(), Box<Error>> {
if let Err(e) = func1() { if let Err(e) = func1() {
eprintln!("Error: {}", e); eprintln!("Error: {}", e);
if let Some(s) = e.downcast_chain_ref::<String>() { if let Some(s) = e.downcast_chain_ref::<String>() {
@ -39,7 +36,6 @@ fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
eprintln!("The root cause was: std::io::Error: {:#?}", ioerror); eprintln!("The root cause was: std::io::Error: {:#?}", ioerror);
} }
} }
std::process::exit(1);
} }
Ok(()) Ok(())
} }

View file

@ -1,34 +1,34 @@
use chainerror::{Context as _, ErrorDown as _}; use chainerror::*;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { fn do_some_io() -> Result<(), Box<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }
chainerror::str_context!(Func2Error); derive_str_cherr!(Func2Error);
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { fn func2() -> Result<(), Box<Error>> {
let filename = "foo.txt"; let filename = "foo.txt";
do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?;
Ok(()) Ok(())
} }
chainerror::str_context!(Func1Error); derive_str_cherr!(Func1Error);
fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { fn func1() -> Result<(), Box<Error>> {
func2().context(Func1Error::new("func1 error"))?; func2().map_err(mstrerr!(Func1Error, "func1 error"))?;
Ok(()) Ok(())
} }
fn main() -> Result<(), Box<dyn Error + Send + Sync>> { fn main() -> Result<(), Box<Error>> {
if let Err(e) = func1() { if let Err(e) = func1() {
if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() { if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() {
eprintln!("Func1Error: {}", f1err); eprintln!("Func1Error: {}", f1err);
if let Some(f2err) = f1err.find_cause::<chainerror::Error<Func2Error>>() { if let Some(f2err) = f1err.find_cause::<ChainError<Func2Error>>() {
eprintln!("Func2Error: {}", f2err); eprintln!("Func2Error: {}", f2err);
} }
@ -36,7 +36,6 @@ fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
eprintln!("Debug Func2Error:\n{:?}", f2err); eprintln!("Debug Func2Error:\n{:?}", f2err);
} }
} }
std::process::exit(1);
} }
Ok(()) Ok(())
} }

View file

@ -1,41 +1,40 @@
use chainerror::{Context as _, ErrorDown}; use chainerror::*;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { fn do_some_io() -> Result<(), Box<Error>> {
Err(io::Error::from(io::ErrorKind::NotFound))?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }
chainerror::str_context!(Func2Error); derive_str_cherr!(Func2Error);
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { fn func2() -> Result<(), Box<Error>> {
let filename = "foo.txt"; let filename = "foo.txt";
do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?;
Ok(()) Ok(())
} }
chainerror::str_context!(Func1ErrorFunc2); derive_str_cherr!(Func1ErrorFunc2);
chainerror::str_context!(Func1ErrorIO); derive_str_cherr!(Func1ErrorIO);
fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { fn func1() -> Result<(), Box<Error>> {
func2().context(Func1ErrorFunc2::new("func1 error calling func2"))?; func2().map_err(mstrerr!(Func1ErrorFunc2, "func1 error calling func2"))?;
let filename = "bar.txt"; let filename = "bar.txt";
do_some_io().context(Func1ErrorIO(format!("Error reading '{}'", filename)))?; do_some_io().map_err(mstrerr!(Func1ErrorIO, "Error reading '{}'", filename))?;
Ok(()) Ok(())
} }
fn main() -> Result<(), Box<dyn Error + Send + Sync>> { fn main() -> Result<(), Box<Error>> {
if let Err(e) = func1() { if let Err(e) = func1() {
if let Some(s) = e.downcast_ref::<chainerror::Error<Func1ErrorIO>>() { if let Some(s) = e.downcast_ref::<ChainError<Func1ErrorIO>>() {
eprintln!("Func1ErrorIO:\n{:?}", s); eprintln!("Func1ErrorIO:\n{:?}", s);
} }
if let Some(s) = e.downcast_chain_ref::<Func1ErrorFunc2>() { if let Some(s) = e.downcast_chain_ref::<Func1ErrorFunc2>() {
eprintln!("Func1ErrorFunc2:\n{:?}", s); eprintln!("Func1ErrorFunc2:\n{:?}", s);
} }
std::process::exit(1);
} }
Ok(()) Ok(())
} }

1428
src/lib.rs

File diff suppressed because it is too large Load diff

View file

@ -1,33 +0,0 @@
use chainerror::Context;
#[test]
fn test_basic() {
use std::path::PathBuf;
type BoxedError = Box<dyn std::error::Error + Send + Sync>;
fn read_config_file(path: PathBuf) -> Result<(), BoxedError> {
// do stuff, return other errors
let _buf = std::fs::read_to_string(&path).context(format!("Reading file: {:?}", &path))?;
// do stuff, return other errors
Ok(())
}
fn process_config_file() -> Result<(), BoxedError> {
// do stuff, return other errors
read_config_file("_non_existent.txt".into()).context("read the config file")?;
// do stuff, return other errors
Ok(())
}
if let Err(e) = process_config_file() {
let os_notfound_error = std::io::Error::from_raw_os_error(2);
let s = format!("{:?}", e);
let lines = s.lines().collect::<Vec<_>>();
assert_eq!(lines.len(), 5);
assert!(lines[0].starts_with("tests/test_basic.rs:"));
assert_eq!(lines[1], "Caused by:");
assert!(lines[2].starts_with("tests/test_basic.rs:"));
assert_eq!(lines[3], "Caused by:");
assert_eq!(lines[4], format!("{:?}", os_notfound_error));
} else {
panic!();
}
}

View file

@ -1,29 +1,28 @@
use chainerror::Context; use chainerror::*;
use std::error::Error; use std::error::Error;
use std::fmt::Write;
use std::io; use std::io;
#[test] #[test]
fn test_iter() -> Result<(), Box<dyn Error + Send + Sync>> { fn test_iter() -> Result<(), Box<Error>> {
use std::fmt::Write; let err = io::Error::from(io::ErrorKind::NotFound);
let err: Result<(), _> = Err(io::Error::from(io::ErrorKind::NotFound)); let err = cherr!(err, "1");
let err = err.context("1"); let err = cherr!(err, "2");
let err = err.context("2"); let err = cherr!(err, "3");
let err = err.context("3"); let err = cherr!(err, "4");
let err = err.context("4"); let err = cherr!(err, "5");
let err = err.context("5"); let err = cherr!(err, "6");
let err = err.context("6");
let err = err.err().unwrap();
let mut res = String::new(); let mut res = String::new();
for e in err.iter() { for e in err.iter() {
write!(res, "{}", e)?; write!(res, "{}", e.to_string())?;
} }
assert_eq!(res, "654321entity not found"); assert_eq!(res, "654321entity not found");
let io_error: Option<&io::Error> = err let io_error: Option<&io::Error> = err
.iter() .iter()
.filter_map(<dyn Error>::downcast_ref::<io::Error>) .filter_map(Error::downcast_ref::<io::Error>)
.next(); .next();
assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound); assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound);
@ -32,40 +31,14 @@ fn test_iter() -> Result<(), Box<dyn Error + Send + Sync>> {
} }
#[test] #[test]
fn test_iter_alternate() -> Result<(), Box<dyn Error + Send + Sync>> { fn test_find_cause() -> Result<(), Box<Error>> {
let err: Result<(), _> = Err(io::Error::from(io::ErrorKind::NotFound)); let err = io::Error::from(io::ErrorKind::NotFound);
let err = err.context("1"); let err = cherr!(err, "1");
let err = err.context("2"); let err = cherr!(err, "2");
let err = err.context("3"); let err = cherr!(err, "3");
let err = err.context("4"); let err = cherr!(err, "4");
let err = err.context("5"); let err = cherr!(err, "5");
let err = err.context("6"); let err = cherr!(err, "6");
let err = err.err().unwrap();
let res = format!("{:#}", err);
assert_eq!(res, format!("6\nCaused by:\n 5\nCaused by:\n 4\nCaused by:\n 3\nCaused by:\n 2\nCaused by:\n 1\nCaused by:\n {:#}", io::Error::from(io::ErrorKind::NotFound)));
let io_error: Option<&io::Error> = err
.iter()
.filter_map(<dyn Error>::downcast_ref::<io::Error>)
.next();
assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound);
Ok(())
}
#[test]
fn test_find_cause() -> Result<(), Box<dyn Error + Send + Sync>> {
let err: Result<(), _> = Err(io::Error::from(io::ErrorKind::NotFound));
let err = err.context("1");
let err = err.context("2");
let err = err.context("3");
let err = err.context("4");
let err = err.context("5");
let err = err.context("6");
let err = err.err().unwrap();
let io_error: Option<&io::Error> = err.find_cause::<io::Error>(); let io_error: Option<&io::Error> = err.find_cause::<io::Error>();
@ -75,18 +48,17 @@ fn test_find_cause() -> Result<(), Box<dyn Error + Send + Sync>> {
} }
#[test] #[test]
fn test_root_cause() -> Result<(), Box<dyn Error + Send + Sync>> { fn test_root_cause() -> Result<(), Box<Error>> {
let err: Result<(), _> = Err(io::Error::from(io::ErrorKind::NotFound)); let err = io::Error::from(io::ErrorKind::NotFound);
let err = err.context("1"); let err = cherr!(err, "1");
let err = err.context("2"); let err = cherr!(err, "2");
let err = err.context("3"); let err = cherr!(err, "3");
let err = err.context("4"); let err = cherr!(err, "4");
let err = err.context("5"); let err = cherr!(err, "5");
let err = err.context("6"); let err = cherr!(err, "6");
let err = err.err().unwrap();
let err: Option<&(dyn std::error::Error + 'static)> = err.root_cause(); let err: Option<&(dyn std::error::Error + 'static)> = err.root_cause();
let io_error: Option<&io::Error> = err.and_then(<dyn Error>::downcast_ref::<io::Error>); let io_error: Option<&io::Error> = err.and_then(Error::downcast_ref::<io::Error>);
assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound); assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound);

611
theme/book.js Normal file
View file

@ -0,0 +1,611 @@
"use strict";
// Fix back button cache problem
window.onunload = function () { };
// Global variable, shared between modules
function playpen_text(playpen) {
let code_block = playpen.querySelector("code");
if (window.ace && code_block.classList.contains("editable")) {
let editor = window.ace.edit(code_block);
return editor.getValue();
} else {
return code_block.textContent;
}
}
(function codeSnippets() {
// Hide Rust code lines prepended with a specific character
var hiding_character = "#";
function fetch_with_timeout(url, options, timeout = 6000) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
]);
}
var playpens = Array.from(document.querySelectorAll(".playpen"));
if (playpens.length > 0) {
fetch_with_timeout("https://play.rust-lang.org/meta/crates", {
headers: {
'Content-Type': "application/json",
},
method: 'POST',
mode: 'cors',
})
.then(response => response.json())
.then(response => {
// get list of crates available in the rust playground
let playground_crates = response.crates.map(item => item["id"]);
playpens.forEach(block => handle_crate_list_update(block, playground_crates));
});
}
function handle_crate_list_update(playpen_block, playground_crates) {
// update the play buttons after receiving the response
update_play_button(playpen_block, playground_crates);
// and install on change listener to dynamically update ACE editors
if (window.ace) {
let code_block = playpen_block.querySelector("code");
if (code_block.classList.contains("editable")) {
let editor = window.ace.edit(code_block);
editor.addEventListener("change", function (e) {
update_play_button(playpen_block, playground_crates);
});
}
}
}
// updates the visibility of play button based on `no_run` class and
// used crates vs ones available on http://play.rust-lang.org
function update_play_button(pre_block, playground_crates) {
var play_button = pre_block.querySelector(".play-button");
// skip if code is `no_run`
if (pre_block.querySelector('code').classList.contains("no_run")) {
play_button.classList.add("hidden");
return;
}
// get list of `extern crate`'s from snippet
var txt = playpen_text(pre_block);
var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g;
var snippet_crates = [];
var item;
while (item = re.exec(txt)) {
snippet_crates.push(item[1]);
}
// check if all used crates are available on play.rust-lang.org
var all_available = snippet_crates.every(function (elem) {
return playground_crates.indexOf(elem) > -1;
});
if (all_available) {
play_button.classList.remove("hidden");
} else {
play_button.classList.add("hidden");
}
}
function run_rust_code(code_block) {
var result_block = code_block.querySelector(".result");
if (!result_block) {
result_block = document.createElement('code');
result_block.className = 'result hljs language-bash';
code_block.append(result_block);
}
let text = playpen_text(code_block);
var params = {
channel: "stable",
mode: "debug",
edition: "2018",
crateType: "bin",
tests: false,
code: text
};
if (text.indexOf("#![feature") !== -1) {
params.channel = "nightly";
}
if (text.indexOf("#[test]") !== -1) {
params.tests = "true";
}
if (text.indexOf("#![crate_type=\"lib\"]") !== -1) {
params.crateType = "lib";
}
result_block.innerText = "Running...";
fetch_with_timeout("https://play.rust-lang.org/execute", {
headers: {
'Content-Type': "application/json",
},
method: 'POST',
mode: 'cors',
body: JSON.stringify(params)
})
.then(response => response.json())
.then(response => result_block.innerText = response.stderr + "\n\n" + response.stdout)
.catch(error => result_block.innerText = "Playground Communication: " + error.message);
}
// Syntax highlighting Configuration
hljs.configure({
tabReplace: ' ', // 4 spaces
languages: [], // Languages used for auto-detection
});
if (window.ace) {
// language-rust class needs to be removed for editable
// blocks or highlightjs will capture events
Array
.from(document.querySelectorAll('code.editable'))
.forEach(function (block) { block.classList.remove('language-rust'); });
Array
.from(document.querySelectorAll('code:not(.editable)'))
.forEach(function (block) { hljs.highlightBlock(block); });
} else {
Array
.from(document.querySelectorAll('code'))
.forEach(function (block) { hljs.highlightBlock(block); });
}
// Adding the hljs class gives code blocks the color css
// even if highlighting doesn't apply
Array
.from(document.querySelectorAll('code'))
.forEach(function (block) { block.classList.add('hljs'); });
Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) {
var code_block = block;
var pre_block = block.parentNode;
// hide lines
var lines = code_block.innerHTML.split("\n");
var first_non_hidden_line = false;
var lines_hidden = false;
var trimmed_line = "";
for (var n = 0; n < lines.length; n++) {
trimmed_line = lines[n].trim();
if (trimmed_line[0] == hiding_character && trimmed_line[1] != hiding_character) {
if (first_non_hidden_line) {
lines[n] = "<span class=\"hidden\">" + "\n" + lines[n].replace(/(\s*)# ?/, "$1") + "</span>";
}
else {
lines[n] = "<span class=\"hidden\">" + lines[n].replace(/(\s*)# ?/, "$1") + "\n" + "</span>";
}
lines_hidden = true;
}
else if (first_non_hidden_line) {
lines[n] = "\n" + lines[n];
}
else {
first_non_hidden_line = true;
}
if (trimmed_line[0] == hiding_character && trimmed_line[1] == hiding_character) {
lines[n] = lines[n].replace("##", "#")
}
}
code_block.innerHTML = lines.join("");
// If no lines were hidden, return
if (!lines_hidden) { return; }
var buttons = document.createElement('div');
buttons.className = 'buttons';
buttons.innerHTML = "<button class=\"fa fa-expand\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>";
// add expand button
pre_block.insertBefore(buttons, pre_block.firstChild);
pre_block.querySelector('.buttons').addEventListener('click', function (e) {
if (e.target.classList.contains('fa-expand')) {
var lines = pre_block.querySelectorAll('span.hidden');
e.target.classList.remove('fa-expand');
e.target.classList.add('fa-compress');
e.target.title = 'Hide lines';
e.target.setAttribute('aria-label', e.target.title);
Array.from(lines).forEach(function (line) {
line.classList.remove('hidden');
line.classList.add('unhidden');
});
} else if (e.target.classList.contains('fa-compress')) {
var lines = pre_block.querySelectorAll('span.unhidden');
e.target.classList.remove('fa-compress');
e.target.classList.add('fa-expand');
e.target.title = 'Show hidden lines';
e.target.setAttribute('aria-label', e.target.title);
Array.from(lines).forEach(function (line) {
line.classList.remove('unhidden');
line.classList.add('hidden');
});
}
});
});
Array.from(document.querySelectorAll('pre code')).forEach(function (block) {
var pre_block = block.parentNode;
if (!pre_block.classList.contains('playpen')) {
var buttons = pre_block.querySelector(".buttons");
if (!buttons) {
buttons = document.createElement('div');
buttons.className = 'buttons';
pre_block.insertBefore(buttons, pre_block.firstChild);
}
var clipButton = document.createElement('button');
clipButton.className = 'fa fa-copy clip-button';
clipButton.title = 'Copy to clipboard';
clipButton.setAttribute('aria-label', clipButton.title);
clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
buttons.insertBefore(clipButton, buttons.firstChild);
}
});
// Process playpen code blocks
Array.from(document.querySelectorAll(".playpen")).forEach(function (pre_block) {
// Add play button
var buttons = pre_block.querySelector(".buttons");
if (!buttons) {
buttons = document.createElement('div');
buttons.className = 'buttons';
pre_block.insertBefore(buttons, pre_block.firstChild);
}
var runCodeButton = document.createElement('button');
runCodeButton.className = 'fa fa-play play-button';
runCodeButton.hidden = true;
runCodeButton.title = 'Run this code';
runCodeButton.setAttribute('aria-label', runCodeButton.title);
var copyCodeClipboardButton = document.createElement('button');
copyCodeClipboardButton.className = 'fa fa-copy clip-button';
copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
copyCodeClipboardButton.title = 'Copy to clipboard';
copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
buttons.insertBefore(runCodeButton, buttons.firstChild);
buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
runCodeButton.addEventListener('click', function (e) {
run_rust_code(pre_block);
});
let code_block = pre_block.querySelector("code");
if (window.ace && code_block.classList.contains("editable")) {
var undoChangesButton = document.createElement('button');
undoChangesButton.className = 'fa fa-history reset-button';
undoChangesButton.title = 'Undo changes';
undoChangesButton.setAttribute('aria-label', undoChangesButton.title);
buttons.insertBefore(undoChangesButton, buttons.firstChild);
undoChangesButton.addEventListener('click', function () {
let editor = window.ace.edit(code_block);
editor.setValue(editor.originalCode);
editor.clearSelection();
});
}
});
})();
(function themes() {
var html = document.querySelector('html');
var themeToggleButton = document.getElementById('theme-toggle');
var themePopup = document.getElementById('theme-list');
var themeColorMetaTag = document.querySelector('meta[name="theme-color"]');
var stylesheets = {
ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"),
tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"),
highlight: document.querySelector("[href$='highlight.css']"),
};
function showThemes() {
themePopup.style.display = 'block';
themeToggleButton.setAttribute('aria-expanded', true);
themePopup.querySelector("button#" + document.body.className).focus();
}
function hideThemes() {
themePopup.style.display = 'none';
themeToggleButton.setAttribute('aria-expanded', false);
themeToggleButton.focus();
}
function set_theme(theme) {
let ace_theme;
if (theme == 'coal' || theme == 'navy') {
stylesheets.ayuHighlight.disabled = true;
stylesheets.tomorrowNight.disabled = false;
stylesheets.highlight.disabled = true;
ace_theme = "ace/theme/tomorrow_night";
} else if (theme == 'ayu') {
stylesheets.ayuHighlight.disabled = false;
stylesheets.tomorrowNight.disabled = true;
stylesheets.highlight.disabled = true;
ace_theme = "ace/theme/tomorrow_night";
} else {
stylesheets.ayuHighlight.disabled = true;
stylesheets.tomorrowNight.disabled = true;
stylesheets.highlight.disabled = false;
ace_theme = "ace/theme/dawn";
}
setTimeout(function () {
themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor;
}, 1);
if (window.ace && window.editors) {
window.editors.forEach(function (editor) {
editor.setTheme(ace_theme);
});
}
var previousTheme;
try { previousTheme = localStorage.getItem('mdbook-theme'); } catch (e) { }
if (previousTheme === null || previousTheme === undefined) { previousTheme = default_theme; }
try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
document.body.className = theme;
html.classList.remove(previousTheme);
html.classList.add(theme);
}
// Set theme
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
set_theme(theme);
themeToggleButton.addEventListener('click', function () {
if (themePopup.style.display === 'block') {
hideThemes();
} else {
showThemes();
}
});
themePopup.addEventListener('click', function (e) {
var theme = e.target.id || e.target.parentElement.id;
set_theme(theme);
});
themePopup.addEventListener('focusout', function(e) {
// e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) {
hideThemes();
}
});
// Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang-nursery/mdBook/issues/628
document.addEventListener('click', function(e) {
if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) {
hideThemes();
}
});
document.addEventListener('keydown', function (e) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
if (!themePopup.contains(e.target)) { return; }
switch (e.key) {
case 'Escape':
e.preventDefault();
hideThemes();
break;
case 'ArrowUp':
e.preventDefault();
var li = document.activeElement.parentElement;
if (li && li.previousElementSibling) {
li.previousElementSibling.querySelector('button').focus();
}
break;
case 'ArrowDown':
e.preventDefault();
var li = document.activeElement.parentElement;
if (li && li.nextElementSibling) {
li.nextElementSibling.querySelector('button').focus();
}
break;
case 'Home':
e.preventDefault();
themePopup.querySelector('li:first-child button').focus();
break;
case 'End':
e.preventDefault();
themePopup.querySelector('li:last-child button').focus();
break;
}
});
})();
(function sidebar() {
var html = document.querySelector("html");
var sidebar = document.getElementById("sidebar");
var sidebarLinks = document.querySelectorAll('#sidebar a');
var sidebarToggleButton = document.getElementById("sidebar-toggle");
var firstContact = null;
function showSidebar() {
html.classList.remove('sidebar-hidden')
html.classList.add('sidebar-visible');
Array.from(sidebarLinks).forEach(function (link) {
link.setAttribute('tabIndex', 0);
});
sidebarToggleButton.setAttribute('aria-expanded', true);
sidebar.setAttribute('aria-hidden', false);
try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { }
}
function hideSidebar() {
html.classList.remove('sidebar-visible')
html.classList.add('sidebar-hidden');
Array.from(sidebarLinks).forEach(function (link) {
link.setAttribute('tabIndex', -1);
});
sidebarToggleButton.setAttribute('aria-expanded', false);
sidebar.setAttribute('aria-hidden', true);
try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { }
}
// Toggle sidebar
sidebarToggleButton.addEventListener('click', function sidebarToggle() {
if (html.classList.contains("sidebar-hidden")) {
showSidebar();
} else if (html.classList.contains("sidebar-visible")) {
hideSidebar();
} else {
if (getComputedStyle(sidebar)['transform'] === 'none') {
hideSidebar();
} else {
showSidebar();
}
}
});
document.addEventListener('touchstart', function (e) {
firstContact = {
x: e.touches[0].clientX,
time: Date.now()
};
}, { passive: true });
document.addEventListener('touchmove', function (e) {
if (!firstContact)
return;
var curX = e.touches[0].clientX;
var xDiff = curX - firstContact.x,
tDiff = Date.now() - firstContact.time;
if (tDiff < 250 && Math.abs(xDiff) >= 150) {
if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300))
showSidebar();
else if (xDiff < 0 && curX < 300)
hideSidebar();
firstContact = null;
}
}, { passive: true });
// Scroll sidebar to current active section
var activeSection = sidebar.querySelector(".active");
if (activeSection) {
sidebar.scrollTop = activeSection.offsetTop;
}
})();
(function chapterNavigation() {
document.addEventListener('keydown', function (e) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
if (window.search && window.search.hasFocus()) { return; }
switch (e.key) {
case 'ArrowRight':
e.preventDefault();
var nextButton = document.querySelector('.nav-chapters.next');
if (nextButton) {
window.location.href = nextButton.href;
}
break;
case 'ArrowLeft':
e.preventDefault();
var previousButton = document.querySelector('.nav-chapters.previous');
if (previousButton) {
window.location.href = previousButton.href;
}
break;
}
});
})();
(function clipboard() {
var clipButtons = document.querySelectorAll('.clip-button');
function hideTooltip(elem) {
elem.firstChild.innerText = "";
elem.className = 'fa fa-copy clip-button';
}
function showTooltip(elem, msg) {
elem.firstChild.innerText = msg;
elem.className = 'fa fa-copy tooltipped';
}
var clipboardSnippets = new Clipboard('.clip-button', {
text: function (trigger) {
hideTooltip(trigger);
let playpen = trigger.closest("pre");
return playpen_text(playpen);
}
});
Array.from(clipButtons).forEach(function (clipButton) {
clipButton.addEventListener('mouseout', function (e) {
hideTooltip(e.currentTarget);
});
});
clipboardSnippets.on('success', function (e) {
e.clearSelection();
showTooltip(e.trigger, "Copied!");
});
clipboardSnippets.on('error', function (e) {
showTooltip(e.trigger, "Clipboard error!");
});
})();
(function scrollToTop () {
var menuTitle = document.querySelector('.menu-title');
menuTitle.addEventListener('click', function () {
document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' });
});
})();
(function autoHideMenu() {
var menu = document.getElementById('menu-bar');
var previousScrollTop = document.scrollingElement.scrollTop;
document.addEventListener('scroll', function () {
if (menu.classList.contains('folded') && document.scrollingElement.scrollTop < previousScrollTop) {
menu.classList.remove('folded');
} else if (!menu.classList.contains('folded') && document.scrollingElement.scrollTop > previousScrollTop) {
menu.classList.add('folded');
}
if (!menu.classList.contains('bordered') && document.scrollingElement.scrollTop > 0) {
menu.classList.add('bordered');
}
if (menu.classList.contains('bordered') && document.scrollingElement.scrollTop === 0) {
menu.classList.remove('bordered');
}
previousScrollTop = document.scrollingElement.scrollTop;
}, { passive: true });
})();