Compare commits

..

No commits in common. "master" and "v0.8.0" have entirely different histories.

34 changed files with 208 additions and 600 deletions

View file

@ -13,26 +13,31 @@ on:
types:
- created
jobs:
coverage:
test:
name: coverage
runs-on: ubuntu-latest
env:
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v1
- uses: dtolnay/rust-toolchain@master
with:
target: x86_64-unknown-linux-gnu
toolchain: nightly
components: llvm-tools-preview
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Generate code coverage
run: cargo +nightly llvm-cov --all-features --workspace --codecov --doctests --output-path codecov.json
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:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
files: codecov.json
fail_ci_if_error: true
directory: ./
fail_ci_if_error: false
files: ./lcov.info
verbose: true

View file

@ -7,7 +7,7 @@ on:
jobs:
deploy:
runs-on: ubuntu-latest
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2

View file

@ -1,6 +1,6 @@
[package]
name = "chainerror"
version = "1.0.0"
version = "0.8.0"
authors = ["Harald Hoyer <harald@redhat.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
@ -21,6 +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" }
[package.metadata.docs.rs]
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]

View file

@ -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::Context as _;
use chainerror::prelude::v1::*;
use std::path::PathBuf;
type BoxedError = Box<dyn std::error::Error + Send + Sync>;
@ -84,87 +84,12 @@ Os { code: 2, kind: NotFound, message: "No such file or directory" }
`chainerror` uses `.source()` of `std::error::Error` along with `#[track_caller]` and `Location` to provide a nice debug error backtrace.
It encapsulates all types, which have `Display + Debug` and can store the error cause internally.
Along with the `Error<T>` struct, `chainerror` comes with some useful helper macros to save a lot of typing.
Along with the `ChainError<T>` struct, `chainerror` comes with some useful helper macros to save a lot of typing.
`chainerror` has no dependencies!
Debug information is worth it!
## Multiple Output Formats
`chainerror` supports multiple output formats, which can be selected with the different format specifiers:
* `{}`: Display
```console
func1 error calling func2
```
* `{:#}`: Alternative Display
```console
func1 error calling func2
Caused by:
func2 error: calling func3
Caused by:
(passed error)
Caused by:
Error reading 'foo.txt'
Caused by:
entity not found
```
* `{:?}`: Debug
```console
examples/example.rs:50:13: func1 error calling func2
Caused by:
examples/example.rs:25:13: Func2Error(func2 error: calling func3)
Caused by:
examples/example.rs:18:13: (passed error)
Caused by:
examples/example.rs:13:18: Error reading 'foo.txt'
Caused by:
Kind(NotFound)
```
* `{:#?}`: Alternative Debug
```console
Error<example::Func1Error> {
occurrence: Some(
"examples/example.rs:50:13",
),
kind: func1 error calling func2,
source: Some(
Error<example::Func2Error> {
occurrence: Some(
"examples/example.rs:25:13",
),
kind: Func2Error(func2 error: calling func3),
source: Some(
Error<chainerror::AnnotatedError> {
occurrence: Some(
"examples/example.rs:18:13",
),
kind: (passed error),
source: Some(
Error<alloc::string::String> {
occurrence: Some(
"examples/example.rs:13:18",
),
kind: "Error reading 'foo.txt'",
source: Some(
Kind(
NotFound,
),
),
},
),
},
),
},
),
}
```
## Tutorial
Read the [Tutorial](https://haraldh.github.io/chainerror/tutorial1.html)
@ -173,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 https://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)
at your option.

24
README.tpl Normal file
View file

@ -0,0 +1,24 @@
[![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.

View file

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

View file

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

View file

@ -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,9 +8,10 @@ a `std::error::Error`.
Only returning `Func1ErrorKind` in `func1()` now let us get rid of `Result<(), Box<Error + Send + Sync>>` and we can
use `ChainResult<(), Func1ErrorKind>`.
In `main` we can now directly use the methods of `chainerror::Error<T>` without downcasting the error first.
In `main` we can now directly use the methods of `ChainError<T>` without downcasting the error first.
Also, a nice `match` on `chainerror::Error<T>.kind()` is now possible, which returns `&T`, meaning `&Func1ErrorKind` here.
Also a nice `match` on `ChainError<T>.kind()` is now possible, which returns `&T`, meaning
`&Func1ErrorKind` here.
~~~rust
{{#include ../examples/tutorial10.rs}}

View file

@ -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

View file

@ -1,6 +1,6 @@
# Deref for the ErrorKind
Because chainerror::Error<T> implements Deref to &T, we can also match on `*e` instead of `e.kind()`
Because ChainError<T> 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}}

View file

@ -1,6 +1,6 @@
# Writing a library
I would advise to only expose an `mycrate::ErrorKind` and type alias `mycrate::Error` to `chainerror::Error<mycrate::ErrorKind>`
I would advise to only expose an `mycrate::ErrorKind` and type alias `mycrate::Error` to `ChainError<mycrate::ErrorKind>`
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

View file

@ -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<Error + Send + Sync>)` as a result.
The `Debug` implementation of `chainerror::Error<T>` (which is returned by `context()`)
The `Debug` implementation of `ChainError<T>` (which is returned by `context()`)
prints the `Debug` of `T` prefixed with the stored filename and line number.
`chainerror::Error<T>` in our case is `chainerror::Error<&str>`.
`ChainError<T>` in our case is `ChainError<&str>`.

View file

@ -14,13 +14,13 @@ If you compare the output to the previous example, you will see,
that:
~~~
Error: examples/tutorial2.rs:20:16: func1 error
Error: src/main.rs:19: "func1 error"
~~~
changed to just:
~~~
examples/tutorial3.rs:17:13: func1 error
src/main.rs:16: "func1 error"
~~~
This is, because we caught the error of `func1()` in `main()` and print it out ourselves.

View file

@ -14,4 +14,5 @@ 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 use the `Display` backtrace, you have to use the alternative display format output `{:#}`.
To enable the `Display` backtrace, you have to enable the feature `display-cause` for `chainerror`.

View file

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

View file

@ -1,14 +1,14 @@
# Finding an Error cause
To distinguish the errors occurring in various places, we can define named string errors with the
To distinguish the errors occuring in various places, we can define named string errors with the
"new type" pattern.
~~~rust,ignore
chainerror::str_context!(Func2Error);
chainerror::str_context!(Func1Error);
derive_str_context!(Func2Error);
derive_str_context!(Func1Error);
~~~
Instead of `chainerror::Error<String>` we now have `struct Func1Error(String)` and `chainerror::Error<Func1Error>`.
Instead of `ChainError<String>` we now have `struct Func1Error(String)` and `ChainError<Func1Error>`.
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::<chainerror::Error<Func2Error>>() {
if let Some(f2err) = f1err.find_cause::<ChainError<Func2Error>>() {
~~~
hiding the `chainerror::Error<T>` implementation detail.
hiding the `ChainError<T>` implementation detail.
~~~rust
{{#include ../examples/tutorial8.rs}}

View file

@ -1,28 +1,24 @@
use chainerror::Context as _;
use std::error::Error;
use std::fmt;
use std::io;
use std::result::Result;
use chainerror::prelude::v1::*;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(())
}
fn func4() -> Result<(), Box<dyn Error + Send + Sync>> {
fn func3() -> Result<(), Box<dyn Error + Send + Sync>> {
let filename = "foo.txt";
do_some_io().context(format!("Error reading '{}'", filename))?;
Ok(())
}
fn func3() -> Result<(), Box<dyn Error + Send + Sync>> {
func4().annotate()?;
Ok(())
}
derive_str_context!(Func2Error);
chainerror::str_context!(Func2Error);
fn func2() -> chainerror::Result<(), Func2Error> {
func3().context(Func2Error::new("func2 error: calling func3"))?;
fn func2() -> ChainResult<(), Func2Error> {
func3().context(Func2Error("func2 error: calling func3".to_string()))?;
Ok(())
}
@ -31,8 +27,8 @@ enum Func1Error {
IO(String),
}
impl fmt::Display for Func1Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
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),
@ -40,13 +36,13 @@ impl fmt::Display for Func1Error {
}
}
impl fmt::Debug for Func1Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
impl ::std::fmt::Debug for Func1Error {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
write!(f, "{}", self)
}
}
fn func1() -> chainerror::Result<(), Func1Error> {
fn func1() -> ChainResult<(), Func1Error> {
func2().context(Func1Error::Func2)?;
let filename = String::from("bar.txt");
do_some_io().context(Func1Error::IO(filename))?;

View file

@ -3,6 +3,7 @@
use std::error::Error;
use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;

View file

@ -1,14 +1,14 @@
use chainerror::Context as _;
use chainerror::prelude::v1::*;
use std::error::Error;
use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(())
}
chainerror::str_context!(Func2Error);
derive_str_context!(Func2Error);
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> {
let filename = "foo.txt";
@ -32,7 +32,7 @@ impl ::std::fmt::Display for Func1ErrorKind {
}
impl ::std::error::Error for Func1ErrorKind {}
fn func1() -> chainerror::Result<(), Func1ErrorKind> {
fn func1() -> ChainResult<(), Func1ErrorKind> {
func2().context(Func1ErrorKind::Func2)?;
let filename = String::from("bar.txt");
do_some_io().context(Func1ErrorKind::IO(filename))?;

View file

@ -1,14 +1,14 @@
use chainerror::Context as _;
use chainerror::prelude::v1::*;
use std::error::Error;
use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(())
}
chainerror::str_context!(Func2Error);
derive_str_context!(Func2Error);
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> {
let filename = "foo.txt";
@ -38,7 +38,7 @@ impl ::std::fmt::Debug for Func1ErrorKind {
impl ::std::error::Error for Func1ErrorKind {}
fn func1() -> chainerror::Result<(), Func1ErrorKind> {
fn func1() -> ChainResult<(), Func1ErrorKind> {
func2().context(Func1ErrorKind::Func2)?;
let filename = String::from("bar.txt");
do_some_io().context(Func1ErrorKind::IO(filename))?;

View file

@ -1,14 +1,14 @@
use chainerror::Context as _;
use chainerror::prelude::v1::*;
use std::error::Error;
use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(())
}
chainerror::str_context!(Func2Error);
derive_str_context!(Func2Error);
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> {
let filename = "foo.txt";
@ -38,7 +38,7 @@ impl ::std::fmt::Debug for Func1ErrorKind {
impl ::std::error::Error for Func1ErrorKind {}
fn func1() -> chainerror::Result<(), Func1ErrorKind> {
fn func1() -> ChainResult<(), Func1ErrorKind> {
func2().context(Func1ErrorKind::Func2)?;
let filename = String::from("bar.txt");
do_some_io().context(Func1ErrorKind::IO(filename))?;

View file

@ -2,8 +2,7 @@
#![allow(clippy::redundant_pattern_matching)]
pub mod mycrate {
use chainerror::Context as _;
use chainerror::prelude::v1::*;
use std::io;
fn do_some_io() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
@ -11,7 +10,7 @@ pub mod mycrate {
Ok(())
}
chainerror::str_context!(Func2Error);
derive_str_context!(Func2Error);
fn func2() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
let filename = "foo.txt";
@ -25,7 +24,7 @@ pub mod mycrate {
IO(String),
}
chainerror::err_kind!(Error, ErrorKind);
derive_err_kind!(Error, ErrorKind);
pub type Result<T> = std::result::Result<T, Error>;

View file

@ -2,8 +2,7 @@
#![allow(clippy::redundant_pattern_matching)]
pub mod mycrate {
use chainerror::{Context as _, ErrorDown as _};
use chainerror::prelude::v1::*;
use std::io;
fn do_some_io(_f: &str) -> std::result::Result<(), io::Error> {
@ -11,7 +10,7 @@ pub mod mycrate {
Ok(())
}
chainerror::str_context!(Func2Error);
derive_str_context!(Func2Error);
fn func2() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
let filename = "foo.txt";
@ -27,7 +26,7 @@ pub mod mycrate {
Unknown,
}
chainerror::err_kind!(Error, ErrorKind);
derive_err_kind!(Error, ErrorKind);
pub type Result<T> = std::result::Result<T, Error>;
impl std::fmt::Display for ErrorKind {

View file

@ -1,7 +1,8 @@
use chainerror::Context as _;
use chainerror::prelude::v1::*;
use std::error::Error;
use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;

View file

@ -1,7 +1,8 @@
use chainerror::Context as _;
use chainerror::prelude::v1::*;
use std::error::Error;
use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;

View file

@ -1,7 +1,7 @@
use chainerror::Context as _;
use chainerror::prelude::v1::*;
use std::error::Error;
use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;

View file

@ -1,7 +1,7 @@
use chainerror::Context as _;
use chainerror::prelude::v1::*;
use std::error::Error;
use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;

View file

@ -1,10 +1,10 @@
#![allow(clippy::single_match)]
#![allow(clippy::redundant_pattern_matching)]
use chainerror::Context as _;
use chainerror::prelude::v1::*;
use std::error::Error;
use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;

View file

@ -1,10 +1,10 @@
#![allow(clippy::single_match)]
#![allow(clippy::redundant_pattern_matching)]
use chainerror::{Context as _, ErrorDown as _};
use chainerror::prelude::v1::*;
use std::error::Error;
use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;

View file

@ -1,14 +1,14 @@
use chainerror::{Context as _, ErrorDown as _};
use chainerror::prelude::v1::*;
use std::error::Error;
use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(())
}
chainerror::str_context!(Func2Error);
derive_str_context!(Func2Error);
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> {
let filename = "foo.txt";
@ -16,10 +16,10 @@ fn func2() -> Result<(), Box<dyn Error + Send + Sync>> {
Ok(())
}
chainerror::str_context!(Func1Error);
derive_str_context!(Func1Error);
fn func1() -> Result<(), Box<dyn Error + Send + Sync>> {
func2().context(Func1Error::new("func1 error"))?;
func2().context(Func1Error("func1 error".to_string()))?;
Ok(())
}
@ -28,7 +28,7 @@ fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() {
eprintln!("Func1Error: {}", f1err);
if let Some(f2err) = f1err.find_cause::<chainerror::Error<Func2Error>>() {
if let Some(f2err) = f1err.find_cause::<ChainError<Func2Error>>() {
eprintln!("Func2Error: {}", f2err);
}

View file

@ -1,14 +1,14 @@
use chainerror::{Context as _, ErrorDown};
use chainerror::prelude::v1::*;
use std::error::Error;
use std::io;
use std::result::Result;
fn do_some_io() -> Result<(), Box<dyn Error + Send + Sync>> {
Err(io::Error::from(io::ErrorKind::NotFound))?;
Ok(())
}
chainerror::str_context!(Func2Error);
derive_str_context!(Func2Error);
fn func2() -> Result<(), Box<dyn Error + Send + Sync>> {
let filename = "foo.txt";
@ -16,11 +16,11 @@ fn func2() -> Result<(), Box<dyn Error + Send + Sync>> {
Ok(())
}
chainerror::str_context!(Func1ErrorFunc2);
chainerror::str_context!(Func1ErrorIO);
derive_str_context!(Func1ErrorFunc2);
derive_str_context!(Func1ErrorIO);
fn func1() -> Result<(), Box<dyn Error + Send + Sync>> {
func2().context(Func1ErrorFunc2::new("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(())
@ -28,7 +28,7 @@ fn func1() -> Result<(), Box<dyn Error + Send + Sync>> {
fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
if let Err(e) = func1() {
if let Some(s) = e.downcast_ref::<chainerror::Error<Func1ErrorIO>>() {
if let Some(s) = e.downcast_ref::<ChainError<Func1ErrorIO>>() {
eprintln!("Func1ErrorIO:\n{:?}", s);
}

View file

@ -8,6 +8,18 @@ use std::error::Error as StdError;
use std::fmt::{Debug, Display, Formatter};
use std::panic::Location;
pub mod prelude {
//! convenience prelude
pub mod v1 {
//! convenience prelude
pub use super::super::ChainErrorDown as _;
pub use super::super::Error as ChainError;
pub use super::super::Result as ChainResult;
pub use super::super::ResultTrait as _;
pub use crate::{derive_err_kind, derive_str_context};
}
}
/// chains an inner error kind `T` with a causing error
pub struct Error<T> {
occurrence: Option<String>,
@ -43,8 +55,7 @@ impl<T: 'static + Display + Debug> Error<T> {
/// # Examples
///
/// ```rust
/// use chainerror::Context as _;
/// use chainerror::ErrorDown as _;
/// use chainerror::prelude::v1::*;
/// use std::error::Error;
/// use std::io;
///
@ -53,7 +64,7 @@ impl<T: 'static + Display + Debug> Error<T> {
/// Ok(())
/// }
///
/// chainerror::str_context!(Func2Error);
/// derive_str_context!(Func2Error);
///
/// fn func2() -> Result<(), Box<dyn Error + Send + Sync>> {
/// let filename = "foo.txt";
@ -61,10 +72,10 @@ impl<T: 'static + Display + Debug> Error<T> {
/// Ok(())
/// }
///
/// chainerror::str_context!(Func1Error);
/// derive_str_context!(Func1Error);
///
/// fn func1() -> Result<(), Box<dyn Error + Send + Sync>> {
/// func2().context(Func1Error::new("func1 error"))?;
/// func2().context(Func1Error("func1 error".into()))?;
/// Ok(())
/// }
///
@ -89,19 +100,20 @@ impl<T: 'static + Display + Debug> Error<T> {
.next()
}
/// Find the first error cause of type [`Error<U>`](Error), if any exists
/// Find the first error cause of type `ChainError<U>`, if any exists
///
/// Same as `find_cause`, but hides the [`Error<U>`](Error) implementation internals
/// Same as `find_cause`, but hides the `ChainError<U>` implementation internals
///
/// # Examples
///
/// ```rust
/// # chainerror::str_context!(FooError);
/// # let err = chainerror::Error::new(String::new(), None, None);
/// # use chainerror::prelude::v1::*;
/// # derive_str_context!(FooError);
/// # let err = ChainError::new(String::new(), None, None);
/// // Instead of writing
/// err.find_cause::<chainerror::Error<FooError>>();
/// err.find_cause::<ChainError<FooError>>();
///
/// // leave out the chainerror::Error<FooError> implementation detail
/// // leave out the ChainError<FooError> implementation detail
/// err.find_chain_cause::<FooError>();
/// ```
#[inline]
@ -111,23 +123,24 @@ impl<T: 'static + Display + Debug> Error<T> {
.next()
}
/// Find the first error cause of type [`Error<U>`](Error) or `U`, if any exists and return `U`
/// Find the first error cause of type `ChainError<U>` or `U`, if any exists and return `U`
///
/// Same as `find_cause` and `find_chain_cause`, but hides the [`Error<U>`](Error) implementation internals
/// Same as `find_cause` and `find_chain_cause`, but hides the `ChainError<U>` implementation internals
///
/// # Examples
///
/// ```rust
/// # chainerror::str_context!(FooErrorKind);
/// # let err = chainerror::Error::new(String::new(), None, None);
/// # use chainerror::prelude::v1::*;
/// # derive_str_context!(FooErrorKind);
/// # let err = ChainError::new(String::new(), None, None);
/// // Instead of writing
/// err.find_cause::<chainerror::Error<FooErrorKind>>();
/// err.find_cause::<ChainError<FooErrorKind>>();
/// // and/or
/// err.find_chain_cause::<FooErrorKind>();
/// // and/or
/// err.find_cause::<FooErrorKind>();
///
/// // leave out the chainerror::Error<FooErrorKind> implementation detail
/// // leave out the ChainError<FooErrorKind> implementation detail
/// err.find_kind_or_cause::<FooErrorKind>();
/// ```
#[inline]
@ -141,12 +154,12 @@ impl<T: 'static + Display + Debug> Error<T> {
.next()
}
/// Return a reference to T of [`Error<T>`](Error)
/// Return a reference to T of `ChainError<T>`
///
/// # Examples
///
/// ```rust
/// use chainerror::Context as _;
/// use chainerror::prelude::v1::*;
/// use std::error::Error;
/// use std::io;
///
@ -155,7 +168,7 @@ impl<T: 'static + Display + Debug> Error<T> {
/// Ok(())
/// }
///
/// chainerror::str_context!(Func2Error);
/// derive_str_context!(Func2Error);
///
/// fn func2() -> Result<(), Box<dyn Error + Send + Sync>> {
/// let filename = "foo.txt";
@ -179,7 +192,7 @@ impl<T: 'static + Display + Debug> Error<T> {
/// # }
/// # }
///
/// fn func1() -> chainerror::Result<(), Func1ErrorKind> {
/// fn func1() -> ChainResult<(), Func1ErrorKind> {
/// func2().context(Func1ErrorKind::Func2)?;
/// do_some_io().context(Func1ErrorKind::IO("bar.txt".into()))?;
/// Ok(())
@ -211,14 +224,11 @@ impl<T: 'static + Display + Debug> Error<T> {
}
}
/// Convenience methods for `Result<>` to turn the error into a decorated [`Error`](Error)
pub trait Context<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> {
/// Convenience methods for `Result<>` to turn the error into a decorated ChainError
pub trait ResultTrait<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> {
/// Decorate the error with a `kind` of type `T` and the source `Location`
fn context<T: 'static + Display + Debug>(self, kind: T) -> std::result::Result<O, Error<T>>;
/// Decorate the error just with the source `Location`
fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>>;
/// Decorate the `error` with a `kind` of type `T` produced with a `FnOnce(&error)` and the source `Location`
fn map_context<T: 'static + Display + Debug, F: FnOnce(&E) -> T>(
self,
@ -226,22 +236,7 @@ pub trait Context<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> {
) -> std::result::Result<O, Error<T>>;
}
/// 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<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> Context<O, E>
impl<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> ResultTrait<O, E>
for std::result::Result<O, E>
{
#[track_caller]
@ -257,19 +252,6 @@ impl<O, E: Into<Box<dyn StdError + 'static + Send + Sync>>> Context<O, E>
}
}
#[track_caller]
#[inline]
fn annotate(self) -> std::result::Result<O, Error<AnnotatedError>> {
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: 'static + Display + Debug, F: FnOnce(&E) -> T>(
@ -315,21 +297,21 @@ impl<T: 'static + Display + Debug> std::ops::Deref for Error<T> {
}
}
/// Convenience trait to hide the [`Error<T>`](Error) implementation internals
pub trait ErrorDown {
/// Test if of type `Error<T>`
/// Convenience trait to hide the `ChainError<T>` implementation internals
pub trait ChainErrorDown {
/// Test if of type `ChainError<T>`
fn is_chain<T: 'static + Display + Debug>(&self) -> bool;
/// Downcast to a reference of `Error<T>`
/// Downcast to a reference of `ChainError<T>`
fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>>;
/// Downcast to a mutable reference of `Error<T>`
/// Downcast to a mutable reference of `ChainError<T>`
fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>>;
/// Downcast to T of `Error<T>`
/// Downcast to T of `ChainError<T>`
fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T>;
/// Downcast to T mutable reference of `Error<T>`
/// Downcast to T mutable reference of `ChainError<T>`
fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T>;
}
impl<U: 'static + Display + Debug> ErrorDown for Error<U> {
impl<U: 'static + Display + Debug> ChainErrorDown for Error<U> {
#[inline]
fn is_chain<T: 'static + Display + Debug>(&self) -> bool {
TypeId::of::<T>() == TypeId::of::<U>()
@ -338,8 +320,11 @@ impl<U: 'static + Display + Debug> ErrorDown for Error<U> {
#[inline]
fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> {
if self.is_chain::<T>() {
// Use transmute when we've verified the types match
unsafe { Some(std::mem::transmute::<&Error<U>, &Error<T>>(self)) }
#[allow(clippy::cast_ptr_alignment)]
unsafe {
#[allow(trivial_casts)]
Some(*(self as *const dyn StdError as *const &Error<T>))
}
} else {
None
}
@ -348,8 +333,11 @@ impl<U: 'static + Display + Debug> ErrorDown for Error<U> {
#[inline]
fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> {
if self.is_chain::<T>() {
// Use transmute when we've verified the types match
unsafe { Some(std::mem::transmute::<&mut Error<U>, &mut Error<T>>(self)) }
#[allow(clippy::cast_ptr_alignment)]
unsafe {
#[allow(trivial_casts)]
Some(&mut *(self as *mut dyn StdError as *mut &mut Error<T>))
}
} else {
None
}
@ -357,8 +345,11 @@ impl<U: 'static + Display + Debug> ErrorDown for Error<U> {
#[inline]
fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> {
if self.is_chain::<T>() {
// Use transmute when we've verified the types match
unsafe { Some(std::mem::transmute::<&U, &T>(&self.kind)) }
#[allow(clippy::cast_ptr_alignment)]
unsafe {
#[allow(trivial_casts)]
Some(&(*(self as *const dyn StdError as *const &Error<T>)).kind)
}
} else {
None
}
@ -367,15 +358,18 @@ impl<U: 'static + Display + Debug> ErrorDown for Error<U> {
#[inline]
fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> {
if self.is_chain::<T>() {
// Use transmute when we've verified the types match
unsafe { Some(std::mem::transmute::<&mut U, &mut T>(&mut self.kind)) }
#[allow(clippy::cast_ptr_alignment)]
unsafe {
#[allow(trivial_casts)]
Some(&mut (*(self as *mut dyn StdError as *mut &mut Error<T>)).kind)
}
} else {
None
}
}
}
impl ErrorDown for dyn StdError + 'static {
impl ChainErrorDown for dyn StdError + 'static {
#[inline]
fn is_chain<T: 'static + Display + Debug>(&self) -> bool {
self.is::<Error<T>>()
@ -408,7 +402,7 @@ impl ErrorDown for dyn StdError + 'static {
}
}
impl ErrorDown for dyn StdError + 'static + Send {
impl ChainErrorDown for dyn StdError + 'static + Send {
#[inline]
fn is_chain<T: 'static + Display + Debug>(&self) -> bool {
self.is::<Error<T>>()
@ -441,7 +435,7 @@ impl ErrorDown for dyn StdError + 'static + Send {
}
}
impl ErrorDown for dyn StdError + 'static + Send + Sync {
impl ChainErrorDown for dyn StdError + 'static + Send + Sync {
#[inline]
fn is_chain<T: 'static + Display + Debug>(&self) -> bool {
self.is::<Error<T>>()
@ -511,7 +505,7 @@ impl<T: 'static + Display + Debug> Debug for Error<T> {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
let mut f = f.debug_struct(&format!("Error<{}>", std::any::type_name::<T>()));
let mut f = f.debug_struct(&format!("ChainError<{}>", std::any::type_name::<T>()));
let f = f
.field("occurrence", &self.occurrence)
@ -555,8 +549,7 @@ where
/// # Examples
///
/// ```rust
/// # use chainerror::Context as _;
/// # use chainerror::ErrorDown as _;
/// # use chainerror::prelude::v1::*;
/// # use std::error::Error;
/// # use std::io;
/// # use std::result::Result;
@ -564,23 +557,23 @@ where
/// # Err(io::Error::from(io::ErrorKind::NotFound))?;
/// # Ok(())
/// # }
/// chainerror::str_context!(Func2Error);
/// derive_str_context!(Func2Error);
///
/// fn func2() -> chainerror::Result<(), Func2Error> {
/// fn func2() -> ChainResult<(), Func2Error> {
/// let filename = "foo.txt";
/// do_some_io().context(Func2Error(format!("Error reading '{}'", filename)))?;
/// Ok(())
/// }
///
/// chainerror::str_context!(Func1Error);
/// derive_str_context!(Func1Error);
///
/// fn func1() -> Result<(), Box<dyn Error>> {
/// func2().context(Func1Error::new("func1 error"))?;
/// func2().context(Func1Error("func1 error".into()))?;
/// Ok(())
/// }
/// # if let Err(e) = func1() {
/// # if let Some(f1err) = e.downcast_chain_ref::<Func1Error>() {
/// # assert!(f1err.find_cause::<chainerror::Error<Func2Error>>().is_some());
/// # assert!(f1err.find_cause::<ChainError<Func2Error>>().is_some());
/// # assert!(f1err.find_chain_cause::<Func2Error>().is_some());
/// # } else {
/// # panic!();
@ -590,16 +583,10 @@ where
/// # }
/// ```
#[macro_export]
macro_rules! str_context {
macro_rules! derive_str_context {
($e:ident) => {
#[derive(Clone)]
pub struct $e(pub String);
impl $e {
#[allow(dead_code)]
pub fn new<S: Into<String>>(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)
@ -614,9 +601,9 @@ macro_rules! str_context {
};
}
/// Derive an Error for an ErrorKind, which wraps a [`Error`](Error) and implements a `kind()` method
/// Derive an Error for an ErrorKind, which wraps a `ChainError` and implements a `kind()` method
///
/// It basically hides [`Error`](Error) to the outside and only exposes the [`kind()`](Error::kind)
/// It basically hides `ChainError` to the outside and only exposes the `kind()`
/// method.
///
/// Error::kind() returns the ErrorKind
@ -625,7 +612,7 @@ macro_rules! str_context {
/// # Examples
///
/// ```rust
/// use chainerror::Context as _;
/// use chainerror::prelude::v1::*;
/// use std::io;
///
/// fn do_some_io(_f: &str) -> std::result::Result<(), io::Error> {
@ -639,7 +626,7 @@ macro_rules! str_context {
/// Unknown,
/// }
///
/// chainerror::err_kind!(Error, ErrorKind);
/// derive_err_kind!(Error, ErrorKind);
///
/// impl std::fmt::Display for ErrorKind {
/// fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::fmt::Result {
@ -677,15 +664,9 @@ 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 {
macro_rules! derive_err_kind {
($e:ident, $k:ident) => {
pub struct $e($crate::Error<$k>);
@ -701,7 +682,7 @@ macro_rules! err_kind {
}
}
impl From<$crate::Error<$k>> for $e {
impl From<ChainError<$k>> for $e {
fn from(e: $crate::Error<$k>) -> Self {
$e(e)
}
@ -735,291 +716,3 @@ 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::<Level1Error>());
assert!(err.find_chain_cause::<Level2Error>().is_some());
assert!(err.find_chain_cause::<Level3Error>().is_some());
assert!(err.find_chain_cause::<io::Error>().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::<io::Error>());
}
#[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::<io::Error>()
.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::<MappedError>());
assert!(err.find_chain_cause::<io::Error>().is_some());
}
#[test]
fn test_error_downcasting() {
str_context!(OriginalError);
let original = Error::new(OriginalError("test".into()), None, None);
let error: Box<dyn StdError + Send + Sync> = Box::new(original);
// Test downcast_chain_ref
assert!(error.is_chain::<OriginalError>());
assert!(error.downcast_chain_ref::<OriginalError>().is_some());
// Test downcast_inner_ref
let inner = error.downcast_inner_ref::<OriginalError>();
assert!(inner.is_some());
}
#[derive(Debug, Clone)]
enum TestErrorKind {
Basic(String),
Complex { message: String },
}
impl Display for TestErrorKind {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
TestErrorKind::Basic(msg) => write!(f, "Basic error: {}", msg),
TestErrorKind::Complex { message } => write!(f, "Complex error: {}", message),
}
}
}
#[test]
fn test_err_kind_macro() {
err_kind!(TestError, TestErrorKind);
let err = TestError::from(TestErrorKind::Basic("test".into()));
assert!(matches!(err.kind(), TestErrorKind::Basic(_)));
// The annotated error should display "(passed error)" even in a chain
assert_eq!(format!("{}", err), "Basic error: test");
assert_eq!(format!("{:?}", err), "Basic(\"test\")");
let complex_err = TestError::from(TestErrorKind::Complex {
message: "test".into(),
});
assert!(matches!(complex_err.kind(), TestErrorKind::Complex { .. }));
// The annotated error should display "(passed error)" even in a chain
assert_eq!(format!("{}", complex_err), "Complex error: test");
assert_eq!(
format!("{:?}", complex_err),
"Complex { message: \"test\" }"
);
}
#[test]
fn test_annotated_error_display_and_debug() {
let annotated = AnnotatedError(());
// Test Display formatting
assert_eq!(format!("{}", annotated), "(passed error)");
// Test Debug formatting
assert_eq!(format!("{:?}", annotated), "(passed error)");
// Test with error chain
let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
let err = Result::<(), _>::Err(io_error.into())
.annotate()
.unwrap_err();
// The annotated error should display "(passed error)" even in a chain
assert_eq!(format!("{}", err), "(passed error)");
assert!(format!("{:?}", err).contains("(passed error)"));
// Verify the error chain is preserved
assert!(err.source().is_some());
assert!(err.source().unwrap().is_chain::<io::Error>());
}
// 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::<TestError>());
assert!(!original_error.is_chain::<io::Error>());
// Test downcast_chain_ref
let downcast_ref = original_error.downcast_chain_ref::<TestError>();
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::<io::Error>();
assert!(invalid_downcast.is_none());
// Test downcast_chain_mut
let mut mutable_error = original_error;
let downcast_mut = mutable_error.downcast_chain_mut::<TestError>();
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::<io::Error>();
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::<TestError>();
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::<io::Error>();
assert!(invalid_inner.is_none());
// Test downcast_inner_mut
let inner_mut = error.downcast_inner_mut::<TestError>();
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::<io::Error>();
assert!(invalid_inner_mut.is_none());
}
#[test]
fn test_error_down_for_dyn_error() {
// Create a boxed error
let error: Box<dyn std::error::Error + 'static> = 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::<TestError>());
assert!(!error.is_chain::<io::Error>());
// Test downcast_chain_ref through trait object
let chain_ref = error.downcast_chain_ref::<TestError>();
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::<TestError>();
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<dyn std::error::Error + Send + Sync> = 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::<TestError>());
assert!(error.downcast_chain_ref::<TestError>().is_some());
assert!(error.downcast_inner_ref::<TestError>().is_some());
// Test invalid downcasts
assert!(!error.is_chain::<io::Error>());
assert!(error.downcast_chain_ref::<io::Error>().is_none());
assert!(error.downcast_inner_ref::<io::Error>().is_none());
}
}

View file

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

View file

@ -1,4 +1,4 @@
use chainerror::Context;
use chainerror::prelude::v1::*;
use std::error::Error;
use std::io;