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                "all" => {
22                    args.exists = true;
23                    args.duplicate_patterns = true;
24                }
25                _ => (),
26            }
27        }
28
29        args
30    }
31
32    pub fn should_run_all(&self) -> bool {
33        !self.exists && !self.duplicate_patterns
34    }
35}
36
37type ValidatorFn = fn(&[CodeOwnerRule]) -> Vec<CodeOwnerRule>;
38
39pub fn run_validator(
40    args: &ValidatorArgs,
41    rules: &[CodeOwnerRule],
42) -> Vec<(String, CodeOwnerRule)> {
43    let mut failed_rules = Vec::new();
44
45    let validators: Vec<(&str, ValidatorFn)> = vec![
46        ("exists", |rules| {
47            let repo_dir = Path::new(".");
48            match validate_directory(repo_dir, rules) {
49                Ok(result) => result,
50                Err(err) => {
51                    eprintln!("❌ Error during 'exists' validation: {}", err);
52                    Vec::new()
53                }
54            }
55        }),
56        ("duplicate_patterns", validate_duplicates),
57    ];
58
59    for (name, validator_fn) in validators {
60        if args.should_run_all()
61            || (name == "exists" && args.exists)
62            || (name == "duplicate_patterns" && args.duplicate_patterns)
63        {
64            let now = time::Instant::now();
65            let results = validator_fn(rules);
66            let num_failures = results.len();
67
68            for rule in results {
69                failed_rules.push((name.to_string(), rule));
70            }
71
72            println!(
73                "✓ {} validation completed in {:?} ({} issues found)",
74                name,
75                now.elapsed(),
76                num_failures
77            );
78        }
79    }
80
81    failed_rules
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use crate::parser::CodeOwnerRule;
88
89    fn rule(pattern: &str, original: &str) -> CodeOwnerRule {
90        CodeOwnerRule {
91            pattern: pattern.trim_matches('/').to_string(),
92            original_path: original.to_string(),
93            owners: vec!["@x".to_string()],
94        }
95    }
96
97    #[test]
98    fn runs_all_by_default() {
99        let rules = vec![
100            rule("missing1.txt", "missing1.txt"),
101            rule("dup.txt", "dup.txt"),
102            rule("dup.txt", "dup.txt"),
103        ];
104        let args = ValidatorArgs::default();
105        let failures = run_validator(&args, &rules);
106        assert!(!failures.is_empty());
107    }
108
109    #[test]
110    fn runs_only_exists_when_enabled() {
111        let rules = vec![rule("notfound.txt", "notfound.txt")];
112        let args = ValidatorArgs {
113            exists: true,
114            duplicate_patterns: false,
115        };
116        let failures = run_validator(&args, &rules);
117        assert_eq!(failures.len(), 1);
118        assert_eq!(failures[0].0, "exists");
119    }
120
121    #[test]
122    fn runs_only_duplicates_when_enabled() {
123        let rules = vec![rule("x.txt", "x.txt"), rule("x.txt", "x.txt")];
124        let args = ValidatorArgs {
125            exists: false,
126            duplicate_patterns: true,
127        };
128        let failures = run_validator(&args, &rules);
129        assert_eq!(failures.len(), 1);
130        assert_eq!(failures[0].0, "duplicate_patterns");
131    }
132
133    #[test]
134    fn from_env_splits_checks() {
135        let args = ValidatorArgs::from_env("exists,duplicate_patterns");
136        assert!(args.exists);
137        assert!(args.duplicate_patterns);
138    }
139
140    #[test]
141    fn from_env_handles_all() {
142        let args = ValidatorArgs::from_env("all");
143        assert!(args.exists);
144        assert!(args.duplicate_patterns);
145    }
146
147    #[test]
148    fn from_env_handles_whitespace() {
149        let args = ValidatorArgs::from_env(" exists , duplicate_patterns ");
150        assert!(args.exists);
151        assert!(args.duplicate_patterns);
152    }
153
154    #[test]
155    fn should_run_all_when_none_specified() {
156        let args = ValidatorArgs::default();
157        assert!(args.should_run_all());
158    }
159
160    #[test]
161    fn not_should_run_all_when_any_specified() {
162        let args = ValidatorArgs {
163            exists: true,
164            duplicate_patterns: false,
165        };
166        assert!(!args.should_run_all());
167    }
168}