cc_audit/engine/scanners/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::engine::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 #[allow(dead_code)]
30 impl $scanner {
31 /// Creates a new scanner with default configuration.
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 pub fn with_skip_comments(mut self, skip: bool) -> Self {
40 self.config = self.config.with_skip_comments(skip);
41 self
42 }
43
44 /// Adds dynamic rules loaded from custom YAML files.
45 pub fn with_dynamic_rules(mut self, rules: Vec<$crate::rules::DynamicRule>) -> Self {
46 self.config = self.config.with_dynamic_rules(rules);
47 self
48 }
49
50 /// Enables or disables strict secrets mode.
51 /// When enabled, dummy key heuristics are disabled for test files.
52 pub fn with_strict_secrets(mut self, strict: bool) -> Self {
53 self.config = self.config.with_strict_secrets(strict);
54 self
55 }
56
57 /// Enables or disables recursive scanning.
58 /// When disabled, only scans the immediate directory (max_depth = 1).
59 pub fn with_recursive(mut self, recursive: bool) -> Self {
60 self.config = self.config.with_recursive(recursive);
61 self
62 }
63
64 /// Overrides the maximum size (in bytes) of a file that will be read
65 /// into memory. Files above the cap are refused before allocation.
66 pub fn with_max_file_size(mut self, max_file_size: u64) -> Self {
67 self.config = self.config.with_max_file_size(max_file_size);
68 self
69 }
70
71 /// Sets a progress callback that will be called for each scanned file.
72 pub fn with_progress_callback(
73 mut self,
74 callback: $crate::engine::scanner::ProgressCallback,
75 ) -> Self {
76 self.config = self.config.with_progress_callback(callback);
77 self
78 }
79 }
80
81 impl Default for $scanner {
82 fn default() -> Self {
83 Self::new()
84 }
85 }
86 };
87}
88
89/// Implements the ContentScanner trait for scanners that use default content scanning.
90///
91/// This macro generates a ContentScanner implementation that delegates to ScannerConfig.
92/// Use this for scanners that don't need custom content processing.
93///
94/// # Example
95///
96/// ```ignore
97/// use crate::engine::scanner::{ContentScanner, ScannerConfig};
98/// use crate::{impl_scanner_builder, impl_content_scanner};
99///
100/// pub struct MyScanner {
101/// config: ScannerConfig,
102/// }
103///
104/// impl_scanner_builder!(MyScanner);
105/// impl_content_scanner!(MyScanner);
106/// ```
107#[macro_export]
108macro_rules! impl_content_scanner {
109 ($scanner:ty) => {
110 impl $crate::engine::scanner::ContentScanner for $scanner {
111 fn config(&self) -> &$crate::engine::scanner::ScannerConfig {
112 &self.config
113 }
114 }
115 };
116}
117
118/// Implements a simple Scanner trait for file-based scanners.
119///
120/// This macro generates a Scanner implementation that:
121/// - Reads file content and delegates to check_content for scan_file
122/// - Iterates over a directory pattern for scan_directory
123///
124/// # Arguments
125///
126/// - `$scanner` - The scanner type
127/// - `$pattern` - A closure that returns file patterns to check in scan_directory
128///
129/// # Example
130///
131/// ```ignore
132/// impl_simple_scanner!(MyScanner, |dir| vec![
133/// dir.join("config.json"),
134/// dir.join(".config.json"),
135/// ]);
136/// ```
137#[macro_export]
138macro_rules! impl_simple_file_scanner {
139 ($scanner:ty, $file_patterns:expr) => {
140 impl $crate::scanner::Scanner for $scanner {
141 fn scan_file(
142 &self,
143 path: &std::path::Path,
144 ) -> $crate::error::Result<Vec<$crate::rules::Finding>> {
145 let content = self.config.read_file(path)?;
146 let path_str = path.display().to_string();
147 Ok(self.config.check_content(&content, &path_str))
148 }
149
150 fn scan_directory(
151 &self,
152 dir: &std::path::Path,
153 ) -> $crate::error::Result<Vec<$crate::rules::Finding>> {
154 let mut findings = Vec::new();
155 let patterns_fn: fn(&std::path::Path) -> Vec<std::path::PathBuf> = $file_patterns;
156 let patterns = patterns_fn(dir);
157
158 for pattern in patterns {
159 if pattern.exists() {
160 findings.extend(self.scan_file(&pattern)?);
161 }
162 }
163
164 Ok(findings)
165 }
166 }
167 };
168}
169
170#[cfg(test)]
171mod tests {
172 use crate::engine::scanner::ScannerConfig;
173
174 // Test struct for macro testing
175 pub struct TestScanner {
176 config: ScannerConfig,
177 }
178
179 impl_scanner_builder!(TestScanner);
180
181 #[test]
182 fn test_new_scanner() {
183 let scanner = TestScanner::new();
184 assert!(!scanner.config.skip_comments());
185 }
186
187 #[test]
188 fn test_with_skip_comments() {
189 let scanner = TestScanner::new().with_skip_comments(true);
190 assert!(scanner.config.skip_comments());
191 }
192
193 #[test]
194 fn test_with_dynamic_rules() {
195 let scanner = TestScanner::new().with_dynamic_rules(vec![]);
196 // Just verify it compiles and runs
197 assert!(!scanner.config.skip_comments());
198 }
199
200 #[test]
201 fn test_with_strict_secrets() {
202 let scanner = TestScanner::new().with_strict_secrets(true);
203 assert!(scanner.config.strict_secrets());
204 }
205
206 #[test]
207 fn test_default_trait() {
208 let scanner = TestScanner::default();
209 assert!(!scanner.config.skip_comments());
210 }
211
212 // Test ContentScanner macro
213 #[allow(dead_code)]
214 pub struct TestContentScanner {
215 config: ScannerConfig,
216 }
217
218 impl_scanner_builder!(TestContentScanner);
219
220 // Scanner trait is required for ContentScanner
221 impl crate::scanner::Scanner for TestContentScanner {
222 fn scan_file(
223 &self,
224 _path: &std::path::Path,
225 ) -> crate::error::Result<Vec<crate::rules::Finding>> {
226 Ok(vec![])
227 }
228
229 fn scan_directory(
230 &self,
231 _dir: &std::path::Path,
232 ) -> crate::error::Result<Vec<crate::rules::Finding>> {
233 Ok(vec![])
234 }
235 }
236
237 impl_content_scanner!(TestContentScanner);
238
239 #[test]
240 fn test_content_scanner_config_access() {
241 use crate::engine::scanner::ContentScanner;
242 let scanner = TestContentScanner::new();
243 let _config = scanner.config();
244 // Just verify it compiles
245 }
246}