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