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