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