diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 798422a..e467637 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,3 +67,9 @@ jobs: command: check args: --features tokio if: ${{ matrix.rust_version == 'stable' || matrix.rust_version == 'beta' }} + + - name: cargo check --features expose_original_error + uses: actions-rs/cargo@v1 + with: + command: check + args: --features expose_original_error diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ad5ccc..6baeb14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # fs-err Changelog +* Change errors to output original `std::io::Error` information Display by default. This functionality can be disabled for [anyhow](https://docs.rs/anyhow/latest/anyhow/) users by using the new feature `expose_original_error` ([#60](https://github.com/andrewhickman/fs-err/pull/60)). + ## 2.11.0 * Added the first line of the standard library documentation to each function's rustdocs, to make them more useful in IDEs ([#50](https://github.com/andrewhickman/fs-err/issues/45)) diff --git a/Cargo.toml b/Cargo.toml index 6c999e3..dd8e02b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,13 @@ serde_json = "1.0.64" # Adds I/O safety traits, introduced in Rust 1.63 io_safety = [] +# Allow custom formatting of the error source +# +# When enabled errors emit `std::error::Error::source()` as Some (default is `None`) and +# no longer include the original `std::io::Error` source in the `Display` implementation. +# This is useful if errors are wrapped in another library such as Anyhow. +expose_original_error = [] + [package.metadata.release] tag-name = "{{version}}" sign-tag = true diff --git a/src/errors.rs b/src/errors.rs index 22a0eaa..56fd4f8 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -99,7 +99,13 @@ impl fmt::Display for Error { E::ReadAt => write!(formatter, "failed to read with offset from `{}`", path), #[cfg(unix)] E::WriteAt => write!(formatter, "failed to write with offset to `{}`", path), - } + }?; + + // The `expose_original_error` feature indicates the caller should display the original error + #[cfg(not(feature = "expose_original_error"))] + write!(formatter, " caused by: {}", self.source)?; + + Ok(()) } } @@ -108,6 +114,12 @@ impl StdError for Error { self.source() } + #[cfg(not(feature = "expose_original_error"))] + fn source(&self) -> Option<&(dyn StdError + 'static)> { + None + } + + #[cfg(feature = "expose_original_error")] fn source(&self) -> Option<&(dyn StdError + 'static)> { Some(&self.source) } @@ -188,7 +200,13 @@ impl fmt::Display for SourceDestError { SourceDestErrorKind::SymlinkDir => { write!(formatter, "failed to symlink dir from {} to {}", from, to) } - } + }?; + + // The `expose_original_error` feature indicates the caller should display the original error + #[cfg(not(feature = "expose_original_error"))] + write!(formatter, " caused by: {}", self.source)?; + + Ok(()) } } @@ -197,6 +215,12 @@ impl StdError for SourceDestError { self.source() } + #[cfg(not(feature = "expose_original_error"))] + fn source(&self) -> Option<&(dyn StdError + 'static)> { + None + } + + #[cfg(feature = "expose_original_error")] fn source(&self) -> Option<&(dyn StdError + 'static)> { Some(&self.source) } diff --git a/src/lib.rs b/src/lib.rs index 19b8c8d..52eb2b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,9 @@ failed to open file `does not exist.txt` caused by: The system cannot find the file specified. (os error 2) ``` +> Note: Users of `anyhow` or other libraries that format an Error's sources can enable the `expose_original_error` feature to control the formatting of the orginal error message. +> When enabled, the `std::fmt::Display` implementation will emit the failed operation and paths but not the original `std::io::Error`. It will instead provide access via [Error::source](https://doc.rust-lang.org/std/error/trait.Error.html#method.source), which will be used by `anyhow` (or similar) libraries. + # Usage fs-err's API is the same as [`std::fs`][std::fs], so migrating code to use it is easy.