-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New error type that includes path information
* Wraps std::io::error & includes the path. * Use thiserror to reduce code maintenance. * Use a wrapped error to maintain API compatibility in further evolutions. --------- Co-authored-by: Ririsoft <[email protected]>
- Loading branch information
Showing
3 changed files
with
125 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
[package] | ||
name = "async-walkdir" | ||
version = "1.0.0" | ||
version = "2.0.0" | ||
authors = ["Ririsoft <[email protected]>"] | ||
edition = "2021" | ||
description = "Asynchronous directory traversal for Rust." | ||
|
@@ -16,6 +16,7 @@ readme = "README.md" | |
[dependencies] | ||
futures-lite = "2.1.0" | ||
async-fs = "2.1.0" | ||
thiserror = "1.0.58" | ||
|
||
[dev-dependencies] | ||
tempfile = "3.9.0" | ||
tempfile = "3.9.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// SPDX-FileCopyrightText: 2020-2024 Ririsoft <[email protected]> | ||
// SPDX-FileCopyrightText: 2024 Jordan Danford <[email protected]> | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
use std::{ | ||
io, | ||
path::{Path, PathBuf}, | ||
}; | ||
|
||
use thiserror::Error; | ||
|
||
#[derive(Debug, Error)] | ||
#[error(transparent)] | ||
/// An error produced during a directory tree traversal. | ||
pub struct Error(#[from] InnerError); | ||
|
||
impl Error { | ||
/// Returns the path where the error occured if it applies, | ||
/// for instance during IO operations. | ||
pub fn path(&self) -> Option<&Path> { | ||
let InnerError::Io { ref path, .. } = self.0; | ||
Some(path) | ||
} | ||
|
||
/// Returns the original [`io::Error`] if any. | ||
pub fn io(&self) -> Option<&io::Error> { | ||
let InnerError::Io { ref source, .. } = self.0; | ||
Some(source) | ||
} | ||
} | ||
|
||
#[derive(Debug, Error)] | ||
pub enum InnerError { | ||
#[error("IO error at '{path}': {source}")] | ||
/// A error produced during an IO operation. | ||
Io { | ||
/// The path in the directory tree where the IO error occured. | ||
path: PathBuf, | ||
/// The IO error. | ||
source: io::Error, | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
// Copyright 2020 Ririsoft <[email protected]> | ||
// Copyright 2024 Jordan Danford <[email protected]> | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
|
@@ -78,6 +79,8 @@ | |
#![forbid(unsafe_code)] | ||
#![deny(missing_docs)] | ||
|
||
mod error; | ||
|
||
use std::future::Future; | ||
use std::path::{Path, PathBuf}; | ||
use std::pin::Pin; | ||
|
@@ -90,8 +93,12 @@ 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; | ||
use error::InnerError; | ||
|
||
/// A specialized [`Result`][`std::result::Result`] type. | ||
pub type Result<T> = std::result::Result<T, Error>; | ||
|
||
type BoxStream = futures_lite::stream::Boxed<Result<DirEntry>>; | ||
|
||
|
@@ -165,9 +172,12 @@ 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(source) => Some(( | ||
Err(InnerError::Io { path: root, source }.into()), | ||
State::Done, | ||
)), | ||
Ok(rd) => walk(vec![(root, rd)], filter).await, | ||
}, | ||
State::Walk((dirs, filter)) => walk(dirs, filter).await, | ||
State::Done => None, | ||
|
@@ -179,22 +189,32 @@ where | |
|
||
enum State<F> { | ||
Start((PathBuf, Option<F>)), | ||
Walk((Vec<ReadDir>, Option<F>)), | ||
Walk((Vec<(PathBuf, ReadDir)>, Option<F>)), | ||
Done, | ||
} | ||
|
||
type UnfoldState<F> = (Result<DirEntry>, State<F>); | ||
|
||
fn walk<F, Fut>(mut dirs: Vec<ReadDir>, filter: Option<F>) -> BoxedFut<Option<UnfoldState<F>>> | ||
fn walk<F, Fut>( | ||
mut dirs: Vec<(PathBuf, ReadDir)>, | ||
filter: Option<F>, | ||
) -> BoxedFut<Option<UnfoldState<F>>> | ||
where | ||
F: FnMut(DirEntry) -> Fut + Send + 'static, | ||
Fut: Future<Output = Filtering> + 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(source)) => Some(( | ||
Err(InnerError::Io { | ||
path: path.to_path_buf(), | ||
source, | ||
} | ||
.into()), | ||
State::Walk((dirs, filter)), | ||
)), | ||
None => { | ||
dirs.pop(); | ||
walk(dirs, filter).await | ||
|
@@ -209,7 +229,7 @@ where | |
|
||
fn walk_entry<F, Fut>( | ||
entry: DirEntry, | ||
mut dirs: Vec<ReadDir>, | ||
mut dirs: Vec<(PathBuf, ReadDir)>, | ||
mut filter: Option<F>, | ||
) -> BoxedFut<Option<UnfoldState<F>>> | ||
where | ||
|
@@ -218,19 +238,32 @@ where | |
{ | ||
async move { | ||
match entry.file_type().await { | ||
Err(e) => Some((Err(e), State::Walk((dirs, filter)))), | ||
Err(source) => Some(( | ||
Err(InnerError::Io { | ||
path: entry.path(), | ||
source, | ||
} | ||
.into()), | ||
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(source) => { | ||
return Some(( | ||
Err(InnerError::Io { path, source }.into()), | ||
State::Walk((dirs, filter)), | ||
)) | ||
} | ||
Ok(rd) => rd, | ||
}; | ||
if filtering != Filtering::IgnoreDir { | ||
dirs.push(rd); | ||
dirs.push((path, rd)); | ||
} | ||
} | ||
match filtering { | ||
|
@@ -267,8 +300,11 @@ mod tests { | |
block_on(async { | ||
let mut wd = WalkDir::new("foobar"); | ||
match wd.next().await.unwrap() { | ||
Ok(_) => panic!("want error"), | ||
Err(e) => assert_eq!(e.kind(), ErrorKind::NotFound), | ||
Err(e) => { | ||
assert_eq!(wd.root, e.path().unwrap()); | ||
assert_eq!(e.io().unwrap().kind(), ErrorKind::NotFound); | ||
} | ||
_ => panic!("want IO error"), | ||
} | ||
}) | ||
} | ||
|
@@ -385,3 +421,31 @@ mod tests { | |
}) | ||
} | ||
} | ||
|
||
#[cfg(all(unix, test))] | ||
mod test_unix { | ||
use async_fs::unix::PermissionsExt; | ||
use std::io::Result; | ||
|
||
use futures_lite::future::block_on; | ||
use futures_lite::stream::StreamExt; | ||
|
||
use super::WalkDir; | ||
#[test] | ||
fn walk_dir_error_path() -> Result<()> { | ||
block_on(async { | ||
let root = tempfile::tempdir()?; | ||
let d1 = root.path().join("d1"); | ||
async_fs::create_dir_all(&d1).await?; | ||
let mut perms = async_fs::metadata(&d1).await?.permissions(); | ||
perms.set_mode(0o222); | ||
async_fs::set_permissions(&d1, perms).await?; | ||
let mut wd = WalkDir::new(&root); | ||
match wd.next().await.unwrap() { | ||
Err(e) => assert_eq!(e.path().unwrap(), d1.as_path()), | ||
_ => panic!("want IO error"), | ||
} | ||
Ok(()) | ||
}) | ||
} | ||
} |