sshconfig_lint/rules/
mod.rs1pub mod basic;
2
3use crate::model::{Config, Finding};
4
5pub trait Rule {
7 fn name(&self) -> &'static str;
9 fn check(&self, config: &Config) -> Vec<Finding>;
11}
12
13pub fn run_all(config: &Config) -> Vec<Finding> {
15 let rules: Vec<Box<dyn Rule>> = vec![
16 Box::new(basic::DuplicateHost),
17 Box::new(basic::IdentityFileExists),
18 Box::new(basic::WildcardHostOrder),
19 Box::new(basic::DeprecatedWeakAlgorithms),
20 Box::new(basic::DuplicateDirectives),
21 Box::new(basic::InsecureOption),
22 Box::new(basic::UnsafeControlPath),
23 ];
24
25 let mut findings = Vec::new();
26 for rule in &rules {
27 findings.extend(rule.check(config));
28 }
29 findings
30}
31
32#[cfg(test)]
33mod tests {
34 use super::*;
35 use crate::model::{Config, Finding, Item, Severity, Span};
36
37 struct DummyRule;
38 impl Rule for DummyRule {
39 fn name(&self) -> &'static str {
40 "dummy"
41 }
42 fn check(&self, _config: &Config) -> Vec<Finding> {
43 vec![Finding::new(
44 Severity::Info,
45 "dummy",
46 "TEST",
47 "this is a test",
48 Span::new(1),
49 )]
50 }
51 }
52
53 #[test]
54 fn trait_rule_returns_finding() {
55 let config = Config { items: vec![] };
56 let rule = DummyRule;
57 let findings = rule.check(&config);
58 assert_eq!(findings.len(), 1);
59 assert_eq!(findings[0].rule, "dummy");
60 }
61
62 #[test]
63 fn run_all_merges_findings() {
64 let config = Config { items: vec![] };
66 let findings = run_all(&config);
67 assert!(findings.is_empty());
69 }
70
71 #[test]
72 fn run_all_on_config_with_duplicates() {
73 let config = Config {
74 items: vec![
75 Item::HostBlock {
76 patterns: vec!["github.com".to_string()],
77 span: Span::new(1),
78 items: vec![],
79 },
80 Item::HostBlock {
81 patterns: vec!["github.com".to_string()],
82 span: Span::new(5),
83 items: vec![],
84 },
85 ],
86 };
87 let findings = run_all(&config);
88 assert!(findings.iter().any(|f| f.rule == "duplicate-host"));
89 }
90}