1use regex::Regex;
4
5pub struct PIIRedactor {
7 ssn_regex: Regex,
8 email_regex: Regex,
9 credit_card_regex: Regex,
10}
11
12impl Default for PIIRedactor {
13 fn default() -> Self {
14 Self::new()
15 }
16}
17
18impl PIIRedactor {
19 pub fn new() -> Self {
20 Self {
21 ssn_regex: Regex::new(r"\b\d{3}-\d{2}-\d{4}\b").unwrap(),
22 email_regex: Regex::new(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b")
23 .unwrap(),
24 credit_card_regex: Regex::new(r"\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b").unwrap(),
25 }
26 }
27
28 pub fn redact(&self, text: &str) -> String {
30 let mut result = text.to_string();
31
32 result = self
34 .ssn_regex
35 .replace_all(&result, "[SSN_REDACTED]")
36 .to_string();
37
38 result = self
40 .email_regex
41 .replace_all(&result, "[EMAIL_REDACTED]")
42 .to_string();
43
44 result = self
46 .credit_card_regex
47 .replace_all(&result, "[CC_REDACTED]")
48 .to_string();
49
50 result
51 }
52
53 pub fn contains_pii(&self, text: &str) -> bool {
55 self.ssn_regex.is_match(text)
56 || self.email_regex.is_match(text)
57 || self.credit_card_regex.is_match(text)
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn test_pii_redaction() {
67 let redactor = PIIRedactor::new();
68
69 let text = "My SSN is 123-45-6789 and email is test@example.com";
70 let redacted = redactor.redact(text);
71
72 assert!(redacted.contains("[SSN_REDACTED]"));
73 assert!(redacted.contains("[EMAIL_REDACTED]"));
74 assert!(!redacted.contains("123-45-6789"));
75 assert!(!redacted.contains("test@example.com"));
76 }
77
78 #[test]
79 fn test_pii_detection() {
80 let redactor = PIIRedactor::new();
81
82 assert!(redactor.contains_pii("My SSN is 123-45-6789"));
83 assert!(redactor.contains_pii("Email: test@example.com"));
84 assert!(!redactor.contains_pii("No PII here"));
85 }
86}