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