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