Skip to main content

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}