use globset::{Glob, GlobSetBuilder};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GlobErrorPolicy {
OverRecall,
Drop,
}
impl GlobErrorPolicy {
#[inline]
const fn verdict(self) -> bool {
match self {
Self::OverRecall => true,
Self::Drop => false,
}
}
}
pub fn glob_match(patterns_json: Option<&str>, path: &str, on_error: GlobErrorPolicy) -> bool {
let Some(raw) = patterns_json.map(str::trim).filter(|s| !s.is_empty()) else {
return true;
};
let patterns: Vec<String> = match serde_json::from_str(raw) {
Ok(v) => v,
Err(_) => return on_error.verdict(),
};
if patterns.is_empty() {
return true;
}
let mut builder = GlobSetBuilder::new();
let mut added = false;
for pattern in &patterns {
if let Ok(glob) = Glob::new(pattern.trim()) {
builder.add(glob);
added = true;
}
}
if !added {
return on_error.verdict();
}
let Ok(set) = builder.build() else {
return on_error.verdict();
};
let normalised = path.trim_start_matches('/').replace('\\', "/");
set.is_match(&normalised)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn absent_or_empty_is_universal_under_either_policy() {
for policy in [GlobErrorPolicy::OverRecall, GlobErrorPolicy::Drop] {
assert!(glob_match(None, "src/lib.rs", policy));
assert!(glob_match(Some(""), "src/lib.rs", policy));
assert!(glob_match(Some(" "), "src/lib.rs", policy));
assert!(glob_match(Some("[]"), "src/lib.rs", policy));
}
}
#[test]
fn glob_match_basic_and_path_normalisation() {
for policy in [GlobErrorPolicy::OverRecall, GlobErrorPolicy::Drop] {
assert!(glob_match(
Some(r#"["**/*.rs"]"#),
"tokio/src/io/uring.rs",
policy
));
assert!(!glob_match(
Some(r#"["**/*.rs"]"#),
".github/workflows/ci.yml",
policy
));
assert!(glob_match(
Some(r#"["tokio/src/io/**"]"#),
"tokio/src/io/uring.rs",
policy
));
assert!(!glob_match(
Some(r#"["tokio/src/io/**"]"#),
"tokio/src/runtime/mod.rs",
policy
));
assert!(glob_match(
Some(r#"["tokio/src/io/**"]"#),
"tokio\\src\\io\\uring.rs",
policy
));
assert!(glob_match(
Some(r#"["tokio/src/io/**"]"#),
"/tokio/src/io/uring.rs",
policy
));
}
}
#[test]
fn malformed_blob_follows_policy() {
assert!(glob_match(
Some("not-json"),
"any/path.rs",
GlobErrorPolicy::OverRecall
));
assert!(!glob_match(
Some("not-json"),
"any/path.rs",
GlobErrorPolicy::Drop
));
assert!(glob_match(
Some("{}"),
"any/path.rs",
GlobErrorPolicy::OverRecall
));
assert!(!glob_match(
Some("{}"),
"any/path.rs",
GlobErrorPolicy::Drop
));
}
}