cleansh_core/
validators.rs1use std::borrow::Cow;
11use std::collections::HashSet;
12use once_cell::sync::Lazy;
13
14pub fn is_valid_ssn_programmatically(ssn: &str) -> bool {
27 let mut parts = ssn.split('-');
28
29 let (Some(area), Some(group), Some(serial), None) = (parts.next(), parts.next(), parts.next(), parts.next()) else {
32 return false;
33 };
34
35 if area.len() != 3 || group.len() != 2 || serial.len() != 4 {
36 return false;
37 }
38
39 let Some(area_num) = area.parse::<u16>().ok() else { return false; };
40 let Some(group_num) = group.parse::<u8>().ok() else { return false; };
41 let Some(serial_num) = serial.parse::<u16>().ok() else { return false; };
42
43 let invalid_area = (area_num == 0) || (area_num == 666) || (area_num >= 800);
45 let invalid_group = group_num == 0;
46 let invalid_serial = serial_num == 0;
47
48 !(invalid_area || invalid_group || invalid_serial)
49}
50
51static INVALID_NINO_PREFIXES: Lazy<HashSet<&'static str>> = Lazy::new(|| {
53 let mut set = HashSet::new();
54 set.extend(["BF", "BG", "EH", "GB", "JE", "NK", "KN", "LI", "NT", "TN", "ZZ"]);
55 set
56});
57
58static INVALID_NINO_PREFIX_CHARS: Lazy<HashSet<char>> = Lazy::new(|| {
59 let mut set = HashSet::new();
60 set.extend(['D', 'F', 'I', 'Q', 'U', 'V', 'O']);
61 set
62});
63
64static VALID_NINO_SUFFIX_CHARS: Lazy<HashSet<char>> = Lazy::new(|| {
65 let mut set = HashSet::new();
66 set.extend(['A', 'B', 'C', 'D']);
67 set
68});
69
70pub fn is_valid_uk_nino_programmatically(nino: &str) -> bool {
83 const NINO_LENGTH: usize = 9;
84
85 let nino_normalized: Cow<str> = if nino.chars().any(|c: char| c.is_ascii_lowercase()) {
86 Cow::Owned(nino.to_uppercase())
87 } else {
88 Cow::Borrowed(nino)
89 };
90
91 let nino_no_spaces = nino_normalized.chars().filter(|c| !c.is_whitespace()).collect::<String>();
92
93 if nino_no_spaces.len() != NINO_LENGTH {
94 return false;
95 }
96
97 let mut chars = nino_no_spaces.chars();
98
99 let (Some(prefix_char1), Some(prefix_char2)) = (chars.next(), chars.next()) else { return false; };
101 if !prefix_char1.is_ascii_alphabetic() || !prefix_char2.is_ascii_alphabetic() {
102 return false;
103 }
104
105 let prefix_str = &nino_no_spaces[0..2];
107 if INVALID_NINO_PREFIXES.contains(prefix_str) {
108 return false;
109 }
110 if INVALID_NINO_PREFIX_CHARS.contains(&prefix_char1) || INVALID_NINO_PREFIX_CHARS.contains(&prefix_char2) {
111 return false;
112 }
113
114 if !chars.by_ref().take(6).all(|c| c.is_ascii_digit()) {
116 return false;
117 }
118
119 let Some(suffix_char) = chars.next() else { return false; };
121 if !VALID_NINO_SUFFIX_CHARS.contains(&suffix_char) {
122 return false;
123 }
124
125 if chars.next().is_some() {
127 return false;
128 }
129
130 true
131}
132
133pub fn is_valid_luhn(num_str: &str) -> bool {
147 let mut sum = 0;
148 let mut alternate = false;
149
150 for c in num_str.chars().rev() {
151 let Some(mut digit) = c.to_digit(10) else { return false; };
152
153 if alternate {
154 digit *= 2;
155 if digit > 9 {
156 digit -= 9;
157 }
158 }
159 sum += digit;
160 alternate = !alternate;
161 }
162
163 sum % 10 == 0
164}
165
166pub fn is_valid_credit_card_programmatically(cc_number: &str) -> bool {
179 let digits: String = cc_number.chars().filter(|c| c.is_ascii_digit()).collect();
180 if digits.is_empty() {
181 return false;
182 }
183 is_valid_luhn(&digits)
184}