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            /// 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}