use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct VerifyConfig {
#[serde(default)]
pub ignore_ephemeral: Vec<String>,
}
impl VerifyConfig {
fn patterns(&self) -> Vec<glob::Pattern> {
self.ignore_ephemeral
.iter()
.filter_map(|pattern| glob::Pattern::new(pattern).ok())
.collect()
}
pub(crate) fn partition_ephemeral(&self, paths: Vec<String>, base_dir: &std::path::Path) -> (Vec<String>, usize) {
let patterns = self.patterns();
if patterns.is_empty() {
return (paths, 0);
}
let mut kept = Vec::with_capacity(paths.len());
let mut excluded = 0usize;
for path in paths {
let relative = std::path::Path::new(&path)
.strip_prefix(base_dir)
.unwrap_or(std::path::Path::new(&path));
if patterns.iter().any(|pattern| pattern.matches_path(relative)) {
excluded += 1;
} else {
kept.push(path);
}
}
(kept, excluded)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_to_no_patterns_and_excludes_nothing() {
let config = VerifyConfig::default();
let (kept, excluded) = config.partition_ephemeral(
vec!["/repo/test_apps/python/conftest.py".to_string()],
std::path::Path::new("/repo"),
);
assert_eq!(kept, vec!["/repo/test_apps/python/conftest.py".to_string()]);
assert_eq!(excluded, 0);
}
#[test]
fn a_matching_glob_excludes_every_path_under_it_and_reports_the_count() {
let config = VerifyConfig {
ignore_ephemeral: vec!["test_apps/**".to_string()],
};
let paths = vec![
"/repo/test_apps/python/conftest.py".to_string(),
"/repo/test_apps/python/pyproject.toml".to_string(),
"/repo/packages/python/lib.rs".to_string(),
];
let (kept, excluded) = config.partition_ephemeral(paths, std::path::Path::new("/repo"));
assert_eq!(kept, vec!["/repo/packages/python/lib.rs".to_string()]);
assert_eq!(excluded, 2);
}
#[test]
fn a_malformed_pattern_is_dropped_without_failing_the_others() {
let config = VerifyConfig {
ignore_ephemeral: vec!["[".to_string(), "test_apps/**".to_string()],
};
let (kept, excluded) = config.partition_ephemeral(
vec!["/repo/test_apps/python/conftest.py".to_string()],
std::path::Path::new("/repo"),
);
assert!(kept.is_empty());
assert_eq!(excluded, 1);
}
#[test]
fn does_not_match_a_sibling_directory_with_a_shared_prefix() {
let config = VerifyConfig {
ignore_ephemeral: vec!["test_apps/**".to_string()],
};
let (kept, excluded) = config.partition_ephemeral(
vec!["/repo/test_apps_backup/README.md".to_string()],
std::path::Path::new("/repo"),
);
assert_eq!(kept, vec!["/repo/test_apps_backup/README.md".to_string()]);
assert_eq!(excluded, 0);
}
}