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::Unknown;
85 }
86 ConditionResult::from(&dtm_value[8..12] == expected_hhmm)
87}
88
89pub fn validate_hhmm_range(dtm_value: &str, min: &str, max: &str) -> ConditionResult {
93 if dtm_value.len() < 12 {
94 return ConditionResult::Unknown;
95 }
96 let hhmm = &dtm_value[8..12];
97 ConditionResult::from(hhmm >= min && hhmm <= max)
98}
99
100pub fn validate_mmddhhmm_equals(dtm_value: &str, expected: &str) -> ConditionResult {
106 if dtm_value.len() < 12 {
107 return ConditionResult::Unknown;
108 }
109 ConditionResult::from(&dtm_value[4..12] == expected)
110}
111
112pub fn validate_timezone_utc(dtm_value: &str) -> ConditionResult {
120 if dtm_value.len() < 15 {
121 return ConditionResult::Unknown;
122 }
123 ConditionResult::from(&dtm_value[12..] == "+00")
124}
125
126pub fn validate_email(value: &str) -> ConditionResult {
132 if value.is_empty() {
133 return ConditionResult::Unknown;
134 }
135 ConditionResult::from(value.contains('@') && value.contains('.'))
136}
137
138pub fn validate_phone(value: &str) -> ConditionResult {
143 if value.is_empty() {
144 return ConditionResult::Unknown;
145 }
146 if !value.starts_with('+') || value.len() < 2 {
147 return ConditionResult::from(false);
148 }
149 ConditionResult::from(value[1..].chars().all(|c| c.is_ascii_digit()))
150}
151
152pub fn validate_malo_id(value: &str) -> ConditionResult {
158 if value.len() != 11 {
159 return ConditionResult::from(false);
160 }
161 if !value.chars().all(|c| c.is_ascii_digit()) {
162 return ConditionResult::from(false);
163 }
164 let digits: Vec<u32> = value.chars().filter_map(|c| c.to_digit(10)).collect();
166 let check = digits[10];
167 let mut sum = 0u32;
168 for (i, &d) in digits[..10].iter().enumerate() {
169 let multiplied = if i % 2 == 0 { d } else { d * 2 };
170 sum += if multiplied > 9 {
171 multiplied - 9
172 } else {
173 multiplied
174 };
175 }
176 let expected = (10 - (sum % 10)) % 10;
177 ConditionResult::from(check == expected)
178}
179
180pub fn validate_tr_id(value: &str) -> ConditionResult {
182 if value.is_empty() {
183 return ConditionResult::Unknown;
184 }
185 ConditionResult::from(value.len() <= 35 && value.chars().all(|c| c.is_ascii_alphanumeric()))
186}
187
188pub fn validate_sr_id(value: &str) -> ConditionResult {
190 validate_malo_id(value)
191}
192
193pub fn validate_zahlpunkt(value: &str) -> ConditionResult {
195 if value.len() != 33 {
196 return ConditionResult::from(false);
197 }
198 ConditionResult::from(value.chars().all(|c| c.is_ascii_alphanumeric()))
199}
200
201pub fn validate_malo_or_zahlpunkt(value: &str) -> ConditionResult {
203 if value.len() == 11 && validate_malo_id(value).is_true() {
204 return ConditionResult::True;
205 }
206 if value.len() == 33 && validate_zahlpunkt(value).is_true() {
207 return ConditionResult::True;
208 }
209 ConditionResult::False
210}
211
212pub fn validate_artikel_pattern(value: &str, segment_lengths: &[usize]) -> ConditionResult {
221 if value.is_empty() {
222 return ConditionResult::Unknown;
223 }
224 let parts: Vec<&str> = value.split('-').collect();
225 if parts.len() != segment_lengths.len() {
226 return ConditionResult::from(false);
227 }
228 let valid = parts
229 .iter()
230 .zip(segment_lengths.iter())
231 .all(|(part, &expected_len)| {
232 part.len() == expected_len && part.chars().all(|c| c.is_ascii_digit())
233 });
234 ConditionResult::from(valid)
235}
236
237pub fn validate_exact_length(value: &str, expected: usize) -> ConditionResult {
241 if value.is_empty() {
242 return ConditionResult::Unknown;
243 }
244 ConditionResult::from(value.len() == expected)
245}
246
247pub fn validate_max_length(value: &str, max: usize) -> ConditionResult {
249 if value.is_empty() {
250 return ConditionResult::Unknown;
251 }
252 ConditionResult::from(value.len() <= max)
253}
254
255pub fn validate_all_digits(value: &str) -> ConditionResult {
257 if value.is_empty() {
258 return ConditionResult::Unknown;
259 }
260 ConditionResult::from(value.chars().all(|c| c.is_ascii_digit()))
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
270 fn test_max_decimal_places() {
271 assert_eq!(
272 validate_max_decimal_places("123.45", 2),
273 ConditionResult::True
274 );
275 assert_eq!(
276 validate_max_decimal_places("123.456", 2),
277 ConditionResult::False
278 );
279 assert_eq!(validate_max_decimal_places("123", 2), ConditionResult::True);
280 assert_eq!(validate_max_decimal_places("0.1", 3), ConditionResult::True);
281 assert_eq!(validate_max_decimal_places("", 2), ConditionResult::Unknown);
282 }
283
284 #[test]
285 fn test_no_decimal_places() {
286 assert_eq!(validate_max_decimal_places("100", 0), ConditionResult::True);
287 assert_eq!(
288 validate_max_decimal_places("100.5", 0),
289 ConditionResult::False
290 );
291 }
292
293 #[test]
294 fn test_max_integer_digits() {
295 assert_eq!(
296 validate_max_integer_digits("1234", 4),
297 ConditionResult::True
298 );
299 assert_eq!(
300 validate_max_integer_digits("12345", 4),
301 ConditionResult::False
302 );
303 assert_eq!(
304 validate_max_integer_digits("-123.45", 4),
305 ConditionResult::True
306 );
307 assert_eq!(validate_max_integer_digits("", 4), ConditionResult::Unknown);
308 }
309
310 #[test]
313 fn test_validate_numeric() {
314 assert_eq!(validate_numeric("5.0", ">=", 0.0), ConditionResult::True);
315 assert_eq!(validate_numeric("-1.0", ">=", 0.0), ConditionResult::False);
316 assert_eq!(validate_numeric("1", "==", 1.0), ConditionResult::True);
317 assert_eq!(validate_numeric("2", "==", 1.0), ConditionResult::False);
318 assert_eq!(validate_numeric("0", ">", 0.0), ConditionResult::False);
319 assert_eq!(validate_numeric("1", ">", 0.0), ConditionResult::True);
320 assert_eq!(validate_numeric("abc", ">=", 0.0), ConditionResult::Unknown);
321 }
322
323 #[test]
326 fn test_hhmm_equals() {
327 assert_eq!(
328 validate_hhmm_equals("202601012200+00", "2200"),
329 ConditionResult::True
330 );
331 assert_eq!(
332 validate_hhmm_equals("202601012300+00", "2200"),
333 ConditionResult::False
334 );
335 assert_eq!(
336 validate_hhmm_equals("short", "2200"),
337 ConditionResult::Unknown
338 );
339 }
340
341 #[test]
342 fn test_hhmm_range() {
343 assert_eq!(
344 validate_hhmm_range("202601011530+00", "0000", "2359"),
345 ConditionResult::True
346 );
347 assert_eq!(
348 validate_hhmm_range("202601010000+00", "0000", "2359"),
349 ConditionResult::True
350 );
351 assert_eq!(
352 validate_hhmm_range("202601012359+00", "0000", "2359"),
353 ConditionResult::True
354 );
355 }
356
357 #[test]
358 fn test_mmddhhmm_equals() {
359 assert_eq!(
360 validate_mmddhhmm_equals("202612312300+00", "12312300"),
361 ConditionResult::True
362 );
363 assert_eq!(
364 validate_mmddhhmm_equals("202601012200+00", "12312300"),
365 ConditionResult::False
366 );
367 }
368
369 #[test]
370 fn test_timezone_utc() {
371 assert_eq!(
372 validate_timezone_utc("202601012200+00"),
373 ConditionResult::True
374 );
375 assert_eq!(
376 validate_timezone_utc("202601012200+01"),
377 ConditionResult::False
378 );
379 assert_eq!(
380 validate_timezone_utc("202601012200"),
381 ConditionResult::Unknown
382 );
383 }
384
385 #[test]
388 fn test_email() {
389 assert_eq!(validate_email("user@example.com"), ConditionResult::True);
390 assert_eq!(validate_email("nope"), ConditionResult::False);
391 assert_eq!(validate_email("has@but-no-dot"), ConditionResult::False);
392 assert_eq!(validate_email(""), ConditionResult::Unknown);
393 }
394
395 #[test]
396 fn test_phone() {
397 assert_eq!(validate_phone("+4930123456"), ConditionResult::True);
398 assert_eq!(validate_phone("030123456"), ConditionResult::False);
399 assert_eq!(validate_phone("+"), ConditionResult::False);
400 assert_eq!(validate_phone("+49 30 123"), ConditionResult::False); assert_eq!(validate_phone(""), ConditionResult::Unknown);
402 }
403
404 #[test]
407 fn test_malo_id() {
408 assert_eq!(validate_malo_id("50820849854"), ConditionResult::True);
413 assert_eq!(validate_malo_id("50820849855"), ConditionResult::False); assert_eq!(validate_malo_id("1234567890"), ConditionResult::False); assert_eq!(validate_malo_id("abcdefghijk"), ConditionResult::False); }
417
418 #[test]
419 fn test_zahlpunkt() {
420 let valid = "DE0001234567890123456789012345678";
421 assert_eq!(valid.len(), 33);
422 assert_eq!(validate_zahlpunkt(valid), ConditionResult::True);
423 assert_eq!(validate_zahlpunkt("tooshort"), ConditionResult::False);
424 }
425
426 #[test]
429 fn test_artikel_pattern() {
430 assert_eq!(
431 validate_artikel_pattern("1-23-4", &[1, 2, 1]),
432 ConditionResult::True
433 );
434 assert_eq!(
435 validate_artikel_pattern("1-23-4-567", &[1, 2, 1, 3]),
436 ConditionResult::True
437 );
438 assert_eq!(
439 validate_artikel_pattern("1-23-4-56", &[1, 2, 1, 3]),
440 ConditionResult::False
441 );
442 assert_eq!(
443 validate_artikel_pattern("1-AB-4", &[1, 2, 1]),
444 ConditionResult::False
445 );
446 assert_eq!(
447 validate_artikel_pattern("", &[1, 2, 1]),
448 ConditionResult::Unknown
449 );
450 }
451
452 #[test]
455 fn test_tr_id() {
456 assert_eq!(validate_tr_id("ABC123"), ConditionResult::True);
457 assert_eq!(validate_tr_id("A"), ConditionResult::True);
458 assert_eq!(validate_tr_id(&"A".repeat(35)), ConditionResult::True);
459 assert_eq!(validate_tr_id(&"A".repeat(36)), ConditionResult::False);
460 assert_eq!(validate_tr_id("has spaces"), ConditionResult::False);
461 assert_eq!(validate_tr_id("has-dash"), ConditionResult::False);
462 assert_eq!(validate_tr_id(""), ConditionResult::Unknown);
463 }
464
465 #[test]
466 fn test_sr_id() {
467 assert_eq!(validate_sr_id("50820849854"), ConditionResult::True);
468 assert_eq!(validate_sr_id("50820849855"), ConditionResult::False);
469 assert_eq!(validate_sr_id("1234567890"), ConditionResult::False);
470 assert_eq!(validate_sr_id(""), ConditionResult::False);
471 }
472
473 #[test]
476 fn test_exact_length() {
477 assert_eq!(
478 validate_exact_length("1234567890123456", 16),
479 ConditionResult::True
480 );
481 assert_eq!(validate_exact_length("123", 16), ConditionResult::False);
482 assert_eq!(validate_exact_length("", 16), ConditionResult::Unknown);
483 }
484
485 #[test]
486 fn test_all_digits() {
487 assert_eq!(validate_all_digits("12345"), ConditionResult::True);
488 assert_eq!(validate_all_digits("123a5"), ConditionResult::False);
489 assert_eq!(validate_all_digits(""), ConditionResult::Unknown);
490 }
491}