1use super::evaluator::ConditionResult;
8
9pub use super::timezone::{dst_transitions_between, is_mesz_utc, is_mez_utc, parse_dtm303};
11pub use super::werktag::{
12 compute_easter, is_german_holiday, is_werktag, parse_ccyymmdd_prefix, werktage_between,
13};
14
15pub fn validate_max_decimal_places(value: &str, max: usize) -> ConditionResult {
26 if value.is_empty() {
27 return ConditionResult::Unknown;
28 }
29 let decimal_places = match value.find('.') {
30 Some(pos) => value.len() - pos - 1,
31 None => 0,
32 };
33 ConditionResult::from(decimal_places <= max)
34}
35
36pub fn validate_max_integer_digits(value: &str, max: usize) -> ConditionResult {
40 if value.is_empty() {
41 return ConditionResult::Unknown;
42 }
43 let s = value.strip_prefix('-').unwrap_or(value);
44 let integer_part = match s.find('.') {
45 Some(pos) => &s[..pos],
46 None => s,
47 };
48 ConditionResult::from(integer_part.len() <= max)
49}
50
51pub fn validate_numeric(value: &str, op: &str, threshold: f64) -> ConditionResult {
63 if value.is_empty() {
64 return ConditionResult::Unknown;
65 }
66 let parsed = match edifact_number(value) {
67 Some(v) => v,
68 None => return ConditionResult::False,
69 };
70 let result = match op {
71 "==" => (parsed - threshold).abs() < f64::EPSILON,
72 "!=" => (parsed - threshold).abs() >= f64::EPSILON,
73 ">" => parsed > threshold,
74 ">=" => parsed >= threshold,
75 "<" => parsed < threshold,
76 "<=" => parsed <= threshold,
77 _ => return ConditionResult::Unknown,
78 };
79 ConditionResult::from(result)
80}
81
82pub fn is_edifact_number(value: &str) -> bool {
86 edifact_number(value).is_some()
87}
88
89fn edifact_number(value: &str) -> Option<f64> {
92 let unsigned = value.strip_prefix('-').unwrap_or(value);
93 let mut marks = 0;
94 let mut digits = 0;
95 for c in unsigned.chars() {
96 match c {
97 '0'..='9' => digits += 1,
98 '.' | ',' => marks += 1,
99 _ => return None,
100 }
101 }
102 if digits == 0 || marks > 1 {
103 return None;
104 }
105 value.replace(',', ".").parse().ok()
106}
107
108pub fn validate_hhmm_equals(dtm_value: &str, expected_hhmm: &str) -> ConditionResult {
117 if dtm_value.len() < 12 {
118 return ConditionResult::False;
120 }
121 ConditionResult::from(&dtm_value[8..12] == expected_hhmm)
122}
123
124pub fn validate_hhmm_range(dtm_value: &str, min: &str, max: &str) -> ConditionResult {
128 if dtm_value.len() < 12 {
129 return ConditionResult::False;
130 }
131 let hhmm = &dtm_value[8..12];
132 ConditionResult::from(hhmm >= min && hhmm <= max)
133}
134
135pub fn validate_mmddhhmm_equals(dtm_value: &str, expected: &str) -> ConditionResult {
141 if dtm_value.len() < 12 {
142 return ConditionResult::False;
143 }
144 ConditionResult::from(&dtm_value[4..12] == expected)
145}
146
147pub fn validate_timezone_utc(dtm_value: &str) -> ConditionResult {
158 if dtm_value.len() < 15 || !dtm_value.is_char_boundary(dtm_value.len() - 3) {
159 return ConditionResult::False;
161 }
162 ConditionResult::from(&dtm_value[dtm_value.len() - 3..] == "+00")
163}
164
165pub fn validate_email(value: &str) -> ConditionResult {
171 if value.is_empty() {
172 return ConditionResult::Unknown;
173 }
174 ConditionResult::from(value.contains('@') && value.contains('.'))
175}
176
177pub fn validate_phone(value: &str) -> ConditionResult {
182 if value.is_empty() {
183 return ConditionResult::Unknown;
184 }
185 if !value.starts_with('+') || value.len() < 2 {
186 return ConditionResult::from(false);
187 }
188 ConditionResult::from(value[1..].chars().all(|c| c.is_ascii_digit()))
189}
190
191pub fn validate_malo_id(value: &str) -> ConditionResult {
205 if value.len() != 11 {
206 return ConditionResult::from(false);
207 }
208 if !value.chars().all(|c| c.is_ascii_digit()) {
209 return ConditionResult::from(false);
210 }
211 let digits: Vec<u32> = value.chars().filter_map(|c| c.to_digit(10)).collect();
212 let check = digits[10];
213 let mut sum = 0u32;
214 for (i, &d) in digits[..10].iter().enumerate() {
215 if (i + 1) % 2 == 0 {
217 sum += 2 * d;
218 } else {
219 sum += d;
220 }
221 }
222 let expected = (10 - (sum % 10)) % 10;
223 ConditionResult::from(check == expected)
224}
225
226pub fn validate_tr_id(value: &str) -> ConditionResult {
228 if value.is_empty() {
229 return ConditionResult::Unknown;
230 }
231 ConditionResult::from(value.len() <= 35 && value.chars().all(|c| c.is_ascii_alphanumeric()))
232}
233
234fn validate_prefixed_location_id(value: &str, prefix: char) -> ConditionResult {
237 let mut chars = value.chars();
238 ConditionResult::from(
239 value.len() == 11
240 && chars.next() == Some(prefix)
241 && chars.all(|c| c.is_ascii_digit() || c.is_ascii_uppercase()),
242 )
243}
244
245pub fn validate_sr_id(value: &str) -> ConditionResult {
248 validate_prefixed_location_id(value, 'C')
249}
250
251pub fn validate_nelo_id(value: &str) -> ConditionResult {
253 validate_prefixed_location_id(value, 'E')
254}
255
256pub fn parse_obis(value: &str) -> Option<(u32, u32)> {
275 let value = value.trim();
276 let (_ab, cde) = value.split_once(':')?;
278 let cde = cde.split('*').next()?;
280 let mut parts = cde.split('.');
281 let c = parts.next()?.parse::<u32>().ok()?;
282 let d = parts.next()?.parse::<u32>().ok()?;
283 Some((c, d))
284}
285
286pub fn obis_tariff(value: &str) -> Option<u32> {
290 let (_ab, cde) = value.trim().split_once(':')?;
291 let cde = cde.split('*').next()?;
292 let mut parts = cde.split('.');
293 parts.next()?.parse::<u32>().ok()?;
294 parts.next()?.parse::<u32>().ok()?;
295 parts.next()?.parse::<u32>().ok()
296}
297
298pub fn is_obis_wirkarbeit_kumuliert(value: &str) -> bool {
304 matches!(parse_obis(value), Some((1 | 2, 8 | 9)))
305}
306
307pub fn is_obis_wirkarbeit_quarter_hour(value: &str) -> bool {
314 matches!(parse_obis(value), Some((1 | 2, 29)))
315}
316
317pub fn is_obis_energiemenge(value: &str) -> bool {
321 value.trim().starts_with("1-") && matches!(parse_obis(value), Some((_, 9 | 29)))
322}
323
324pub fn is_obis_zaehlerstand(value: &str) -> bool {
326 value.trim().starts_with("1-") && matches!(parse_obis(value), Some((_, 8)))
327}
328
329pub fn is_obis_wirkleistung_maximum(value: &str) -> bool {
333 value.trim().starts_with("1-") && matches!(parse_obis(value), Some((1 | 2, 6)))
334}
335
336pub fn is_obis_blindarbeit(value: &str) -> bool {
339 value.trim().starts_with("1-") && matches!(parse_obis(value), Some((3..=8, 8 | 9 | 29)))
340}
341
342pub fn obis_has_zaehlzeit(value: &str) -> bool {
345 obis_tariff(value).is_some_and(|e| (1..=62).contains(&e))
346}
347
348pub fn is_energy_register_obis(value: &str) -> bool {
354 value.trim().starts_with("1-") && matches!(parse_obis(value), Some((1..=8, 8)))
355}
356
357pub fn validate_x509_cert_body(value: &str) -> ConditionResult {
376 let trimmed = value.trim();
377 if trimmed.is_empty() {
378 return ConditionResult::Unknown;
379 }
380 let body = trimmed
383 .trim_start_matches("-----BEGIN CERTIFICATE-----")
384 .trim_end_matches("-----END CERTIFICATE-----")
385 .trim();
386 let chars_ok = body.chars().all(|c| {
387 c.is_ascii_alphanumeric() || matches!(c, '+' | '/' | '=' | '\n' | '\r' | '\t' | ' ')
388 });
389 let len_ok = body.chars().filter(|c| !c.is_whitespace()).count() >= 100;
392 ConditionResult::from(chars_ok && len_ok)
393}
394
395pub fn validate_zahlpunkt(value: &str) -> ConditionResult {
397 if value.len() != 33 {
398 return ConditionResult::from(false);
399 }
400 ConditionResult::from(value.chars().all(|c| c.is_ascii_alphanumeric()))
401}
402
403pub fn validate_malo_or_zahlpunkt(value: &str) -> ConditionResult {
405 if value.len() == 11 && validate_malo_id(value).is_true() {
406 return ConditionResult::True;
407 }
408 if value.len() == 33 && validate_zahlpunkt(value).is_true() {
409 return ConditionResult::True;
410 }
411 ConditionResult::False
412}
413
414pub fn validate_artikel_pattern(value: &str, segment_lengths: &[usize]) -> ConditionResult {
423 if value.is_empty() {
424 return ConditionResult::Unknown;
425 }
426 let parts: Vec<&str> = value.split('-').collect();
427 if parts.len() != segment_lengths.len() {
428 return ConditionResult::from(false);
429 }
430 let valid = parts
431 .iter()
432 .zip(segment_lengths.iter())
433 .all(|(part, &expected_len)| {
434 part.len() == expected_len && part.chars().all(|c| c.is_ascii_digit())
435 });
436 ConditionResult::from(valid)
437}
438
439pub fn validate_exact_length(value: &str, expected: usize) -> ConditionResult {
443 if value.is_empty() {
444 return ConditionResult::Unknown;
445 }
446 ConditionResult::from(value.len() == expected)
447}
448
449pub fn validate_max_length(value: &str, max: usize) -> ConditionResult {
451 if value.is_empty() {
452 return ConditionResult::Unknown;
453 }
454 ConditionResult::from(value.len() <= max)
455}
456
457pub fn validate_all_digits(value: &str) -> ConditionResult {
459 if value.is_empty() {
460 return ConditionResult::Unknown;
461 }
462 ConditionResult::from(value.chars().all(|c| c.is_ascii_digit()))
463}
464
465pub fn utc_now_ccyymmddhhmm() -> String {
467 chrono::Utc::now().format("%Y%m%d%H%M").to_string()
468}
469
470pub fn german_local_time(value: &str) -> Option<chrono::NaiveDateTime> {
477 use chrono::{Datelike, Duration, NaiveDate, NaiveDateTime, Weekday};
478 let value = value.replace('?', "");
479 let stamp = NaiveDateTime::parse_from_str(value.get(..12)?, "%Y%m%d%H%M").ok()?;
480 let offset: i64 = match value.get(12..) {
481 None | Some("") => 0,
482 Some(zone) => zone.parse().ok()?,
483 };
484 let utc = stamp - Duration::hours(offset);
485 let switch = |month: u32| {
486 let mut day = NaiveDate::from_ymd_opt(utc.year(), month, 31)?;
487 while day.weekday() != Weekday::Sun {
488 day = day.pred_opt()?;
489 }
490 day.and_hms_opt(1, 0, 0)
491 };
492 let summer = utc >= switch(3)? && utc < switch(10)?;
493 Some(utc + Duration::hours(if summer { 2 } else { 1 }))
494}
495
496#[cfg(test)]
497mod tests {
498 #[test]
501 fn obis_classes_follow_the_code_list() {
502 use super::*;
503 assert!(is_obis_zaehlerstand("1-1:1.8.0") && !is_obis_energiemenge("1-1:1.8.0"));
505 assert!(is_obis_energiemenge("1-1:1.9.0") && !is_obis_zaehlerstand("1-1:1.9.0"));
506 assert!(
507 is_obis_energiemenge("1-1:1.29.0") && is_obis_wirkarbeit_quarter_hour("1-1:1.29.0")
508 );
509 assert!(!is_obis_wirkarbeit_quarter_hour("1-1:1.9.0"));
510 assert!(is_obis_wirkleistung_maximum("1-1:2.6.0"));
512 assert!(!is_obis_wirkleistung_maximum("1-1:3.6.0"));
513 assert!(is_obis_blindarbeit("1-1:3.9.0") && is_obis_blindarbeit("1-1:4.29.0"));
514 assert!(!is_obis_blindarbeit("1-1:1.8.0"));
515 assert!(obis_has_zaehlzeit("1-1:1.9.1") && obis_has_zaehlzeit("1-1:1.8.62"));
517 assert!(!obis_has_zaehlzeit("1-1:1.9.0") && !obis_has_zaehlzeit("1-1:1.8.63"));
518 assert!(!is_obis_energiemenge("7-0:3.9.0"));
520 }
521
522 #[test]
523 fn the_tariff_is_the_e_of_c_d_e() {
524 use super::obis_tariff;
525 assert_eq!(obis_tariff("1-1:1.8.1"), Some(1));
526 assert_eq!(obis_tariff("1-0:2.8.62*255"), Some(62));
527 assert_eq!(obis_tariff("1-0:1.8.0"), Some(0));
528 assert_eq!(obis_tariff("1-0:1.8"), None);
529 assert_eq!(obis_tariff("x"), None);
530 }
531
532 #[test]
533 fn energy_registers_are_c_8_e_with_c_one_to_eight() {
534 use super::is_energy_register_obis;
535 for obis in [
536 "1-1:1.8.0",
537 "1-0:2.8.1",
538 "1-1:8.8.0",
539 "1-1:1.8.63",
540 "1-65:3.8.2",
541 ] {
542 assert!(is_energy_register_obis(obis), "{obis}");
543 }
544 for obis in [
545 "1-1:1.29.0",
546 "1-1:2.29.0",
547 "1-1:9.8.0",
548 "7-0:3.0.0",
549 "1-1:1.9.0",
550 "x",
551 ] {
552 assert!(!is_energy_register_obis(obis), "{obis}");
553 }
554 }
555
556 use super::*;
557
558 #[test]
561 fn test_max_decimal_places() {
562 assert_eq!(
563 validate_max_decimal_places("123.45", 2),
564 ConditionResult::True
565 );
566 assert_eq!(
567 validate_max_decimal_places("123.456", 2),
568 ConditionResult::False
569 );
570 assert_eq!(validate_max_decimal_places("123", 2), ConditionResult::True);
571 assert_eq!(validate_max_decimal_places("0.1", 3), ConditionResult::True);
572 assert_eq!(validate_max_decimal_places("", 2), ConditionResult::Unknown);
573 }
574
575 #[test]
576 fn test_no_decimal_places() {
577 assert_eq!(validate_max_decimal_places("100", 0), ConditionResult::True);
578 assert_eq!(
579 validate_max_decimal_places("100.5", 0),
580 ConditionResult::False
581 );
582 }
583
584 #[test]
585 fn test_max_integer_digits() {
586 assert_eq!(
587 validate_max_integer_digits("1234", 4),
588 ConditionResult::True
589 );
590 assert_eq!(
591 validate_max_integer_digits("12345", 4),
592 ConditionResult::False
593 );
594 assert_eq!(
595 validate_max_integer_digits("-123.45", 4),
596 ConditionResult::True
597 );
598 assert_eq!(validate_max_integer_digits("", 4), ConditionResult::Unknown);
599 }
600
601 #[test]
604 fn test_validate_numeric() {
605 assert_eq!(validate_numeric("5.0", ">=", 0.0), ConditionResult::True);
606 assert_eq!(validate_numeric("-1.0", ">=", 0.0), ConditionResult::False);
607 assert_eq!(validate_numeric("1", "==", 1.0), ConditionResult::True);
608 assert_eq!(validate_numeric("2", "==", 1.0), ConditionResult::False);
609 assert_eq!(validate_numeric("0", ">", 0.0), ConditionResult::False);
610 assert_eq!(validate_numeric("1", ">", 0.0), ConditionResult::True);
611 assert_eq!(validate_numeric("abc", ">=", 0.0), ConditionResult::False);
614 assert_eq!(
615 validate_numeric("O5D2DXBG1A", ">", 0.0),
616 ConditionResult::False
617 );
618 assert_eq!(validate_numeric("NaN", ">", 0.0), ConditionResult::False);
619 assert_eq!(validate_numeric("inf", ">", 0.0), ConditionResult::False);
620 assert_eq!(validate_numeric("1e3", ">", 0.0), ConditionResult::False);
621 assert_eq!(validate_numeric("0,5", ">", 0.0), ConditionResult::True);
623 assert_eq!(validate_numeric("-2", "<=", 0.0), ConditionResult::True);
624 assert_eq!(validate_numeric("", ">", 0.0), ConditionResult::Unknown);
625 }
626
627 #[test]
630 fn test_hhmm_equals() {
631 assert_eq!(
632 validate_hhmm_equals("202601012200+00", "2200"),
633 ConditionResult::True
634 );
635 assert_eq!(
636 validate_hhmm_equals("202601012300+00", "2200"),
637 ConditionResult::False
638 );
639 assert_eq!(
640 validate_hhmm_equals("short", "2200"),
641 ConditionResult::False
642 );
643 }
644
645 #[test]
646 fn test_hhmm_range() {
647 assert_eq!(
648 validate_hhmm_range("202601011530+00", "0000", "2359"),
649 ConditionResult::True
650 );
651 assert_eq!(
652 validate_hhmm_range("202601010000+00", "0000", "2359"),
653 ConditionResult::True
654 );
655 assert_eq!(
656 validate_hhmm_range("202601012359+00", "0000", "2359"),
657 ConditionResult::True
658 );
659 }
660
661 #[test]
662 fn test_mmddhhmm_equals() {
663 assert_eq!(
664 validate_mmddhhmm_equals("202612312300+00", "12312300"),
665 ConditionResult::True
666 );
667 assert_eq!(
668 validate_mmddhhmm_equals("202601012200+00", "12312300"),
669 ConditionResult::False
670 );
671 }
672
673 #[test]
674 fn test_timezone_utc() {
675 assert_eq!(
676 validate_timezone_utc("202601012200+00"),
677 ConditionResult::True
678 );
679 assert_eq!(
680 validate_timezone_utc("202601012200+01"),
681 ConditionResult::False
682 );
683 assert_eq!(
684 validate_timezone_utc("202601012200"),
685 ConditionResult::False
686 );
687 assert_eq!(
689 validate_timezone_utc("20260101220000+00"),
690 ConditionResult::True
691 );
692 assert_eq!(
693 validate_timezone_utc("20260101220000+01"),
694 ConditionResult::False
695 );
696 }
697
698 #[test]
701 fn test_email() {
702 assert_eq!(validate_email("user@example.com"), ConditionResult::True);
703 assert_eq!(validate_email("nope"), ConditionResult::False);
704 assert_eq!(validate_email("has@but-no-dot"), ConditionResult::False);
705 assert_eq!(validate_email(""), ConditionResult::Unknown);
706 }
707
708 #[test]
709 fn test_phone() {
710 assert_eq!(validate_phone("+4930123456"), ConditionResult::True);
711 assert_eq!(validate_phone("030123456"), ConditionResult::False);
712 assert_eq!(validate_phone("+"), ConditionResult::False);
713 assert_eq!(validate_phone("+49 30 123"), ConditionResult::False); assert_eq!(validate_phone(""), ConditionResult::Unknown);
715 }
716
717 #[test]
720 fn test_malo_id() {
721 assert_eq!(validate_malo_id("51238696781"), ConditionResult::True);
725 assert_eq!(validate_malo_id("41373559241"), ConditionResult::True);
726 assert_eq!(validate_malo_id("56789012345"), ConditionResult::True);
727 assert_eq!(validate_malo_id("52935155442"), ConditionResult::True);
728
729 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); }
735
736 #[test]
737 fn test_zahlpunkt() {
738 let valid = "DE0001234567890123456789012345678";
739 assert_eq!(valid.len(), 33);
740 assert_eq!(validate_zahlpunkt(valid), ConditionResult::True);
741 assert_eq!(validate_zahlpunkt("tooshort"), ConditionResult::False);
742 }
743
744 #[test]
747 fn test_artikel_pattern() {
748 assert_eq!(
749 validate_artikel_pattern("1-23-4", &[1, 2, 1]),
750 ConditionResult::True
751 );
752 assert_eq!(
753 validate_artikel_pattern("1-23-4-567", &[1, 2, 1, 3]),
754 ConditionResult::True
755 );
756 assert_eq!(
757 validate_artikel_pattern("1-23-4-56", &[1, 2, 1, 3]),
758 ConditionResult::False
759 );
760 assert_eq!(
761 validate_artikel_pattern("1-AB-4", &[1, 2, 1]),
762 ConditionResult::False
763 );
764 assert_eq!(
765 validate_artikel_pattern("", &[1, 2, 1]),
766 ConditionResult::Unknown
767 );
768 }
769
770 #[test]
773 fn test_tr_id() {
774 assert_eq!(validate_tr_id("ABC123"), ConditionResult::True);
775 assert_eq!(validate_tr_id("A"), ConditionResult::True);
776 assert_eq!(validate_tr_id(&"A".repeat(35)), ConditionResult::True);
777 assert_eq!(validate_tr_id(&"A".repeat(36)), ConditionResult::False);
778 assert_eq!(validate_tr_id("has spaces"), ConditionResult::False);
779 assert_eq!(validate_tr_id("has-dash"), ConditionResult::False);
780 assert_eq!(validate_tr_id(""), ConditionResult::Unknown);
781 }
782
783 #[test]
784 fn test_parse_obis() {
785 assert_eq!(parse_obis("1-1:1.8.0"), Some((1, 8)));
786 assert_eq!(parse_obis("1-1:2.29.0"), Some((2, 29)));
787 assert_eq!(parse_obis("1-0:1.8.0*255"), Some((1, 8))); assert_eq!(parse_obis(" 1-1:1.8.0 "), Some((1, 8))); assert_eq!(parse_obis("no-colon"), None);
790 assert_eq!(parse_obis("1-1:abc.8.0"), None);
791 assert_eq!(parse_obis(""), None);
792 }
793
794 #[test]
795 fn test_obis_wirkarbeit_kumuliert() {
796 assert!(is_obis_wirkarbeit_kumuliert("1-1:1.8.0"));
798 assert!(is_obis_wirkarbeit_kumuliert("1-1:2.8.0"));
799 assert!(is_obis_wirkarbeit_kumuliert("1-1:1.9.0"));
800 assert!(is_obis_wirkarbeit_kumuliert("1-1:2.9.0"));
801 assert!(is_obis_wirkarbeit_kumuliert("1-0:1.8.0*255"));
802 assert!(!is_obis_wirkarbeit_kumuliert("1-1:3.8.0"));
804 assert!(!is_obis_wirkarbeit_kumuliert("1-1:4.8.0"));
805 assert!(!is_obis_wirkarbeit_kumuliert("1-1:1.29.0"));
807 assert!(!is_obis_wirkarbeit_kumuliert("1-1:1.5.0"));
809 }
810
811 #[test]
812 fn test_obis_wirkarbeit_quarter_hour() {
813 assert!(is_obis_wirkarbeit_quarter_hour("1-1:1.29.0"));
815 assert!(is_obis_wirkarbeit_quarter_hour("1-1:2.29.0"));
816 assert!(is_obis_wirkarbeit_quarter_hour("1-0:1.29.0*255"));
817 assert!(!is_obis_wirkarbeit_quarter_hour("1-1:1.8.0"));
819 assert!(!is_obis_wirkarbeit_quarter_hour("1-1:1.9.0"));
820 assert!(!is_obis_wirkarbeit_quarter_hour("1-1:3.29.0"));
822 }
823
824 #[test]
825 fn test_x509_cert_body() {
826 let body = "A".repeat(600);
828 assert_eq!(validate_x509_cert_body(&body), ConditionResult::True);
829
830 let pem_body = "A".repeat(64) + "\n" + &"B".repeat(64) + "\n" + &"C".repeat(64);
832 assert_eq!(validate_x509_cert_body(&pem_body), ConditionResult::True);
833
834 let armored = format!(
836 "-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----",
837 "A".repeat(200)
838 );
839 assert_eq!(validate_x509_cert_body(&armored), ConditionResult::True);
840
841 let with_punct = "AB+/==".to_string() + &"A".repeat(200);
843 assert_eq!(validate_x509_cert_body(&with_punct), ConditionResult::True);
844
845 assert_eq!(validate_x509_cert_body("QUJD"), ConditionResult::False);
847
848 let invalid_chars = "A".repeat(200) + "!@#";
850 assert_eq!(
851 validate_x509_cert_body(&invalid_chars),
852 ConditionResult::False
853 );
854
855 assert_eq!(validate_x509_cert_body(""), ConditionResult::Unknown);
857 assert_eq!(validate_x509_cert_body(" \n "), ConditionResult::Unknown);
858 }
859
860 #[test]
864 fn test_sr_id() {
865 for real in ["C816417ST77", "CZZ6M5DGSS5", "COPFWS2EX96"] {
866 assert_eq!(validate_sr_id(real), ConditionResult::True, "{real}");
867 }
868 assert_eq!(
869 validate_sr_id("51238696781"),
870 ConditionResult::False,
871 "a MaLo-ID"
872 );
873 assert_eq!(validate_sr_id("C12345"), ConditionResult::False);
874 assert_eq!(validate_sr_id(""), ConditionResult::False);
875 }
876
877 #[test]
881 fn test_nelo_id() {
882 for real in ["E1688117482", "EU5UQAWW8D0", "EEW7TM7V906"] {
883 assert_eq!(validate_nelo_id(real), ConditionResult::True, "{real}");
884 }
885 assert_eq!(
886 validate_nelo_id("51238696781"),
887 ConditionResult::False,
888 "a MaLo-ID"
889 );
890 assert_eq!(validate_nelo_id("E123"), ConditionResult::False);
891 assert_eq!(validate_nelo_id("e1688117482"), ConditionResult::False);
892 }
893
894 #[test]
897 fn test_exact_length() {
898 assert_eq!(
899 validate_exact_length("1234567890123456", 16),
900 ConditionResult::True
901 );
902 assert_eq!(validate_exact_length("123", 16), ConditionResult::False);
903 assert_eq!(validate_exact_length("", 16), ConditionResult::Unknown);
904 }
905
906 #[test]
907 fn test_all_digits() {
908 assert_eq!(validate_all_digits("12345"), ConditionResult::True);
909 assert_eq!(validate_all_digits("123a5"), ConditionResult::False);
910 assert_eq!(validate_all_digits(""), ConditionResult::Unknown);
911 }
912}