1use crate::anonymizers::{AnonymizerConfig, AnonymizerRegistry};
6use crate::recognizers::{
7 generic::GenericSecretRecognizer, pattern::PatternRecognizer, RecognizerRegistry,
8};
9use crate::types::{AnalysisMetadata, AnalysisResult, AnonymizedResult, EntityType};
10use anyhow::Result;
11use std::sync::Arc;
12use std::time::Instant;
13
14struct Timer {
20 start: Option<Instant>,
21}
22
23impl Timer {
24 fn new() -> Self {
25 Self {
26 #[cfg(not(target_arch = "wasm32"))]
27 start: Some(Instant::now()),
28 #[cfg(target_arch = "wasm32")]
29 start: None,
30 }
31 }
32
33 fn elapsed_ms(&self) -> u64 {
34 self.start
35 .map(|s| s.elapsed().as_millis() as u64)
36 .unwrap_or(0)
37 }
38}
39
40#[derive(Debug, Clone)]
42pub struct AnalyzerEngine {
43 recognizer_registry: RecognizerRegistry,
44 anonymizer_registry: AnonymizerRegistry,
45 default_language: String,
46 model_version: Option<String>,
47}
48
49impl AnalyzerEngine {
50 pub fn new() -> Self {
52 let mut recognizer_registry = RecognizerRegistry::new();
53
54 let pattern_recognizer = Arc::new(PatternRecognizer::new());
56 recognizer_registry.add_recognizer(pattern_recognizer);
57 recognizer_registry.add_recognizer(Arc::new(GenericSecretRecognizer::new()));
58
59 Self {
60 recognizer_registry,
61 anonymizer_registry: AnonymizerRegistry::new(),
62 default_language: "en".to_string(),
63 model_version: None,
64 }
65 }
66
67 pub fn builder() -> AnalyzerEngineBuilder {
69 AnalyzerEngineBuilder::new()
70 }
71
72 pub fn with_language(mut self, language: impl Into<String>) -> Self {
74 self.default_language = language.into();
75 self
76 }
77
78 pub fn with_model_version(mut self, version: impl Into<String>) -> Self {
80 self.model_version = Some(version.into());
81 self
82 }
83
84 pub fn recognizer_registry(&self) -> &RecognizerRegistry {
86 &self.recognizer_registry
87 }
88
89 pub fn recognizer_registry_mut(&mut self) -> &mut RecognizerRegistry {
91 &mut self.recognizer_registry
92 }
93
94 pub fn anonymizer_registry(&self) -> &AnonymizerRegistry {
96 &self.anonymizer_registry
97 }
98
99 pub fn anonymizer_registry_mut(&mut self) -> &mut AnonymizerRegistry {
101 &mut self.anonymizer_registry
102 }
103
104 pub fn analyze(&self, text: &str, language: Option<&str>) -> Result<AnalysisResult> {
106 let start = Timer::new();
107 let lang = language.unwrap_or(&self.default_language);
108
109 let detected_entities = self.recognizer_registry.analyze(text, lang)?;
110
111 let processing_time_ms = start.elapsed_ms();
112
113 Ok(AnalysisResult {
114 original_text: None,
115 detected_entities,
116 anonymized: None,
117 metadata: AnalysisMetadata {
118 recognizers_used: self.recognizer_registry.recognizers().len(),
119 processing_time_ms,
120 language: lang.to_string(),
121 model_version: self.model_version.clone(),
122 },
123 })
124 }
125
126 pub fn analyze_with_entities(
128 &self,
129 text: &str,
130 entity_types: &[EntityType],
131 language: Option<&str>,
132 ) -> Result<AnalysisResult> {
133 let start = Timer::new();
134 let lang = language.unwrap_or(&self.default_language);
135
136 let detected_entities =
137 self.recognizer_registry
138 .analyze_with_entities(text, lang, entity_types)?;
139
140 let processing_time_ms = start.elapsed_ms();
141
142 Ok(AnalysisResult {
143 original_text: None,
144 detected_entities,
145 anonymized: None,
146 metadata: AnalysisMetadata {
147 recognizers_used: self.recognizer_registry.recognizers().len(),
148 processing_time_ms,
149 language: lang.to_string(),
150 model_version: self.model_version.clone(),
151 },
152 })
153 }
154
155 pub fn anonymize(
157 &self,
158 text: &str,
159 language: Option<&str>,
160 config: &AnonymizerConfig,
161 ) -> Result<AnonymizedResult> {
162 let lang = language.unwrap_or(&self.default_language);
163
164 let analysis = self.analyze(text, Some(lang))?;
166
167 self.anonymizer_registry
169 .anonymize(text, analysis.detected_entities, config)
170 }
171
172 pub fn analyze_and_anonymize(
174 &self,
175 text: &str,
176 language: Option<&str>,
177 config: &AnonymizerConfig,
178 ) -> Result<AnalysisResult> {
179 let start = Timer::new();
180 let lang = language.unwrap_or(&self.default_language);
181
182 let mut result = self.analyze(text, Some(lang))?;
184
185 let anonymized =
187 self.anonymizer_registry
188 .anonymize(text, result.detected_entities.clone(), config)?;
189
190 result.anonymized = Some(anonymized);
191 result.metadata.processing_time_ms = start.elapsed_ms();
192
193 Ok(result)
194 }
195}
196
197impl Default for AnalyzerEngine {
198 fn default() -> Self {
199 Self::new()
200 }
201}
202
203pub struct AnalyzerEngineBuilder {
205 recognizer_registry: RecognizerRegistry,
206 anonymizer_registry: AnonymizerRegistry,
207 default_language: String,
208 model_version: Option<String>,
209}
210
211impl AnalyzerEngineBuilder {
212 pub fn new() -> Self {
213 Self {
214 recognizer_registry: RecognizerRegistry::new(),
215 anonymizer_registry: AnonymizerRegistry::new(),
216 default_language: "en".to_string(),
217 model_version: None,
218 }
219 }
220
221 pub fn with_recognizer_registry(mut self, registry: RecognizerRegistry) -> Self {
222 self.recognizer_registry = registry;
223 self
224 }
225
226 pub fn with_anonymizer_registry(mut self, registry: AnonymizerRegistry) -> Self {
227 self.anonymizer_registry = registry;
228 self
229 }
230
231 pub fn with_language(mut self, language: impl Into<String>) -> Self {
232 self.default_language = language.into();
233 self
234 }
235
236 pub fn with_model_version(mut self, version: impl Into<String>) -> Self {
237 self.model_version = Some(version.into());
238 self
239 }
240
241 pub fn build(self) -> AnalyzerEngine {
242 AnalyzerEngine {
243 recognizer_registry: self.recognizer_registry,
244 anonymizer_registry: self.anonymizer_registry,
245 default_language: self.default_language,
246 model_version: self.model_version,
247 }
248 }
249}
250
251impl Default for AnalyzerEngineBuilder {
252 fn default() -> Self {
253 Self::new()
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260 use crate::anonymizers::AnonymizationStrategy;
261
262 #[test]
263 fn test_analyzer_engine_new() {
264 let engine = AnalyzerEngine::new();
265 assert_eq!(engine.default_language, "en");
266 assert!(!engine.recognizer_registry.recognizers().is_empty());
267 }
268
269 #[test]
270 fn test_analyze() {
271 let engine = AnalyzerEngine::new();
272 let text = "Email: john@example.com, Phone: (555) 123-4567";
273
274 let result = engine.analyze(text, None).unwrap();
275
276 assert!(result.detected_entities.len() >= 2);
277 assert_eq!(result.metadata.language, "en");
278 assert!(result.metadata.processing_time_ms > 0);
279 }
280
281 #[test]
282 fn test_analyze_with_entities() {
283 let engine = AnalyzerEngine::new();
284 let text = "Email: john@example.com, Phone: (555) 123-4567";
285
286 let result = engine
287 .analyze_with_entities(text, &[EntityType::EmailAddress], None)
288 .unwrap();
289
290 assert!(result
291 .detected_entities
292 .iter()
293 .all(|e| e.entity_type == EntityType::EmailAddress));
294 }
295
296 #[test]
297 fn test_anonymize() {
298 let engine = AnalyzerEngine::new();
299 let text = "Email: john@example.com";
300 let config = AnonymizerConfig {
301 strategy: AnonymizationStrategy::Replace,
302 ..Default::default()
303 };
304
305 let result = engine.anonymize(text, None, &config).unwrap();
306
307 assert!(result.text.contains("[EMAIL_ADDRESS]"));
308 }
309
310 #[test]
311 fn test_analyze_and_anonymize() {
312 let engine = AnalyzerEngine::new();
313 let text = "Email: john@example.com, SSN: 123-45-6789";
314 let config = AnonymizerConfig {
315 strategy: AnonymizationStrategy::Replace,
316 ..Default::default()
317 };
318
319 let result = engine.analyze_and_anonymize(text, None, &config).unwrap();
320
321 assert!(result.detected_entities.len() >= 2);
322 assert!(result.anonymized.is_some());
323
324 let anonymized = result.anonymized.unwrap();
325 assert!(anonymized.text.contains("[EMAIL_ADDRESS]"));
326 assert!(anonymized.text.contains("[US_SSN]"));
327 }
328
329 #[test]
330 fn test_builder() {
331 let engine = AnalyzerEngine::builder()
332 .with_language("es")
333 .with_model_version("v1.0.0")
334 .build();
335
336 assert_eq!(engine.default_language, "es");
337 assert_eq!(engine.model_version, Some("v1.0.0".to_string()));
338 }
339}