codeowners_validation/validators/
validator.rs1use crate::parser::CodeOwnerRule;
2use crate::validators::duplicate_patterns::validate_duplicates;
3use crate::validators::exists::validate_directory;
4use std::path::Path;
5use std::time;
6
7#[derive(Debug, Clone, Default)]
8pub struct ValidatorArgs {
9 pub exists: bool,
10 pub duplicate_patterns: bool,
11}
12
13impl ValidatorArgs {
14 pub fn from_env(args_str: &str) -> Self {
15 let mut args = ValidatorArgs::default();
16
17 for arg in args_str.split(',') {
18 match arg.trim() {
19 "exists" => args.exists = true,
20 "duplicate_patterns" => args.duplicate_patterns = true,
21 _ => (),
22 }
23 }
24
25 args
26 }
27
28 pub fn should_run_all(&self) -> bool {
29 !self.exists && !self.duplicate_patterns
30 }
31}
32
33pub fn run_validator(
34 args: &ValidatorArgs,
35 rules: &[CodeOwnerRule],
36) -> Vec<(String, CodeOwnerRule)> {
37 let mut failed_rules = Vec::new();
38
39 let validators: Vec<(&str, fn(&[CodeOwnerRule]) -> Vec<CodeOwnerRule>)> = vec![
40 ("exists", |rules| {
41 let repo_dir = Path::new(".");
42 match validate_directory(repo_dir, rules) {
43 Ok(result) => result,
44 Err(err) => {
45 eprintln!("❌ Error during 'exists' validation: {}", err);
46 Vec::new()
47 }
48 }
49 }),
50 ("duplicate_patterns", validate_duplicates),
51 ];
52
53 for (name, validator_fn) in validators {
54 if args.should_run_all()
55 || (name == "exists" && args.exists)
56 || (name == "duplicate_patterns" && args.duplicate_patterns)
57 {
58 let now = time::Instant::now();
59 let results = validator_fn(rules);
60 for rule in results {
61 failed_rules.push((name.to_string(), rule));
62 }
63 println!("{} validation run in {:?}", name, now.elapsed());
64 }
65 }
66
67 failed_rules
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use crate::parser::CodeOwnerRule;
74 use globset::Glob;
75
76 fn rule(pattern: &str) -> CodeOwnerRule {
77 CodeOwnerRule {
78 pattern: pattern.to_string(),
79 original_path: pattern.to_string(),
80 owners: vec!["@x".to_string()],
81 glob: Glob::new(&format!("**/{}", pattern)).unwrap(),
82 }
83 }
84
85 #[test]
86 fn runs_all_by_default() {
87 let rules = vec![rule("missing1.txt"), rule("dup.txt"), rule("dup.txt")];
88 let args = ValidatorArgs::default();
89 let failures = run_validator(&args, &rules);
90 assert!(!failures.is_empty());
91 }
92
93 #[test]
94 fn runs_only_exists_when_enabled() {
95 let rules = vec![rule("notfound.txt")];
96 let args = ValidatorArgs {
97 exists: true,
98 duplicate_patterns: false,
99 };
100 let failures = run_validator(&args, &rules);
101 assert_eq!(failures.len(), 1);
102 assert_eq!(failures[0].0, "exists");
103 }
104
105 #[test]
106 fn runs_only_duplicates_when_enabled() {
107 let rules = vec![rule("x.txt"), rule("x.txt")];
108 let args = ValidatorArgs {
109 exists: false,
110 duplicate_patterns: true,
111 };
112 let failures = run_validator(&args, &rules);
113 assert_eq!(failures.len(), 1);
114 assert_eq!(failures[0].0, "duplicate_patterns");
115 }
116
117 #[test]
118 fn from_env_splits_checks() {
119 let args = ValidatorArgs::from_env("exists,duplicate_patterns");
120 assert!(args.exists);
121 assert!(args.duplicate_patterns);
122 }
123
124 #[test]
125 fn should_run_all_when_none_specified() {
126 let args = ValidatorArgs::default();
127 assert!(args.should_run_all());
128 }
129}