From 3beab9d001a8ea4b8111ff86807f6e7063450b12 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Jul 2023 15:16:21 +0200 Subject: [PATCH 01/19] chore: Release chainerror version 0.8.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 668f5a0..cb63019 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chainerror" -version = "0.8.1-alpha.1" +version = "0.8.1" authors = ["Harald Hoyer "] edition = "2018" license = "MIT OR Apache-2.0" From cacfb37d4469280c8c62bcb78c317692f2c90266 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 28 Jul 2023 15:18:14 +0200 Subject: [PATCH 02/19] 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 03/19] 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 04/19] 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 05/19] 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 06/19] 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 07/19] 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 08/19] 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 09/19] 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 10/19] 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 11/19] 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 12/19] 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 13/19] 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 14/19] 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 15/19] 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 16/19] 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 17/19] 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 18/19] 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 19/19] 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()); + } }