diff --git a/Cargo.toml b/Cargo.toml index 4073b56..f4c7569 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-walkdir" -version = "1.0.0" +version = "1.1.0" authors = ["Ririsoft "] edition = "2021" description = "Asynchronous directory traversal for Rust." @@ -18,4 +18,4 @@ futures-lite = "2.1.0" async-fs = "2.1.0" [dev-dependencies] -tempfile = "3.9.0" \ No newline at end of file +tempfile = "3.9.0" diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..75f6eaa --- /dev/null +++ b/src/error.rs @@ -0,0 +1,56 @@ +use std::{ + error, fmt, io, ops::Deref, path::{Path, PathBuf} +}; + +/// A wrapper around [`io::Error`] that includes the associated path. +#[derive(Debug)] +pub struct Error { + path: PathBuf, + inner: io::Error, +} + +impl Error { + /// Create a new [`Error`] + pub fn new(path: PathBuf, inner: io::Error) -> Self { + Error { path, inner } + } + + /// Returns the path associated with this error. + pub fn path(&self) -> &Path { + &self.path + } +} + +impl Deref for Error { + type Target = io::Error; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl error::Error for Error { + #[allow(deprecated)] + fn description(&self) -> &str { + self.inner.description() + } + + fn cause(&self) -> Option<&dyn error::Error> { + self.source() + } + + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + Some(&self.inner) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "IO error for operation on {}: {}", + self.path.display(), + self.inner + ) + } +} diff --git a/src/lib.rs b/src/lib.rs index 6c42cf9..9f1cb7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,8 @@ #![forbid(unsafe_code)] #![deny(missing_docs)] +mod error; + use std::future::Future; use std::path::{Path, PathBuf}; use std::pin::Pin; @@ -90,8 +92,11 @@ use futures_lite::stream::{self, Stream, StreamExt}; #[doc(no_inline)] pub use async_fs::DirEntry; -#[doc(no_inline)] -pub use std::io::Result; + +pub use error::Error; + +/// A specialized [`Result`] type. +pub type Result = std::result::Result; type BoxStream = futures_lite::stream::Boxed>; @@ -165,9 +170,9 @@ where State::Start((root.as_ref().to_owned(), filter)), move |state| async move { match state { - State::Start((root, filter)) => match read_dir(root).await { - Err(e) => Some((Err(e), State::Done)), - Ok(rd) => walk(vec![rd], filter).await, + State::Start((root, filter)) => match read_dir(&root).await { + Err(e) => Some((Err(Error::new(root, e)), State::Done)), + Ok(rd) => walk(vec![(root, rd)], filter).await, }, State::Walk((dirs, filter)) => walk(dirs, filter).await, State::Done => None, @@ -179,22 +184,28 @@ where enum State { Start((PathBuf, Option)), - Walk((Vec, Option)), + Walk((Vec<(PathBuf, ReadDir)>, Option)), Done, } type UnfoldState = (Result, State); -fn walk(mut dirs: Vec, filter: Option) -> BoxedFut>> +fn walk( + mut dirs: Vec<(PathBuf, ReadDir)>, + filter: Option, +) -> BoxedFut>> where F: FnMut(DirEntry) -> Fut + Send + 'static, Fut: Future + Send, { async move { - if let Some(dir) = dirs.last_mut() { + if let Some((path, dir)) = dirs.last_mut() { match dir.next().await { Some(Ok(entry)) => walk_entry(entry, dirs, filter).await, - Some(Err(e)) => Some((Err(e), State::Walk((dirs, filter)))), + Some(Err(e)) => Some(( + Err(Error::new(path.to_path_buf(), e)), + State::Walk((dirs, filter)), + )), None => { dirs.pop(); walk(dirs, filter).await @@ -209,7 +220,7 @@ where fn walk_entry( entry: DirEntry, - mut dirs: Vec, + mut dirs: Vec<(PathBuf, ReadDir)>, mut filter: Option, ) -> BoxedFut>> where @@ -218,19 +229,25 @@ where { async move { match entry.file_type().await { - Err(e) => Some((Err(e), State::Walk((dirs, filter)))), + Err(e) => Some(( + Err(Error::new(entry.path(), e)), + State::Walk((dirs, filter)), + )), Ok(ft) => { let filtering = match filter.as_mut() { Some(filter) => filter(entry.clone()).await, None => Filtering::Continue, }; if ft.is_dir() { - let rd = match read_dir(entry.path()).await { - Err(e) => return Some((Err(e), State::Walk((dirs, filter)))), + let path = entry.path(); + let rd = match read_dir(&path).await { + Err(e) => { + return Some((Err(Error::new(path, e)), State::Walk((dirs, filter)))) + } Ok(rd) => rd, }; if filtering != Filtering::IgnoreDir { - dirs.push(rd); + dirs.push((path, rd)); } } match filtering {