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