Skip to content

Commit 66128a7

Browse files
committed
add support for user provided closure to receive nonfatal errors
1 parent 7c58cf0 commit 66128a7

File tree

5 files changed

+243
-104
lines changed

5 files changed

+243
-104
lines changed

src/checker.rs

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::finder::Checker;
2+
use crate::{NonFatalError, NonFatalErrorHandler};
23
use std::fs;
34
use std::path::Path;
45

@@ -12,16 +13,32 @@ impl ExecutableChecker {
1213

1314
impl Checker for ExecutableChecker {
1415
#[cfg(any(unix, target_os = "wasi", target_os = "redox"))]
15-
fn is_valid(&self, path: &Path) -> bool {
16+
fn is_valid<F: NonFatalErrorHandler>(
17+
&self,
18+
path: &Path,
19+
nonfatal_error_handler: &mut F,
20+
) -> bool {
21+
use std::io;
22+
1623
use rustix::fs as rfs;
17-
let ret = rfs::access(path, rfs::Access::EXEC_OK).is_ok();
24+
let ret = rfs::access(path, rfs::Access::EXEC_OK)
25+
.map_err(|e| {
26+
nonfatal_error_handler.handle(NonFatalError::Io(io::Error::from_raw_os_error(
27+
e.raw_os_error(),
28+
)))
29+
})
30+
.is_ok();
1831
#[cfg(feature = "tracing")]
1932
tracing::trace!("{} EXEC_OK = {ret}", path.display());
2033
ret
2134
}
2235

2336
#[cfg(windows)]
24-
fn is_valid(&self, _path: &Path) -> bool {
37+
fn is_valid<F: NonFatalErrorHandler>(
38+
&self,
39+
_path: &Path,
40+
_nonfatal_error_handler: &mut F,
41+
) -> bool {
2542
true
2643
}
2744
}
@@ -36,7 +53,11 @@ impl ExistedChecker {
3653

3754
impl Checker for ExistedChecker {
3855
#[cfg(target_os = "windows")]
39-
fn is_valid(&self, path: &Path) -> bool {
56+
fn is_valid<F: NonFatalErrorHandler>(
57+
&self,
58+
path: &Path,
59+
nonfatal_error_handler: &mut F,
60+
) -> bool {
4061
let ret = fs::symlink_metadata(path)
4162
.map(|metadata| {
4263
let file_type = metadata.file_type();
@@ -49,8 +70,11 @@ impl Checker for ExistedChecker {
4970
);
5071
file_type.is_file() || file_type.is_symlink()
5172
})
73+
.map_err(|e| {
74+
nonfatal_error_handler.handle(NonFatalError::Io(e));
75+
})
5276
.unwrap_or(false)
53-
&& (path.extension().is_some() || matches_arch(path));
77+
&& (path.extension().is_some() || matches_arch(path, nonfatal_error_handler));
5478
#[cfg(feature = "tracing")]
5579
tracing::trace!(
5680
"{} has_extension = {}, ExistedChecker::is_valid() = {ret}",
@@ -61,43 +85,63 @@ impl Checker for ExistedChecker {
6185
}
6286

6387
#[cfg(not(target_os = "windows"))]
64-
fn is_valid(&self, path: &Path) -> bool {
65-
let ret = fs::metadata(path)
66-
.map(|metadata| metadata.is_file())
67-
.unwrap_or(false);
88+
fn is_valid<F: NonFatalErrorHandler>(
89+
&self,
90+
path: &Path,
91+
nonfatal_error_handler: &mut F,
92+
) -> bool {
93+
let ret = fs::metadata(path).map(|metadata| metadata.is_file());
6894
#[cfg(feature = "tracing")]
69-
tracing::trace!("{} is_file() = {ret}", path.display());
70-
ret
95+
tracing::trace!("{} is_file() = {ret:?}", path.display());
96+
match ret {
97+
Ok(ret) => ret,
98+
Err(e) => {
99+
nonfatal_error_handler.handle(NonFatalError::Io(e));
100+
false
101+
}
102+
}
71103
}
72104
}
73105

74106
#[cfg(target_os = "windows")]
75-
fn matches_arch(path: &Path) -> bool {
76-
let ret = winsafe::GetBinaryType(&path.display().to_string()).is_ok();
107+
fn matches_arch<F: NonFatalErrorHandler>(path: &Path, nonfatal_error_handler: &mut F) -> bool {
108+
use std::io;
109+
110+
let ret = winsafe::GetBinaryType(&path.display().to_string())
111+
.map_err(|e| {
112+
nonfatal_error_handler.handle(NonFatalError::Io(io::Error::from_raw_os_error(
113+
e.raw() as i32
114+
)))
115+
})
116+
.is_ok();
77117
#[cfg(feature = "tracing")]
78118
tracing::trace!("{} matches_arch() = {ret}", path.display());
79119
ret
80120
}
81121

82122
pub struct CompositeChecker {
83-
checkers: Vec<Box<dyn Checker>>,
123+
existed_checker: ExistedChecker,
124+
executable_checker: ExecutableChecker,
84125
}
85126

86127
impl CompositeChecker {
87128
pub fn new() -> CompositeChecker {
88129
CompositeChecker {
89-
checkers: Vec::new(),
130+
executable_checker: ExecutableChecker::new(),
131+
existed_checker: ExistedChecker::new(),
90132
}
91133
}
92-
93-
pub fn add_checker(mut self, checker: Box<dyn Checker>) -> CompositeChecker {
94-
self.checkers.push(checker);
95-
self
96-
}
97134
}
98135

99136
impl Checker for CompositeChecker {
100-
fn is_valid(&self, path: &Path) -> bool {
101-
self.checkers.iter().all(|checker| checker.is_valid(path))
137+
fn is_valid<F: NonFatalErrorHandler>(
138+
&self,
139+
path: &Path,
140+
nonfatal_error_handler: &mut F,
141+
) -> bool {
142+
self.existed_checker.is_valid(path, nonfatal_error_handler)
143+
&& self
144+
.executable_checker
145+
.is_valid(path, nonfatal_error_handler)
102146
}
103147
}

src/error.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::fmt;
1+
use std::{fmt, io};
22

33
pub type Result<T> = std::result::Result<T, Error>;
44

@@ -26,3 +26,19 @@ impl fmt::Display for Error {
2626
}
2727
}
2828
}
29+
30+
#[derive(Debug)]
31+
#[non_exhaustive]
32+
pub enum NonFatalError {
33+
Io(io::Error),
34+
}
35+
36+
impl std::error::Error for NonFatalError {}
37+
38+
impl fmt::Display for NonFatalError {
39+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40+
match self {
41+
Self::Io(e) => write!(f, "{e}"),
42+
}
43+
}
44+
}

src/finder.rs

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::checker::CompositeChecker;
2-
use crate::error::*;
32
#[cfg(windows)]
43
use crate::helper::has_executable_extension;
4+
use crate::{error::*, NonFatalErrorHandler};
55
use either::Either;
66
#[cfg(feature = "regex")]
77
use regex::Regex;
@@ -25,7 +25,11 @@ fn home_dir() -> Option<std::path::PathBuf> {
2525
}
2626

2727
pub trait Checker {
28-
fn is_valid(&self, path: &Path) -> bool;
28+
fn is_valid<F: NonFatalErrorHandler>(
29+
&self,
30+
path: &Path,
31+
nonfatal_error_handler: &mut F,
32+
) -> bool;
2933
}
3034

3135
trait PathExt {
@@ -62,17 +66,18 @@ impl Finder {
6266
Finder
6367
}
6468

65-
pub fn find<T, U, V>(
69+
pub fn find<'a, T, U, V, F: NonFatalErrorHandler + 'a>(
6670
&self,
6771
binary_name: T,
6872
paths: Option<U>,
6973
cwd: Option<V>,
7074
binary_checker: CompositeChecker,
71-
) -> Result<impl Iterator<Item = PathBuf>>
75+
mut nonfatal_error_handler: F,
76+
) -> Result<impl Iterator<Item = PathBuf> + 'a>
7277
where
7378
T: AsRef<OsStr>,
7479
U: AsRef<OsStr>,
75-
V: AsRef<Path>,
80+
V: AsRef<Path> + 'a,
7681
{
7782
let path = PathBuf::from(&binary_name);
7883

@@ -92,39 +97,40 @@ impl Finder {
9297
path.display()
9398
);
9499
// Search binary in cwd if the path have a path separator.
95-
Either::Left(Self::cwd_search_candidates(path, cwd).into_iter())
100+
Either::Left(Self::cwd_search_candidates(path, cwd))
96101
}
97102
_ => {
98103
#[cfg(feature = "tracing")]
99104
tracing::trace!("{} has no path seperators, so only paths in PATH environment variable will be searched.", path.display());
100105
// Search binary in PATHs(defined in environment variable).
101-
let paths =
102-
env::split_paths(&paths.ok_or(Error::CannotGetCurrentDirAndPathListEmpty)?)
103-
.collect::<Vec<_>>();
106+
let paths = paths.ok_or(Error::CannotGetCurrentDirAndPathListEmpty)?;
107+
let paths = env::split_paths(&paths).collect::<Vec<_>>();
104108
if paths.is_empty() {
105109
return Err(Error::CannotGetCurrentDirAndPathListEmpty);
106110
}
107111

108-
Either::Right(Self::path_search_candidates(path, paths).into_iter())
112+
Either::Right(Self::path_search_candidates(path, paths))
109113
}
110114
};
111-
let ret = binary_path_candidates
112-
.filter(move |p| binary_checker.is_valid(p))
113-
.map(correct_casing);
115+
let ret = binary_path_candidates.into_iter().filter_map(move |p| {
116+
binary_checker
117+
.is_valid(&p, &mut nonfatal_error_handler)
118+
.then(|| correct_casing(p, &mut nonfatal_error_handler))
119+
});
114120
#[cfg(feature = "tracing")]
115-
let ret = ret.map(|p| {
121+
let ret = ret.inspect(|p| {
116122
tracing::debug!("found path {}", p.display());
117-
p
118123
});
119124
Ok(ret)
120125
}
121126

122127
#[cfg(feature = "regex")]
123-
pub fn find_re<T>(
128+
pub fn find_re<T, F: NonFatalErrorHandler>(
124129
&self,
125130
binary_regex: impl Borrow<Regex>,
126131
paths: Option<T>,
127132
binary_checker: CompositeChecker,
133+
mut nonfatal_error_handler: F,
128134
) -> Result<impl Iterator<Item = PathBuf>>
129135
where
130136
T: AsRef<OsStr>,
@@ -148,7 +154,7 @@ impl Finder {
148154
false
149155
}
150156
})
151-
.filter(move |p| binary_checker.is_valid(p));
157+
.filter(move |p| binary_checker.is_valid(p, &mut nonfatal_error_handler));
152158

153159
Ok(matching_re)
154160
}
@@ -277,14 +283,24 @@ fn tilde_expansion(p: &PathBuf) -> Cow<'_, PathBuf> {
277283
}
278284

279285
#[cfg(target_os = "windows")]
280-
fn correct_casing(mut p: PathBuf) -> PathBuf {
286+
fn correct_casing<F: NonFatalErrorHandler>(
287+
mut p: PathBuf,
288+
nonfatal_error_handler: &mut F,
289+
) -> PathBuf {
281290
if let (Some(parent), Some(file_name)) = (p.parent(), p.file_name()) {
282291
if let Ok(iter) = fs::read_dir(parent) {
283-
for e in iter.filter_map(std::result::Result::ok) {
284-
if e.file_name().eq_ignore_ascii_case(file_name) {
285-
p.pop();
286-
p.push(e.file_name());
287-
break;
292+
for e in iter {
293+
match e {
294+
Ok(e) => {
295+
if e.file_name().eq_ignore_ascii_case(file_name) {
296+
p.pop();
297+
p.push(e.file_name());
298+
break;
299+
}
300+
}
301+
Err(e) => {
302+
nonfatal_error_handler.handle(NonFatalError::Io(e));
303+
}
288304
}
289305
}
290306
}
@@ -293,6 +309,6 @@ fn correct_casing(mut p: PathBuf) -> PathBuf {
293309
}
294310

295311
#[cfg(not(target_os = "windows"))]
296-
fn correct_casing(p: PathBuf) -> PathBuf {
312+
fn correct_casing<F: NonFatalErrorHandler>(p: PathBuf, _nonfatal_error_handler: &mut F) -> PathBuf {
297313
p
298314
}

0 commit comments

Comments
 (0)