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 {
168 if value.len() != 11 {
169 return ConditionResult::from(false);
170 }
171 if !value.chars().all(|c| c.is_ascii_digit()) {
172 return ConditionResult::from(false);
173 }
174 let digits: Vec<u32> = value.chars().filter_map(|c| c.to_digit(10)).collect();
175 let check = digits[10];
176 let mut sum = 0u32;
177 for (i, &d) in digits[..10].iter().enumerate() {
178 if (i + 1) % 2 == 0 {
180 sum += 2 * d;
181 } else {
182 sum += d;
183 }
184 }
185 let expected = (10 - (sum % 10)) % 10;
186 ConditionResult::from(check == expected)
187}
188
189pub fn validate_tr_id(value: &str) -> ConditionResult {
191 if value.is_empty() {
192 return ConditionResult::Unknown;
193 }
194 ConditionResult::from(value.len() <= 35 && value.chars().all(|c| c.is_ascii_alphanumeric()))
195}
196
197pub fn validate_sr_id(value: &str) -> ConditionResult {
199 validate_malo_id(value)
200}
201
202pub fn validate_zahlpunkt(value: &str) -> ConditionResult {
204 if value.len() != 33 {
205 return ConditionResult::from(false);
206 }
207 ConditionResult::from(value.chars().all(|c| c.is_ascii_alphanumeric()))
208}
209
210pub fn validate_malo_or_zahlpunkt(value: &str) -> ConditionResult {
212 if value.len() == 11 && validate_malo_id(value).is_true() {
213 return ConditionResult::True;
214 }
215 if value.len() == 33 && validate_zahlpunkt(value).is_true() {
216 return ConditionResult::True;
217 }
218 ConditionResult::False
219}
220
221pub fn validate_artikel_pattern(value: &str, segment_lengths: &[usize]) -> ConditionResult {
230 if value.is_empty() {
231 return ConditionResult::Unknown;
232 }
233 let parts: Vec<&str> = value.split('-').collect();
234 if parts.len() != segment_lengths.len() {
235 return ConditionResult::from(false);
236 }
237 let valid = parts
238 .iter()
239 .zip(segment_lengths.iter())
240 .all(|(part, &expected_len)| {
241 part.len() == expected_len && part.chars().all(|c| c.is_ascii_digit())
242 });
243 ConditionResult::from(valid)
244}
245
246pub fn validate_exact_length(value: &str, expected: usize) -> ConditionResult {
250 if value.is_empty() {
251 return ConditionResult::Unknown;
252 }
253 ConditionResult::from(value.len() == expected)
254}
255
256pub fn validate_max_length(value: &str, max: usize) -> ConditionResult {
258 if value.is_empty() {
259 return ConditionResult::Unknown;
260 }
261 ConditionResult::from(value.len() <= max)
262}
263
264pub fn validate_all_digits(value: &str) -> ConditionResult {
266 if value.is_empty() {
267 return ConditionResult::Unknown;
268 }
269 ConditionResult::from(value.chars().all(|c| c.is_ascii_digit()))
270}
271
272pub fn utc_now_ccyymmddhhmm() -> String {
274 chrono::Utc::now().format("%Y%m%d%H%M").to_string()
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 #[test]
284 fn test_max_decimal_places() {
285 assert_eq!(
286 validate_max_decimal_places("123.45", 2),
287 ConditionResult::True
288 );
289 assert_eq!(
290 validate_max_decimal_places("123.456", 2),
291 ConditionResult::False
292 );
293 assert_eq!(validate_max_decimal_places("123", 2), ConditionResult::True);
294 assert_eq!(validate_max_decimal_places("0.1", 3), ConditionResult::True);
295 assert_eq!(validate_max_decimal_places("", 2), ConditionResult::Unknown);
296 }
297
298 #[test]
299 fn test_no_decimal_places() {
300 assert_eq!(validate_max_decimal_places("100", 0), ConditionResult::True);
301 assert_eq!(
302 validate_max_decimal_places("100.5", 0),
303 ConditionResult::False
304 );
305 }
306
307 #[test]
308 fn test_max_integer_digits() {
309 assert_eq!(
310 validate_max_integer_digits("1234", 4),
311 ConditionResult::True
312 );
313 assert_eq!(
314 validate_max_integer_digits("12345", 4),
315 ConditionResult::False
316 );
317 assert_eq!(
318 validate_max_integer_digits("-123.45", 4),
319 ConditionResult::True
320 );
321 assert_eq!(validate_max_integer_digits("", 4), ConditionResult::Unknown);
322 }
323
324 #[test]
327 fn test_validate_numeric() {
328 assert_eq!(validate_numeric("5.0", ">=", 0.0), ConditionResult::True);
329 assert_eq!(validate_numeric("-1.0", ">=", 0.0), ConditionResult::False);
330 assert_eq!(validate_numeric("1", "==", 1.0), ConditionResult::True);
331 assert_eq!(validate_numeric("2", "==", 1.0), ConditionResult::False);
332 assert_eq!(validate_numeric("0", ">", 0.0), ConditionResult::False);
333 assert_eq!(validate_numeric("1", ">", 0.0), ConditionResult::True);
334 assert_eq!(validate_numeric("abc", ">=", 0.0), ConditionResult::Unknown);
335 }
336
337 #[test]
340 fn test_hhmm_equals() {
341 assert_eq!(
342 validate_hhmm_equals("202601012200+00", "2200"),
343 ConditionResult::True
344 );
345 assert_eq!(
346 validate_hhmm_equals("202601012300+00", "2200"),
347 ConditionResult::False
348 );
349 assert_eq!(
350 validate_hhmm_equals("short", "2200"),
351 ConditionResult::False
352 );
353 }
354
355 #[test]
356 fn test_hhmm_range() {
357 assert_eq!(
358 validate_hhmm_range("202601011530+00", "0000", "2359"),
359 ConditionResult::True
360 );
361 assert_eq!(
362 validate_hhmm_range("202601010000+00", "0000", "2359"),
363 ConditionResult::True
364 );
365 assert_eq!(
366 validate_hhmm_range("202601012359+00", "0000", "2359"),
367 ConditionResult::True
368 );
369 }
370
371 #[test]
372 fn test_mmddhhmm_equals() {
373 assert_eq!(
374 validate_mmddhhmm_equals("202612312300+00", "12312300"),
375 ConditionResult::True
376 );
377 assert_eq!(
378 validate_mmddhhmm_equals("202601012200+00", "12312300"),
379 ConditionResult::False
380 );
381 }
382
383 #[test]
384 fn test_timezone_utc() {
385 assert_eq!(
386 validate_timezone_utc("202601012200+00"),
387 ConditionResult::True
388 );
389 assert_eq!(
390 validate_timezone_utc("202601012200+01"),
391 ConditionResult::False
392 );
393 assert_eq!(
394 validate_timezone_utc("202601012200"),
395 ConditionResult::False
396 );
397 }
398
399 #[test]
402 fn test_email() {
403 assert_eq!(validate_email("user@example.com"), ConditionResult::True);
404 assert_eq!(validate_email("nope"), ConditionResult::False);
405 assert_eq!(validate_email("has@but-no-dot"), ConditionResult::False);
406 assert_eq!(validate_email(""), ConditionResult::Unknown);
407 }
408
409 #[test]
410 fn test_phone() {
411 assert_eq!(validate_phone("+4930123456"), ConditionResult::True);
412 assert_eq!(validate_phone("030123456"), ConditionResult::False);
413 assert_eq!(validate_phone("+"), ConditionResult::False);
414 assert_eq!(validate_phone("+49 30 123"), ConditionResult::False); assert_eq!(validate_phone(""), ConditionResult::Unknown);
416 }
417
418 #[test]
421 fn test_malo_id() {
422 assert_eq!(validate_malo_id("51238696781"), ConditionResult::True);
426 assert_eq!(validate_malo_id("41373559241"), ConditionResult::True);
427 assert_eq!(validate_malo_id("56789012345"), ConditionResult::True);
428 assert_eq!(validate_malo_id("52935155442"), ConditionResult::True);
429
430 assert_eq!(validate_malo_id("41373559240"), ConditionResult::False); assert_eq!(validate_malo_id("512386967890"), ConditionResult::False); assert_eq!(validate_malo_id("1234567890"), ConditionResult::False); assert_eq!(validate_malo_id("abcdefghijk"), ConditionResult::False); }
436
437 #[test]
438 fn test_zahlpunkt() {
439 let valid = "DE0001234567890123456789012345678";
440 assert_eq!(valid.len(), 33);
441 assert_eq!(validate_zahlpunkt(valid), ConditionResult::True);
442 assert_eq!(validate_zahlpunkt("tooshort"), ConditionResult::False);
443 }
444
445 #[test]
448 fn test_artikel_pattern() {
449 assert_eq!(
450 validate_artikel_pattern("1-23-4", &[1, 2, 1]),
451 ConditionResult::True
452 );
453 assert_eq!(
454 validate_artikel_pattern("1-23-4-567", &[1, 2, 1, 3]),
455 ConditionResult::True
456 );
457 assert_eq!(
458 validate_artikel_pattern("1-23-4-56", &[1, 2, 1, 3]),
459 ConditionResult::False
460 );
461 assert_eq!(
462 validate_artikel_pattern("1-AB-4", &[1, 2, 1]),
463 ConditionResult::False
464 );
465 assert_eq!(
466 validate_artikel_pattern("", &[1, 2, 1]),
467 ConditionResult::Unknown
468 );
469 }
470
471 #[test]
474 fn test_tr_id() {
475 assert_eq!(validate_tr_id("ABC123"), ConditionResult::True);
476 assert_eq!(validate_tr_id("A"), ConditionResult::True);
477 assert_eq!(validate_tr_id(&"A".repeat(35)), ConditionResult::True);
478 assert_eq!(validate_tr_id(&"A".repeat(36)), ConditionResult::False);
479 assert_eq!(validate_tr_id("has spaces"), ConditionResult::False);
480 assert_eq!(validate_tr_id("has-dash"), ConditionResult::False);
481 assert_eq!(validate_tr_id(""), ConditionResult::Unknown);
482 }
483
484 #[test]
485 fn test_sr_id() {
486 assert_eq!(validate_sr_id("51238696781"), ConditionResult::True);
489 assert_eq!(validate_sr_id("41373559240"), ConditionResult::False);
490 assert_eq!(validate_sr_id("1234567890"), ConditionResult::False);
491 assert_eq!(validate_sr_id(""), ConditionResult::False);
492 }
493
494 #[test]
497 fn test_exact_length() {
498 assert_eq!(
499 validate_exact_length("1234567890123456", 16),
500 ConditionResult::True
501 );
502 assert_eq!(validate_exact_length("123", 16), ConditionResult::False);
503 assert_eq!(validate_exact_length("", 16), ConditionResult::Unknown);
504 }
505
506 #[test]
507 fn test_all_digits() {
508 assert_eq!(validate_all_digits("12345"), ConditionResult::True);
509 assert_eq!(validate_all_digits("123a5"), ConditionResult::False);
510 assert_eq!(validate_all_digits(""), ConditionResult::Unknown);
511 }
512}