Skip to main content

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}