Skip to main content

cc_audit/scanner/
macros.rs

1//! Scanner builder macros for reducing boilerplate.
2//!
3//! This module provides macros that generate common scanner builder methods,
4//! reducing code duplication across scanner implementations.
5
6/// Implements common scanner builder methods for structs with a `config: ScannerConfig` field.
7///
8/// This macro generates:
9/// - `new()` - Creates a new scanner with default ScannerConfig
10/// - `with_skip_comments(self, skip: bool)` - Builder method for skip_comments setting
11/// - `with_dynamic_rules(self, rules: Vec<DynamicRule>)` - Builder method for dynamic rules
12/// - `Default` trait implementation
13///
14/// # Example
15///
16/// ```ignore
17/// use crate::scanner::ScannerConfig;
18/// use crate::impl_scanner_builder;
19///
20/// pub struct MyScanner {
21///     config: ScannerConfig,
22/// }
23///
24/// impl_scanner_builder!(MyScanner);
25/// ```
26#[macro_export]
27macro_rules! impl_scanner_builder {
28    ($scanner:ty) => {
29        impl $scanner {
30            /// Creates a new scanner with default configuration.
31            #[allow(dead_code)]
32            pub fn new() -> Self {
33                Self {
34                    config: $crate::scanner::ScannerConfig::new(),
35                }
36            }
37
38            /// Enables or disables comment skipping during scanning.
39            #[allow(dead_code)]
40            pub fn with_skip_comments(mut self, skip: bool) -> Self {
41                self.config = self.config.with_skip_comments(skip);
42                self
43            }
44
45            /// Adds dynamic rules loaded from custom YAML files.
46            #[allow(dead_code)]
47            pub fn with_dynamic_rules(mut self, rules: Vec<$crate::rules::DynamicRule>) -> Self {
48                self.config = self.config.with_dynamic_rules(rules);
49                self
50            }
51        }
52
53        impl Default for $scanner {
54            fn default() -> Self {
55                Self::new()
56            }
57        }
58    };
59}
60
61/// Implements the ContentScanner trait for scanners that use default content scanning.
62///
63/// This macro generates a ContentScanner implementation that delegates to ScannerConfig.
64/// Use this for scanners that don't need custom content processing.
65///
66/// # Example
67///
68/// ```ignore
69/// use crate::scanner::{ContentScanner, ScannerConfig};
70/// use crate::{impl_scanner_builder, impl_content_scanner};
71///
72/// pub struct MyScanner {
73///     config: ScannerConfig,
74/// }
75///
76/// impl_scanner_builder!(MyScanner);
77/// impl_content_scanner!(MyScanner);
78/// ```
79#[macro_export]
80macro_rules! impl_content_scanner {
81    ($scanner:ty) => {
82        impl $crate::scanner::ContentScanner for $scanner {
83            fn config(&self) -> &$crate::scanner::ScannerConfig {
84                &self.config
85            }
86        }
87    };
88}
89
90/// Implements a simple Scanner trait for file-based scanners.
91///
92/// This macro generates a Scanner implementation that:
93/// - Reads file content and delegates to check_content for scan_file
94/// - Iterates over a directory pattern for scan_directory
95///
96/// # Arguments
97///
98/// - `$scanner` - The scanner type
99/// - `$pattern` - A closure that returns file patterns to check in scan_directory
100///
101/// # Example
102///
103/// ```ignore
104/// impl_simple_scanner!(MyScanner, |dir| vec![
105///     dir.join("config.json"),
106///     dir.join(".config.json"),
107/// ]);
108/// ```
109#[macro_export]
110macro_rules! impl_simple_file_scanner {
111    ($scanner:ty, $file_patterns:expr) => {
112        impl $crate::scanner::Scanner for $scanner {
113            fn scan_file(
114                &self,
115                path: &std::path::Path,
116            ) -> $crate::error::Result<Vec<$crate::rules::Finding>> {
117                let content = self.config.read_file(path)?;
118                let path_str = path.display().to_string();
119                Ok(self.config.check_content(&content, &path_str))
120            }
121
122            fn scan_directory(
123                &self,
124                dir: &std::path::Path,
125            ) -> $crate::error::Result<Vec<$crate::rules::Finding>> {
126                let mut findings = Vec::new();
127                let patterns_fn: fn(&std::path::Path) -> Vec<std::path::PathBuf> = $file_patterns;
128                let patterns = patterns_fn(dir);
129
130                for pattern in patterns {
131                    if pattern.exists() {
132                        findings.extend(self.scan_file(&pattern)?);
133                    }
134                }
135
136                Ok(findings)
137            }
138        }
139    };
140}
141
142#[cfg(test)]
143mod tests {
144    use crate::scanner::ScannerConfig;
145
146    // Test struct for macro testing
147    pub struct TestScanner {
148        config: ScannerConfig,
149    }
150
151    impl_scanner_builder!(TestScanner);
152
153    #[test]
154    fn test_new_scanner() {
155        let scanner = TestScanner::new();
156        assert!(!scanner.config.skip_comments());
157    }
158
159    #[test]
160    fn test_with_skip_comments() {
161        let scanner = TestScanner::new().with_skip_comments(true);
162        assert!(scanner.config.skip_comments());
163    }
164
165    #[test]
166    fn test_with_dynamic_rules() {
167        let scanner = TestScanner::new().with_dynamic_rules(vec![]);
168        // Just verify it compiles and runs
169        assert!(!scanner.config.skip_comments());
170    }
171
172    #[test]
173    fn test_default_trait() {
174        let scanner = TestScanner::default();
175        assert!(!scanner.config.skip_comments());
176    }
177
178    // Test ContentScanner macro
179    #[allow(dead_code)]
180    pub struct TestContentScanner {
181        config: ScannerConfig,
182    }
183
184    impl_scanner_builder!(TestContentScanner);
185
186    // Scanner trait is required for ContentScanner
187    impl crate::scanner::Scanner for TestContentScanner {
188        fn scan_file(
189            &self,
190            _path: &std::path::Path,
191        ) -> crate::error::Result<Vec<crate::rules::Finding>> {
192            Ok(vec![])
193        }
194
195        fn scan_directory(
196            &self,
197            _dir: &std::path::Path,
198        ) -> crate::error::Result<Vec<crate::rules::Finding>> {
199            Ok(vec![])
200        }
201    }
202
203    impl_content_scanner!(TestContentScanner);
204
205    #[test]
206    fn test_content_scanner_config_access() {
207        use crate::scanner::ContentScanner;
208        let scanner = TestContentScanner::new();
209        let _config = scanner.config();
210        // Just verify it compiles
211    }
212}