diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5cde165 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: cargo + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 1925bb5..7dff904 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,22 +1,38 @@ -name: coverage +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 + -on: [ "push" , "pull_request" ] jobs: - test: - name: coverage - runs-on: ubuntu-latest - container: - image: xd009642/tarpaulin - options: --security-opt seccomp=unconfined + coverage: + runs-on: ubuntu-latest + env: + CARGO_TERM_COLOR: always steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Generate code coverage - run: | - cargo tarpaulin --verbose --workspace --timeout 120 --out Lcov --output-dir coverage - - - name: Upload to coveralls - uses: coverallsapp/github-action@master + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master with: - github-token: ${{ secrets.GITHUB_TOKEN }} + 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 index b8d27bd..7062679 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -7,31 +7,10 @@ on: jobs: deploy: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Set CURRENT_TWO_WEEKS for use in cache keys - run: echo "::set-env name=CURRENT_TWO_WEEKS::$(($(date +%V) / 2))" - - - name: Cache cargo registry - uses: actions/cache@v1 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ env.CURRENT_TWO_WEEKS }} - - - name: Cache cargo index - uses: actions/cache@v1 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ env.CURRENT_TWO_WEEKS }} - - - name: Cache mdbook binary - uses: actions/cache@v1 - with: - path: ~/.cargo/bin/mdbook - key: ${{ runner.os }}-cargo-mdbook-${{ env.CURRENT_TWO_WEEKS }} - - name: Build mdbook run: cargo install mdbook diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9e148b2..3fc2545 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -19,17 +19,16 @@ jobs: strategy: matrix: version: - - 1.46.0 + - 1.54.0 - stable - beta - nightly steps: - uses: actions/checkout@v1 - name: Install toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.version }} - default: true profile: minimal - name: Build run: cargo build --verbose @@ -45,7 +44,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master with: components: rustfmt toolchain: stable @@ -61,7 +60,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master with: components: clippy toolchain: stable @@ -71,16 +70,3 @@ jobs: with: command: clippy args: -- -D warnings - - readme: - name: cargo readme - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true - - run: cargo install cargo-readme - - run: cargo readme > README.md && git diff --exit-code diff --git a/Cargo.toml b/Cargo.toml index c7e19c6..59b8808 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "chainerror" -version = "0.6.0" +version = "1.0.0" authors = ["Harald Hoyer "] edition = "2018" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" documentation = "https://docs.rs/chainerror" homepage = "https://haraldh.github.io/chainerror/" repository = "https://github.com/haraldh/chainerror" @@ -22,6 +22,5 @@ maintenance = { status = "actively-developed" } is-it-maintained-issue-resolution = { repository = "haraldh/chainerror" } is-it-maintained-open-issues = { repository = "haraldh/chainerror" } -[features] -default = [] -display-cause = [] +[package.metadata.docs.rs] +cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] diff --git a/README.md b/README.md index 86405f0..3a46567 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,6 @@ [![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://coveralls.io/repos/github/haraldh/chainerror/badge.svg?branch=master)](https://coveralls.io/github/haraldh/chainerror?branch=master) -[![Workflow Status](https://github.com/haraldh/chainerror/workflows/Rust/badge.svg)](https://github.com/haraldh/chainerror/actions?query=workflow%3A%22Rust%22) -[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/haraldh/chainerror.svg)](https://isitmaintained.com/project/haraldh/chainerror "Average time to resolve an issue") -[![Percentage of issues still open](https://isitmaintained.com/badge/open/haraldh/chainerror.svg)](https://isitmaintained.com/project/haraldh/chainerror "Percentage of issues still open") +[![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 @@ -11,71 +8,155 @@ `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your binaries, you still have the error backtrace. -`chainerror` has no dependencies! +Having nested function returning errors, the output doesn't tell where the error originates from. + +```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 `ChainError` struct, `chainerror` comes with some useful helper macros to save a lot of typing. +Along with the `Error` struct, `chainerror` comes with some useful helper macros to save a lot of typing. + +`chainerror` has no dependencies! Debug information is worth it! -### Features +## Multiple Output Formats -`display-cause` -: turn on printing a backtrace of the errors in `Display` +`chainerror` supports multiple output formats, which can be selected with the different format specifiers: -## Tutorial - -Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) - -## Examples - -examples/example.rs: -```rust -// […] -fn main() { - if let Err(e) = func1() { - eprintln!("\nDebug Error {{:?}}:\n{:?}", e); - eprintln!("\nAlternative Debug Error {{:#?}}:\n{:#?}\n", e); - // […] - } -} +* `{}`: Display +```console +func1 error calling func2 ``` +* `{:#}`: Alternative Display ```console -$ cargo run -q --example example -Debug Error {:?}: -examples/example.rs:46:13: func1 error calling func2 +func1 error calling func2 Caused by: -examples/example.rs:21:13: Func2Error(func2 error: calling func3) + func2 error: calling func3 Caused by: -examples/example.rs:14:18: Error reading 'foo.txt' + (passed error) +Caused by: + Error reading 'foo.txt' +Caused by: + entity not found +``` + +* `{:?}`: Debug +```console +examples/example.rs:50:13: func1 error calling func2 +Caused by: +examples/example.rs:25:13: Func2Error(func2 error: calling func3) +Caused by: +examples/example.rs:18:13: (passed error) +Caused by: +examples/example.rs:13:18: Error reading 'foo.txt' Caused by: Kind(NotFound) -Alternative Debug Error {:#?}: -ChainError { +``` + +* `{:#?}`: Alternative Debug +```console +Error { occurrence: Some( - "examples/example.rs:46:13", + "examples/example.rs:50:13", ), kind: func1 error calling func2, source: Some( - ChainError { + Error { occurrence: Some( - "examples/example.rs:21:13", + "examples/example.rs:25:13", ), kind: Func2Error(func2 error: calling func3), source: Some( - ChainError { + Error { occurrence: Some( - "examples/example.rs:14:18", - ), - kind: "Error reading \'foo.txt\'", + "examples/example.rs:18:13", + ), + kind: (passed error), source: Some( - Kind( - NotFound, - ), + Error { + occurrence: Some( + "examples/example.rs:13:18", + ), + kind: "Error reading 'foo.txt'", + source: Some( + Kind( + NotFound, + ), + ), + }, ), }, ), @@ -84,142 +165,16 @@ ChainError { } ``` -```rust -use chainerror::prelude::v1::*; -use std::error::Error; -use std::io; -use std::result::Result; +## Tutorial -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().context(format!("Error reading '{}'", filename))?; - Ok(()) -} - -fn func1() -> Result<(), Box> { - func2().context("func1 error")?; - Ok(()) -} - -if let Err(e) = func1() { - #[cfg(not(windows))] - assert_eq!( - format!("\n{:?}\n", e), - r#" -src/lib.rs:21:13: func1 error -Caused by: -src/lib.rs:16:18: Error reading 'foo.txt' -Caused by: -Kind(NotFound) -"# - ); -} -``` - - -```rust -use chainerror::prelude::v1::*; -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().context(format!("Error reading '{}'", filename))?; - Ok(()) -} - -derive_str_context!(Func2Error); - -fn func2() -> ChainResult<(), Func2Error> { - func3().context(Func2Error("func2 error: calling func3".into()))?; - 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().context(Func1Error::Func2)?; - let filename = String::from("bar.txt"); - do_some_io().context(Func1Error::IO(filename))?; - Ok(()) -} - -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 io_error = e.downcast_ref::().unwrap(); - eprintln!("\nThe root cause was: std::io::Error: {:#?}", io_error); - } - - #[cfg(not(windows))] - assert_eq!( - format!("\n{:?}\n", e), - r#" -src/lib.rs:48:13: func1 error calling func2 -Caused by: -src/lib.rs:23:13: Func2Error(func2 error: calling func3) -Caused by: -src/lib.rs:16:18: Error reading 'foo.txt' -Caused by: -Kind(NotFound) -"# - ); -} -``` +Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) ## License Licensed under either of -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. diff --git a/README.tpl b/README.tpl deleted file mode 100644 index 01e969a..0000000 --- a/README.tpl +++ /dev/null @@ -1,24 +0,0 @@ -[![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://coveralls.io/repos/github/haraldh/chainerror/badge.svg?branch=master)](https://coveralls.io/github/haraldh/chainerror?branch=master) -{{badges}} - -# {{crate}} - -{{readme}} - -## License - -Licensed under either of - -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally -submitted for inclusion in the work by you, as defined in the Apache-2.0 -license, shall be dual licensed as above, without any additional terms or -conditions. diff --git a/booksrc/LICENSE-APACHE b/booksrc/LICENSE-APACHE new file mode 120000 index 0000000..965b606 --- /dev/null +++ b/booksrc/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/booksrc/LICENSE-MIT b/booksrc/LICENSE-MIT new file mode 120000 index 0000000..76219eb --- /dev/null +++ b/booksrc/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/booksrc/tutorial10.md b/booksrc/tutorial10.md index 07d0100..5547954 100644 --- a/booksrc/tutorial10.md +++ b/booksrc/tutorial10.md @@ -1,6 +1,6 @@ # 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`. @@ -8,10 +8,9 @@ a `std::error::Error`. Only returning `Func1ErrorKind` in `func1()` now let us get rid of `Result<(), Box>` and we can use `ChainResult<(), Func1ErrorKind>`. -In `main` we can now directly use the methods of `ChainError` without downcasting the error first. +In `main` we can now directly use the methods of `chainerror::Error` without downcasting the error first. -Also a nice `match` on `ChainError.kind()` is now possible, which returns `&T`, meaning -`&Func1ErrorKind` here. +Also, a nice `match` on `chainerror::Error.kind()` is now possible, which returns `&T`, meaning `&Func1ErrorKind` here. ~~~rust {{#include ../examples/tutorial10.rs}} diff --git a/booksrc/tutorial11.md b/booksrc/tutorial11.md index 197b9d3..7d37814 100644 --- a/booksrc/tutorial11.md +++ b/booksrc/tutorial11.md @@ -21,7 +21,7 @@ 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 diff --git a/booksrc/tutorial12.md b/booksrc/tutorial12.md index a8c80e6..037803b 100644 --- a/booksrc/tutorial12.md +++ b/booksrc/tutorial12.md @@ -1,6 +1,6 @@ # Deref for the ErrorKind -Because ChainError implements Deref to &T, we can also match on `*e` instead of `e.kind()` +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}} diff --git a/booksrc/tutorial13.md b/booksrc/tutorial13.md index 6d438f4..0ed9db8 100644 --- a/booksrc/tutorial13.md +++ b/booksrc/tutorial13.md @@ -1,6 +1,6 @@ # Writing a library -I would advise to only expose an `mycrate::ErrorKind` and type alias `mycrate::Error` to `ChainError` +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 diff --git a/booksrc/tutorial2.md b/booksrc/tutorial2.md index 27e1fbb..a330c1a 100644 --- a/booksrc/tutorial2.md +++ b/booksrc/tutorial2.md @@ -25,7 +25,7 @@ along with the `Location` of the `context()` call and returns `Err(newerror)`. `?` then returns the inner error applying `.into()`, so that we again have a `Err(Box)` as a result. -The `Debug` implementation of `ChainError` (which is returned by `context()`) +The `Debug` implementation of `chainerror::Error` (which is returned by `context()`) prints the `Debug` of `T` prefixed with the stored filename and line number. -`ChainError` in our case is `ChainError<&str>`. +`chainerror::Error` in our case is `chainerror::Error<&str>`. diff --git a/booksrc/tutorial3.md b/booksrc/tutorial3.md index e44cc7d..f6c26ac 100644 --- a/booksrc/tutorial3.md +++ b/booksrc/tutorial3.md @@ -14,13 +14,13 @@ If you compare the output to the previous example, you will see, that: ~~~ -Error: src/main.rs:19: "func1 error" +Error: examples/tutorial2.rs:20:16: func1 error ~~~ changed to just: ~~~ -src/main.rs:16: "func1 error" +examples/tutorial3.rs:17:13: func1 error ~~~ This is, because we caught the error of `func1()` in `main()` and print it out ourselves. diff --git a/booksrc/tutorial5.md b/booksrc/tutorial5.md index 05db784..57932fa 100644 --- a/booksrc/tutorial5.md +++ b/booksrc/tutorial5.md @@ -14,5 +14,4 @@ Sometimes you want to inspect the `source()` of an `Error`. Note, that because we changed the output of the error in `main()` from `Debug` to `Display`, we don't see the error backtrace with filename and line number. -To enable the `Display` backtrace, you have to enable the feature `display-cause` for `chainerror`. - \ No newline at end of file +To use the `Display` backtrace, you have to use the alternative display format output `{:#}`. diff --git a/booksrc/tutorial7.md b/booksrc/tutorial7.md index ccb6ff6..90665f7 100644 --- a/booksrc/tutorial7.md +++ b/booksrc/tutorial7.md @@ -4,15 +4,15 @@ ~~~rust,ignore fn is_chain(&self) -> bool -fn downcast_chain_ref(&self) -> Option<&ChainError> -fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> +fn downcast_chain_ref(&self) -> Option<&chainerror::Error> +fn downcast_chain_mut(&mut self) -> Option<&mut chainerror::Error> fn root_cause(&self) -> Option<&(dyn Error + 'static)> fn find_cause(&self) -> Option<&U> -fn find_chain_cause(&self) -> Option<&ChainError> +fn find_chain_cause(&self) -> Option<&chainerror::Error> fn kind<'a>(&'a self) -> &'a T ~~~ -Using `downcast_chain_ref::()` gives a `ChainError`, which can be used +Using `downcast_chain_ref::()` gives a `chainerror::Error`, which can be used to call `.find_cause::()`. ~~~rust,ignore diff --git a/booksrc/tutorial8.md b/booksrc/tutorial8.md index da0c405..17070fe 100644 --- a/booksrc/tutorial8.md +++ b/booksrc/tutorial8.md @@ -1,14 +1,14 @@ # Finding an Error cause -To distinguish the errors occuring in various places, we can define named string errors with the +To distinguish the errors occurring in various places, we can define named string errors with the "new type" pattern. ~~~rust,ignore -derive_str_context!(Func2Error); -derive_str_context!(Func1Error); +chainerror::str_context!(Func2Error); +chainerror::str_context!(Func1Error); ~~~ -Instead of `ChainError` we now have `struct Func1Error(String)` and `ChainError`. +Instead of `chainerror::Error` we now have `struct Func1Error(String)` and `chainerror::Error`. In the `main` function you can see, how we can match the different errors. @@ -18,9 +18,9 @@ Also see: ~~~ as a shortcut to ~~~rust,ignore - if let Some(f2err) = f1err.find_cause::>() { + if let Some(f2err) = f1err.find_cause::>() { ~~~ -hiding the `ChainError` implementation detail. +hiding the `chainerror::Error` implementation detail. ~~~rust {{#include ../examples/tutorial8.rs}} diff --git a/examples/example.rs b/examples/example.rs index 89b0d77..82c073f 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -1,24 +1,28 @@ +use chainerror::Context as _; use std::error::Error; +use std::fmt; use std::io; -use std::result::Result; - -use chainerror::prelude::v1::*; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -fn func3() -> Result<(), Box> { +fn func4() -> Result<(), Box> { let filename = "foo.txt"; do_some_io().context(format!("Error reading '{}'", filename))?; Ok(()) } -derive_str_context!(Func2Error); +fn func3() -> Result<(), Box> { + func4().annotate()?; + Ok(()) +} -fn func2() -> ChainResult<(), Func2Error> { - func3().context(Func2Error(format!("func2 error: calling func3")))?; +chainerror::str_context!(Func2Error); + +fn func2() -> chainerror::Result<(), Func2Error> { + func3().context(Func2Error::new("func2 error: calling func3"))?; Ok(()) } @@ -27,8 +31,8 @@ enum Func1Error { IO(String), } -impl ::std::fmt::Display for Func1Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { +impl fmt::Display for Func1Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Func1Error::Func2 => write!(f, "func1 error calling func2"), Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), @@ -36,13 +40,13 @@ impl ::std::fmt::Display for Func1Error { } } -impl ::std::fmt::Debug for Func1Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { +impl fmt::Debug for Func1Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self) } } -fn func1() -> ChainResult<(), Func1Error> { +fn func1() -> chainerror::Result<(), Func1Error> { func2().context(Func1Error::Func2)?; let filename = String::from("bar.txt"); do_some_io().context(Func1Error::IO(filename))?; @@ -51,6 +55,10 @@ fn func1() -> ChainResult<(), Func1Error> { 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); diff --git a/examples/tutorial1.rs b/examples/tutorial1.rs index 4ac1eb3..08ca3e8 100644 --- a/examples/tutorial1.rs +++ b/examples/tutorial1.rs @@ -1,6 +1,8 @@ +#![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> { Err(io::Error::from(io::ErrorKind::NotFound))?; diff --git a/examples/tutorial10.rs b/examples/tutorial10.rs index 5118c63..3e0af4a 100644 --- a/examples/tutorial10.rs +++ b/examples/tutorial10.rs @@ -1,14 +1,14 @@ -use chainerror::prelude::v1::*; +use chainerror::Context 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(()) } -derive_str_context!(Func2Error); +chainerror::str_context!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; @@ -32,7 +32,7 @@ impl ::std::fmt::Display for Func1ErrorKind { } impl ::std::error::Error for Func1ErrorKind {} -fn func1() -> ChainResult<(), Func1ErrorKind> { +fn func1() -> chainerror::Result<(), Func1ErrorKind> { func2().context(Func1ErrorKind::Func2)?; let filename = String::from("bar.txt"); do_some_io().context(Func1ErrorKind::IO(filename))?; diff --git a/examples/tutorial11.rs b/examples/tutorial11.rs index a943b97..abbca94 100644 --- a/examples/tutorial11.rs +++ b/examples/tutorial11.rs @@ -1,14 +1,14 @@ -use chainerror::prelude::v1::*; +use chainerror::Context 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(()) } -derive_str_context!(Func2Error); +chainerror::str_context!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; @@ -38,7 +38,7 @@ impl ::std::fmt::Debug for Func1ErrorKind { impl ::std::error::Error for Func1ErrorKind {} -fn func1() -> ChainResult<(), Func1ErrorKind> { +fn func1() -> chainerror::Result<(), Func1ErrorKind> { func2().context(Func1ErrorKind::Func2)?; let filename = String::from("bar.txt"); do_some_io().context(Func1ErrorKind::IO(filename))?; diff --git a/examples/tutorial12.rs b/examples/tutorial12.rs index e2bf644..caa8fca 100644 --- a/examples/tutorial12.rs +++ b/examples/tutorial12.rs @@ -1,14 +1,14 @@ -use chainerror::prelude::v1::*; +use chainerror::Context 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(()) } -derive_str_context!(Func2Error); +chainerror::str_context!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; @@ -38,7 +38,7 @@ impl ::std::fmt::Debug for Func1ErrorKind { impl ::std::error::Error for Func1ErrorKind {} -fn func1() -> ChainResult<(), Func1ErrorKind> { +fn func1() -> chainerror::Result<(), Func1ErrorKind> { func2().context(Func1ErrorKind::Func2)?; let filename = String::from("bar.txt"); do_some_io().context(Func1ErrorKind::IO(filename))?; diff --git a/examples/tutorial13.rs b/examples/tutorial13.rs index 19bc713..0d50d68 100644 --- a/examples/tutorial13.rs +++ b/examples/tutorial13.rs @@ -1,5 +1,9 @@ +#![allow(clippy::single_match)] +#![allow(clippy::redundant_pattern_matching)] + pub mod mycrate { - use chainerror::prelude::v1::*; + use chainerror::Context as _; + use std::io; fn do_some_io() -> std::result::Result<(), Box> { @@ -7,7 +11,7 @@ pub mod mycrate { Ok(()) } - derive_str_context!(Func2Error); + chainerror::str_context!(Func2Error); fn func2() -> std::result::Result<(), Box> { let filename = "foo.txt"; @@ -21,7 +25,7 @@ pub mod mycrate { IO(String), } - derive_err_kind!(Error, ErrorKind); + chainerror::err_kind!(Error, ErrorKind); pub type Result = std::result::Result; diff --git a/examples/tutorial14.rs b/examples/tutorial14.rs index 9c624dc..5ec216e 100644 --- a/examples/tutorial14.rs +++ b/examples/tutorial14.rs @@ -1,3 +1,6 @@ +#![allow(clippy::single_match)] +#![allow(clippy::redundant_pattern_matching)] + pub mod mycrate { use std::error::Error as StdError; diff --git a/examples/tutorial15.rs b/examples/tutorial15.rs index ef35003..42355b3 100644 --- a/examples/tutorial15.rs +++ b/examples/tutorial15.rs @@ -1,5 +1,9 @@ +#![allow(clippy::single_match)] +#![allow(clippy::redundant_pattern_matching)] + pub mod mycrate { - use chainerror::prelude::v1::*; + use chainerror::{Context as _, ErrorDown as _}; + use std::io; fn do_some_io(_f: &str) -> std::result::Result<(), io::Error> { @@ -7,7 +11,7 @@ pub mod mycrate { Ok(()) } - derive_str_context!(Func2Error); + chainerror::str_context!(Func2Error); fn func2() -> std::result::Result<(), Box> { let filename = "foo.txt"; @@ -23,7 +27,7 @@ pub mod mycrate { Unknown, } - derive_err_kind!(Error, ErrorKind); + chainerror::err_kind!(Error, ErrorKind); pub type Result = std::result::Result; impl std::fmt::Display for ErrorKind { @@ -84,7 +88,7 @@ pub mod mycrate { 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::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))?; diff --git a/examples/tutorial2.rs b/examples/tutorial2.rs index 8c68dc0..96742ad 100644 --- a/examples/tutorial2.rs +++ b/examples/tutorial2.rs @@ -1,8 +1,7 @@ -use chainerror::prelude::v1::*; +use chainerror::Context 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))?; diff --git a/examples/tutorial3.rs b/examples/tutorial3.rs index 8475d6f..62a44eb 100644 --- a/examples/tutorial3.rs +++ b/examples/tutorial3.rs @@ -1,8 +1,7 @@ -use chainerror::prelude::v1::*; +use chainerror::Context 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))?; diff --git a/examples/tutorial4.rs b/examples/tutorial4.rs index d1b247b..3dea51c 100644 --- a/examples/tutorial4.rs +++ b/examples/tutorial4.rs @@ -1,7 +1,7 @@ -use chainerror::prelude::v1::*; +use chainerror::Context 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))?; diff --git a/examples/tutorial5.rs b/examples/tutorial5.rs index 333208c..d9fe708 100644 --- a/examples/tutorial5.rs +++ b/examples/tutorial5.rs @@ -1,7 +1,7 @@ -use chainerror::prelude::v1::*; +use chainerror::Context 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))?; diff --git a/examples/tutorial6.rs b/examples/tutorial6.rs index 9310c8c..5d81310 100644 --- a/examples/tutorial6.rs +++ b/examples/tutorial6.rs @@ -1,7 +1,10 @@ -use chainerror::prelude::v1::*; +#![allow(clippy::single_match)] +#![allow(clippy::redundant_pattern_matching)] + +use chainerror::Context as _; + use std::error::Error; use std::io; -use std::result::Result; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; diff --git a/examples/tutorial7.rs b/examples/tutorial7.rs index feb7cf8..214484c 100644 --- a/examples/tutorial7.rs +++ b/examples/tutorial7.rs @@ -1,7 +1,10 @@ -use chainerror::prelude::v1::*; +#![allow(clippy::single_match)] +#![allow(clippy::redundant_pattern_matching)] + +use chainerror::{Context as _, ErrorDown as _}; + use std::error::Error; use std::io; -use std::result::Result; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; @@ -15,7 +18,7 @@ fn func2() -> Result<(), Box> { } fn func1() -> Result<(), Box> { - func2().context(format!("func1 error"))?; + func2().context("func1 error")?; Ok(()) } diff --git a/examples/tutorial8.rs b/examples/tutorial8.rs index 2093782..e32a449 100644 --- a/examples/tutorial8.rs +++ b/examples/tutorial8.rs @@ -1,14 +1,14 @@ -use chainerror::prelude::v1::*; +use chainerror::{Context as _, 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(()) } -derive_str_context!(Func2Error); +chainerror::str_context!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; @@ -16,10 +16,10 @@ fn func2() -> Result<(), Box> { Ok(()) } -derive_str_context!(Func1Error); +chainerror::str_context!(Func1Error); fn func1() -> Result<(), Box> { - func2().context(Func1Error(format!("func1 error")))?; + func2().context(Func1Error::new("func1 error"))?; Ok(()) } @@ -28,7 +28,7 @@ fn main() -> Result<(), Box> { 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); } diff --git a/examples/tutorial9.rs b/examples/tutorial9.rs index 1561535..bbbe810 100644 --- a/examples/tutorial9.rs +++ b/examples/tutorial9.rs @@ -1,14 +1,14 @@ -use chainerror::prelude::v1::*; +use chainerror::{Context as _, ErrorDown}; + 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_context!(Func2Error); +chainerror::str_context!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; @@ -16,11 +16,11 @@ fn func2() -> Result<(), Box> { Ok(()) } -derive_str_context!(Func1ErrorFunc2); -derive_str_context!(Func1ErrorIO); +chainerror::str_context!(Func1ErrorFunc2); +chainerror::str_context!(Func1ErrorIO); fn func1() -> Result<(), Box> { - func2().context(Func1ErrorFunc2(format!("func1 error calling func2")))?; + func2().context(Func1ErrorFunc2::new("func1 error calling func2"))?; let filename = "bar.txt"; do_some_io().context(Func1ErrorIO(format!("Error reading '{}'", filename)))?; Ok(()) @@ -28,7 +28,7 @@ fn func1() -> 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); } diff --git a/src/lib.rs b/src/lib.rs index bd4edd6..623523d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,251 +1,29 @@ -//! `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 `#[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 `ChainError` struct, `chainerror` comes with some useful helper macros to save a lot of typing. -//! -//! Debug information is worth it! -//! -//! ## Features -//! -//! `display-cause` -//! : turn on printing a backtrace of the errors in `Display` -//! -//! # Tutorial -//! -//! Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) -//! -//! # Examples -//! -//! examples/example.rs: -//! ```rust,ignore -//! // […] -//! fn main() { -//! if let Err(e) = func1() { -//! eprintln!("\nDebug Error {{:?}}:\n{:?}", e); -//! eprintln!("\nAlternative Debug Error {{:#?}}:\n{:#?}\n", e); -//! // […] -//! } -//! } -//! ``` -//! -//! ```console -//! $ cargo run -q --example example -//! Debug Error {:?}: -//! examples/example.rs:46:13: func1 error calling func2 -//! Caused by: -//! examples/example.rs:21:13: Func2Error(func2 error: calling func3) -//! Caused by: -//! examples/example.rs:14:18: Error reading 'foo.txt' -//! Caused by: -//! Kind(NotFound) -//! -//! Alternative Debug Error {:#?}: -//! ChainError { -//! occurrence: Some( -//! "examples/example.rs:46:13", -//! ), -//! kind: func1 error calling func2, -//! source: Some( -//! ChainError { -//! occurrence: Some( -//! "examples/example.rs:21:13", -//! ), -//! kind: Func2Error(func2 error: calling func3), -//! source: Some( -//! ChainError { -//! occurrence: Some( -//! "examples/example.rs:14:18", -//! ), -//! kind: "Error reading \'foo.txt\'", -//! source: Some( -//! Kind( -//! NotFound, -//! ), -//! ), -//! }, -//! ), -//! }, -//! ), -//! } -//! ``` -//! -//! ```rust -//! use chainerror::prelude::v1::*; -//! 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().context(format!("Error reading '{}'", filename))?; -//! Ok(()) -//! } -//! -//! fn func1() -> Result<(), Box> { -//! func2().context("func1 error")?; -//! Ok(()) -//! } -//! -//! if let Err(e) = func1() { -//! #[cfg(not(windows))] -//! assert_eq!( -//! format!("\n{:?}\n", e), -//! r#" -//! src/lib.rs:21:13: func1 error -//! Caused by: -//! src/lib.rs:16:18: Error reading 'foo.txt' -//! Caused by: -//! Kind(NotFound) -//! "# -//! ); -//! } -//! # else { -//! # unreachable!(); -//! # } -//! ``` -//! -//! -//! ```rust -//! use chainerror::prelude::v1::*; -//! 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().context(format!("Error reading '{}'", filename))?; -//! Ok(()) -//! } -//! -//! derive_str_context!(Func2Error); -//! -//! fn func2() -> ChainResult<(), Func2Error> { -//! func3().context(Func2Error("func2 error: calling func3".into()))?; -//! 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().context(Func1Error::Func2)?; -//! let filename = String::from("bar.txt"); -//! do_some_io().context(Func1Error::IO(filename))?; -//! Ok(()) -//! } -//! -//! 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 io_error = e.downcast_ref::().unwrap(); -//! eprintln!("\nThe root cause was: std::io::Error: {:#?}", io_error); -//! } -//! -//! #[cfg(not(windows))] -//! assert_eq!( -//! format!("\n{:?}\n", e), -//! r#" -//! src/lib.rs:48:13: func1 error calling func2 -//! Caused by: -//! src/lib.rs:23:13: Func2Error(func2 error: calling func3) -//! Caused by: -//! src/lib.rs:16:18: Error reading 'foo.txt' -//! Caused by: -//! Kind(NotFound) -//! "# -//! ); -//! } -//! # else { -//! # unreachable!(); -//! # } -//! ``` - +#![doc = include_str!("../README.md")] #![deny(clippy::all)] -#![deny(clippy::integer_arithmetic)] +#![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] use std::any::TypeId; -use std::error::Error; -use std::fmt::{Debug, Display, Formatter, Result}; +use std::error::Error as StdError; +use std::fmt::{Debug, Display, Formatter}; use std::panic::Location; -pub mod prelude { - //! convenience prelude - pub mod v1 { - //! convenience prelude - pub use super::super::ChainErrorDown as _; - pub use super::super::ResultTrait as _; - pub use super::super::{ChainError, ChainResult}; - pub use crate::{derive_err_kind, derive_str_context}; - } -} - /// chains an inner error kind `T` with a causing error -pub struct ChainError { +pub struct Error { occurrence: Option, kind: T, - error_cause: Option>, + error_cause: Option>, } /// convenience type alias -pub type ChainResult = std::result::Result>; +pub type Result = std::result::Result>; -impl ChainError { +impl Error { /// Use the `context()` or `map_context()` Result methods instead of calling this directly #[inline] pub fn new( kind: T, - error_cause: Option>, + error_cause: Option>, occurrence: Option, ) -> Self { Self { @@ -256,7 +34,7 @@ impl ChainError { } /// return the root cause of the error chain, if any exists - pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> { + pub fn root_cause(&self) -> Option<&(dyn StdError + 'static)> { self.iter().last() } @@ -265,7 +43,8 @@ impl ChainError { /// # Examples /// /// ```rust - /// use chainerror::prelude::v1::*; + /// use chainerror::Context as _; + /// use chainerror::ErrorDown as _; /// use std::error::Error; /// use std::io; /// @@ -274,7 +53,7 @@ impl ChainError { /// Ok(()) /// } /// - /// derive_str_context!(Func2Error); + /// chainerror::str_context!(Func2Error); /// /// fn func2() -> Result<(), Box> { /// let filename = "foo.txt"; @@ -282,10 +61,10 @@ impl ChainError { /// Ok(()) /// } /// - /// derive_str_context!(Func1Error); + /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box> { - /// func2().context(Func1Error("func1 error".into()))?; + /// func2().context(Func1Error::new("func1 error"))?; /// Ok(()) /// } /// @@ -304,70 +83,70 @@ impl ChainError { /// # } /// ``` #[inline] - pub fn find_cause(&self) -> Option<&U> { - self.iter().filter_map(Error::downcast_ref::).next() - } - - /// Find the first error cause of type `ChainError`, if any exists - /// - /// Same as `find_cause`, but hides the `ChainError` implementation internals - /// - /// # Examples - /// - /// ```rust - /// # use chainerror::prelude::v1::*; - /// # derive_str_context!(FooError); - /// # let err = ChainError::new(String::new(), None, None); - /// // Instead of writing - /// err.find_cause::>(); - /// - /// // leave out the ChainError implementation detail - /// err.find_chain_cause::(); - /// ``` - #[inline] - pub fn find_chain_cause(&self) -> Option<&ChainError> { + pub fn find_cause(&self) -> Option<&U> { self.iter() - .filter_map(Error::downcast_ref::>) + .filter_map(::downcast_ref::) .next() } - /// Find the first error cause of type `ChainError` or `U`, if any exists and return `U` + /// Find the first error cause of type [`Error`](Error), if any exists /// - /// Same as `find_cause` and `find_chain_cause`, but hides the `ChainError` implementation internals + /// Same as `find_cause`, but hides the [`Error`](Error) implementation internals /// /// # Examples /// /// ```rust - /// # use chainerror::prelude::v1::*; - /// # derive_str_context!(FooErrorKind); - /// # let err = ChainError::new(String::new(), None, None); + /// # chainerror::str_context!(FooError); + /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing - /// err.find_cause::>(); + /// 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 implementation detail + /// // leave out the chainerror::Error implementation detail /// err.find_kind_or_cause::(); /// ``` #[inline] - pub fn find_kind_or_cause(&self) -> Option<&U> { + pub fn find_kind_or_cause(&self) -> Option<&U> { self.iter() .filter_map(|e| { - e.downcast_ref::>() + e.downcast_ref::>() .map(|e| e.kind()) .or_else(|| e.downcast_ref::()) }) .next() } - /// Return a reference to T of `ChainError` + /// Return a reference to T of [`Error`](Error) /// /// # Examples /// /// ```rust - /// use chainerror::prelude::v1::*; + /// use chainerror::Context as _; /// use std::error::Error; /// use std::io; /// @@ -376,7 +155,7 @@ impl ChainError { /// Ok(()) /// } /// - /// derive_str_context!(Func2Error); + /// chainerror::str_context!(Func2Error); /// /// fn func2() -> Result<(), Box> { /// let filename = "foo.txt"; @@ -400,7 +179,7 @@ impl ChainError { /// # } /// # } /// - /// fn func1() -> ChainResult<(), Func1ErrorKind> { + /// fn func1() -> chainerror::Result<(), Func1ErrorKind> { /// func2().context(Func1ErrorKind::Func2)?; /// do_some_io().context(Func1ErrorKind::IO("bar.txt".into()))?; /// Ok(()) @@ -425,39 +204,52 @@ impl ChainError { /// /// # Example #[inline] - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { ErrorIter { current: Some(self), } } } -/// Convenience methods for `Result<>` to turn the error into a decorated ChainError -pub trait ResultTrait>> { +/// 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>; + fn context(self, kind: T) -> std::result::Result>; + + /// Decorate the error just with the source `Location` + fn annotate(self) -> std::result::Result>; /// Decorate the `error` with a `kind` of type `T` produced with a `FnOnce(&error)` and the source `Location` fn map_context T>( self, op: F, - ) -> std::result::Result>; + ) -> std::result::Result>; } -impl>> ResultTrait +/// Convenience type to just decorate the error with the source `Location` +pub struct AnnotatedError(()); + +impl Display for AnnotatedError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "(passed error)") + } +} + +impl Debug for AnnotatedError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "(passed error)") + } +} + +impl>> Context for std::result::Result { #[track_caller] - fn context( - self, - kind: T, - ) -> std::result::Result> { + #[inline] + fn context(self, kind: T) -> std::result::Result> { match self { Ok(t) => Ok(t), - Err(error_cause) => Err(ChainError::new( + Err(error_cause) => Err(Error::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), @@ -466,15 +258,29 @@ impl>> ResultTrait } #[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> { + ) -> std::result::Result> { match self { Ok(t) => Ok(t), Err(error_cause) => { let kind = op(&error_cause); - Err(ChainError::new( + Err(Error::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), @@ -486,21 +292,21 @@ impl>> ResultTrait /// An iterator over all error causes/sources pub struct ErrorIter<'a> { - current: Option<&'a (dyn Error + 'static)>, + current: Option<&'a (dyn StdError + 'static)>, } impl<'a> Iterator for ErrorIter<'a> { - type Item = &'a (dyn Error + 'static); + type Item = &'a (dyn StdError + 'static); #[inline] fn next(&mut self) -> Option { let current = self.current; - self.current = self.current.and_then(Error::source); + self.current = self.current.and_then(StdError::source); current } } -impl std::ops::Deref for ChainError { +impl std::ops::Deref for Error { type Target = T; #[inline] @@ -509,225 +315,203 @@ impl std::ops::Deref for ChainError { } } -/// Convenience trait to hide the `ChainError` implementation internals -pub trait ChainErrorDown { - /// Test if of type `ChainError` +/// Convenience trait to hide the [`Error`](Error) implementation internals +pub trait ErrorDown { + /// Test if of type `Error` fn is_chain(&self) -> bool; - /// Downcast to a reference of `ChainError` - fn downcast_chain_ref(&self) -> Option<&ChainError>; - /// Downcast to a mutable reference of `ChainError` - fn downcast_chain_mut(&mut self) -> Option<&mut ChainError>; - /// Downcast to T of `ChainError` - fn downcast_inner_ref(&self) -> Option<&T>; - /// Downcast to T mutable reference of `ChainError` - fn downcast_inner_mut(&mut self) -> Option<&mut T>; + /// Downcast to a reference of `Error` + fn downcast_chain_ref(&self) -> Option<&Error>; + /// Downcast to a mutable reference of `Error` + fn downcast_chain_mut(&mut self) -> Option<&mut Error>; + /// Downcast to T of `Error` + fn downcast_inner_ref(&self) -> Option<&T>; + /// Downcast to T mutable reference of `Error` + fn downcast_inner_mut(&mut self) -> Option<&mut T>; } -impl ChainErrorDown for ChainError { +impl ErrorDown for Error { #[inline] fn is_chain(&self) -> bool { TypeId::of::() == TypeId::of::() } #[inline] - fn downcast_chain_ref(&self) -> Option<&ChainError> { + fn downcast_chain_ref(&self) -> Option<&Error> { if self.is_chain::() { - #[allow(clippy::cast_ptr_alignment)] - unsafe { - #[allow(trivial_casts)] - Some(&*(self as *const dyn Error as *const &ChainError)) - } + // Use transmute when we've verified the types match + unsafe { Some(std::mem::transmute::<&Error, &Error>(self)) } } else { None } } #[inline] - fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { + fn downcast_chain_mut(&mut self) -> Option<&mut Error> { if self.is_chain::() { - #[allow(clippy::cast_ptr_alignment)] - unsafe { - #[allow(trivial_casts)] - Some(&mut *(self as *mut dyn Error as *mut &mut ChainError)) - } + // Use transmute when we've verified the types match + unsafe { Some(std::mem::transmute::<&mut Error, &mut Error>(self)) } } else { None } } #[inline] - fn downcast_inner_ref(&self) -> Option<&T> { + fn downcast_inner_ref(&self) -> Option<&T> { if self.is_chain::() { - #[allow(clippy::cast_ptr_alignment)] - unsafe { - #[allow(trivial_casts)] - Some(&(*(self as *const dyn Error as *const &ChainError)).kind) - } + // 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> { + fn downcast_inner_mut(&mut self) -> Option<&mut T> { if self.is_chain::() { - #[allow(clippy::cast_ptr_alignment)] - unsafe { - #[allow(trivial_casts)] - Some(&mut (*(self as *mut dyn Error as *mut &mut ChainError)).kind) - } + // Use transmute when we've verified the types match + unsafe { Some(std::mem::transmute::<&mut U, &mut T>(&mut self.kind)) } } else { None } } } -impl ChainErrorDown for dyn Error + 'static { +impl ErrorDown for dyn StdError + 'static { #[inline] fn is_chain(&self) -> bool { - self.is::>() + self.is::>() } #[inline] - fn downcast_chain_ref(&self) -> Option<&ChainError> { - self.downcast_ref::>() + fn downcast_chain_ref(&self) -> Option<&Error> { + self.downcast_ref::>() } #[inline] - fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { - self.downcast_mut::>() + fn downcast_chain_mut(&mut self) -> Option<&mut Error> { + self.downcast_mut::>() } #[inline] - fn downcast_inner_ref(&self) -> Option<&T> { + fn downcast_inner_ref(&self) -> Option<&T> { self.downcast_ref::() - .or_else(|| self.downcast_ref::>().map(|e| e.kind())) + .or_else(|| self.downcast_ref::>().map(|e| e.kind())) } #[inline] - fn downcast_inner_mut(&mut self) -> Option<&mut T> { + fn downcast_inner_mut(&mut self) -> Option<&mut T> { if self.is::() { return self.downcast_mut::(); } - self.downcast_mut::>() + self.downcast_mut::>() .and_then(|e| e.downcast_inner_mut::()) } } -impl ChainErrorDown for dyn Error + 'static + Send { +impl ErrorDown for dyn StdError + 'static + Send { #[inline] fn is_chain(&self) -> bool { - self.is::>() + self.is::>() } #[inline] - fn downcast_chain_ref(&self) -> Option<&ChainError> { - self.downcast_ref::>() + fn downcast_chain_ref(&self) -> Option<&Error> { + self.downcast_ref::>() } #[inline] - fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { - self.downcast_mut::>() + fn downcast_chain_mut(&mut self) -> Option<&mut Error> { + self.downcast_mut::>() } #[inline] - fn downcast_inner_ref(&self) -> Option<&T> { + fn downcast_inner_ref(&self) -> Option<&T> { self.downcast_ref::() - .or_else(|| self.downcast_ref::>().map(|e| e.kind())) + .or_else(|| self.downcast_ref::>().map(|e| e.kind())) } #[inline] - fn downcast_inner_mut(&mut self) -> Option<&mut T> { + fn downcast_inner_mut(&mut self) -> Option<&mut T> { if self.is::() { return self.downcast_mut::(); } - self.downcast_mut::>() + self.downcast_mut::>() .and_then(|e| e.downcast_inner_mut::()) } } -impl ChainErrorDown for dyn Error + 'static + Send + Sync { +impl ErrorDown for dyn StdError + 'static + Send + Sync { #[inline] fn is_chain(&self) -> bool { - self.is::>() + self.is::>() } #[inline] - fn downcast_chain_ref(&self) -> Option<&ChainError> { - self.downcast_ref::>() + fn downcast_chain_ref(&self) -> Option<&Error> { + self.downcast_ref::>() } #[inline] - fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { - self.downcast_mut::>() + fn downcast_chain_mut(&mut self) -> Option<&mut Error> { + self.downcast_mut::>() } #[inline] - fn downcast_inner_ref(&self) -> Option<&T> { + fn downcast_inner_ref(&self) -> Option<&T> { self.downcast_ref::() - .or_else(|| self.downcast_ref::>().map(|e| e.kind())) + .or_else(|| self.downcast_ref::>().map(|e| e.kind())) } #[inline] - fn downcast_inner_mut(&mut self) -> Option<&mut T> { + fn downcast_inner_mut(&mut self) -> Option<&mut T> { if self.is::() { return self.downcast_mut::(); } - self.downcast_mut::>() + self.downcast_mut::>() .and_then(|e| e.downcast_inner_mut::()) } } -impl Error for ChainError { +impl StdError for Error { #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { + fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() - .map(|e| e.as_ref() as &(dyn Error + 'static)) + .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } -impl Error for &ChainError { +impl StdError for &mut Error { #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { + fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() - .map(|e| e.as_ref() as &(dyn Error + 'static)) + .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } -impl Error for &mut ChainError { +impl Display for Error { #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - self.error_cause - .as_ref() - .map(|e| e.as_ref() as &(dyn Error + 'static)) - } -} - -impl Display for ChainError { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.kind)?; - #[cfg(feature = "display-cause")] - { + if f.alternate() { if let Some(e) = self.source() { - writeln!(f, "\nCaused by:")?; - Display::fmt(&e, f)?; + write!(f, "\nCaused by:\n {:#}", &e)?; } } + Ok(()) } } -impl Debug for ChainError { +impl Debug for Error { #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if f.alternate() { - let mut f = f.debug_struct(&format!("ChainError<{}>", std::any::type_name::())); + let mut f = f.debug_struct(&format!("Error<{}>", std::any::type_name::())); let f = f .field("occurrence", &self.occurrence) @@ -749,54 +533,30 @@ impl Debug for ChainError { } if let Some(e) = self.source() { - writeln!(f, "\nCaused by:")?; - Debug::fmt(&e, f)?; + write!(f, "\nCaused by:\n{:?}", &e)?; } Ok(()) } } } -/// `ChainErrorFrom` is similar to `From` -pub trait ChainErrorFrom: Sized { - /// similar to From::from() - fn chain_error_from(from: T, line_filename: Option) -> ChainError; -} - -/// `IntoChainError` is similar to `Into` -pub trait IntoChainError: Sized { - /// similar to Into::into() - fn into_chain_error(self, line_filename: Option) -> ChainError; -} - -impl IntoChainError for T +impl From for Error where - U: ChainErrorFrom, + T: 'static + Display + Debug, { + #[track_caller] #[inline] - fn into_chain_error(self, line_filename: Option) -> ChainError { - U::chain_error_from(self, line_filename) + fn from(e: T) -> Error { + Error::new(e, None, Some(Location::caller().to_string())) } } - -impl ChainErrorFrom for U -where - T: Into, - U: 'static + Display + Debug, -{ - #[inline] - fn chain_error_from(t: T, line_filename: Option) -> ChainError { - let e: U = t.into(); - ChainError::new(e, None, line_filename) - } -} - /// Convenience macro to create a "new type" T(String) and implement Display + Debug for T /// /// # Examples /// /// ```rust -/// # use crate::chainerror::*; +/// # use chainerror::Context as _; +/// # use chainerror::ErrorDown as _; /// # use std::error::Error; /// # use std::io; /// # use std::result::Result; @@ -804,23 +564,23 @@ where /// # Err(io::Error::from(io::ErrorKind::NotFound))?; /// # Ok(()) /// # } -/// derive_str_context!(Func2Error); +/// chainerror::str_context!(Func2Error); /// -/// fn func2() -> ChainResult<(), Func2Error> { +/// fn func2() -> chainerror::Result<(), Func2Error> { /// let filename = "foo.txt"; /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// -/// derive_str_context!(Func1Error); +/// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box> { -/// func2().context(Func1Error("func1 error".into()))?; +/// 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_cause::>().is_some()); /// # assert!(f1err.find_chain_cause::().is_some()); /// # } else { /// # panic!(); @@ -830,10 +590,16 @@ where /// # } /// ``` #[macro_export] -macro_rules! derive_str_context { +macro_rules! str_context { ($e:ident) => { #[derive(Clone)] pub struct $e(pub String); + impl $e { + #[allow(dead_code)] + pub fn new>(s: S) -> Self { + $e(s.into()) + } + } impl ::std::fmt::Display for $e { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "{}", self.0) @@ -848,9 +614,9 @@ macro_rules! derive_str_context { }; } -/// Derive an Error for an ErrorKind, which wraps a `ChainError` and implements a `kind()` method +/// Derive an Error for an ErrorKind, which wraps a [`Error`](Error) and implements a `kind()` method /// -/// It basically hides `ChainError` to the outside and only exposes the `kind()` +/// It basically hides [`Error`](Error) to the outside and only exposes the [`kind()`](Error::kind) /// method. /// /// Error::kind() returns the ErrorKind @@ -859,7 +625,7 @@ macro_rules! derive_str_context { /// # Examples /// /// ```rust -/// use chainerror::prelude::v1::*; +/// use chainerror::Context as _; /// use std::io; /// /// fn do_some_io(_f: &str) -> std::result::Result<(), io::Error> { @@ -873,7 +639,7 @@ macro_rules! derive_str_context { /// Unknown, /// } /// -/// derive_err_kind!(Error, ErrorKind); +/// chainerror::err_kind!(Error, ErrorKind); /// /// impl std::fmt::Display for ErrorKind { /// fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result { @@ -911,11 +677,17 @@ macro_rules! derive_str_context { /// 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! derive_err_kind { +macro_rules! err_kind { ($e:ident, $k:ident) => { - pub struct $e($crate::ChainError<$k>); + pub struct $e($crate::Error<$k>); impl $e { pub fn kind(&self) -> &$k { @@ -925,12 +697,12 @@ macro_rules! derive_err_kind { impl From<$k> for $e { fn from(e: $k) -> Self { - $e($crate::ChainError::new(e, None, None)) + $e($crate::Error::new(e, None, None)) } } - impl From> for $e { - fn from(e: $crate::ChainError<$k>) -> Self { + impl From<$crate::Error<$k>> for $e { + fn from(e: $crate::Error<$k>) -> Self { $e(e) } } @@ -963,3 +735,291 @@ macro_rules! derive_err_kind { } }; } + +#[cfg(test)] +mod tests { + use super::Context as _; + use super::*; + use std::io; + + #[test] + fn test_error_chain_with_multiple_causes() { + // Create a chain of errors + let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); + + str_context!(Level3Error); + str_context!(Level2Error); + str_context!(Level1Error); + + let err = Result::<(), _>::Err(io_error.into()) + .context(Level3Error("level 3".into())) + .context(Level2Error("level 2".into())) + .context(Level1Error("level 1".into())) + .unwrap_err(); + + // Test the error chain + assert!(err.is_chain::()); + assert!(err.find_chain_cause::().is_some()); + assert!(err.find_chain_cause::().is_some()); + assert!(err.find_chain_cause::().is_some()); + } + + #[test] + fn test_error_root_cause() { + let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); + + str_context!(WrapperError); + let err = Result::<(), _>::Err(io_error.into()) + .context(WrapperError("wrapper".into())) + .unwrap_err(); + + let root = err.root_cause().unwrap(); + assert!(root.is_chain::()); + } + + #[test] + fn test_error_display_and_debug() { + str_context!(CustomError); + let err = Error::new( + CustomError("test error".into()), + None, + Some("src/lib.rs:100".into()), + ); + + // Test Display formatting + assert_eq!(format!("{}", err), "test error"); + + // Test alternate Display formatting + assert_eq!(format!("{:#}", err), "test error"); + + // Test Debug formatting + let debug_output = format!("{:?}", err); + assert!(debug_output.contains("test error")); + assert!(debug_output.contains("src/lib.rs:100")); + } + + #[test] + fn test_error_annotation() { + let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); + let err = Result::<(), _>::Err(io_error.into()) + .annotate() + .unwrap_err(); + + assert!(err.source().is_some()); + err.source() + .unwrap() + .downcast_inner_ref::() + .unwrap(); + } + + #[test] + fn test_map_context() { + let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); + + str_context!(MappedError); + let err = Result::<(), _>::Err(io_error.into()) + .map_context(|e| MappedError(format!("Mapped: {}", e))) + .unwrap_err(); + + assert!(err.is_chain::()); + assert!(err.find_chain_cause::().is_some()); + } + + #[test] + fn test_error_downcasting() { + str_context!(OriginalError); + let original = Error::new(OriginalError("test".into()), None, None); + + let error: Box = Box::new(original); + + // Test downcast_chain_ref + assert!(error.is_chain::()); + assert!(error.downcast_chain_ref::().is_some()); + + // Test downcast_inner_ref + let inner = error.downcast_inner_ref::(); + assert!(inner.is_some()); + } + + #[derive(Debug, Clone)] + enum TestErrorKind { + Basic(String), + Complex { message: String }, + } + + impl Display for TestErrorKind { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + TestErrorKind::Basic(msg) => write!(f, "Basic error: {}", msg), + TestErrorKind::Complex { message } => write!(f, "Complex error: {}", message), + } + } + } + + #[test] + fn test_err_kind_macro() { + err_kind!(TestError, TestErrorKind); + + let err = TestError::from(TestErrorKind::Basic("test".into())); + assert!(matches!(err.kind(), TestErrorKind::Basic(_))); + // The annotated error should display "(passed error)" even in a chain + assert_eq!(format!("{}", err), "Basic error: test"); + assert_eq!(format!("{:?}", err), "Basic(\"test\")"); + + let complex_err = TestError::from(TestErrorKind::Complex { + message: "test".into(), + }); + assert!(matches!(complex_err.kind(), TestErrorKind::Complex { .. })); + // The annotated error should display "(passed error)" even in a chain + assert_eq!(format!("{}", complex_err), "Complex error: test"); + assert_eq!( + format!("{:?}", complex_err), + "Complex { message: \"test\" }" + ); + } + #[test] + fn test_annotated_error_display_and_debug() { + let annotated = AnnotatedError(()); + + // Test Display formatting + assert_eq!(format!("{}", annotated), "(passed error)"); + + // Test Debug formatting + assert_eq!(format!("{:?}", annotated), "(passed error)"); + + // Test with error chain + let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); + let err = Result::<(), _>::Err(io_error.into()) + .annotate() + .unwrap_err(); + + // The annotated error should display "(passed error)" even in a chain + assert_eq!(format!("{}", err), "(passed error)"); + assert!(format!("{:?}", err).contains("(passed error)")); + + // Verify the error chain is preserved + assert!(err.source().is_some()); + assert!(err.source().unwrap().is_chain::()); + } + + // Helper error types for testing + #[derive(Debug)] + struct TestError(String); + + impl std::fmt::Display for TestError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } + } + + impl std::error::Error for TestError {} + + #[test] + fn test_downcast_chain_operations() { + // Create a test error chain + let original_error = Error::new( + TestError("test message".to_string()), + None, + Some("test location".to_string()), + ); + + // Test is_chain + assert!(original_error.is_chain::()); + assert!(!original_error.is_chain::()); + + // Test downcast_chain_ref + let downcast_ref = original_error.downcast_chain_ref::(); + assert!(downcast_ref.is_some()); + let downcast_kind = downcast_ref.unwrap().kind(); + assert_eq!(format!("{}", downcast_kind), "test message"); + assert_eq!( + format!("{:?}", downcast_kind), + "TestError(\"test message\")" + ); + + // Test invalid downcast_chain_ref + let invalid_downcast = original_error.downcast_chain_ref::(); + assert!(invalid_downcast.is_none()); + + // Test downcast_chain_mut + let mut mutable_error = original_error; + let downcast_mut = mutable_error.downcast_chain_mut::(); + assert!(downcast_mut.is_some()); + assert_eq!(downcast_mut.unwrap().kind().0, "test message"); + + // Test invalid downcast_chain_mut + let invalid_downcast_mut = mutable_error.downcast_chain_mut::(); + assert!(invalid_downcast_mut.is_none()); + } + + #[test] + fn test_downcast_inner_operations() { + // Create a test error + let mut error = Error::new( + TestError("inner test".to_string()), + None, + Some("test location".to_string()), + ); + + // Test downcast_inner_ref + let inner_ref = error.downcast_inner_ref::(); + assert!(inner_ref.is_some()); + assert_eq!(inner_ref.unwrap().0, "inner test"); + // Test invalid downcast_inner_ref + let invalid_inner = error.downcast_inner_ref::(); + assert!(invalid_inner.is_none()); + + // Test downcast_inner_mut + let inner_mut = error.downcast_inner_mut::(); + assert!(inner_mut.is_some()); + assert_eq!(inner_mut.unwrap().0, "inner test"); + + // Test invalid downcast_inner_mut + let invalid_inner_mut = error.downcast_inner_mut::(); + assert!(invalid_inner_mut.is_none()); + } + + #[test] + fn test_error_down_for_dyn_error() { + // Create a boxed error + let error: Box = Box::new(Error::new( + TestError("dyn test".to_string()), + None, + Some("test location".to_string()), + )); + + // Test is_chain through trait object + assert!(error.is_chain::()); + assert!(!error.is_chain::()); + + // Test downcast_chain_ref through trait object + let chain_ref = error.downcast_chain_ref::(); + assert!(chain_ref.is_some()); + assert_eq!(chain_ref.unwrap().kind().0, "dyn test"); + + // Test downcast_inner_ref through trait object + let inner_ref = error.downcast_inner_ref::(); + assert!(inner_ref.is_some()); + assert_eq!(inner_ref.unwrap().0, "dyn test"); + } + + #[test] + fn test_error_down_with_sync_send() { + // Create a boxed error with Send + Sync + let error: Box = Box::new(Error::new( + TestError("sync test".to_string()), + None, + Some("test location".to_string()), + )); + + // Test operations on Send + Sync error + assert!(error.is_chain::()); + assert!(error.downcast_chain_ref::().is_some()); + assert!(error.downcast_inner_ref::().is_some()); + + // Test invalid downcasts + assert!(!error.is_chain::()); + assert!(error.downcast_chain_ref::().is_none()); + assert!(error.downcast_inner_ref::().is_none()); + } +} diff --git a/tests/test_basic.rs b/tests/test_basic.rs new file mode 100644 index 0000000..bbc9f16 --- /dev/null +++ b/tests/test_basic.rs @@ -0,0 +1,33 @@ +use chainerror::Context; + +#[test] +fn test_basic() { + use std::path::PathBuf; + type BoxedError = Box; + fn read_config_file(path: PathBuf) -> Result<(), BoxedError> { + // do stuff, return other errors + let _buf = std::fs::read_to_string(&path).context(format!("Reading file: {:?}", &path))?; + // do stuff, return other errors + Ok(()) + } + fn process_config_file() -> Result<(), BoxedError> { + // do stuff, return other errors + read_config_file("_non_existent.txt".into()).context("read the config file")?; + // do stuff, return other errors + Ok(()) + } + + if let Err(e) = process_config_file() { + let os_notfound_error = std::io::Error::from_raw_os_error(2); + let s = format!("{:?}", e); + let lines = s.lines().collect::>(); + assert_eq!(lines.len(), 5); + assert!(lines[0].starts_with("tests/test_basic.rs:")); + assert_eq!(lines[1], "Caused by:"); + assert!(lines[2].starts_with("tests/test_basic.rs:")); + assert_eq!(lines[3], "Caused by:"); + assert_eq!(lines[4], format!("{:?}", os_notfound_error)); + } else { + panic!(); + } +} diff --git a/tests/test_iter.rs b/tests/test_iter.rs index 318c7a7..9023078 100644 --- a/tests/test_iter.rs +++ b/tests/test_iter.rs @@ -1,8 +1,7 @@ -use chainerror::prelude::v1::*; +use chainerror::Context; use std::error::Error; use std::io; -#[cfg(not(feature = "display-cause"))] #[test] fn test_iter() -> Result<(), Box> { use std::fmt::Write; @@ -18,13 +17,13 @@ fn test_iter() -> Result<(), Box> { let mut res = String::new(); for e in err.iter() { - write!(res, "{}", e.to_string())?; + write!(res, "{}", e)?; } assert_eq!(res, "654321entity not found"); let io_error: Option<&io::Error> = err .iter() - .filter_map(Error::downcast_ref::) + .filter_map(::downcast_ref::) .next(); assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound); @@ -32,9 +31,8 @@ fn test_iter() -> Result<(), Box> { Ok(()) } -#[cfg(feature = "display-cause")] #[test] -fn test_iter() -> Result<(), Box> { +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"); @@ -44,13 +42,13 @@ fn test_iter() -> Result<(), Box> { let err = err.context("6"); let err = err.err().unwrap(); - let res = err.to_string(); + let res = format!("{:#}", err); - assert_eq!(res, "6\nCaused by:\n5\nCaused by:\n4\nCaused by:\n3\nCaused by:\n2\nCaused by:\n1\nCaused by:\nentity not found"); + 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(Error::downcast_ref::) + .filter_map(::downcast_ref::) .next(); assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound); @@ -88,7 +86,7 @@ fn test_root_cause() -> Result<(), Box> { 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(Error::downcast_ref::); + let io_error: Option<&io::Error> = err.and_then(::downcast_ref::); assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound);