diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5cde165 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: cargo + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..7dff904 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,38 @@ +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 diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml new file mode 100644 index 0000000..7062679 --- /dev/null +++ b/.github/workflows/gh-pages.yml @@ -0,0 +1,30 @@ +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 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..3fc2545 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,72 @@ +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 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b39f048..0000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -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 diff --git a/Cargo.toml b/Cargo.toml index 4b4d99d..59b8808 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "chainerror" -version = "0.4.0" +version = "1.0.0" authors = ["Harald Hoyer "] edition = "2018" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" documentation = "https://docs.rs/chainerror" homepage = "https://haraldh.github.io/chainerror/" repository = "https://github.com/haraldh/chainerror" @@ -16,10 +16,11 @@ exclude = [ ".gitignore", "examples/*", "booksrc/*", "book.toml", "theme/*", "git-deploy-branch.sh", ".travis.yml" ] [badges] -travis-ci = { repository = "haraldh/chainerror" } +# See https://doc.rust-lang.org/cargo/reference/manifest.html#the-badges-section +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" } -[features] -default = [ ] -no-fileline = [] -display-cause = [] -no-debug-cause = [] +[package.metadata.docs.rs] +cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] diff --git a/README.md b/README.md index b24834d..3a46567 100644 --- a/README.md +++ b/README.md @@ -1,129 +1,186 @@ -# chainerror - -[![Build Status](https://travis-ci.com/haraldh/chainerror.svg?branch=master)](https://travis-ci.com/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/) +[![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` provides an error backtrace like `failure` without doing a real backtrace, so even after you `strip` your +# chainerror + +`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! +Having nested function returning errors, the output doesn't tell where the error originates from. -`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. +```rust +use std::path::PathBuf; -Along with the `ChainError` struct, `chainerror` comes with some useful helper macros to save a lot of typing. - -Debug information is worth it! - -Now continue reading the -[Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) - -## Example: -Output: - -~~~ -$ cargo run -q --example example -Main Error Report: func1 error calling func2 - -Error reported by Func2Error: func2 error: calling func3 - -The root cause was: std::io::Error: Kind( - NotFound -) - -Debug Error: -examples/example.rs:45: func1 error calling func2 -Caused by: -examples/example.rs:20: Func2Error(func2 error: calling func3) -Caused by: -examples/example.rs:13: Error reading 'foo.txt' -Caused by: -Kind(NotFound) -~~~ - -~~~rust,ignore -use chainerror::*; -use std::error::Error; -use std::io; -use std::result::Result; - -fn do_some_io() -> Result<(), Box> { - Err(io::Error::from(io::ErrorKind::NotFound))?; +type BoxedError = Box; +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 func3() -> Result<(), Box> { - let filename = "foo.txt"; - do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; - Ok(()) -} - -derive_str_cherr!(Func2Error); - -fn func2() -> ChainResult<(), Func2Error> { - func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; - Ok(()) -} - -enum Func1Error { - Func2, - IO(String), -} - -impl ::std::fmt::Display for Func1Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - match self { - Func1Error::Func2 => write!(f, "func1 error calling func2"), - Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), - } - } -} - -impl ::std::fmt::Debug for Func1Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "{}", self) - } -} - -fn func1() -> ChainResult<(), Func1Error> { - func2().map_err(|e| cherr!(e, Func1Error::Func2))?; - let filename = String::from("bar.txt"); - do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; +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) = func1() { - match e.kind() { - Func1Error::Func2 => eprintln!("Main Error Report: func1 error calling func2"), - Func1Error::IO(filename) => { - eprintln!("Main Error Report: func1 error reading '{}'", filename) - } - } - - if let Some(e) = e.find_chain_cause::() { - eprintln!("\nError reported by Func2Error: {}", e) - } - - if let Some(e) = e.root_cause() { - let ioerror = e.downcast_ref::().unwrap(); - eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); - } - - eprintln!("\nDebug Error:\n{:?}", e); + 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. -## Features -`no-fileline` -: completely turn off storing filename and line +With `chainerror`, you can supply a context and get a nice error backtrace: -`display-cause` -: turn on printing a backtrace of the errors in `Display` +```rust +use chainerror::Context as _; +use std::path::PathBuf; -`no-debug-cause` -: turn off printing a backtrace of the errors in `Debug` +type BoxedError = Box; +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` struct, `chainerror` comes with some useful helper macros to save a lot of typing. + +`chainerror` has no dependencies! + +Debug information is worth it! + +## Multiple Output Formats + +`chainerror` supports multiple output formats, which can be selected with the different format specifiers: + +* `{}`: Display +```console +func1 error calling func2 +``` + +* `{:#}`: Alternative Display +```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 +```console +examples/example.rs:50:13: func1 error calling func2 +Caused by: +examples/example.rs:25:13: Func2Error(func2 error: calling func3) +Caused by: +examples/example.rs:18:13: (passed error) +Caused by: +examples/example.rs:13:18: Error reading 'foo.txt' +Caused by: +Kind(NotFound) + +``` + +* `{:#?}`: Alternative Debug +```console +Error { + occurrence: Some( + "examples/example.rs:50:13", + ), + kind: func1 error calling func2, + source: Some( + Error { + occurrence: Some( + "examples/example.rs:25:13", + ), + kind: Func2Error(func2 error: calling func3), + source: Some( + Error { + occurrence: Some( + "examples/example.rs:18:13", + ), + kind: (passed error), + source: Some( + Error { + occurrence: Some( + "examples/example.rs:13:18", + ), + kind: "Error reading 'foo.txt'", + source: Some( + Kind( + NotFound, + ), + ), + }, + ), + }, + ), + }, + ), +} +``` + +## Tutorial + +Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) + +## License + +Licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/booksrc/LICENSE-APACHE b/booksrc/LICENSE-APACHE new file mode 120000 index 0000000..965b606 --- /dev/null +++ b/booksrc/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/booksrc/LICENSE-MIT b/booksrc/LICENSE-MIT new file mode 120000 index 0000000..76219eb --- /dev/null +++ b/booksrc/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/booksrc/SUMMARY.md b/booksrc/SUMMARY.md index ac070ee..6bbc3b4 100644 --- a/booksrc/SUMMARY.md +++ b/booksrc/SUMMARY.md @@ -5,7 +5,7 @@ - [Simple String Errors](tutorial1.md) - [Simple Chained String Errors](tutorial2.md) - [Mapping Errors](tutorial3.md) -- [Saving coding chars](tutorial4.md) +- [More Information](tutorial4.md) - [The source() of Errors](tutorial5.md) - [Downcast the Errors](tutorial6.md) - [The root cause of all Errors](tutorial7.md) @@ -13,5 +13,8 @@ - [Selective Error Handling](tutorial9.md) - [ErrorKind to the rescue](tutorial10.md) - [Debug for the ErrorKind](tutorial11.md) +- [Deref for the ErrorKind](tutorial12.md) +- [Writing a library](tutorial13.md) +- [Going back to std](tutorial14.md) -[The End](end.md) \ No newline at end of file +[The End](end.md) diff --git a/booksrc/tutorial1.md b/booksrc/tutorial1.md index cf1d9d0..57a502f 100644 --- a/booksrc/tutorial1.md +++ b/booksrc/tutorial1.md @@ -9,7 +9,7 @@ this only prints out the last `Error`. ~~~ -Error: StringError("func1 error") +Error: "func1 error" ~~~ The next chapters of this tutorial show how `chainerror` adds more information @@ -17,10 +17,10 @@ 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). -~~~ +~~~console $ cargo run -q --example tutorial1 ~~~ ~~~rust {{#include ../examples/tutorial1.rs}} -~~~ \ No newline at end of file +~~~ diff --git a/booksrc/tutorial10.md b/booksrc/tutorial10.md index 7feaf6e..5547954 100644 --- a/booksrc/tutorial10.md +++ b/booksrc/tutorial10.md @@ -1,26 +1,21 @@ # 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 a `std::error::Error`. -Not using `String` errors anymore, the `cherr!()` macro seen in the beginning of -the tutorial has to be used again. - -Only returning `Func1ErrorKind` in `func1()` now let us get rid of `Result<(), Box>` and we can +Only returning `Func1ErrorKind` in `func1()` now let us get rid of `Result<(), Box>` and we can use `ChainResult<(), Func1ErrorKind>`. -In `main` we can now directly use the methods of `ChainError` without downcasting the error first. +In `main` we can now directly use the methods of `chainerror::Error` without downcasting the error first. -Also a nice `match` on `ChainError.kind()` is now possible, which returns `&T`, meaning -`&Func1ErrorKind` here. +Also, a nice `match` on `chainerror::Error.kind()` is now possible, which returns `&T`, meaning `&Func1ErrorKind` here. ~~~rust -use crate::chainerror::*; -{{#include ../examples/tutorial10.rs:2:}} +{{#include ../examples/tutorial10.rs}} # #[allow(dead_code)] # mod chainerror { -{{#includecomment ../src/lib.rs}} +{{#rustdoc_include ../src/lib.rs:-1}} # } -~~~ \ No newline at end of file +~~~ diff --git a/booksrc/tutorial11.md b/booksrc/tutorial11.md index c679792..7d37814 100644 --- a/booksrc/tutorial11.md +++ b/booksrc/tutorial11.md @@ -1,6 +1,6 @@ # Debug for the ErrorKind -One small improvement at the end of the tutorial is to fix the debug output of +One small improvement is to fix the debug output of `Func1ErrorKind`. As you probably noticed, the output doesn't say much of the enum. ~~~ @@ -21,14 +21,13 @@ 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. -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`. ~~~rust -use crate::chainerror::*; -{{#include ../examples/tutorial11.rs:2:}} +{{#include ../examples/tutorial11.rs}} # #[allow(dead_code)] # mod chainerror { -{{#includecomment ../src/lib.rs}} +{{#rustdoc_include ../src/lib.rs:-1}} # } ~~~ diff --git a/booksrc/tutorial12.md b/booksrc/tutorial12.md new file mode 100644 index 0000000..037803b --- /dev/null +++ b/booksrc/tutorial12.md @@ -0,0 +1,11 @@ +# Deref for the ErrorKind + +Because chainerror::Error implements Deref to &T, we can also match on `*e` instead of `e.kind()` +or call a function with `&e` +~~~rust +{{#include ../examples/tutorial12.rs}} +# #[allow(dead_code)] +# mod chainerror { +{{#rustdoc_include ../src/lib.rs:-1}} +# } +~~~ diff --git a/booksrc/tutorial13.md b/booksrc/tutorial13.md new file mode 100644 index 0000000..0ed9db8 --- /dev/null +++ b/booksrc/tutorial13.md @@ -0,0 +1,18 @@ +# Writing a library + +I would advise to only expose an `mycrate::ErrorKind` and type alias `mycrate::Error` to `chainerror::Error` +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:}} +~~~ diff --git a/booksrc/tutorial14.md b/booksrc/tutorial14.md new file mode 100644 index 0000000..1bdb97b --- /dev/null +++ b/booksrc/tutorial14.md @@ -0,0 +1,9 @@ +# 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}} +~~~ diff --git a/booksrc/tutorial2.md b/booksrc/tutorial2.md index 86e2726..a330c1a 100644 --- a/booksrc/tutorial2.md +++ b/booksrc/tutorial2.md @@ -1,16 +1,15 @@ # Simple Chained String Errors -With relatively small changes and the help of the `cherr!` macro of the `chainerror` crate -the `String` errors are now chained together. +With relatively small changes and the help of the `context()` method of the `chainerror` crate +the `&str` errors are now chained together. Press the play button in the upper right corner and see the nice debug output. ~~~rust -use crate::chainerror::*; -{{#include ../examples/tutorial2.rs:2:}} +{{#include ../examples/tutorial2.rs}} # #[allow(dead_code)] # mod chainerror { -{{#includecomment ../src/lib.rs}} +{{#rustdoc_include ../src/lib.rs:-1}} # } ~~~ @@ -20,14 +19,13 @@ use crate::chainerror::*; {{#include ../examples/tutorial2.rs:13:15}} ~~~ -The macro `cherr!(olderror, newerror)` stores `olderror` as the source/cause of `newerror` -along with the filename (`file!()`) and line number (`line!()`) -and returns `newerror`. +The function `context(newerror)` stores `olderror` as the source/cause of `newerror` +along with the `Location` of the `context()` call and returns `Err(newerror)`. -`Err()?` then returns the inner error applying `.into()`, so that we -again have a `Err(Box)` as a result. +`?` then returns the inner error applying `.into()`, so that we +again have a `Err(Box)` as a result. -The `Debug` implementation of `ChainError` (which is returned by `cherr!()`) +The `Debug` implementation of `chainerror::Error` (which is returned by `context()`) prints the `Debug` of `T` prefixed with the stored filename and line number. -`ChainError` in our case is `ChainError`. \ No newline at end of file +`chainerror::Error` in our case is `chainerror::Error<&str>`. diff --git a/booksrc/tutorial3.md b/booksrc/tutorial3.md index b86e0ad..f6c26ac 100644 --- a/booksrc/tutorial3.md +++ b/booksrc/tutorial3.md @@ -1,13 +1,12 @@ # Mapping Errors -Now let's get more rust idiomatic by using `.map_err()`. +Now let's get more rust idiomatic by using `.context()` directly on the previous `Result`. ~~~rust -use crate::chainerror::*; -{{#include ../examples/tutorial3.rs:2:}} +{{#include ../examples/tutorial3.rs}} # #[allow(dead_code)] # mod chainerror { -{{#includecomment ../src/lib.rs}} +{{#rustdoc_include ../src/lib.rs:-1}} # } ~~~ @@ -15,16 +14,16 @@ If you compare the output to the previous example, you will see, that: ~~~ -Error: src/main.rs:19: "func1 error" +Error: examples/tutorial2.rs:20:16: func1 error ~~~ changed to just: ~~~ -src/main.rs:16: "func1 error" +examples/tutorial3.rs:17:13: func1 error ~~~ This is, because we caught the error of `func1()` in `main()` and print it out ourselves. We can now control, whether to output in `Debug` or `Display` mode. -Maybe depending on `--debug` as a CLI argument. \ No newline at end of file +Maybe depending on `--debug` as a CLI argument. diff --git a/booksrc/tutorial4.md b/booksrc/tutorial4.md index 95a6fab..f717706 100644 --- a/booksrc/tutorial4.md +++ b/booksrc/tutorial4.md @@ -1,18 +1,12 @@ -# Saving coding chars +# More information -Because decorating an error with more information should not -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!()`. +To give more context to the error, you want to use `format!` +to extend the information in the context string. ~~~rust -use crate::chainerror::*; -{{#include ../examples/tutorial4.rs:2:}} +{{#include ../examples/tutorial4.rs}} # #[allow(dead_code)] # mod chainerror { -{{#includecomment ../src/lib.rs}} +{{#rustdoc_include ../src/lib.rs:-1}} # } -~~~ \ No newline at end of file +~~~ diff --git a/booksrc/tutorial5.md b/booksrc/tutorial5.md index d98f09e..57932fa 100644 --- a/booksrc/tutorial5.md +++ b/booksrc/tutorial5.md @@ -4,16 +4,14 @@ 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. ~~~rust -use crate::chainerror::*; -{{#include ../examples/tutorial5.rs:2:}} +{{#include ../examples/tutorial5.rs}} # #[allow(dead_code)] # mod chainerror { -{{#includecomment ../src/lib.rs}} +{{#rustdoc_include ../src/lib.rs:-1}} # } ~~~ Note, that because we changed the output of the error in `main()` from `Debug` to `Display`, we don't see the error backtrace with filename and line number. -To enable the `Display` backtrace, you have to enable the feature `display-cause` for `chainerror`. - \ No newline at end of file +To use the `Display` backtrace, you have to use the alternative display format output `{:#}`. diff --git a/booksrc/tutorial6.md b/booksrc/tutorial6.md index 8154874..72fe33a 100644 --- a/booksrc/tutorial6.md +++ b/booksrc/tutorial6.md @@ -11,10 +11,9 @@ pub fn downcast_mut(&mut self) -> Option<&mut T> This is how it looks like, when using those: ~~~rust -use crate::chainerror::*; -{{#include ../examples/tutorial6.rs:2:}} +{{#include ../examples/tutorial6.rs}} # #[allow(dead_code)] # mod chainerror { -{{#includecomment ../src/lib.rs}} +{{#rustdoc_include ../src/lib.rs:-1}} # } ~~~ \ No newline at end of file diff --git a/booksrc/tutorial7.md b/booksrc/tutorial7.md index 4ddd17f..90665f7 100644 --- a/booksrc/tutorial7.md +++ b/booksrc/tutorial7.md @@ -4,15 +4,15 @@ ~~~rust,ignore fn is_chain(&self) -> bool -fn downcast_chain_ref(&self) -> Option<&ChainError> -fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> +fn downcast_chain_ref(&self) -> Option<&chainerror::Error> +fn downcast_chain_mut(&mut self) -> Option<&mut chainerror::Error> fn root_cause(&self) -> Option<&(dyn Error + 'static)> fn find_cause(&self) -> Option<&U> -fn find_chain_cause(&self) -> Option<&ChainError> +fn find_chain_cause(&self) -> Option<&chainerror::Error> fn kind<'a>(&'a self) -> &'a T ~~~ -Using `downcast_chain_ref::()` gives a `ChainError`, which can be used +Using `downcast_chain_ref::()` gives a `chainerror::Error`, which can be used to call `.find_cause::()`. ~~~rust,ignore @@ -27,10 +27,9 @@ or to use `.root_cause()`, which of course can be of any type implementing `std: ~~~ ~~~rust -use crate::chainerror::*; -{{#include ../examples/tutorial7.rs:2:}} +{{#include ../examples/tutorial7.rs}} # #[allow(dead_code)] # mod chainerror { -{{#includecomment ../src/lib.rs}} +{{#rustdoc_include ../src/lib.rs:-1}} # } ~~~ \ No newline at end of file diff --git a/booksrc/tutorial8.md b/booksrc/tutorial8.md index ce123e1..17070fe 100644 --- a/booksrc/tutorial8.md +++ b/booksrc/tutorial8.md @@ -1,14 +1,14 @@ # Finding an Error cause -To distinguish the errors occuring in various places, we can define named string errors with the +To distinguish the errors occurring in various places, we can define named string errors with the "new type" pattern. ~~~rust,ignore -derive_str_cherr!(Func2Error); -derive_str_cherr!(Func1Error); +chainerror::str_context!(Func2Error); +chainerror::str_context!(Func1Error); ~~~ -Instead of `ChainError` we now have `struct Func1Error(String)` and `ChainError`. +Instead of `chainerror::Error` we now have `struct Func1Error(String)` and `chainerror::Error`. In the `main` function you can see, how we can match the different errors. @@ -18,15 +18,14 @@ Also see: ~~~ as a shortcut to ~~~rust,ignore - if let Some(f2err) = f1err.find_cause::>() { + if let Some(f2err) = f1err.find_cause::>() { ~~~ -hiding the `ChainError` implementation detail. +hiding the `chainerror::Error` implementation detail. ~~~rust -use crate::chainerror::*; -{{#include ../examples/tutorial8.rs:2:}} +{{#include ../examples/tutorial8.rs}} # #[allow(dead_code)] # mod chainerror { -{{#includecomment ../src/lib.rs}} +{{#rustdoc_include ../src/lib.rs:-1}} # } -~~~ \ No newline at end of file +~~~ diff --git a/booksrc/tutorial9.md b/booksrc/tutorial9.md index e9107ca..f66dd0e 100644 --- a/booksrc/tutorial9.md +++ b/booksrc/tutorial9.md @@ -7,7 +7,7 @@ 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> { +fn main() -> Result<(), Box> { match func1() { Err(e) if let Some(s) = e.downcast_chain_ref::() => eprintln!("Func1ErrorIO:\n{:?}", s), @@ -25,10 +25,9 @@ but this is not valid rust code, so we end up doing it the hard way. In the next chapter, we will see, how to solve this more elegantly. ~~~rust -use crate::chainerror::*; -{{#include ../examples/tutorial9.rs:2:}} +{{#include ../examples/tutorial9.rs}} # #[allow(dead_code)] # mod chainerror { -{{#includecomment ../src/lib.rs}} +{{#rustdoc_include ../src/lib.rs:-1}} # } ~~~ diff --git a/examples/example.rs b/examples/example.rs index 3d97f70..82c073f 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -1,23 +1,28 @@ -use chainerror::*; +use chainerror::Context as _; use std::error::Error; +use std::fmt; use std::io; -use std::result::Result; -fn do_some_io() -> Result<(), Box> { +fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -fn func3() -> Result<(), Box> { +fn func4() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + do_some_io().context(format!("Error reading '{}'", filename))?; Ok(()) } -derive_str_cherr!(Func2Error); +fn func3() -> Result<(), Box> { + func4().annotate()?; + Ok(()) +} -fn func2() -> ChainResult<(), Func2Error> { - func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; +chainerror::str_context!(Func2Error); + +fn func2() -> chainerror::Result<(), Func2Error> { + func3().context(Func2Error::new("func2 error: calling func3"))?; Ok(()) } @@ -26,8 +31,8 @@ enum Func1Error { IO(String), } -impl ::std::fmt::Display for Func1Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { +impl fmt::Display for Func1Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Func1Error::Func2 => write!(f, "func1 error calling func2"), Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), @@ -35,21 +40,29 @@ impl ::std::fmt::Display for Func1Error { } } -impl ::std::fmt::Debug for Func1Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { +impl fmt::Debug for Func1Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self) } } -fn func1() -> ChainResult<(), Func1Error> { - func2().map_err(|e| cherr!(e, Func1Error::Func2))?; +fn func1() -> chainerror::Result<(), Func1Error> { + func2().context(Func1Error::Func2)?; let filename = String::from("bar.txt"); - do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; + do_some_io().context(Func1Error::IO(filename))?; Ok(()) } fn main() { 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() { Func1Error::Func2 => eprintln!("Main Error Report: func1 error calling func2"), Func1Error::IO(filename) => { @@ -65,7 +78,5 @@ fn main() { let ioerror = e.downcast_ref::().unwrap(); eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); } - - eprintln!("\nDebug Error:\n{:?}", e); } } diff --git a/examples/tutorial1.rs b/examples/tutorial1.rs index 87778d9..08ca3e8 100644 --- a/examples/tutorial1.rs +++ b/examples/tutorial1.rs @@ -1,26 +1,28 @@ +#![allow(clippy::single_match)] +#![allow(clippy::redundant_pattern_matching)] + use std::error::Error; use std::io; -use std::result::Result; -fn do_some_io() -> Result<(), Box> { +fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -fn func2() -> Result<(), Box> { +fn func2() -> Result<(), Box> { if let Err(_) = do_some_io() { Err("func2 error")?; } Ok(()) } -fn func1() -> Result<(), Box> { +fn func1() -> Result<(), Box> { if let Err(_) = func2() { Err("func1 error")?; } Ok(()) } -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { func1() } diff --git a/examples/tutorial10.rs b/examples/tutorial10.rs index b22d0b7..3e0af4a 100644 --- a/examples/tutorial10.rs +++ b/examples/tutorial10.rs @@ -1,18 +1,18 @@ -use chainerror::*; +use chainerror::Context as _; + use std::error::Error; use std::io; -use std::result::Result; -fn do_some_io() -> Result<(), Box> { +fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -derive_str_cherr!(Func2Error); +chainerror::str_context!(Func2Error); -fn func2() -> Result<(), Box> { +fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; Ok(()) } @@ -32,14 +32,14 @@ impl ::std::fmt::Display for Func1ErrorKind { } impl ::std::error::Error for Func1ErrorKind {} -fn func1() -> ChainResult<(), Func1ErrorKind> { - func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; +fn func1() -> chainerror::Result<(), Func1ErrorKind> { + func2().context(Func1ErrorKind::Func2)?; let filename = String::from("bar.txt"); - do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO(filename)))?; + do_some_io().context(Func1ErrorKind::IO(filename))?; Ok(()) } -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { if let Err(e) = func1() { match e.kind() { Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), @@ -53,6 +53,8 @@ fn main() -> Result<(), Box> { } eprintln!("\nDebug Error:\n{:?}", e); + + std::process::exit(1); } Ok(()) } diff --git a/examples/tutorial11.rs b/examples/tutorial11.rs index 46cf00c..abbca94 100644 --- a/examples/tutorial11.rs +++ b/examples/tutorial11.rs @@ -1,18 +1,18 @@ -use chainerror::*; +use chainerror::Context as _; + use std::error::Error; use std::io; -use std::result::Result; -fn do_some_io() -> Result<(), Box> { +fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -derive_str_cherr!(Func2Error); +chainerror::str_context!(Func2Error); -fn func2() -> Result<(), Box> { +fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; Ok(()) } @@ -38,14 +38,14 @@ impl ::std::fmt::Debug for Func1ErrorKind { impl ::std::error::Error for Func1ErrorKind {} -fn func1() -> ChainResult<(), Func1ErrorKind> { - func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; +fn func1() -> chainerror::Result<(), Func1ErrorKind> { + func2().context(Func1ErrorKind::Func2)?; let filename = String::from("bar.txt"); - do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO(filename)))?; + do_some_io().context(Func1ErrorKind::IO(filename))?; Ok(()) } -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { if let Err(e) = func1() { match e.kind() { Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), @@ -59,6 +59,8 @@ fn main() -> Result<(), Box> { } eprintln!("\nDebug Error:\n{:?}", e); + + std::process::exit(1); } Ok(()) } diff --git a/examples/tutorial12.rs b/examples/tutorial12.rs new file mode 100644 index 0000000..caa8fca --- /dev/null +++ b/examples/tutorial12.rs @@ -0,0 +1,77 @@ +use chainerror::Context as _; + +use std::error::Error; +use std::io; + +fn do_some_io() -> Result<(), Box> { + Err(io::Error::from(io::ErrorKind::NotFound))?; + Ok(()) +} + +chainerror::str_context!(Func2Error); + +fn func2() -> Result<(), Box> { + let filename = "foo.txt"; + do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; + Ok(()) +} + +enum Func1ErrorKind { + Func2, + IO(String), +} + +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), + } + } +} + +impl ::std::fmt::Debug for Func1ErrorKind { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "{}", self) + } +} + +impl ::std::error::Error for Func1ErrorKind {} + +fn func1() -> chainerror::Result<(), Func1ErrorKind> { + func2().context(Func1ErrorKind::Func2)?; + let filename = String::from("bar.txt"); + do_some_io().context(Func1ErrorKind::IO(filename))?; + Ok(()) +} + +fn handle_func1errorkind(e: &Func1ErrorKind) { + match e { + Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), + Func1ErrorKind::IO(ref filename) => { + eprintln!("Main Error Report: func1 error reading '{}'", filename) + } + } +} + +fn main() -> Result<(), Box> { + if let Err(e) = func1() { + match *e { + Func1ErrorKind::Func2 => eprintln!("Main Error Report: func1 error calling func2"), + Func1ErrorKind::IO(ref filename) => { + eprintln!("Main Error Report: func1 error reading '{}'", filename) + } + } + + handle_func1errorkind(&e); + + if let Some(e) = e.find_chain_cause::() { + eprintln!("\nError reported by Func2Error: {}", e) + } + + eprintln!("\nDebug Error:\n{:?}", e); + + std::process::exit(1); + } + Ok(()) +} diff --git a/examples/tutorial13.rs b/examples/tutorial13.rs new file mode 100644 index 0000000..0d50d68 --- /dev/null +++ b/examples/tutorial13.rs @@ -0,0 +1,83 @@ +#![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> { + Err(io::Error::from(io::ErrorKind::NotFound))?; + Ok(()) + } + + chainerror::str_context!(Func2Error); + + fn func2() -> std::result::Result<(), Box> { + 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 = std::result::Result; + + 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> { + 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::() { + 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(()) +} diff --git a/examples/tutorial14.rs b/examples/tutorial14.rs new file mode 100644 index 0000000..5ec216e --- /dev/null +++ b/examples/tutorial14.rs @@ -0,0 +1,222 @@ +#![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>, + Option<&'static str>, + ); + + impl Error { + pub fn kind(&self) -> &ErrorKind { + &self.0 + } + } + + impl From 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> { + 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>, + Option<&'static str>, + ); + + impl Error { + pub fn kind(&self) -> &ErrorKind { + &self.0 + } + } + + impl From 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 = std::result::Result; + + 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> { + 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::() { + 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(()) +} diff --git a/examples/tutorial15.rs b/examples/tutorial15.rs new file mode 100644 index 0000000..42355b3 --- /dev/null +++ b/examples/tutorial15.rs @@ -0,0 +1,140 @@ +#![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> { + 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 = std::result::Result; + + 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::() { + ErrorKind::IO(String::from("Unknown filename")) + } else if let Some(_) = e.downcast_inner_ref::() { + ErrorKind::Func2 + } else { + ErrorKind::Unknown + } + } + } + + impl From<&std::boxed::Box> for ErrorKind { + fn from(e: &std::boxed::Box) -> 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> { + 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::() { + 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(()) +} diff --git a/examples/tutorial2.rs b/examples/tutorial2.rs index d75f0a4..96742ad 100644 --- a/examples/tutorial2.rs +++ b/examples/tutorial2.rs @@ -1,28 +1,27 @@ -use chainerror::*; +use chainerror::Context as _; use std::error::Error; use std::io; -use std::result::Result; -fn do_some_io() -> Result<(), Box> { +fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -fn func2() -> Result<(), Box> { +fn func2() -> Result<(), Box> { if let Err(e) = do_some_io() { - Err(cherr!(e, "func2 error"))?; + Err(e).context("func2 error")?; } Ok(()) } -fn func1() -> Result<(), Box> { +fn func1() -> Result<(), Box> { if let Err(e) = func2() { - Err(cherr!(e, "func1 error"))?; + Err(e).context("func1 error")?; } Ok(()) } -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { func1() } diff --git a/examples/tutorial3.rs b/examples/tutorial3.rs index 6915a62..62a44eb 100644 --- a/examples/tutorial3.rs +++ b/examples/tutorial3.rs @@ -1,27 +1,27 @@ -use chainerror::*; +use chainerror::Context as _; use std::error::Error; use std::io; -use std::result::Result; -fn do_some_io() -> Result<(), Box> { +fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -fn func2() -> Result<(), Box> { - do_some_io().map_err(|e| cherr!(e, "func2 error"))?; +fn func2() -> Result<(), Box> { + do_some_io().context("func2 error")?; Ok(()) } -fn func1() -> Result<(), Box> { - func2().map_err(|e| cherr!(e, "func1 error"))?; +fn func1() -> Result<(), Box> { + func2().context("func1 error")?; Ok(()) } -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { if let Err(e) = func1() { eprintln!("{:?}", e); + std::process::exit(1); } Ok(()) } diff --git a/examples/tutorial4.rs b/examples/tutorial4.rs index de51745..3dea51c 100644 --- a/examples/tutorial4.rs +++ b/examples/tutorial4.rs @@ -1,27 +1,28 @@ -use chainerror::*; +use chainerror::Context as _; + use std::error::Error; use std::io; -use std::result::Result; -fn do_some_io() -> Result<(), Box> { +fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -fn func2() -> Result<(), Box> { +fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + do_some_io().context(format!("Error reading '{}'", filename))?; Ok(()) } -fn func1() -> Result<(), Box> { - func2().map_err(mstrerr!("func1 error"))?; +fn func1() -> Result<(), Box> { + func2().context("func1 error")?; Ok(()) } -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { if let Err(e) = func1() { eprintln!("{:?}", e); + std::process::exit(1); } Ok(()) } diff --git a/examples/tutorial5.rs b/examples/tutorial5.rs index e6f36f3..d9fe708 100644 --- a/examples/tutorial5.rs +++ b/examples/tutorial5.rs @@ -1,32 +1,33 @@ -use chainerror::*; +use chainerror::Context as _; + use std::error::Error; use std::io; -use std::result::Result; -fn do_some_io() -> Result<(), Box> { +fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -fn func2() -> Result<(), Box> { +fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + do_some_io().context(format!("Error reading '{}'", filename))?; Ok(()) } -fn func1() -> Result<(), Box> { +fn func1() -> Result<(), Box> { if let Err(e) = func2() { if let Some(s) = e.source() { eprintln!("func2 failed because of '{}'", s); - Err(e).map_err(mstrerr!("func1 error"))?; + Err(e).context("func1 error")?; } } Ok(()) } -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { if let Err(e) = func1() { eprintln!("{}", e); + std::process::exit(1); } Ok(()) } diff --git a/examples/tutorial6.rs b/examples/tutorial6.rs index aca48b4..5d81310 100644 --- a/examples/tutorial6.rs +++ b/examples/tutorial6.rs @@ -1,28 +1,31 @@ -use chainerror::*; +#![allow(clippy::single_match)] +#![allow(clippy::redundant_pattern_matching)] + +use chainerror::Context as _; + use std::error::Error; use std::io; -use std::result::Result; -fn do_some_io() -> Result<(), Box> { +fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -fn func2() -> Result<(), Box> { +fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + do_some_io().context(format!("Error reading '{}'", filename))?; Ok(()) } -fn func1() -> Result<(), Box> { - func2().map_err(mstrerr!("func1 error"))?; +fn func1() -> Result<(), Box> { + func2().context("func1 error")?; Ok(()) } -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { if let Err(e) = func1() { eprintln!("Error: {}", e); - let mut s = e.as_ref(); + let mut s: &(dyn Error) = e.as_ref(); while let Some(c) = s.source() { if let Some(ioerror) = c.downcast_ref::() { eprintln!("caused by: std::io::Error: {}", ioerror); @@ -35,6 +38,7 @@ fn main() -> Result<(), Box> { } s = c; } + std::process::exit(1); } Ok(()) } diff --git a/examples/tutorial7.rs b/examples/tutorial7.rs index 3cb3d2c..214484c 100644 --- a/examples/tutorial7.rs +++ b/examples/tutorial7.rs @@ -1,25 +1,28 @@ -use chainerror::*; +#![allow(clippy::single_match)] +#![allow(clippy::redundant_pattern_matching)] + +use chainerror::{Context as _, ErrorDown as _}; + use std::error::Error; use std::io; -use std::result::Result; -fn do_some_io() -> Result<(), Box> { +fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -fn func2() -> Result<(), Box> { +fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + do_some_io().context(format!("Error reading '{}'", filename))?; Ok(()) } -fn func1() -> Result<(), Box> { - func2().map_err(mstrerr!("func1 error"))?; +fn func1() -> Result<(), Box> { + func2().context("func1 error")?; Ok(()) } -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { if let Err(e) = func1() { eprintln!("Error: {}", e); if let Some(s) = e.downcast_chain_ref::() { @@ -36,6 +39,7 @@ fn main() -> Result<(), Box> { eprintln!("The root cause was: std::io::Error: {:#?}", ioerror); } } + std::process::exit(1); } Ok(()) } diff --git a/examples/tutorial8.rs b/examples/tutorial8.rs index 831e6ed..e32a449 100644 --- a/examples/tutorial8.rs +++ b/examples/tutorial8.rs @@ -1,34 +1,34 @@ -use chainerror::*; +use chainerror::{Context as _, ErrorDown as _}; + use std::error::Error; use std::io; -use std::result::Result; -fn do_some_io() -> Result<(), Box> { +fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -derive_str_cherr!(Func2Error); +chainerror::str_context!(Func2Error); -fn func2() -> Result<(), Box> { +fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; Ok(()) } -derive_str_cherr!(Func1Error); +chainerror::str_context!(Func1Error); -fn func1() -> Result<(), Box> { - func2().map_err(mstrerr!(Func1Error, "func1 error"))?; +fn func1() -> Result<(), Box> { + func2().context(Func1Error::new("func1 error"))?; Ok(()) } -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { if let Err(e) = func1() { if let Some(f1err) = e.downcast_chain_ref::() { eprintln!("Func1Error: {}", f1err); - if let Some(f2err) = f1err.find_cause::>() { + if let Some(f2err) = f1err.find_cause::>() { eprintln!("Func2Error: {}", f2err); } @@ -36,6 +36,7 @@ fn main() -> Result<(), Box> { eprintln!("Debug Func2Error:\n{:?}", f2err); } } + std::process::exit(1); } Ok(()) } diff --git a/examples/tutorial9.rs b/examples/tutorial9.rs index f1954d3..bbbe810 100644 --- a/examples/tutorial9.rs +++ b/examples/tutorial9.rs @@ -1,40 +1,41 @@ -use chainerror::*; +use chainerror::{Context as _, ErrorDown}; + use std::error::Error; use std::io; -use std::result::Result; -fn do_some_io() -> Result<(), Box> { +fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -derive_str_cherr!(Func2Error); +chainerror::str_context!(Func2Error); -fn func2() -> Result<(), Box> { +fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; Ok(()) } -derive_str_cherr!(Func1ErrorFunc2); -derive_str_cherr!(Func1ErrorIO); +chainerror::str_context!(Func1ErrorFunc2); +chainerror::str_context!(Func1ErrorIO); -fn func1() -> Result<(), Box> { - func2().map_err(mstrerr!(Func1ErrorFunc2, "func1 error calling func2"))?; +fn func1() -> Result<(), Box> { + func2().context(Func1ErrorFunc2::new("func1 error calling func2"))?; let filename = "bar.txt"; - do_some_io().map_err(mstrerr!(Func1ErrorIO, "Error reading '{}'", filename))?; + do_some_io().context(Func1ErrorIO(format!("Error reading '{}'", filename)))?; Ok(()) } -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { if let Err(e) = func1() { - if let Some(s) = e.downcast_ref::>() { + if let Some(s) = e.downcast_ref::>() { eprintln!("Func1ErrorIO:\n{:?}", s); } if let Some(s) = e.downcast_chain_ref::() { eprintln!("Func1ErrorFunc2:\n{:?}", s); } + std::process::exit(1); } Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 09df831..623523d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,199 +1,30 @@ -/*! - -`chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your -binaries, you still have the error backtrace. - -`chainerror` has no dependencies! - -`chainerror` uses `.source()` of `std::error::Error` along with `line()!` and `file()!` to provide a nice debug error backtrace. -It encapsulates all types, which have `Display + Debug` and can store the error cause internally. - -Along with the `ChainError` struct, `chainerror` comes with some useful helper macros to save a lot of typing. - -## 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` - - -# Tutorial - -Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) - -# Examples - -~~~rust -use chainerror::*; -use std::error::Error; -use std::io; -use std::result::Result; - -fn do_some_io() -> Result<(), Box> { - Err(io::Error::from(io::ErrorKind::NotFound))?; - Ok(()) -} - -fn func2() -> Result<(), Box> { - let filename = "foo.txt"; - do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; - Ok(()) -} - -fn func1() -> Result<(), Box> { - func2().map_err(mstrerr!("func1 error"))?; - Ok(()) -} - -fn main() { - if let Err(e) = func1() { - #[cfg(not(windows))] - assert_eq!( - format!("\n{:?}\n", e), r#" -src/lib.rs:20: func1 error -Caused by: -src/lib.rs:15: Error reading 'foo.txt' -Caused by: -Kind(NotFound) -"# - ); - } -# else { -# unreachable!(); -# } -} -~~~ - - -~~~rust -use chainerror::*; -use std::error::Error; -use std::io; -use std::result::Result; - -fn do_some_io() -> Result<(), Box> { - Err(io::Error::from(io::ErrorKind::NotFound))?; - Ok(()) -} - -fn func3() -> Result<(), Box> { - let filename = "foo.txt"; - do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; - Ok(()) -} - -derive_str_cherr!(Func2Error); - -fn func2() -> ChainResult<(), Func2Error> { - func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; - Ok(()) -} - -enum Func1Error { - Func2, - IO(String), -} - -impl ::std::fmt::Display for Func1Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - match self { - Func1Error::Func2 => write!(f, "func1 error calling func2"), - Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), - } - } -} - -impl ::std::fmt::Debug for Func1Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "{}", self) - } -} - -fn func1() -> ChainResult<(), Func1Error> { - func2().map_err(|e| cherr!(e, Func1Error::Func2))?; - let filename = String::from("bar.txt"); - do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; - Ok(()) -} - -fn main() { - if let Err(e) = func1() { - assert!( - match e.kind() { - Func1Error::Func2 => { - eprintln!("Main Error Report: func1 error calling func2"); - true - } - Func1Error::IO(filename) => { - eprintln!("Main Error Report: func1 error reading '{}'", filename); - false - } - } - ); - - assert!(e.find_chain_cause::().is_some()); - - if let Some(e) = e.find_chain_cause::() { - eprintln!("\nError reported by Func2Error: {}", e) - } - - - assert!(e.root_cause().is_some()); - - if let Some(e) = e.root_cause() { - let ioerror = e.downcast_ref::().unwrap(); - eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); - } - - #[cfg(not(windows))] - 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!(); -# } -} -~~~ - -!*/ +#![doc = include_str!("../README.md")] +#![deny(clippy::all)] +#![allow(clippy::needless_doctest_main)] +#![deny(missing_docs)] use std::any::TypeId; -use std::error::Error; -use std::fmt::{Debug, Display, Formatter, Result}; +use std::error::Error as StdError; +use std::fmt::{Debug, Display, Formatter}; +use std::panic::Location; -/** chains an inner error kind `T` with a causing error -**/ -pub struct ChainError { - #[cfg(not(feature = "no-fileline"))] - occurrence: Option<(u32, &'static str)>, +/// chains an inner error kind `T` with a causing error +pub struct Error { + occurrence: Option, kind: T, - error_cause: Option>, + error_cause: Option>, } /// convenience type alias -pub type ChainResult = std::result::Result>; +pub type Result = std::result::Result>; -impl ChainError { - #[cfg(not(feature = "no-fileline"))] - /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly +impl Error { + /// Use the `context()` or `map_context()` Result methods instead of calling this directly + #[inline] pub fn new( kind: T, - error_cause: Option>, - occurrence: Option<(u32, &'static str)>, + error_cause: Option>, + occurrence: Option, ) -> Self { Self { occurrence, @@ -202,724 +33,573 @@ impl ChainError { } } - #[cfg(feature = "no-fileline")] - /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly - pub fn new( - kind: T, - error_cause: Option>, - _occurrence: Option<(u32, &'static str)>, - ) -> Self { - Self { kind, error_cause } - } - /// return the root cause of the error chain, if any exists - pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> { - let mut cause = self as &(dyn Error + 'static); - while let Some(c) = cause.source() { - cause = c; - } - Some(cause) + pub fn root_cause(&self) -> Option<&(dyn StdError + 'static)> { + self.iter().last() } - /** find the first error cause of type U, if any exists - - # Examples - - ~~~rust - # use crate::chainerror::*; - # use std::error::Error; - # use std::io; - # use std::result::Result; - # - fn do_some_io() -> Result<(), Box> { - Err(io::Error::from(io::ErrorKind::NotFound))?; - Ok(()) + /// Find the first error cause of type U, if any exists + /// + /// # Examples + /// + /// ```rust + /// use chainerror::Context as _; + /// use chainerror::ErrorDown as _; + /// use std::error::Error; + /// use std::io; + /// + /// fn do_some_io() -> Result<(), Box> { + /// Err(io::Error::from(io::ErrorKind::NotFound))?; + /// Ok(()) + /// } + /// + /// chainerror::str_context!(Func2Error); + /// + /// fn func2() -> Result<(), Box> { + /// let filename = "foo.txt"; + /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; + /// Ok(()) + /// } + /// + /// chainerror::str_context!(Func1Error); + /// + /// fn func1() -> Result<(), Box> { + /// func2().context(Func1Error::new("func1 error"))?; + /// Ok(()) + /// } + /// + /// if let Err(e) = func1() { + /// if let Some(f1err) = e.downcast_chain_ref::() { + /// assert!(f1err.find_cause::().is_some()); + /// + /// assert!(f1err.find_chain_cause::().is_some()); + /// } + /// # else { + /// # panic!(); + /// # } + /// } + /// # else { + /// # unreachable!(); + /// # } + /// ``` + #[inline] + pub fn find_cause(&self) -> Option<&U> { + self.iter() + .filter_map(::downcast_ref::) + .next() } - derive_str_cherr!(Func2Error); - - fn func2() -> Result<(), Box> { - let filename = "foo.txt"; - do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; - Ok(()) + /// Find the first error cause of type [`Error`](Error), if any exists + /// + /// Same as `find_cause`, but hides the [`Error`](Error) implementation internals + /// + /// # Examples + /// + /// ```rust + /// # chainerror::str_context!(FooError); + /// # let err = chainerror::Error::new(String::new(), None, None); + /// // Instead of writing + /// err.find_cause::>(); + /// + /// // leave out the chainerror::Error implementation detail + /// err.find_chain_cause::(); + /// ``` + #[inline] + pub fn find_chain_cause(&self) -> Option<&Error> { + self.iter() + .filter_map(::downcast_ref::>) + .next() } - derive_str_cherr!(Func1Error); - - fn func1() -> Result<(), Box> { - func2().map_err(mstrerr!(Func1Error, "func1 error"))?; - Ok(()) + /// Find the first error cause of type [`Error`](Error) or `U`, if any exists and return `U` + /// + /// Same as `find_cause` and `find_chain_cause`, but hides the [`Error`](Error) implementation internals + /// + /// # Examples + /// + /// ```rust + /// # chainerror::str_context!(FooErrorKind); + /// # let err = chainerror::Error::new(String::new(), None, None); + /// // Instead of writing + /// err.find_cause::>(); + /// // and/or + /// err.find_chain_cause::(); + /// // and/or + /// err.find_cause::(); + /// + /// // leave out the chainerror::Error implementation detail + /// err.find_kind_or_cause::(); + /// ``` + #[inline] + pub fn find_kind_or_cause(&self) -> Option<&U> { + self.iter() + .filter_map(|e| { + e.downcast_ref::>() + .map(|e| e.kind()) + .or_else(|| e.downcast_ref::()) + }) + .next() } - fn main() { - if let Err(e) = func1() { - if let Some(f1err) = e.downcast_chain_ref::() { - - assert!(f1err.find_cause::().is_some()); - - assert!(f1err.find_chain_cause::().is_some()); - } - # else { - # panic!(); - # } - } - # else { - # unreachable!(); - # } - } - ~~~ - **/ - pub fn find_cause(&self) -> Option<&U> { - let mut cause = self as &(dyn Error + 'static); - loop { - if cause.is::() { - return cause.downcast_ref::(); - } - - match cause.source() { - Some(c) => cause = c, - None => return None, - } - } - } - - /** find the first error cause of type ChainError, if any exists - - Same as `find_cause`, but hides the `ChainError` implementation internals - - # Examples - - ~~~rust,ignore - /// Instead of writing - err.find_cause::>(); - - /// leave out the ChainError implementation detail - err.find_chain_cause::(); - ~~~ - - **/ - pub fn find_chain_cause(&self) -> Option<&ChainError> { - let mut cause = self as &(dyn Error + 'static); - loop { - if cause.is::>() { - return cause.downcast_ref::>(); - } - - match cause.source() { - Some(c) => cause = c, - None => return None, - } - } - } - - /** return a reference to T of `ChainError` - - # Examples - - ~~~rust - # use crate::chainerror::*; - # use std::error::Error; - # use std::io; - # use std::result::Result; - # - fn do_some_io() -> Result<(), Box> { - Err(io::Error::from(io::ErrorKind::NotFound))?; - Ok(()) - } - - derive_str_cherr!(Func2Error); - - fn func2() -> Result<(), Box> { - let filename = "foo.txt"; - do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; - Ok(()) - } - - #[derive(Debug)] - enum Func1ErrorKind { - Func2, - IO(String), - } - - // impl ::std::fmt::Display for Func1ErrorKind {…} - # impl ::std::fmt::Display for Func1ErrorKind { - # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - # match self { - # Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), - # Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), - # } - # } - # } - - fn func1() -> ChainResult<(), Func1ErrorKind> { - func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; - do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO("bar.txt".into())))?; - Ok(()) - } - - fn main() { - if let Err(e) = func1() { - match e.kind() { - Func1ErrorKind::Func2 => {}, - Func1ErrorKind::IO(filename) => panic!(), - } - } - # else { - # unreachable!(); - # } - } - ~~~ - - **/ + /// Return a reference to T of [`Error`](Error) + /// + /// # Examples + /// + /// ```rust + /// use chainerror::Context as _; + /// use std::error::Error; + /// use std::io; + /// + /// fn do_some_io() -> Result<(), Box> { + /// Err(io::Error::from(io::ErrorKind::NotFound))?; + /// Ok(()) + /// } + /// + /// chainerror::str_context!(Func2Error); + /// + /// fn func2() -> Result<(), Box> { + /// let filename = "foo.txt"; + /// do_some_io().context(Func2Error(format!("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() -> chainerror::Result<(), Func1ErrorKind> { + /// func2().context(Func1ErrorKind::Func2)?; + /// do_some_io().context(Func1ErrorKind::IO("bar.txt".into()))?; + /// Ok(()) + /// } + /// + /// if let Err(e) = func1() { + /// match e.kind() { + /// Func1ErrorKind::Func2 => {} + /// Func1ErrorKind::IO(filename) => panic!(), + /// } + /// } + /// # else { + /// # unreachable!(); + /// # } + /// ``` + #[inline] pub fn kind(&self) -> &T { &self.kind } + + /// Returns an Iterator over all error causes/sources + /// + /// # Example + #[inline] + pub fn iter(&self) -> impl Iterator { + ErrorIter { + current: Some(self), + } + } +} + +/// Convenience methods for `Result<>` to turn the error into a decorated [`Error`](Error) +pub trait Context>> { + /// Decorate the error with a `kind` of type `T` and the source `Location` + fn context(self, kind: T) -> std::result::Result>; + + /// Decorate the error just with the source `Location` + fn annotate(self) -> std::result::Result>; + + /// Decorate the `error` with a `kind` of type `T` produced with a `FnOnce(&error)` and the source `Location` + fn map_context T>( + self, + op: F, + ) -> std::result::Result>; +} + +/// Convenience type to just decorate the error with the source `Location` +pub struct AnnotatedError(()); + +impl Display for AnnotatedError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "(passed error)") + } +} + +impl Debug for AnnotatedError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "(passed error)") + } +} + +impl>> Context + for std::result::Result +{ + #[track_caller] + #[inline] + fn context(self, kind: T) -> std::result::Result> { + match self { + Ok(t) => Ok(t), + Err(error_cause) => Err(Error::new( + kind, + Some(error_cause.into()), + Some(Location::caller().to_string()), + )), + } + } + + #[track_caller] + #[inline] + fn annotate(self) -> std::result::Result> { + match self { + Ok(t) => Ok(t), + Err(error_cause) => Err(Error::new( + AnnotatedError(()), + Some(error_cause.into()), + Some(Location::caller().to_string()), + )), + } + } + + #[track_caller] + #[inline] + fn map_context T>( + self, + op: F, + ) -> std::result::Result> { + match self { + Ok(t) => Ok(t), + Err(error_cause) => { + let kind = op(&error_cause); + Err(Error::new( + kind, + Some(error_cause.into()), + Some(Location::caller().to_string()), + )) + } + } + } +} + +/// An iterator over all error causes/sources +pub struct ErrorIter<'a> { + current: Option<&'a (dyn StdError + 'static)>, +} + +impl<'a> Iterator for ErrorIter<'a> { + type Item = &'a (dyn StdError + 'static); + + #[inline] + fn next(&mut self) -> Option { + let current = self.current; + self.current = self.current.and_then(StdError::source); + current + } +} + +impl std::ops::Deref for Error { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.kind + } } -/** convenience trait to hide the `ChainError` implementation internals -**/ -pub trait ChainErrorDown { - /** test if of type `ChainError` - **/ +/// Convenience trait to hide the [`Error`](Error) implementation internals +pub trait ErrorDown { + /// Test if of type `Error` fn is_chain(&self) -> bool; - /** downcast to a reference of `ChainError` - **/ - fn downcast_chain_ref(&self) -> Option<&ChainError>; - /** downcast to a mutable reference of `ChainError` - **/ - fn downcast_chain_mut(&mut self) -> Option<&mut ChainError>; + /// Downcast to a reference of `Error` + fn downcast_chain_ref(&self) -> Option<&Error>; + /// Downcast to a mutable reference of `Error` + fn downcast_chain_mut(&mut self) -> Option<&mut Error>; + /// Downcast to T of `Error` + fn downcast_inner_ref(&self) -> Option<&T>; + /// Downcast to T mutable reference of `Error` + fn downcast_inner_mut(&mut self) -> Option<&mut T>; } -impl ChainErrorDown for ChainError { +impl ErrorDown for Error { + #[inline] fn is_chain(&self) -> bool { TypeId::of::() == TypeId::of::() } - fn downcast_chain_ref(&self) -> Option<&ChainError> { + #[inline] + fn downcast_chain_ref(&self) -> Option<&Error> { if self.is_chain::() { - #[allow(clippy::cast_ptr_alignment)] - unsafe { - Some(&*(self as *const dyn Error as *const &ChainError)) - } + // Use transmute when we've verified the types match + unsafe { Some(std::mem::transmute::<&Error, &Error>(self)) } } else { None } } - fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { + #[inline] + fn downcast_chain_mut(&mut self) -> Option<&mut Error> { if self.is_chain::() { - #[allow(clippy::cast_ptr_alignment)] - unsafe { - Some(&mut *(self as *mut dyn Error as *mut &mut ChainError)) - } + // Use transmute when we've verified the types match + unsafe { Some(std::mem::transmute::<&mut Error, &mut Error>(self)) } + } else { + None + } + } + #[inline] + fn downcast_inner_ref(&self) -> Option<&T> { + if self.is_chain::() { + // Use transmute when we've verified the types match + unsafe { Some(std::mem::transmute::<&U, &T>(&self.kind)) } + } else { + None + } + } + + #[inline] + fn downcast_inner_mut(&mut self) -> Option<&mut T> { + if self.is_chain::() { + // Use transmute when we've verified the types match + unsafe { Some(std::mem::transmute::<&mut U, &mut T>(&mut self.kind)) } } else { None } } } -impl ChainErrorDown for dyn Error + 'static { +impl ErrorDown for dyn StdError + 'static { + #[inline] fn is_chain(&self) -> bool { - self.is::>() + self.is::>() } - fn downcast_chain_ref(&self) -> Option<&ChainError> { - self.downcast_ref::>() + #[inline] + fn downcast_chain_ref(&self) -> Option<&Error> { + self.downcast_ref::>() } - fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { - self.downcast_mut::>() + #[inline] + fn downcast_chain_mut(&mut self) -> Option<&mut Error> { + self.downcast_mut::>() + } + + #[inline] + fn downcast_inner_ref(&self) -> Option<&T> { + self.downcast_ref::() + .or_else(|| self.downcast_ref::>().map(|e| e.kind())) + } + + #[inline] + fn downcast_inner_mut(&mut self) -> Option<&mut T> { + if self.is::() { + return self.downcast_mut::(); + } + + self.downcast_mut::>() + .and_then(|e| e.downcast_inner_mut::()) } } -impl ChainErrorDown for dyn Error + 'static + Send { +impl ErrorDown for dyn StdError + 'static + Send { + #[inline] fn is_chain(&self) -> bool { - self.is::>() + self.is::>() } - fn downcast_chain_ref(&self) -> Option<&ChainError> { - self.downcast_ref::>() + #[inline] + fn downcast_chain_ref(&self) -> Option<&Error> { + self.downcast_ref::>() } - fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { - self.downcast_mut::>() + #[inline] + fn downcast_chain_mut(&mut self) -> Option<&mut Error> { + self.downcast_mut::>() + } + + #[inline] + fn downcast_inner_ref(&self) -> Option<&T> { + self.downcast_ref::() + .or_else(|| self.downcast_ref::>().map(|e| e.kind())) + } + + #[inline] + fn downcast_inner_mut(&mut self) -> Option<&mut T> { + if self.is::() { + return self.downcast_mut::(); + } + + self.downcast_mut::>() + .and_then(|e| e.downcast_inner_mut::()) } } -impl ChainErrorDown for dyn Error + 'static + Send + Sync { +impl ErrorDown for dyn StdError + 'static + Send + Sync { + #[inline] fn is_chain(&self) -> bool { - self.is::>() + self.is::>() } - fn downcast_chain_ref(&self) -> Option<&ChainError> { - self.downcast_ref::>() + #[inline] + fn downcast_chain_ref(&self) -> Option<&Error> { + self.downcast_ref::>() } - fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { - self.downcast_mut::>() + #[inline] + fn downcast_chain_mut(&mut self) -> Option<&mut Error> { + self.downcast_mut::>() } -} -impl Error for ChainError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - if let Some(ref e) = self.error_cause { - Some(e.as_ref()) - } else { - None + #[inline] + fn downcast_inner_ref(&self) -> Option<&T> { + self.downcast_ref::() + .or_else(|| self.downcast_ref::>().map(|e| e.kind())) + } + + #[inline] + fn downcast_inner_mut(&mut self) -> Option<&mut T> { + if self.is::() { + return self.downcast_mut::(); } + + self.downcast_mut::>() + .and_then(|e| e.downcast_inner_mut::()) } } -impl Error for &ChainError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - if let Some(ref e) = self.error_cause { - Some(e.as_ref()) - } else { - None - } +impl StdError for Error { + #[inline] + fn source(&self) -> Option<&(dyn StdError + 'static)> { + self.error_cause + .as_ref() + .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } -impl Error for &mut ChainError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - if let Some(ref e) = self.error_cause { - Some(e.as_ref()) - } else { - None - } +impl StdError for &mut Error { + #[inline] + fn source(&self) -> Option<&(dyn StdError + 'static)> { + self.error_cause + .as_ref() + .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } -impl Display for ChainError { - fn fmt(&self, f: &mut Formatter) -> Result { +impl Display for Error { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.kind)?; - #[cfg(feature = "display-cause")] - { + if f.alternate() { if let Some(e) = self.source() { - writeln!(f, "\nCaused by:")?; - Display::fmt(&e, f)?; + write!(f, "\nCaused by:\n {:#}", &e)?; } } + Ok(()) } } -impl Debug for ChainError { - fn fmt(&self, f: &mut Formatter) -> Result { - #[cfg(not(feature = "no-fileline"))] - { - if let Some(o) = self.occurrence { - write!(f, "{}:{}: ", o.1, o.0)?; - } - } +impl Debug for Error { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if f.alternate() { + let mut f = f.debug_struct(&format!("Error<{}>", std::any::type_name::())); - if self.is_chain::() { - Display::fmt(&self.kind, f)?; + let f = f + .field("occurrence", &self.occurrence) + .field("kind", &self.kind) + .field("source", &self.source()); + + f.finish() } else { - Debug::fmt(&self.kind, f)?; - } + if let Some(ref o) = self.occurrence { + write!(f, "{}: ", o)?; + } + + if TypeId::of::() == TypeId::of::() + || TypeId::of::<&str>() == TypeId::of::() + { + Display::fmt(&self.kind, f)?; + } else { + Debug::fmt(&self.kind, f)?; + } - #[cfg(not(feature = "no-debug-cause"))] - { if let Some(e) = self.source() { - writeln!(f, "\nCaused by:")?; - Debug::fmt(&e, f)?; + write!(f, "\nCaused by:\n{:?}", &e)?; + } + Ok(()) + } + } +} + +impl From for Error +where + T: 'static + Display + Debug, +{ + #[track_caller] + #[inline] + fn from(e: T) -> Error { + Error::new(e, None, Some(Location::caller().to_string())) + } +} +/// Convenience macro to create a "new type" T(String) and implement Display + Debug for T +/// +/// # Examples +/// +/// ```rust +/// # use chainerror::Context as _; +/// # use chainerror::ErrorDown as _; +/// # use std::error::Error; +/// # use std::io; +/// # use std::result::Result; +/// # fn do_some_io() -> Result<(), Box> { +/// # Err(io::Error::from(io::ErrorKind::NotFound))?; +/// # Ok(()) +/// # } +/// chainerror::str_context!(Func2Error); +/// +/// fn func2() -> chainerror::Result<(), Func2Error> { +/// let filename = "foo.txt"; +/// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; +/// Ok(()) +/// } +/// +/// chainerror::str_context!(Func1Error); +/// +/// fn func1() -> Result<(), Box> { +/// func2().context(Func1Error::new("func1 error"))?; +/// Ok(()) +/// } +/// # if let Err(e) = func1() { +/// # if let Some(f1err) = e.downcast_chain_ref::() { +/// # assert!(f1err.find_cause::>().is_some()); +/// # assert!(f1err.find_chain_cause::().is_some()); +/// # } else { +/// # panic!(); +/// # } +/// # } else { +/// # unreachable!(); +/// # } +/// ``` +#[macro_export] +macro_rules! str_context { + ($e:ident) => { + #[derive(Clone)] + pub struct $e(pub String); + impl $e { + #[allow(dead_code)] + pub fn new>(s: S) -> Self { + $e(s.into()) } } - Ok(()) - } -} - -pub trait ChainErrorFrom: Sized { - fn chain_error_from(_: T, line_filename: Option<(u32, &'static str)>) -> ChainError; -} - -pub trait IntoChainError: Sized { - fn into_chain_error(self, line_filename: Option<(u32, &'static str)>) -> ChainError; -} - -impl IntoChainError for T -where - U: ChainErrorFrom, -{ - fn into_chain_error(self, line_filename: Option<(u32, &'static str)>) -> ChainError { - U::chain_error_from(self, line_filename) - } -} - -impl ChainErrorFrom for U -where - T: Into, - U: 'static + Display + Debug, -{ - fn chain_error_from(t: T, line_filename: Option<(u32, &'static str)>) -> ChainError { - let e: U = t.into(); - ChainError::<_>::new(e, None, line_filename) - } -} - -#[macro_export] -macro_rules! minto_cherr { - ( ) => { - |e| e.into_chain_error(Some((line!(), file!()))) - }; -} - -#[macro_export] -macro_rules! into_cherr { - ( $t:expr ) => { - $t.into_chain_error(Some((line!(), file!()))) - }; -} - -/** creates a new `ChainError` - -# Examples - -Create a new ChainError, where `FooError` must implement `Display` and `Debug`. -~~~rust -# use chainerror::*; -# -# #[derive(Debug)] -enum FooError { - Bar, - Baz(&'static str), -} -# -# impl ::std::fmt::Display for FooError { -# fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { -# match self { -# FooError::Bar => write!(f, "Bar Error"), -# FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), -# } -# } -# } - -// impl ::std::fmt::Display for FooError - -fn do_some_stuff() -> bool { - false -} - -fn func() -> ChainResult<(), FooError> { - if ! do_some_stuff() { - Err(cherr!(FooError::Baz("Error")))?; - } - Ok(()) -} -# -# pub fn main() { -# match func().unwrap_err().kind() { -# FooError::Baz(s) if s == &"Error" => {}, -# _ => panic!(), -# } -# } -~~~ - -Additionally an error cause can be added. - -~~~rust -# use chainerror::*; -# use std::io; -# use std::error::Error; -# -# #[derive(Debug)] -# enum FooError { -# Bar, -# Baz(&'static str), -# } -# -# impl ::std::fmt::Display for FooError { -# fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { -# match self { -# FooError::Bar => write!(f, "Bar Error"), -# FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), -# } -# } -# } -# -fn do_some_stuff() -> Result<(), Box> { - Err(io::Error::from(io::ErrorKind::NotFound))?; - Ok(()) -} - -fn func() -> ChainResult<(), FooError> { - do_some_stuff().map_err( - |e| cherr!(e, FooError::Baz("Error")) - )?; - Ok(()) -} -# -# pub fn main() { -# match func().unwrap_err().kind() { -# FooError::Baz(s) if s == &"Error" => {}, -# _ => panic!(), -# } -# } -~~~ - -**/ -#[macro_export] -macro_rules! cherr { - ( $k:expr ) => ({ - ChainError::<_>::new($k, None, Some((line!(), file!()))) - }); - ( None, $k:expr ) => ({ - ChainError::<_>::new($k, None, Some((line!(), file!()))) - }); - ( None, $fmt:expr, $($arg:tt)+ ) => ({ - cherr!(None, format!($fmt, $($arg)+ )) - }); - ( None, $fmt:expr, $($arg:tt)+ ) => ({ - cherr!(None, format!($fmt, $($arg)+ )) - }); - ( $e:ident, $k:expr ) => ({ - ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) - }); - ( $e:path, $k:expr ) => ({ - ChainError::<_>::new($k, Some(Box::from($e)), Some((line!(), file!()))) - }); - ( $e:ident, $fmt:expr, $($arg:tt)+ ) => ({ - cherr!($e, format!($fmt, $($arg)+ )) - }); - ( $e:path, $fmt:expr, $($arg:tt)+ ) => ({ - cherr!($e, format!($fmt, $($arg)+ )) - }); - -} - -/** convenience macro for |e| cherr!(e, format!(…)) - -# Examples - -~~~rust -# use crate::chainerror::*; -# use std::error::Error; -# use std::io; -# use std::result::Result; -# -# fn do_some_io() -> Result<(), Box> { -# Err(io::Error::from(io::ErrorKind::NotFound))?; -# Ok(()) -# } -# -fn func2() -> Result<(), Box> { - let filename = "foo.txt"; - do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; - Ok(()) -} - -fn func1() -> Result<(), Box> { - func2().map_err(mstrerr!("func1 error"))?; - Ok(()) -} - -# fn main() { -# if let Err(e) = func1() { -# #[cfg(not(windows))] -# assert_eq!( -# format!("\n{:?}\n", e), r#" -# src/lib.rs:20: func1 error -# Caused by: -# src/lib.rs:15: Error reading 'foo.txt' -# Caused by: -# Kind(NotFound) -# "# -# ); -# } else { -# unreachable!(); -# } -# } -~~~ - -`mstrerr!()` can also be used to map a new `ChainError`, where T was defined with -`derive_str_cherr!(T)` - -~~~rust -# use crate::chainerror::*; -# use std::error::Error; -# use std::io; -# use std::result::Result; -# -# fn do_some_io() -> Result<(), Box> { -# Err(io::Error::from(io::ErrorKind::NotFound))?; -# Ok(()) -# } -# -derive_str_cherr!(Func2Error); - -fn func2() -> Result<(), Box> { - let filename = "foo.txt"; - do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; - Ok(()) -} - -derive_str_cherr!(Func1Error); - -fn func1() -> Result<(), Box> { - func2().map_err(mstrerr!(Func1Error, "func1 error"))?; - Ok(()) -} -# -# fn main() { -# if let Err(e) = func1() { -# if let Some(f1err) = e.downcast_chain_ref::() { -# assert!(f1err.find_cause::>().is_some()); -# assert!(f1err.find_chain_cause::().is_some()); -# } else { -# panic!(); -# } -# } else { -# unreachable!(); -# } -# } -~~~ -**/ -#[macro_export] -macro_rules! mstrerr { - ( $t:ident, $msg:expr ) => ({ - |e| cherr!(e, $t ($msg.to_string())) - }); - ( $t:path, $msg:expr ) => ({ - |e| cherr!(e, $t ($msg.to_string())) - }); - ( $t:ident, $msg:expr, ) => ({ - |e| cherr!(e, $t ($msg.to_string())) - }); - ( $t:path, $msg:expr, ) => ({ - |e| cherr!(e, $t ($msg.to_string())) - }); - ( $t:ident, $fmt:expr, $($arg:tt)+ ) => ({ - |e| cherr!(e, $t (format!($fmt, $($arg)+ ))) - }); - ( $t:path, $fmt:expr, $($arg:tt)+ ) => ({ - |e| cherr!(e, $t (format!($fmt, $($arg)+ ))) - }); - ($msg:expr) => ({ - |e| cherr!(e, $msg.to_string()) - }); - ($msg:expr, ) => ({ - |e| cherr!(e, $msg.to_string()) - }); - ($fmt:expr, $($arg:tt)+) => ({ - |e| cherr!(e, format!($fmt, $($arg)+ )) - }); -} - -/** convenience macro for cherr!(T(format!(…))) where T(String) -~~~rust -# use crate::chainerror::*; -# use std::error::Error; -# use std::result::Result; -# -derive_str_cherr!(Func2Error); - -fn func2() -> ChainResult<(), Func2Error> { - let filename = "foo.txt"; - Err(strerr!(Func2Error, "Error reading '{}'", filename)) -} - -derive_str_cherr!(Func1Error); - -fn func1() -> Result<(), Box> { - func2().map_err(mstrerr!(Func1Error, "func1 error"))?; - Ok(()) -} -# -# fn main() { -# if let Err(e) = func1() { -# if let Some(f1err) = e.downcast_chain_ref::() { -# assert!(f1err.find_cause::>().is_some()); -# assert!(f1err.find_chain_cause::().is_some()); -# } else { -# panic!(); -# } -# } else { -# unreachable!(); -# } -# } -~~~ - -**/ -#[macro_export] -macro_rules! strerr { - ( $t:ident, $msg:expr ) => ({ - cherr!($t ($msg.to_string())) - }); - ( $t:path, $msg:expr ) => ({ - cherr!($t ($msg.to_string())) - }); - ( $t:ident, $msg:expr, ) => ({ - cherr!($t ($msg.to_string())) - }); - ( $t:path, $msg:expr, ) => ({ - cherr!($t ($msg.to_string())) - }); - ( $t:ident, $fmt:expr, $($arg:tt)+ ) => ({ - cherr!($t (format!($fmt, $($arg)+ ))) - }); - ( $t:path, $fmt:expr, $($arg:tt)+ ) => ({ - cherr!($t (format!($fmt, $($arg)+ ))) - }); - ($msg:expr) => ({ - cherr!($msg.to_string()) - }); - ($msg:expr, ) => ({ - cherr!($msg.to_string()) - }); - ($fmt:expr, $($arg:tt)+) => ({ - cherr!(format!($fmt, $($arg)+ )) - }); -} - -/** convenience macro to create a "new type" T(String) and implement Display + Debug for T - -~~~rust -# use crate::chainerror::*; -# use std::error::Error; -# use std::io; -# use std::result::Result; -# -# fn do_some_io() -> Result<(), Box> { -# Err(io::Error::from(io::ErrorKind::NotFound))?; -# Ok(()) -# } -# -derive_str_cherr!(Func2Error); - -fn func2() -> ChainResult<(), Func2Error> { - let filename = "foo.txt"; - do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; - Ok(()) -} - -derive_str_cherr!(Func1Error); - -fn func1() -> Result<(), Box> { - func2().map_err(mstrerr!(Func1Error, "func1 error"))?; - Ok(()) -} -# -# fn main() { -# if let Err(e) = func1() { -# if let Some(f1err) = e.downcast_chain_ref::() { -# assert!(f1err.find_cause::>().is_some()); -# assert!(f1err.find_chain_cause::().is_some()); -# } else { -# panic!(); -# } -# } else { -# unreachable!(); -# } -# } -~~~ - -**/ -#[macro_export] -macro_rules! derive_str_cherr { - ($e:ident) => { - pub struct $e(pub String); impl ::std::fmt::Display for $e { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "{}", self.0) @@ -933,3 +613,413 @@ macro_rules! derive_str_cherr { impl ::std::error::Error for $e {} }; } + +/// Derive an Error for an ErrorKind, which wraps a [`Error`](Error) and implements a `kind()` method +/// +/// It basically hides [`Error`](Error) to the outside and only exposes the [`kind()`](Error::kind) +/// method. +/// +/// Error::kind() returns the ErrorKind +/// Error::source() returns the parent error +/// +/// # Examples +/// +/// ```rust +/// use chainerror::Context as _; +/// use std::io; +/// +/// fn do_some_io(_f: &str) -> std::result::Result<(), io::Error> { +/// return Err(io::Error::from(io::ErrorKind::NotFound)); +/// } +/// +/// #[derive(Debug, Clone)] +/// pub enum ErrorKind { +/// IO(String), +/// FatalError(String), +/// Unknown, +/// } +/// +/// chainerror::err_kind!(Error, ErrorKind); +/// +/// 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::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<&io::Error> for ErrorKind { +/// fn from(e: &io::Error) -> Self { +/// ErrorKind::IO(format!("{}", e)) +/// } +/// } +/// +/// pub fn func1() -> std::result::Result<(), Error> { +/// let filename = "bar.txt"; +/// +/// do_some_io(filename).map_context(|e| ErrorKind::from_io_error(e, filename.into()))?; +/// do_some_io(filename).map_context(|e| ErrorKind::IO(filename.into()))?; +/// do_some_io(filename).map_context(|e| ErrorKind::from(e))?; +/// Ok(()) +/// } +/// +/// # fn main() { +/// # if let Err(e) = func1() { +/// # eprintln!("Error:\n{:?}", e); +/// # } +/// # } +/// ``` +#[macro_export] +macro_rules! err_kind { + ($e:ident, $k:ident) => { + pub struct $e($crate::Error<$k>); + + impl $e { + pub fn kind(&self) -> &$k { + self.0.kind() + } + } + + impl From<$k> for $e { + fn from(e: $k) -> Self { + $e($crate::Error::new(e, None, None)) + } + } + + impl From<$crate::Error<$k>> for $e { + fn from(e: $crate::Error<$k>) -> Self { + $e(e) + } + } + + impl From<&$e> for $k + where + $k: Clone, + { + fn from(e: &$e) -> Self { + e.kind().clone() + } + } + + impl std::error::Error for $e { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.0.source() + } + } + + impl std::fmt::Display for $e { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } + } + + impl std::fmt::Debug for $e { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Debug::fmt(&self.0, f) + } + } + }; +} + +#[cfg(test)] +mod tests { + use super::Context as _; + use super::*; + use std::io; + + #[test] + fn test_error_chain_with_multiple_causes() { + // Create a chain of errors + let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); + + str_context!(Level3Error); + str_context!(Level2Error); + str_context!(Level1Error); + + let err = Result::<(), _>::Err(io_error.into()) + .context(Level3Error("level 3".into())) + .context(Level2Error("level 2".into())) + .context(Level1Error("level 1".into())) + .unwrap_err(); + + // Test the error chain + assert!(err.is_chain::()); + assert!(err.find_chain_cause::().is_some()); + assert!(err.find_chain_cause::().is_some()); + assert!(err.find_chain_cause::().is_some()); + } + + #[test] + fn test_error_root_cause() { + let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); + + str_context!(WrapperError); + let err = Result::<(), _>::Err(io_error.into()) + .context(WrapperError("wrapper".into())) + .unwrap_err(); + + let root = err.root_cause().unwrap(); + assert!(root.is_chain::()); + } + + #[test] + fn test_error_display_and_debug() { + str_context!(CustomError); + let err = Error::new( + CustomError("test error".into()), + None, + Some("src/lib.rs:100".into()), + ); + + // Test Display formatting + assert_eq!(format!("{}", err), "test error"); + + // Test alternate Display formatting + assert_eq!(format!("{:#}", err), "test error"); + + // Test Debug formatting + let debug_output = format!("{:?}", err); + assert!(debug_output.contains("test error")); + assert!(debug_output.contains("src/lib.rs:100")); + } + + #[test] + fn test_error_annotation() { + let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); + let err = Result::<(), _>::Err(io_error.into()) + .annotate() + .unwrap_err(); + + assert!(err.source().is_some()); + err.source() + .unwrap() + .downcast_inner_ref::() + .unwrap(); + } + + #[test] + fn test_map_context() { + let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); + + str_context!(MappedError); + let err = Result::<(), _>::Err(io_error.into()) + .map_context(|e| MappedError(format!("Mapped: {}", e))) + .unwrap_err(); + + assert!(err.is_chain::()); + assert!(err.find_chain_cause::().is_some()); + } + + #[test] + fn test_error_downcasting() { + str_context!(OriginalError); + let original = Error::new(OriginalError("test".into()), None, None); + + let error: Box = Box::new(original); + + // Test downcast_chain_ref + assert!(error.is_chain::()); + assert!(error.downcast_chain_ref::().is_some()); + + // Test downcast_inner_ref + let inner = error.downcast_inner_ref::(); + assert!(inner.is_some()); + } + + #[derive(Debug, Clone)] + enum TestErrorKind { + Basic(String), + Complex { message: String }, + } + + impl Display for TestErrorKind { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + TestErrorKind::Basic(msg) => write!(f, "Basic error: {}", msg), + TestErrorKind::Complex { message } => write!(f, "Complex error: {}", message), + } + } + } + + #[test] + fn test_err_kind_macro() { + err_kind!(TestError, TestErrorKind); + + let err = TestError::from(TestErrorKind::Basic("test".into())); + assert!(matches!(err.kind(), TestErrorKind::Basic(_))); + // The annotated error should display "(passed error)" even in a chain + assert_eq!(format!("{}", err), "Basic error: test"); + assert_eq!(format!("{:?}", err), "Basic(\"test\")"); + + let complex_err = TestError::from(TestErrorKind::Complex { + message: "test".into(), + }); + assert!(matches!(complex_err.kind(), TestErrorKind::Complex { .. })); + // The annotated error should display "(passed error)" even in a chain + assert_eq!(format!("{}", complex_err), "Complex error: test"); + assert_eq!( + format!("{:?}", complex_err), + "Complex { message: \"test\" }" + ); + } + #[test] + fn test_annotated_error_display_and_debug() { + let annotated = AnnotatedError(()); + + // Test Display formatting + assert_eq!(format!("{}", annotated), "(passed error)"); + + // Test Debug formatting + assert_eq!(format!("{:?}", annotated), "(passed error)"); + + // Test with error chain + let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); + let err = Result::<(), _>::Err(io_error.into()) + .annotate() + .unwrap_err(); + + // The annotated error should display "(passed error)" even in a chain + assert_eq!(format!("{}", err), "(passed error)"); + assert!(format!("{:?}", err).contains("(passed error)")); + + // Verify the error chain is preserved + assert!(err.source().is_some()); + assert!(err.source().unwrap().is_chain::()); + } + + // Helper error types for testing + #[derive(Debug)] + struct TestError(String); + + impl std::fmt::Display for TestError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } + } + + impl std::error::Error for TestError {} + + #[test] + fn test_downcast_chain_operations() { + // Create a test error chain + let original_error = Error::new( + TestError("test message".to_string()), + None, + Some("test location".to_string()), + ); + + // Test is_chain + assert!(original_error.is_chain::()); + assert!(!original_error.is_chain::()); + + // Test downcast_chain_ref + let downcast_ref = original_error.downcast_chain_ref::(); + assert!(downcast_ref.is_some()); + let downcast_kind = downcast_ref.unwrap().kind(); + assert_eq!(format!("{}", downcast_kind), "test message"); + assert_eq!( + format!("{:?}", downcast_kind), + "TestError(\"test message\")" + ); + + // Test invalid downcast_chain_ref + let invalid_downcast = original_error.downcast_chain_ref::(); + assert!(invalid_downcast.is_none()); + + // Test downcast_chain_mut + let mut mutable_error = original_error; + let downcast_mut = mutable_error.downcast_chain_mut::(); + assert!(downcast_mut.is_some()); + assert_eq!(downcast_mut.unwrap().kind().0, "test message"); + + // Test invalid downcast_chain_mut + let invalid_downcast_mut = mutable_error.downcast_chain_mut::(); + assert!(invalid_downcast_mut.is_none()); + } + + #[test] + fn test_downcast_inner_operations() { + // Create a test error + let mut error = Error::new( + TestError("inner test".to_string()), + None, + Some("test location".to_string()), + ); + + // Test downcast_inner_ref + let inner_ref = error.downcast_inner_ref::(); + assert!(inner_ref.is_some()); + assert_eq!(inner_ref.unwrap().0, "inner test"); + // Test invalid downcast_inner_ref + let invalid_inner = error.downcast_inner_ref::(); + assert!(invalid_inner.is_none()); + + // Test downcast_inner_mut + let inner_mut = error.downcast_inner_mut::(); + assert!(inner_mut.is_some()); + assert_eq!(inner_mut.unwrap().0, "inner test"); + + // Test invalid downcast_inner_mut + let invalid_inner_mut = error.downcast_inner_mut::(); + assert!(invalid_inner_mut.is_none()); + } + + #[test] + fn test_error_down_for_dyn_error() { + // Create a boxed error + let error: Box = Box::new(Error::new( + TestError("dyn test".to_string()), + None, + Some("test location".to_string()), + )); + + // Test is_chain through trait object + assert!(error.is_chain::()); + assert!(!error.is_chain::()); + + // Test downcast_chain_ref through trait object + let chain_ref = error.downcast_chain_ref::(); + assert!(chain_ref.is_some()); + assert_eq!(chain_ref.unwrap().kind().0, "dyn test"); + + // Test downcast_inner_ref through trait object + let inner_ref = error.downcast_inner_ref::(); + assert!(inner_ref.is_some()); + assert_eq!(inner_ref.unwrap().0, "dyn test"); + } + + #[test] + fn test_error_down_with_sync_send() { + // Create a boxed error with Send + Sync + let error: Box = Box::new(Error::new( + TestError("sync test".to_string()), + None, + Some("test location".to_string()), + )); + + // Test operations on Send + Sync error + assert!(error.is_chain::()); + assert!(error.downcast_chain_ref::().is_some()); + assert!(error.downcast_inner_ref::().is_some()); + + // Test invalid downcasts + assert!(!error.is_chain::()); + assert!(error.downcast_chain_ref::().is_none()); + assert!(error.downcast_inner_ref::().is_none()); + } +} diff --git a/tests/test_basic.rs b/tests/test_basic.rs new file mode 100644 index 0000000..bbc9f16 --- /dev/null +++ b/tests/test_basic.rs @@ -0,0 +1,33 @@ +use chainerror::Context; + +#[test] +fn test_basic() { + use std::path::PathBuf; + type BoxedError = Box; + 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::>(); + 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!(); + } +} diff --git a/tests/test_iter.rs b/tests/test_iter.rs new file mode 100644 index 0000000..9023078 --- /dev/null +++ b/tests/test_iter.rs @@ -0,0 +1,94 @@ +use chainerror::Context; +use std::error::Error; +use std::io; + +#[test] +fn test_iter() -> Result<(), Box> { + use std::fmt::Write; + 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 mut res = String::new(); + + for e in err.iter() { + write!(res, "{}", e)?; + } + assert_eq!(res, "654321entity not found"); + + let io_error: Option<&io::Error> = err + .iter() + .filter_map(::downcast_ref::) + .next(); + + assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound); + + Ok(()) +} + +#[test] +fn test_iter_alternate() -> Result<(), Box> { + 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 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(::downcast_ref::) + .next(); + + assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound); + + Ok(()) +} + +#[test] +fn test_find_cause() -> Result<(), Box> { + 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::(); + + assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound); + + Ok(()) +} + +#[test] +fn test_root_cause() -> Result<(), Box> { + 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 err: Option<&(dyn std::error::Error + 'static)> = err.root_cause(); + let io_error: Option<&io::Error> = err.and_then(::downcast_ref::); + + assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound); + + Ok(()) +} diff --git a/theme/book.js b/theme/book.js deleted file mode 100644 index 8c6946d..0000000 --- a/theme/book.js +++ /dev/null @@ -1,611 +0,0 @@ -"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] = "" + "\n" + lines[n].replace(/(\s*)# ?/, "$1") + ""; - } - else { - lines[n] = "" + lines[n].replace(/(\s*)# ?/, "$1") + "\n" + ""; - } - 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 = ""; - - // 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 = ''; - - 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 = ''; - 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 }); -})();