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