From 356335e80740effe162c6d419186be2cec73825d Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Aug 2020 16:45:24 +0200 Subject: [PATCH] use `#[track_caller]` and `Location` --- .github/workflows/gh-pages.yml | 6 + .github/workflows/rust.yml | 87 ++++- .travis.yml | 22 -- Cargo.toml | 14 +- README.md | 193 ++++++++--- examples/example.rs | 12 +- examples/tutorial10.rs | 8 +- examples/tutorial11.rs | 8 +- examples/tutorial12.rs | 8 +- examples/tutorial13.rs | 8 +- examples/tutorial15.rs | 17 +- examples/tutorial2.rs | 6 +- examples/tutorial3.rs | 6 +- examples/tutorial4.rs | 6 +- examples/tutorial5.rs | 6 +- examples/tutorial6.rs | 6 +- examples/tutorial7.rs | 6 +- examples/tutorial8.rs | 6 +- examples/tutorial9.rs | 8 +- src/lib.rs | 568 +++++++++++---------------------- tests/test_iter.rs | 62 ++-- 21 files changed, 515 insertions(+), 548 deletions(-) delete mode 100644 .travis.yml diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 36952c2..b8d27bd 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -35,6 +35,12 @@ jobs: - name: Build mdbook run: cargo install mdbook + - name: Build cargo-readme + run: cargo install cargo-readme + + - name: Build README.md + run: cargo readme > README.md + - name: Build run: mdbook build diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 312cbfd..9e148b2 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,15 +1,86 @@ 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: build: - runs-on: ubuntu-latest - + strategy: + matrix: + version: + - 1.46.0 + - stable + - beta + - nightly steps: - - uses: actions/checkout@v2 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose + - uses: actions/checkout@v1 + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.version }} + default: true + 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: actions-rs/toolchain@v1 + 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: actions-rs/toolchain@v1 + with: + components: clippy + toolchain: stable + profile: minimal + override: true + - uses: actions-rs/cargo@v1 + 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/.travis.yml b/.travis.yml deleted file mode 100644 index b39f048..0000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: rust - -branches: - except: - - gh-pages - -rust: -- stable -- nightly - -os: -- linux -- windows - -script: -- cargo build --all -- cargo test --all - -matrix: - allow_failures: - - rust: nightly - fast_finish: true diff --git a/Cargo.toml b/Cargo.toml index 327757b..6224d65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chainerror" -version = "0.5.0" +version = "0.6.0-alpha.0" authors = ["Harald Hoyer "] edition = "2018" license = "MIT/Apache-2.0" @@ -16,10 +16,14 @@ exclude = [ ".gitignore", "examples/*", "booksrc/*", "book.toml", "theme/*", "git-deploy-branch.sh", ".travis.yml" ] [badges] -travis-ci = { repository = "haraldh/chainerror" } +# See https://doc.rust-lang.org/cargo/reference/manifest.html#the-badges-section +github = { repository = "haraldh/chainerror", workflow = "rust" } +maintenance = { status = "actively-developed" } +is-it-maintained-issue-resolution = { repository = "haraldh/chainerror" } +is-it-maintained-open-issues = { repository = "haraldh/chainerror" } [features] -default = [ ] -no-fileline = [] +default = [ "location", "debug-cause" ] +location = [] display-cause = [] -no-debug-cause = [] +debug-cause = [] diff --git a/README.md b/README.md index 78837ee..9b17c55 100644 --- a/README.md +++ b/README.md @@ -1,69 +1,148 @@ -# chainerror -[![Rust](https://github.com/haraldh/chainerror/workflows/Rust/badge.svg)](https://github.com/haraldh/chainerror/actions?query=workflow%3ARust) -[![Build Status](https://travis-ci.org/haraldh/chainerror.svg?branch=master)](https://travis-ci.org/haraldh/chainerror) -[![Coverage Status](https://coveralls.io/repos/github/haraldh/chainerror/badge.svg?branch=master)](https://coveralls.io/github/haraldh/chainerror?branch=master) -[![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/) +[![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") +![Maintenance](https://img.shields.io/badge/maintenance-activly--developed-brightgreen.svg) -`chainerror` provides an error backtrace like `failure` without doing a real backtrace, so even after you `strip` your +# chainerror + +`chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your binaries, you still have the error backtrace. `chainerror` has no dependencies! -`chainerror` uses `.source()` of `std::error::Error` along with `line()!` and `file()!` to provide a nice debug error backtrace. +`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! -Now continue reading the -[Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) +### Features -## Example: -Output: +`default = [ "location", "debug-cause" ]` -~~~ +`location` +: store the error location + +`display-cause` +: turn on printing a backtrace of the errors in `Display` + +`debug-cause` +: print a backtrace of the errors in `Debug` + + +## Tutorial + +Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) + +## Examples + +```console $ 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 +examples/example.rs:46:13: func1 error calling func2 Caused by: -examples/example.rs:20: Func2Error(func2 error: calling func3) +examples/example.rs:21:13: Func2Error(func2 error: calling func3) Caused by: -examples/example.rs:13: Error reading 'foo.txt' +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,ignore -use chainerror::*; +```rust +use chainerror::prelude::v1::*; use std::error::Error; use std::io; use std::result::Result; -fn do_some_io() -> Result<(), Box> { +fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -fn func3() -> Result<(), Box> { +fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + do_some_io().cherr(format!("Error reading '{}'", filename))?; + Ok(()) +} + +fn func1() -> Result<(), Box> { + func2().cherr("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().cherr(format!("Error reading '{}'", filename))?; Ok(()) } derive_str_cherr!(Func2Error); fn func2() -> ChainResult<(), Func2Error> { - func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; + func3().cherr(Func2Error("func2 error: calling func3".into()))?; Ok(()) } @@ -88,43 +167,51 @@ impl ::std::fmt::Debug for Func1Error { } fn func1() -> ChainResult<(), Func1Error> { - func2().map_err(|e| cherr!(e, Func1Error::Func2))?; + func2().cherr(Func1Error::Func2)?; let filename = String::from("bar.txt"); - do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; + do_some_io().cherr(Func1Error::IO(filename))?; Ok(()) } -fn main() { - if let Err(e) = func1() { - match e.kind() { - Func1Error::Func2 => eprintln!("Main Error Report: func1 error calling func2"), - Func1Error::IO(filename) => { - eprintln!("Main Error Report: func1 error reading '{}'", filename) - } +if let Err(e) = func1() { + assert!(match e.kind() { + Func1Error::Func2 => { + eprintln!("Main Error Report: func1 error calling func2"); + true } - - if let Some(e) = e.find_chain_cause::() { - eprintln!("\nError reported by Func2Error: {}", e) + Func1Error::IO(filename) => { + eprintln!("Main Error Report: func1 error reading '{}'", filename); + false } + }); - if let Some(e) = e.root_cause() { - let ioerror = e.downcast_ref::().unwrap(); - eprintln!("\nThe root cause was: std::io::Error: {:#?}", ioerror); - } + assert!(e.find_chain_cause::().is_some()); - eprintln!("\nDebug Error:\n{:?}", e); + 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) +"# + ); } +``` -~~~ - -## Features - -`no-fileline` -: completely turn off storing filename and line - -`display-cause` -: turn on printing a backtrace of the errors in `Display` - -`no-debug-cause` -: turn off printing a backtrace of the errors in `Debug` +License: MIT/Apache-2.0 diff --git a/examples/example.rs b/examples/example.rs index 99f965e..4044155 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -2,7 +2,7 @@ use std::error::Error; use std::io; use std::result::Result; -use chainerror::*; +use chainerror::prelude::v1::*; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; @@ -11,14 +11,14 @@ fn do_some_io() -> Result<(), Box> { fn func3() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + do_some_io().cherr(format!("Error reading '{}'", filename))?; Ok(()) } derive_str_cherr!(Func2Error); fn func2() -> ChainResult<(), Func2Error> { - func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; + func3().cherr(Func2Error(format!("func2 error: calling func3")))?; Ok(()) } @@ -43,9 +43,9 @@ impl ::std::fmt::Debug for Func1Error { } fn func1() -> ChainResult<(), Func1Error> { - func2().map_err(|e| cherr!(e, Func1Error::Func2))?; + func2().cherr(Func1Error::Func2)?; let filename = String::from("bar.txt"); - do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; + do_some_io().cherr(Func1Error::IO(filename))?; Ok(()) } @@ -68,5 +68,7 @@ fn main() { } eprintln!("\nDebug Error:\n{:?}", e); + + eprintln!("\nAlternative Debug Error:\n{:#?}", e); } } diff --git a/examples/tutorial10.rs b/examples/tutorial10.rs index cf13909..4ee8f44 100644 --- a/examples/tutorial10.rs +++ b/examples/tutorial10.rs @@ -1,4 +1,4 @@ -use chainerror::*; +use chainerror::prelude::v1::*; use std::error::Error; use std::io; use std::result::Result; @@ -12,7 +12,7 @@ derive_str_cherr!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + do_some_io().cherr(Func2Error(format!("Error reading '{}'", filename)))?; Ok(()) } @@ -33,9 +33,9 @@ impl ::std::fmt::Display for Func1ErrorKind { impl ::std::error::Error for Func1ErrorKind {} fn func1() -> ChainResult<(), Func1ErrorKind> { - func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; + func2().cherr(Func1ErrorKind::Func2)?; let filename = String::from("bar.txt"); - do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO(filename)))?; + do_some_io().cherr(Func1ErrorKind::IO(filename))?; Ok(()) } diff --git a/examples/tutorial11.rs b/examples/tutorial11.rs index 5f85c23..42e5de8 100644 --- a/examples/tutorial11.rs +++ b/examples/tutorial11.rs @@ -1,4 +1,4 @@ -use chainerror::*; +use chainerror::prelude::v1::*; use std::error::Error; use std::io; use std::result::Result; @@ -12,7 +12,7 @@ derive_str_cherr!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + do_some_io().cherr(Func2Error(format!("Error reading '{}'", filename)))?; Ok(()) } @@ -39,9 +39,9 @@ impl ::std::fmt::Debug for Func1ErrorKind { impl ::std::error::Error for Func1ErrorKind {} fn func1() -> ChainResult<(), Func1ErrorKind> { - func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; + func2().cherr(Func1ErrorKind::Func2)?; let filename = String::from("bar.txt"); - do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO(filename)))?; + do_some_io().cherr(Func1ErrorKind::IO(filename))?; Ok(()) } diff --git a/examples/tutorial12.rs b/examples/tutorial12.rs index f5e38de..ce3cea6 100644 --- a/examples/tutorial12.rs +++ b/examples/tutorial12.rs @@ -1,4 +1,4 @@ -use chainerror::*; +use chainerror::prelude::v1::*; use std::error::Error; use std::io; use std::result::Result; @@ -12,7 +12,7 @@ derive_str_cherr!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + do_some_io().cherr(Func2Error(format!("Error reading '{}'", filename)))?; Ok(()) } @@ -39,9 +39,9 @@ impl ::std::fmt::Debug for Func1ErrorKind { impl ::std::error::Error for Func1ErrorKind {} fn func1() -> ChainResult<(), Func1ErrorKind> { - func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; + func2().cherr(Func1ErrorKind::Func2)?; let filename = String::from("bar.txt"); - do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO(filename)))?; + do_some_io().cherr(Func1ErrorKind::IO(filename))?; Ok(()) } diff --git a/examples/tutorial13.rs b/examples/tutorial13.rs index c345157..29a0374 100644 --- a/examples/tutorial13.rs +++ b/examples/tutorial13.rs @@ -1,5 +1,5 @@ pub mod mycrate { - use chainerror::*; + use chainerror::prelude::v1::*; use std::io; fn do_some_io() -> std::result::Result<(), Box> { @@ -11,7 +11,7 @@ pub mod mycrate { fn func2() -> std::result::Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + do_some_io().cherr(Func2Error(format!("Error reading '{}'", filename)))?; Ok(()) } @@ -35,9 +35,9 @@ pub mod mycrate { } pub fn func1() -> Result<()> { - func2().map_err(|e| cherr!(e, ErrorKind::Func2))?; + func2().cherr(ErrorKind::Func2)?; let filename = String::from("bar.txt"); - do_some_io().map_err(|e| cherr!(e, ErrorKind::IO(filename)))?; + do_some_io().cherr(ErrorKind::IO(filename))?; Ok(()) } } diff --git a/examples/tutorial15.rs b/examples/tutorial15.rs index 581840c..5130bfc 100644 --- a/examples/tutorial15.rs +++ b/examples/tutorial15.rs @@ -1,7 +1,7 @@ pub mod mycrate { use std::io; - use chainerror::*; + use chainerror::prelude::v1::*; fn do_some_io(_f: &str) -> std::result::Result<(), io::Error> { Err(io::Error::from(io::ErrorKind::NotFound))?; @@ -12,8 +12,7 @@ pub mod mycrate { fn func2() -> std::result::Result<(), Box> { let filename = "foo.txt"; - do_some_io(filename) - .map_err(|e| cherr!(e, Func2Error(format!("Error reading '{}'", filename))))?; + do_some_io(filename).cherr(Func2Error(format!("Error reading '{}'", filename)))?; Ok(()) } @@ -82,21 +81,19 @@ pub mod mycrate { } pub fn func1() -> Result<()> { - func2().map_err(|e| cherr!(e, ErrorKind::from(&e)))?; + func2().map_err(|e| ErrorKind::from(&e))?; let filename = "bar.txt"; - do_some_io(filename) - .map_err(|e| cherr!(e, ErrorKind::from_io_error(&e, filename.into())))?; - do_some_io(filename).map_err(|e| cherr!(e, ErrorKind::IO(filename.into())))?; - do_some_io(filename).map_err(|e| cherr!(e, ErrorKind::from(&e)))?; - do_some_io(filename).map_err(minto_cherr!(ErrorKind))?; + do_some_io(filename).map_cherr(|e| ErrorKind::from_io_error(&e, filename.into()))?; + do_some_io(filename).map_cherr(|_| ErrorKind::IO(filename.into()))?; + do_some_io(filename).map_cherr(|e| ErrorKind::from(e))?; Ok(()) } pub fn super_func1() -> Result<()> { - func1().map_err(minto_cherr!(ErrorKind))?; + func1().map_cherr(|e| ErrorKind::from(e))?; Ok(()) } } diff --git a/examples/tutorial2.rs b/examples/tutorial2.rs index b13f0cc..140862f 100644 --- a/examples/tutorial2.rs +++ b/examples/tutorial2.rs @@ -1,4 +1,4 @@ -use chainerror::*; +use chainerror::prelude::v1::*; use std::error::Error; use std::io; @@ -11,14 +11,14 @@ fn do_some_io() -> Result<(), Box> { fn func2() -> Result<(), Box> { if let Err(e) = do_some_io() { - Err(cherr!(e, "func2 error"))?; + Err(e).cherr("func2 error")?; } Ok(()) } fn func1() -> Result<(), Box> { if let Err(e) = func2() { - Err(cherr!(e, "func1 error"))?; + Err(e).cherr("func1 error")?; } Ok(()) } diff --git a/examples/tutorial3.rs b/examples/tutorial3.rs index 6da067c..9096e07 100644 --- a/examples/tutorial3.rs +++ b/examples/tutorial3.rs @@ -1,4 +1,4 @@ -use chainerror::*; +use chainerror::prelude::v1::*; use std::error::Error; use std::io; @@ -10,12 +10,12 @@ fn do_some_io() -> Result<(), Box> { } fn func2() -> Result<(), Box> { - do_some_io().map_err(|e| cherr!(e, "func2 error"))?; + do_some_io().cherr("func2 error")?; Ok(()) } fn func1() -> Result<(), Box> { - func2().map_err(|e| cherr!(e, "func1 error"))?; + func2().cherr("func1 error")?; Ok(()) } diff --git a/examples/tutorial4.rs b/examples/tutorial4.rs index e1225c0..3b5fc15 100644 --- a/examples/tutorial4.rs +++ b/examples/tutorial4.rs @@ -1,4 +1,4 @@ -use chainerror::*; +use chainerror::prelude::v1::*; use std::error::Error; use std::io; use std::result::Result; @@ -10,12 +10,12 @@ fn do_some_io() -> Result<(), Box> { fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + do_some_io().cherr(format!("Error reading '{}'", filename))?; Ok(()) } fn func1() -> Result<(), Box> { - func2().map_err(mstrerr!("func1 error"))?; + func2().cherr("func1 error")?; Ok(()) } diff --git a/examples/tutorial5.rs b/examples/tutorial5.rs index cb80569..edabbbf 100644 --- a/examples/tutorial5.rs +++ b/examples/tutorial5.rs @@ -1,4 +1,4 @@ -use chainerror::*; +use chainerror::prelude::v1::*; use std::error::Error; use std::io; use std::result::Result; @@ -10,7 +10,7 @@ fn do_some_io() -> Result<(), Box> { fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + do_some_io().cherr(format!("Error reading '{}'", filename))?; Ok(()) } @@ -18,7 +18,7 @@ fn func1() -> Result<(), Box> { if let Err(e) = func2() { if let Some(s) = e.source() { eprintln!("func2 failed because of '{}'", s); - Err(e).map_err(mstrerr!("func1 error"))?; + Err(e).cherr("func1 error")?; } } Ok(()) diff --git a/examples/tutorial6.rs b/examples/tutorial6.rs index 996540c..f8c6549 100644 --- a/examples/tutorial6.rs +++ b/examples/tutorial6.rs @@ -1,4 +1,4 @@ -use chainerror::*; +use chainerror::prelude::v1::*; use std::error::Error; use std::io; use std::result::Result; @@ -10,12 +10,12 @@ fn do_some_io() -> Result<(), Box> { fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + do_some_io().cherr(format!("Error reading '{}'", filename))?; Ok(()) } fn func1() -> Result<(), Box> { - func2().map_err(mstrerr!("func1 error"))?; + func2().cherr("func1 error")?; Ok(()) } diff --git a/examples/tutorial7.rs b/examples/tutorial7.rs index b36fa8b..11658f2 100644 --- a/examples/tutorial7.rs +++ b/examples/tutorial7.rs @@ -1,4 +1,4 @@ -use chainerror::*; +use chainerror::prelude::v1::*; use std::error::Error; use std::io; use std::result::Result; @@ -10,12 +10,12 @@ fn do_some_io() -> Result<(), Box> { fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; + do_some_io().cherr(format!("Error reading '{}'", filename))?; Ok(()) } fn func1() -> Result<(), Box> { - func2().map_err(mstrerr!("func1 error"))?; + func2().cherr(format!("func1 error"))?; Ok(()) } diff --git a/examples/tutorial8.rs b/examples/tutorial8.rs index 1644a32..f55b83b 100644 --- a/examples/tutorial8.rs +++ b/examples/tutorial8.rs @@ -1,4 +1,4 @@ -use chainerror::*; +use chainerror::prelude::v1::*; use std::error::Error; use std::io; use std::result::Result; @@ -12,14 +12,14 @@ derive_str_cherr!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + do_some_io().cherr(Func2Error(format!("Error reading '{}'", filename)))?; Ok(()) } derive_str_cherr!(Func1Error); fn func1() -> Result<(), Box> { - func2().map_err(mstrerr!(Func1Error, "func1 error"))?; + func2().cherr(Func1Error(format!("func1 error")))?; Ok(()) } diff --git a/examples/tutorial9.rs b/examples/tutorial9.rs index 99b0f23..374cb77 100644 --- a/examples/tutorial9.rs +++ b/examples/tutorial9.rs @@ -1,4 +1,4 @@ -use chainerror::*; +use chainerror::prelude::v1::*; use std::error::Error; use std::io; use std::result::Result; @@ -12,7 +12,7 @@ derive_str_cherr!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; - do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + do_some_io().cherr(Func2Error(format!("Error reading '{}'", filename)))?; Ok(()) } @@ -20,9 +20,9 @@ derive_str_cherr!(Func1ErrorFunc2); derive_str_cherr!(Func1ErrorIO); fn func1() -> Result<(), Box> { - func2().map_err(mstrerr!(Func1ErrorFunc2, "func1 error calling func2"))?; + func2().cherr(Func1ErrorFunc2(format!("func1 error calling func2")))?; let filename = "bar.txt"; - do_some_io().map_err(mstrerr!(Func1ErrorIO, "Error reading '{}'", filename))?; + do_some_io().cherr(Func1ErrorIO(format!("Error reading '{}'", filename)))?; Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 817dde2..71fbf3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,21 +3,25 @@ //! //! `chainerror` has no dependencies! //! -//! `chainerror` uses `.source()` of `std::error::Error` along with `line()!` and `file()!` to provide a nice debug error backtrace. +//! `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 //! -//! `no-fileline` -//! : completely turn off storing filename and line +//! `default = [ "location", "debug-cause" ]` +//! +//! `location` +//! : store the error location //! //! `display-cause` //! : turn on printing a backtrace of the errors in `Display` //! -//! `no-debug-cause` -//! : turn off printing a backtrace of the errors in `Debug` +//! `debug-cause` +//! : print a backtrace of the errors in `Debug` //! //! //! # Tutorial @@ -26,8 +30,55 @@ //! //! # Examples //! +//! ```console +//! $ 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: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::*; +//! use chainerror::prelude::v1::*; //! use std::error::Error; //! use std::io; //! use std::result::Result; @@ -39,24 +90,24 @@ //! //! fn func2() -> Result<(), Box> { //! let filename = "foo.txt"; -//! do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; +//! do_some_io().cherr(format!("Error reading '{}'", filename))?; //! Ok(()) //! } //! //! fn func1() -> Result<(), Box> { -//! func2().map_err(mstrerr!("func1 error"))?; +//! func2().cherr("func1 error")?; //! Ok(()) //! } //! //! if let Err(e) = func1() { -//! # #[cfg(not(feature = "no-debug-cause"))] +//! # #[cfg(feature = "debug-cause")] //! #[cfg(not(windows))] //! assert_eq!( //! format!("\n{:?}\n", e), //! r#" -//! src/lib.rs:21: func1 error +//! src/lib.rs:21:13: func1 error //! Caused by: -//! src/lib.rs:16: Error reading 'foo.txt' +//! src/lib.rs:16:18: Error reading 'foo.txt' //! Caused by: //! Kind(NotFound) //! "# @@ -69,7 +120,7 @@ //! //! //! ```rust -//! use chainerror::*; +//! use chainerror::prelude::v1::*; //! use std::error::Error; //! use std::io; //! use std::result::Result; @@ -81,14 +132,14 @@ //! //! fn func3() -> Result<(), Box> { //! let filename = "foo.txt"; -//! do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; +//! do_some_io().cherr(format!("Error reading '{}'", filename))?; //! Ok(()) //! } //! //! derive_str_cherr!(Func2Error); //! //! fn func2() -> ChainResult<(), Func2Error> { -//! func3().map_err(mstrerr!(Func2Error, "func2 error: calling func3"))?; +//! func3().cherr(Func2Error("func2 error: calling func3".into()))?; //! Ok(()) //! } //! @@ -113,9 +164,9 @@ //! } //! //! fn func1() -> ChainResult<(), Func1Error> { -//! func2().map_err(|e| cherr!(e, Func1Error::Func2))?; +//! func2().cherr(Func1Error::Func2)?; //! let filename = String::from("bar.txt"); -//! do_some_io().map_err(|e| cherr!(e, Func1Error::IO(filename)))?; +//! do_some_io().cherr(Func1Error::IO(filename))?; //! Ok(()) //! } //! @@ -144,16 +195,16 @@ //! eprintln!("\nThe root cause was: std::io::Error: {:#?}", io_error); //! } //! -//! # #[cfg(not(feature = "no-debug-cause"))] +//! # #[cfg(feature = "no-debug-cause")] //! #[cfg(not(windows))] //! assert_eq!( //! format!("\n{:?}\n", e), //! r#" -//! src/lib.rs:48: func1 error calling func2 +//! src/lib.rs:48:13: func1 error calling func2 //! Caused by: -//! src/lib.rs:23: Func2Error(func2 error: calling func3) +//! src/lib.rs:23:13: Func2Error(func2 error: calling func3) //! Caused by: -//! src/lib.rs:16: Error reading 'foo.txt' +//! src/lib.rs:16:18: Error reading 'foo.txt' //! Caused by: //! Kind(NotFound) //! "# @@ -164,37 +215,29 @@ //! # } //! ``` -#![deny( - warnings, - absolute_paths_not_starting_with_crate, - deprecated_in_future, - keyword_idents, - macro_use_extern_crate, - missing_debug_implementations, - trivial_numeric_casts, - unused_extern_crates, - unused_import_braces, - unused_qualifications, - unused_results, - unused_labels, - unused_lifetimes, - unstable_features, - unreachable_pub, - future_incompatible, - missing_copy_implementations, - missing_doc_code_examples, - rust_2018_idioms, - rust_2018_compatibility -)] +#![deny(clippy::all)] +#![deny(clippy::integer_arithmetic)] +#![deny(missing_docs)] use std::any::TypeId; use std::error::Error; use std::fmt::{Debug, Display, Formatter, Result}; +use std::panic::Location; + +pub mod prelude { + //! convenience prelude + pub mod v1 { + //! convenience prelude + pub use crate::ChainErrorDown as _; + pub use crate::ResultTrait as _; + pub use crate::{derive_err_kind, derive_str_cherr, ChainError, ChainResult}; + } +} /// chains an inner error kind `T` with a causing error pub struct ChainError { - #[cfg(not(feature = "no-fileline"))] - occurrence: Option<&'static str>, + #[cfg(feature = "location")] + occurrence: Option, kind: T, error_cause: Option>, } @@ -203,13 +246,13 @@ pub struct ChainError { pub type ChainResult = std::result::Result>; impl ChainError { - #[cfg(not(feature = "no-fileline"))] - /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly + #[cfg(feature = "location")] + /// Use the `cherr()` or `map_cherr()` Result methods instead of calling this directly #[inline] pub fn new( kind: T, error_cause: Option>, - occurrence: Option<&'static str>, + occurrence: Option, ) -> Self { Self { occurrence, @@ -218,13 +261,13 @@ impl ChainError { } } - #[cfg(feature = "no-fileline")] - /// Use the `cherr!()` or `mstrerr!()` macro instead of calling this directly + #[cfg(not(feature = "location"))] + /// Use the `cherr()` or `map_cherr()` Result methods instead of calling this directly #[inline] pub fn new( kind: T, error_cause: Option>, - _occurrence: Option<&'static str>, + _occurrence: Option, ) -> Self { Self { kind, error_cause } } @@ -239,7 +282,7 @@ impl ChainError { /// # Examples /// /// ```rust - /// use chainerror::*; + /// use chainerror::prelude::v1::*; /// use std::error::Error; /// use std::io; /// @@ -252,14 +295,14 @@ impl ChainError { /// /// fn func2() -> Result<(), Box> { /// let filename = "foo.txt"; - /// do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + /// do_some_io().cherr(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// /// derive_str_cherr!(Func1Error); /// /// fn func1() -> Result<(), Box> { - /// func2().map_err(mstrerr!(Func1Error, "func1 error"))?; + /// func2().cherr(Func1Error("func1 error".into()))?; /// Ok(()) /// } /// @@ -289,7 +332,7 @@ impl ChainError { /// # Examples /// /// ```rust - /// # use chainerror::*; + /// # use chainerror::prelude::v1::*; /// # derive_str_cherr!(FooError); /// # let err = ChainError::new(String::new(), None, None); /// // Instead of writing @@ -312,7 +355,7 @@ impl ChainError { /// # Examples /// /// ```rust - /// # use chainerror::*; + /// # use chainerror::prelude::v1::*; /// # derive_str_cherr!(FooErrorKind); /// # let err = ChainError::new(String::new(), None, None); /// // Instead of writing @@ -341,7 +384,7 @@ impl ChainError { /// # Examples /// /// ```rust - /// use chainerror::*; + /// use chainerror::prelude::v1::*; /// use std::error::Error; /// use std::io; /// @@ -354,7 +397,7 @@ impl ChainError { /// /// fn func2() -> Result<(), Box> { /// let filename = "foo.txt"; - /// do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; + /// do_some_io().cherr(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// @@ -375,8 +418,8 @@ impl ChainError { /// # } /// /// fn func1() -> ChainResult<(), Func1ErrorKind> { - /// func2().map_err(|e| cherr!(e, Func1ErrorKind::Func2))?; - /// do_some_io().map_err(|e| cherr!(e, Func1ErrorKind::IO("bar.txt".into())))?; + /// func2().cherr(Func1ErrorKind::Func2)?; + /// do_some_io().cherr(Func1ErrorKind::IO("bar.txt".into()))?; /// Ok(()) /// } /// @@ -406,7 +449,54 @@ impl ChainError { } } -struct ErrorIter<'a> { +/// Convenience methods for `Result<>` to turn the error into a decorated ChainError +pub trait ResultTrait>> { + /// Decorate the error with a `kind` of type `T` and the source `Location` + fn cherr(self, kind: T) -> std::result::Result>; + + /// Decorate the error with a `kind` of type `T` produced with a `FnOnce` and the source `Location` + fn map_cherr T>( + self, + op: F, + ) -> std::result::Result>; +} + +impl>> ResultTrait + for std::result::Result +{ + #[track_caller] + fn cherr(self, kind: T) -> std::result::Result> { + match self { + Ok(t) => Ok(t), + Err(error_cause) => Err(ChainError::new( + kind, + Some(error_cause.into()), + Some(Location::caller().to_string()), + )), + } + } + + #[track_caller] + fn map_cherr T>( + self, + op: F, + ) -> std::result::Result> { + match self { + Ok(t) => Ok(t), + Err(error_cause) => { + let kind = op(&error_cause); + Err(ChainError::new( + kind, + Some(error_cause.into()), + Some(Location::caller().to_string()), + )) + } + } + } +} + +/// An iterator over all error causes/sources +pub struct ErrorIter<'a> { current: Option<&'a (dyn Error + 'static)>, } @@ -647,40 +737,56 @@ impl Display for ChainError { impl Debug for ChainError { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> Result { - #[cfg(not(feature = "no-fileline"))] - { - if let Some(ref o) = self.occurrence { - Display::fmt(o, f)?; - } - } + if f.alternate() { + let mut f = f.debug_struct(&format!("ChainError<{}>", std::any::type_name::())); - if self.is_chain::() { - Display::fmt(&self.kind, f)?; + #[cfg(feature = "location")] + let f = f.field("occurrence", &self.occurrence); + + let f = f.field("kind", &self.kind); + + #[cfg(feature = "debug-cause")] + let f = f.field("source", &self.source()); + + f.finish() } else { - Debug::fmt(&self.kind, f)?; - } - - #[cfg(not(feature = "no-debug-cause"))] - { - if let Some(e) = self.source() { - writeln!(f, "\nCaused by:")?; - Debug::fmt(&e, f)?; + #[cfg(feature = "location")] + { + if let Some(ref o) = self.occurrence { + write!(f, "{}: ", o)?; + } } + + if TypeId::of::() == TypeId::of::() + || TypeId::of::<&str>() == TypeId::of::() + { + Display::fmt(&self.kind, f)?; + } else { + Debug::fmt(&self.kind, f)?; + } + + #[cfg(feature = "debug-cause")] + { + if let Some(e) = self.source() { + writeln!(f, "\nCaused by:")?; + Debug::fmt(&e, f)?; + } + } + Ok(()) } - Ok(()) } } /// `ChainErrorFrom` is similar to `From` pub trait ChainErrorFrom: Sized { /// similar to From::from() - fn chain_error_from(from: T, line_filename: Option<&'static str>) -> ChainError; + 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<&'static str>) -> ChainError; + fn into_chain_error(self, line_filename: Option) -> ChainError; } impl IntoChainError for T @@ -688,7 +794,7 @@ where U: ChainErrorFrom, { #[inline] - fn into_chain_error(self, line_filename: Option<&'static str>) -> ChainError { + fn into_chain_error(self, line_filename: Option) -> ChainError { U::chain_error_from(self, line_filename) } } @@ -699,286 +805,12 @@ where U: 'static + Display + Debug, { #[inline] - fn chain_error_from(t: T, line_filename: Option<&'static str>) -> ChainError { + fn chain_error_from(t: T, line_filename: Option) -> ChainError { let e: U = t.into(); ChainError::new(e, None, line_filename) } } -/* -impl ChainErrorFrom for U - where - T: 'static + Error + Into> + Clone, - U: 'static + Display + Debug + From, -{ - #[inline] - fn chain_error_from(t: T, line_filename: Option<&'static str>) -> ChainError { - ChainError::new(U::from(t.clone()), Some(Box::from(t)), line_filename) - } -} -*/ - -/// map into `ChainError` with `T::from(err)` -/// -/// adds `line!()` and `file!()` information -#[macro_export] -macro_rules! minto_cherr { - ( $k:ident ) => ( - |e| $crate::cherr!(e, $k::from(&e)) - ); - ( $enum:ident $(:: $enum_path:ident)* ) => ( - |e| $crate::cherr!(e, $enum $(:: $enum_path)*::from(&e)) - ); -} - -/// Creates a new `ChainError` -/// -/// # Examples -/// -/// Create a new ChainError, where `FooError` must implement `Display` and `Debug`. -/// ```rust -/// # use chainerror::*; -/// # #[derive(Debug)] -/// enum FooError { -/// Bar, -/// Baz(&'static str), -/// } -/// # impl ::std::fmt::Display for FooError { -/// # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { -/// # match self { -/// # FooError::Bar => write!(f, "Bar Error"), -/// # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), -/// # } -/// # } -/// # } -/// -/// // impl ::std::fmt::Display for FooError -/// -/// fn do_some_stuff() -> bool { -/// false -/// } -/// -/// fn func() -> ChainResult<(), FooError> { -/// if !do_some_stuff() { -/// Err(cherr!(FooError::Baz("Error")))?; -/// } -/// Ok(()) -/// } -/// # match func().unwrap_err().kind() { -/// # FooError::Baz(s) if s == &"Error" => {} -/// # _ => panic!(), -/// # } -/// ``` -/// -/// Additionally an error cause can be added. -/// -/// ```rust -/// # use chainerror::*; -/// # use std::io; -/// # use std::error::Error; -/// # #[derive(Debug)] -/// # enum FooError { -/// # Bar, -/// # Baz(&'static str), -/// # } -/// # impl ::std::fmt::Display for FooError { -/// # fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { -/// # match self { -/// # FooError::Bar => write!(f, "Bar Error"), -/// # FooError::Baz(s) => write!(f, "Baz Error: '{}'", s), -/// # } -/// # } -/// # } -/// fn do_some_stuff() -> Result<(), Box> { -/// Err(io::Error::from(io::ErrorKind::NotFound))?; -/// Ok(()) -/// } -/// -/// fn func() -> ChainResult<(), FooError> { -/// do_some_stuff().map_err(|e| cherr!(e, FooError::Baz("Error")))?; -/// Ok(()) -/// } -/// # match func().unwrap_err().kind() { -/// # FooError::Baz(s) if s == &"Error" => {} -/// # _ => panic!(), -/// # } -/// ``` -#[macro_export] -macro_rules! cherr { - ( $k:expr ) => ({ - $crate::ChainError::new($k, None, Some(concat!(file!(), ":", line!(), ": "))) - }); - ( None, $k:expr ) => ({ - $crate::ChainError::new($k, None, Some(concat!(file!(), ":", line!(), ": "))) - }); - ( None, $fmt:expr, $($arg:tt)+ ) => ({ - $crate::cherr!(None, format!($fmt, $($arg)+ )) - }); - ( None, $fmt:expr, $($arg:tt)+ ) => ({ - $crate::cherr!(None, format!($fmt, $($arg)+ )) - }); - ( $e:path, $k:expr ) => ({ - $crate::ChainError::new($k, Some(Box::from($e)), Some(concat!(file!(), ":", line!(), ": "))) - }); - ( $e:path, $fmt:expr, $($arg:tt)+ ) => ({ - $crate::cherr!($e, format!($fmt, $($arg)+ )) - }); -} - -/// Convenience macro for `|e| cherr!(e, format!(…))` -/// -/// # Examples -/// -/// ```rust -/// # use crate::chainerror::*; -/// # use std::error::Error; -/// # use std::io; -/// # use std::result::Result; -/// # fn do_some_io() -> Result<(), Box> { -/// # Err(io::Error::from(io::ErrorKind::NotFound))?; -/// # Ok(()) -/// # } -/// fn func2() -> Result<(), Box> { -/// let filename = "foo.txt"; -/// do_some_io().map_err(mstrerr!("Error reading '{}'", filename))?; -/// Ok(()) -/// } -/// -/// fn func1() -> Result<(), Box> { -/// func2().map_err(mstrerr!("func1 error"))?; -/// Ok(()) -/// } -/// -/// # if let Err(e) = func1() { -/// # #[cfg(not(feature = "no-debug-cause"))] -/// # #[cfg(not(windows))] -/// # assert_eq!( -/// # format!("\n{:?}\n", e), r#" -/// # src/lib.rs:19: func1 error -/// # Caused by: -/// # src/lib.rs:14: Error reading 'foo.txt' -/// # Caused by: -/// # Kind(NotFound) -/// # "# -/// # ); -/// # } else { -/// # unreachable!(); -/// # } -/// ``` -/// -/// `mstrerr!()` can also be used to map a new `ChainError`, where T was defined with -/// `derive_str_cherr!(T)` -/// -/// ```rust -/// # use crate::chainerror::*; -/// # use std::error::Error; -/// # use std::io; -/// # use std::result::Result; -/// # fn do_some_io() -> Result<(), Box> { -/// # Err(io::Error::from(io::ErrorKind::NotFound))?; -/// # Ok(()) -/// # } -/// derive_str_cherr!(Func2Error); -/// -/// fn func2() -> Result<(), Box> { -/// let filename = "foo.txt"; -/// do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; -/// Ok(()) -/// } -/// -/// derive_str_cherr!(Func1Error); -/// -/// fn func1() -> Result<(), Box> { -/// func2().map_err(mstrerr!(Func1Error, "func1 error"))?; -/// Ok(()) -/// } -/// # if let Err(e) = func1() { -/// # if let Some(f1err) = e.downcast_chain_ref::() { -/// # assert!(f1err.find_cause::>().is_some()); -/// # assert!(f1err.find_chain_cause::().is_some()); -/// # } else { -/// # panic!(); -/// # } -/// # } else { -/// # unreachable!(); -/// # } -/// ``` -#[macro_export] -macro_rules! mstrerr { - ( $t:path, $msg:expr ) => ({ - |e| $crate::cherr!(e, $t ($msg.to_string())) - }); - ( $t:path, $msg:expr, ) => ({ - |e| $crate::cherr!(e, $t ($msg.to_string())) - }); - ( $t:path, $fmt:expr, $($arg:tt)+ ) => ({ - |e| $crate::cherr!(e, $t (format!($fmt, $($arg)+ ))) - }); - ($msg:expr) => ({ - |e| $crate::cherr!(e, $msg.to_string()) - }); - ($msg:expr, ) => ({ - |e| $crate::cherr!(e, $msg.to_string()) - }); - ($fmt:expr, $($arg:tt)+) => ({ - |e| $crate::cherr!(e, format!($fmt, $($arg)+ )) - }); -} - -/// Convenience macro for `cherr!(T(format!(…)))` where `T(String)` -/// -/// # Examples -/// -/// ```rust -/// # use crate::chainerror::*; -/// # use std::error::Error; -/// # use std::result::Result; -/// derive_str_cherr!(Func2Error); -/// -/// fn func2() -> ChainResult<(), Func2Error> { -/// let filename = "foo.txt"; -/// Err(strerr!(Func2Error, "Error reading '{}'", filename)) -/// } -/// -/// derive_str_cherr!(Func1Error); -/// -/// fn func1() -> Result<(), Box> { -/// func2().map_err(mstrerr!(Func1Error, "func1 error"))?; -/// Ok(()) -/// } -/// # if let Err(e) = func1() { -/// # if let Some(f1err) = e.downcast_chain_ref::() { -/// # assert!(f1err.find_cause::>().is_some()); -/// # assert!(f1err.find_chain_cause::().is_some()); -/// # } else { -/// # panic!(); -/// # } -/// # } else { -/// # unreachable!(); -/// # } -/// ``` -#[macro_export] -macro_rules! strerr { - ( $t:path, $msg:expr ) => ({ - $crate::cherr!($t ($msg.to_string())) - }); - ( $t:path, $msg:expr, ) => ({ - $crate::cherr!($t ($msg.to_string())) - }); - ( $t:path, $fmt:expr, $($arg:tt)+ ) => ({ - $crate::cherr!($t (format!($fmt, $($arg)+ ))) - }); - ($msg:expr) => ({ - $crate::cherr!($msg.to_string()) - }); - ($msg:expr, ) => ({ - $crate::cherr!($msg.to_string()) - }); - ($fmt:expr, $($arg:tt)+) => ({ - $crate::cherr!(format!($fmt, $($arg)+ )) - }); -} - /// Convenience macro to create a "new type" T(String) and implement Display + Debug for T /// /// # Examples @@ -996,14 +828,14 @@ macro_rules! strerr { /// /// fn func2() -> ChainResult<(), Func2Error> { /// let filename = "foo.txt"; -/// do_some_io().map_err(mstrerr!(Func2Error, "Error reading '{}'", filename))?; +/// do_some_io().cherr(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) /// } /// /// derive_str_cherr!(Func1Error); /// /// fn func1() -> Result<(), Box> { -/// func2().map_err(mstrerr!(Func1Error, "func1 error"))?; +/// func2().cherr(Func1Error("func1 error".into()))?; /// Ok(()) /// } /// # if let Err(e) = func1() { @@ -1047,7 +879,7 @@ macro_rules! derive_str_cherr { /// # Examples /// /// ```rust -/// use chainerror::*; +/// use chainerror::prelude::v1::*; /// use std::io; /// /// fn do_some_io(_f: &str) -> std::result::Result<(), io::Error> { @@ -1095,10 +927,9 @@ macro_rules! derive_str_cherr { /// let filename = "bar.txt"; /// /// do_some_io(filename) -/// .map_err(|e| cherr!(e, ErrorKind::from_io_error(&e, filename.into())))?; -/// do_some_io(filename).map_err(|e| cherr!(e, ErrorKind::IO(filename.into())))?; -/// do_some_io(filename).map_err(|e| cherr!(e, ErrorKind::from(&e)))?; -/// do_some_io(filename).map_err(minto_cherr!(ErrorKind))?; +/// .map_cherr(|e| ErrorKind::from_io_error(e, filename.into()))?; +/// do_some_io(filename).map_cherr(|e| ErrorKind::IO(filename.into()))?; +/// do_some_io(filename).map_cherr(|e| ErrorKind::from(e))?; /// Ok(()) /// } /// ``` @@ -1134,19 +965,6 @@ macro_rules! derive_err_kind { } } - impl $crate::ChainErrorFrom<$e> for $k - where - $k: Clone, - { - #[inline] - fn chain_error_from( - t: $e, - line_filename: Option<&'static str>, - ) -> $crate::ChainError<$k> { - $crate::ChainError::new((*t.kind()).clone(), Some(Box::from(t)), line_filename) - } - } - impl std::error::Error for $e { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() diff --git a/tests/test_iter.rs b/tests/test_iter.rs index ce100d2..b567544 100644 --- a/tests/test_iter.rs +++ b/tests/test_iter.rs @@ -1,4 +1,4 @@ -use chainerror::*; +use chainerror::prelude::v1::*; use std::error::Error; use std::io; @@ -6,13 +6,14 @@ use std::io; #[test] fn test_iter() -> Result<(), Box> { use std::fmt::Write; - let err = io::Error::from(io::ErrorKind::NotFound); - let err = cherr!(err, "1"); - let err = cherr!(err, "2"); - let err = cherr!(err, "3"); - let err = cherr!(err, "4"); - let err = cherr!(err, "5"); - let err = cherr!(err, "6"); + let err: Result<(), _> = Err(io::Error::from(io::ErrorKind::NotFound)); + let err = err.cherr("1"); + let err = err.cherr("2"); + let err = err.cherr("3"); + let err = err.cherr("4"); + let err = err.cherr("5"); + let err = err.cherr("6"); + let err = err.err().unwrap(); let mut res = String::new(); @@ -34,13 +35,14 @@ fn test_iter() -> Result<(), Box> { #[cfg(feature = "display-cause")] #[test] fn test_iter() -> Result<(), Box> { - let err = io::Error::from(io::ErrorKind::NotFound); - let err = cherr!(err, "1"); - let err = cherr!(err, "2"); - let err = cherr!(err, "3"); - let err = cherr!(err, "4"); - let err = cherr!(err, "5"); - let err = cherr!(err, "6"); + let err: Result<(), _> = Err(io::Error::from(io::ErrorKind::NotFound)); + let err = err.cherr("1"); + let err = err.cherr("2"); + let err = err.cherr("3"); + let err = err.cherr("4"); + let err = err.cherr("5"); + let err = err.cherr("6"); + let err = err.err().unwrap(); let res = err.to_string(); @@ -58,13 +60,14 @@ fn test_iter() -> Result<(), Box> { #[test] fn test_find_cause() -> Result<(), Box> { - let err = io::Error::from(io::ErrorKind::NotFound); - let err = cherr!(err, "1"); - let err = cherr!(err, "2"); - let err = cherr!(err, "3"); - let err = cherr!(err, "4"); - let err = cherr!(err, "5"); - let err = cherr!(err, "6"); + let err: Result<(), _> = Err(io::Error::from(io::ErrorKind::NotFound)); + let err = err.cherr("1"); + let err = err.cherr("2"); + let err = err.cherr("3"); + let err = err.cherr("4"); + let err = err.cherr("5"); + let err = err.cherr("6"); + let err = err.err().unwrap(); let io_error: Option<&io::Error> = err.find_cause::(); @@ -75,13 +78,14 @@ fn test_find_cause() -> Result<(), Box> { #[test] fn test_root_cause() -> Result<(), Box> { - let err = io::Error::from(io::ErrorKind::NotFound); - let err = cherr!(err, "1"); - let err = cherr!(err, "2"); - let err = cherr!(err, "3"); - let err = cherr!(err, "4"); - let err = cherr!(err, "5"); - let err = cherr!(err, "6"); + let err: Result<(), _> = Err(io::Error::from(io::ErrorKind::NotFound)); + let err = err.cherr("1"); + let err = err.cherr("2"); + let err = err.cherr("3"); + let err = err.cherr("4"); + let err = err.cherr("5"); + let err = err.cherr("6"); + let err = err.err().unwrap(); let err: Option<&(dyn std::error::Error + 'static)> = err.root_cause(); let io_error: Option<&io::Error> = err.and_then(Error::downcast_ref::);