1use crate::alphabet::Alphabet;
7use crate::damm::DammTable;
8use crate::error::ValidationResult;
9
10pub fn validate(
38 code: &str,
39 alphabet: &Alphabet,
40 expected_length: usize,
41 damm_table: &DammTable,
42) -> ValidationResult {
43 let actual_length = code.chars().count();
45 if actual_length != expected_length {
46 return ValidationResult::InvalidLength {
47 expected: expected_length,
48 actual: actual_length,
49 };
50 }
51
52 for (pos, c) in code.chars().enumerate() {
54 if !alphabet.contains(c) {
55 return ValidationResult::InvalidCharacter {
56 char: c,
57 position: pos,
58 };
59 }
60 }
61
62 if !damm_table.validate(code, alphabet) {
64 return ValidationResult::InvalidCheckDigit;
65 }
66
67 ValidationResult::Valid
68}
69
70#[inline]
86pub fn is_valid(
87 code: &str,
88 alphabet: &Alphabet,
89 expected_length: usize,
90 damm_table: &DammTable,
91) -> bool {
92 validate(code, alphabet, expected_length, damm_table).is_valid()
93}
94
95pub fn validate_format(
99 code: &str,
100 alphabet: &Alphabet,
101 expected_length: usize,
102) -> ValidationResult {
103 let actual_length = code.chars().count();
105 if actual_length != expected_length {
106 return ValidationResult::InvalidLength {
107 expected: expected_length,
108 actual: actual_length,
109 };
110 }
111
112 for (pos, c) in code.chars().enumerate() {
114 if !alphabet.contains(c) {
115 return ValidationResult::InvalidCharacter {
116 char: c,
117 position: pos,
118 };
119 }
120 }
121
122 ValidationResult::Valid
123}
124
125pub fn validate_batch(
129 codes: &[&str],
130 alphabet: &Alphabet,
131 expected_length: usize,
132 damm_table: &DammTable,
133) -> Vec<ValidationResult> {
134 codes
135 .iter()
136 .map(|code| validate(code, alphabet, expected_length, damm_table))
137 .collect()
138}
139
140pub fn count_valid(
144 codes: &[&str],
145 alphabet: &Alphabet,
146 expected_length: usize,
147 damm_table: &DammTable,
148) -> usize {
149 codes
150 .iter()
151 .filter(|code| is_valid(code, alphabet, expected_length, damm_table))
152 .count()
153}
154
155pub fn partition_codes<'a>(
159 codes: &[&'a str],
160 alphabet: &Alphabet,
161 expected_length: usize,
162 damm_table: &DammTable,
163) -> (Vec<&'a str>, Vec<(&'a str, ValidationResult)>) {
164 let mut valid = Vec::new();
165 let mut invalid = Vec::new();
166
167 for &code in codes {
168 let result = validate(code, alphabet, expected_length, damm_table);
169 if result.is_valid() {
170 valid.push(code);
171 } else {
172 invalid.push((code, result));
173 }
174 }
175
176 (valid, invalid)
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182 use crate::generator::{CheckPosition, generate_code};
183
184 fn setup() -> (Alphabet, DammTable, [u8; 32]) {
185 let alphabet = Alphabet::default_alphabet();
186 let damm = DammTable::new(alphabet.len());
187 let secret = [42u8; 32];
188 (alphabet, damm, secret)
189 }
190
191 #[test]
192 fn test_validate_generated_code() {
193 let (alphabet, damm, secret) = setup();
194
195 let code = generate_code(&secret, 0, &alphabet, 9, CheckPosition::End, &damm);
196 let result = validate(&code, &alphabet, 10, &damm);
197
198 assert!(result.is_valid());
199 }
200
201 #[test]
202 fn test_validate_invalid_length_short() {
203 let (alphabet, damm, _) = setup();
204
205 let result = validate("ABC", &alphabet, 10, &damm);
206 assert!(matches!(
207 result,
208 ValidationResult::InvalidLength {
209 expected: 10,
210 actual: 3
211 }
212 ));
213 }
214
215 #[test]
216 fn test_validate_invalid_length_long() {
217 let (alphabet, damm, _) = setup();
218
219 let result = validate("ABCDEFGHIJKLMNO", &alphabet, 10, &damm);
220 assert!(matches!(
221 result,
222 ValidationResult::InvalidLength {
223 expected: 10,
224 actual: 15
225 }
226 ));
227 }
228
229 #[test]
230 fn test_validate_invalid_character() {
231 let (alphabet, damm, _) = setup();
232
233 let result = validate("ABCDEFGH!J", &alphabet, 10, &damm);
235 assert!(matches!(
236 result,
237 ValidationResult::InvalidCharacter {
238 char: '!',
239 position: 8
240 }
241 ));
242 }
243
244 #[test]
245 fn test_validate_invalid_checkdigit() {
246 let (alphabet, damm, secret) = setup();
247
248 let code = generate_code(&secret, 0, &alphabet, 9, CheckPosition::End, &damm);
250
251 let mut chars: Vec<char> = code.chars().collect();
253 chars[4] = if chars[4] == 'A' { 'B' } else { 'A' };
254 let invalid_code: String = chars.iter().collect();
255
256 let result = validate(&invalid_code, &alphabet, 10, &damm);
257 assert!(matches!(result, ValidationResult::InvalidCheckDigit));
258 }
259
260 #[test]
261 fn test_is_valid() {
262 let (alphabet, damm, secret) = setup();
263
264 let code = generate_code(&secret, 0, &alphabet, 9, CheckPosition::End, &damm);
265 assert!(is_valid(&code, &alphabet, 10, &damm));
266
267 assert!(!is_valid("INVALID!!!", &alphabet, 10, &damm));
268 }
269
270 #[test]
271 fn test_validate_format() {
272 let (alphabet, _, _) = setup();
273
274 let result = validate_format("ABCDEFGHXY", &alphabet, 10);
276 assert!(result.is_valid());
277
278 let result = validate_format("ABCDEFGH!X", &alphabet, 10);
280 assert!(matches!(result, ValidationResult::InvalidCharacter { .. }));
281 }
282
283 #[test]
284 fn test_validate_batch() {
285 let (alphabet, damm, secret) = setup();
286
287 let code1 = generate_code(&secret, 0, &alphabet, 9, CheckPosition::End, &damm);
288 let code2 = generate_code(&secret, 1, &alphabet, 9, CheckPosition::End, &damm);
289 let invalid = "INVALID!!!";
290
291 let codes: Vec<&str> = vec![&code1, &code2, invalid];
292 let results = validate_batch(&codes, &alphabet, 10, &damm);
293
294 assert!(results[0].is_valid());
295 assert!(results[1].is_valid());
296 assert!(!results[2].is_valid());
297 }
298
299 #[test]
300 fn test_count_valid() {
301 let (alphabet, damm, secret) = setup();
302
303 let code1 = generate_code(&secret, 0, &alphabet, 9, CheckPosition::End, &damm);
304 let code2 = generate_code(&secret, 1, &alphabet, 9, CheckPosition::End, &damm);
305 let invalid = "INVALID!!!";
306
307 let codes: Vec<&str> = vec![&code1, &code2, invalid];
308 let count = count_valid(&codes, &alphabet, 10, &damm);
309
310 assert_eq!(count, 2);
311 }
312
313 #[test]
314 fn test_partition_codes() {
315 let (alphabet, damm, secret) = setup();
316
317 let code1 = generate_code(&secret, 0, &alphabet, 9, CheckPosition::End, &damm);
318 let code2 = generate_code(&secret, 1, &alphabet, 9, CheckPosition::End, &damm);
319 let invalid = "INVALID!!!";
320
321 let codes: Vec<&str> = vec![&code1, &code2, invalid];
322 let (valid, invalid_with_reasons) = partition_codes(&codes, &alphabet, 10, &damm);
323
324 assert_eq!(valid.len(), 2);
325 assert_eq!(invalid_with_reasons.len(), 1);
326 assert_eq!(invalid_with_reasons[0].0, "INVALID!!!");
327 }
328
329 #[test]
330 fn test_validation_result_error_code() {
331 assert_eq!(ValidationResult::Valid.error_code(), 0);
332 assert_eq!(
333 ValidationResult::InvalidLength {
334 expected: 10,
335 actual: 5
336 }
337 .error_code(),
338 1
339 );
340 assert_eq!(
341 ValidationResult::InvalidCharacter {
342 char: '!',
343 position: 0
344 }
345 .error_code(),
346 2
347 );
348 assert_eq!(ValidationResult::InvalidCheckDigit.error_code(), 3);
349 }
350}