universal_mask/lib.rs
1// src/lib.rs
2
3/// Applies a mask to text according to the specified format.
4///
5/// Accepts one or more mask formats (separated by '|') and applies the most suitable one
6/// based on the length of the input text.
7///
8/// # Examples
9///
10/// ```
11/// // SSN: XXX-XX-XXXX
12/// let ssn = mask("123456789", "XXX-XX-XXXX");
13/// assert_eq!(ssn, "123-45-6789");
14///
15/// // Phone: (XXX) XXX-XXXX
16/// let phone = mask("1234567890", "(XXX) XXX-XXXX");
17/// assert_eq!(phone, "(123) 456-7890");
18///
19/// // Multiple formats: SSN or EIN
20/// let number = mask("123456789012", "XXX-XX-XXXX | XX-XXXXXXX");
21/// assert_eq!(number, "12-3456789");
22///
23/// // Multiple formats with various patterns
24/// let multi_format = mask("123456789012345", "XXX-XX-XXXX | XX-XXXXXXX | XXX-XXX-XXX-XXX-XXX");
25/// assert_eq!(multi_format, "123-456-789-012-345");
26/// ```
27pub fn mask(text: &str, format_patterns: &str) -> String {
28 // Return empty string if the input text is empty
29 if text.is_empty() {
30 return String::new();
31 }
32
33 // Pre-allocate bytes instead of using Vec
34 let text_bytes = text.as_bytes();
35 let text_len = text_bytes.len();
36
37 // Split format patterns and find the best format
38 let mut best_format = "";
39 let mut best_x_count = 0;
40
41 for format in format_patterns.split('|') {
42 let format = format.trim();
43 let x_count = format.bytes().filter(|&b| b == b'X').count();
44
45 if best_format.is_empty() || (text_len > best_x_count && x_count > best_x_count) {
46 best_format = format;
47 best_x_count = x_count;
48 }
49 }
50
51 // Log a warning if the text has more characters than the format can handle
52 if text_len > best_x_count {
53 eprintln!("Warning: The provided text has more characters ({}) than the format can handle ({})",
54 text_len, best_x_count);
55 }
56
57 // Pre-allocate result string with estimated capacity
58 let mut result = String::with_capacity(best_format.len());
59 let mut text_index = 0;
60
61 // Use bytes for faster iteration where possible
62 for &b in best_format.as_bytes() {
63 if b == b'X' {
64 if text_index < text_len {
65 result.push(text_bytes[text_index] as char);
66 text_index += 1;
67 } else {
68 break;
69 }
70 } else {
71 result.push(b as char);
72 }
73 }
74
75 result
76}