Compare commits

..

76 commits

Author SHA1 Message Date
Harald Hoyer 28eb28e47d
refactor: simplify downcasting logic with std::mem::transmute (#20)
Simplified the downcasting implementations by replacing pointer casting
logic with `std::mem::transmute`, ensuring type safety after matching.
Added tests to validate various downcasting behaviors for both owned and
trait-object error scenarios, improving overall reliability and test
coverage.
2025-03-31 14:55:16 +02:00
Harald Hoyer 4c42d37598 refactor: simplify downcasting logic with std::mem::transmute
Simplified the downcasting implementations by replacing pointer casting logic with `std::mem::transmute`, ensuring type safety after matching. Added tests to validate various downcasting behaviors for both owned and trait-object error scenarios, improving overall reliability and test coverage.

Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2025-03-31 14:53:52 +02:00
Harald Hoyer 82a4164780
tests: add tests for annotated error display, debug, and chaining (#19)
Introduced tests to verify the behavior of `AnnotatedError` in display,
debug, and error chaining scenarios. Ensured proper formatting for
standalone errors and preservation of the error chain for wrapped
errors.
2025-03-31 14:16:35 +02:00
Harald Hoyer 75b7fdf363 tests: add tests for annotated error display, debug, and chaining
Introduced tests to verify the behavior of `AnnotatedError` in display, debug, and error chaining scenarios. Ensured proper formatting for standalone errors and preservation of the error chain for wrapped errors.
2025-03-31 14:14:43 +02:00
Harald Hoyer d164662537
doc: add usage example for error handling in main function (#18)
This commit adds a usage example demonstrating how to handle errors
returned by the `func1` function in the main function. The example
provides clarity on practical error handling and makes the documentation
more comprehensive for users.
2025-03-31 14:05:37 +02:00
Harald Hoyer 5390007cbe doc: add usage example for error handling in main function
This commit adds a usage example demonstrating how to handle errors returned by the `func1` function in the main function. The example provides clarity on practical error handling and makes the documentation more comprehensive for users.
2025-03-31 14:04:15 +02:00
Harald Hoyer 354f7b92ed
tests: add comprehensive unit tests for error handling utilities (#17)
This commit introduces a series of unit tests to validate various error
handling functionalities, including error chaining, root cause
extraction, display/debug formatting, annotation, context mapping,
downcasting, and custom error kinds. These tests improve code
reliability and ensure expected behavior across different error
scenarios.
2025-03-31 13:57:25 +02:00
Harald Hoyer 9aa0183d65 tests: add comprehensive unit tests for error handling utilities
This commit introduces a series of unit tests to validate various error handling functionalities, including error chaining, root cause extraction, display/debug formatting, annotation, context mapping, downcasting, and custom error kinds. These tests improve code reliability and ensure expected behavior across different error scenarios.
2025-03-31 13:54:52 +02:00
Harald Hoyer 46bf63fd32
chore: update Rust installation in coverage workflow (#16)
Replaced manual Rust installation with dtolnay/rust-toolchain action for
better maintainability and clarity. Added necessary components like
llvm-tools-preview to support code coverage generation. These changes
simplify the workflow setup.
2025-03-31 13:23:23 +02:00
Harald Hoyer 5cb96eeee3 chore: update Rust installation in coverage workflow
Replaced manual Rust installation with dtolnay/rust-toolchain action for better maintainability and clarity. Added necessary components like llvm-tools-preview to support code coverage generation. These changes simplify the workflow setup.
2025-03-31 13:21:45 +02:00
Harald Hoyer 0cee763264
chore: update coverage workflow to simplify and modernize setup (#15)
Revised the GitHub Actions workflow for code coverage by updating
dependencies, using modern, maintained actions, and improving
configuration clarity. Streamlined Rust installation and replaced manual
steps with dedicated actions for better reliability. Adjusted Codecov
settings for stricter error handling.
2025-03-31 13:15:11 +02:00
Harald Hoyer 74f1dd4314 chore: update coverage workflow to simplify and modernize setup
Revised the GitHub Actions workflow for code coverage by updating dependencies, using modern, maintained actions, and improving configuration clarity. Streamlined Rust installation and replaced manual steps with dedicated actions for better reliability. Adjusted Codecov settings for stricter error handling.

Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2025-03-31 13:14:20 +02:00
Harald Hoyer 24382fbaed
chore: Release chainerror version 1.0.0 2023-07-28 17:11:10 +02:00
Harald Hoyer ae10e2e14f
doc: README.md changed text to console (#14) 2023-07-28 17:10:33 +02:00
Harald Hoyer c630f84690
doc: README.md changed text to console
Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-28 17:09:18 +02:00
Harald Hoyer 25ebe8333f
0.9 (#13)
prepare for 1.0
2023-07-28 17:08:42 +02:00
Harald Hoyer 9e03541ac1
doc: extend README.md with display formats
Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-28 17:06:57 +02:00
Harald Hoyer 46b7f58e72
feat: add annotate() method to Context
to just annotate the passed error with location data

Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-28 17:06:10 +02:00
Harald Hoyer 101d2074e1
feat: removed prelude
It was causing the rust compiler to output the renamed structs in error
messages confusing users who don't know about the old renamed ones.

Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-28 17:04:39 +02:00
Harald Hoyer aaca6945b0
feat: add new(Into<String>) method for str_context! types
Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-28 16:32:46 +02:00
Harald Hoyer cb9465f0df
chore: remove obsolete README.tpl
Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-28 16:22:15 +02:00
Harald Hoyer 82257c881a
docs: use new names
Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-28 16:22:00 +02:00
Harald Hoyer 55c16d7867
tests: use new names
Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-28 16:20:55 +02:00
Harald Hoyer 3ab04cca25
feat: cleanup names
- ResultTrait -> Context
- ChainErrorDown -> ErrorDown
- derive_err_kind -> err_kind
- derive_str_context -> str_context
- add `prelude::v2` with only the traits

Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-28 16:18:54 +02:00
Harald Hoyer cb2e1509e5
chore: remove more Chain.. occurences
Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-28 15:28:27 +02:00
Harald Hoyer cacfb37d44
chore: Release chainerror version 0.8.2 2023-07-28 15:18:14 +02:00
Harald Hoyer 5ddc595e3f
ci: use ubuntu-latest (#12)
The ubuntu-18.04 environment is deprecated.
2023-07-28 15:15:21 +02:00
Harald Hoyer 11aeeb17c3
ci: use ubuntu-latest
The ubuntu-18.04 environment is deprecated.

Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-28 15:13:50 +02:00
Harald Hoyer 7267a08b68
fix: re-add the basic doc test (#11)
and more doc improvements
2023-07-28 15:09:35 +02:00
Harald Hoyer a116310c4d
doc: improvements
- remove `Chain…` mentions in the docs
- add doc links
- add rustdoc feature to scrape the examples for code

Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-28 15:06:31 +02:00
Harald Hoyer 968c83983a
fix: re-add the basic doc test
It got lost with the `README.md` conversion.

Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-28 15:04:29 +02:00
Harald Hoyer 9e7492f67f
chore: Release chainerror version 0.8.1-alpha.1 2023-07-27 15:29:06 +02:00
Harald Hoyer 87bac108d2
chore: Release chainerror version 0.8.0 2023-07-27 15:28:18 +02:00
Harald Hoyer 005d46bf4d
0.8 changes (#10)
* use `#[track_caller]`
* removed feature `display-cause`
* remove ChainErrorFrom and IntoChainError, add From
* cleanup
2023-07-27 15:00:07 +02:00
Harald Hoyer cf62d1a9f9
chore: remove need for cargo readme
Just use `#![doc = include_str!("../README.md")]`

Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-27 14:59:05 +02:00
Harald Hoyer 165c1b939c
chore: suppress clippy errors in tutorial
code is in this state by purpose

Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-27 14:37:19 +02:00
Harald Hoyer 1327575aa9
feat!: remove Chain prefix from Error and Result
like `anyhow`

Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-27 14:36:52 +02:00
Harald Hoyer f5c8afce0d
feat: remove ChainErrorFrom and IntoChainError, add From
ChainErrorFrom and IntoChainError are not needed anymore with the
`#[track_caller]` feature.

Now, a proper `From<T> for ChainError<T>` can be implemented.
2023-07-27 14:32:30 +02:00
Harald Hoyer bdfec08228
feat: add inline to context and map_context methods 2023-07-27 14:32:26 +02:00
Harald Hoyer 4eae3da3c1
feat: removed feature display-cause
`display-cause` can be turned on with the `{:#}` format specifier
2023-07-27 14:32:21 +02:00
Harald Hoyer b2a62b2f55
chore: Release chainerror version 0.7.2-alpha.1 2023-07-27 14:29:29 +02:00
Harald Hoyer e90072f079
chore: Release chainerror version 0.7.1 2023-07-27 14:24:20 +02:00
Harald Hoyer 1a102e1b5c
Merge pull request #9 from haraldh/license
chore: fix License to SPDX syntax
2023-07-27 14:15:37 +02:00
Harald Hoyer a34929600e
chore: fix License to SPDX syntax
Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-27 14:13:34 +02:00
Harald Hoyer 06da605462
Merge pull request #8 from haraldh/CI
CI update + Doc test fix
2023-07-27 14:07:21 +02:00
Harald Hoyer 95c5a02d50
test: rewrite doc test
Line numbering has changed in Rust 1.71 in the doc tests, due to an
extra inserted `#[allow(unused_extern_crates)]` line.

Don't test for the exact line number anymore.

Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-27 14:04:02 +02:00
Harald Hoyer 376e133836
ci: use codecov for coverage
Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-27 14:03:18 +02:00
Harald Hoyer bb5f372a92
fix: use dtolnay/rust-toolchain
instead of `actions-rs/toolchain`

Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-27 14:03:15 +02:00
Harald Hoyer d60cdf9cdb
fix: clippy
Signed-off-by: Harald Hoyer <harald@hoyer.xyz>
2023-07-27 14:03:12 +02:00
Harald Hoyer 05085229be
chore: fix github pages deploy
set-env is deprecated ... remove the caching stuff
2021-02-02 09:45:23 +01:00
Harald Hoyer d577243d8e
(cargo-release) start next development iteration 0.7.1-alpha.0 2021-02-02 09:12:07 +01:00
Harald Hoyer ee385c1fe0
(cargo-release) version 0.7.0 2021-02-02 09:11:42 +01:00
Jakub Duchniewicz 259712931f Rust PR 75180 fix. 2021-02-01 16:51:34 +01:00
Harald Hoyer 11b30b1c6f
Merge pull request #5 from haraldh/dependabot/add-v2-config-file
Create Dependabot config file
2020-09-02 21:36:53 +02:00
dependabot-preview[bot] 523b8633fb
Create Dependabot config file 2020-09-02 19:31:40 +00:00
Harald Hoyer 050c1bf99e
Merge pull request #4 from haraldh/release
Release
2020-09-01 23:02:57 +02:00
Harald Hoyer cd0fc471cb
(cargo-release) start next development iteration 0.6.2-alpha.0 2020-09-01 22:59:58 +02:00
Harald Hoyer 1003671be3
(cargo-release) version 0.6.1 2020-09-01 22:59:44 +02:00
Harald Hoyer a7925b35bd
Merge pull request #3 from haraldh/better_readme
Better introduction examples.
2020-09-01 22:59:10 +02:00
Harald Hoyer f586b52675
Better introduction examples. 2020-09-01 22:55:29 +02:00
Harald Hoyer b6ecfdb6d7
Merge pull request #2 from haraldh/new_branch
New branch
2020-09-01 21:51:22 +02:00
Harald Hoyer 455b01d3fb
(cargo-release) start next development iteration 0.6.1-alpha.0 2020-09-01 21:47:49 +02:00
Harald Hoyer a78b9a22f7
(cargo-release) version 0.6.0 2020-09-01 21:47:29 +02:00
Harald Hoyer 56c13d0be5
update README.md 2020-09-01 21:43:29 +02:00
Harald Hoyer 14c67e1903
ignore first example 2020-09-01 21:42:17 +02:00
Harald Hoyer 1654624c08
more documentation and formatting 2020-09-01 21:34:46 +02:00
Harald Hoyer ed710fada3 rename cherr() to context() 2020-09-01 21:03:57 +02:00
Harald Hoyer 2af5fb7ad6
doc fix 2020-08-28 17:11:56 +02:00
Harald Hoyer 9d8f316db2
add README.tpl template 2020-08-28 17:07:19 +02:00
Harald Hoyer ebbdddf971
Cargo.toml: fixed github workflow badge 2020-08-28 17:01:50 +02:00
Harald Hoyer 356335e807
use #[track_caller] and Location 2020-08-28 16:58:58 +02:00
Harald Hoyer e2815321bc
add github CI and coveralls badges to README 2020-06-03 14:42:33 +02:00
Harald Hoyer fb99427f19
coverage: don't use all features 2020-06-03 14:42:29 +02:00
Harald Hoyer 935eb658cf
make test pass with --all-features 2020-06-03 14:42:24 +02:00
Harald Hoyer 241502de75
remove main in doc tests and cargo fmt the doc tests 2020-06-03 14:19:48 +02:00
Harald Hoyer 174d81c8d9
add code coverage via cargo tarpaulin and coveralls 2020-06-03 14:01:44 +02:00
40 changed files with 1139 additions and 1088 deletions

7
.github/dependabot.yml vendored Normal file
View file

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

38
.github/workflows/coverage.yml vendored Normal file
View file

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

View file

@ -7,34 +7,19 @@ on:
jobs: jobs:
deploy: deploy:
runs-on: ubuntu-18.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - 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 - name: Build mdbook
run: cargo install 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 - name: Build
run: mdbook build run: mdbook build

View file

@ -1,15 +1,72 @@
name: Rust name: Rust
on: [push] 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: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
version:
- 1.54.0
- stable
- beta
- nightly
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v1
- name: Build - name: Install toolchain
run: cargo build --verbose uses: dtolnay/rust-toolchain@master
- name: Run tests with:
run: cargo test --verbose 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

View file

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

View file

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

273
README.md
View file

@ -1,129 +1,186 @@
# chainerror
[![Build Status](https://travis-ci.org/haraldh/chainerror.svg?branch=master)](https://travis-ci.org/haraldh/chainerror)
[![Crate](https://img.shields.io/crates/v/chainerror.svg)](https://crates.io/crates/chainerror) [![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/) [![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/chainerror/)
[![Coverage Status](https://codecov.io/gh/haraldh/chainerror/branch/master/graph/badge.svg?token=HGLJFGA11B)](https://codecov.io/gh/haraldh/chainerror)
![Maintenance](https://img.shields.io/badge/maintenance-activly--developed-brightgreen.svg)
`chainerror` provides an error backtrace like `failure` without doing a real backtrace, so even after you `strip` your # chainerror
`chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your
binaries, you still have the error backtrace. binaries, you still have the error backtrace.
`chainerror` has no dependencies! Having nested function returning errors, the output doesn't tell where the error originates from.
`chainerror` uses `.source()` of `std::error::Error` along with `line()!` and `file()!` to provide a nice debug error backtrace. ```rust
It encapsulates all types, which have `Display + Debug` and can store the error cause internally. use std::path::PathBuf;
Along with the `ChainError<T>` struct, `chainerror` comes with some useful helper macros to save a lot of typing. type BoxedError = Box<dyn std::error::Error + Send + Sync>;
fn read_config_file(path: PathBuf) -> Result<(), BoxedError> {
Debug information is worth it! // do stuff, return other errors
let _buf = std::fs::read_to_string(&path)?;
Now continue reading the // do stuff, return other errors
[Tutorial](https://haraldh.github.io/chainerror/tutorial1.html)
## Example:
Output:
~~~
$ cargo run -q --example example
Main Error Report: func1 error calling func2
Error reported by Func2Error: func2 error: calling func3
The root cause was: std::io::Error: Kind(
NotFound
)
Debug Error:
examples/example.rs:45: func1 error calling func2
Caused by:
examples/example.rs:20: Func2Error(func2 error: calling func3)
Caused by:
examples/example.rs:13: Error reading 'foo.txt'
Caused by:
Kind(NotFound)
~~~
~~~rust,ignore
use chainerror::*;
use std::error::Error;
use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<Error + Send + Sync>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }
fn func3() -> Result<(), Box<Error + Send + Sync>> { fn process_config_file() -> Result<(), BoxedError> {
let filename = "foo.txt"; // do stuff, return other errors
do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; let _buf = read_config_file("foo.txt".into())?;
Ok(()) // do stuff, return other errors
}
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(()) Ok(())
} }
fn main() { fn main() {
if let Err(e) = func1() { if let Err(e) = process_config_file() {
match e.kind() { eprintln!("Error:\n{:?}", e);
Func1Error::Func2 => eprintln!("Main Error Report: func1 error calling func2"),
Func1Error::IO(filename) => {
eprintln!("Main Error Report: func1 error reading '{}'", filename)
}
}
if let Some(e) = e.find_chain_cause::<Func2Error>() {
eprintln!("\nError reported by Func2Error: {}", e)
}
if let Some(e) = e.root_cause() {
let ioerror = e.downcast_ref::<io::Error>().unwrap();
eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror);
}
eprintln!("\nDebug Error:\n{:?}", e);
} }
} }
```
~~~ This gives the output:
```console
Error:
Os { code: 2, kind: NotFound, message: "No such file or directory" }
```
and you have no idea where it comes from.
## Features
`no-fileline` With `chainerror`, you can supply a context and get a nice error backtrace:
: completely turn off storing filename and line
`display-cause` ```rust
: turn on printing a backtrace of the errors in `Display` use chainerror::Context as _;
use std::path::PathBuf;
`no-debug-cause` type BoxedError = Box<dyn std::error::Error + Send + Sync>;
: turn off printing a backtrace of the errors in `Debug` fn read_config_file(path: PathBuf) -> Result<(), BoxedError> {
// do stuff, return other errors
let _buf = std::fs::read_to_string(&path).context(format!("Reading file: {:?}", &path))?;
// do stuff, return other errors
Ok(())
}
fn process_config_file() -> Result<(), BoxedError> {
// do stuff, return other errors
let _buf = read_config_file("foo.txt".into()).context("read the config file")?;
// do stuff, return other errors
Ok(())
}
fn main() {
if let Err(e) = process_config_file() {
eprintln!("Error:\n{:?}", e);
}
}
```
with the output:
```console
Error:
examples/simple.rs:14:51: read the config file
Caused by:
examples/simple.rs:7:47: Reading file: "foo.txt"
Caused by:
Os { code: 2, kind: NotFound, message: "No such file or directory" }
```
`chainerror` uses `.source()` of `std::error::Error` along with `#[track_caller]` and `Location` to provide a nice debug error backtrace.
It encapsulates all types, which have `Display + Debug` and can store the error cause internally.
Along with the `Error<T>` struct, `chainerror` comes with some useful helper macros to save a lot of typing.
`chainerror` has no dependencies!
Debug information is worth it!
## Multiple Output Formats
`chainerror` supports multiple output formats, which can be selected with the different format specifiers:
* `{}`: Display
```console
func1 error calling func2
```
* `{:#}`: Alternative Display
```console
func1 error calling func2
Caused by:
func2 error: calling func3
Caused by:
(passed error)
Caused by:
Error reading 'foo.txt'
Caused by:
entity not found
```
* `{:?}`: Debug
```console
examples/example.rs:50:13: func1 error calling func2
Caused by:
examples/example.rs:25:13: Func2Error(func2 error: calling func3)
Caused by:
examples/example.rs:18:13: (passed error)
Caused by:
examples/example.rs:13:18: Error reading 'foo.txt'
Caused by:
Kind(NotFound)
```
* `{:#?}`: Alternative Debug
```console
Error<example::Func1Error> {
occurrence: Some(
"examples/example.rs:50:13",
),
kind: func1 error calling func2,
source: Some(
Error<example::Func2Error> {
occurrence: Some(
"examples/example.rs:25:13",
),
kind: Func2Error(func2 error: calling func3),
source: Some(
Error<chainerror::AnnotatedError> {
occurrence: Some(
"examples/example.rs:18:13",
),
kind: (passed error),
source: Some(
Error<alloc::string::String> {
occurrence: Some(
"examples/example.rs:13:18",
),
kind: "Error reading 'foo.txt'",
source: Some(
Kind(
NotFound,
),
),
},
),
},
),
},
),
}
```
## Tutorial
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>)
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.

1
booksrc/LICENSE-APACHE Symbolic link
View file

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

1
booksrc/LICENSE-MIT Symbolic link
View file

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

View file

@ -5,7 +5,7 @@
- [Simple String Errors](tutorial1.md) - [Simple String Errors](tutorial1.md)
- [Simple Chained String Errors](tutorial2.md) - [Simple Chained String Errors](tutorial2.md)
- [Mapping Errors](tutorial3.md) - [Mapping Errors](tutorial3.md)
- [Saving coding chars](tutorial4.md) - [More Information](tutorial4.md)
- [The source() of Errors](tutorial5.md) - [The source() of Errors](tutorial5.md)
- [Downcast the Errors](tutorial6.md) - [Downcast the Errors](tutorial6.md)
- [The root cause of all Errors](tutorial7.md) - [The root cause of all Errors](tutorial7.md)

View file

@ -9,7 +9,7 @@ this only
prints out the last `Error`. prints out the last `Error`.
~~~ ~~~
Error: StringError("func1 error") Error: "func1 error"
~~~ ~~~
The next chapters of this tutorial show how `chainerror` adds more information The next chapters of this tutorial show how `chainerror` adds more information

View file

@ -1,20 +1,16 @@
# ErrorKind to the rescue # ErrorKind to the rescue
To cope with different kind of errors, we introduce the kind of an error `Func1ErrorKind` with an enum. To cope with different kind of errors, we introduce the `kind` of an error `Func1ErrorKind` with an enum.
Because we derive `Debug` and implement `Display` our `Func1ErrorKind` enum, this enum can be used as Because we derive `Debug` and implement `Display` our `Func1ErrorKind` enum, this enum can be used as
a `std::error::Error`. a `std::error::Error`.
Not using `String` errors anymore, the `cherr!()` macro seen in the beginning of
the tutorial has to be used again.
Only returning `Func1ErrorKind` in `func1()` now let us get rid of `Result<(), Box<Error + Send + Sync>>` and we can Only returning `Func1ErrorKind` in `func1()` now let us get rid of `Result<(), Box<Error + Send + Sync>>` and we can
use `ChainResult<(), Func1ErrorKind>`. use `ChainResult<(), Func1ErrorKind>`.
In `main` we can now directly use the methods of `ChainError<T>` without downcasting the error first. In `main` we can now directly use the methods of `chainerror::Error<T>` without downcasting the error first.
Also a nice `match` on `ChainError<T>.kind()` is now possible, which returns `&T`, meaning Also, a nice `match` on `chainerror::Error<T>.kind()` is now possible, which returns `&T`, meaning `&Func1ErrorKind` here.
`&Func1ErrorKind` here.
~~~rust ~~~rust
{{#include ../examples/tutorial10.rs}} {{#include ../examples/tutorial10.rs}}

View file

@ -1,6 +1,6 @@
# Debug for the ErrorKind # Debug for the ErrorKind
One small improvement at the end of the tutorial is to fix the debug output of One small improvement is to fix the debug output of
`Func1ErrorKind`. As you probably noticed, the output doesn't say much of the enum. `Func1ErrorKind`. As you probably noticed, the output doesn't say much of the enum.
~~~ ~~~
@ -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. To create your own Errors, you might find [crates](https://crates.io) which create enum `Display+Debug` via derive macros.
Also noteworthy is [custom_error](https://crates.io/crates/custom_error) to define your custom errors, Also, noteworthy is [custom_error](https://crates.io/crates/custom_error) to define your custom errors,
which can then be used with `chainerror`. which can then be used with `chainerror`.
~~~rust ~~~rust

View file

@ -1,6 +1,6 @@
# Deref for the ErrorKind # Deref for the ErrorKind
Because ChainError<T> implements Deref to &T, we can also match on `*e` instead of `e.kind()` Because chainerror::Error<T> implements Deref to &T, we can also match on `*e` instead of `e.kind()`
or call a function with `&e` or call a function with `&e`
~~~rust ~~~rust
{{#include ../examples/tutorial12.rs}} {{#include ../examples/tutorial12.rs}}

View file

@ -1,6 +1,6 @@
# Writing a library # Writing a library
I would advise to only expose an `mycrate::ErrorKind` and type alias `mycrate::Error` to `ChainError<mycrate::ErrorKind>` I would advise to only expose an `mycrate::ErrorKind` and type alias `mycrate::Error` to `chainerror::Error<mycrate::ErrorKind>`
so you can tell your library users to use the `.kind()` method as `std::io::Error` does. 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 If you later decide to make your own `Error` implementation, your library users don't

View file

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

View file

@ -1,6 +1,6 @@
# Mapping Errors # Mapping Errors
Now let's get more rust idiomatic by using `.map_err()`. Now let's get more rust idiomatic by using `.context()` directly on the previous `Result`.
~~~rust ~~~rust
{{#include ../examples/tutorial3.rs}} {{#include ../examples/tutorial3.rs}}
@ -14,13 +14,13 @@ If you compare the output to the previous example, you will see,
that: that:
~~~ ~~~
Error: src/main.rs:19: "func1 error" Error: examples/tutorial2.rs:20:16: func1 error
~~~ ~~~
changed to just: 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. This is, because we caught the error of `func1()` in `main()` and print it out ourselves.

View file

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

View file

@ -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 Note, that because we changed the output of the error in `main()` from
`Debug` to `Display`, we don't see the error backtrace with filename and line number. `Debug` to `Display`, we don't see the error backtrace with filename and line number.
To enable the `Display` backtrace, you have to enable the feature `display-cause` for `chainerror`. To use the `Display` backtrace, you have to use the alternative display format output `{:#}`.

View file

@ -4,15 +4,15 @@
~~~rust,ignore ~~~rust,ignore
fn is_chain<T: 'static + Display + Debug>(&self) -> bool fn is_chain<T: 'static + Display + Debug>(&self) -> bool
fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&ChainError<T>> fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&chainerror::Error<T>>
fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut ChainError<T>> fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut chainerror::Error<T>>
fn root_cause(&self) -> Option<&(dyn Error + 'static)> fn root_cause(&self) -> Option<&(dyn Error + 'static)>
fn find_cause<U: Error + 'static>(&self) -> Option<&U> fn find_cause<U: Error + 'static>(&self) -> Option<&U>
fn find_chain_cause<U: Error + 'static>(&self) -> Option<&ChainError<U>> fn find_chain_cause<U: Error + 'static>(&self) -> Option<&chainerror::Error<U>>
fn kind<'a>(&'a self) -> &'a T fn kind<'a>(&'a self) -> &'a T
~~~ ~~~
Using `downcast_chain_ref::<String>()` gives a `ChainError<String>`, which can be used Using `downcast_chain_ref::<String>()` gives a `chainerror::Error<String>`, which can be used
to call `.find_cause::<io::Error>()`. to call `.find_cause::<io::Error>()`.
~~~rust,ignore ~~~rust,ignore

View file

@ -1,14 +1,14 @@
# Finding an Error cause # 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. "new type" pattern.
~~~rust,ignore ~~~rust,ignore
derive_str_cherr!(Func2Error); chainerror::str_context!(Func2Error);
derive_str_cherr!(Func1Error); chainerror::str_context!(Func1Error);
~~~ ~~~
Instead of `ChainError<String>` we now have `struct Func1Error(String)` and `ChainError<Func1Error>`. Instead of `chainerror::Error<String>` we now have `struct Func1Error(String)` and `chainerror::Error<Func1Error>`.
In the `main` function you can see, how we can match the different errors. In the `main` function you can see, how we can match the different errors.
@ -18,9 +18,9 @@ Also see:
~~~ ~~~
as a shortcut to as a shortcut to
~~~rust,ignore ~~~rust,ignore
if let Some(f2err) = f1err.find_cause::<ChainError<Func2Error>>() { if let Some(f2err) = f1err.find_cause::<chainerror::Error<Func2Error>>() {
~~~ ~~~
hiding the `ChainError<T>` implementation detail. hiding the `chainerror::Error<T>` implementation detail.
~~~rust ~~~rust
{{#include ../examples/tutorial8.rs}} {{#include ../examples/tutorial8.rs}}

View file

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

View file

@ -1,6 +1,8 @@
#![allow(clippy::single_match)]
#![allow(clippy::redundant_pattern_matching)]
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> {
Err(io::Error::from(io::ErrorKind::NotFound))?; Err(io::Error::from(io::ErrorKind::NotFound))?;

View file

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

View file

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

View file

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

View file

@ -1,5 +1,9 @@
#![allow(clippy::single_match)]
#![allow(clippy::redundant_pattern_matching)]
pub mod mycrate { pub mod mycrate {
use chainerror::*; use chainerror::Context as _;
use std::io; use std::io;
fn do_some_io() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> { fn do_some_io() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
@ -7,11 +11,11 @@ pub mod mycrate {
Ok(()) Ok(())
} }
derive_str_cherr!(Func2Error); chainerror::str_context!(Func2Error);
fn func2() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> { fn func2() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
let filename = "foo.txt"; let filename = "foo.txt";
do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?;
Ok(()) Ok(())
} }
@ -21,7 +25,7 @@ pub mod mycrate {
IO(String), IO(String),
} }
derive_err_kind!(Error, ErrorKind); chainerror::err_kind!(Error, ErrorKind);
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
@ -35,9 +39,9 @@ pub mod mycrate {
} }
pub fn func1() -> Result<()> { pub fn func1() -> Result<()> {
func2().map_err(|e| cherr!(e, ErrorKind::Func2))?; func2().context(ErrorKind::Func2)?;
let filename = String::from("bar.txt"); let filename = String::from("bar.txt");
do_some_io().map_err(|e| cherr!(e, ErrorKind::IO(filename)))?; do_some_io().context(ErrorKind::IO(filename))?;
Ok(()) Ok(())
} }
} }
@ -72,6 +76,8 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
} }
eprintln!("\nDebug Error:\n{:?}", e); eprintln!("\nDebug Error:\n{:?}", e);
std::process::exit(1);
} }
Ok(()) Ok(())
} }

View file

@ -1,7 +1,10 @@
#![allow(clippy::single_match)]
#![allow(clippy::redundant_pattern_matching)]
pub mod mycrate { pub mod mycrate {
use std::error::Error as StdError; use std::error::Error as StdError;
use func2mod::{do_some_io, func2}; use self::func2mod::{do_some_io, func2};
pub mod func2mod { pub mod func2mod {
use std::error::Error as StdError; use std::error::Error as StdError;
@ -27,7 +30,7 @@ pub mod mycrate {
} }
} }
macro_rules! mcherr { macro_rules! mcontext {
( $k:expr ) => {{ ( $k:expr ) => {{
|e| { |e| {
Error( Error(
@ -92,7 +95,7 @@ pub mod mycrate {
pub fn func2() -> std::result::Result<(), Error> { pub fn func2() -> std::result::Result<(), Error> {
let filename = "foo.txt"; let filename = "foo.txt";
do_some_io().map_err(mcherr!(ErrorKind::IO(format!( do_some_io().map_err(mcontext!(ErrorKind::IO(format!(
"Error reading '{}'", "Error reading '{}'",
filename filename
))))?; ))))?;
@ -115,7 +118,7 @@ pub mod mycrate {
} }
} }
macro_rules! mcherr { macro_rules! mcontext {
( $k:expr ) => {{ ( $k:expr ) => {{
|e| { |e| {
Error( Error(
@ -175,9 +178,9 @@ pub mod mycrate {
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
pub fn func1() -> Result<()> { pub fn func1() -> Result<()> {
func2().map_err(mcherr!(ErrorKind::Func2))?; func2().map_err(mcontext!(ErrorKind::Func2))?;
let filename = String::from("bar.txt"); let filename = String::from("bar.txt");
do_some_io().map_err(mcherr!(ErrorKind::IO(filename)))?; do_some_io().map_err(mcontext!(ErrorKind::IO(filename)))?;
Ok(()) Ok(())
} }
} }
@ -212,6 +215,8 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
} }
eprintln!("\nDebug Error:\n{:?}", e); eprintln!("\nDebug Error:\n{:?}", e);
std::process::exit(1);
} }
Ok(()) Ok(())
} }

View file

@ -1,19 +1,21 @@
pub mod mycrate { #![allow(clippy::single_match)]
use std::io; #![allow(clippy::redundant_pattern_matching)]
use chainerror::*; pub mod mycrate {
use chainerror::{Context as _, ErrorDown as _};
use std::io;
fn do_some_io(_f: &str) -> std::result::Result<(), io::Error> { fn do_some_io(_f: &str) -> std::result::Result<(), io::Error> {
Err(io::Error::from(io::ErrorKind::NotFound))?; Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(()) Ok(())
} }
derive_str_cherr!(Func2Error); chainerror::str_context!(Func2Error);
fn func2() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> { fn func2() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
let filename = "foo.txt"; let filename = "foo.txt";
do_some_io(filename) do_some_io(filename).context(Func2Error(format!("Error reading '{}'", filename)))?;
.map_err(|e| cherr!(e, Func2Error(format!("Error reading '{}'", filename))))?;
Ok(()) Ok(())
} }
@ -25,7 +27,7 @@ pub mod mycrate {
Unknown, Unknown,
} }
derive_err_kind!(Error, ErrorKind); chainerror::err_kind!(Error, ErrorKind);
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
impl std::fmt::Display for ErrorKind { impl std::fmt::Display for ErrorKind {
@ -82,21 +84,19 @@ pub mod mycrate {
} }
pub fn func1() -> Result<()> { pub fn func1() -> Result<()> {
func2().map_err(|e| cherr!(e, ErrorKind::from(&e)))?; func2().map_err(|e| ErrorKind::from(&e))?;
let filename = "bar.txt"; let filename = "bar.txt";
do_some_io(filename) do_some_io(filename).map_context(|e| ErrorKind::from_io_error(e, filename.into()))?;
.map_err(|e| cherr!(e, ErrorKind::from_io_error(&e, filename.into())))?; do_some_io(filename).map_context(|_| ErrorKind::IO(filename.into()))?;
do_some_io(filename).map_err(|e| cherr!(e, ErrorKind::IO(filename.into())))?; do_some_io(filename).map_context(|e| ErrorKind::from(e))?;
do_some_io(filename).map_err(|e| cherr!(e, ErrorKind::from(&e)))?;
do_some_io(filename).map_err(minto_cherr!(ErrorKind))?;
Ok(()) Ok(())
} }
pub fn super_func1() -> Result<()> { pub fn super_func1() -> Result<()> {
func1().map_err(minto_cherr!(ErrorKind))?; func1().map_context(|e| ErrorKind::from(e))?;
Ok(()) Ok(())
} }
} }
@ -133,6 +133,8 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
} }
eprintln!("\nDebug Error:\n{:?}", e); eprintln!("\nDebug Error:\n{:?}", e);
std::process::exit(1);
} }
Ok(()) Ok(())
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,10 @@
use chainerror::*; #![allow(clippy::single_match)]
#![allow(clippy::redundant_pattern_matching)]
use chainerror::Context as _;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> { fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> {
Err(io::Error::from(io::ErrorKind::NotFound))?; Err(io::Error::from(io::ErrorKind::NotFound))?;
@ -10,12 +13,12 @@ fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> {
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> { fn func2() -> Result<(), Box<dyn Error + Send + Sync>> {
let filename = "foo.txt"; let filename = "foo.txt";
do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; do_some_io().context(format!("Error reading '{}'", filename))?;
Ok(()) Ok(())
} }
fn func1() -> Result<(), Box<dyn Error + Send + Sync>> { fn func1() -> Result<(), Box<dyn Error + Send + Sync>> {
func2().map_err(mstrerr!("func1 error"))?; func2().context("func1 error")?;
Ok(()) Ok(())
} }
@ -35,6 +38,7 @@ fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
} }
s = c; s = c;
} }
std::process::exit(1);
} }
Ok(()) Ok(())
} }

View file

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

View file

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

View file

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

1314
src/lib.rs

File diff suppressed because it is too large Load diff

33
tests/test_basic.rs Normal file
View file

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

View file

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