codeowners_validation/validators/
validator.rs

1use 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}