From 455b01d3fb2c761ef88af79ba2656b0f71c6285e Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Tue, 1 Sep 2020 21:47:49 +0200 Subject: [PATCH 01/45] (cargo-release) start next development iteration 0.6.1-alpha.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c7e19c6..ea0e924 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chainerror" -version = "0.6.0" +version = "0.6.1-alpha.0" authors = ["Harald Hoyer "] edition = "2018" license = "MIT/Apache-2.0" From f586b52675cf5d13a337a8220d562a152ef0df41 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Tue, 1 Sep 2020 22:47:15 +0200 Subject: [PATCH 02/45] Better introduction examples. --- README.md | 260 ++++++++++++++----------------------------------- src/lib.rs | 277 ++++++++++++++++------------------------------------- 2 files changed, 159 insertions(+), 378 deletions(-) diff --git a/README.md b/README.md index 86405f0..2441848 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,86 @@ `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your binaries, you still have the error backtrace. -`chainerror` has no dependencies! +Having nested function returning errors, the output doesn't tell where the error originates from. + +```rust +use std::path::PathBuf; + +type BoxedError = Box; +fn read_config_file(path: PathBuf) -> Result<(), BoxedError> { + // do stuff, return other errors + let _buf = std::fs::read_to_string(&path)?; + // do stuff, return other errors + Ok(()) +} + +fn process_config_file() -> Result<(), BoxedError> { + // do stuff, return other errors + let _buf = read_config_file("foo.txt".into())?; + // do stuff, return other errors + Ok(()) +} + +fn main() { + if let Err(e) = process_config_file() { + eprintln!("Error:\n{:?}", e); + } +} +``` + +This gives the output: +```console +Error: +Os { code: 2, kind: NotFound, message: "No such file or directory" } +``` +and you have no idea where it comes from. + + +With `chainerror`, you can supply a context and get a nice error backtrace: + +```rust +use chainerror::prelude::v1::*; +use std::path::PathBuf; + +type BoxedError = Box; +fn read_config_file(path: PathBuf) -> Result<(), BoxedError> { + // do stuff, return other errors + let _buf = std::fs::read_to_string(&path).context(format!("Reading file: {:?}", &path))?; + // do stuff, return other errors + Ok(()) +} + +fn process_config_file() -> Result<(), BoxedError> { + // do stuff, return other errors + let _buf = read_config_file("foo.txt".into()).context("read the config file")?; + // do stuff, return other errors + Ok(()) +} + +fn main() { + if let Err(e) = process_config_file() { + eprintln!("Error:\n{:?}", e); + } +} +``` + +with the output: +```console +Error: +examples/simple.rs:14:51: read the config file +Caused by: +examples/simple.rs:7:47: Reading file: "foo.txt" +Caused by: +Os { code: 2, kind: NotFound, message: "No such file or directory" } +``` `chainerror` uses `.source()` of `std::error::Error` along with `#[track_caller]` and `Location` to provide a nice debug error backtrace. It encapsulates all types, which have `Display + Debug` and can store the error cause internally. Along with the `ChainError` struct, `chainerror` comes with some useful helper macros to save a lot of typing. +`chainerror` has no dependencies! + Debug information is worth it! ### Features @@ -29,191 +102,6 @@ Debug information is worth it! Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) -## Examples - -examples/example.rs: -```rust -// […] -fn main() { - if let Err(e) = func1() { - eprintln!("\nDebug Error {{:?}}:\n{:?}", e); - eprintln!("\nAlternative Debug Error {{:#?}}:\n{:#?}\n", e); - // […] - } -} -``` - -```console -$ cargo run -q --example example -Debug Error {:?}: -examples/example.rs:46:13: func1 error calling func2 -Caused by: -examples/example.rs:21:13: Func2Error(func2 error: calling func3) -Caused by: -examples/example.rs:14:18: Error reading 'foo.txt' -Caused by: -Kind(NotFound) - -Alternative Debug Error {:#?}: -ChainError { - occurrence: Some( - "examples/example.rs:46:13", - ), - kind: func1 error calling func2, - source: Some( - ChainError { - occurrence: Some( - "examples/example.rs:21:13", - ), - kind: Func2Error(func2 error: calling func3), - source: Some( - ChainError { - occurrence: Some( - "examples/example.rs:14:18", - ), - kind: "Error reading \'foo.txt\'", - source: Some( - Kind( - NotFound, - ), - ), - }, - ), - }, - ), -} -``` - -```rust -use chainerror::prelude::v1::*; -use std::error::Error; -use std::io; -use std::result::Result; - -fn do_some_io() -> Result<(), Box> { - Err(io::Error::from(io::ErrorKind::NotFound))?; - Ok(()) -} - -fn func2() -> Result<(), Box> { - let filename = "foo.txt"; - do_some_io().context(format!("Error reading '{}'", filename))?; - Ok(()) -} - -fn func1() -> Result<(), Box> { - func2().context("func1 error")?; - Ok(()) -} - -if let Err(e) = func1() { - #[cfg(not(windows))] - assert_eq!( - format!("\n{:?}\n", e), - r#" -src/lib.rs:21:13: func1 error -Caused by: -src/lib.rs:16:18: Error reading 'foo.txt' -Caused by: -Kind(NotFound) -"# - ); -} -``` - - -```rust -use chainerror::prelude::v1::*; -use std::error::Error; -use std::io; -use std::result::Result; - -fn do_some_io() -> Result<(), Box> { - Err(io::Error::from(io::ErrorKind::NotFound))?; - Ok(()) -} - -fn func3() -> Result<(), Box> { - let filename = "foo.txt"; - do_some_io().context(format!("Error reading '{}'", filename))?; - Ok(()) -} - -derive_str_context!(Func2Error); - -fn func2() -> ChainResult<(), Func2Error> { - func3().context(Func2Error("func2 error: calling func3".into()))?; - Ok(()) -} - -enum Func1Error { - Func2, - IO(String), -} - -impl ::std::fmt::Display for Func1Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - match self { - Func1Error::Func2 => write!(f, "func1 error calling func2"), - Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), - } - } -} - -impl ::std::fmt::Debug for Func1Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "{}", self) - } -} - -fn func1() -> ChainResult<(), Func1Error> { - func2().context(Func1Error::Func2)?; - let filename = String::from("bar.txt"); - do_some_io().context(Func1Error::IO(filename))?; - Ok(()) -} - -if let Err(e) = func1() { - assert!(match e.kind() { - Func1Error::Func2 => { - eprintln!("Main Error Report: func1 error calling func2"); - true - } - Func1Error::IO(filename) => { - eprintln!("Main Error Report: func1 error reading '{}'", filename); - false - } - }); - - assert!(e.find_chain_cause::().is_some()); - - if let Some(e) = e.find_chain_cause::() { - eprintln!("\nError reported by Func2Error: {}", e) - } - - assert!(e.root_cause().is_some()); - - if let Some(e) = e.root_cause() { - let io_error = e.downcast_ref::().unwrap(); - eprintln!("\nThe root cause was: std::io::Error: {:#?}", io_error); - } - - #[cfg(not(windows))] - assert_eq!( - format!("\n{:?}\n", e), - r#" -src/lib.rs:48:13: func1 error calling func2 -Caused by: -src/lib.rs:23:13: Func2Error(func2 error: calling func3) -Caused by: -src/lib.rs:16:18: Error reading 'foo.txt' -Caused by: -Kind(NotFound) -"# - ); -} -``` - ## License Licensed under either of diff --git a/src/lib.rs b/src/lib.rs index bd4edd6..48a0f3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,96 @@ //! `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your //! binaries, you still have the error backtrace. //! -//! `chainerror` has no dependencies! +//! Having nested function returning errors, the output doesn't tell where the error originates from. +//! +//! ```rust +//! use std::path::PathBuf; +//! +//! type BoxedError = Box; +//! fn read_config_file(path: PathBuf) -> Result<(), BoxedError> { +//! // do stuff, return other errors +//! let _buf = std::fs::read_to_string(&path)?; +//! // do stuff, return other errors +//! Ok(()) +//! } +//! +//! fn process_config_file() -> Result<(), BoxedError> { +//! // do stuff, return other errors +//! let _buf = read_config_file("foo.txt".into())?; +//! // do stuff, return other errors +//! Ok(()) +//! } +//! +//! fn main() { +//! if let Err(e) = process_config_file() { +//! eprintln!("Error:\n{:?}", e); +//! } +//! } +//! ``` +//! +//! This gives the output: +//! ```console +//! Error: +//! Os { code: 2, kind: NotFound, message: "No such file or directory" } +//! ``` +//! and you have no idea where it comes from. +//! +//! +//! With `chainerror`, you can supply a context and get a nice error backtrace: +//! +//! ```rust +//! use chainerror::prelude::v1::*; +//! use std::path::PathBuf; +//! +//! type BoxedError = Box; +//! fn read_config_file(path: PathBuf) -> Result<(), BoxedError> { +//! // do stuff, return other errors +//! let _buf = std::fs::read_to_string(&path).context(format!("Reading file: {:?}", &path))?; +//! // do stuff, return other errors +//! Ok(()) +//! } +//! +//! fn process_config_file() -> Result<(), BoxedError> { +//! // do stuff, return other errors +//! let _buf = read_config_file("foo.txt".into()).context("read the config file")?; +//! // do stuff, return other errors +//! Ok(()) +//! } +//! +//! fn main() { +//! if let Err(e) = process_config_file() { +//! eprintln!("Error:\n{:?}", e); +//! # assert_eq!( +//! # format!("{:?}\n", e), +//! # "\ +//! # src/lib.rs:16:51: read the config file\n\ +//! # Caused by:\n\ +//! # src/lib.rs:9:47: Reading file: \"foo.txt\"\n\ +//! # Caused by:\n\ +//! # Os { code: 2, kind: NotFound, message: \"No such file or directory\" }\n\ +//! # ", +//! # ); +//! } +//! } +//! ``` +//! +//! with the output: +//! ```console +//! Error: +//! examples/simple.rs:14:51: read the config file +//! Caused by: +//! examples/simple.rs:7:47: Reading file: "foo.txt" +//! Caused by: +//! Os { code: 2, kind: NotFound, message: "No such file or directory" } +//! ``` //! //! `chainerror` uses `.source()` of `std::error::Error` along with `#[track_caller]` and `Location` to provide a nice debug error backtrace. //! It encapsulates all types, which have `Display + Debug` and can store the error cause internally. //! //! Along with the `ChainError` struct, `chainerror` comes with some useful helper macros to save a lot of typing. //! +//! `chainerror` has no dependencies! +//! //! Debug information is worth it! //! //! ## Features @@ -18,200 +101,10 @@ //! # Tutorial //! //! Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) -//! -//! # Examples -//! -//! examples/example.rs: -//! ```rust,ignore -//! // […] -//! fn main() { -//! if let Err(e) = func1() { -//! eprintln!("\nDebug Error {{:?}}:\n{:?}", e); -//! eprintln!("\nAlternative Debug Error {{:#?}}:\n{:#?}\n", e); -//! // […] -//! } -//! } -//! ``` -//! -//! ```console -//! $ cargo run -q --example example -//! Debug Error {:?}: -//! examples/example.rs:46:13: func1 error calling func2 -//! Caused by: -//! examples/example.rs:21:13: Func2Error(func2 error: calling func3) -//! Caused by: -//! examples/example.rs:14:18: Error reading 'foo.txt' -//! Caused by: -//! Kind(NotFound) -//! -//! Alternative Debug Error {:#?}: -//! ChainError { -//! occurrence: Some( -//! "examples/example.rs:46:13", -//! ), -//! kind: func1 error calling func2, -//! source: Some( -//! ChainError { -//! occurrence: Some( -//! "examples/example.rs:21:13", -//! ), -//! kind: Func2Error(func2 error: calling func3), -//! source: Some( -//! ChainError { -//! occurrence: Some( -//! "examples/example.rs:14:18", -//! ), -//! kind: "Error reading \'foo.txt\'", -//! source: Some( -//! Kind( -//! NotFound, -//! ), -//! ), -//! }, -//! ), -//! }, -//! ), -//! } -//! ``` -//! -//! ```rust -//! use chainerror::prelude::v1::*; -//! use std::error::Error; -//! use std::io; -//! use std::result::Result; -//! -//! fn do_some_io() -> Result<(), Box> { -//! Err(io::Error::from(io::ErrorKind::NotFound))?; -//! Ok(()) -//! } -//! -//! fn func2() -> Result<(), Box> { -//! let filename = "foo.txt"; -//! do_some_io().context(format!("Error reading '{}'", filename))?; -//! Ok(()) -//! } -//! -//! fn func1() -> Result<(), Box> { -//! func2().context("func1 error")?; -//! Ok(()) -//! } -//! -//! if let Err(e) = func1() { -//! #[cfg(not(windows))] -//! assert_eq!( -//! format!("\n{:?}\n", e), -//! r#" -//! src/lib.rs:21:13: func1 error -//! Caused by: -//! src/lib.rs:16:18: Error reading 'foo.txt' -//! Caused by: -//! Kind(NotFound) -//! "# -//! ); -//! } -//! # else { -//! # unreachable!(); -//! # } -//! ``` -//! -//! -//! ```rust -//! use chainerror::prelude::v1::*; -//! use std::error::Error; -//! use std::io; -//! use std::result::Result; -//! -//! fn do_some_io() -> Result<(), Box> { -//! Err(io::Error::from(io::ErrorKind::NotFound))?; -//! Ok(()) -//! } -//! -//! fn func3() -> Result<(), Box> { -//! let filename = "foo.txt"; -//! do_some_io().context(format!("Error reading '{}'", filename))?; -//! Ok(()) -//! } -//! -//! derive_str_context!(Func2Error); -//! -//! fn func2() -> ChainResult<(), Func2Error> { -//! func3().context(Func2Error("func2 error: calling func3".into()))?; -//! Ok(()) -//! } -//! -//! enum Func1Error { -//! Func2, -//! IO(String), -//! } -//! -//! impl ::std::fmt::Display for Func1Error { -//! fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { -//! match self { -//! Func1Error::Func2 => write!(f, "func1 error calling func2"), -//! Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), -//! } -//! } -//! } -//! -//! impl ::std::fmt::Debug for Func1Error { -//! fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { -//! write!(f, "{}", self) -//! } -//! } -//! -//! fn func1() -> ChainResult<(), Func1Error> { -//! func2().context(Func1Error::Func2)?; -//! let filename = String::from("bar.txt"); -//! do_some_io().context(Func1Error::IO(filename))?; -//! Ok(()) -//! } -//! -//! if let Err(e) = func1() { -//! assert!(match e.kind() { -//! Func1Error::Func2 => { -//! eprintln!("Main Error Report: func1 error calling func2"); -//! true -//! } -//! Func1Error::IO(filename) => { -//! eprintln!("Main Error Report: func1 error reading '{}'", filename); -//! false -//! } -//! }); -//! -//! assert!(e.find_chain_cause::().is_some()); -//! -//! if let Some(e) = e.find_chain_cause::() { -//! eprintln!("\nError reported by Func2Error: {}", e) -//! } -//! -//! assert!(e.root_cause().is_some()); -//! -//! if let Some(e) = e.root_cause() { -//! let io_error = e.downcast_ref::().unwrap(); -//! eprintln!("\nThe root cause was: std::io::Error: {:#?}", io_error); -//! } -//! -//! #[cfg(not(windows))] -//! assert_eq!( -//! format!("\n{:?}\n", e), -//! r#" -//! src/lib.rs:48:13: func1 error calling func2 -//! Caused by: -//! src/lib.rs:23:13: Func2Error(func2 error: calling func3) -//! Caused by: -//! src/lib.rs:16:18: Error reading 'foo.txt' -//! Caused by: -//! Kind(NotFound) -//! "# -//! ); -//! } -//! # else { -//! # unreachable!(); -//! # } -//! ``` #![deny(clippy::all)] #![deny(clippy::integer_arithmetic)] +#![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] use std::any::TypeId; From 1003671be34d0ffd73e0742838eeddddd4b4aab5 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Tue, 1 Sep 2020 22:59:44 +0200 Subject: [PATCH 03/45] (cargo-release) version 0.6.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ea0e924..49cf712 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chainerror" -version = "0.6.1-alpha.0" +version = "0.6.1" authors = ["Harald Hoyer "] edition = "2018" license = "MIT/Apache-2.0" From cd0fc471cb4ebbbd6ef98091fa1001dfe207e11d Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Tue, 1 Sep 2020 22:59:58 +0200 Subject: [PATCH 04/45] (cargo-release) start next development iteration 0.6.2-alpha.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 49cf712..86e0e24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chainerror" -version = "0.6.1" +version = "0.6.2-alpha.0" authors = ["Harald Hoyer "] edition = "2018" license = "MIT/Apache-2.0" From 523b8633fb2328baa0120ebce70448b1ec88c621 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2020 19:31:40 +0000 Subject: [PATCH 05/45] Create Dependabot config file --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5cde165 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: cargo + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 From 259712931f45f362318a2b3ab6076d67c762e002 Mon Sep 17 00:00:00 2001 From: Jakub Duchniewicz Date: Mon, 11 Jan 2021 19:38:01 +0100 Subject: [PATCH 06/45] Rust PR 75180 fix. --- src/lib.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 48a0f3d..5febac3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -582,15 +582,6 @@ impl Error for ChainError { } } -impl Error for &ChainError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - self.error_cause - .as_ref() - .map(|e| e.as_ref() as &(dyn Error + 'static)) - } -} - impl Error for &mut ChainError { #[inline] fn source(&self) -> Option<&(dyn Error + 'static)> { From ee385c1fe03a68eb9a96874e91673ce1b205b934 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Tue, 2 Feb 2021 09:11:42 +0100 Subject: [PATCH 07/45] (cargo-release) version 0.7.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 86e0e24..3afdf67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chainerror" -version = "0.6.2-alpha.0" +version = "0.7.0" authors = ["Harald Hoyer "] edition = "2018" license = "MIT/Apache-2.0" From d577243d8e965ca8d2d27b994e11d731bc27df2e Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Tue, 2 Feb 2021 09:12:07 +0100 Subject: [PATCH 08/45] (cargo-release) start next development iteration 0.7.1-alpha.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3afdf67..e5089e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chainerror" -version = "0.7.0" +version = "0.7.1-alpha.0" authors = ["Harald Hoyer "] edition = "2018" license = "MIT/Apache-2.0" From 05085229be60482ee25dc2a00a3ce7e28d5afc3f Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Tue, 2 Feb 2021 09:45:23 +0100 Subject: [PATCH 09/45] chore: fix github pages deploy set-env is deprecated ... remove the caching stuff --- .github/workflows/gh-pages.yml | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index b8d27bd..bcc15ae 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -11,27 +11,6 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set CURRENT_TWO_WEEKS for use in cache keys - run: echo "::set-env name=CURRENT_TWO_WEEKS::$(($(date +%V) / 2))" - - - name: Cache cargo registry - uses: actions/cache@v1 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ env.CURRENT_TWO_WEEKS }} - - - name: Cache cargo index - uses: actions/cache@v1 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ env.CURRENT_TWO_WEEKS }} - - - name: Cache mdbook binary - uses: actions/cache@v1 - with: - path: ~/.cargo/bin/mdbook - key: ${{ runner.os }}-cargo-mdbook-${{ env.CURRENT_TWO_WEEKS }} - - name: Build mdbook run: cargo install mdbook From d60cdf9cdb6953f0211cd03204b72cc355500b2f Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Thu, 27 Jul 2023 12:39:31 +0200 Subject: [PATCH 10/45] fix: clippy Signed-off-by: Harald Hoyer --- examples/example.rs | 2 +- examples/tutorial15.rs | 2 +- examples/tutorial7.rs | 2 +- examples/tutorial8.rs | 2 +- examples/tutorial9.rs | 2 +- src/lib.rs | 9 +++++---- tests/test_iter.rs | 8 ++++---- 7 files changed, 14 insertions(+), 13 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index 89b0d77..370631e 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -18,7 +18,7 @@ fn func3() -> Result<(), Box> { derive_str_context!(Func2Error); fn func2() -> ChainResult<(), Func2Error> { - func3().context(Func2Error(format!("func2 error: calling func3")))?; + func3().context(Func2Error("func2 error: calling func3".to_string()))?; Ok(()) } diff --git a/examples/tutorial15.rs b/examples/tutorial15.rs index ef35003..3b0eef3 100644 --- a/examples/tutorial15.rs +++ b/examples/tutorial15.rs @@ -84,7 +84,7 @@ pub mod mycrate { let filename = "bar.txt"; - do_some_io(filename).map_context(|e| ErrorKind::from_io_error(&e, filename.into()))?; + do_some_io(filename).map_context(|e| ErrorKind::from_io_error(e, filename.into()))?; do_some_io(filename).map_context(|_| ErrorKind::IO(filename.into()))?; do_some_io(filename).map_context(|e| ErrorKind::from(e))?; diff --git a/examples/tutorial7.rs b/examples/tutorial7.rs index feb7cf8..969588d 100644 --- a/examples/tutorial7.rs +++ b/examples/tutorial7.rs @@ -15,7 +15,7 @@ fn func2() -> Result<(), Box> { } fn func1() -> Result<(), Box> { - func2().context(format!("func1 error"))?; + func2().context("func1 error")?; Ok(()) } diff --git a/examples/tutorial8.rs b/examples/tutorial8.rs index 2093782..cf5e654 100644 --- a/examples/tutorial8.rs +++ b/examples/tutorial8.rs @@ -19,7 +19,7 @@ fn func2() -> Result<(), Box> { derive_str_context!(Func1Error); fn func1() -> Result<(), Box> { - func2().context(Func1Error(format!("func1 error")))?; + func2().context(Func1Error("func1 error".to_string()))?; Ok(()) } diff --git a/examples/tutorial9.rs b/examples/tutorial9.rs index 1561535..754c621 100644 --- a/examples/tutorial9.rs +++ b/examples/tutorial9.rs @@ -20,7 +20,7 @@ derive_str_context!(Func1ErrorFunc2); derive_str_context!(Func1ErrorIO); fn func1() -> Result<(), Box> { - func2().context(Func1ErrorFunc2(format!("func1 error calling func2")))?; + func2().context(Func1ErrorFunc2("func1 error calling func2".to_string()))?; let filename = "bar.txt"; do_some_io().context(Func1ErrorIO(format!("Error reading '{}'", filename)))?; Ok(()) diff --git a/src/lib.rs b/src/lib.rs index 5febac3..9ba0f28 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,7 +103,6 @@ //! Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) #![deny(clippy::all)] -#![deny(clippy::integer_arithmetic)] #![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] @@ -198,7 +197,9 @@ impl ChainError { /// ``` #[inline] pub fn find_cause(&self) -> Option<&U> { - self.iter().filter_map(Error::downcast_ref::).next() + self.iter() + .filter_map(::downcast_ref::) + .next() } /// Find the first error cause of type `ChainError`, if any exists @@ -220,7 +221,7 @@ impl ChainError { #[inline] pub fn find_chain_cause(&self) -> Option<&ChainError> { self.iter() - .filter_map(Error::downcast_ref::>) + .filter_map(::downcast_ref::>) .next() } @@ -428,7 +429,7 @@ impl ChainErrorDown for ChainError { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] - Some(&*(self as *const dyn Error as *const &ChainError)) + Some(*(self as *const dyn Error as *const &ChainError)) } } else { None diff --git a/tests/test_iter.rs b/tests/test_iter.rs index 318c7a7..ad4f9f1 100644 --- a/tests/test_iter.rs +++ b/tests/test_iter.rs @@ -18,13 +18,13 @@ fn test_iter() -> Result<(), Box> { let mut res = String::new(); for e in err.iter() { - write!(res, "{}", e.to_string())?; + write!(res, "{}", e)?; } assert_eq!(res, "654321entity not found"); let io_error: Option<&io::Error> = err .iter() - .filter_map(Error::downcast_ref::) + .filter_map(::downcast_ref::) .next(); assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound); @@ -50,7 +50,7 @@ fn test_iter() -> Result<(), Box> { let io_error: Option<&io::Error> = err .iter() - .filter_map(Error::downcast_ref::) + .filter_map(::downcast_ref::) .next(); assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound); @@ -88,7 +88,7 @@ fn test_root_cause() -> Result<(), Box> { let err = err.err().unwrap(); let err: Option<&(dyn std::error::Error + 'static)> = err.root_cause(); - let io_error: Option<&io::Error> = err.and_then(Error::downcast_ref::); + let io_error: Option<&io::Error> = err.and_then(::downcast_ref::); assert_eq!(io_error.unwrap().kind(), io::ErrorKind::NotFound); From bb5f372a92d6cca496d97e1dbf9a42ae3910c724 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Thu, 27 Jul 2023 12:27:28 +0200 Subject: [PATCH 11/45] fix: use `dtolnay/rust-toolchain` instead of `actions-rs/toolchain` Signed-off-by: Harald Hoyer --- .github/workflows/rust.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9e148b2..2f81b29 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -26,10 +26,9 @@ jobs: steps: - uses: actions/checkout@v1 - name: Install toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.version }} - default: true profile: minimal - name: Build run: cargo build --verbose @@ -45,7 +44,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master with: components: rustfmt toolchain: stable @@ -61,7 +60,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master with: components: clippy toolchain: stable @@ -77,7 +76,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master with: toolchain: stable profile: minimal From 376e133836b3f1c2a22d598a0ca61509c8ee2128 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Thu, 27 Jul 2023 12:32:37 +0200 Subject: [PATCH 12/45] ci: use codecov for coverage Signed-off-by: Harald Hoyer --- .github/workflows/coverage.yml | 55 +++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 1925bb5..21025a8 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,22 +1,43 @@ -name: coverage +name: coverage + +on: + # Trigger the workflow on push or pull request, + # but only for the master branch + push: + branches: + - master + pull_request: + branches: + - master + release: + types: + - created -on: [ "push" , "pull_request" ] jobs: test: - name: coverage - runs-on: ubuntu-latest - container: - image: xd009642/tarpaulin - options: --security-opt seccomp=unconfined + name: coverage + runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Generate code coverage - run: | - cargo tarpaulin --verbose --workspace --timeout 120 --out Lcov --output-dir coverage - - - name: Upload to coveralls - uses: coverallsapp/github-action@master + - uses: actions/checkout@v1 + - uses: dtolnay/rust-toolchain@master with: - github-token: ${{ secrets.GITHUB_TOKEN }} + target: x86_64-unknown-linux-gnu + toolchain: nightly + components: llvm-tools-preview + + - name: Install cargo-llvm-cov + run: > + curl -LsSf 'https://github.com/taiki-e/cargo-llvm-cov/releases/download/v0.5.23/cargo-llvm-cov-x86_64-unknown-linux-musl.tar.gz' + | tar xzf - + && mv cargo-llvm-cov $HOME/.cargo/bin + + - name: Run cargo-llvm-cov + run: cargo llvm-cov --doctests --all --all-features --lcov --output-path lcov.info + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + directory: ./ + fail_ci_if_error: false + files: ./lcov.info + verbose: true From 95c5a02d507a418c68cadf1c4a4a9db73111c1f8 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Thu, 27 Jul 2023 13:28:15 +0200 Subject: [PATCH 13/45] 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 --- src/lib.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9ba0f28..2c58ec4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,17 +60,16 @@ //! fn main() { //! if let Err(e) = process_config_file() { //! eprintln!("Error:\n{:?}", e); -//! # assert_eq!( -//! # format!("{:?}\n", e), -//! # "\ -//! # src/lib.rs:16:51: read the config file\n\ -//! # Caused by:\n\ -//! # src/lib.rs:9:47: Reading file: \"foo.txt\"\n\ -//! # Caused by:\n\ -//! # Os { code: 2, kind: NotFound, message: \"No such file or directory\" }\n\ -//! # ", -//! # ); +//! # let s = format!("{:?}", e); +//! # let lines = s.lines().collect::>(); +//! # assert_eq!(lines.len(), 5); +//! # assert!(lines[0].starts_with("src/lib.rs:")); +//! # assert_eq!(lines[1], "Caused by:"); +//! # assert!(lines[2].starts_with("src/lib.rs:")); +//! # assert_eq!(lines[3], "Caused by:"); +//! # assert_eq!(lines[4], "Os { code: 2, kind: NotFound, message: \"No such file or directory\" }"); //! } +//! # else { panic!(); } //! } //! ``` //! From a34929600e383edea3842eef1875157f2ba98d57 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Thu, 27 Jul 2023 14:13:34 +0200 Subject: [PATCH 14/45] chore: fix License to SPDX syntax Signed-off-by: Harald Hoyer --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e5089e9..4093bde 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "chainerror" version = "0.7.1-alpha.0" authors = ["Harald Hoyer "] edition = "2018" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" documentation = "https://docs.rs/chainerror" homepage = "https://haraldh.github.io/chainerror/" repository = "https://github.com/haraldh/chainerror" From e90072f079c69d3561eb57d9c96810deb375b7ed Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Thu, 27 Jul 2023 14:24:20 +0200 Subject: [PATCH 15/45] chore: Release chainerror version 0.7.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4093bde..266c241 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chainerror" -version = "0.7.1-alpha.0" +version = "0.7.1" authors = ["Harald Hoyer "] edition = "2018" license = "MIT OR Apache-2.0" From b2a62b2f550e5dc2e775edf0580f4f0f6422568f Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Thu, 27 Jul 2023 14:29:29 +0200 Subject: [PATCH 16/45] chore: Release chainerror version 0.7.2-alpha.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 266c241..6c4e589 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chainerror" -version = "0.7.1" +version = "0.7.2-alpha.1" authors = ["Harald Hoyer "] edition = "2018" license = "MIT OR Apache-2.0" From 4eae3da3c165134b86081d91c893a89ccc4fe843 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Tue, 2 Feb 2021 11:16:48 +0100 Subject: [PATCH 17/45] feat: removed feature `display-cause` `display-cause` can be turned on with the `{:#}` format specifier --- Cargo.toml | 4 ---- examples/example.rs | 4 ++++ src/lib.rs | 15 ++++----------- tests/test_iter.rs | 8 +++----- 4 files changed, 11 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6c4e589..dae493e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,3 @@ 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 = [] -display-cause = [] diff --git a/examples/example.rs b/examples/example.rs index 370631e..859a12c 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -51,6 +51,10 @@ fn func1() -> ChainResult<(), Func1Error> { fn main() { if let Err(e) = func1() { + eprintln!("\nDisplay Error {{}}:\n{}", e); + + eprintln!("\nAlternative Display Error {{:#}}:\n{:#}", e); + eprintln!("\nDebug Error {{:?}}:\n{:?}", e); eprintln!("\nAlternative Debug Error {{:#?}}:\n{:#?}\n", e); diff --git a/src/lib.rs b/src/lib.rs index 2c58ec4..480a763 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,11 +92,6 @@ //! //! Debug information is worth it! //! -//! ## Features -//! -//! `display-cause` -//! : turn on printing a backtrace of the errors in `Display` -//! //! # Tutorial //! //! Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) @@ -596,13 +591,12 @@ impl Display for ChainError { fn fmt(&self, f: &mut Formatter<'_>) -> Result { write!(f, "{}", self.kind)?; - #[cfg(feature = "display-cause")] - { + if f.alternate() { if let Some(e) = self.source() { - writeln!(f, "\nCaused by:")?; - Display::fmt(&e, f)?; + write!(f, "\nCaused by:\n {:#}", &e)?; } } + Ok(()) } } @@ -633,8 +627,7 @@ impl Debug for ChainError { } if let Some(e) = self.source() { - writeln!(f, "\nCaused by:")?; - Debug::fmt(&e, f)?; + write!(f, "\nCaused by:\n{:?}", &e)?; } Ok(()) } diff --git a/tests/test_iter.rs b/tests/test_iter.rs index ad4f9f1..f5f0e58 100644 --- a/tests/test_iter.rs +++ b/tests/test_iter.rs @@ -2,7 +2,6 @@ use chainerror::prelude::v1::*; use std::error::Error; use std::io; -#[cfg(not(feature = "display-cause"))] #[test] fn test_iter() -> Result<(), Box> { use std::fmt::Write; @@ -32,9 +31,8 @@ fn test_iter() -> Result<(), Box> { Ok(()) } -#[cfg(feature = "display-cause")] #[test] -fn test_iter() -> Result<(), Box> { +fn test_iter_alternate() -> Result<(), Box> { let err: Result<(), _> = Err(io::Error::from(io::ErrorKind::NotFound)); let err = err.context("1"); let err = err.context("2"); @@ -44,9 +42,9 @@ fn test_iter() -> Result<(), Box> { let err = err.context("6"); let err = err.err().unwrap(); - let res = err.to_string(); + let res = format!("{:#}", err); - assert_eq!(res, "6\nCaused by:\n5\nCaused by:\n4\nCaused by:\n3\nCaused by:\n2\nCaused by:\n1\nCaused by:\nentity not found"); + assert_eq!(res, format!("6\nCaused by:\n 5\nCaused by:\n 4\nCaused by:\n 3\nCaused by:\n 2\nCaused by:\n 1\nCaused by:\n {:#}", io::Error::from(io::ErrorKind::NotFound))); let io_error: Option<&io::Error> = err .iter() From bdfec082286d38a3eec768713991ea6d8908480d Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Tue, 2 Feb 2021 15:35:50 +0100 Subject: [PATCH 18/45] feat: add inline to context and map_context methods --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 480a763..910e8aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -339,6 +339,7 @@ impl>> ResultTrait for std::result::Result { #[track_caller] + #[inline] fn context( self, kind: T, @@ -354,6 +355,7 @@ impl>> ResultTrait } #[track_caller] + #[inline] fn map_context T>( self, op: F, From f5c8afce0d025de5bc0684e1853bf504316b2b74 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Tue, 2 Feb 2021 15:36:43 +0100 Subject: [PATCH 19/45] feat: remove ChainErrorFrom and IntoChainError, add From ChainErrorFrom and IntoChainError are not needed anymore with the `#[track_caller]` feature. Now, a proper `From for ChainError` can be implemented. --- src/lib.rs | 34 +++++----------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 910e8aa..7dfa5a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -636,40 +636,16 @@ impl Debug for ChainError { } } -/// `ChainErrorFrom` is similar to `From` -pub trait ChainErrorFrom: Sized { - /// similar to From::from() - fn chain_error_from(from: T, line_filename: Option) -> ChainError; -} - -/// `IntoChainError` is similar to `Into` -pub trait IntoChainError: Sized { - /// similar to Into::into() - fn into_chain_error(self, line_filename: Option) -> ChainError; -} - -impl IntoChainError for T +impl From for ChainError where - U: ChainErrorFrom, + T: 'static + Display + Debug, { + #[track_caller] #[inline] - fn into_chain_error(self, line_filename: Option) -> ChainError { - U::chain_error_from(self, line_filename) + fn from(e: T) -> ChainError { + ChainError::new(e, None, Some(Location::caller().to_string())) } } - -impl ChainErrorFrom for U -where - T: Into, - U: 'static + Display + Debug, -{ - #[inline] - fn chain_error_from(t: T, line_filename: Option) -> ChainError { - let e: U = t.into(); - ChainError::new(e, None, line_filename) - } -} - /// Convenience macro to create a "new type" T(String) and implement Display + Debug for T /// /// # Examples From 1327575aa99bccd2a7355dd23d3934694fcbc2bf Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Thu, 27 Jul 2023 14:36:10 +0200 Subject: [PATCH 20/45] feat!: remove `Chain` prefix from `Error` and `Result` like `anyhow` Signed-off-by: Harald Hoyer --- src/lib.rs | 183 ++++++++++++++++++++++++++--------------------------- 1 file changed, 89 insertions(+), 94 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7dfa5a0..0ea1dbe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,8 +101,8 @@ #![deny(missing_docs)] use std::any::TypeId; -use std::error::Error; -use std::fmt::{Debug, Display, Formatter, Result}; +use std::error::Error as StdError; +use std::fmt::{Debug, Display, Formatter}; use std::panic::Location; pub mod prelude { @@ -110,28 +110,29 @@ pub mod prelude { pub mod v1 { //! convenience prelude pub use super::super::ChainErrorDown as _; + pub use super::super::Error as ChainError; + pub use super::super::Result as ChainResult; pub use super::super::ResultTrait as _; - pub use super::super::{ChainError, ChainResult}; pub use crate::{derive_err_kind, derive_str_context}; } } /// chains an inner error kind `T` with a causing error -pub struct ChainError { +pub struct Error { occurrence: Option, kind: T, - error_cause: Option>, + error_cause: Option>, } /// convenience type alias -pub type ChainResult = std::result::Result>; +pub type Result = std::result::Result>; -impl ChainError { +impl Error { /// Use the `context()` or `map_context()` Result methods instead of calling this directly #[inline] pub fn new( kind: T, - error_cause: Option>, + error_cause: Option>, occurrence: Option, ) -> Self { Self { @@ -142,7 +143,7 @@ impl ChainError { } /// return the root cause of the error chain, if any exists - pub fn root_cause(&self) -> Option<&(dyn Error + 'static)> { + pub fn root_cause(&self) -> Option<&(dyn StdError + 'static)> { self.iter().last() } @@ -190,9 +191,9 @@ impl ChainError { /// # } /// ``` #[inline] - pub fn find_cause(&self) -> Option<&U> { + pub fn find_cause(&self) -> Option<&U> { self.iter() - .filter_map(::downcast_ref::) + .filter_map(::downcast_ref::) .next() } @@ -213,9 +214,9 @@ impl ChainError { /// err.find_chain_cause::(); /// ``` #[inline] - pub fn find_chain_cause(&self) -> Option<&ChainError> { + pub fn find_chain_cause(&self) -> Option<&Error> { self.iter() - .filter_map(::downcast_ref::>) + .filter_map(::downcast_ref::>) .next() } @@ -240,10 +241,10 @@ impl ChainError { /// err.find_kind_or_cause::(); /// ``` #[inline] - pub fn find_kind_or_cause(&self) -> Option<&U> { + pub fn find_kind_or_cause(&self) -> Option<&U> { self.iter() .filter_map(|e| { - e.downcast_ref::>() + e.downcast_ref::>() .map(|e| e.kind()) .or_else(|| e.downcast_ref::()) }) @@ -313,7 +314,7 @@ impl ChainError { /// /// # Example #[inline] - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { ErrorIter { current: Some(self), } @@ -321,32 +322,26 @@ impl ChainError { } /// Convenience methods for `Result<>` to turn the error into a decorated ChainError -pub trait ResultTrait>> { +pub trait ResultTrait>> { /// Decorate the error with a `kind` of type `T` and the source `Location` - fn context( - self, - kind: T, - ) -> std::result::Result>; + fn context(self, kind: T) -> std::result::Result>; /// Decorate the `error` with a `kind` of type `T` produced with a `FnOnce(&error)` and the source `Location` fn map_context T>( self, op: F, - ) -> std::result::Result>; + ) -> std::result::Result>; } -impl>> ResultTrait +impl>> ResultTrait for std::result::Result { #[track_caller] #[inline] - fn context( - self, - kind: T, - ) -> std::result::Result> { + fn context(self, kind: T) -> std::result::Result> { match self { Ok(t) => Ok(t), - Err(error_cause) => Err(ChainError::new( + Err(error_cause) => Err(Error::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), @@ -359,12 +354,12 @@ impl>> ResultTrait fn map_context T>( self, op: F, - ) -> std::result::Result> { + ) -> std::result::Result> { match self { Ok(t) => Ok(t), Err(error_cause) => { let kind = op(&error_cause); - Err(ChainError::new( + Err(Error::new( kind, Some(error_cause.into()), Some(Location::caller().to_string()), @@ -376,21 +371,21 @@ impl>> ResultTrait /// An iterator over all error causes/sources pub struct ErrorIter<'a> { - current: Option<&'a (dyn Error + 'static)>, + current: Option<&'a (dyn StdError + 'static)>, } impl<'a> Iterator for ErrorIter<'a> { - type Item = &'a (dyn Error + 'static); + type Item = &'a (dyn StdError + 'static); #[inline] fn next(&mut self) -> Option { let current = self.current; - self.current = self.current.and_then(Error::source); + self.current = self.current.and_then(StdError::source); current } } -impl std::ops::Deref for ChainError { +impl std::ops::Deref for Error { type Target = T; #[inline] @@ -404,28 +399,28 @@ pub trait ChainErrorDown { /// Test if of type `ChainError` fn is_chain(&self) -> bool; /// Downcast to a reference of `ChainError` - fn downcast_chain_ref(&self) -> Option<&ChainError>; + fn downcast_chain_ref(&self) -> Option<&Error>; /// Downcast to a mutable reference of `ChainError` - fn downcast_chain_mut(&mut self) -> Option<&mut ChainError>; + fn downcast_chain_mut(&mut self) -> Option<&mut Error>; /// Downcast to T of `ChainError` - fn downcast_inner_ref(&self) -> Option<&T>; + fn downcast_inner_ref(&self) -> Option<&T>; /// Downcast to T mutable reference of `ChainError` - fn downcast_inner_mut(&mut self) -> Option<&mut T>; + fn downcast_inner_mut(&mut self) -> Option<&mut T>; } -impl ChainErrorDown for ChainError { +impl ChainErrorDown for Error { #[inline] fn is_chain(&self) -> bool { TypeId::of::() == TypeId::of::() } #[inline] - fn downcast_chain_ref(&self) -> Option<&ChainError> { + fn downcast_chain_ref(&self) -> Option<&Error> { if self.is_chain::() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] - Some(*(self as *const dyn Error as *const &ChainError)) + Some(*(self as *const dyn StdError as *const &Error)) } } else { None @@ -433,24 +428,24 @@ impl ChainErrorDown for ChainError { } #[inline] - fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { + fn downcast_chain_mut(&mut self) -> Option<&mut Error> { if self.is_chain::() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] - Some(&mut *(self as *mut dyn Error as *mut &mut ChainError)) + Some(&mut *(self as *mut dyn StdError as *mut &mut Error)) } } else { None } } #[inline] - fn downcast_inner_ref(&self) -> Option<&T> { + fn downcast_inner_ref(&self) -> Option<&T> { if self.is_chain::() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] - Some(&(*(self as *const dyn Error as *const &ChainError)).kind) + Some(&(*(self as *const dyn StdError as *const &Error)).kind) } } else { None @@ -458,12 +453,12 @@ impl ChainErrorDown for ChainError { } #[inline] - fn downcast_inner_mut(&mut self) -> Option<&mut T> { + fn downcast_inner_mut(&mut self) -> Option<&mut T> { if self.is_chain::() { #[allow(clippy::cast_ptr_alignment)] unsafe { #[allow(trivial_casts)] - Some(&mut (*(self as *mut dyn Error as *mut &mut ChainError)).kind) + Some(&mut (*(self as *mut dyn StdError as *mut &mut Error)).kind) } } else { None @@ -471,126 +466,126 @@ impl ChainErrorDown for ChainError { } } -impl ChainErrorDown for dyn Error + 'static { +impl ChainErrorDown for dyn StdError + 'static { #[inline] fn is_chain(&self) -> bool { - self.is::>() + self.is::>() } #[inline] - fn downcast_chain_ref(&self) -> Option<&ChainError> { - self.downcast_ref::>() + fn downcast_chain_ref(&self) -> Option<&Error> { + self.downcast_ref::>() } #[inline] - fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { - self.downcast_mut::>() + fn downcast_chain_mut(&mut self) -> Option<&mut Error> { + self.downcast_mut::>() } #[inline] - fn downcast_inner_ref(&self) -> Option<&T> { + fn downcast_inner_ref(&self) -> Option<&T> { self.downcast_ref::() - .or_else(|| self.downcast_ref::>().map(|e| e.kind())) + .or_else(|| self.downcast_ref::>().map(|e| e.kind())) } #[inline] - fn downcast_inner_mut(&mut self) -> Option<&mut T> { + fn downcast_inner_mut(&mut self) -> Option<&mut T> { if self.is::() { return self.downcast_mut::(); } - self.downcast_mut::>() + self.downcast_mut::>() .and_then(|e| e.downcast_inner_mut::()) } } -impl ChainErrorDown for dyn Error + 'static + Send { +impl ChainErrorDown for dyn StdError + 'static + Send { #[inline] fn is_chain(&self) -> bool { - self.is::>() + self.is::>() } #[inline] - fn downcast_chain_ref(&self) -> Option<&ChainError> { - self.downcast_ref::>() + fn downcast_chain_ref(&self) -> Option<&Error> { + self.downcast_ref::>() } #[inline] - fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { - self.downcast_mut::>() + fn downcast_chain_mut(&mut self) -> Option<&mut Error> { + self.downcast_mut::>() } #[inline] - fn downcast_inner_ref(&self) -> Option<&T> { + fn downcast_inner_ref(&self) -> Option<&T> { self.downcast_ref::() - .or_else(|| self.downcast_ref::>().map(|e| e.kind())) + .or_else(|| self.downcast_ref::>().map(|e| e.kind())) } #[inline] - fn downcast_inner_mut(&mut self) -> Option<&mut T> { + fn downcast_inner_mut(&mut self) -> Option<&mut T> { if self.is::() { return self.downcast_mut::(); } - self.downcast_mut::>() + self.downcast_mut::>() .and_then(|e| e.downcast_inner_mut::()) } } -impl ChainErrorDown for dyn Error + 'static + Send + Sync { +impl ChainErrorDown for dyn StdError + 'static + Send + Sync { #[inline] fn is_chain(&self) -> bool { - self.is::>() + self.is::>() } #[inline] - fn downcast_chain_ref(&self) -> Option<&ChainError> { - self.downcast_ref::>() + fn downcast_chain_ref(&self) -> Option<&Error> { + self.downcast_ref::>() } #[inline] - fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> { - self.downcast_mut::>() + fn downcast_chain_mut(&mut self) -> Option<&mut Error> { + self.downcast_mut::>() } #[inline] - fn downcast_inner_ref(&self) -> Option<&T> { + fn downcast_inner_ref(&self) -> Option<&T> { self.downcast_ref::() - .or_else(|| self.downcast_ref::>().map(|e| e.kind())) + .or_else(|| self.downcast_ref::>().map(|e| e.kind())) } #[inline] - fn downcast_inner_mut(&mut self) -> Option<&mut T> { + fn downcast_inner_mut(&mut self) -> Option<&mut T> { if self.is::() { return self.downcast_mut::(); } - self.downcast_mut::>() + self.downcast_mut::>() .and_then(|e| e.downcast_inner_mut::()) } } -impl Error for ChainError { +impl StdError for Error { #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { + fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() - .map(|e| e.as_ref() as &(dyn Error + 'static)) + .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } -impl Error for &mut ChainError { +impl StdError for &mut Error { #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { + fn source(&self) -> Option<&(dyn StdError + 'static)> { self.error_cause .as_ref() - .map(|e| e.as_ref() as &(dyn Error + 'static)) + .map(|e| e.as_ref() as &(dyn StdError + 'static)) } } -impl Display for ChainError { +impl Display for Error { #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.kind)?; if f.alternate() { @@ -603,9 +598,9 @@ impl Display for ChainError { } } -impl Debug for ChainError { +impl Debug for Error { #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if f.alternate() { let mut f = f.debug_struct(&format!("ChainError<{}>", std::any::type_name::())); @@ -636,14 +631,14 @@ impl Debug for ChainError { } } -impl From for ChainError +impl From for Error where T: 'static + Display + Debug, { #[track_caller] #[inline] - fn from(e: T) -> ChainError { - ChainError::new(e, None, Some(Location::caller().to_string())) + fn from(e: T) -> Error { + Error::new(e, None, Some(Location::caller().to_string())) } } /// Convenience macro to create a "new type" T(String) and implement Display + Debug for T @@ -651,7 +646,7 @@ where /// # Examples /// /// ```rust -/// # use crate::chainerror::*; +/// # use chainerror::prelude::v1::*; /// # use std::error::Error; /// # use std::io; /// # use std::result::Result; @@ -770,7 +765,7 @@ macro_rules! derive_str_context { #[macro_export] macro_rules! derive_err_kind { ($e:ident, $k:ident) => { - pub struct $e($crate::ChainError<$k>); + pub struct $e($crate::Error<$k>); impl $e { pub fn kind(&self) -> &$k { @@ -780,12 +775,12 @@ macro_rules! derive_err_kind { impl From<$k> for $e { fn from(e: $k) -> Self { - $e($crate::ChainError::new(e, None, None)) + $e($crate::Error::new(e, None, None)) } } impl From> for $e { - fn from(e: $crate::ChainError<$k>) -> Self { + fn from(e: $crate::Error<$k>) -> Self { $e(e) } } From 165c1b939c9289dc47782345d81c2b0617b1d07d Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Thu, 27 Jul 2023 14:37:19 +0200 Subject: [PATCH 21/45] chore: suppress clippy errors in tutorial code is in this state by purpose Signed-off-by: Harald Hoyer --- examples/tutorial1.rs | 3 +++ examples/tutorial13.rs | 3 +++ examples/tutorial14.rs | 3 +++ examples/tutorial15.rs | 3 +++ examples/tutorial6.rs | 3 +++ examples/tutorial7.rs | 3 +++ 6 files changed, 18 insertions(+) diff --git a/examples/tutorial1.rs b/examples/tutorial1.rs index 4ac1eb3..939fb73 100644 --- a/examples/tutorial1.rs +++ b/examples/tutorial1.rs @@ -1,3 +1,6 @@ +#![allow(clippy::single_match)] +#![allow(clippy::redundant_pattern_matching)] + use std::error::Error; use std::io; use std::result::Result; diff --git a/examples/tutorial13.rs b/examples/tutorial13.rs index 19bc713..35dc832 100644 --- a/examples/tutorial13.rs +++ b/examples/tutorial13.rs @@ -1,3 +1,6 @@ +#![allow(clippy::single_match)] +#![allow(clippy::redundant_pattern_matching)] + pub mod mycrate { use chainerror::prelude::v1::*; use std::io; diff --git a/examples/tutorial14.rs b/examples/tutorial14.rs index 9c624dc..5ec216e 100644 --- a/examples/tutorial14.rs +++ b/examples/tutorial14.rs @@ -1,3 +1,6 @@ +#![allow(clippy::single_match)] +#![allow(clippy::redundant_pattern_matching)] + pub mod mycrate { use std::error::Error as StdError; diff --git a/examples/tutorial15.rs b/examples/tutorial15.rs index 3b0eef3..39594b4 100644 --- a/examples/tutorial15.rs +++ b/examples/tutorial15.rs @@ -1,3 +1,6 @@ +#![allow(clippy::single_match)] +#![allow(clippy::redundant_pattern_matching)] + pub mod mycrate { use chainerror::prelude::v1::*; use std::io; diff --git a/examples/tutorial6.rs b/examples/tutorial6.rs index 9310c8c..de1a0a8 100644 --- a/examples/tutorial6.rs +++ b/examples/tutorial6.rs @@ -1,3 +1,6 @@ +#![allow(clippy::single_match)] +#![allow(clippy::redundant_pattern_matching)] + use chainerror::prelude::v1::*; use std::error::Error; use std::io; diff --git a/examples/tutorial7.rs b/examples/tutorial7.rs index 969588d..9a1ca67 100644 --- a/examples/tutorial7.rs +++ b/examples/tutorial7.rs @@ -1,3 +1,6 @@ +#![allow(clippy::single_match)] +#![allow(clippy::redundant_pattern_matching)] + use chainerror::prelude::v1::*; use std::error::Error; use std::io; From cf62d1a9f979ee9df9a615fbf8f369f2d7daef94 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Thu, 27 Jul 2023 14:54:57 +0200 Subject: [PATCH 22/45] chore: remove need for `cargo readme` Just use `#![doc = include_str!("../README.md")]` Signed-off-by: Harald Hoyer --- .github/workflows/rust.yml | 15 +----- README.md | 10 +--- src/lib.rs | 99 +------------------------------------- 3 files changed, 3 insertions(+), 121 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 2f81b29..3fc2545 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: version: - - 1.46.0 + - 1.54.0 - stable - beta - nightly @@ -70,16 +70,3 @@ jobs: with: command: clippy args: -- -D warnings - - readme: - name: cargo readme - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - profile: minimal - override: true - - run: cargo install cargo-readme - - run: cargo readme > README.md && git diff --exit-code diff --git a/README.md b/README.md index 2441848..7a7b3a9 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,6 @@ [![Crate](https://img.shields.io/crates/v/chainerror.svg)](https://crates.io/crates/chainerror) [![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/chainerror/) -[![Coverage Status](https://coveralls.io/repos/github/haraldh/chainerror/badge.svg?branch=master)](https://coveralls.io/github/haraldh/chainerror?branch=master) -[![Workflow Status](https://github.com/haraldh/chainerror/workflows/Rust/badge.svg)](https://github.com/haraldh/chainerror/actions?query=workflow%3A%22Rust%22) -[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/haraldh/chainerror.svg)](https://isitmaintained.com/project/haraldh/chainerror "Average time to resolve an issue") -[![Percentage of issues still open](https://isitmaintained.com/badge/open/haraldh/chainerror.svg)](https://isitmaintained.com/project/haraldh/chainerror "Percentage of issues still open") +[![Coverage Status](https://codecov.io/gh/haraldh/chainerror/branch/master/graph/badge.svg?token=HGLJFGA11B)](https://codecov.io/gh/haraldh/chainerror) ![Maintenance](https://img.shields.io/badge/maintenance-activly--developed-brightgreen.svg) # chainerror @@ -93,11 +90,6 @@ Along with the `ChainError` struct, `chainerror` comes with some useful helpe Debug information is worth it! -### Features - -`display-cause` -: turn on printing a backtrace of the errors in `Display` - ## Tutorial Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) diff --git a/src/lib.rs b/src/lib.rs index 0ea1dbe..50dbd51 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,101 +1,4 @@ -//! `chainerror` provides an error backtrace without doing a real backtrace, so even after you `strip` your -//! binaries, you still have the error backtrace. -//! -//! Having nested function returning errors, the output doesn't tell where the error originates from. -//! -//! ```rust -//! use std::path::PathBuf; -//! -//! type BoxedError = Box; -//! fn read_config_file(path: PathBuf) -> Result<(), BoxedError> { -//! // do stuff, return other errors -//! let _buf = std::fs::read_to_string(&path)?; -//! // do stuff, return other errors -//! Ok(()) -//! } -//! -//! fn process_config_file() -> Result<(), BoxedError> { -//! // do stuff, return other errors -//! let _buf = read_config_file("foo.txt".into())?; -//! // do stuff, return other errors -//! Ok(()) -//! } -//! -//! fn main() { -//! if let Err(e) = process_config_file() { -//! eprintln!("Error:\n{:?}", e); -//! } -//! } -//! ``` -//! -//! This gives the output: -//! ```console -//! Error: -//! Os { code: 2, kind: NotFound, message: "No such file or directory" } -//! ``` -//! and you have no idea where it comes from. -//! -//! -//! With `chainerror`, you can supply a context and get a nice error backtrace: -//! -//! ```rust -//! use chainerror::prelude::v1::*; -//! use std::path::PathBuf; -//! -//! type BoxedError = Box; -//! fn read_config_file(path: PathBuf) -> Result<(), BoxedError> { -//! // do stuff, return other errors -//! let _buf = std::fs::read_to_string(&path).context(format!("Reading file: {:?}", &path))?; -//! // do stuff, return other errors -//! Ok(()) -//! } -//! -//! fn process_config_file() -> Result<(), BoxedError> { -//! // do stuff, return other errors -//! let _buf = read_config_file("foo.txt".into()).context("read the config file")?; -//! // do stuff, return other errors -//! Ok(()) -//! } -//! -//! fn main() { -//! if let Err(e) = process_config_file() { -//! eprintln!("Error:\n{:?}", e); -//! # let s = format!("{:?}", e); -//! # let lines = s.lines().collect::>(); -//! # assert_eq!(lines.len(), 5); -//! # assert!(lines[0].starts_with("src/lib.rs:")); -//! # assert_eq!(lines[1], "Caused by:"); -//! # assert!(lines[2].starts_with("src/lib.rs:")); -//! # assert_eq!(lines[3], "Caused by:"); -//! # assert_eq!(lines[4], "Os { code: 2, kind: NotFound, message: \"No such file or directory\" }"); -//! } -//! # else { panic!(); } -//! } -//! ``` -//! -//! with the output: -//! ```console -//! Error: -//! examples/simple.rs:14:51: read the config file -//! Caused by: -//! examples/simple.rs:7:47: Reading file: "foo.txt" -//! Caused by: -//! Os { code: 2, kind: NotFound, message: "No such file or directory" } -//! ``` -//! -//! `chainerror` uses `.source()` of `std::error::Error` along with `#[track_caller]` and `Location` to provide a nice debug error backtrace. -//! It encapsulates all types, which have `Display + Debug` and can store the error cause internally. -//! -//! Along with the `ChainError` struct, `chainerror` comes with some useful helper macros to save a lot of typing. -//! -//! `chainerror` has no dependencies! -//! -//! Debug information is worth it! -//! -//! # Tutorial -//! -//! Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) - +#![doc = include_str!("../README.md")] #![deny(clippy::all)] #![allow(clippy::needless_doctest_main)] #![deny(missing_docs)] From 87bac108d29ca664452f314bdebca840a705eb87 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Thu, 27 Jul 2023 15:28:18 +0200 Subject: [PATCH 23/45] chore: Release chainerror version 0.8.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index dae493e..9c1824f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chainerror" -version = "0.7.2-alpha.1" +version = "0.8.0" authors = ["Harald Hoyer "] edition = "2018" license = "MIT OR Apache-2.0" From 9e7492f67f0036722ea0e2289561de13eff12c24 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Thu, 27 Jul 2023 15:29:06 +0200 Subject: [PATCH 24/45] chore: Release chainerror version 0.8.1-alpha.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9c1824f..3bac3ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chainerror" -version = "0.8.0" +version = "0.8.1-alpha.1" authors = ["Harald Hoyer "] edition = "2018" license = "MIT OR Apache-2.0" From 968c83983aebe60f4c2931262186641a59bb6434 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Jul 2023 15:04:29 +0200 Subject: [PATCH 25/45] fix: re-add the basic doc test It got lost with the `README.md` conversion. Signed-off-by: Harald Hoyer --- tests/test_basic.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/test_basic.rs diff --git a/tests/test_basic.rs b/tests/test_basic.rs new file mode 100644 index 0000000..ac5412c --- /dev/null +++ b/tests/test_basic.rs @@ -0,0 +1,34 @@ +use chainerror::prelude::v1::*; + +#[test] +fn test_basic() { + use std::path::PathBuf; + type BoxedError = Box; + fn read_config_file(path: PathBuf) -> Result<(), BoxedError> { + // do stuff, return other errors + let _buf = std::fs::read_to_string(&path).context(format!("Reading file: {:?}", &path))?; + // do stuff, return other errors + Ok(()) + } + fn process_config_file() -> Result<(), BoxedError> { + // do stuff, return other errors + read_config_file("_non_existent.txt".into()).context("read the config file")?; + // do stuff, return other errors + Ok(()) + } + + if let Err(e) = process_config_file() { + let os_notfound_error = std::io::Error::from_raw_os_error(2); + eprintln!("Error:\n{:?}", e); + let s = format!("{:?}", e); + let lines = s.lines().collect::>(); + assert_eq!(lines.len(), 5); + assert!(lines[0].starts_with("tests/test_basic.rs:")); + assert_eq!(lines[1], "Caused by:"); + assert!(lines[2].starts_with("tests/test_basic.rs:")); + assert_eq!(lines[3], "Caused by:"); + assert_eq!(lines[4], format!("{:?}", os_notfound_error)); + } else { + panic!(); + } +} From a116310c4d20aea4ec0a2c444b1c18161a68a590 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Jul 2023 15:05:49 +0200 Subject: [PATCH 26/45] doc: improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - remove `Chain…` mentions in the docs - add doc links - add rustdoc feature to scrape the examples for code Signed-off-by: Harald Hoyer --- Cargo.toml | 3 +++ README.md | 6 +++--- src/lib.rs | 32 ++++++++++++++++---------------- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3bac3ee..668f5a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,6 @@ 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" } + +[package.metadata.docs.rs] +cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] diff --git a/README.md b/README.md index 7a7b3a9..7df21e4 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ Os { code: 2, kind: NotFound, message: "No such file or directory" } `chainerror` uses `.source()` of `std::error::Error` along with `#[track_caller]` and `Location` to provide a nice debug error backtrace. It encapsulates all types, which have `Display + Debug` and can store the error cause internally. -Along with the `ChainError` struct, `chainerror` comes with some useful helper macros to save a lot of typing. +Along with the `Error` struct, `chainerror` comes with some useful helper macros to save a lot of typing. `chainerror` has no dependencies! @@ -98,8 +98,8 @@ Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) Licensed under either of -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. diff --git a/src/lib.rs b/src/lib.rs index 50dbd51..1b6039c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,9 +100,9 @@ impl Error { .next() } - /// Find the first error cause of type `ChainError`, if any exists + /// Find the first error cause of type [`Error`](Error), if any exists /// - /// Same as `find_cause`, but hides the `ChainError` implementation internals + /// Same as `find_cause`, but hides the [`Error`](Error) implementation internals /// /// # Examples /// @@ -123,9 +123,9 @@ impl Error { .next() } - /// Find the first error cause of type `ChainError` or `U`, if any exists and return `U` + /// Find the first error cause of type [`Error`](Error) or `U`, if any exists and return `U` /// - /// Same as `find_cause` and `find_chain_cause`, but hides the `ChainError` implementation internals + /// Same as `find_cause` and `find_chain_cause`, but hides the [`Error`](Error) implementation internals /// /// # Examples /// @@ -154,7 +154,7 @@ impl Error { .next() } - /// Return a reference to T of `ChainError` + /// Return a reference to T of [`Error`](Error) /// /// # Examples /// @@ -224,7 +224,7 @@ impl Error { } } -/// Convenience methods for `Result<>` to turn the error into a decorated ChainError +/// Convenience methods for `Result<>` to turn the error into a decorated [`Error`](Error) pub trait ResultTrait>> { /// Decorate the error with a `kind` of type `T` and the source `Location` fn context(self, kind: T) -> std::result::Result>; @@ -297,17 +297,17 @@ impl std::ops::Deref for Error { } } -/// Convenience trait to hide the `ChainError` implementation internals +/// Convenience trait to hide the [`Error`](Error) implementation internals pub trait ChainErrorDown { - /// Test if of type `ChainError` + /// Test if of type `Error` fn is_chain(&self) -> bool; - /// Downcast to a reference of `ChainError` + /// Downcast to a reference of `Error` fn downcast_chain_ref(&self) -> Option<&Error>; - /// Downcast to a mutable reference of `ChainError` + /// Downcast to a mutable reference of `Error` fn downcast_chain_mut(&mut self) -> Option<&mut Error>; - /// Downcast to T of `ChainError` + /// Downcast to T of `Error` fn downcast_inner_ref(&self) -> Option<&T>; - /// Downcast to T mutable reference of `ChainError` + /// Downcast to T mutable reference of `Error` fn downcast_inner_mut(&mut self) -> Option<&mut T>; } @@ -505,7 +505,7 @@ impl Debug for Error { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if f.alternate() { - let mut f = f.debug_struct(&format!("ChainError<{}>", std::any::type_name::())); + let mut f = f.debug_struct(&format!("Error<{}>", std::any::type_name::())); let f = f .field("occurrence", &self.occurrence) @@ -601,9 +601,9 @@ macro_rules! derive_str_context { }; } -/// Derive an Error for an ErrorKind, which wraps a `ChainError` and implements a `kind()` method +/// Derive an Error for an ErrorKind, which wraps a [`Error`](Error) and implements a `kind()` method /// -/// It basically hides `ChainError` to the outside and only exposes the `kind()` +/// It basically hides [`Error`](Error) to the outside and only exposes the [`kind()`](Error::kind) /// method. /// /// Error::kind() returns the ErrorKind @@ -682,7 +682,7 @@ macro_rules! derive_err_kind { } } - impl From> for $e { + impl From<$crate::Error<$k>> for $e { fn from(e: $crate::Error<$k>) -> Self { $e(e) } From 11aeeb17c3ac1dfc4971ba6de0b25f720ae88876 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Jul 2023 15:13:50 +0200 Subject: [PATCH 27/45] ci: use ubuntu-latest The ubuntu-18.04 environment is deprecated. Signed-off-by: Harald Hoyer --- .github/workflows/gh-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index bcc15ae..7062679 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -7,7 +7,7 @@ on: jobs: deploy: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 From cacfb37d4469280c8c62bcb78c317692f2c90266 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Jul 2023 15:18:14 +0200 Subject: [PATCH 28/45] chore: Release chainerror version 0.8.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 668f5a0..c4f3b17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chainerror" -version = "0.8.1-alpha.1" +version = "0.8.2" authors = ["Harald Hoyer "] edition = "2018" license = "MIT OR Apache-2.0" From cb2e1509e590805018bb8d1633d0f4bdfb92bb72 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Jul 2023 15:28:27 +0200 Subject: [PATCH 29/45] chore: remove more `Chain..` occurences Signed-off-by: Harald Hoyer --- src/lib.rs | 12 ++++++------ tests/test_basic.rs | 3 +-- tests/test_iter.rs | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1b6039c..8aedf66 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,9 +111,9 @@ impl Error { /// # derive_str_context!(FooError); /// # let err = ChainError::new(String::new(), None, None); /// // Instead of writing - /// err.find_cause::>(); + /// err.find_cause::>(); /// - /// // leave out the ChainError implementation detail + /// // leave out the chainerror::Error implementation detail /// err.find_chain_cause::(); /// ``` #[inline] @@ -134,13 +134,13 @@ impl Error { /// # derive_str_context!(FooErrorKind); /// # let err = ChainError::new(String::new(), None, None); /// // Instead of writing - /// err.find_cause::>(); + /// err.find_cause::>(); /// // and/or /// err.find_chain_cause::(); /// // and/or /// err.find_cause::(); /// - /// // leave out the ChainError implementation detail + /// // leave out the chainerror::Error implementation detail /// err.find_kind_or_cause::(); /// ``` #[inline] @@ -192,7 +192,7 @@ impl Error { /// # } /// # } /// - /// fn func1() -> ChainResult<(), Func1ErrorKind> { + /// fn func1() -> chainerror::Result<(), Func1ErrorKind> { /// func2().context(Func1ErrorKind::Func2)?; /// do_some_io().context(Func1ErrorKind::IO("bar.txt".into()))?; /// Ok(()) @@ -559,7 +559,7 @@ where /// # } /// derive_str_context!(Func2Error); /// -/// fn func2() -> ChainResult<(), Func2Error> { +/// fn func2() -> chainerror::Result<(), Func2Error> { /// let filename = "foo.txt"; /// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?; /// Ok(()) diff --git a/tests/test_basic.rs b/tests/test_basic.rs index ac5412c..dfdc5f8 100644 --- a/tests/test_basic.rs +++ b/tests/test_basic.rs @@ -1,4 +1,4 @@ -use chainerror::prelude::v1::*; +use chainerror::ResultTrait; #[test] fn test_basic() { @@ -19,7 +19,6 @@ fn test_basic() { if let Err(e) = process_config_file() { let os_notfound_error = std::io::Error::from_raw_os_error(2); - eprintln!("Error:\n{:?}", e); let s = format!("{:?}", e); let lines = s.lines().collect::>(); assert_eq!(lines.len(), 5); diff --git a/tests/test_iter.rs b/tests/test_iter.rs index f5f0e58..3a464b1 100644 --- a/tests/test_iter.rs +++ b/tests/test_iter.rs @@ -1,4 +1,4 @@ -use chainerror::prelude::v1::*; +use chainerror::ResultTrait; use std::error::Error; use std::io; From 3ab04cca257bd05d35e18cd4306a190573fd6cd2 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Jul 2023 16:18:54 +0200 Subject: [PATCH 30/45] 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 --- src/lib.rs | 66 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8aedf66..a4c82c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,11 +12,19 @@ pub mod prelude { //! convenience prelude pub mod v1 { //! convenience prelude - pub use super::super::ChainErrorDown as _; + pub use super::super::Context as _; + pub use super::super::Context as ResultTrait; pub use super::super::Error as ChainError; + pub use super::super::ErrorDown as _; + pub use super::super::ErrorDown as ChainErrorDown; pub use super::super::Result as ChainResult; - pub use super::super::ResultTrait as _; - pub use crate::{derive_err_kind, derive_str_context}; + pub use crate::err_kind as derive_err_kind; + pub use crate::str_context as derive_str_context; + } + pub mod v2 { + //! convenience prelude + pub use super::super::Context as _; + pub use super::super::ErrorDown as _; } } @@ -55,7 +63,8 @@ impl Error { /// # Examples /// /// ```rust - /// use chainerror::prelude::v1::*; + /// use chainerror::Context as _; + /// use chainerror::ErrorDown as _; /// use std::error::Error; /// use std::io; /// @@ -64,7 +73,7 @@ impl Error { /// Ok(()) /// } /// - /// derive_str_context!(Func2Error); + /// chainerror::str_context!(Func2Error); /// /// fn func2() -> Result<(), Box> { /// let filename = "foo.txt"; @@ -72,7 +81,7 @@ impl Error { /// Ok(()) /// } /// - /// derive_str_context!(Func1Error); + /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box> { /// func2().context(Func1Error("func1 error".into()))?; @@ -107,9 +116,8 @@ impl Error { /// # Examples /// /// ```rust - /// # use chainerror::prelude::v1::*; - /// # derive_str_context!(FooError); - /// # let err = ChainError::new(String::new(), None, None); + /// # chainerror::str_context!(FooError); + /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::>(); /// @@ -130,9 +138,8 @@ impl Error { /// # Examples /// /// ```rust - /// # use chainerror::prelude::v1::*; - /// # derive_str_context!(FooErrorKind); - /// # let err = ChainError::new(String::new(), None, None); + /// # chainerror::str_context!(FooErrorKind); + /// # let err = chainerror::Error::new(String::new(), None, None); /// // Instead of writing /// err.find_cause::>(); /// // and/or @@ -159,7 +166,7 @@ impl Error { /// # Examples /// /// ```rust - /// use chainerror::prelude::v1::*; + /// use chainerror::Context as _; /// use std::error::Error; /// use std::io; /// @@ -168,7 +175,7 @@ impl Error { /// Ok(()) /// } /// - /// derive_str_context!(Func2Error); + /// chainerror::str_context!(Func2Error); /// /// fn func2() -> Result<(), Box> { /// let filename = "foo.txt"; @@ -225,7 +232,7 @@ impl Error { } /// Convenience methods for `Result<>` to turn the error into a decorated [`Error`](Error) -pub trait ResultTrait>> { +pub trait Context>> { /// Decorate the error with a `kind` of type `T` and the source `Location` fn context(self, kind: T) -> std::result::Result>; @@ -236,7 +243,7 @@ pub trait ResultTrait>> { ) -> std::result::Result>; } -impl>> ResultTrait +impl>> Context for std::result::Result { #[track_caller] @@ -298,7 +305,7 @@ impl std::ops::Deref for Error { } /// Convenience trait to hide the [`Error`](Error) implementation internals -pub trait ChainErrorDown { +pub trait ErrorDown { /// Test if of type `Error` fn is_chain(&self) -> bool; /// Downcast to a reference of `Error` @@ -311,7 +318,7 @@ pub trait ChainErrorDown { fn downcast_inner_mut(&mut self) -> Option<&mut T>; } -impl ChainErrorDown for Error { +impl ErrorDown for Error { #[inline] fn is_chain(&self) -> bool { TypeId::of::() == TypeId::of::() @@ -369,7 +376,7 @@ impl ChainErrorDown for Error { } } -impl ChainErrorDown for dyn StdError + 'static { +impl ErrorDown for dyn StdError + 'static { #[inline] fn is_chain(&self) -> bool { self.is::>() @@ -402,7 +409,7 @@ impl ChainErrorDown for dyn StdError + 'static { } } -impl ChainErrorDown for dyn StdError + 'static + Send { +impl ErrorDown for dyn StdError + 'static + Send { #[inline] fn is_chain(&self) -> bool { self.is::>() @@ -435,7 +442,7 @@ impl ChainErrorDown for dyn StdError + 'static + Send { } } -impl ChainErrorDown for dyn StdError + 'static + Send + Sync { +impl ErrorDown for dyn StdError + 'static + Send + Sync { #[inline] fn is_chain(&self) -> bool { self.is::>() @@ -549,7 +556,8 @@ where /// # Examples /// /// ```rust -/// # use chainerror::prelude::v1::*; +/// # use chainerror::Context as _; +/// # use chainerror::ErrorDown as _; /// # use std::error::Error; /// # use std::io; /// # use std::result::Result; @@ -557,7 +565,7 @@ where /// # Err(io::Error::from(io::ErrorKind::NotFound))?; /// # Ok(()) /// # } -/// derive_str_context!(Func2Error); +/// chainerror::str_context!(Func2Error); /// /// fn func2() -> chainerror::Result<(), Func2Error> { /// let filename = "foo.txt"; @@ -565,7 +573,7 @@ where /// Ok(()) /// } /// -/// derive_str_context!(Func1Error); +/// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box> { /// func2().context(Func1Error("func1 error".into()))?; @@ -573,7 +581,7 @@ where /// } /// # if let Err(e) = func1() { /// # if let Some(f1err) = e.downcast_chain_ref::() { -/// # assert!(f1err.find_cause::>().is_some()); +/// # assert!(f1err.find_cause::>().is_some()); /// # assert!(f1err.find_chain_cause::().is_some()); /// # } else { /// # panic!(); @@ -583,7 +591,7 @@ where /// # } /// ``` #[macro_export] -macro_rules! derive_str_context { +macro_rules! str_context { ($e:ident) => { #[derive(Clone)] pub struct $e(pub String); @@ -612,7 +620,7 @@ macro_rules! derive_str_context { /// # Examples /// /// ```rust -/// use chainerror::prelude::v1::*; +/// use chainerror::Context as _; /// use std::io; /// /// fn do_some_io(_f: &str) -> std::result::Result<(), io::Error> { @@ -626,7 +634,7 @@ macro_rules! derive_str_context { /// Unknown, /// } /// -/// derive_err_kind!(Error, ErrorKind); +/// chainerror::err_kind!(Error, ErrorKind); /// /// impl std::fmt::Display for ErrorKind { /// fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result { @@ -666,7 +674,7 @@ macro_rules! derive_str_context { /// } /// ``` #[macro_export] -macro_rules! derive_err_kind { +macro_rules! err_kind { ($e:ident, $k:ident) => { pub struct $e($crate::Error<$k>); From 55c16d7867f7dd1e2177a846453e4926b24bcaa2 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Jul 2023 16:20:55 +0200 Subject: [PATCH 31/45] tests: use new names Signed-off-by: Harald Hoyer --- tests/test_basic.rs | 2 +- tests/test_iter.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_basic.rs b/tests/test_basic.rs index dfdc5f8..bbc9f16 100644 --- a/tests/test_basic.rs +++ b/tests/test_basic.rs @@ -1,4 +1,4 @@ -use chainerror::ResultTrait; +use chainerror::Context; #[test] fn test_basic() { diff --git a/tests/test_iter.rs b/tests/test_iter.rs index 3a464b1..9023078 100644 --- a/tests/test_iter.rs +++ b/tests/test_iter.rs @@ -1,4 +1,4 @@ -use chainerror::ResultTrait; +use chainerror::Context; use std::error::Error; use std::io; From 82257c881a81fc6fb21319fc307ec3ef3c1381f8 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Jul 2023 16:21:22 +0200 Subject: [PATCH 32/45] docs: use new names Signed-off-by: Harald Hoyer --- README.md | 2 +- booksrc/LICENSE-APACHE | 1 + booksrc/LICENSE-MIT | 1 + booksrc/tutorial10.md | 7 +++---- booksrc/tutorial11.md | 2 +- booksrc/tutorial12.md | 2 +- booksrc/tutorial13.md | 2 +- booksrc/tutorial2.md | 4 ++-- booksrc/tutorial3.md | 4 ++-- booksrc/tutorial5.md | 3 +-- booksrc/tutorial7.md | 8 ++++---- booksrc/tutorial8.md | 12 ++++++------ examples/example.rs | 19 +++++++++---------- examples/tutorial1.rs | 1 - examples/tutorial10.rs | 8 ++++---- examples/tutorial11.rs | 8 ++++---- examples/tutorial12.rs | 8 ++++---- examples/tutorial13.rs | 7 ++++--- examples/tutorial15.rs | 7 ++++--- examples/tutorial2.rs | 3 +-- examples/tutorial3.rs | 3 +-- examples/tutorial4.rs | 4 ++-- examples/tutorial5.rs | 4 ++-- examples/tutorial6.rs | 4 ++-- examples/tutorial7.rs | 4 ++-- examples/tutorial8.rs | 10 +++++----- examples/tutorial9.rs | 12 ++++++------ 27 files changed, 74 insertions(+), 76 deletions(-) create mode 120000 booksrc/LICENSE-APACHE create mode 120000 booksrc/LICENSE-MIT diff --git a/README.md b/README.md index 7df21e4..865cb0d 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ and you have no idea where it comes from. With `chainerror`, you can supply a context and get a nice error backtrace: ```rust -use chainerror::prelude::v1::*; +use chainerror::Context as _; use std::path::PathBuf; type BoxedError = Box; diff --git a/booksrc/LICENSE-APACHE b/booksrc/LICENSE-APACHE new file mode 120000 index 0000000..965b606 --- /dev/null +++ b/booksrc/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/booksrc/LICENSE-MIT b/booksrc/LICENSE-MIT new file mode 120000 index 0000000..76219eb --- /dev/null +++ b/booksrc/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/booksrc/tutorial10.md b/booksrc/tutorial10.md index 07d0100..5547954 100644 --- a/booksrc/tutorial10.md +++ b/booksrc/tutorial10.md @@ -1,6 +1,6 @@ # ErrorKind to the rescue -To cope with different kind of errors, we introduce the kind of an error `Func1ErrorKind` with an enum. +To cope with different kind of errors, we introduce the `kind` of an error `Func1ErrorKind` with an enum. Because we derive `Debug` and implement `Display` our `Func1ErrorKind` enum, this enum can be used as a `std::error::Error`. @@ -8,10 +8,9 @@ a `std::error::Error`. Only returning `Func1ErrorKind` in `func1()` now let us get rid of `Result<(), Box>` and we can use `ChainResult<(), Func1ErrorKind>`. -In `main` we can now directly use the methods of `ChainError` without downcasting the error first. +In `main` we can now directly use the methods of `chainerror::Error` without downcasting the error first. -Also a nice `match` on `ChainError.kind()` is now possible, which returns `&T`, meaning -`&Func1ErrorKind` here. +Also, a nice `match` on `chainerror::Error.kind()` is now possible, which returns `&T`, meaning `&Func1ErrorKind` here. ~~~rust {{#include ../examples/tutorial10.rs}} diff --git a/booksrc/tutorial11.md b/booksrc/tutorial11.md index 197b9d3..7d37814 100644 --- a/booksrc/tutorial11.md +++ b/booksrc/tutorial11.md @@ -21,7 +21,7 @@ which gives us a lot more detail. To create your own Errors, you might find [crates](https://crates.io) which create enum `Display+Debug` via derive macros. -Also noteworthy is [custom_error](https://crates.io/crates/custom_error) to define your custom errors, +Also, noteworthy is [custom_error](https://crates.io/crates/custom_error) to define your custom errors, which can then be used with `chainerror`. ~~~rust diff --git a/booksrc/tutorial12.md b/booksrc/tutorial12.md index a8c80e6..037803b 100644 --- a/booksrc/tutorial12.md +++ b/booksrc/tutorial12.md @@ -1,6 +1,6 @@ # Deref for the ErrorKind -Because ChainError implements Deref to &T, we can also match on `*e` instead of `e.kind()` +Because chainerror::Error implements Deref to &T, we can also match on `*e` instead of `e.kind()` or call a function with `&e` ~~~rust {{#include ../examples/tutorial12.rs}} diff --git a/booksrc/tutorial13.md b/booksrc/tutorial13.md index 6d438f4..0ed9db8 100644 --- a/booksrc/tutorial13.md +++ b/booksrc/tutorial13.md @@ -1,6 +1,6 @@ # Writing a library -I would advise to only expose an `mycrate::ErrorKind` and type alias `mycrate::Error` to `ChainError` +I would advise to only expose an `mycrate::ErrorKind` and type alias `mycrate::Error` to `chainerror::Error` so you can tell your library users to use the `.kind()` method as `std::io::Error` does. If you later decide to make your own `Error` implementation, your library users don't diff --git a/booksrc/tutorial2.md b/booksrc/tutorial2.md index 27e1fbb..a330c1a 100644 --- a/booksrc/tutorial2.md +++ b/booksrc/tutorial2.md @@ -25,7 +25,7 @@ along with the `Location` of the `context()` call and returns `Err(newerror)`. `?` then returns the inner error applying `.into()`, so that we again have a `Err(Box)` as a result. -The `Debug` implementation of `ChainError` (which is returned by `context()`) +The `Debug` implementation of `chainerror::Error` (which is returned by `context()`) prints the `Debug` of `T` prefixed with the stored filename and line number. -`ChainError` in our case is `ChainError<&str>`. +`chainerror::Error` in our case is `chainerror::Error<&str>`. diff --git a/booksrc/tutorial3.md b/booksrc/tutorial3.md index e44cc7d..f6c26ac 100644 --- a/booksrc/tutorial3.md +++ b/booksrc/tutorial3.md @@ -14,13 +14,13 @@ If you compare the output to the previous example, you will see, that: ~~~ -Error: src/main.rs:19: "func1 error" +Error: examples/tutorial2.rs:20:16: func1 error ~~~ changed to just: ~~~ -src/main.rs:16: "func1 error" +examples/tutorial3.rs:17:13: func1 error ~~~ This is, because we caught the error of `func1()` in `main()` and print it out ourselves. diff --git a/booksrc/tutorial5.md b/booksrc/tutorial5.md index 05db784..57932fa 100644 --- a/booksrc/tutorial5.md +++ b/booksrc/tutorial5.md @@ -14,5 +14,4 @@ Sometimes you want to inspect the `source()` of an `Error`. Note, that because we changed the output of the error in `main()` from `Debug` to `Display`, we don't see the error backtrace with filename and line number. -To enable the `Display` backtrace, you have to enable the feature `display-cause` for `chainerror`. - \ No newline at end of file +To use the `Display` backtrace, you have to use the alternative display format output `{:#}`. diff --git a/booksrc/tutorial7.md b/booksrc/tutorial7.md index ccb6ff6..90665f7 100644 --- a/booksrc/tutorial7.md +++ b/booksrc/tutorial7.md @@ -4,15 +4,15 @@ ~~~rust,ignore fn is_chain(&self) -> bool -fn downcast_chain_ref(&self) -> Option<&ChainError> -fn downcast_chain_mut(&mut self) -> Option<&mut ChainError> +fn downcast_chain_ref(&self) -> Option<&chainerror::Error> +fn downcast_chain_mut(&mut self) -> Option<&mut chainerror::Error> fn root_cause(&self) -> Option<&(dyn Error + 'static)> fn find_cause(&self) -> Option<&U> -fn find_chain_cause(&self) -> Option<&ChainError> +fn find_chain_cause(&self) -> Option<&chainerror::Error> fn kind<'a>(&'a self) -> &'a T ~~~ -Using `downcast_chain_ref::()` gives a `ChainError`, which can be used +Using `downcast_chain_ref::()` gives a `chainerror::Error`, which can be used to call `.find_cause::()`. ~~~rust,ignore diff --git a/booksrc/tutorial8.md b/booksrc/tutorial8.md index da0c405..17070fe 100644 --- a/booksrc/tutorial8.md +++ b/booksrc/tutorial8.md @@ -1,14 +1,14 @@ # Finding an Error cause -To distinguish the errors occuring in various places, we can define named string errors with the +To distinguish the errors occurring in various places, we can define named string errors with the "new type" pattern. ~~~rust,ignore -derive_str_context!(Func2Error); -derive_str_context!(Func1Error); +chainerror::str_context!(Func2Error); +chainerror::str_context!(Func1Error); ~~~ -Instead of `ChainError` we now have `struct Func1Error(String)` and `ChainError`. +Instead of `chainerror::Error` we now have `struct Func1Error(String)` and `chainerror::Error`. In the `main` function you can see, how we can match the different errors. @@ -18,9 +18,9 @@ Also see: ~~~ as a shortcut to ~~~rust,ignore - if let Some(f2err) = f1err.find_cause::>() { + if let Some(f2err) = f1err.find_cause::>() { ~~~ -hiding the `ChainError` implementation detail. +hiding the `chainerror::Error` implementation detail. ~~~rust {{#include ../examples/tutorial8.rs}} diff --git a/examples/example.rs b/examples/example.rs index 859a12c..02fcba2 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -1,8 +1,7 @@ +use chainerror::prelude::v2::*; use std::error::Error; +use std::fmt; use std::io; -use std::result::Result; - -use chainerror::prelude::v1::*; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; @@ -15,9 +14,9 @@ fn func3() -> Result<(), Box> { Ok(()) } -derive_str_context!(Func2Error); +chainerror::str_context!(Func2Error); -fn func2() -> ChainResult<(), Func2Error> { +fn func2() -> chainerror::Result<(), Func2Error> { func3().context(Func2Error("func2 error: calling func3".to_string()))?; Ok(()) } @@ -27,8 +26,8 @@ enum Func1Error { IO(String), } -impl ::std::fmt::Display for Func1Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { +impl fmt::Display for Func1Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Func1Error::Func2 => write!(f, "func1 error calling func2"), Func1Error::IO(filename) => write!(f, "Error reading '{}'", filename), @@ -36,13 +35,13 @@ impl ::std::fmt::Display for Func1Error { } } -impl ::std::fmt::Debug for Func1Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { +impl fmt::Debug for Func1Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self) } } -fn func1() -> ChainResult<(), Func1Error> { +fn func1() -> chainerror::Result<(), Func1Error> { func2().context(Func1Error::Func2)?; let filename = String::from("bar.txt"); do_some_io().context(Func1Error::IO(filename))?; diff --git a/examples/tutorial1.rs b/examples/tutorial1.rs index 939fb73..08ca3e8 100644 --- a/examples/tutorial1.rs +++ b/examples/tutorial1.rs @@ -3,7 +3,6 @@ use std::error::Error; use std::io; -use std::result::Result; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; diff --git a/examples/tutorial10.rs b/examples/tutorial10.rs index 5118c63..3e0af4a 100644 --- a/examples/tutorial10.rs +++ b/examples/tutorial10.rs @@ -1,14 +1,14 @@ -use chainerror::prelude::v1::*; +use chainerror::Context as _; + use std::error::Error; use std::io; -use std::result::Result; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -derive_str_context!(Func2Error); +chainerror::str_context!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; @@ -32,7 +32,7 @@ impl ::std::fmt::Display for Func1ErrorKind { } impl ::std::error::Error for Func1ErrorKind {} -fn func1() -> ChainResult<(), Func1ErrorKind> { +fn func1() -> chainerror::Result<(), Func1ErrorKind> { func2().context(Func1ErrorKind::Func2)?; let filename = String::from("bar.txt"); do_some_io().context(Func1ErrorKind::IO(filename))?; diff --git a/examples/tutorial11.rs b/examples/tutorial11.rs index a943b97..abbca94 100644 --- a/examples/tutorial11.rs +++ b/examples/tutorial11.rs @@ -1,14 +1,14 @@ -use chainerror::prelude::v1::*; +use chainerror::Context as _; + use std::error::Error; use std::io; -use std::result::Result; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -derive_str_context!(Func2Error); +chainerror::str_context!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; @@ -38,7 +38,7 @@ impl ::std::fmt::Debug for Func1ErrorKind { impl ::std::error::Error for Func1ErrorKind {} -fn func1() -> ChainResult<(), Func1ErrorKind> { +fn func1() -> chainerror::Result<(), Func1ErrorKind> { func2().context(Func1ErrorKind::Func2)?; let filename = String::from("bar.txt"); do_some_io().context(Func1ErrorKind::IO(filename))?; diff --git a/examples/tutorial12.rs b/examples/tutorial12.rs index e2bf644..caa8fca 100644 --- a/examples/tutorial12.rs +++ b/examples/tutorial12.rs @@ -1,14 +1,14 @@ -use chainerror::prelude::v1::*; +use chainerror::Context as _; + use std::error::Error; use std::io; -use std::result::Result; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -derive_str_context!(Func2Error); +chainerror::str_context!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; @@ -38,7 +38,7 @@ impl ::std::fmt::Debug for Func1ErrorKind { impl ::std::error::Error for Func1ErrorKind {} -fn func1() -> ChainResult<(), Func1ErrorKind> { +fn func1() -> chainerror::Result<(), Func1ErrorKind> { func2().context(Func1ErrorKind::Func2)?; let filename = String::from("bar.txt"); do_some_io().context(Func1ErrorKind::IO(filename))?; diff --git a/examples/tutorial13.rs b/examples/tutorial13.rs index 35dc832..0d50d68 100644 --- a/examples/tutorial13.rs +++ b/examples/tutorial13.rs @@ -2,7 +2,8 @@ #![allow(clippy::redundant_pattern_matching)] pub mod mycrate { - use chainerror::prelude::v1::*; + use chainerror::Context as _; + use std::io; fn do_some_io() -> std::result::Result<(), Box> { @@ -10,7 +11,7 @@ pub mod mycrate { Ok(()) } - derive_str_context!(Func2Error); + chainerror::str_context!(Func2Error); fn func2() -> std::result::Result<(), Box> { let filename = "foo.txt"; @@ -24,7 +25,7 @@ pub mod mycrate { IO(String), } - derive_err_kind!(Error, ErrorKind); + chainerror::err_kind!(Error, ErrorKind); pub type Result = std::result::Result; diff --git a/examples/tutorial15.rs b/examples/tutorial15.rs index 39594b4..42355b3 100644 --- a/examples/tutorial15.rs +++ b/examples/tutorial15.rs @@ -2,7 +2,8 @@ #![allow(clippy::redundant_pattern_matching)] pub mod mycrate { - use chainerror::prelude::v1::*; + use chainerror::{Context as _, ErrorDown as _}; + use std::io; fn do_some_io(_f: &str) -> std::result::Result<(), io::Error> { @@ -10,7 +11,7 @@ pub mod mycrate { Ok(()) } - derive_str_context!(Func2Error); + chainerror::str_context!(Func2Error); fn func2() -> std::result::Result<(), Box> { let filename = "foo.txt"; @@ -26,7 +27,7 @@ pub mod mycrate { Unknown, } - derive_err_kind!(Error, ErrorKind); + chainerror::err_kind!(Error, ErrorKind); pub type Result = std::result::Result; impl std::fmt::Display for ErrorKind { diff --git a/examples/tutorial2.rs b/examples/tutorial2.rs index 8c68dc0..96742ad 100644 --- a/examples/tutorial2.rs +++ b/examples/tutorial2.rs @@ -1,8 +1,7 @@ -use chainerror::prelude::v1::*; +use chainerror::Context as _; use std::error::Error; use std::io; -use std::result::Result; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; diff --git a/examples/tutorial3.rs b/examples/tutorial3.rs index 8475d6f..62a44eb 100644 --- a/examples/tutorial3.rs +++ b/examples/tutorial3.rs @@ -1,8 +1,7 @@ -use chainerror::prelude::v1::*; +use chainerror::Context as _; use std::error::Error; use std::io; -use std::result::Result; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; diff --git a/examples/tutorial4.rs b/examples/tutorial4.rs index d1b247b..3dea51c 100644 --- a/examples/tutorial4.rs +++ b/examples/tutorial4.rs @@ -1,7 +1,7 @@ -use chainerror::prelude::v1::*; +use chainerror::Context as _; + use std::error::Error; use std::io; -use std::result::Result; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; diff --git a/examples/tutorial5.rs b/examples/tutorial5.rs index 333208c..d9fe708 100644 --- a/examples/tutorial5.rs +++ b/examples/tutorial5.rs @@ -1,7 +1,7 @@ -use chainerror::prelude::v1::*; +use chainerror::Context as _; + use std::error::Error; use std::io; -use std::result::Result; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; diff --git a/examples/tutorial6.rs b/examples/tutorial6.rs index de1a0a8..5d81310 100644 --- a/examples/tutorial6.rs +++ b/examples/tutorial6.rs @@ -1,10 +1,10 @@ #![allow(clippy::single_match)] #![allow(clippy::redundant_pattern_matching)] -use chainerror::prelude::v1::*; +use chainerror::Context as _; + use std::error::Error; use std::io; -use std::result::Result; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; diff --git a/examples/tutorial7.rs b/examples/tutorial7.rs index 9a1ca67..214484c 100644 --- a/examples/tutorial7.rs +++ b/examples/tutorial7.rs @@ -1,10 +1,10 @@ #![allow(clippy::single_match)] #![allow(clippy::redundant_pattern_matching)] -use chainerror::prelude::v1::*; +use chainerror::{Context as _, ErrorDown as _}; + use std::error::Error; use std::io; -use std::result::Result; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; diff --git a/examples/tutorial8.rs b/examples/tutorial8.rs index cf5e654..7661bd8 100644 --- a/examples/tutorial8.rs +++ b/examples/tutorial8.rs @@ -1,14 +1,14 @@ -use chainerror::prelude::v1::*; +use chainerror::{Context as _, ErrorDown as _}; + use std::error::Error; use std::io; -use std::result::Result; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -derive_str_context!(Func2Error); +chainerror::str_context!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; @@ -16,7 +16,7 @@ fn func2() -> Result<(), Box> { Ok(()) } -derive_str_context!(Func1Error); +chainerror::str_context!(Func1Error); fn func1() -> Result<(), Box> { func2().context(Func1Error("func1 error".to_string()))?; @@ -28,7 +28,7 @@ fn main() -> Result<(), Box> { if let Some(f1err) = e.downcast_chain_ref::() { eprintln!("Func1Error: {}", f1err); - if let Some(f2err) = f1err.find_cause::>() { + if let Some(f2err) = f1err.find_cause::>() { eprintln!("Func2Error: {}", f2err); } diff --git a/examples/tutorial9.rs b/examples/tutorial9.rs index 754c621..1cf24d3 100644 --- a/examples/tutorial9.rs +++ b/examples/tutorial9.rs @@ -1,14 +1,14 @@ -use chainerror::prelude::v1::*; +use chainerror::{Context as _, ErrorDown}; + use std::error::Error; use std::io; -use std::result::Result; fn do_some_io() -> Result<(), Box> { Err(io::Error::from(io::ErrorKind::NotFound))?; Ok(()) } -derive_str_context!(Func2Error); +chainerror::str_context!(Func2Error); fn func2() -> Result<(), Box> { let filename = "foo.txt"; @@ -16,8 +16,8 @@ fn func2() -> Result<(), Box> { Ok(()) } -derive_str_context!(Func1ErrorFunc2); -derive_str_context!(Func1ErrorIO); +chainerror::str_context!(Func1ErrorFunc2); +chainerror::str_context!(Func1ErrorIO); fn func1() -> Result<(), Box> { func2().context(Func1ErrorFunc2("func1 error calling func2".to_string()))?; @@ -28,7 +28,7 @@ fn func1() -> Result<(), Box> { fn main() -> Result<(), Box> { if let Err(e) = func1() { - if let Some(s) = e.downcast_ref::>() { + if let Some(s) = e.downcast_ref::>() { eprintln!("Func1ErrorIO:\n{:?}", s); } From cb9465f0dfa85690002c9dfc1276a2360d62a9e0 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Jul 2023 16:22:15 +0200 Subject: [PATCH 33/45] chore: remove obsolete README.tpl Signed-off-by: Harald Hoyer --- README.tpl | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 README.tpl diff --git a/README.tpl b/README.tpl deleted file mode 100644 index 01e969a..0000000 --- a/README.tpl +++ /dev/null @@ -1,24 +0,0 @@ -[![Crate](https://img.shields.io/crates/v/chainerror.svg)](https://crates.io/crates/chainerror) -[![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/chainerror/) -[![Coverage Status](https://coveralls.io/repos/github/haraldh/chainerror/badge.svg?branch=master)](https://coveralls.io/github/haraldh/chainerror?branch=master) -{{badges}} - -# {{crate}} - -{{readme}} - -## License - -Licensed under either of - -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally -submitted for inclusion in the work by you, as defined in the Apache-2.0 -license, shall be dual licensed as above, without any additional terms or -conditions. From aaca6945b0fcb0fe3239a9eac18093183bcacd79 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Jul 2023 16:32:46 +0200 Subject: [PATCH 34/45] feat: add `new(Into)` method for `str_context!` types Signed-off-by: Harald Hoyer --- examples/example.rs | 2 +- examples/tutorial8.rs | 2 +- examples/tutorial9.rs | 2 +- src/lib.rs | 9 +++++++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index 02fcba2..c1d5702 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -17,7 +17,7 @@ fn func3() -> Result<(), Box> { chainerror::str_context!(Func2Error); fn func2() -> chainerror::Result<(), Func2Error> { - func3().context(Func2Error("func2 error: calling func3".to_string()))?; + func3().context(Func2Error::new("func2 error: calling func3"))?; Ok(()) } diff --git a/examples/tutorial8.rs b/examples/tutorial8.rs index 7661bd8..e32a449 100644 --- a/examples/tutorial8.rs +++ b/examples/tutorial8.rs @@ -19,7 +19,7 @@ fn func2() -> Result<(), Box> { chainerror::str_context!(Func1Error); fn func1() -> Result<(), Box> { - func2().context(Func1Error("func1 error".to_string()))?; + func2().context(Func1Error::new("func1 error"))?; Ok(()) } diff --git a/examples/tutorial9.rs b/examples/tutorial9.rs index 1cf24d3..bbbe810 100644 --- a/examples/tutorial9.rs +++ b/examples/tutorial9.rs @@ -20,7 +20,7 @@ chainerror::str_context!(Func1ErrorFunc2); chainerror::str_context!(Func1ErrorIO); fn func1() -> Result<(), Box> { - func2().context(Func1ErrorFunc2("func1 error calling func2".to_string()))?; + func2().context(Func1ErrorFunc2::new("func1 error calling func2"))?; let filename = "bar.txt"; do_some_io().context(Func1ErrorIO(format!("Error reading '{}'", filename)))?; Ok(()) diff --git a/src/lib.rs b/src/lib.rs index a4c82c4..95dbcfe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,7 +84,7 @@ impl Error { /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box> { - /// func2().context(Func1Error("func1 error".into()))?; + /// func2().context(Func1Error::new("func1 error"))?; /// Ok(()) /// } /// @@ -576,7 +576,7 @@ where /// chainerror::str_context!(Func1Error); /// /// fn func1() -> Result<(), Box> { -/// func2().context(Func1Error("func1 error".into()))?; +/// func2().context(Func1Error::new("func1 error"))?; /// Ok(()) /// } /// # if let Err(e) = func1() { @@ -595,6 +595,11 @@ macro_rules! str_context { ($e:ident) => { #[derive(Clone)] pub struct $e(pub String); + impl $e { + pub fn new>(s: S) -> Self { + $e(s.into()) + } + } impl ::std::fmt::Display for $e { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "{}", self.0) From 101d2074e112d72020752ff1e21c08047c53efdb Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Jul 2023 17:04:39 +0200 Subject: [PATCH 35/45] 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 --- src/lib.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 95dbcfe..f15fd7d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,26 +8,6 @@ use std::error::Error as StdError; use std::fmt::{Debug, Display, Formatter}; use std::panic::Location; -pub mod prelude { - //! convenience prelude - pub mod v1 { - //! convenience prelude - pub use super::super::Context as _; - pub use super::super::Context as ResultTrait; - pub use super::super::Error as ChainError; - pub use super::super::ErrorDown as _; - pub use super::super::ErrorDown as ChainErrorDown; - pub use super::super::Result as ChainResult; - pub use crate::err_kind as derive_err_kind; - pub use crate::str_context as derive_str_context; - } - pub mod v2 { - //! convenience prelude - pub use super::super::Context as _; - pub use super::super::ErrorDown as _; - } -} - /// chains an inner error kind `T` with a causing error pub struct Error { occurrence: Option, From 46b7f58e72671756d3462cfecc3c92d195c6ed37 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Jul 2023 17:06:10 +0200 Subject: [PATCH 36/45] feat: add `annotate()` method to Context to just annotate the passed error with location data Signed-off-by: Harald Hoyer --- examples/example.rs | 9 +++++++-- src/lib.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index c1d5702..82c073f 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -1,4 +1,4 @@ -use chainerror::prelude::v2::*; +use chainerror::Context as _; use std::error::Error; use std::fmt; use std::io; @@ -8,12 +8,17 @@ fn do_some_io() -> Result<(), Box> { Ok(()) } -fn func3() -> Result<(), Box> { +fn func4() -> Result<(), Box> { let filename = "foo.txt"; do_some_io().context(format!("Error reading '{}'", filename))?; Ok(()) } +fn func3() -> Result<(), Box> { + func4().annotate()?; + Ok(()) +} + chainerror::str_context!(Func2Error); fn func2() -> chainerror::Result<(), Func2Error> { diff --git a/src/lib.rs b/src/lib.rs index f15fd7d..372cef2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -216,6 +216,9 @@ pub trait Context>> { /// Decorate the error with a `kind` of type `T` and the source `Location` fn context(self, kind: T) -> std::result::Result>; + /// Decorate the error just with the source `Location` + fn annotate(self) -> std::result::Result>; + /// Decorate the `error` with a `kind` of type `T` produced with a `FnOnce(&error)` and the source `Location` fn map_context T>( self, @@ -223,6 +226,21 @@ pub trait Context>> { ) -> std::result::Result>; } +/// Convenience type to just decorate the error with the source `Location` +pub struct AnnotatedError(()); + +impl Display for AnnotatedError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "(passed error)") + } +} + +impl Debug for AnnotatedError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "(passed error)") + } +} + impl>> Context for std::result::Result { @@ -239,6 +257,19 @@ impl>> Context } } + #[track_caller] + #[inline] + fn annotate(self) -> std::result::Result> { + match self { + Ok(t) => Ok(t), + Err(error_cause) => Err(Error::new( + AnnotatedError(()), + Some(error_cause.into()), + Some(Location::caller().to_string()), + )), + } + } + #[track_caller] #[inline] fn map_context T>( From 9e03541ac102f9d3b3eab529dcc66c74be8e49a8 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Jul 2023 17:06:57 +0200 Subject: [PATCH 37/45] doc: extend README.md with display formats Signed-off-by: Harald Hoyer --- README.md | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/README.md b/README.md index 865cb0d..6f57ae8 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,81 @@ Along with the `Error` struct, `chainerror` comes with some useful helper mac Debug information is worth it! +## Multiple Output Formats + +`chainerror` supports multiple output formats, which can be selected with the different format specifiers: + +* `{}`: Display +```text +func1 error calling func2 +``` + +* `{:#}`: Alternative Display +```text +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 +```text +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 +```text +Error { + occurrence: Some( + "examples/example.rs:50:13", + ), + kind: func1 error calling func2, + source: Some( + Error { + occurrence: Some( + "examples/example.rs:25:13", + ), + kind: Func2Error(func2 error: calling func3), + source: Some( + Error { + occurrence: Some( + "examples/example.rs:18:13", + ), + kind: (passed error), + source: Some( + Error { + occurrence: Some( + "examples/example.rs:13:18", + ), + kind: "Error reading 'foo.txt'", + source: Some( + Kind( + NotFound, + ), + ), + }, + ), + }, + ), + }, + ), +} +``` + ## Tutorial Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html) From c630f84690ad8f1864c95f50d962f02427cd5c06 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Jul 2023 17:09:18 +0200 Subject: [PATCH 38/45] doc: README.md changed `text` to `console` Signed-off-by: Harald Hoyer --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6f57ae8..3a46567 100644 --- a/README.md +++ b/README.md @@ -95,12 +95,12 @@ Debug information is worth it! `chainerror` supports multiple output formats, which can be selected with the different format specifiers: * `{}`: Display -```text +```console func1 error calling func2 ``` * `{:#}`: Alternative Display -```text +```console func1 error calling func2 Caused by: func2 error: calling func3 @@ -113,7 +113,7 @@ Caused by: ``` * `{:?}`: Debug -```text +```console examples/example.rs:50:13: func1 error calling func2 Caused by: examples/example.rs:25:13: Func2Error(func2 error: calling func3) @@ -127,7 +127,7 @@ Kind(NotFound) ``` * `{:#?}`: Alternative Debug -```text +```console Error { occurrence: Some( "examples/example.rs:50:13", From 24382fbaed2d2acd97076f1f1a46c854da6789e1 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Jul 2023 17:11:10 +0200 Subject: [PATCH 39/45] chore: Release chainerror version 1.0.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c4f3b17..59b8808 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chainerror" -version = "0.8.2" +version = "1.0.0" authors = ["Harald Hoyer "] edition = "2018" license = "MIT OR Apache-2.0" From 74f1dd43148be7d29b4e4d7d29cc40298d5ba9c5 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Mon, 31 Mar 2025 13:07:27 +0200 Subject: [PATCH 40/45] 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 --- .github/workflows/coverage.yml | 35 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 21025a8..6492f1a 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -13,31 +13,24 @@ on: types: - created + jobs: - test: - name: coverage + coverage: runs-on: ubuntu-latest + env: + CARGO_TERM_COLOR: always steps: - - uses: actions/checkout@v1 - - uses: dtolnay/rust-toolchain@master - with: - target: x86_64-unknown-linux-gnu - toolchain: nightly - components: llvm-tools-preview - + - uses: actions/checkout@v4 + - name: Install Rust + run: | + rustup update nightly - name: Install cargo-llvm-cov - run: > - curl -LsSf 'https://github.com/taiki-e/cargo-llvm-cov/releases/download/v0.5.23/cargo-llvm-cov-x86_64-unknown-linux-musl.tar.gz' - | tar xzf - - && mv cargo-llvm-cov $HOME/.cargo/bin - - - name: Run cargo-llvm-cov - run: cargo llvm-cov --doctests --all --all-features --lcov --output-path lcov.info - + 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: - directory: ./ - fail_ci_if_error: false - files: ./lcov.info - verbose: true + token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos + files: codecov.json + fail_ci_if_error: true From 5cb96eeee39b4f9efed24ddda81e6dbdf3f6e03a Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Mon, 31 Mar 2025 13:21:34 +0200 Subject: [PATCH 41/45] 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. --- .github/workflows/coverage.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 6492f1a..7dff904 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -21,9 +21,11 @@ jobs: CARGO_TERM_COLOR: always steps: - uses: actions/checkout@v4 - - name: Install Rust - run: | - rustup update nightly + - 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 From 9aa0183d654c8394738c2f8dc428df6c0617f38f Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Mon, 31 Mar 2025 13:54:45 +0200 Subject: [PATCH 42/45] 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. --- src/lib.rs | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 372cef2..51eaf7e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -607,6 +607,7 @@ macro_rules! str_context { #[derive(Clone)] pub struct $e(pub String); impl $e { + #[allow(dead_code)] pub fn new>(s: S) -> Self { $e(s.into()) } @@ -740,3 +741,137 @@ macro_rules! err_kind { } }; } + +#[cfg(test)] +mod tests { + use super::Context as _; + use super::*; + use std::io; + + #[test] + fn test_error_chain_with_multiple_causes() { + // Create a chain of errors + let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); + + str_context!(Level3Error); + str_context!(Level2Error); + str_context!(Level1Error); + + let err = Result::<(), _>::Err(io_error.into()) + .context(Level3Error("level 3".into())) + .context(Level2Error("level 2".into())) + .context(Level1Error("level 1".into())) + .unwrap_err(); + + // Test the error chain + assert!(err.is_chain::()); + assert!(err.find_chain_cause::().is_some()); + assert!(err.find_chain_cause::().is_some()); + assert!(err.find_chain_cause::().is_some()); + } + + #[test] + fn test_error_root_cause() { + let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); + + str_context!(WrapperError); + let err = Result::<(), _>::Err(io_error.into()) + .context(WrapperError("wrapper".into())) + .unwrap_err(); + + let root = err.root_cause().unwrap(); + assert!(root.is_chain::()); + } + + #[test] + fn test_error_display_and_debug() { + str_context!(CustomError); + let err = Error::new( + CustomError("test error".into()), + None, + Some("src/lib.rs:100".into()), + ); + + // Test Display formatting + assert_eq!(format!("{}", err), "test error"); + + // Test alternate Display formatting + assert_eq!(format!("{:#}", err), "test error"); + + // Test Debug formatting + let debug_output = format!("{:?}", err); + assert!(debug_output.contains("test error")); + assert!(debug_output.contains("src/lib.rs:100")); + } + + #[test] + fn test_error_annotation() { + let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); + let err = Result::<(), _>::Err(io_error.into()) + .annotate() + .unwrap_err(); + + assert!(err.source().is_some()); + err.source() + .unwrap() + .downcast_inner_ref::() + .unwrap(); + } + + #[test] + fn test_map_context() { + let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); + + str_context!(MappedError); + let err = Result::<(), _>::Err(io_error.into()) + .map_context(|e| MappedError(format!("Mapped: {}", e))) + .unwrap_err(); + + assert!(err.is_chain::()); + assert!(err.find_chain_cause::().is_some()); + } + + #[test] + fn test_error_downcasting() { + str_context!(OriginalError); + let original = Error::new(OriginalError("test".into()), None, None); + + let error: Box = Box::new(original); + + // Test downcast_chain_ref + assert!(error.is_chain::()); + assert!(error.downcast_chain_ref::().is_some()); + + // Test downcast_inner_ref + let inner = error.downcast_inner_ref::(); + assert!(inner.is_some()); + } + + #[derive(Debug, Clone)] + enum TestErrorKind { + Basic(String), + Complex { message: String }, + } + + impl Display for TestErrorKind { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + TestErrorKind::Basic(msg) => write!(f, "Basic error: {}", msg), + TestErrorKind::Complex { message } => write!(f, "Complex error: {}", message), + } + } + } + + #[test] + fn test_err_kind_macro() { + err_kind!(TestError, TestErrorKind); + + let err = TestError::from(TestErrorKind::Basic("test".into())); + assert!(matches!(err.kind(), TestErrorKind::Basic(_))); + + let complex_err = TestError::from(TestErrorKind::Complex { + message: "test".into(), + }); + assert!(matches!(complex_err.kind(), TestErrorKind::Complex { .. })); + } +} From 5390007cbeb85887e56d9a9c517ea1f0f0a51e85 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Mon, 31 Mar 2025 14:04:01 +0200 Subject: [PATCH 43/45] 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. --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 51eaf7e..450eab3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -689,6 +689,12 @@ macro_rules! str_context { /// do_some_io(filename).map_context(|e| ErrorKind::from(e))?; /// Ok(()) /// } +/// +/// # fn main() { +/// # if let Err(e) = func1() { +/// # eprintln!("Error:\n{:?}", e); +/// # } +/// # } /// ``` #[macro_export] macro_rules! err_kind { From 75b7fdf3630418d29c4f70c6b8e6b5dc832346fe Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Mon, 31 Mar 2025 14:14:32 +0200 Subject: [PATCH 44/45] 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. --- src/lib.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 450eab3..982ef83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -874,10 +874,43 @@ mod tests { let err = TestError::from(TestErrorKind::Basic("test".into())); assert!(matches!(err.kind(), TestErrorKind::Basic(_))); + // The annotated error should display "(passed error)" even in a chain + assert_eq!(format!("{}", err), "Basic error: test"); + assert_eq!(format!("{:?}", err), "Basic(\"test\")"); let complex_err = TestError::from(TestErrorKind::Complex { message: "test".into(), }); assert!(matches!(complex_err.kind(), TestErrorKind::Complex { .. })); + // The annotated error should display "(passed error)" even in a chain + assert_eq!(format!("{}", complex_err), "Complex error: test"); + assert_eq!( + format!("{:?}", complex_err), + "Complex { message: \"test\" }" + ); + } + #[test] + fn test_annotated_error_display_and_debug() { + let annotated = AnnotatedError(()); + + // Test Display formatting + assert_eq!(format!("{}", annotated), "(passed error)"); + + // Test Debug formatting + assert_eq!(format!("{:?}", annotated), "(passed error)"); + + // Test with error chain + let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); + let err = Result::<(), _>::Err(io_error.into()) + .annotate() + .unwrap_err(); + + // The annotated error should display "(passed error)" even in a chain + assert_eq!(format!("{}", err), "(passed error)"); + assert!(format!("{:?}", err).contains("(passed error)")); + + // Verify the error chain is preserved + assert!(err.source().is_some()); + assert!(err.source().unwrap().is_chain::()); } } From 4c42d3759894492b90af063ad82ceacf0925ee2c Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Mon, 31 Mar 2025 14:45:22 +0200 Subject: [PATCH 45/45] 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 --- src/lib.rs | 149 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 129 insertions(+), 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 982ef83..623523d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -338,11 +338,8 @@ impl ErrorDown for Error { #[inline] fn downcast_chain_ref(&self) -> Option<&Error> { if self.is_chain::() { - #[allow(clippy::cast_ptr_alignment)] - unsafe { - #[allow(trivial_casts)] - Some(*(self as *const dyn StdError as *const &Error)) - } + // Use transmute when we've verified the types match + unsafe { Some(std::mem::transmute::<&Error, &Error>(self)) } } else { None } @@ -351,11 +348,8 @@ impl ErrorDown for Error { #[inline] fn downcast_chain_mut(&mut self) -> Option<&mut Error> { if self.is_chain::() { - #[allow(clippy::cast_ptr_alignment)] - unsafe { - #[allow(trivial_casts)] - Some(&mut *(self as *mut dyn StdError as *mut &mut Error)) - } + // Use transmute when we've verified the types match + unsafe { Some(std::mem::transmute::<&mut Error, &mut Error>(self)) } } else { None } @@ -363,11 +357,8 @@ impl ErrorDown for Error { #[inline] fn downcast_inner_ref(&self) -> Option<&T> { if self.is_chain::() { - #[allow(clippy::cast_ptr_alignment)] - unsafe { - #[allow(trivial_casts)] - Some(&(*(self as *const dyn StdError as *const &Error)).kind) - } + // Use transmute when we've verified the types match + unsafe { Some(std::mem::transmute::<&U, &T>(&self.kind)) } } else { None } @@ -376,11 +367,8 @@ impl ErrorDown for Error { #[inline] fn downcast_inner_mut(&mut self) -> Option<&mut T> { if self.is_chain::() { - #[allow(clippy::cast_ptr_alignment)] - unsafe { - #[allow(trivial_casts)] - Some(&mut (*(self as *mut dyn StdError as *mut &mut Error)).kind) - } + // Use transmute when we've verified the types match + unsafe { Some(std::mem::transmute::<&mut U, &mut T>(&mut self.kind)) } } else { None } @@ -913,4 +901,125 @@ mod tests { assert!(err.source().is_some()); assert!(err.source().unwrap().is_chain::()); } + + // Helper error types for testing + #[derive(Debug)] + struct TestError(String); + + impl std::fmt::Display for TestError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } + } + + impl std::error::Error for TestError {} + + #[test] + fn test_downcast_chain_operations() { + // Create a test error chain + let original_error = Error::new( + TestError("test message".to_string()), + None, + Some("test location".to_string()), + ); + + // Test is_chain + assert!(original_error.is_chain::()); + assert!(!original_error.is_chain::()); + + // Test downcast_chain_ref + let downcast_ref = original_error.downcast_chain_ref::(); + assert!(downcast_ref.is_some()); + let downcast_kind = downcast_ref.unwrap().kind(); + assert_eq!(format!("{}", downcast_kind), "test message"); + assert_eq!( + format!("{:?}", downcast_kind), + "TestError(\"test message\")" + ); + + // Test invalid downcast_chain_ref + let invalid_downcast = original_error.downcast_chain_ref::(); + assert!(invalid_downcast.is_none()); + + // Test downcast_chain_mut + let mut mutable_error = original_error; + let downcast_mut = mutable_error.downcast_chain_mut::(); + assert!(downcast_mut.is_some()); + assert_eq!(downcast_mut.unwrap().kind().0, "test message"); + + // Test invalid downcast_chain_mut + let invalid_downcast_mut = mutable_error.downcast_chain_mut::(); + assert!(invalid_downcast_mut.is_none()); + } + + #[test] + fn test_downcast_inner_operations() { + // Create a test error + let mut error = Error::new( + TestError("inner test".to_string()), + None, + Some("test location".to_string()), + ); + + // Test downcast_inner_ref + let inner_ref = error.downcast_inner_ref::(); + assert!(inner_ref.is_some()); + assert_eq!(inner_ref.unwrap().0, "inner test"); + // Test invalid downcast_inner_ref + let invalid_inner = error.downcast_inner_ref::(); + assert!(invalid_inner.is_none()); + + // Test downcast_inner_mut + let inner_mut = error.downcast_inner_mut::(); + assert!(inner_mut.is_some()); + assert_eq!(inner_mut.unwrap().0, "inner test"); + + // Test invalid downcast_inner_mut + let invalid_inner_mut = error.downcast_inner_mut::(); + assert!(invalid_inner_mut.is_none()); + } + + #[test] + fn test_error_down_for_dyn_error() { + // Create a boxed error + let error: Box = Box::new(Error::new( + TestError("dyn test".to_string()), + None, + Some("test location".to_string()), + )); + + // Test is_chain through trait object + assert!(error.is_chain::()); + assert!(!error.is_chain::()); + + // Test downcast_chain_ref through trait object + let chain_ref = error.downcast_chain_ref::(); + assert!(chain_ref.is_some()); + assert_eq!(chain_ref.unwrap().kind().0, "dyn test"); + + // Test downcast_inner_ref through trait object + let inner_ref = error.downcast_inner_ref::(); + assert!(inner_ref.is_some()); + assert_eq!(inner_ref.unwrap().0, "dyn test"); + } + + #[test] + fn test_error_down_with_sync_send() { + // Create a boxed error with Send + Sync + let error: Box = Box::new(Error::new( + TestError("sync test".to_string()), + None, + Some("test location".to_string()), + )); + + // Test operations on Send + Sync error + assert!(error.is_chain::()); + assert!(error.downcast_chain_ref::().is_some()); + assert!(error.downcast_inner_ref::().is_some()); + + // Test invalid downcasts + assert!(!error.is_chain::()); + assert!(error.downcast_chain_ref::().is_none()); + assert!(error.downcast_inner_ref::().is_none()); + } }