automapper_validation/eval/
format_validators.rs1use super::evaluator::ConditionResult;
8
9pub use super::timezone::{is_mesz_utc, is_mez_utc};
11
12pub fn validate_max_decimal_places(value: &str, max: usize) -> ConditionResult {
23 if value.is_empty() {
24 return ConditionResult::Unknown;
25 }
26 let decimal_places = match value.find('.') {
27 Some(pos) => value.len() - pos - 1,
28 None => 0,
29 };
30 ConditionResult::from(decimal_places <= max)
31}
32
33pub fn validate_max_integer_digits(value: &str, max: usize) -> ConditionResult {
37 if value.is_empty() {
38 return ConditionResult::Unknown;
39 }
40 let s = value.strip_prefix('-').unwrap_or(value);
41 let integer_part = match s.find('.') {
42 Some(pos) => &s[..pos],
43 None => s,
44 };
45 ConditionResult::from(integer_part.len() <= max)
46}
47
48pub fn validate_numeric(value: &str, op: &str, threshold: f64) -> ConditionResult {
58 let parsed = match value.parse::<f64>() {
59 Ok(v) => v,
60 Err(_) => return ConditionResult::Unknown,
61 };
62 let result = match op {
63 "==" => (parsed - threshold).abs() < f64::EPSILON,
64 "!=" => (parsed - threshold).abs() >= f64::EPSILON,
65 ">" => parsed > threshold,
66 ">=" => parsed >= threshold,
67 "<" => parsed < threshold,
68 "<=" => parsed <= threshold,
69 _ => return ConditionResult::Unknown,
70 };
71 ConditionResult::from(result)
72}
73
74pub fn validate_hhmm_equals(dtm_value: &str, expected_hhmm: &str) -> ConditionResult {
83 if dtm_value.len() < 12 {
84 return ConditionResult::False;
86 }
87 ConditionResult::from(&dtm_value[8..12] == expected_hhmm)
88}
89
90pub fn validate_hhmm_range(dtm_value: &str, min: &str, max: &str) -> ConditionResult {
94 if dtm_value.len() < 12 {
95 return ConditionResult::False;
96 }
97 let hhmm = &dtm_value[8..12];
98 ConditionResult::from(hhmm >= min && hhmm <= max)
99}
100
101pub fn validate_mmddhhmm_equals(dtm_value: &str, expected: &str) -> ConditionResult {
107 if dtm_value.len() < 12 {
108 return ConditionResult::False;
109 }
110 ConditionResult::from(&dtm_value[4..12] == expected)
111}
112
113pub fn validate_timezone_utc(dtm_value: &str) -> ConditionResult {
121 if dtm_value.len() < 15 {
122 return ConditionResult::False;
124 }
125 ConditionResult::from(&dtm_value[12..] == "+00")
126}
127
128pub fn validate_email(value: &str) -> ConditionResult {
134 if value.is_empty() {
135 return ConditionResult::Unknown;
136 }
137 ConditionResult::from(value.contains('@') && value.contains('.'))
138}
139
140pub fn validate_phone(value: &str) -> ConditionResult {
145 if value.is_empty() {
146 return ConditionResult::Unknown;
147 }
148 if !value.starts_with('+') || value.len() < 2 {
149 return ConditionResult::from(false);
150 }
151 ConditionResult::from(value[1..].chars().all(|c| c.is_ascii_digit()))
152}
153
154pub fn validate_malo_id(value: &str) -> ConditionResult {
160 if value.len() != 11 {
161 return ConditionResult::from(false);
162 }
163 if !value.chars().all(|c| c.is_ascii_digit()) {
164 return ConditionResult::from(false);
165 }
166 let digits: Vec<u32> = value.chars().filter_map(|c| c.to_digit(10)).collect();
168 let check = digits[10];
169 let mut sum = 0u32;
170 for (i, &d) in digits[..10].iter().enumerate() {
171 let multiplied = if i % 2 == 0 { d } else { d * 2 };
172 sum += if multiplied > 9 {
173 multiplied - 9
174 } else {
175 multiplied
176 };
177 }
178 let expected = (10 - (sum % 10)) % 10;
179 ConditionResult::from(check == expected)
180}
181
182pub fn validate_tr_id(value: &str) -> ConditionResult {
184 if value.is_empty() {
185 return ConditionResult::Unknown;
186 }
187 ConditionResult::from(value.len() <= 35 && value.chars().all(|c| c.is_ascii_alphanumeric()))
188}
189
190pub fn validate_sr_id(value: &str) -> ConditionResult {
192 validate_malo_id(value)
193}
194
195pub fn validate_zahlpunkt(value: &str) -> ConditionResult {
197 if value.len() != 33 {
198 return ConditionResult::from(false);
199 }
200 ConditionResult::from(value.chars().all(|c| c.is_ascii_alphanumeric()))
201}
202
203pub fn validate_malo_or_zahlpunkt(value: &str) -> ConditionResult {
205 if value.len() == 11 && validate_malo_id(value).is_true() {
206 return ConditionResult::True;
207 }
208 if value.len() == 33 && validate_zahlpunkt(value).is_true() {
209 return ConditionResult::True;
210 }
211 ConditionResult::False
212}
213
214pub fn validate_artikel_pattern(value: &str, segment_lengths: &[usize]) -> ConditionResult {
223 if value.is_empty() {
224 return ConditionResult::Unknown;
225 }
226 let parts: Vec<&str> = value.split('-').collect();
227 if parts.len() != segment_lengths.len() {
228 return ConditionResult::from(false);
229 }
230 let valid = parts
231 .iter()
232 .zip(segment_lengths.iter())
233 .all(|(part, &expected_len)| {
234 part.len() == expected_len && part.chars().all(|c| c.is_ascii_digit())
235 });
236 ConditionResult::from(valid)
237}
238
239pub fn validate_exact_length(value: &str, expected: usize) -> ConditionResult {
243 if value.is_empty() {
244 return ConditionResult::Unknown;
245 }
246 ConditionResult::from(value.len() == expected)
247}
248
249pub fn validate_max_length(value: &str, max: usize) -> ConditionResult {
251 if value.is_empty() {
252 return ConditionResult::Unknown;
253 }
254 ConditionResult::from(value.len() <= max)
255}
256
257pub fn validate_all_digits(value: &str) -> ConditionResult {
259 if value.is_empty() {
260 return ConditionResult::Unknown;
261 }
262 ConditionResult::from(value.chars().all(|c| c.is_ascii_digit()))
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 #[test]
272 fn test_max_decimal_places() {
273 assert_eq!(
274 validate_max_decimal_places("123.45", 2),
275 ConditionResult::True
276 );
277 assert_eq!(
278 validate_max_decimal_places("123.456", 2),
279 ConditionResult::False
280 );
281 assert_eq!(validate_max_decimal_places("123", 2), ConditionResult::True);
282 assert_eq!(validate_max_decimal_places("0.1", 3), ConditionResult::True);
283 assert_eq!(validate_max_decimal_places("", 2), ConditionResult::Unknown);
284 }
285
286 #[test]
287 fn test_no_decimal_places() {
288 assert_eq!(validate_max_decimal_places("100", 0), ConditionResult::True);
289 assert_eq!(
290 validate_max_decimal_places("100.5", 0),
291 ConditionResult::False
292 );
293 }
294
295 #[test]
296 fn test_max_integer_digits() {
297 assert_eq!(
298 validate_max_integer_digits("1234", 4),
299 ConditionResult::True
300 );
301 assert_eq!(
302 validate_max_integer_digits("12345", 4),
303 ConditionResult::False
304 );
305 assert_eq!(
306 validate_max_integer_digits("-123.45", 4),
307 ConditionResult::True
308 );
309 assert_eq!(validate_max_integer_digits("", 4), ConditionResult::Unknown);
310 }
311
312 #[test]
315 fn test_validate_numeric() {
316 assert_eq!(validate_numeric("5.0", ">=", 0.0), ConditionResult::True);
317 assert_eq!(validate_numeric("-1.0", ">=", 0.0), ConditionResult::False);
318 assert_eq!(validate_numeric("1", "==", 1.0), ConditionResult::True);
319 assert_eq!(validate_numeric("2", "==", 1.0), ConditionResult::False);
320 assert_eq!(validate_numeric("0", ">", 0.0), ConditionResult::False);
321 assert_eq!(validate_numeric("1", ">", 0.0), ConditionResult::True);
322 assert_eq!(validate_numeric("abc", ">=", 0.0), ConditionResult::Unknown);
323 }
324
325 #[test]
328 fn test_hhmm_equals() {
329 assert_eq!(
330 validate_hhmm_equals("202601012200+00", "2200"),
331 ConditionResult::True
332 );
333 assert_eq!(
334 validate_hhmm_equals("202601012300+00", "2200"),
335 ConditionResult::False
336 );
337 assert_eq!(
338 validate_hhmm_equals("short", "2200"),
339 ConditionResult::False
340 );
341 }
342
343 #[test]
344 fn test_hhmm_range() {
345 assert_eq!(
346 validate_hhmm_range("202601011530+00", "0000", "2359"),
347 ConditionResult::True
348 );
349 assert_eq!(
350 validate_hhmm_range("202601010000+00", "0000", "2359"),
351 ConditionResult::True
352 );
353 assert_eq!(
354 validate_hhmm_range("202601012359+00", "0000", "2359"),
355 ConditionResult::True
356 );
357 }
358
359 #[test]
360 fn test_mmddhhmm_equals() {
361 assert_eq!(
362 validate_mmddhhmm_equals("202612312300+00", "12312300"),
363 ConditionResult::True
364 );
365 assert_eq!(
366 validate_mmddhhmm_equals("202601012200+00", "12312300"),
367 ConditionResult::False
368 );
369 }
370
371 #[test]
372 fn test_timezone_utc() {
373 assert_eq!(
374 validate_timezone_utc("202601012200+00"),
375 ConditionResult::True
376 );
377 assert_eq!(
378 validate_timezone_utc("202601012200+01"),
379 ConditionResult::False
380 );
381 assert_eq!(
382 validate_timezone_utc("202601012200"),
383 ConditionResult::False
384 );
385 }
386
387 #[test]
390 fn test_email() {
391 assert_eq!(validate_email("user@example.com"), ConditionResult::True);
392 assert_eq!(validate_email("nope"), ConditionResult::False);
393 assert_eq!(validate_email("has@but-no-dot"), ConditionResult::False);
394 assert_eq!(validate_email(""), ConditionResult::Unknown);
395 }
396
397 #[test]
398 fn test_phone() {
399 assert_eq!(validate_phone("+4930123456"), ConditionResult::True);
400 assert_eq!(validate_phone("030123456"), ConditionResult::False);
401 assert_eq!(validate_phone("+"), ConditionResult::False);
402 assert_eq!(validate_phone("+49 30 123"), ConditionResult::False); assert_eq!(validate_phone(""), ConditionResult::Unknown);
404 }
405
406 #[test]
409 fn test_malo_id() {
410 assert_eq!(validate_malo_id("50820849854"), ConditionResult::True);
415 assert_eq!(validate_malo_id("50820849855"), ConditionResult::False); assert_eq!(validate_malo_id("1234567890"), ConditionResult::False); assert_eq!(validate_malo_id("abcdefghijk"), ConditionResult::False); }
419
420 #[test]
421 fn test_zahlpunkt() {
422 let valid = "DE0001234567890123456789012345678";
423 assert_eq!(valid.len(), 33);
424 assert_eq!(validate_zahlpunkt(valid), ConditionResult::True);
425 assert_eq!(validate_zahlpunkt("tooshort"), ConditionResult::False);
426 }
427
428 #[test]
431 fn test_artikel_pattern() {
432 assert_eq!(
433 validate_artikel_pattern("1-23-4", &[1, 2, 1]),
434 ConditionResult::True
435 );
436 assert_eq!(
437 validate_artikel_pattern("1-23-4-567", &[1, 2, 1, 3]),
438 ConditionResult::True
439 );
440 assert_eq!(
441 validate_artikel_pattern("1-23-4-56", &[1, 2, 1, 3]),
442 ConditionResult::False
443 );
444 assert_eq!(
445 validate_artikel_pattern("1-AB-4", &[1, 2, 1]),
446 ConditionResult::False
447 );
448 assert_eq!(
449 validate_artikel_pattern("", &[1, 2, 1]),
450 ConditionResult::Unknown
451 );
452 }
453
454 #[test]
457 fn test_tr_id() {
458 assert_eq!(validate_tr_id("ABC123"), ConditionResult::True);
459 assert_eq!(validate_tr_id("A"), ConditionResult::True);
460 assert_eq!(validate_tr_id(&"A".repeat(35)), ConditionResult::True);
461 assert_eq!(validate_tr_id(&"A".repeat(36)), ConditionResult::False);
462 assert_eq!(validate_tr_id("has spaces"), ConditionResult::False);
463 assert_eq!(validate_tr_id("has-dash"), ConditionResult::False);
464 assert_eq!(validate_tr_id(""), ConditionResult::Unknown);
465 }
466
467 #[test]
468 fn test_sr_id() {
469 assert_eq!(validate_sr_id("50820849854"), ConditionResult::True);
470 assert_eq!(validate_sr_id("50820849855"), ConditionResult::False);
471 assert_eq!(validate_sr_id("1234567890"), ConditionResult::False);
472 assert_eq!(validate_sr_id(""), ConditionResult::False);
473 }
474
475 #[test]
478 fn test_exact_length() {
479 assert_eq!(
480 validate_exact_length("1234567890123456", 16),
481 ConditionResult::True
482 );
483 assert_eq!(validate_exact_length("123", 16), ConditionResult::False);
484 assert_eq!(validate_exact_length("", 16), ConditionResult::Unknown);
485 }
486
487 #[test]
488 fn test_all_digits() {
489 assert_eq!(validate_all_digits("12345"), ConditionResult::True);
490 assert_eq!(validate_all_digits("123a5"), ConditionResult::False);
491 assert_eq!(validate_all_digits(""), ConditionResult::Unknown);
492 }
493}