diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 5cde165..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 7dff904..0000000 --- a/.github/workflows/coverage.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: coverage - -on: - # Trigger the workflow on push or pull request, - # but only for the master branch - push: - branches: - - master - pull_request: - branches: - - master - release: - types: - - created - - -jobs: - coverage: - runs-on: ubuntu-latest - env: - CARGO_TERM_COLOR: always - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master - with: - target: x86_64-unknown-linux-gnu - toolchain: nightly - components: llvm-tools-preview - - name: Install cargo-llvm-cov - uses: taiki-e/install-action@cargo-llvm-cov - - name: Generate code coverage - run: cargo +nightly llvm-cov --all-features --workspace --codecov --doctests --output-path codecov.json - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos - files: codecov.json - fail_ci_if_error: true diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml deleted file mode 100644 index 7062679..0000000 --- a/.github/workflows/gh-pages.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: github pages - -on: - push: - tags: - - '*' - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Build mdbook - run: cargo install mdbook - - - name: Build cargo-readme - run: cargo install cargo-readme - - - name: Build README.md - run: cargo readme > README.md - - - name: Build - run: mdbook build - - - name: Deploy - uses: peaceiris/actions-gh-pages@v3 - with: - deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} - publish_dir: ./book diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml deleted file mode 100644 index 3fc2545..0000000 --- a/.github/workflows/rust.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Rust - -on: - # Trigger the workflow on push or pull request, - # but only for the master branch - push: - branches: - - master - pull_request: - branches: - - master - release: - types: - - created - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - version: - - 1.54.0 - - stable - - beta - - nightly - steps: - - uses: actions/checkout@v1 - - name: Install toolchain - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ matrix.version }} - profile: minimal - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose - - name: Build --all-features - run: cargo build --verbose --all-features - - name: Run tests --all-features - run: cargo test --verbose --all-features - - fmt: - name: cargo fmt - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: dtolnay/rust-toolchain@master - with: - components: rustfmt - toolchain: stable - profile: minimal - override: true - - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check - - clippy: - name: cargo clippy - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: dtolnay/rust-toolchain@master - with: - components: clippy - toolchain: stable - profile: minimal - override: true - - uses: actions-rs/cargo@v1 - with: - command: clippy - args: -- -D warnings diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b39f048 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +language: rust + +branches: + except: + - gh-pages + +rust: +- stable +- nightly + +os: +- linux +- windows + +script: +- cargo build --all +- cargo test --all + +matrix: + allow_failures: + - rust: nightly + fast_finish: true diff --git a/Cargo.toml b/Cargo.toml index 59b8808..4b4d99d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "chainerror" -version = "1.0.0" +version = "0.4.0" authors = ["Harald Hoyer "] edition = "2018" -license = "MIT OR Apache-2.0" +license = "MIT/Apache-2.0" documentation = "https://docs.rs/chainerror" homepage = "https://haraldh.github.io/chainerror/" repository = "https://github.com/haraldh/chainerror" @@ -16,11 +16,10 @@ exclude = [ ".gitignore", "examples/*", "booksrc/*", "book.toml", "theme/*", "git-deploy-branch.sh", ".travis.yml" ] [badges] -# 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" } +travis-ci = { repository = "haraldh/chainerror" } -[package.metadata.docs.rs] -cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] +[features] +default = [ ] +no-fileline = [] +display-cause = [] +no-debug-cause = [] diff --git a/README.md b/README.md index 3a46567..b24834d 100644 --- a/README.md +++ b/README.md @@ -1,186 +1,129 @@ -[![Crate](https://img.shields.io/crates/v/chainerror.svg)](https://crates.io/crates/chainerror) -[![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/chainerror/) -[![Coverage Status](https://codecov.io/gh/haraldh/chainerror/branch/master/graph/badge.svg?token=HGLJFGA11B)](https://codecov.io/gh/haraldh/chainerror) -![Maintenance](https://img.shields.io/badge/maintenance-activly--developed-brightgreen.svg) - # chainerror -`chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your +[![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/) + +`chainerror` provides an error backtrace like `failure` without doing a real backtrace, so even after you `strip` your binaries, you still have the error backtrace. -Having nested function returning errors, the output doesn't tell where the error originates from. - -```rust -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)?; - // do stuff, return other errors - Ok(()) -} - -fn process_config_file() -> Result<(), BoxedError> { - // do stuff, return other errors - let _buf = read_config_file("foo.txt".into())?; - // do stuff, return other errors - Ok(()) -} - -fn main() { - if let Err(e) = process_config_file() { - eprintln!("Error:\n{:?}", e); - } -} -``` - -This gives the output: -```console -Error: -Os { code: 2, kind: NotFound, message: "No such file or directory" } -``` -and you have no idea where it comes from. - - -With `chainerror`, you can supply a context and get a nice error backtrace: - -```rust -use chainerror::Context as _; -use std::path::PathBuf; - -type BoxedError = Box; -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! +`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. + Debug information is worth it! -## Multiple Output Formats +Now continue reading the +[Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) -`chainerror` supports multiple output formats, which can be selected with the different format specifiers: +## Example: +Output: -* `{}`: Display -```console -func1 error calling func2 -``` +~~~ +$ cargo run -q --example example +Main Error Report: 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 -``` +Error reported by Func2Error: func2 error: calling func3 -* `{:?}`: Debug -```console -examples/example.rs:50:13: func1 error calling func2 +The root cause was: std::io::Error: Kind( + NotFound +) + +Debug Error: +examples/example.rs:45: func1 error calling func2 Caused by: -examples/example.rs:25:13: Func2Error(func2 error: calling func3) +examples/example.rs:20: 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' +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; -* `{:#?}`: 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, - ), - ), - }, - ), - }, - ), - }, - ), +fn do_some_io() -> Result<(), Box> { + Err(io::Error::from(io::ErrorKind::NotFound))?; + Ok(()) } -``` -## Tutorial +fn func3() -> Result<(), Box> { + let filename = "foo.txt"; + do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + Ok(()) +} -Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) +derive_str_cherr!(Func2Error); -## License +fn func2() -> ChainResult<(), Func2Error> { + func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; + Ok(()) +} -Licensed under either of +enum Func1Error { + Func2, + IO(String), +} -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or ) +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), + } + } +} -at your option. +impl ::std::fmt::Debug for Func1Error { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "{}", self) + } +} -### Contribution +fn func1() -> ChainResult<(), Func1Error> { + func2().map_err(|e| cherr!(e, Func1Error::Func2))?; + let filename = String::from("bar.txt"); + do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; + Ok(()) +} -Unless you explicitly state otherwise, any contribution intentionally -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. +fn main() { + if let Err(e) = func1() { + match e.kind() { + Func1Error::Func2 => eprintln!("Main Error Report: func1 error calling func2"), + Func1Error::IO(filename) => { + eprintln!("Main Error Report: func1 error reading '{}'", filename) + } + } + + if let Some(e) = e.find_chain_cause::() { + eprintln!("\nError reported by Func2Error: {}", e) + } + + if let Some(e) = e.root_cause() { + let ioerror = e.downcast_ref::().unwrap(); + eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); + } + + eprintln!("\nDebug Error:\n{:?}", e); + } +} + +~~~ + +## Features + +`no-fileline` +: completely turn off storing filename and line + +`display-cause` +: turn on printing a backtrace of the errors in `Display` + +`no-debug-cause` +: turn off printing a backtrace of the errors in `Debug` diff --git a/booksrc/LICENSE-APACHE b/booksrc/LICENSE-APACHE deleted file mode 120000 index 965b606..0000000 --- a/booksrc/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-APACHE \ No newline at end of file diff --git a/booksrc/LICENSE-MIT b/booksrc/LICENSE-MIT deleted file mode 120000 index 76219eb..0000000 --- a/booksrc/LICENSE-MIT +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-MIT \ No newline at end of file diff --git a/booksrc/SUMMARY.md b/booksrc/SUMMARY.md index 6bbc3b4..ac070ee 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) -- [More Information](tutorial4.md) +- [Saving coding chars](tutorial4.md) - [The source() of Errors](tutorial5.md) - [Downcast the Errors](tutorial6.md) - [The root cause of all Errors](tutorial7.md) @@ -13,8 +13,5 @@ - [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) +[The End](end.md) \ No newline at end of file diff --git a/booksrc/tutorial1.md b/booksrc/tutorial1.md index 57a502f..cf1d9d0 100644 --- a/booksrc/tutorial1.md +++ b/booksrc/tutorial1.md @@ -9,7 +9,7 @@ this only prints out the last `Error`. ~~~ -Error: "func1 error" +Error: StringError("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 5547954..7feaf6e 100644 --- a/booksrc/tutorial10.md +++ b/booksrc/tutorial10.md @@ -1,21 +1,26 @@ # 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`. -Only returning `Func1ErrorKind` in `func1()` now let us get rid of `Result<(), Box>` and we can +Not using `String` errors anymore, the `cherr!()` macro seen in the beginning of +the tutorial has to be used again. + +Only returning `Func1ErrorKind` in `func1()` now let us get rid of `Result<(), Box>` and we can use `ChainResult<(), Func1ErrorKind>`. -In `main` we can now directly use the methods of `chainerror::Error` without downcasting the error first. +In `main` we can now directly use the methods of `ChainError` without downcasting the error first. -Also, a nice `match` on `chainerror::Error.kind()` is now possible, which returns `&T`, meaning `&Func1ErrorKind` here. +Also a nice `match` on `ChainError.kind()` is now possible, which returns `&T`, meaning +`&Func1ErrorKind` here. ~~~rust -{{#include ../examples/tutorial10.rs}} +use crate::chainerror::*; +{{#include ../examples/tutorial10.rs:2:}} # #[allow(dead_code)] # mod chainerror { -{{#rustdoc_include ../src/lib.rs:-1}} +{{#includecomment ../src/lib.rs}} # } -~~~ +~~~ \ No newline at end of file diff --git a/booksrc/tutorial11.md b/booksrc/tutorial11.md index 7d37814..c679792 100644 --- a/booksrc/tutorial11.md +++ b/booksrc/tutorial11.md @@ -1,6 +1,6 @@ # Debug for the ErrorKind -One small improvement is to fix the debug output of +One small improvement at the end of the tutorial is to fix the debug output of `Func1ErrorKind`. As you probably noticed, the output doesn't say much of the enum. ~~~ @@ -21,13 +21,14 @@ which gives us a lot more detail. To create your own Errors, you might find [crates](https://crates.io) which create enum `Display+Debug` via derive macros. -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 -{{#include ../examples/tutorial11.rs}} +use crate::chainerror::*; +{{#include ../examples/tutorial11.rs:2:}} # #[allow(dead_code)] # mod chainerror { -{{#rustdoc_include ../src/lib.rs:-1}} +{{#includecomment ../src/lib.rs}} # } ~~~ diff --git a/booksrc/tutorial12.md b/booksrc/tutorial12.md deleted file mode 100644 index 037803b..0000000 --- a/booksrc/tutorial12.md +++ /dev/null @@ -1,11 +0,0 @@ -# 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 deleted file mode 100644 index 0ed9db8..0000000 --- a/booksrc/tutorial13.md +++ /dev/null @@ -1,18 +0,0 @@ -# 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 deleted file mode 100644 index 1bdb97b..0000000 --- a/booksrc/tutorial14.md +++ /dev/null @@ -1,9 +0,0 @@ -# Going back to std - -Not using `chainerror` and going full `std` would look like this: - -Btw, the code size is bigger than using `chainerror` :-) - -~~~rust -{{#include ../examples/tutorial14.rs}} -~~~ diff --git a/booksrc/tutorial2.md b/booksrc/tutorial2.md index a330c1a..86e2726 100644 --- a/booksrc/tutorial2.md +++ b/booksrc/tutorial2.md @@ -1,15 +1,16 @@ # Simple Chained String Errors -With relatively small changes and the help of the `context()` method of the `chainerror` crate -the `&str` errors are now chained together. +With relatively small changes and the help of the `cherr!` macro of the `chainerror` crate +the `String` errors are now chained together. Press the play button in the upper right corner and see the nice debug output. ~~~rust -{{#include ../examples/tutorial2.rs}} +use crate::chainerror::*; +{{#include ../examples/tutorial2.rs:2:}} # #[allow(dead_code)] # mod chainerror { -{{#rustdoc_include ../src/lib.rs:-1}} +{{#includecomment ../src/lib.rs}} # } ~~~ @@ -19,13 +20,14 @@ Press the play button in the upper right corner and see the nice debug output. {{#include ../examples/tutorial2.rs:13:15}} ~~~ -The function `context(newerror)` stores `olderror` as the source/cause of `newerror` -along with the `Location` of the `context()` call and returns `Err(newerror)`. +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`. -`?` then returns the inner error applying `.into()`, so that we -again have a `Err(Box)` as a result. +`Err()?` then returns the inner error applying `.into()`, so that we +again have a `Err(Box)` as a result. -The `Debug` implementation of `chainerror::Error` (which is returned by `context()`) +The `Debug` implementation of `ChainError` (which is returned by `cherr!()`) prints the `Debug` of `T` prefixed with the stored filename and line number. -`chainerror::Error` in our case is `chainerror::Error<&str>`. +`ChainError` in our case is `ChainError`. \ No newline at end of file diff --git a/booksrc/tutorial3.md b/booksrc/tutorial3.md index f6c26ac..b86e0ad 100644 --- a/booksrc/tutorial3.md +++ b/booksrc/tutorial3.md @@ -1,12 +1,13 @@ # Mapping Errors -Now let's get more rust idiomatic by using `.context()` directly on the previous `Result`. +Now let's get more rust idiomatic by using `.map_err()`. ~~~rust -{{#include ../examples/tutorial3.rs}} +use crate::chainerror::*; +{{#include ../examples/tutorial3.rs:2:}} # #[allow(dead_code)] # mod chainerror { -{{#rustdoc_include ../src/lib.rs:-1}} +{{#includecomment ../src/lib.rs}} # } ~~~ @@ -14,16 +15,16 @@ If you compare the output to the previous example, you will see, that: ~~~ -Error: examples/tutorial2.rs:20:16: func1 error +Error: src/main.rs:19: "func1 error" ~~~ changed to just: ~~~ -examples/tutorial3.rs:17:13: func1 error +src/main.rs:16: "func1 error" ~~~ This is, because we caught the error of `func1()` in `main()` and print it out ourselves. We can now control, whether to output in `Debug` or `Display` mode. -Maybe depending on `--debug` as a CLI argument. +Maybe depending on `--debug` as a CLI argument. \ No newline at end of file diff --git a/booksrc/tutorial4.md b/booksrc/tutorial4.md index f717706..95a6fab 100644 --- a/booksrc/tutorial4.md +++ b/booksrc/tutorial4.md @@ -1,12 +1,18 @@ -# More information +# Saving coding chars -To give more context to the error, you want to use `format!` -to extend the information in the context string. +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!()`. ~~~rust -{{#include ../examples/tutorial4.rs}} +use crate::chainerror::*; +{{#include ../examples/tutorial4.rs:2:}} # #[allow(dead_code)] # mod chainerror { -{{#rustdoc_include ../src/lib.rs:-1}} +{{#includecomment ../src/lib.rs}} # } -~~~ +~~~ \ No newline at end of file diff --git a/booksrc/tutorial5.md b/booksrc/tutorial5.md index 57932fa..d98f09e 100644 --- a/booksrc/tutorial5.md +++ b/booksrc/tutorial5.md @@ -4,14 +4,16 @@ Sometimes you want to inspect the `source()` of an `Error`. `chainerror` implements `std::error::Error::source()`, so you can get the cause of an error. ~~~rust -{{#include ../examples/tutorial5.rs}} +use crate::chainerror::*; +{{#include ../examples/tutorial5.rs:2:}} # #[allow(dead_code)] # mod chainerror { -{{#rustdoc_include ../src/lib.rs:-1}} +{{#includecomment ../src/lib.rs}} # } ~~~ Note, that because we changed the output of the error in `main()` from `Debug` to `Display`, we don't see the error backtrace with filename and line number. -To use the `Display` backtrace, you have to use the alternative display format output `{:#}`. +To enable the `Display` backtrace, you have to enable the feature `display-cause` for `chainerror`. + \ No newline at end of file diff --git a/booksrc/tutorial6.md b/booksrc/tutorial6.md index 72fe33a..8154874 100644 --- a/booksrc/tutorial6.md +++ b/booksrc/tutorial6.md @@ -11,9 +11,10 @@ pub fn downcast_mut(&mut self) -> Option<&mut T> This is how it looks like, when using those: ~~~rust -{{#include ../examples/tutorial6.rs}} +use crate::chainerror::*; +{{#include ../examples/tutorial6.rs:2:}} # #[allow(dead_code)] # mod chainerror { -{{#rustdoc_include ../src/lib.rs:-1}} +{{#includecomment ../src/lib.rs}} # } ~~~ \ No newline at end of file diff --git a/booksrc/tutorial7.md b/booksrc/tutorial7.md index 90665f7..4ddd17f 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::Error> -fn downcast_chain_mut(&mut self) -> Option<&mut chainerror::Error> +fn downcast_chain_ref(&self) -> Option<&ChainError> +fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> fn root_cause(&self) -> Option<&(dyn Error + 'static)> fn find_cause(&self) -> Option<&U> -fn find_chain_cause(&self) -> Option<&chainerror::Error> +fn find_chain_cause(&self) -> Option<&ChainError> fn kind<'a>(&'a self) -> &'a T ~~~ -Using `downcast_chain_ref::()` gives a `chainerror::Error`, which can be used +Using `downcast_chain_ref::()` gives a `ChainError`, which can be used to call `.find_cause::()`. ~~~rust,ignore @@ -27,9 +27,10 @@ or to use `.root_cause()`, which of course can be of any type implementing `std: ~~~ ~~~rust -{{#include ../examples/tutorial7.rs}} +use crate::chainerror::*; +{{#include ../examples/tutorial7.rs:2:}} # #[allow(dead_code)] # mod chainerror { -{{#rustdoc_include ../src/lib.rs:-1}} +{{#includecomment ../src/lib.rs}} # } ~~~ \ No newline at end of file diff --git a/booksrc/tutorial8.md b/booksrc/tutorial8.md index 17070fe..ce123e1 100644 --- a/booksrc/tutorial8.md +++ b/booksrc/tutorial8.md @@ -1,14 +1,14 @@ # Finding an Error cause -To distinguish the errors occurring in various places, we can define named string errors with the +To distinguish the errors occuring in various places, we can define named string errors with the "new type" pattern. ~~~rust,ignore -chainerror::str_context!(Func2Error); -chainerror::str_context!(Func1Error); +derive_str_cherr!(Func2Error); +derive_str_cherr!(Func1Error); ~~~ -Instead of `chainerror::Error` we now have `struct Func1Error(String)` and `chainerror::Error`. +Instead of `ChainError` we now have `struct Func1Error(String)` and `ChainError`. In the `main` function you can see, how we can match the different errors. @@ -18,14 +18,15 @@ 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::Error` implementation detail. +hiding the `ChainError` implementation detail. ~~~rust -{{#include ../examples/tutorial8.rs}} +use crate::chainerror::*; +{{#include ../examples/tutorial8.rs:2:}} # #[allow(dead_code)] # mod chainerror { -{{#rustdoc_include ../src/lib.rs:-1}} +{{#includecomment ../src/lib.rs}} # } -~~~ +~~~ \ No newline at end of file diff --git a/booksrc/tutorial9.md b/booksrc/tutorial9.md index f66dd0e..e9107ca 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,9 +25,10 @@ but this is not valid rust code, so we end up doing it the hard way. In the next chapter, we will see, how to solve this more elegantly. ~~~rust -{{#include ../examples/tutorial9.rs}} +use crate::chainerror::*; +{{#include ../examples/tutorial9.rs:2:}} # #[allow(dead_code)] # mod chainerror { -{{#rustdoc_include ../src/lib.rs:-1}} +{{#includecomment ../src/lib.rs}} # } ~~~ diff --git a/examples/example.rs b/examples/example.rs index 82c073f..3d97f70 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -1,28 +1,23 @@ -use chainerror::Context as _; +use chainerror::*; 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 func4() -> Result<(), Box> { +fn func3() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().context(format!("Error reading '{}'", filename))?; + do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; Ok(()) } -fn func3() -> Result<(), Box> { - func4().annotate()?; - Ok(()) -} +derive_str_cherr!(Func2Error); -chainerror::str_context!(Func2Error); - -fn func2() -> chainerror::Result<(), Func2Error> { - func3().context(Func2Error::new("func2 error: calling func3"))?; +fn func2() -> ChainResult<(), Func2Error> { + func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; Ok(()) } @@ -31,8 +26,8 @@ enum Func1Error { IO(String), } -impl fmt::Display for Func1Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +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), @@ -40,29 +35,21 @@ impl fmt::Display for Func1Error { } } -impl fmt::Debug for Func1Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl ::std::fmt::Debug for Func1Error { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "{}", self) } } -fn func1() -> chainerror::Result<(), Func1Error> { - func2().context(Func1Error::Func2)?; +fn func1() -> ChainResult<(), Func1Error> { + func2().map_err(|e| cherr!(e, Func1Error::Func2))?; let filename = String::from("bar.txt"); - do_some_io().context(Func1Error::IO(filename))?; + do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; Ok(()) } 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) => { @@ -78,5 +65,7 @@ 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 08ca3e8..87778d9 100644 --- a/examples/tutorial1.rs +++ b/examples/tutorial1.rs @@ -1,28 +1,26 @@ -#![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 3e0af4a..b22d0b7 100644 --- a/examples/tutorial10.rs +++ b/examples/tutorial10.rs @@ -1,18 +1,18 @@ -use chainerror::Context as _; - +use chainerror::*; 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(()) } -chainerror::str_context!(Func2Error); +derive_str_cherr!(Func2Error); -fn func2() -> Result<(), Box> { +fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; + do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; Ok(()) } @@ -32,14 +32,14 @@ impl ::std::fmt::Display for Func1ErrorKind { } impl ::std::error::Error for Func1ErrorKind {} -fn func1() -> chainerror::Result<(), Func1ErrorKind> { - func2().context(Func1ErrorKind::Func2)?; +fn func1() -> ChainResult<(), Func1ErrorKind> { + func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; let filename = String::from("bar.txt"); - do_some_io().context(Func1ErrorKind::IO(filename))?; + do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO(filename)))?; Ok(()) } -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,8 +53,6 @@ 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 abbca94..46cf00c 100644 --- a/examples/tutorial11.rs +++ b/examples/tutorial11.rs @@ -1,18 +1,18 @@ -use chainerror::Context as _; - +use chainerror::*; 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(()) } -chainerror::str_context!(Func2Error); +derive_str_cherr!(Func2Error); -fn func2() -> Result<(), Box> { +fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; + do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; Ok(()) } @@ -38,14 +38,14 @@ impl ::std::fmt::Debug for Func1ErrorKind { impl ::std::error::Error for Func1ErrorKind {} -fn func1() -> chainerror::Result<(), Func1ErrorKind> { - func2().context(Func1ErrorKind::Func2)?; +fn func1() -> ChainResult<(), Func1ErrorKind> { + func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; let filename = String::from("bar.txt"); - do_some_io().context(Func1ErrorKind::IO(filename))?; + do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO(filename)))?; Ok(()) } -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,8 +59,6 @@ fn main() -> Result<(), Box> { } eprintln!("\nDebug Error:\n{:?}", e); - - std::process::exit(1); } Ok(()) } diff --git a/examples/tutorial12.rs b/examples/tutorial12.rs deleted file mode 100644 index caa8fca..0000000 --- a/examples/tutorial12.rs +++ /dev/null @@ -1,77 +0,0 @@ -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 deleted file mode 100644 index 0d50d68..0000000 --- a/examples/tutorial13.rs +++ /dev/null @@ -1,83 +0,0 @@ -#![allow(clippy::single_match)] -#![allow(clippy::redundant_pattern_matching)] - -pub mod mycrate { - use chainerror::Context as _; - - use std::io; - - fn do_some_io() -> std::result::Result<(), Box> { - 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 deleted file mode 100644 index 5ec216e..0000000 --- a/examples/tutorial14.rs +++ /dev/null @@ -1,222 +0,0 @@ -#![allow(clippy::single_match)] -#![allow(clippy::redundant_pattern_matching)] - -pub mod mycrate { - use std::error::Error as StdError; - - use self::func2mod::{do_some_io, func2}; - - pub mod func2mod { - use std::error::Error as StdError; - use std::io; - - pub enum ErrorKind { - IO(String), - } - - impl std::fmt::Display for ErrorKind { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result { - match self { - ErrorKind::IO(s) => std::fmt::Display::fmt(s, f), - } - } - } - - impl std::fmt::Debug for ErrorKind { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result { - match self { - ErrorKind::IO(s) => std::fmt::Display::fmt(s, f), - } - } - } - - macro_rules! mcontext { - ( $k:expr ) => {{ - |e| { - Error( - $k, - Some(Box::from(e)), - Some(concat!(file!(), ":", line!(), ": ")), - ) - } - }}; - } - - pub struct Error( - ErrorKind, - Option>, - 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 deleted file mode 100644 index 42355b3..0000000 --- a/examples/tutorial15.rs +++ /dev/null @@ -1,140 +0,0 @@ -#![allow(clippy::single_match)] -#![allow(clippy::redundant_pattern_matching)] - -pub mod mycrate { - use chainerror::{Context as _, ErrorDown as _}; - - use std::io; - - fn do_some_io(_f: &str) -> std::result::Result<(), io::Error> { - Err(io::Error::from(io::ErrorKind::NotFound))?; - Ok(()) - } - - chainerror::str_context!(Func2Error); - - fn func2() -> std::result::Result<(), Box> { - 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 96742ad..d75f0a4 100644 --- a/examples/tutorial2.rs +++ b/examples/tutorial2.rs @@ -1,27 +1,28 @@ -use chainerror::Context as _; +use chainerror::*; 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(e).context("func2 error")?; + Err(cherr!(e, "func2 error"))?; } Ok(()) } -fn func1() -> Result<(), Box> { +fn func1() -> Result<(), Box> { if let Err(e) = func2() { - Err(e).context("func1 error")?; + Err(cherr!(e, "func1 error"))?; } Ok(()) } -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { func1() } diff --git a/examples/tutorial3.rs b/examples/tutorial3.rs index 62a44eb..6915a62 100644 --- a/examples/tutorial3.rs +++ b/examples/tutorial3.rs @@ -1,27 +1,27 @@ -use chainerror::Context as _; +use chainerror::*; 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().context("func2 error")?; +fn func2() -> Result<(), Box> { + do_some_io().map_err(|e| cherr!(e, "func2 error"))?; Ok(()) } -fn func1() -> Result<(), Box> { - func2().context("func1 error")?; +fn func1() -> Result<(), Box> { + func2().map_err(|e| cherr!(e, "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 3dea51c..de51745 100644 --- a/examples/tutorial4.rs +++ b/examples/tutorial4.rs @@ -1,28 +1,27 @@ -use chainerror::Context as _; - +use chainerror::*; 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().context(format!("Error reading '{}'", filename))?; + do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; Ok(()) } -fn func1() -> Result<(), Box> { - func2().context("func1 error")?; +fn func1() -> Result<(), Box> { + func2().map_err(mstrerr!("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 d9fe708..e6f36f3 100644 --- a/examples/tutorial5.rs +++ b/examples/tutorial5.rs @@ -1,33 +1,32 @@ -use chainerror::Context as _; - +use chainerror::*; 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().context(format!("Error reading '{}'", filename))?; + do_some_io().map_err(mstrerr!("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).context("func1 error")?; + Err(e).map_err(mstrerr!("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 5d81310..aca48b4 100644 --- a/examples/tutorial6.rs +++ b/examples/tutorial6.rs @@ -1,31 +1,28 @@ -#![allow(clippy::single_match)] -#![allow(clippy::redundant_pattern_matching)] - -use chainerror::Context as _; - +use chainerror::*; 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().context(format!("Error reading '{}'", filename))?; + do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; Ok(()) } -fn func1() -> Result<(), Box> { - func2().context("func1 error")?; +fn func1() -> Result<(), Box> { + func2().map_err(mstrerr!("func1 error"))?; Ok(()) } -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { if let Err(e) = func1() { eprintln!("Error: {}", e); - let mut s: &(dyn Error) = e.as_ref(); + let mut s = e.as_ref(); while let Some(c) = s.source() { if let Some(ioerror) = c.downcast_ref::() { eprintln!("caused by: std::io::Error: {}", ioerror); @@ -38,7 +35,6 @@ fn main() -> Result<(), Box> { } s = c; } - std::process::exit(1); } Ok(()) } diff --git a/examples/tutorial7.rs b/examples/tutorial7.rs index 214484c..3cb3d2c 100644 --- a/examples/tutorial7.rs +++ b/examples/tutorial7.rs @@ -1,28 +1,25 @@ -#![allow(clippy::single_match)] -#![allow(clippy::redundant_pattern_matching)] - -use chainerror::{Context as _, ErrorDown as _}; - +use chainerror::*; 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().context(format!("Error reading '{}'", filename))?; + do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; Ok(()) } -fn func1() -> Result<(), Box> { - func2().context("func1 error")?; +fn func1() -> Result<(), Box> { + func2().map_err(mstrerr!("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::() { @@ -39,7 +36,6 @@ 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 e32a449..831e6ed 100644 --- a/examples/tutorial8.rs +++ b/examples/tutorial8.rs @@ -1,34 +1,34 @@ -use chainerror::{Context as _, ErrorDown as _}; - +use chainerror::*; 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(()) } -chainerror::str_context!(Func2Error); +derive_str_cherr!(Func2Error); -fn func2() -> Result<(), Box> { +fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; + do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; Ok(()) } -chainerror::str_context!(Func1Error); +derive_str_cherr!(Func1Error); -fn func1() -> Result<(), Box> { - func2().context(Func1Error::new("func1 error"))?; +fn func1() -> Result<(), Box> { + func2().map_err(mstrerr!(Func1Error, "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,7 +36,6 @@ 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 bbbe810..f1954d3 100644 --- a/examples/tutorial9.rs +++ b/examples/tutorial9.rs @@ -1,41 +1,40 @@ -use chainerror::{Context as _, ErrorDown}; - +use chainerror::*; 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(()) } -chainerror::str_context!(Func2Error); +derive_str_cherr!(Func2Error); -fn func2() -> Result<(), Box> { +fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; + do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; Ok(()) } -chainerror::str_context!(Func1ErrorFunc2); -chainerror::str_context!(Func1ErrorIO); +derive_str_cherr!(Func1ErrorFunc2); +derive_str_cherr!(Func1ErrorIO); -fn func1() -> Result<(), Box> { - func2().context(Func1ErrorFunc2::new("func1 error calling func2"))?; +fn func1() -> Result<(), Box> { + func2().map_err(mstrerr!(Func1ErrorFunc2, "func1 error calling func2"))?; let filename = "bar.txt"; - do_some_io().context(Func1ErrorIO(format!("Error reading '{}'", filename)))?; + do_some_io().map_err(mstrerr!(Func1ErrorIO, "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 623523d..09df831 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,30 +1,199 @@ -#![doc = include_str!("../README.md")] -#![deny(clippy::all)] -#![allow(clippy::needless_doctest_main)] -#![deny(missing_docs)] +/*! + +`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!(); +# } +} +~~~ + +!*/ use std::any::TypeId; -use std::error::Error as StdError; -use std::fmt::{Debug, Display, Formatter}; -use std::panic::Location; +use std::error::Error; +use std::fmt::{Debug, Display, Formatter, Result}; -/// chains an inner error kind `T` with a causing error -pub struct Error { - occurrence: Option, +/** chains an inner error kind `T` with a causing error +**/ +pub struct ChainError { + #[cfg(not(feature = "no-fileline"))] + occurrence: Option<(u32, &'static str)>, kind: T, - error_cause: Option>, + error_cause: Option>, } /// convenience type alias -pub type Result = std::result::Result>; +pub type ChainResult = std::result::Result>; -impl Error { - /// Use the `context()` or `map_context()` Result methods instead of calling this directly - #[inline] +impl ChainError { + #[cfg(not(feature = "no-fileline"))] + /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly pub fn new( kind: T, - error_cause: Option>, - occurrence: Option, + error_cause: Option>, + occurrence: Option<(u32, &'static str)>, ) -> Self { Self { occurrence, @@ -33,573 +202,724 @@ impl Error { } } + #[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 StdError + 'static)> { - self.iter().last() - } - - /// 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() - } - - /// 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() - } - - /// 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() - } - - /// 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), + 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) } -} -/// 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>; + /** find the first error cause of type U, if any exists - /// Decorate the error just with the source `Location` - fn annotate(self) -> std::result::Result>; + # Examples - /// 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)") + ~~~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(()) } -} -impl Debug for AnnotatedError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "(passed error)") + derive_str_cherr!(Func2Error); + + fn func2() -> Result<(), Box> { + let filename = "foo.txt"; + do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + Ok(()) } -} -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()), - )), + derive_str_cherr!(Func1Error); + + fn func1() -> Result<(), Box> { + func2().map_err(mstrerr!(Func1Error, "func1 error"))?; + Ok(()) + } + + fn main() { + if let Err(e) = func1() { + if let Some(f1err) = e.downcast_chain_ref::() { + + assert!(f1err.find_cause::().is_some()); + + assert!(f1err.find_chain_cause::().is_some()); + } + # else { + # panic!(); + # } } + # else { + # unreachable!(); + # } } + ~~~ + **/ + pub fn find_cause(&self) -> Option<&U> { + let mut cause = self as &(dyn Error + 'static); + loop { + if cause.is::() { + return cause.downcast_ref::(); + } - #[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()), - )) + match cause.source() { + Some(c) => cause = c, + None => return None, } } } -} -/// An iterator over all error causes/sources -pub struct ErrorIter<'a> { - current: Option<&'a (dyn StdError + 'static)>, -} + /** find the first error cause of type ChainError, if any exists -impl<'a> Iterator for ErrorIter<'a> { - type Item = &'a (dyn StdError + 'static); + Same as `find_cause`, but hides the `ChainError` implementation internals - #[inline] - fn next(&mut self) -> Option { - let current = self.current; - self.current = self.current.and_then(StdError::source); - current + # 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, + } + } } -} -impl std::ops::Deref for Error { - type Target = T; + /** return a reference to T of `ChainError` - #[inline] - fn deref(&self) -> &Self::Target { + # Examples + + ~~~rust + # use crate::chainerror::*; + # use std::error::Error; + # use std::io; + # use std::result::Result; + # + fn do_some_io() -> Result<(), Box> { + Err(io::Error::from(io::ErrorKind::NotFound))?; + Ok(()) + } + + derive_str_cherr!(Func2Error); + + fn func2() -> Result<(), Box> { + let filename = "foo.txt"; + do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + Ok(()) + } + + #[derive(Debug)] + enum Func1ErrorKind { + Func2, + IO(String), + } + + // impl ::std::fmt::Display for Func1ErrorKind {…} + # impl ::std::fmt::Display for Func1ErrorKind { + # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + # match self { + # Func1ErrorKind::Func2 => write!(f, "func1 error calling func2"), + # Func1ErrorKind::IO(filename) => write!(f, "Error reading '{}'", filename), + # } + # } + # } + + fn func1() -> ChainResult<(), Func1ErrorKind> { + func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; + do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO("bar.txt".into())))?; + Ok(()) + } + + fn main() { + if let Err(e) = func1() { + match e.kind() { + Func1ErrorKind::Func2 => {}, + Func1ErrorKind::IO(filename) => panic!(), + } + } + # else { + # unreachable!(); + # } + } + ~~~ + + **/ + pub fn kind(&self) -> &T { &self.kind } } -/// Convenience trait to hide the [`Error`](Error) implementation internals -pub trait ErrorDown { - /// Test if of type `Error` +/** convenience trait to hide the `ChainError` implementation internals +**/ +pub trait ChainErrorDown { + /** test if of type `ChainError` + **/ fn is_chain(&self) -> bool; - /// Downcast to a reference of `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>; + /** 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>; } -impl ErrorDown for Error { - #[inline] +impl ChainErrorDown for ChainError { fn is_chain(&self) -> bool { TypeId::of::() == TypeId::of::() } - #[inline] - fn downcast_chain_ref(&self) -> Option<&Error> { + fn downcast_chain_ref(&self) -> Option<&ChainError> { if self.is_chain::() { - // Use transmute when we've verified the types match - unsafe { Some(std::mem::transmute::<&Error, &Error>(self)) } + #[allow(clippy::cast_ptr_alignment)] + unsafe { + Some(&*(self as *const dyn Error as *const &ChainError)) + } } else { None } } - #[inline] - fn downcast_chain_mut(&mut self) -> Option<&mut Error> { + fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { if self.is_chain::() { - // 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)) } + #[allow(clippy::cast_ptr_alignment)] + unsafe { + Some(&mut *(self as *mut dyn Error as *mut &mut ChainError)) + } } else { None } } } -impl ErrorDown for dyn StdError + 'static { - #[inline] +impl ChainErrorDown for dyn Error + 'static { fn is_chain(&self) -> bool { - self.is::>() + self.is::>() } - #[inline] - fn downcast_chain_ref(&self) -> Option<&Error> { - self.downcast_ref::>() + fn downcast_chain_ref(&self) -> Option<&ChainError> { + self.downcast_ref::>() } - #[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::()) + fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { + self.downcast_mut::>() } } -impl ErrorDown for dyn StdError + 'static + Send { - #[inline] +impl ChainErrorDown for dyn Error + 'static + Send { fn is_chain(&self) -> bool { - self.is::>() + self.is::>() } - #[inline] - fn downcast_chain_ref(&self) -> Option<&Error> { - self.downcast_ref::>() + fn downcast_chain_ref(&self) -> Option<&ChainError> { + self.downcast_ref::>() } - #[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::()) + fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { + self.downcast_mut::>() } } -impl ErrorDown for dyn StdError + 'static + Send + Sync { - #[inline] +impl ChainErrorDown for dyn Error + 'static + Send + Sync { fn is_chain(&self) -> bool { - self.is::>() + self.is::>() } - #[inline] - fn downcast_chain_ref(&self) -> Option<&Error> { - self.downcast_ref::>() + fn downcast_chain_ref(&self) -> Option<&ChainError> { + self.downcast_ref::>() } - #[inline] - fn downcast_chain_mut(&mut self) -> Option<&mut Error> { - self.downcast_mut::>() + fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { + 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::(); +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 } - - self.downcast_mut::>() - .and_then(|e| e.downcast_inner_mut::()) } } -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 &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 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 Display for Error { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { +impl Display for ChainError { + fn fmt(&self, f: &mut Formatter) -> Result { write!(f, "{}", self.kind)?; - if f.alternate() { + #[cfg(feature = "display-cause")] + { if let Some(e) = self.source() { - write!(f, "\nCaused by:\n {:#}", &e)?; + writeln!(f, "\nCaused by:")?; + Display::fmt(&e, f)?; } } - Ok(()) } } -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::())); +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)?; + } + } - let f = f - .field("occurrence", &self.occurrence) - .field("kind", &self.kind) - .field("source", &self.source()); - - f.finish() + if self.is_chain::() { + Display::fmt(&self.kind, f)?; } else { - 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)?; - } + Debug::fmt(&self.kind, f)?; + } + #[cfg(not(feature = "no-debug-cause"))] + { if let Some(e) = self.source() { - write!(f, "\nCaused by:\n{:?}", &e)?; + writeln!(f, "\nCaused by:")?; + Debug::fmt(&e, f)?; } - Ok(()) } + Ok(()) } } -impl From for Error +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 - T: 'static + Display + Debug, + U: ChainErrorFrom, { - #[track_caller] - #[inline] - fn from(e: T) -> Error { - Error::new(e, None, Some(Location::caller().to_string())) + fn into_chain_error(self, line_filename: Option<(u32, &'static str)>) -> ChainError { + U::chain_error_from(self, line_filename) } } -/// 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!(); -/// # } -/// ``` + +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! str_context { +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) => { - #[derive(Clone)] pub struct $e(pub String); - impl $e { - #[allow(dead_code)] - pub fn new>(s: S) -> Self { - $e(s.into()) - } - } impl ::std::fmt::Display for $e { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "{}", self.0) @@ -613,413 +933,3 @@ macro_rules! str_context { 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 deleted file mode 100644 index bbc9f16..0000000 --- a/tests/test_basic.rs +++ /dev/null @@ -1,33 +0,0 @@ -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 deleted file mode 100644 index 9023078..0000000 --- a/tests/test_iter.rs +++ /dev/null @@ -1,94 +0,0 @@ -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 new file mode 100644 index 0000000..8c6946d --- /dev/null +++ b/theme/book.js @@ -0,0 +1,611 @@ +"use strict"; + +// Fix back button cache problem +window.onunload = function () { }; + +// Global variable, shared between modules +function playpen_text(playpen) { + let code_block = playpen.querySelector("code"); + + if (window.ace && code_block.classList.contains("editable")) { + let editor = window.ace.edit(code_block); + return editor.getValue(); + } else { + return code_block.textContent; + } +} + +(function codeSnippets() { + // Hide Rust code lines prepended with a specific character + var hiding_character = "#"; + + function fetch_with_timeout(url, options, timeout = 6000) { + return Promise.race([ + fetch(url, options), + new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout)) + ]); + } + + var playpens = Array.from(document.querySelectorAll(".playpen")); + if (playpens.length > 0) { + fetch_with_timeout("https://play.rust-lang.org/meta/crates", { + headers: { + 'Content-Type': "application/json", + }, + method: 'POST', + mode: 'cors', + }) + .then(response => response.json()) + .then(response => { + // get list of crates available in the rust playground + let playground_crates = response.crates.map(item => item["id"]); + playpens.forEach(block => handle_crate_list_update(block, playground_crates)); + }); + } + + function handle_crate_list_update(playpen_block, playground_crates) { + // update the play buttons after receiving the response + update_play_button(playpen_block, playground_crates); + + // and install on change listener to dynamically update ACE editors + if (window.ace) { + let code_block = playpen_block.querySelector("code"); + if (code_block.classList.contains("editable")) { + let editor = window.ace.edit(code_block); + editor.addEventListener("change", function (e) { + update_play_button(playpen_block, playground_crates); + }); + } + } + } + + // updates the visibility of play button based on `no_run` class and + // used crates vs ones available on http://play.rust-lang.org + function update_play_button(pre_block, playground_crates) { + var play_button = pre_block.querySelector(".play-button"); + + // skip if code is `no_run` + if (pre_block.querySelector('code').classList.contains("no_run")) { + play_button.classList.add("hidden"); + return; + } + + // get list of `extern crate`'s from snippet + var txt = playpen_text(pre_block); + var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g; + var snippet_crates = []; + var item; + while (item = re.exec(txt)) { + snippet_crates.push(item[1]); + } + + // check if all used crates are available on play.rust-lang.org + var all_available = snippet_crates.every(function (elem) { + return playground_crates.indexOf(elem) > -1; + }); + + if (all_available) { + play_button.classList.remove("hidden"); + } else { + play_button.classList.add("hidden"); + } + } + + function run_rust_code(code_block) { + var result_block = code_block.querySelector(".result"); + if (!result_block) { + result_block = document.createElement('code'); + result_block.className = 'result hljs language-bash'; + + code_block.append(result_block); + } + + let text = playpen_text(code_block); + + var params = { + channel: "stable", + mode: "debug", + edition: "2018", + crateType: "bin", + tests: false, + code: text + }; + + if (text.indexOf("#![feature") !== -1) { + params.channel = "nightly"; + } + + if (text.indexOf("#[test]") !== -1) { + params.tests = "true"; + } + + if (text.indexOf("#![crate_type=\"lib\"]") !== -1) { + params.crateType = "lib"; + } + + result_block.innerText = "Running..."; + + fetch_with_timeout("https://play.rust-lang.org/execute", { + headers: { + 'Content-Type': "application/json", + }, + method: 'POST', + mode: 'cors', + body: JSON.stringify(params) + }) + .then(response => response.json()) + .then(response => result_block.innerText = response.stderr + "\n\n" + response.stdout) + .catch(error => result_block.innerText = "Playground Communication: " + error.message); + } + + // Syntax highlighting Configuration + hljs.configure({ + tabReplace: ' ', // 4 spaces + languages: [], // Languages used for auto-detection + }); + + if (window.ace) { + // language-rust class needs to be removed for editable + // blocks or highlightjs will capture events + Array + .from(document.querySelectorAll('code.editable')) + .forEach(function (block) { block.classList.remove('language-rust'); }); + + Array + .from(document.querySelectorAll('code:not(.editable)')) + .forEach(function (block) { hljs.highlightBlock(block); }); + } else { + Array + .from(document.querySelectorAll('code')) + .forEach(function (block) { hljs.highlightBlock(block); }); + } + + // Adding the hljs class gives code blocks the color css + // even if highlighting doesn't apply + Array + .from(document.querySelectorAll('code')) + .forEach(function (block) { block.classList.add('hljs'); }); + + Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) { + + var code_block = block; + var pre_block = block.parentNode; + // hide lines + var lines = code_block.innerHTML.split("\n"); + var first_non_hidden_line = false; + var lines_hidden = false; + var trimmed_line = ""; + + for (var n = 0; n < lines.length; n++) { + trimmed_line = lines[n].trim(); + if (trimmed_line[0] == hiding_character && trimmed_line[1] != hiding_character) { + if (first_non_hidden_line) { + lines[n] = "" + "\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 }); +})();