1use super::ValidationRuleContext;
4use super::Validator;
5use crate::group::SegmentGroupIndexed;
6use crate::{EdifactError, Segment, ValidationIssue, ValidationReport, ValidationSeverity};
7use std::sync::Arc;
8
9pub trait ProfileRule: Send + Sync {
29 fn evaluate(
33 &self,
34 segments: &[Segment<'_>],
35 context: &ValidationRuleContext<'_>,
36 issues: &mut Vec<ValidationIssue>,
37 );
38}
39
40struct ClosureProfileRule<F>(F);
42
43impl<F> ProfileRule for ClosureProfileRule<F>
44where
45 F: for<'a> Fn(&[Segment<'a>], &ValidationRuleContext<'_>, &mut Vec<ValidationIssue>)
46 + Send
47 + Sync,
48{
49 fn evaluate(
50 &self,
51 segments: &[Segment<'_>],
52 context: &ValidationRuleContext<'_>,
53 issues: &mut Vec<ValidationIssue>,
54 ) {
55 (self.0)(segments, context, issues);
56 }
57}
58
59struct StatelessClosureProfileRule<F>(F);
61
62impl<F> ProfileRule for StatelessClosureProfileRule<F>
63where
64 F: for<'a> Fn(&[Segment<'a>], &mut Vec<ValidationIssue>) + Send + Sync,
65{
66 fn evaluate(
67 &self,
68 segments: &[Segment<'_>],
69 _context: &ValidationRuleContext<'_>,
70 issues: &mut Vec<ValidationIssue>,
71 ) {
72 (self.0)(segments, issues);
73 }
74}
75
76pub(super) struct NamedRule {
82 pub(super) id: Option<Arc<str>>,
86 pub(super) rule: Arc<dyn ProfileRule + Send + Sync>,
87}
88
89impl Clone for NamedRule {
90 fn clone(&self) -> Self {
91 Self {
92 id: self.id.clone(),
93 rule: Arc::clone(&self.rule),
94 }
95 }
96}
97
98pub(super) struct NamedGroupRule {
109 pub(super) id: Option<Arc<str>>,
111 pub(super) group_scope: Option<&'static str>,
113 pub(super) rule: Arc<
115 dyn Fn(
116 &SegmentGroupIndexed,
117 &[Segment<'_>],
118 &ValidationRuleContext<'_>,
119 &mut Vec<ValidationIssue>,
120 ) + Send
121 + Sync,
122 >,
123}
124
125impl Clone for NamedGroupRule {
126 fn clone(&self) -> Self {
127 Self {
128 id: self.id.clone(),
129 group_scope: self.group_scope,
130 rule: Arc::clone(&self.rule),
131 }
132 }
133}
134
135pub struct ProfileRulePack {
137 name: String,
138 message_types: std::collections::BTreeSet<String>,
140 release: Option<String>,
142 pub(super) rules: Vec<NamedRule>,
143 pub(super) group_rules: Vec<NamedGroupRule>,
144 pub(super) bail_on_first_error: bool,
145}
146
147impl ProfileRulePack {
148 pub fn new(name: impl Into<String>) -> Self {
150 Self {
151 name: name.into(),
152 message_types: std::collections::BTreeSet::new(),
153 release: None,
154 rules: Vec::new(),
155 group_rules: Vec::new(),
156 bail_on_first_error: false,
157 }
158 }
159
160 pub fn name(&self) -> &str {
162 &self.name
163 }
164
165 pub fn message_types(&self) -> impl Iterator<Item = &str> {
167 self.message_types.iter().map(|s| s.as_str())
168 }
169
170 pub fn rule_count(&self) -> usize {
172 self.rules.len()
173 }
174
175 pub fn named_rule_count(&self) -> usize {
177 self.rules.iter().filter(|r| r.id.is_some()).count()
178 }
179
180 pub fn anonymous_rule_count(&self) -> usize {
182 self.rules.iter().filter(|r| r.id.is_none()).count()
183 }
184
185 pub fn rule_ids(&self) -> impl Iterator<Item = &str> {
187 self.rules.iter().filter_map(|r| r.id.as_deref())
188 }
189
190 pub fn release(&self) -> Option<&str> {
192 self.release.as_deref()
193 }
194
195 pub fn for_message_type(mut self, message_type: impl Into<String>) -> Self {
197 self.message_types.insert(message_type.into());
198 self
199 }
200
201 pub fn for_release(mut self, release: impl Into<String>) -> Self {
203 self.release = Some(release.into());
204 self
205 }
206
207 pub fn bail_on_first_error(mut self, bail: bool) -> Self {
210 self.bail_on_first_error = bail;
211 self
212 }
213
214 pub fn with_rule_fn<F>(mut self, rule: F) -> Self
216 where
217 F: for<'a> Fn(&[Segment<'a>], &ValidationRuleContext<'_>, &mut Vec<ValidationIssue>)
218 + Send
219 + Sync
220 + 'static,
221 {
222 self.rules.push(NamedRule {
223 id: None,
224 rule: Arc::new(ClosureProfileRule(rule)),
225 });
226 self
227 }
228
229 pub fn with_named_rule_fn<F>(mut self, id: impl Into<Arc<str>>, rule: F) -> Self
231 where
232 F: for<'a> Fn(&[Segment<'a>], &ValidationRuleContext<'_>, &mut Vec<ValidationIssue>)
233 + Send
234 + Sync
235 + 'static,
236 {
237 self.rules.push(NamedRule {
238 id: Some(id.into()),
239 rule: Arc::new(ClosureProfileRule(rule)),
240 });
241 self
242 }
243
244 pub fn with_stateless_rule_fn<F>(mut self, rule: F) -> Self
246 where
247 F: for<'a> Fn(&[Segment<'a>], &mut Vec<ValidationIssue>) + Send + Sync + 'static,
248 {
249 self.rules.push(NamedRule {
250 id: None,
251 rule: Arc::new(StatelessClosureProfileRule(rule)),
252 });
253 self
254 }
255
256 pub fn with_named_stateless_rule_fn<F>(mut self, id: impl Into<Arc<str>>, rule: F) -> Self
258 where
259 F: for<'a> Fn(&[Segment<'a>], &mut Vec<ValidationIssue>) + Send + Sync + 'static,
260 {
261 self.rules.push(NamedRule {
262 id: Some(id.into()),
263 rule: Arc::new(StatelessClosureProfileRule(rule)),
264 });
265 self
266 }
267
268 pub fn require_segment(self, tag: &'static str, rule_id: impl Into<Arc<str>>) -> Self {
280 let id: Arc<str> = rule_id.into();
281 self.with_named_stateless_rule_fn(id.clone(), move |segments, issues| {
282 if !segments.iter().any(|s| s.tag == tag) {
283 issues.push(
284 ValidationIssue::new(
285 ValidationSeverity::Error,
286 format!("mandatory segment {tag} is missing"),
287 )
288 .with_segment(tag)
289 .with_rule_id(id.as_ref()),
290 );
291 }
292 })
293 }
294
295 pub fn forbid_segment(self, tag: &'static str, rule_id: impl Into<Arc<str>>) -> Self {
299 let id: Arc<str> = rule_id.into();
300 self.with_named_stateless_rule_fn(id.clone(), move |segments, issues| {
301 for (occ, _s) in segments.iter().filter(|s| s.tag == tag).enumerate() {
302 issues.push(
303 ValidationIssue::new(
304 ValidationSeverity::Error,
305 format!("segment {tag} must not appear"),
306 )
307 .with_segment(tag)
308 .with_segment_occurrence(u16::try_from(occ).unwrap_or(u16::MAX))
309 .with_rule_id(id.as_ref()),
310 );
311 }
312 })
313 }
314
315 pub fn require_qualifier(
318 self,
319 tag: &'static str,
320 element: u8,
321 component: u8,
322 qualifier: &'static str,
323 rule_id: impl Into<Arc<str>>,
324 ) -> Self {
325 let id: Arc<str> = rule_id.into();
326 self.with_named_stateless_rule_fn(id.clone(), move |segments, issues| {
327 for (occ, s) in segments.iter().filter(|s| s.tag == tag).enumerate() {
328 let actual = s
329 .get_element(element as usize)
330 .and_then(|e| e.get_component(component as usize));
331 if actual != Some(qualifier) {
332 issues.push(
333 ValidationIssue::new(
334 ValidationSeverity::Error,
335 format!(
336 "segment {tag} element {element} component {component} must be \
337 {qualifier:?} but found {:?}",
338 actual.unwrap_or("<absent>")
339 ),
340 )
341 .with_segment(tag)
342 .with_element_index(element)
343 .with_component_index(component)
344 .with_segment_occurrence(u16::try_from(occ).unwrap_or(u16::MAX))
345 .with_rule_id(id.as_ref()),
346 );
347 }
348 }
349 })
350 }
351
352 pub fn with_group_rule_fn<F>(mut self, rule: F) -> Self
370 where
371 F: Fn(
372 &SegmentGroupIndexed,
373 &[Segment<'_>],
374 &ValidationRuleContext<'_>,
375 &mut Vec<ValidationIssue>,
376 ) + Send
377 + Sync
378 + 'static,
379 {
380 self.group_rules.push(NamedGroupRule {
381 id: None,
382 group_scope: None,
383 rule: Arc::new(rule),
384 });
385 self
386 }
387
388 pub fn with_named_group_rule_fn<F>(mut self, id: impl Into<Arc<str>>, rule: F) -> Self
390 where
391 F: Fn(
392 &SegmentGroupIndexed,
393 &[Segment<'_>],
394 &ValidationRuleContext<'_>,
395 &mut Vec<ValidationIssue>,
396 ) + Send
397 + Sync
398 + 'static,
399 {
400 self.group_rules.push(NamedGroupRule {
401 id: Some(id.into()),
402 group_scope: None,
403 rule: Arc::new(rule),
404 });
405 self
406 }
407
408 pub fn with_scoped_group_rule_fn<F>(
428 mut self,
429 group_scope: &'static str,
430 id: impl Into<Arc<str>>,
431 rule: F,
432 ) -> Self
433 where
434 F: Fn(
435 &SegmentGroupIndexed,
436 &[Segment<'_>],
437 &ValidationRuleContext<'_>,
438 &mut Vec<ValidationIssue>,
439 ) + Send
440 + Sync
441 + 'static,
442 {
443 self.group_rules.push(NamedGroupRule {
444 id: Some(id.into()),
445 group_scope: Some(group_scope),
446 rule: Arc::new(rule),
447 });
448 self
449 }
450
451 pub fn require_segment_in_group(
458 self,
459 group_scope: &'static str,
460 tag: &'static str,
461 rule_id: impl Into<Arc<str>>,
462 ) -> Self {
463 let id: Arc<str> = rule_id.into();
464 self.with_scoped_group_rule_fn(
465 group_scope,
466 id.clone(),
467 move |_group, segs, _ctx, issues| {
468 if !segs.iter().any(|s| s.tag == tag) {
469 issues.push(
470 ValidationIssue::new(
471 ValidationSeverity::Error,
472 format!("mandatory segment {tag} is missing from group {group_scope}"),
473 )
474 .with_segment(tag)
475 .with_rule_id(id.as_ref()),
476 );
477 }
478 },
479 )
480 }
481
482 pub fn forbid_segment_in_group(
486 self,
487 group_scope: &'static str,
488 tag: &'static str,
489 rule_id: impl Into<Arc<str>>,
490 ) -> Self {
491 let id: Arc<str> = rule_id.into();
492 self.with_scoped_group_rule_fn(
493 group_scope,
494 id.clone(),
495 move |_group, segs, _ctx, issues| {
496 for (occ, _s) in segs.iter().filter(|s| s.tag == tag).enumerate() {
497 issues.push(
498 ValidationIssue::new(
499 ValidationSeverity::Error,
500 format!("segment {tag} must not appear in group {group_scope}"),
501 )
502 .with_segment(tag)
503 .with_segment_occurrence(u16::try_from(occ).unwrap_or(u16::MAX))
504 .with_rule_id(id.as_ref()),
505 );
506 }
507 },
508 )
509 }
510
511 pub fn require_qualifier_in_group(
514 self,
515 group_scope: &'static str,
516 tag: &'static str,
517 element: u8,
518 component: u8,
519 qualifier: &'static str,
520 rule_id: impl Into<Arc<str>>,
521 ) -> Self {
522 let id: Arc<str> = rule_id.into();
523 self.with_scoped_group_rule_fn(
524 group_scope,
525 id.clone(),
526 move |_group, segs, _ctx, issues| {
527 for (occ, s) in segs.iter().filter(|s| s.tag == tag).enumerate() {
528 let actual = s
529 .get_element(element as usize)
530 .and_then(|e| e.get_component(component as usize));
531 if actual != Some(qualifier) {
532 issues.push(
533 ValidationIssue::new(
534 ValidationSeverity::Error,
535 format!(
536 "segment {tag} element {element} component {component} must be \
537 {qualifier:?} in group {group_scope}, found {:?}",
538 actual.unwrap_or("<absent>")
539 ),
540 )
541 .with_segment(tag)
542 .with_element_index(element)
543 .with_component_index(component)
544 .with_segment_occurrence(u16::try_from(occ).unwrap_or(u16::MAX))
545 .with_rule_id(id.as_ref()),
546 );
547 }
548 }
549 },
550 )
551 }
552
553 pub fn group_rule_count(&self) -> usize {
555 self.group_rules.len()
556 }
557
558 fn walk_group_tree(
564 &self,
565 group: &SegmentGroupIndexed,
566 all_segments: &[Segment<'_>],
567 report: &mut ValidationReport,
568 context: &ValidationRuleContext<'_>,
569 ) {
570 let group_segs = all_segments.get(group.total_span.clone()).unwrap_or(&[]);
571 let mut rule_issues: Vec<ValidationIssue> = Vec::new();
572
573 for named in &self.group_rules {
574 if let Some(scope) = named.group_scope {
576 if group.definition != scope {
577 continue;
578 }
579 }
580 let errors_before = report.errors.len();
581 (named.rule)(group, group_segs, context, &mut rule_issues);
582 for mut issue in rule_issues.drain(..) {
583 if issue.segment_group.is_none() {
585 issue = issue.with_segment_group(group.definition);
586 }
587 match issue.severity {
588 ValidationSeverity::Critical | ValidationSeverity::Error => {
589 report.add_error(issue);
590 }
591 ValidationSeverity::Warning => {
592 report.add_warning(issue);
593 }
594 ValidationSeverity::Info => {
595 report.add_info(issue);
596 }
597 }
598 }
599 if self.bail_on_first_error && report.errors.len() > errors_before {
600 return;
601 }
602 }
603
604 for child in &group.children {
605 let errors_before_child = report.errors.len();
606 self.walk_group_tree(child, all_segments, report, context);
607 if self.bail_on_first_error && report.errors.len() > errors_before_child {
608 return;
609 }
610 }
611 }
612
613 pub fn with_rule(mut self, rule: impl ProfileRule + 'static) -> Self {
615 self.rules.push(NamedRule {
616 id: None,
617 rule: Arc::new(rule),
618 });
619 self
620 }
621
622 pub fn with_named_rule(
624 mut self,
625 id: impl Into<Arc<str>>,
626 rule: impl ProfileRule + 'static,
627 ) -> Self {
628 self.rules.push(NamedRule {
629 id: Some(id.into()),
630 rule: Arc::new(rule),
631 });
632 self
633 }
634
635 pub fn extend_from(mut self, base: &ProfileRulePack) -> Result<Self, EdifactError> {
657 let mut combined = base.rules.clone();
658 combined.append(&mut self.rules);
659 self.rules = combined;
660 let mut combined_group = base.group_rules.clone();
662 combined_group.append(&mut self.group_rules);
663 self.group_rules = combined_group;
664 for mt in &base.message_types {
665 self.message_types.insert(mt.clone());
666 }
667 self.release = merge_release_scopes(self.release.take(), base.release.clone())?;
668 Ok(self)
669 }
670
671 pub fn merge_with_override(mut self, mut other: Self) -> Result<Self, EdifactError> {
700 let mut id_to_index: std::collections::HashMap<Arc<str>, usize> = Default::default();
701 for (idx, rule) in self.rules.iter().enumerate() {
702 if let Some(id) = &rule.id {
703 id_to_index.insert(id.clone(), idx);
704 }
705 }
706
707 let mut replacements: Vec<(usize, NamedRule)> = Vec::new();
708 let mut to_append = Vec::new();
709
710 for other_rule in other.rules.drain(..) {
711 if let Some(id) = &other_rule.id {
712 if let Some(&idx) = id_to_index.get(id) {
713 replacements.push((idx, other_rule));
714 } else {
715 to_append.push(other_rule);
716 }
717 } else {
718 to_append.push(other_rule);
719 }
720 }
721
722 for (idx, rule) in replacements {
723 if idx < self.rules.len() {
724 self.rules[idx] = rule;
725 }
726 }
727
728 self.rules.append(&mut to_append);
729 self.message_types.append(&mut other.message_types);
730 let mut group_id_to_index: std::collections::HashMap<Arc<str>, usize> = Default::default();
732 for (idx, rule) in self.group_rules.iter().enumerate() {
733 if let Some(id) = &rule.id {
734 group_id_to_index.insert(id.clone(), idx);
735 }
736 }
737 let mut group_replacements: Vec<(usize, NamedGroupRule)> = Vec::new();
738 let mut group_to_append = Vec::new();
739 for other_rule in other.group_rules.drain(..) {
740 if let Some(id) = &other_rule.id {
741 if let Some(&idx) = group_id_to_index.get(id) {
742 group_replacements.push((idx, other_rule));
743 } else {
744 group_to_append.push(other_rule);
745 }
746 } else {
747 group_to_append.push(other_rule);
748 }
749 }
750 for (idx, rule) in group_replacements {
751 if idx < self.group_rules.len() {
752 self.group_rules[idx] = rule;
753 }
754 }
755 self.group_rules.append(&mut group_to_append);
756 self.release = merge_release_scopes(self.release.take(), other.release.take())?;
757 Ok(self)
758 }
759}
760
761pub(super) fn merge_release_scopes(
762 current: Option<String>,
763 incoming: Option<String>,
764) -> Result<Option<String>, EdifactError> {
765 match (current, incoming) {
766 (Some(x), Some(y)) if x != y => Err(EdifactError::IncompatibleReleaseScopes {
767 current: x,
768 incoming: y,
769 }),
770 (Some(x), Some(_)) => Ok(Some(x)),
771 (Some(x), None) => Ok(Some(x)),
772 (None, incoming) => Ok(incoming),
773 }
774}
775
776impl Validator for ProfileRulePack {
777 fn validate_batch(
778 &self,
779 segments: &[Segment<'_>],
780 report: &mut ValidationReport,
781 context: &ValidationRuleContext<'_>,
782 ) {
783 let unh_e1_storage;
786 let unh_e1: Option<&crate::model::Element<'_>> = if context.message_type.is_some() {
787 None
788 } else {
789 unh_e1_storage = segments
790 .iter()
791 .find(|s| s.tag == "UNH")
792 .and_then(|s| s.get_element(1));
793 unh_e1_storage
794 };
795
796 let message_type = context
797 .message_type
798 .or_else(|| unh_e1.and_then(|e| e.get_component(0)));
799
800 if !self.message_types.is_empty()
801 && !message_type.is_some_and(|mt| self.message_types.contains(mt))
802 {
803 return;
804 }
805
806 if let Some(bound_release) = &self.release {
807 let msg_association = segments
808 .iter()
809 .find(|s| s.tag == "UNH")
810 .and_then(|s| s.get_element(1))
811 .and_then(|e| e.get_component(4));
812 if msg_association != Some(bound_release.as_str()) {
813 return;
814 }
815 }
816
817 let mut rule_issues: Vec<ValidationIssue> = Vec::new();
818
819 for named in &self.rules {
820 let errors_before = report.errors.len();
821 named.rule.evaluate(segments, context, &mut rule_issues);
822 for issue in rule_issues.drain(..) {
823 match issue.severity {
824 ValidationSeverity::Critical | ValidationSeverity::Error => {
825 report.add_error(issue);
826 }
827 ValidationSeverity::Warning => {
828 report.add_warning(issue);
829 }
830 ValidationSeverity::Info => {
831 report.add_info(issue);
832 }
833 }
834 }
835 if self.bail_on_first_error && report.errors.len() > errors_before {
836 return;
837 }
838 }
839 }
840
841 fn validate_group_batch(
842 &self,
843 root: &SegmentGroupIndexed,
844 all_segments: &[Segment<'_>],
845 report: &mut ValidationReport,
846 context: &ValidationRuleContext<'_>,
847 ) {
848 if self.group_rules.is_empty() {
849 return;
850 }
851
852 let unh_e1_storage;
854 let unh_e1: Option<&crate::model::Element<'_>> = if context.message_type.is_some() {
855 None
856 } else {
857 unh_e1_storage = all_segments
858 .iter()
859 .find(|s| s.tag == "UNH")
860 .and_then(|s| s.get_element(1));
861 unh_e1_storage
862 };
863 let message_type = context
864 .message_type
865 .or_else(|| unh_e1.and_then(|e| e.get_component(0)));
866
867 if !self.message_types.is_empty()
868 && !message_type.is_some_and(|mt| self.message_types.contains(mt))
869 {
870 return;
871 }
872
873 if let Some(bound_release) = &self.release {
874 let msg_association = all_segments
875 .iter()
876 .find(|s| s.tag == "UNH")
877 .and_then(|s| s.get_element(1))
878 .and_then(|e| e.get_component(4));
879 if msg_association != Some(bound_release.as_str()) {
880 return;
881 }
882 }
883
884 self.walk_group_tree(root, all_segments, report, context);
885 }
886
887 fn fork(&self) -> Box<dyn Validator + Send + Sync> {
888 Box::new(self.clone())
889 }
890}
891
892impl Clone for ProfileRulePack {
893 fn clone(&self) -> Self {
894 Self {
895 name: self.name.clone(),
896 message_types: self.message_types.clone(),
897 release: self.release.clone(),
898 rules: self.rules.clone(),
899 group_rules: self.group_rules.clone(),
900 bail_on_first_error: self.bail_on_first_error,
901 }
902 }
903}
904
905impl std::fmt::Debug for ProfileRulePack {
906 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
907 f.debug_struct("ProfileRulePack")
908 .field("name", &self.name)
909 .field("message_types", &self.message_types)
910 .field("release", &self.release)
911 .field("rule_count", &self.rules.len())
912 .field("group_rule_count", &self.group_rules.len())
913 .field("bail_on_first_error", &self.bail_on_first_error)
914 .finish()
915 }
916}
917
918impl Validator for Arc<ProfileRulePack> {
943 fn validate_batch(
944 &self,
945 segments: &[Segment<'_>],
946 report: &mut ValidationReport,
947 context: &ValidationRuleContext<'_>,
948 ) {
949 self.as_ref().validate_batch(segments, report, context);
950 }
951
952 fn validate_group_batch(
953 &self,
954 root: &SegmentGroupIndexed,
955 all_segments: &[Segment<'_>],
956 report: &mut ValidationReport,
957 context: &ValidationRuleContext<'_>,
958 ) {
959 self.as_ref()
960 .validate_group_batch(root, all_segments, report, context);
961 }
962
963 fn fork(&self) -> Box<dyn Validator + Send + Sync> {
964 Box::new(Arc::clone(self))
965 }
966}