1use std::collections::HashMap;
8
9use serde::{Deserialize, Serialize};
10
11use crate::MaskingRule;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct MaskingProfile {
22 name: String,
23 description: String,
24 rules: HashMap<String, MaskingRule>,
25 enabled: bool,
26}
27
28impl MaskingProfile {
29 pub fn new(name: &str) -> Self {
31 Self {
32 name: name.to_string(),
33 description: String::new(),
34 rules: HashMap::new(),
35 enabled: true,
36 }
37 }
38
39 pub fn with_description(name: &str, description: &str) -> Self {
41 Self {
42 name: name.to_string(),
43 description: description.to_string(),
44 rules: HashMap::new(),
45 enabled: true,
46 }
47 }
48
49 pub fn name(&self) -> &str {
51 &self.name
52 }
53
54 pub fn description(&self) -> &str {
56 &self.description
57 }
58
59 pub fn is_enabled(&self) -> bool {
61 self.enabled
62 }
63
64 pub fn set_enabled(mut self, enabled: bool) -> Self {
66 self.enabled = enabled;
67 self
68 }
69
70 pub fn with_rule(mut self, field: &str, rule: MaskingRule) -> Self {
72 self.rules.insert(field.to_string(), rule);
73 self
74 }
75
76 pub fn with_rules(mut self, rules: HashMap<String, MaskingRule>) -> Self {
78 self.rules.extend(rules);
79 self
80 }
81
82 pub fn rule_count(&self) -> usize {
84 self.rules.len()
85 }
86
87 pub fn get_rule(&self, field: &str) -> Option<&MaskingRule> {
89 self.rules.get(field)
90 }
91
92 pub fn remove_rule(&mut self, field: &str) -> Option<MaskingRule> {
94 self.rules.remove(field)
95 }
96
97 pub fn rules(&self) -> &HashMap<String, MaskingRule> {
99 &self.rules
100 }
101
102 pub fn apply_to_map(&self, data: &HashMap<String, String>) -> HashMap<String, String> {
104 if !self.enabled {
105 return data.clone();
106 }
107 crate::DataMasker::mask_map(&self.rules, data)
108 }
109
110 pub fn apply_to_json(&self, json: &str) -> String {
112 if !self.enabled {
113 return json.to_string();
114 }
115 crate::DataMasker::mask_json(&self.rules, json)
116 }
117}
118
119#[derive(Debug, Clone, Default)]
127pub struct MaskingConfigManager {
128 profiles: HashMap<String, MaskingProfile>,
129 default_profile: String,
130}
131
132impl MaskingConfigManager {
133 pub fn new() -> Self {
135 Self::default()
136 }
137
138 pub fn add_profile(mut self, profile: MaskingProfile) -> Self {
140 let name = profile.name().to_string();
141 if self.profiles.is_empty() {
142 self.default_profile = name.clone();
143 }
144 self.profiles.insert(name, profile);
145 self
146 }
147
148 pub fn set_default(&mut self, name: &str) -> bool {
150 if self.profiles.contains_key(name) {
151 self.default_profile = name.to_string();
152 true
153 } else {
154 false
155 }
156 }
157
158 pub fn default_profile_name(&self) -> &str {
160 &self.default_profile
161 }
162
163 pub fn get_profile(&self, name: &str) -> Option<&MaskingProfile> {
165 self.profiles.get(name)
166 }
167
168 pub fn default_profile(&self) -> Option<&MaskingProfile> {
170 self.profiles.get(&self.default_profile)
171 }
172
173 pub fn profile_count(&self) -> usize {
175 self.profiles.len()
176 }
177
178 pub fn profile_names(&self) -> Vec<&str> {
180 self.profiles.keys().map(|s| s.as_str()).collect()
181 }
182
183 pub fn remove_profile(&mut self, name: &str) -> Option<MaskingProfile> {
185 let removed = self.profiles.remove(name);
186 if removed.is_some() && self.default_profile == name {
187 self.default_profile = self.profiles.keys().next().cloned().unwrap_or_default();
188 }
189 removed
190 }
191
192 pub fn apply_to_map(
194 &self,
195 profile_name: &str,
196 data: &HashMap<String, String>,
197 ) -> Option<HashMap<String, String>> {
198 self.profiles
199 .get(profile_name)
200 .map(|p| p.apply_to_map(data))
201 }
202
203 pub fn apply_with_default(
205 &self,
206 data: &HashMap<String, String>,
207 ) -> Option<HashMap<String, String>> {
208 self.default_profile().map(|p| p.apply_to_map(data))
209 }
210
211 pub fn apply_to_json(&self, profile_name: &str, json: &str) -> Option<String> {
213 self.profiles
214 .get(profile_name)
215 .map(|p| p.apply_to_json(json))
216 }
217
218 pub fn merge_profiles(&self, target: &str, source: &str) -> Option<MaskingProfile> {
220 let target_profile = self.profiles.get(target)?;
221 let source_profile = self.profiles.get(source)?;
222 let mut merged = target_profile.clone();
223 for (field, rule) in source_profile.rules() {
224 if !merged.rules().contains_key(field) {
225 merged = merged.with_rule(field, rule.clone());
226 }
227 }
228 Some(merged)
229 }
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct FieldPattern {
239 keywords: Vec<String>,
240 rule: MaskingRule,
241}
242
243impl FieldPattern {
244 pub fn new(keywords: Vec<String>, rule: MaskingRule) -> Self {
246 Self { keywords, rule }
247 }
248
249 pub fn keywords(&self) -> &[String] {
251 &self.keywords
252 }
253
254 pub fn rule(&self) -> &MaskingRule {
256 &self.rule
257 }
258
259 pub fn matches(&self, field: &str) -> bool {
261 let field_lower = field.to_lowercase();
262 self.keywords
263 .iter()
264 .any(|kw| field_lower.contains(&kw.to_lowercase()))
265 }
266}
267
268#[derive(Debug, Clone, Default)]
274pub struct SensitiveFieldDetector {
275 patterns: Vec<FieldPattern>,
276}
277
278impl SensitiveFieldDetector {
279 pub fn new() -> Self {
281 Self {
282 patterns: Self::builtin_patterns(),
283 }
284 }
285
286 pub fn empty() -> Self {
288 Self::default()
289 }
290
291 pub fn add_pattern(mut self, pattern: FieldPattern) -> Self {
293 self.patterns.push(pattern);
294 self
295 }
296
297 pub fn pattern_count(&self) -> usize {
299 self.patterns.len()
300 }
301
302 pub fn detect(&self, field: &str) -> Option<&MaskingRule> {
304 self.patterns
305 .iter()
306 .find(|p| p.matches(field))
307 .map(|p| p.rule())
308 }
309
310 pub fn detect_fields(&self, fields: &[String]) -> Vec<(String, &MaskingRule)> {
312 fields
313 .iter()
314 .filter_map(|f| self.detect(f).map(|r| (f.clone(), r)))
315 .collect()
316 }
317
318 pub fn auto_rules(&self, fields: &[String]) -> HashMap<String, MaskingRule> {
320 fields
321 .iter()
322 .filter_map(|f| self.detect(f).map(|r| (f.clone(), r.clone())))
323 .collect()
324 }
325
326 pub fn auto_mask(&self, data: &HashMap<String, String>) -> HashMap<String, String> {
328 let fields: Vec<String> = data.keys().cloned().collect();
329 let rules = self.auto_rules(&fields);
330 crate::DataMasker::mask_map(&rules, data)
331 }
332
333 fn builtin_patterns() -> Vec<FieldPattern> {
335 vec![
336 FieldPattern::new(
337 vec![
338 "phone".to_string(),
339 "mobile".to_string(),
340 "tel".to_string(),
341 "telephone".to_string(),
342 ],
343 MaskingRule::Phone,
344 ),
345 FieldPattern::new(
346 vec![
347 "email".to_string(),
348 "mail".to_string(),
349 "email_addr".to_string(),
350 ],
351 MaskingRule::Email,
352 ),
353 FieldPattern::new(
354 vec![
355 "idcard".to_string(),
356 "identity".to_string(),
357 "id_number".to_string(),
358 "id_card".to_string(),
359 ],
360 MaskingRule::IdCard,
361 ),
362 FieldPattern::new(
363 vec![
364 "bankcard".to_string(),
365 "bank_card".to_string(),
366 "card_number".to_string(),
367 "cardno".to_string(),
368 ],
369 MaskingRule::BankCard,
370 ),
371 FieldPattern::new(
372 vec![
373 "password".to_string(),
374 "pwd".to_string(),
375 "passwd".to_string(),
376 "secret".to_string(),
377 ],
378 MaskingRule::Password,
379 ),
380 FieldPattern::new(
381 vec![
382 "apikey".to_string(),
383 "api_key".to_string(),
384 "access_token".to_string(),
385 "auth_token".to_string(),
386 ],
387 MaskingRule::ApiKey,
388 ),
389 FieldPattern::new(
390 vec![
391 "name".to_string(),
392 "username".to_string(),
393 "fullname".to_string(),
394 ],
395 MaskingRule::Name,
396 ),
397 FieldPattern::new(
398 vec![
399 "ip".to_string(),
400 "ip_addr".to_string(),
401 "ipaddress".to_string(),
402 ],
403 MaskingRule::Ip,
404 ),
405 FieldPattern::new(
406 vec![
407 "address".to_string(),
408 "addr".to_string(),
409 "home_addr".to_string(),
410 ],
411 MaskingRule::Address,
412 ),
413 FieldPattern::new(
414 vec!["imei".to_string(), "device_imei".to_string()],
415 MaskingRule::Imei,
416 ),
417 FieldPattern::new(
418 vec![
419 "plate".to_string(),
420 "license_plate".to_string(),
421 "car_plate".to_string(),
422 ],
423 MaskingRule::Plate,
424 ),
425 ]
426 }
427}
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432
433 #[test]
436 fn profile_new() {
437 let p = MaskingProfile::new("default");
438 assert_eq!(p.name(), "default");
439 assert_eq!(p.description(), "");
440 assert!(p.is_enabled());
441 assert_eq!(p.rule_count(), 0);
442 }
443
444 #[test]
445 fn profile_with_description() {
446 let p = MaskingProfile::with_description("strict", "Strict GDPR masking");
447 assert_eq!(p.description(), "Strict GDPR masking");
448 }
449
450 #[test]
451 fn profile_with_rule() {
452 let p = MaskingProfile::new("default").with_rule("phone", MaskingRule::Phone);
453 assert_eq!(p.rule_count(), 1);
454 assert!(p.get_rule("phone").is_some());
455 }
456
457 #[test]
458 fn profile_with_rules_batch() {
459 let mut rules = HashMap::new();
460 rules.insert("phone".to_string(), MaskingRule::Phone);
461 rules.insert("email".to_string(), MaskingRule::Email);
462 let p = MaskingProfile::new("default").with_rules(rules);
463 assert_eq!(p.rule_count(), 2);
464 }
465
466 #[test]
467 fn profile_set_disabled() {
468 let p = MaskingProfile::new("default").set_enabled(false);
469 assert!(!p.is_enabled());
470 }
471
472 #[test]
473 fn profile_remove_rule() {
474 let mut p = MaskingProfile::new("default").with_rule("phone", MaskingRule::Phone);
475 let removed = p.remove_rule("phone");
476 assert!(removed.is_some());
477 assert_eq!(p.rule_count(), 0);
478 }
479
480 #[test]
481 fn profile_apply_to_map() {
482 let p = MaskingProfile::new("default").with_rule("phone", MaskingRule::Phone);
483 let mut data = HashMap::new();
484 data.insert("phone".to_string(), "13812345678".to_string());
485 let result = p.apply_to_map(&data);
486 assert_eq!(result["phone"], "138****5678");
487 }
488
489 #[test]
490 fn profile_apply_to_map_disabled() {
491 let p = MaskingProfile::new("default")
492 .with_rule("phone", MaskingRule::Phone)
493 .set_enabled(false);
494 let mut data = HashMap::new();
495 data.insert("phone".to_string(), "13812345678".to_string());
496 let result = p.apply_to_map(&data);
497 assert_eq!(result["phone"], "13812345678");
498 }
499
500 #[test]
501 fn profile_apply_to_json() {
502 let p = MaskingProfile::new("default").with_rule("phone", MaskingRule::Phone);
503 let json = r#"{"phone":"13812345678"}"#;
504 let result = p.apply_to_json(json);
505 assert!(result.contains("138****5678"));
506 }
507
508 #[test]
509 fn profile_apply_to_json_disabled() {
510 let p = MaskingProfile::new("default")
511 .with_rule("phone", MaskingRule::Phone)
512 .set_enabled(false);
513 let json = r#"{"phone":"13812345678"}"#;
514 assert_eq!(p.apply_to_json(json), json);
515 }
516
517 #[test]
520 fn config_manager_default_empty() {
521 let m = MaskingConfigManager::new();
522 assert_eq!(m.profile_count(), 0);
523 }
524
525 #[test]
526 fn config_manager_add_profile() {
527 let m = MaskingConfigManager::new().add_profile(MaskingProfile::new("default"));
528 assert_eq!(m.profile_count(), 1);
529 assert_eq!(m.default_profile_name(), "default");
530 }
531
532 #[test]
533 fn config_manager_first_profile_is_default() {
534 let m = MaskingConfigManager::new()
535 .add_profile(MaskingProfile::new("first"))
536 .add_profile(MaskingProfile::new("second"));
537 assert_eq!(m.default_profile_name(), "first");
538 }
539
540 #[test]
541 fn config_manager_set_default() {
542 let mut m = MaskingConfigManager::new()
543 .add_profile(MaskingProfile::new("a"))
544 .add_profile(MaskingProfile::new("b"));
545 assert!(m.set_default("b"));
546 assert_eq!(m.default_profile_name(), "b");
547 }
548
549 #[test]
550 fn config_manager_set_default_nonexistent() {
551 let mut m = MaskingConfigManager::new().add_profile(MaskingProfile::new("a"));
552 assert!(!m.set_default("nonexistent"));
553 }
554
555 #[test]
556 fn config_manager_get_profile() {
557 let m = MaskingConfigManager::new().add_profile(MaskingProfile::new("default"));
558 assert!(m.get_profile("default").is_some());
559 assert!(m.get_profile("nonexistent").is_none());
560 }
561
562 #[test]
563 fn config_manager_remove_profile() {
564 let mut m = MaskingConfigManager::new()
565 .add_profile(MaskingProfile::new("a"))
566 .add_profile(MaskingProfile::new("b"));
567 let removed = m.remove_profile("a");
568 assert!(removed.is_some());
569 assert_eq!(m.profile_count(), 1);
570 assert_eq!(m.default_profile_name(), "b");
572 }
573
574 #[test]
575 fn config_manager_apply_to_map() {
576 let m = MaskingConfigManager::new()
577 .add_profile(MaskingProfile::new("default").with_rule("phone", MaskingRule::Phone));
578 let mut data = HashMap::new();
579 data.insert("phone".to_string(), "13812345678".to_string());
580 let result = m.apply_to_map("default", &data).unwrap();
581 assert_eq!(result["phone"], "138****5678");
582 }
583
584 #[test]
585 fn config_manager_apply_with_default() {
586 let m = MaskingConfigManager::new()
587 .add_profile(MaskingProfile::new("default").with_rule("phone", MaskingRule::Phone));
588 let mut data = HashMap::new();
589 data.insert("phone".to_string(), "13812345678".to_string());
590 let result = m.apply_with_default(&data).unwrap();
591 assert_eq!(result["phone"], "138****5678");
592 }
593
594 #[test]
595 fn config_manager_apply_to_json() {
596 let m = MaskingConfigManager::new()
597 .add_profile(MaskingProfile::new("default").with_rule("phone", MaskingRule::Phone));
598 let json = r#"{"phone":"13812345678"}"#;
599 let result = m.apply_to_json("default", json).unwrap();
600 assert!(result.contains("138****5678"));
601 }
602
603 #[test]
604 fn config_manager_merge_profiles() {
605 let m = MaskingConfigManager::new()
606 .add_profile(MaskingProfile::new("base").with_rule("phone", MaskingRule::Phone))
607 .add_profile(MaskingProfile::new("extra").with_rule("email", MaskingRule::Email));
608 let merged = m.merge_profiles("base", "extra").unwrap();
609 assert_eq!(merged.rule_count(), 2);
610 }
611
612 #[test]
613 fn config_manager_profile_names() {
614 let m = MaskingConfigManager::new()
615 .add_profile(MaskingProfile::new("a"))
616 .add_profile(MaskingProfile::new("b"));
617 let names = m.profile_names();
618 assert_eq!(names.len(), 2);
619 }
620
621 #[test]
624 fn field_pattern_matches() {
625 let p = FieldPattern::new(
626 vec!["phone".to_string(), "mobile".to_string()],
627 MaskingRule::Phone,
628 );
629 assert!(p.matches("phone_number"));
630 assert!(p.matches("user_mobile"));
631 assert!(!p.matches("email"));
632 }
633
634 #[test]
635 fn field_pattern_case_insensitive() {
636 let p = FieldPattern::new(vec!["phone".to_string()], MaskingRule::Phone);
637 assert!(p.matches("PHONE"));
638 assert!(p.matches("Phone"));
639 }
640
641 #[test]
642 fn field_pattern_keywords() {
643 let p = FieldPattern::new(
644 vec!["phone".to_string(), "tel".to_string()],
645 MaskingRule::Phone,
646 );
647 assert_eq!(p.keywords().len(), 2);
648 }
649
650 #[test]
653 fn detector_new_has_builtin_patterns() {
654 let d = SensitiveFieldDetector::new();
655 assert!(d.pattern_count() > 0);
656 }
657
658 #[test]
659 fn detector_empty() {
660 let d = SensitiveFieldDetector::empty();
661 assert_eq!(d.pattern_count(), 0);
662 }
663
664 #[test]
665 fn detector_add_pattern() {
666 let d = SensitiveFieldDetector::empty().add_pattern(FieldPattern::new(
667 vec!["custom".to_string()],
668 MaskingRule::Password,
669 ));
670 assert_eq!(d.pattern_count(), 1);
671 }
672
673 #[test]
674 fn detector_phone() {
675 let d = SensitiveFieldDetector::new();
676 let rule = d.detect("phone_number").unwrap();
677 assert_eq!(rule, &MaskingRule::Phone);
678 }
679
680 #[test]
681 fn detector_mobile() {
682 let d = SensitiveFieldDetector::new();
683 assert!(d.detect("user_mobile").is_some());
684 }
685
686 #[test]
687 fn detector_email() {
688 let d = SensitiveFieldDetector::new();
689 let rule = d.detect("email_addr").unwrap();
690 assert_eq!(rule, &MaskingRule::Email);
691 }
692
693 #[test]
694 fn detector_password() {
695 let d = SensitiveFieldDetector::new();
696 let rule = d.detect("user_password").unwrap();
697 assert_eq!(rule, &MaskingRule::Password);
698 }
699
700 #[test]
701 fn detector_idcard() {
702 let d = SensitiveFieldDetector::new();
703 let rule = d.detect("id_card").unwrap();
704 assert_eq!(rule, &MaskingRule::IdCard);
705 }
706
707 #[test]
708 fn detector_bankcard() {
709 let d = SensitiveFieldDetector::new();
710 let rule = d.detect("bank_card_no").unwrap();
711 assert_eq!(rule, &MaskingRule::BankCard);
712 }
713
714 #[test]
715 fn detector_apikey() {
716 let d = SensitiveFieldDetector::new();
717 let rule = d.detect("api_key").unwrap();
718 assert_eq!(rule, &MaskingRule::ApiKey);
719 }
720
721 #[test]
722 fn detector_name() {
723 let d = SensitiveFieldDetector::new();
724 let rule = d.detect("username").unwrap();
725 assert_eq!(rule, &MaskingRule::Name);
726 }
727
728 #[test]
729 fn detector_address() {
730 let d = SensitiveFieldDetector::new();
731 let rule = d.detect("home_address").unwrap();
732 assert_eq!(rule, &MaskingRule::Address);
733 }
734
735 #[test]
736 fn detector_ip() {
737 let d = SensitiveFieldDetector::new();
738 let rule = d.detect("ip_addr").unwrap();
739 assert_eq!(rule, &MaskingRule::Ip);
740 }
741
742 #[test]
743 fn detector_imei() {
744 let d = SensitiveFieldDetector::new();
745 let rule = d.detect("device_imei").unwrap();
746 assert_eq!(rule, &MaskingRule::Imei);
747 }
748
749 #[test]
750 fn detector_plate() {
751 let d = SensitiveFieldDetector::new();
752 let rule = d.detect("license_plate").unwrap();
753 assert_eq!(rule, &MaskingRule::Plate);
754 }
755
756 #[test]
757 fn detector_non_sensitive() {
758 let d = SensitiveFieldDetector::new();
759 assert!(d.detect("created_at").is_none());
760 assert!(d.detect("order_id").is_none());
761 }
762
763 #[test]
764 fn detector_detect_fields() {
765 let d = SensitiveFieldDetector::new();
766 let fields = vec![
767 "phone".to_string(),
768 "email".to_string(),
769 "created_at".to_string(),
770 ];
771 let detected = d.detect_fields(&fields);
772 assert_eq!(detected.len(), 2);
773 }
774
775 #[test]
776 fn detector_auto_rules() {
777 let d = SensitiveFieldDetector::new();
778 let fields = vec![
779 "phone".to_string(),
780 "email".to_string(),
781 "created_at".to_string(),
782 ];
783 let rules = d.auto_rules(&fields);
784 assert_eq!(rules.len(), 2);
785 assert!(rules.contains_key("phone"));
786 assert!(rules.contains_key("email"));
787 }
788
789 #[test]
790 fn detector_auto_mask() {
791 let d = SensitiveFieldDetector::new();
792 let mut data = HashMap::new();
793 data.insert("phone".to_string(), "13812345678".to_string());
794 data.insert("email".to_string(), "test@example.com".to_string());
795 data.insert("created_at".to_string(), "2024-01-01".to_string());
796 let result = d.auto_mask(&data);
797 assert_eq!(result["phone"], "138****5678");
798 assert_eq!(result["email"], "t***@example.com");
799 assert_eq!(result["created_at"], "2024-01-01");
800 }
801}