From 8ce3e78952bd8f1fcbf6098951fa4e3937018c8b Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Sun, 17 Nov 2019 21:54:44 +0100 Subject: [PATCH] verbose errors feature This adds a new "verbose-errors" feature flag to async-std that enables wrapping certain errors in structures with more context. As an example, we use it in `fs::File::{open,create}` to add the given path to the error message (something that is lacking in std to annoyance of many). --- .github/workflows/ci.yml | 6 +++++ Cargo.toml | 1 + src/fs/file.rs | 13 ++++++++-- src/io/mod.rs | 1 + src/io/utils.rs | 51 ++++++++++++++++++++++++++++++++++++++++ src/utils.rs | 8 +++++++ tests/verbose_errors.rs | 20 ++++++++++++++++ 7 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 src/io/utils.rs create mode 100644 tests/verbose_errors.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99436b72..5d5639c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,6 +64,12 @@ jobs: command: test args: --all --features unstable + - name: tests with verbose errors + uses: actions-rs/cargo@v1 + with: + command: test + args: --all --features 'unstable verbose-errors' + check_fmt_and_docs: name: Checking fmt and docs runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 7ffaae3a..7886bcfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ std = [ "pin-utils", "slab", ] +verbose-errors = [] [dependencies] async-attributes = { version = "1.1.1", optional = true } diff --git a/src/fs/file.rs b/src/fs/file.rs index 8bc6c2ce..5186e96f 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -9,6 +9,7 @@ use std::sync::{Arc, Mutex}; use crate::fs::{Metadata, Permissions}; use crate::future; +use crate::utils::VerboseErrorExt; use crate::io::{self, Read, Seek, SeekFrom, Write}; use crate::path::Path; use crate::prelude::*; @@ -112,7 +113,11 @@ impl File { /// ``` pub async fn open>(path: P) -> io::Result { let path = path.as_ref().to_owned(); - let file = spawn_blocking(move || std::fs::File::open(&path)).await?; + let file = spawn_blocking(move || { + std::fs::File::open(&path) + .verbose_context(|| format!("Could not open {}", path.display())) + }) + .await?; Ok(File::new(file, true)) } @@ -147,7 +152,11 @@ impl File { /// ``` pub async fn create>(path: P) -> io::Result { let path = path.as_ref().to_owned(); - let file = spawn_blocking(move || std::fs::File::create(&path)).await?; + let file = spawn_blocking(move || { + std::fs::File::create(&path) + .verbose_context(|| format!("Could not create {}", path.display())) + }) + .await?; Ok(File::new(file, true)) } diff --git a/src/io/mod.rs b/src/io/mod.rs index 4e832305..d9660a72 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -291,6 +291,7 @@ cfg_std! { pub(crate) mod read; pub(crate) mod seek; pub(crate) mod write; + pub(crate) mod utils; mod buf_reader; mod buf_writer; diff --git a/src/io/utils.rs b/src/io/utils.rs new file mode 100644 index 00000000..ba6285f6 --- /dev/null +++ b/src/io/utils.rs @@ -0,0 +1,51 @@ +use std::{error::Error, fmt, io}; +use crate::utils::VerboseErrorExt; + +/// Wrap `std::io::Error` with additional message +/// +/// *Note* Only active when `verbose-errors` feature is enabled for this crate! +/// +/// Keeps the original error kind and stores the original I/O error as `source`. +impl VerboseErrorExt for Result { + fn verbose_context(self, message: impl Fn() -> String) -> Self { + if cfg!(feature = "verbose-errors") { + self.map_err(|e| VerboseError::wrap(e, message())) + } else { + self + } + } +} + +#[derive(Debug)] +struct VerboseError { + source: io::Error, + message: String, +} + +impl VerboseError { + fn wrap(source: io::Error, message: impl Into) -> io::Error { + io::Error::new( + source.kind(), + VerboseError { + source, + message: message.into(), + }, + ) + } +} + +impl fmt::Display for VerboseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.message) + } +} + +impl Error for VerboseError { + fn description(&self) -> &str { + self.source.description() + } + + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.source) + } +} diff --git a/src/utils.rs b/src/utils.rs index 13dbe37d..00dc7931 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -52,6 +52,14 @@ pub fn random(n: u32) -> u32 { }) } +/// Add additional context to errors +/// +/// *Note for implementors:* The given closure must only be executed when +/// `verbose-errors` feature is enabled for this crate! +pub(crate) trait VerboseErrorExt { + fn verbose_context(self, message: impl Fn() -> String) -> Self; +} + /// Defers evaluation of a block of code until the end of the scope. #[cfg(feature = "default")] #[doc(hidden)] diff --git a/tests/verbose_errors.rs b/tests/verbose_errors.rs new file mode 100644 index 00000000..3d99ede7 --- /dev/null +++ b/tests/verbose_errors.rs @@ -0,0 +1,20 @@ +#[cfg(feature = "verbose-errors")] +mod verbose_tests { + use async_std::{fs, task}; + + #[test] + fn open_file() { + task::block_on(async { + let non_existing_file = + "/ashjudlkahasdasdsikdhajik/asdasdasdasdasdasd/fjuiklashdbflasas"; + let res = fs::File::open(non_existing_file).await; + match res { + Ok(_) => panic!("Found file with random name: We live in a simulation"), + Err(e) => assert_eq!( + "Could not open /ashjudlkahasdasdsikdhajik/asdasdasdasdasdasd/fjuiklashdbflasas", + &format!("{}", e) + ), + } + }) + } +}