diff --git a/src/fs/file.rs b/src/fs/file.rs index 8bc6c2c..f824281 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::Context as _; 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) + .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) + .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 4e83230..d9660a7 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 0000000..1b73064 --- /dev/null +++ b/src/io/utils.rs @@ -0,0 +1,46 @@ +use crate::utils::Context; + +/// Wrap `std::io::Error` with additional message +/// +/// Keeps the original error kind and stores the original I/O error as `source`. +impl Context for Result { + fn context(self, message: impl Fn() -> String) -> Self { + self.map_err(|e| VerboseError::wrap(e, message())) + } +} + +use std::{error::Error as StdError, fmt, io}; + +#[derive(Debug)] +pub(crate) struct VerboseError { + source: io::Error, + message: String, +} + +impl VerboseError { + pub(crate) 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 StdError for VerboseError { + fn description(&self) -> &str { + self.source.description() + } + + fn source(&self) -> Option<&(dyn StdError + 'static)> { + Some(&self.source) + } +} diff --git a/src/utils.rs b/src/utils.rs index 49e3d99..7d253b4 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -52,6 +52,11 @@ pub fn random(n: u32) -> u32 { }) } +/// Add additional context to errors +pub(crate) trait Context { + fn 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 0000000..54d04f8 --- /dev/null +++ b/tests/verbose_errors.rs @@ -0,0 +1,16 @@ +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) + ), + } + }) +}