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}