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
265pub fn utc_now_ccyymmddhhmm() -> String {
267 chrono::Utc::now().format("%Y%m%d%H%M").to_string()
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 #[test]
277 fn test_max_decimal_places() {
278 assert_eq!(
279 validate_max_decimal_places("123.45", 2),
280 ConditionResult::True
281 );
282 assert_eq!(
283 validate_max_decimal_places("123.456", 2),
284 ConditionResult::False
285 );
286 assert_eq!(validate_max_decimal_places("123", 2), ConditionResult::True);
287 assert_eq!(validate_max_decimal_places("0.1", 3), ConditionResult::True);
288 assert_eq!(validate_max_decimal_places("", 2), ConditionResult::Unknown);
289 }
290
291 #[test]
292 fn test_no_decimal_places() {
293 assert_eq!(validate_max_decimal_places("100", 0), ConditionResult::True);
294 assert_eq!(
295 validate_max_decimal_places("100.5", 0),
296 ConditionResult::False
297 );
298 }
299
300 #[test]
301 fn test_max_integer_digits() {
302 assert_eq!(
303 validate_max_integer_digits("1234", 4),
304 ConditionResult::True
305 );
306 assert_eq!(
307 validate_max_integer_digits("12345", 4),
308 ConditionResult::False
309 );
310 assert_eq!(
311 validate_max_integer_digits("-123.45", 4),
312 ConditionResult::True
313 );
314 assert_eq!(validate_max_integer_digits("", 4), ConditionResult::Unknown);
315 }
316
317 #[test]
320 fn test_validate_numeric() {
321 assert_eq!(validate_numeric("5.0", ">=", 0.0), ConditionResult::True);
322 assert_eq!(validate_numeric("-1.0", ">=", 0.0), ConditionResult::False);
323 assert_eq!(validate_numeric("1", "==", 1.0), ConditionResult::True);
324 assert_eq!(validate_numeric("2", "==", 1.0), ConditionResult::False);
325 assert_eq!(validate_numeric("0", ">", 0.0), ConditionResult::False);
326 assert_eq!(validate_numeric("1", ">", 0.0), ConditionResult::True);
327 assert_eq!(validate_numeric("abc", ">=", 0.0), ConditionResult::Unknown);
328 }
329
330 #[test]
333 fn test_hhmm_equals() {
334 assert_eq!(
335 validate_hhmm_equals("202601012200+00", "2200"),
336 ConditionResult::True
337 );
338 assert_eq!(
339 validate_hhmm_equals("202601012300+00", "2200"),
340 ConditionResult::False
341 );
342 assert_eq!(
343 validate_hhmm_equals("short", "2200"),
344 ConditionResult::False
345 );
346 }
347
348 #[test]
349 fn test_hhmm_range() {
350 assert_eq!(
351 validate_hhmm_range("202601011530+00", "0000", "2359"),
352 ConditionResult::True
353 );
354 assert_eq!(
355 validate_hhmm_range("202601010000+00", "0000", "2359"),
356 ConditionResult::True
357 );
358 assert_eq!(
359 validate_hhmm_range("202601012359+00", "0000", "2359"),
360 ConditionResult::True
361 );
362 }
363
364 #[test]
365 fn test_mmddhhmm_equals() {
366 assert_eq!(
367 validate_mmddhhmm_equals("202612312300+00", "12312300"),
368 ConditionResult::True
369 );
370 assert_eq!(
371 validate_mmddhhmm_equals("202601012200+00", "12312300"),
372 ConditionResult::False
373 );
374 }
375
376 #[test]
377 fn test_timezone_utc() {
378 assert_eq!(
379 validate_timezone_utc("202601012200+00"),
380 ConditionResult::True
381 );
382 assert_eq!(
383 validate_timezone_utc("202601012200+01"),
384 ConditionResult::False
385 );
386 assert_eq!(
387 validate_timezone_utc("202601012200"),
388 ConditionResult::False
389 );
390 }
391
392 #[test]
395 fn test_email() {
396 assert_eq!(validate_email("user@example.com"), ConditionResult::True);
397 assert_eq!(validate_email("nope"), ConditionResult::False);
398 assert_eq!(validate_email("has@but-no-dot"), ConditionResult::False);
399 assert_eq!(validate_email(""), ConditionResult::Unknown);
400 }
401
402 #[test]
403 fn test_phone() {
404 assert_eq!(validate_phone("+4930123456"), ConditionResult::True);
405 assert_eq!(validate_phone("030123456"), ConditionResult::False);
406 assert_eq!(validate_phone("+"), ConditionResult::False);
407 assert_eq!(validate_phone("+49 30 123"), ConditionResult::False); assert_eq!(validate_phone(""), ConditionResult::Unknown);
409 }
410
411 #[test]
414 fn test_malo_id() {
415 assert_eq!(validate_malo_id("50820849854"), ConditionResult::True);
420 assert_eq!(validate_malo_id("50820849855"), ConditionResult::False); assert_eq!(validate_malo_id("1234567890"), ConditionResult::False); assert_eq!(validate_malo_id("abcdefghijk"), ConditionResult::False); }
424
425 #[test]
426 fn test_zahlpunkt() {
427 let valid = "DE0001234567890123456789012345678";
428 assert_eq!(valid.len(), 33);
429 assert_eq!(validate_zahlpunkt(valid), ConditionResult::True);
430 assert_eq!(validate_zahlpunkt("tooshort"), ConditionResult::False);
431 }
432
433 #[test]
436 fn test_artikel_pattern() {
437 assert_eq!(
438 validate_artikel_pattern("1-23-4", &[1, 2, 1]),
439 ConditionResult::True
440 );
441 assert_eq!(
442 validate_artikel_pattern("1-23-4-567", &[1, 2, 1, 3]),
443 ConditionResult::True
444 );
445 assert_eq!(
446 validate_artikel_pattern("1-23-4-56", &[1, 2, 1, 3]),
447 ConditionResult::False
448 );
449 assert_eq!(
450 validate_artikel_pattern("1-AB-4", &[1, 2, 1]),
451 ConditionResult::False
452 );
453 assert_eq!(
454 validate_artikel_pattern("", &[1, 2, 1]),
455 ConditionResult::Unknown
456 );
457 }
458
459 #[test]
462 fn test_tr_id() {
463 assert_eq!(validate_tr_id("ABC123"), ConditionResult::True);
464 assert_eq!(validate_tr_id("A"), ConditionResult::True);
465 assert_eq!(validate_tr_id(&"A".repeat(35)), ConditionResult::True);
466 assert_eq!(validate_tr_id(&"A".repeat(36)), ConditionResult::False);
467 assert_eq!(validate_tr_id("has spaces"), ConditionResult::False);
468 assert_eq!(validate_tr_id("has-dash"), ConditionResult::False);
469 assert_eq!(validate_tr_id(""), ConditionResult::Unknown);
470 }
471
472 #[test]
473 fn test_sr_id() {
474 assert_eq!(validate_sr_id("50820849854"), ConditionResult::True);
475 assert_eq!(validate_sr_id("50820849855"), ConditionResult::False);
476 assert_eq!(validate_sr_id("1234567890"), ConditionResult::False);
477 assert_eq!(validate_sr_id(""), ConditionResult::False);
478 }
479
480 #[test]
483 fn test_exact_length() {
484 assert_eq!(
485 validate_exact_length("1234567890123456", 16),
486 ConditionResult::True
487 );
488 assert_eq!(validate_exact_length("123", 16), ConditionResult::False);
489 assert_eq!(validate_exact_length("", 16), ConditionResult::Unknown);
490 }
491
492 #[test]
493 fn test_all_digits() {
494 assert_eq!(validate_all_digits("12345"), ConditionResult::True);
495 assert_eq!(validate_all_digits("123a5"), ConditionResult::False);
496 assert_eq!(validate_all_digits(""), ConditionResult::Unknown);
497 }
498}