1use crate::cursor::SegmentCursor;
8use crate::diagnostic::{StructureDiagnostic, StructureDiagnosticKind};
9use crate::matcher;
10use crate::tokenize::OwnedSegment;
11use crate::AssemblyError;
12use mig_types::schema::mig::{MigSchema, MigSegment, MigSegmentGroup};
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct AssembledTree {
18 pub segments: Vec<AssembledSegment>,
19 pub groups: Vec<AssembledGroup>,
20 #[serde(default)]
23 pub post_group_start: usize,
24 #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
29 pub inter_group_segments: std::collections::BTreeMap<usize, Vec<AssembledSegment>>,
30}
31
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34pub struct AssembledSegment {
35 pub tag: String,
36 pub elements: Vec<Vec<String>>,
38 #[serde(default, skip_serializing_if = "Option::is_none")]
42 pub mig_number: Option<String>,
43 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub segment_number: Option<u32>,
50}
51
52#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
54pub struct AssembledGroup {
55 pub group_id: String,
56 pub repetitions: Vec<AssembledGroupInstance>,
57}
58
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61pub struct AssembledGroupInstance {
62 pub segments: Vec<AssembledSegment>,
63 pub child_groups: Vec<AssembledGroup>,
64 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub entry_mig_number: Option<String>,
67 #[serde(default, skip_serializing_if = "Vec::is_empty")]
75 pub variant_mig_numbers: Vec<String>,
76 #[serde(default, skip_serializing_if = "Vec::is_empty")]
80 pub skipped_segments: Vec<AssembledSegment>,
81 #[serde(default, skip_serializing_if = "Vec::is_empty")]
85 pub skipped_positions: Vec<usize>,
86}
87
88impl AssembledGroupInstance {
89 pub fn as_assembled_tree(&self) -> AssembledTree {
96 AssembledTree {
97 segments: self.segments.clone(),
98 groups: self.child_groups.clone(),
99 post_group_start: self.segments.len(),
100 inter_group_segments: std::collections::BTreeMap::new(),
101 }
102 }
103}
104
105#[derive(Debug, Clone, Default)]
107pub struct AssemblerConfig {
108 pub skip_unknown_segments: bool,
115
116 pub qualifier_map: std::collections::HashMap<String, (usize, usize, String)>,
128
129 pub strict_code_matching: bool,
140}
141
142pub struct Assembler<'a> {
147 mig: &'a MigSchema,
148 config: AssemblerConfig,
149}
150
151impl<'a> Assembler<'a> {
152 pub fn new(mig: &'a MigSchema) -> Self {
153 Self {
154 mig,
155 config: AssemblerConfig::default(),
156 }
157 }
158
159 pub fn with_config(mig: &'a MigSchema, config: AssemblerConfig) -> Self {
160 Self { mig, config }
161 }
162
163 fn top_level_enclosing_for_group(
177 &self,
178 current_group_idx: usize,
179 matched_seg_indices: &[usize],
180 ) -> std::collections::HashSet<String> {
181 if !self.config.skip_unknown_segments {
182 return std::collections::HashSet::new();
183 }
184 let mut tags: std::collections::HashSet<String> = self
185 .mig
186 .segments
187 .iter()
188 .enumerate()
189 .filter_map(|(i, s)| {
190 if matched_seg_indices.contains(&i) {
191 None
192 } else {
193 Some(s.id.clone())
194 }
195 })
196 .collect();
197 for (idx, group) in self.mig.segment_groups.iter().enumerate() {
198 if idx < current_group_idx {
199 continue;
200 }
201 if let Some(entry) = group.segments.first() {
202 tags.insert(entry.id.clone());
203 }
204 }
205 tags
206 }
207
208 pub fn assemble_generic(
210 &self,
211 segments: &[OwnedSegment],
212 ) -> Result<AssembledTree, AssemblyError> {
213 let mut cursor = SegmentCursor::new(segments.len());
214 let mut tree = AssembledTree {
215 segments: Vec::new(),
216 groups: Vec::new(),
217 post_group_start: 0,
218 inter_group_segments: std::collections::BTreeMap::new(),
219 };
220
221 let mut matched_seg_indices = Vec::new();
223
224 for (i, mig_seg) in self.mig.segments.iter().enumerate() {
226 if cursor.is_exhausted() {
227 break;
228 }
229 if let Some(assembled) = self.try_consume_segment(segments, &mut cursor, mig_seg)? {
230 tree.segments.push(assembled);
231 matched_seg_indices.push(i);
232 }
233 }
234
235 let mut group_idx = 0;
244 while group_idx < self.mig.segment_groups.len() {
245 if cursor.is_exhausted() {
246 break;
247 }
248
249 let mig_group = &self.mig.segment_groups[group_idx];
250
251 if self.config.skip_unknown_segments {
260 let tree_group_idx = tree.groups.len();
261 while !cursor.is_exhausted() {
262 let seg = &segments[cursor.position()];
263 let tag = &seg.id;
264 let is_unmatched_root_seg = self
265 .mig
266 .segments
267 .iter()
268 .enumerate()
269 .any(|(i, ms)| !matched_seg_indices.contains(&i) && ms.id == *tag);
270 let is_any_group_entry = self
271 .mig
272 .segment_groups
273 .iter()
274 .any(|g| g.segments.first().is_some_and(|s| s.id == *tag));
275 if is_unmatched_root_seg || is_any_group_entry {
276 break;
277 }
278 tree.inter_group_segments
279 .entry(tree_group_idx)
280 .or_default()
281 .push(owned_to_assembled(seg));
282 cursor.advance();
283 }
284 if cursor.is_exhausted() {
285 break;
286 }
287 }
288
289 let tree_group_idx = tree.groups.len();
291 for (i, mig_seg) in self.mig.segments.iter().enumerate() {
292 if cursor.is_exhausted() {
293 break;
294 }
295 if matched_seg_indices.contains(&i) {
296 continue;
297 }
298 if let Some(assembled) = self.try_consume_segment(segments, &mut cursor, mig_seg)? {
299 tree.inter_group_segments
300 .entry(tree_group_idx)
301 .or_default()
302 .push(assembled);
303 matched_seg_indices.push(i);
304 }
305 }
306
307 let top_enclosing = self.top_level_enclosing_for_group(group_idx, &matched_seg_indices);
313
314 if mig_group.variant_code.is_some() {
316 let variant_count = self.mig.segment_groups[group_idx..]
317 .iter()
318 .take_while(|g| g.id == mig_group.id && g.variant_code.is_some())
319 .count();
320 let variant_end = group_idx + variant_count;
321
322 let variant_groups = &self.mig.segment_groups[group_idx..variant_end];
323 if let Some(combined) = self.try_consume_variant_groups(
324 segments,
325 &mut cursor,
326 variant_groups,
327 &top_enclosing,
328 )? {
329 tree.groups.push(combined);
330 }
331 group_idx = variant_end;
332 } else {
333 if let Some(assembled) =
334 self.try_consume_group(segments, &mut cursor, mig_group, &top_enclosing)?
335 {
336 tree.groups.push(assembled);
337 }
338 group_idx += 1;
339 }
340 }
341
342 tree.post_group_start = tree.segments.len();
344
345 for (i, mig_seg) in self.mig.segments.iter().enumerate() {
347 if cursor.is_exhausted() {
348 break;
349 }
350 if matched_seg_indices.contains(&i) {
351 continue;
352 }
353 if let Some(assembled) = self.try_consume_segment(segments, &mut cursor, mig_seg)? {
354 tree.segments.push(assembled);
355 }
356 }
357
358 Ok(tree)
359 }
360
361 fn try_consume_segment(
362 &self,
363 segments: &[OwnedSegment],
364 cursor: &mut SegmentCursor,
365 mig_seg: &MigSegment,
366 ) -> Result<Option<AssembledSegment>, AssemblyError> {
367 if cursor.is_exhausted() {
368 return Ok(None);
369 }
370 let seg = &segments[cursor.position()];
371 if matcher::matches_segment_tag(&seg.id, &mig_seg.id) {
372 if let Some(ref num) = mig_seg.number {
375 if let Some((el_idx, comp_idx, expected)) = self.config.qualifier_map.get(num) {
376 let actual = seg
377 .elements
378 .get(*el_idx)
379 .and_then(|e| e.get(*comp_idx))
380 .map(|s| s.as_str())
381 .unwrap_or("");
382 if actual != expected {
383 return Ok(None); }
385 }
386 }
387 let mut assembled = owned_to_assembled(seg);
395 assembled.mig_number = mig_seg.number.clone();
396 cursor.advance();
397 Ok(Some(assembled))
398 } else {
399 Ok(None) }
401 }
402
403 fn consume_entry_run_best_match(
419 &self,
420 segments: &[OwnedSegment],
421 cursor: &mut SegmentCursor,
422 entry_slots: &[MigSegment],
423 repeat: bool,
424 instance: &mut AssembledGroupInstance,
425 ) -> Result<(), AssemblyError> {
426 let capacity = |slot: &MigSegment| {
427 if repeat {
428 slot.max_rep_spec.max(1) as usize
429 } else {
430 1
431 }
432 };
433 let mut used = vec![0usize; entry_slots.len()];
434 let total: usize = entry_slots.iter().map(capacity).sum();
435 for _ in 0..total {
436 if cursor.is_exhausted() {
437 break;
438 }
439 let seg = &segments[cursor.position()];
440 let mut strict_match: Option<usize> = None;
441 let mut tag_match: Option<usize> = None;
442 for (i, slot) in entry_slots.iter().enumerate() {
443 if used[i] >= capacity(slot) {
444 continue;
445 }
446 if !matcher::matches_segment_tag(&seg.id, &slot.id) {
447 continue;
448 }
449 if !self.segment_passes_qualifier_map(seg, slot) {
450 continue;
451 }
452 if tag_match.is_none() && used[i] == 0 {
454 tag_match = Some(i);
455 }
456 if strict_match.is_none() && segment_matches_mig_codes(seg, slot) {
457 strict_match = Some(i);
458 }
459 }
460 let Some(i) = strict_match.or(tag_match) else {
461 break;
462 };
463 used[i] += 1;
464 let slot = &entry_slots[i];
465 let mut assembled = owned_to_assembled(seg);
466 assembled.mig_number = slot.number.clone();
467 instance.segments.push(assembled);
468 cursor.advance();
469 }
470 Ok(())
471 }
472
473 fn segment_passes_qualifier_map(&self, seg: &OwnedSegment, mig_seg: &MigSegment) -> bool {
474 let Some(ref num) = mig_seg.number else {
475 return true;
476 };
477 let Some((el_idx, comp_idx, expected)) = self.config.qualifier_map.get(num) else {
478 return true;
479 };
480 let actual = seg
481 .elements
482 .get(*el_idx)
483 .and_then(|e| e.get(*comp_idx))
484 .map(|s| s.as_str())
485 .unwrap_or("");
486 actual == expected
487 }
488
489 fn try_consume_group(
490 &self,
491 segments: &[OwnedSegment],
492 cursor: &mut SegmentCursor,
493 mig_group: &MigSegmentGroup,
494 enclosing: &std::collections::HashSet<String>,
495 ) -> Result<Option<AssembledGroup>, AssemblyError> {
496 let mut repetitions = Vec::new();
497 let entry_segment = mig_group.segments.first().ok_or_else(|| {
498 AssemblyError::ParseError(format!("Group {} has no segments", mig_group.id))
499 })?;
500
501 let nested_enclosing: std::collections::HashSet<String> =
506 if self.config.skip_unknown_segments {
507 let mut set = enclosing.clone();
508 set.extend(group_local_scope(mig_group));
509 set
510 } else {
511 std::collections::HashSet::new()
512 };
513
514 while !cursor.is_exhausted() {
516 let iter_start = cursor.position();
517 let seg = &segments[cursor.position()];
518 if !matcher::matches_segment_tag(&seg.id, &entry_segment.id) {
519 break; }
521
522 if !mig_group.variant_codes.is_empty() {
524 let (ei, ci) = mig_group.variant_qualifier_position.unwrap_or((0, 0));
525 let actual_qual = seg
526 .elements
527 .get(ei)
528 .and_then(|e| e.get(ci))
529 .map(|s| s.as_str())
530 .unwrap_or("");
531 if !mig_group
532 .variant_codes
533 .iter()
534 .any(|c| actual_qual.eq_ignore_ascii_case(c))
535 {
536 break;
537 }
538 } else if let Some(ref expected_code) = mig_group.variant_code {
539 let (ei, ci) = mig_group.variant_qualifier_position.unwrap_or((0, 0));
540 let actual_qual = seg
541 .elements
542 .get(ei)
543 .and_then(|e| e.get(ci))
544 .map(|s| s.as_str())
545 .unwrap_or("");
546 if !actual_qual.eq_ignore_ascii_case(expected_code) {
547 break;
548 }
549 }
550
551 let mut instance = AssembledGroupInstance {
552 segments: Vec::new(),
553 child_groups: Vec::new(),
554 entry_mig_number: entry_segment.number.clone(),
555 variant_mig_numbers: collect_mig_numbers(mig_group),
556 skipped_segments: Vec::new(),
557 skipped_positions: Vec::new(),
558 };
559
560 let mut slot_idx = 0;
571 let mut is_entry_run = true;
572 while slot_idx < mig_group.segments.len() {
573 if cursor.is_exhausted() {
574 break;
575 }
576 let current_tag = &mig_group.segments[slot_idx].id;
577 let run_len = mig_group.segments[slot_idx..]
578 .iter()
579 .take_while(|s| s.id == *current_tag)
580 .count();
581
582 if is_entry_run {
583 let entry_slots = &mig_group.segments[slot_idx..slot_idx + run_len];
585 if self.config.strict_code_matching && run_len > 1 {
586 self.consume_entry_run_best_match(
592 segments,
593 cursor,
594 entry_slots,
595 false,
596 &mut instance,
597 )?;
598 } else {
599 for slot in entry_slots {
600 if cursor.is_exhausted() {
601 break;
602 }
603 if let Some(assembled) =
604 self.try_consume_segment(segments, cursor, slot)?
605 {
606 instance.segments.push(assembled);
607 }
608 }
609 }
610 is_entry_run = false;
611 } else if matcher::matches_segment_tag(current_tag, &entry_segment.id) {
612 if cursor.is_exhausted() {
630 break;
631 }
632 let seg = &segments[cursor.position()];
633 if !matcher::matches_segment_tag(&seg.id, current_tag) {
634 break;
635 }
636 let has_following_non_entry = if cursor.position() + 1 < segments.len() {
639 let next = &segments[cursor.position() + 1];
640 !matcher::matches_segment_tag(&next.id, &entry_segment.id)
641 && mig_group.segments.iter().any(|s| {
642 matcher::matches_segment_tag(&next.id, &s.id)
643 && !matcher::matches_segment_tag(&s.id, &entry_segment.id)
644 })
645 } else {
646 false
647 };
648 if has_following_non_entry {
649 instance.segments.push(owned_to_assembled(seg));
651 cursor.advance();
652 } else {
653 break;
655 }
656 } else {
657 let slots = &mig_group.segments[slot_idx..slot_idx + run_len];
663 if self.config.strict_code_matching && run_len > 1 {
664 self.consume_entry_run_best_match(
665 segments,
666 cursor,
667 slots,
668 true,
669 &mut instance,
670 )?;
671 } else {
672 for slot in slots {
673 if cursor.is_exhausted() {
674 break;
675 }
676 if let Some(assembled) =
677 self.try_consume_segment(segments, cursor, slot)?
678 {
679 instance.segments.push(assembled);
680 }
681 }
682 }
683 while !cursor.is_exhausted() {
685 let seg = &segments[cursor.position()];
686 if matcher::matches_segment_tag(&seg.id, current_tag) {
687 instance.segments.push(owned_to_assembled(seg));
688 cursor.advance();
689 } else {
690 break;
691 }
692 }
693 }
694
695 slot_idx += run_len;
696
697 if self.config.skip_unknown_segments {
704 while !cursor.is_exhausted() {
705 let seg = &segments[cursor.position()];
706 if matcher::matches_segment_tag(&seg.id, &entry_segment.id) {
708 break;
709 }
710 if mig_group.segments[slot_idx..]
712 .iter()
713 .any(|s| matcher::matches_segment_tag(&seg.id, &s.id))
714 {
715 break;
716 }
717 if mig_group.nested_groups.iter().any(|ng| {
719 ng.segments
720 .first()
721 .is_some_and(|es| matcher::matches_segment_tag(&seg.id, &es.id))
722 }) {
723 break;
724 }
725 if enclosing.contains(&seg.id) {
731 break;
732 }
733 instance.skipped_positions.push(cursor.position());
735 instance.skipped_segments.push(owned_to_assembled(seg));
736 cursor.advance();
737 }
738 }
739 }
740
741 loop {
751 let pass_start = cursor.position();
752 let mut nested_idx = 0;
753 while nested_idx < mig_group.nested_groups.len() {
754 if cursor.is_exhausted() {
755 break;
756 }
757 let nested = &mig_group.nested_groups[nested_idx];
758
759 if nested.variant_code.is_some() {
760 let variant_count = mig_group.nested_groups[nested_idx..]
762 .iter()
763 .take_while(|g| g.id == nested.id && g.variant_code.is_some())
764 .count();
765 let variant_end = nested_idx + variant_count;
766 let variant_groups = &mig_group.nested_groups[nested_idx..variant_end];
767 if let Some(combined) = self.try_consume_variant_groups(
768 segments,
769 cursor,
770 variant_groups,
771 &nested_enclosing,
772 )? {
773 push_or_merge_child(&mut instance.child_groups, combined);
774 }
775 nested_idx = variant_end;
776 } else {
777 if let Some(assembled) =
778 self.try_consume_group(segments, cursor, nested, &nested_enclosing)?
779 {
780 push_or_merge_child(&mut instance.child_groups, assembled);
781 }
782 nested_idx += 1;
783 }
784 }
785
786 if !self.config.skip_unknown_segments || cursor.is_exhausted() {
787 break;
788 }
789 let seg = &segments[cursor.position()];
790 if enclosing.contains(&seg.id) {
805 break;
806 }
807 if cursor.position() == pass_start && !self.config.skip_unknown_segments {
811 break;
812 }
813
814 instance.skipped_positions.push(cursor.position());
815 instance.skipped_segments.push(owned_to_assembled(seg));
816 cursor.advance();
817 }
818
819 if cursor.position() == iter_start {
825 break;
826 }
827 repetitions.push(instance);
828 }
829
830 if repetitions.is_empty() {
831 Ok(None)
832 } else {
833 Ok(Some(AssembledGroup {
834 group_id: mig_group.id.clone(),
835 repetitions,
836 }))
837 }
838 }
839
840 fn try_consume_variant_groups(
846 &self,
847 segments: &[OwnedSegment],
848 cursor: &mut SegmentCursor,
849 variants: &[MigSegmentGroup],
850 enclosing: &std::collections::HashSet<String>,
851 ) -> Result<Option<AssembledGroup>, AssemblyError> {
852 let group_id = variants[0].id.clone();
853 let entry_tag = variants[0]
854 .segments
855 .first()
856 .map(|s| s.id.as_str())
857 .unwrap_or("");
858 let mut all_reps = Vec::new();
859
860 while !cursor.is_exhausted() {
861 let seg = &segments[cursor.position()];
862 if !matcher::matches_segment_tag(&seg.id, entry_tag) {
863 break;
864 }
865
866 let matched = variants.iter().find(|v| {
870 let (ei, ci) = v.variant_qualifier_position.unwrap_or((0, 0));
871 let actual_qual = seg
872 .elements
873 .get(ei)
874 .and_then(|e| e.get(ci))
875 .map(|s| s.as_str())
876 .unwrap_or("");
877 if !v.variant_codes.is_empty() {
878 v.variant_codes
879 .iter()
880 .any(|c| actual_qual.eq_ignore_ascii_case(c))
881 } else if let Some(ref expected_code) = v.variant_code {
882 actual_qual.eq_ignore_ascii_case(expected_code)
883 } else {
884 false
885 }
886 });
887
888 if let Some(variant) = matched {
889 if let Some(group) = self.try_consume_group(segments, cursor, variant, enclosing)? {
890 all_reps.extend(group.repetitions);
891 } else {
892 break;
893 }
894 } else {
895 if let Some(group) =
899 self.try_consume_group(segments, cursor, &variants[0], enclosing)?
900 {
901 all_reps.extend(group.repetitions);
902 } else {
903 break;
904 }
905 }
906 }
907
908 if all_reps.is_empty() {
909 Ok(None)
910 } else {
911 Ok(Some(AssembledGroup {
912 group_id,
913 repetitions: all_reps,
914 }))
915 }
916 }
917
918 pub fn assemble_with_diagnostics(
923 &self,
924 segments: &[OwnedSegment],
925 ) -> (AssembledTree, Vec<StructureDiagnostic>) {
926 let mut diagnostics = Vec::new();
927
928 let tree = match self.assemble_generic(segments) {
929 Ok(tree) => tree,
930 Err(e) => {
931 diagnostics.push(StructureDiagnostic {
932 kind: StructureDiagnosticKind::UnexpectedSegment,
933 segment_id: String::new(),
934 position: 0,
935 message: format!("Assembly failed: {e}"),
936 });
937 return (
938 AssembledTree {
939 segments: Vec::new(),
940 groups: Vec::new(),
941 post_group_start: 0,
942 inter_group_segments: std::collections::BTreeMap::new(),
943 },
944 diagnostics,
945 );
946 }
947 };
948
949 let consumed = count_tree_segments(&tree);
953
954 for (i, seg) in segments.iter().enumerate().skip(consumed) {
957 diagnostics.push(StructureDiagnostic {
958 kind: StructureDiagnosticKind::UnexpectedSegment,
959 segment_id: seg.id.clone(),
960 position: i,
961 message: format!(
962 "Segment '{}' at position {} was not consumed by MIG-guided assembly",
963 seg.id, i
964 ),
965 });
966 }
967
968 let mut skipped: Vec<SkippedSegment> = Vec::new();
972 let top_level: Vec<&MigSegmentGroup> = self.mig.segment_groups.iter().collect();
973 for group in &tree.groups {
974 collect_skipped_from_group(group, &top_level, &mut skipped);
975 }
976 skipped.sort_by_key(|s| s.position);
977 for SkippedSegment {
978 position: pos,
979 tag,
980 orphaned_in,
981 } in skipped
982 {
983 diagnostics.push(match orphaned_in {
984 Some((group_id, entry)) => StructureDiagnostic {
987 kind: StructureDiagnosticKind::OrphanedGroupSegment,
988 segment_id: tag.clone(),
989 position: pos,
990 message: format!(
991 "Segment '{tag}' at position {pos} belongs to group {group_id}, but that group's entry segment '{entry}' is missing; the assembler advanced past it and its content is lost",
992 ),
993 },
994 None if mig_defines_tag(self.mig, &tag) => StructureDiagnostic {
995 kind: StructureDiagnosticKind::SkippedUnknownSegment,
996 segment_id: tag.clone(),
997 position: pos,
998 message: format!(
999 "Segment '{tag}' at position {pos} has no slot at this point of the PID-filtered MIG; the assembler advanced past it",
1000 ),
1001 },
1002 None => StructureDiagnostic {
1003 kind: StructureDiagnosticKind::SkippedUnknownSegment,
1004 segment_id: tag.clone(),
1005 position: pos,
1006 message: format!(
1007 "Segment '{tag}' at position {pos} is not defined in the PID-filtered MIG; the assembler advanced past it",
1008 ),
1009 },
1010 });
1011 }
1012
1013 (tree, diagnostics)
1014 }
1015}
1016
1017struct SkippedSegment {
1020 position: usize,
1021 tag: String,
1022 orphaned_in: Option<(String, String)>,
1024}
1025
1026fn collect_skipped_from_group(
1029 group: &AssembledGroup,
1030 mig_candidates: &[&MigSegmentGroup],
1031 out: &mut Vec<SkippedSegment>,
1032) {
1033 let defs: Vec<&MigSegmentGroup> = mig_candidates
1034 .iter()
1035 .copied()
1036 .filter(|g| g.id == group.group_id)
1037 .collect();
1038 let nested: Vec<&MigSegmentGroup> = defs.iter().flat_map(|g| &g.nested_groups).collect();
1039 for rep in &group.repetitions {
1040 for (i, seg) in rep.skipped_segments.iter().enumerate() {
1041 let pos = rep.skipped_positions.get(i).copied().unwrap_or(0);
1042 out.push(SkippedSegment {
1043 position: pos,
1044 tag: seg.tag.clone(),
1045 orphaned_in: orphaning_group(&nested, &seg.tag),
1046 });
1047 }
1048 for child in &rep.child_groups {
1049 collect_skipped_from_group(child, &nested, out);
1050 }
1051 }
1052}
1053
1054fn orphaning_group(nested: &[&MigSegmentGroup], tag: &str) -> Option<(String, String)> {
1060 if nested
1061 .iter()
1062 .any(|g| g.segments.first().is_some_and(|e| e.id == tag))
1063 {
1064 return None;
1065 }
1066 nested.iter().find_map(|g| {
1067 let (entry, rest) = g.segments.split_first()?;
1068 rest.iter()
1069 .any(|s| s.id == tag)
1070 .then(|| (g.id.clone(), entry.id.clone()))
1071 })
1072}
1073
1074fn mig_defines_tag(mig: &MigSchema, tag: &str) -> bool {
1076 fn in_group(g: &MigSegmentGroup, tag: &str) -> bool {
1077 g.segments.iter().any(|s| s.id == tag) || g.nested_groups.iter().any(|n| in_group(n, tag))
1078 }
1079 mig.segments.iter().any(|s| s.id == tag) || mig.segment_groups.iter().any(|g| in_group(g, tag))
1080}
1081
1082fn count_tree_segments(tree: &AssembledTree) -> usize {
1083 let mut count = tree.segments.len();
1084 for group in &tree.groups {
1085 count += count_group_segments(group);
1086 }
1087 for segs in tree.inter_group_segments.values() {
1089 count += segs.len();
1090 }
1091 count
1092}
1093
1094fn count_group_segments(group: &AssembledGroup) -> usize {
1095 let mut count = 0;
1096 for rep in &group.repetitions {
1097 count += rep.segments.len();
1098 count += rep.skipped_segments.len();
1099 for child in &rep.child_groups {
1100 count += count_group_segments(child);
1101 }
1102 }
1103 count
1104}
1105
1106fn group_local_scope(mig_group: &MigSegmentGroup) -> std::collections::HashSet<String> {
1112 let mut tags = std::collections::HashSet::new();
1113 for seg in &mig_group.segments {
1114 tags.insert(seg.id.clone());
1115 }
1116 for nested in &mig_group.nested_groups {
1117 if let Some(entry) = nested.segments.first() {
1118 tags.insert(entry.id.clone());
1119 }
1120 }
1121 tags
1122}
1123
1124fn push_or_merge_child(child_groups: &mut Vec<AssembledGroup>, new: AssembledGroup) {
1129 if let Some(existing) = child_groups.iter_mut().find(|g| g.group_id == new.group_id) {
1130 existing.repetitions.extend(new.repetitions);
1131 } else {
1132 child_groups.push(new);
1133 }
1134}
1135
1136fn collect_mig_numbers(group: &MigSegmentGroup) -> Vec<String> {
1141 let mut numbers = Vec::new();
1142 for seg in &group.segments {
1143 if let Some(ref num) = seg.number {
1144 numbers.push(num.clone());
1145 }
1146 }
1147 for nested in &group.nested_groups {
1148 numbers.extend(collect_mig_numbers(nested));
1149 for q in &nested.variant_entry_qualifiers {
1152 for n in &q.mig_numbers {
1153 if !numbers.contains(n) {
1154 numbers.push(n.clone());
1155 }
1156 }
1157 }
1158 }
1159 numbers
1160}
1161
1162pub fn owned_to_assembled(seg: &OwnedSegment) -> AssembledSegment {
1163 AssembledSegment {
1164 tag: seg.id.clone(),
1165 elements: seg.elements.clone(),
1166 mig_number: None,
1167 segment_number: Some(seg.segment_number),
1168 }
1169}
1170
1171fn segment_matches_mig_codes(seg: &OwnedSegment, mig_seg: &MigSegment) -> bool {
1179 let actual_at = |el: usize, c: usize| -> &str {
1180 seg.elements
1181 .get(el)
1182 .and_then(|e| e.get(c))
1183 .map(|s| s.as_str())
1184 .unwrap_or("")
1185 };
1186 for de in &mig_seg.data_elements {
1187 if !de.codes.is_empty() {
1188 let actual = actual_at(de.position, 0);
1189 if !actual.is_empty() && !de.codes.iter().any(|c| c.value == actual) {
1190 return false;
1191 }
1192 }
1193 }
1194 for comp in &mig_seg.composites {
1195 for de in &comp.data_elements {
1196 if !de.codes.is_empty() {
1197 let actual = actual_at(comp.position, de.position);
1198 if !actual.is_empty() && !de.codes.iter().any(|c| c.value == actual) {
1199 return false;
1200 }
1201 }
1202 }
1203 }
1204 true
1205}
1206
1207#[cfg(test)]
1208mod tests {
1209 use super::*;
1210 use crate::test_support::{make_mig_group, make_mig_group_with_variant, make_mig_segment};
1211
1212 fn make_owned_seg(id: &str, elements: Vec<Vec<&str>>) -> OwnedSegment {
1213 OwnedSegment {
1214 id: id.to_string(),
1215 elements: elements
1216 .into_iter()
1217 .map(|e| e.into_iter().map(|c| c.to_string()).collect())
1218 .collect(),
1219 segment_number: 0,
1220 }
1221 }
1222
1223 fn make_mig_schema(segments: Vec<&str>, groups: Vec<MigSegmentGroup>) -> MigSchema {
1224 MigSchema {
1225 message_type: "UTILMD".to_string(),
1226 variant: Some("Strom".to_string()),
1227 version: "S2.1".to_string(),
1228 publication_date: "2025-03-20".to_string(),
1229 author: "BDEW".to_string(),
1230 format_version: "FV2504".to_string(),
1231 source_file: "test".to_string(),
1232 segments: segments.into_iter().map(make_mig_segment).collect(),
1233 segment_groups: groups,
1234 }
1235 }
1236
1237 #[test]
1238 fn test_assembler_top_level_segments_only() {
1239 let mig = make_mig_schema(vec!["UNH", "BGM", "DTM", "UNT"], vec![]);
1240
1241 let segments = vec![
1242 make_owned_seg("UNH", vec![vec!["001", "UTILMD:D:11A:UN:S2.1"]]),
1243 make_owned_seg("BGM", vec![vec!["E01", "DOC001"]]),
1244 make_owned_seg("DTM", vec![vec!["137", "20250101", "102"]]),
1245 make_owned_seg("UNT", vec![vec!["4", "001"]]),
1246 ];
1247
1248 let assembler = Assembler::new(&mig);
1249 let result = assembler.assemble_generic(&segments).unwrap();
1250
1251 assert_eq!(result.segments.len(), 4);
1252 assert_eq!(result.segments[0].tag, "UNH");
1253 assert_eq!(result.segments[1].tag, "BGM");
1254 assert_eq!(result.segments[2].tag, "DTM");
1255 assert_eq!(result.segments[3].tag, "UNT");
1256 assert!(result.groups.is_empty());
1257 }
1258
1259 #[test]
1260 fn test_assembler_with_segment_group() {
1261 let mig = make_mig_schema(
1262 vec!["UNH", "BGM"],
1263 vec![
1264 make_mig_group("SG2", vec!["NAD"], vec![]),
1265 make_mig_group("SG4", vec!["IDE", "STS"], vec![]),
1266 ],
1267 );
1268
1269 let segments = vec![
1270 make_owned_seg("UNH", vec![vec!["001"]]),
1271 make_owned_seg("BGM", vec![vec!["E01"]]),
1272 make_owned_seg("NAD", vec![vec!["MS", "9900123"]]),
1273 make_owned_seg("NAD", vec![vec!["MR", "9900456"]]),
1274 make_owned_seg("IDE", vec![vec!["24", "TX001"]]),
1275 make_owned_seg("STS", vec![vec!["7"], vec!["Z33"]]),
1276 ];
1277
1278 let assembler = Assembler::new(&mig);
1279 let result = assembler.assemble_generic(&segments).unwrap();
1280
1281 assert_eq!(result.segments.len(), 2);
1283 assert_eq!(result.groups.len(), 2);
1285 assert_eq!(result.groups[0].group_id, "SG2");
1286 assert_eq!(result.groups[0].repetitions.len(), 2);
1287 assert_eq!(result.groups[0].repetitions[0].segments[0].tag, "NAD");
1288 assert_eq!(result.groups[0].repetitions[1].segments[0].tag, "NAD");
1289 assert_eq!(result.groups[1].group_id, "SG4");
1291 assert_eq!(result.groups[1].repetitions.len(), 1);
1292 assert_eq!(result.groups[1].repetitions[0].segments.len(), 2);
1293 }
1294
1295 #[test]
1296 fn test_assembler_nested_groups() {
1297 let sg3 = make_mig_group("SG3", vec!["CTA", "COM"], vec![]);
1298 let mig = make_mig_schema(
1299 vec!["UNH", "BGM"],
1300 vec![make_mig_group("SG2", vec!["NAD"], vec![sg3])],
1301 );
1302
1303 let segments = vec![
1304 make_owned_seg("UNH", vec![vec!["001"]]),
1305 make_owned_seg("BGM", vec![vec!["E01"]]),
1306 make_owned_seg("NAD", vec![vec!["MS", "9900123"]]),
1307 make_owned_seg("CTA", vec![vec!["IC", "Kontakt"]]),
1308 make_owned_seg("COM", vec![vec!["040@example.com", "EM"]]),
1309 ];
1310
1311 let assembler = Assembler::new(&mig);
1312 let result = assembler.assemble_generic(&segments).unwrap();
1313
1314 let sg2 = &result.groups[0];
1316 assert_eq!(sg2.group_id, "SG2");
1317 assert_eq!(sg2.repetitions.len(), 1);
1318
1319 let sg2_inst = &sg2.repetitions[0];
1320 assert_eq!(sg2_inst.segments[0].tag, "NAD");
1321
1322 assert_eq!(sg2_inst.child_groups.len(), 1);
1324 let sg3 = &sg2_inst.child_groups[0];
1325 assert_eq!(sg3.group_id, "SG3");
1326 assert_eq!(sg3.repetitions[0].segments.len(), 2);
1327 assert_eq!(sg3.repetitions[0].segments[0].tag, "CTA");
1328 assert_eq!(sg3.repetitions[0].segments[1].tag, "COM");
1329 }
1330
1331 #[test]
1332 fn test_assembler_optional_segments_skipped() {
1333 let mig = make_mig_schema(vec!["UNH", "BGM", "DTM", "UNT"], vec![]);
1335
1336 let segments = vec![
1337 make_owned_seg("UNH", vec![vec!["001"]]),
1338 make_owned_seg("BGM", vec![vec!["E01"]]),
1339 make_owned_seg("UNT", vec![vec!["2", "001"]]),
1340 ];
1341
1342 let assembler = Assembler::new(&mig);
1343 let result = assembler.assemble_generic(&segments).unwrap();
1344
1345 assert_eq!(result.segments.len(), 3);
1347 assert_eq!(result.segments[0].tag, "UNH");
1348 assert_eq!(result.segments[1].tag, "BGM");
1349 assert_eq!(result.segments[2].tag, "UNT");
1350 }
1351
1352 #[test]
1353 fn test_assembler_empty_segments() {
1354 let mig = make_mig_schema(vec!["UNH"], vec![]);
1355 let assembler = Assembler::new(&mig);
1356 let result = assembler.assemble_generic(&[]).unwrap();
1357 assert!(result.segments.is_empty());
1358 assert!(result.groups.is_empty());
1359 }
1360
1361 #[test]
1362 fn test_assembler_preserves_element_data() {
1363 let mig = make_mig_schema(vec!["DTM"], vec![]);
1364
1365 let segments = vec![make_owned_seg(
1366 "DTM",
1367 vec![vec!["137", "202501010000+01", "303"]],
1368 )];
1369
1370 let assembler = Assembler::new(&mig);
1371 let result = assembler.assemble_generic(&segments).unwrap();
1372
1373 let dtm = &result.segments[0];
1374 assert_eq!(dtm.elements[0][0], "137");
1375 assert_eq!(dtm.elements[0][1], "202501010000+01");
1376 assert_eq!(dtm.elements[0][2], "303");
1377 }
1378
1379 #[test]
1380 fn test_group_instance_as_assembled_tree() {
1381 let sg5 = AssembledGroup {
1383 group_id: "SG5".to_string(),
1384 repetitions: vec![AssembledGroupInstance {
1385 segments: vec![AssembledSegment {
1386 tag: "LOC".to_string(),
1387 elements: vec![vec!["Z16".to_string(), "DE000111222333".to_string()]],
1388 mig_number: None,
1389 segment_number: None,
1390 }],
1391 child_groups: vec![],
1392 entry_mig_number: None,
1393 variant_mig_numbers: vec![],
1394 skipped_segments: vec![],
1395 skipped_positions: Vec::new(),
1396 }],
1397 };
1398
1399 let sg4_instance = AssembledGroupInstance {
1400 segments: vec![
1401 AssembledSegment {
1402 tag: "IDE".to_string(),
1403 elements: vec![vec!["24".to_string(), "TX001".to_string()]],
1404 mig_number: None,
1405 segment_number: None,
1406 },
1407 AssembledSegment {
1408 tag: "STS".to_string(),
1409 elements: vec![vec!["7".to_string()]],
1410 mig_number: None,
1411 segment_number: None,
1412 },
1413 ],
1414 child_groups: vec![sg5],
1415 entry_mig_number: None,
1416 variant_mig_numbers: vec![],
1417 skipped_segments: vec![],
1418 skipped_positions: Vec::new(),
1419 };
1420
1421 let sub_tree = sg4_instance.as_assembled_tree();
1422
1423 assert_eq!(sub_tree.segments.len(), 2);
1425 assert_eq!(sub_tree.segments[0].tag, "IDE");
1426 assert_eq!(sub_tree.segments[1].tag, "STS");
1427
1428 assert_eq!(sub_tree.groups.len(), 1);
1430 assert_eq!(sub_tree.groups[0].group_id, "SG5");
1431
1432 assert_eq!(sub_tree.post_group_start, 2);
1434 }
1435
1436 #[test]
1437 fn test_assembler_from_parsed_edifact() {
1438 let input = b"UNA:+.? 'UNB+UNOC:3+SENDER+RECEIVER+210101:1200+REF001'UNH+MSG001+UTILMD:D:11A:UN:S2.1'BGM+E01+DOC001+9'DTM+137:20250101:102'UNT+3+MSG001'UNZ+1+REF001'";
1440 let segments = crate::tokenize::parse_to_segments(input).unwrap();
1441
1442 let mig = make_mig_schema(vec!["UNB", "UNH", "BGM", "DTM", "UNT", "UNZ"], vec![]);
1443
1444 let assembler = Assembler::new(&mig);
1445 let result = assembler.assemble_generic(&segments).unwrap();
1446
1447 assert!(result.segments.iter().any(|s| s.tag == "UNH"));
1448 assert!(result.segments.iter().any(|s| s.tag == "BGM"));
1449 assert!(result.segments.iter().any(|s| s.tag == "DTM"));
1450 }
1451
1452 #[test]
1453 fn test_assemble_with_diagnostics_clean_input() {
1454 let mig = make_mig_schema(vec!["UNH", "BGM", "UNT"], vec![]);
1455 let segments = vec![
1456 make_owned_seg("UNH", vec![vec!["001"]]),
1457 make_owned_seg("BGM", vec![vec!["E01"]]),
1458 make_owned_seg("UNT", vec![vec!["2", "001"]]),
1459 ];
1460 let assembler = Assembler::new(&mig);
1461 let (tree, diagnostics) = assembler.assemble_with_diagnostics(&segments);
1462 assert_eq!(tree.segments.len(), 3);
1463 assert!(
1464 diagnostics.is_empty(),
1465 "Clean input should have no diagnostics"
1466 );
1467 }
1468
1469 #[test]
1470 fn test_assemble_with_diagnostics_unconsumed_segments() {
1471 let mig = make_mig_schema(vec!["UNH", "BGM"], vec![]);
1472 let segments = vec![
1473 make_owned_seg("UNH", vec![vec!["001"]]),
1474 make_owned_seg("BGM", vec![vec!["E01"]]),
1475 make_owned_seg("FTX", vec![vec!["AAA", "extra text"]]),
1476 ];
1477 let assembler = Assembler::new(&mig);
1478 let (tree, diagnostics) = assembler.assemble_with_diagnostics(&segments);
1479 assert_eq!(tree.segments.len(), 2);
1480 assert_eq!(diagnostics.len(), 1);
1481 assert_eq!(
1482 diagnostics[0].kind,
1483 StructureDiagnosticKind::UnexpectedSegment
1484 );
1485 assert_eq!(diagnostics[0].segment_id, "FTX");
1486 assert_eq!(diagnostics[0].position, 2);
1487 }
1488
1489 #[test]
1490 fn test_assemble_with_diagnostics_multiple_unconsumed() {
1491 let mig = make_mig_schema(vec!["UNH"], vec![]);
1492 let segments = vec![
1493 make_owned_seg("UNH", vec![vec!["001"]]),
1494 make_owned_seg("FOO", vec![]),
1495 make_owned_seg("BAR", vec![]),
1496 make_owned_seg("BAZ", vec![]),
1497 ];
1498 let assembler = Assembler::new(&mig);
1499 let (tree, diagnostics) = assembler.assemble_with_diagnostics(&segments);
1500 assert_eq!(tree.segments.len(), 1);
1501 assert_eq!(diagnostics.len(), 3);
1502 assert_eq!(diagnostics[0].segment_id, "FOO");
1503 assert_eq!(diagnostics[1].segment_id, "BAR");
1504 assert_eq!(diagnostics[2].segment_id, "BAZ");
1505 }
1506
1507 #[test]
1510 fn test_non_entry_segments_get_mig_number_from_bounded_slots() {
1511 use crate::test_support::make_mig_segment_numbered;
1515
1516 let sg4 = MigSegmentGroup {
1517 segments: vec![
1518 make_mig_segment_numbered("IDE", "00020"),
1519 make_mig_segment_numbered("DTM", "00023"),
1520 make_mig_segment_numbered("DTM", "00024"),
1521 make_mig_segment_numbered("STS", "00035"),
1522 ],
1523 ..make_mig_group("SG4", vec![], vec![])
1524 };
1525 let mig = make_mig_schema(vec!["UNH"], vec![sg4]);
1526
1527 let segments = vec![
1528 make_owned_seg("UNH", vec![vec!["001"]]),
1529 make_owned_seg("IDE", vec![vec!["24", "TX001"]]),
1530 make_owned_seg("DTM", vec![vec!["92", "202505312200+00", "303"]]),
1531 make_owned_seg("DTM", vec![vec!["93", "202512312300+00", "303"]]),
1532 make_owned_seg("STS", vec![vec!["7"], vec![], vec!["E01"]]),
1533 ];
1534
1535 let assembler = Assembler::new(&mig);
1536 let tree = assembler.assemble_generic(&segments).unwrap();
1537
1538 let sg4_instance = &tree.groups[0].repetitions[0];
1539
1540 assert_eq!(sg4_instance.segments[0].tag, "IDE");
1542 assert_eq!(
1543 sg4_instance.segments[0].mig_number.as_deref(),
1544 Some("00020")
1545 );
1546
1547 assert_eq!(sg4_instance.segments[1].tag, "DTM");
1549 assert_eq!(
1550 sg4_instance.segments[1].mig_number.as_deref(),
1551 Some("00023")
1552 );
1553
1554 assert_eq!(sg4_instance.segments[2].tag, "DTM");
1556 assert_eq!(
1557 sg4_instance.segments[2].mig_number.as_deref(),
1558 Some("00024")
1559 );
1560
1561 assert_eq!(sg4_instance.segments[3].tag, "STS");
1563 assert_eq!(
1564 sg4_instance.segments[3].mig_number.as_deref(),
1565 Some("00035")
1566 );
1567
1568 assert!(sg4_instance
1570 .variant_mig_numbers
1571 .contains(&"00020".to_string()));
1572 assert!(sg4_instance
1573 .variant_mig_numbers
1574 .contains(&"00023".to_string()));
1575 assert!(sg4_instance
1576 .variant_mig_numbers
1577 .contains(&"00024".to_string()));
1578 assert!(sg4_instance
1579 .variant_mig_numbers
1580 .contains(&"00035".to_string()));
1581 }
1582
1583 #[test]
1584 fn test_greedy_extra_segments_get_no_mig_number() {
1585 use crate::test_support::make_mig_segment_numbered;
1588
1589 let sg4 = MigSegmentGroup {
1590 segments: vec![
1591 make_mig_segment_numbered("IDE", "00020"),
1592 make_mig_segment_numbered("DTM", "00023"),
1593 ],
1594 ..make_mig_group("SG4", vec![], vec![])
1595 };
1596 let mig = make_mig_schema(vec!["UNH"], vec![sg4]);
1597
1598 let segments = vec![
1599 make_owned_seg("UNH", vec![vec!["001"]]),
1600 make_owned_seg("IDE", vec![vec!["24"]]),
1601 make_owned_seg("DTM", vec![vec!["92", "20250531"]]),
1602 make_owned_seg("DTM", vec![vec!["93", "20251231"]]), ];
1604
1605 let assembler = Assembler::new(&mig);
1606 let tree = assembler.assemble_generic(&segments).unwrap();
1607
1608 let sg4_instance = &tree.groups[0].repetitions[0];
1609 assert_eq!(sg4_instance.segments.len(), 3); assert_eq!(
1613 sg4_instance.segments[1].mig_number.as_deref(),
1614 Some("00023")
1615 );
1616
1617 assert_eq!(sg4_instance.segments[2].mig_number, None);
1619 }
1620
1621 #[test]
1624 fn test_qualifier_map_prevents_wrong_slot_consumption() {
1625 use crate::test_support::make_mig_segment_numbered;
1630 use std::collections::HashMap;
1631
1632 let sg4 = MigSegmentGroup {
1633 segments: vec![
1634 make_mig_segment_numbered("IDE", "00020"),
1635 make_mig_segment_numbered("DTM", "00023"),
1636 make_mig_segment_numbered("DTM", "00024"),
1637 ],
1638 ..make_mig_group("SG4", vec![], vec![])
1639 };
1640 let mig = make_mig_schema(vec!["UNH"], vec![sg4]);
1641
1642 let segments = vec![
1643 make_owned_seg("UNH", vec![vec!["001"]]),
1644 make_owned_seg("IDE", vec![vec!["24"]]),
1645 make_owned_seg("DTM", vec![vec!["93", "202512312300+00", "303"]]),
1646 ];
1647
1648 let mut qualifier_map = HashMap::new();
1649 qualifier_map.insert("00023".to_string(), (0, 0, "92".to_string()));
1650 qualifier_map.insert("00024".to_string(), (0, 0, "93".to_string()));
1651
1652 let config = AssemblerConfig {
1653 skip_unknown_segments: false,
1654 qualifier_map,
1655 ..Default::default()
1656 };
1657 let assembler = Assembler::with_config(&mig, config);
1658 let tree = assembler.assemble_generic(&segments).unwrap();
1659
1660 let sg4_instance = &tree.groups[0].repetitions[0];
1661
1662 assert_eq!(sg4_instance.segments.len(), 2); let dtm = &sg4_instance.segments[1];
1665 assert_eq!(dtm.tag, "DTM");
1666 assert_eq!(
1667 dtm.mig_number.as_deref(),
1668 Some("00024"),
1669 "DTM+93 should get mig_number 00024 (not 00023)"
1670 );
1671 }
1672
1673 #[test]
1674 fn test_group_entry_qualifier_mismatch_does_not_infinite_loop() {
1675 use crate::test_support::make_mig_segment_numbered;
1685 use std::collections::HashMap;
1686
1687 let sg5 = MigSegmentGroup {
1688 segments: vec![make_mig_segment_numbered("LOC", "00050")],
1689 ..make_mig_group("SG5", vec![], vec![])
1690 };
1691 let mig = make_mig_schema(vec!["UNH"], vec![sg5]);
1692
1693 let segments = vec![
1694 make_owned_seg("UNH", vec![vec!["001"]]),
1695 make_owned_seg("LOC", vec![vec!["172"], vec!["92003964705"]]),
1697 ];
1698
1699 let mut qualifier_map = HashMap::new();
1700 qualifier_map.insert("00050".to_string(), (0, 0, "Z16".to_string()));
1701
1702 let config = AssemblerConfig {
1703 skip_unknown_segments: false,
1704 qualifier_map,
1705 ..Default::default()
1706 };
1707 let assembler = Assembler::with_config(&mig, config);
1708
1709 let start = std::time::Instant::now();
1713 let tree = assembler.assemble_generic(&segments).unwrap();
1714 assert!(
1715 start.elapsed() < std::time::Duration::from_secs(5),
1716 "assembly took {:?} — suspected infinite-loop regression",
1717 start.elapsed()
1718 );
1719
1720 assert!(tree.groups.is_empty());
1724 }
1725
1726 #[test]
1729 fn test_skip_unknown_segment_between_slots() {
1730 let sg8 = make_mig_group("SG8", vec!["SEQ", "CCI"], vec![]);
1734 let mig = make_mig_schema(vec!["UNH"], vec![sg8.clone()]);
1735
1736 let segments = vec![
1737 make_owned_seg("UNH", vec![vec!["001"]]),
1738 make_owned_seg("SEQ", vec![vec!["Z98"]]),
1739 make_owned_seg("RFF", vec![vec!["Z38", "CROSSREF"]]),
1740 make_owned_seg("CCI", vec![vec!["Z30"]]),
1741 ];
1742
1743 let off = Assembler::new(&mig);
1745 let tree_off = off.assemble_generic(&segments).unwrap();
1746 let sg8_off = &tree_off.groups[0];
1747 assert_eq!(sg8_off.repetitions[0].segments.len(), 1); assert_eq!(sg8_off.repetitions[0].segments[0].tag, "SEQ");
1749
1750 let on = Assembler::with_config(
1752 &mig,
1753 AssemblerConfig {
1754 skip_unknown_segments: true,
1755 ..Default::default()
1756 },
1757 );
1758 let tree_on = on.assemble_generic(&segments).unwrap();
1759 let sg8_on = &tree_on.groups[0];
1760 assert_eq!(sg8_on.repetitions[0].segments.len(), 2); assert_eq!(sg8_on.repetitions[0].segments[0].tag, "SEQ");
1762 assert_eq!(sg8_on.repetitions[0].segments[1].tag, "CCI");
1763 }
1764
1765 #[test]
1766 fn test_skip_preserves_on_instance() {
1767 let sg8 = make_mig_group("SG8", vec!["SEQ", "CCI"], vec![]);
1769 let mig = make_mig_schema(vec!["UNH"], vec![sg8]);
1770
1771 let segments = vec![
1772 make_owned_seg("UNH", vec![vec!["001"]]),
1773 make_owned_seg("SEQ", vec![vec!["Z98"]]),
1774 make_owned_seg("RFF", vec![vec!["Z38", "REF1"]]),
1775 make_owned_seg("DTM", vec![vec!["92", "20250101"]]),
1776 make_owned_seg("CCI", vec![vec!["Z30"]]),
1777 ];
1778
1779 let assembler = Assembler::with_config(
1780 &mig,
1781 AssemblerConfig {
1782 skip_unknown_segments: true,
1783 ..Default::default()
1784 },
1785 );
1786 let tree = assembler.assemble_generic(&segments).unwrap();
1787 let instance = &tree.groups[0].repetitions[0];
1788
1789 assert_eq!(instance.segments.len(), 2); assert_eq!(instance.skipped_segments.len(), 2); assert_eq!(instance.skipped_segments[0].tag, "RFF");
1792 assert_eq!(instance.skipped_segments[1].tag, "DTM");
1793 }
1794
1795 #[test]
1796 fn test_skip_mode_off_default() {
1797 let mig = make_mig_schema(vec![], vec![]);
1799 let assembler = Assembler::new(&mig);
1800 assert!(!assembler.config.skip_unknown_segments);
1801 }
1802
1803 #[test]
1804 fn test_skip_does_not_consume_nested_group_entry() {
1805 let sg5 = make_mig_group("SG5", vec!["LOC"], vec![]);
1809 let sg4 = make_mig_group("SG4", vec!["IDE", "STS"], vec![sg5]);
1810 let mig = make_mig_schema(vec!["UNH"], vec![sg4]);
1811
1812 let segments = vec![
1813 make_owned_seg("UNH", vec![vec!["001"]]),
1814 make_owned_seg("IDE", vec![vec!["24"]]),
1815 make_owned_seg("FOO", vec![vec!["unknown"]]),
1816 make_owned_seg("STS", vec![vec!["7"]]),
1817 make_owned_seg("LOC", vec![vec!["Z16"]]),
1818 ];
1819
1820 let assembler = Assembler::with_config(
1821 &mig,
1822 AssemblerConfig {
1823 skip_unknown_segments: true,
1824 ..Default::default()
1825 },
1826 );
1827 let tree = assembler.assemble_generic(&segments).unwrap();
1828 let sg4 = &tree.groups[0];
1829 let inst = &sg4.repetitions[0];
1830
1831 assert_eq!(inst.segments.len(), 2);
1833 assert_eq!(inst.segments[0].tag, "IDE");
1834 assert_eq!(inst.segments[1].tag, "STS");
1835 assert_eq!(inst.skipped_segments.len(), 1);
1836 assert_eq!(inst.skipped_segments[0].tag, "FOO");
1837
1838 assert_eq!(inst.child_groups.len(), 1);
1840 assert_eq!(inst.child_groups[0].group_id, "SG5");
1841 assert_eq!(inst.child_groups[0].repetitions[0].segments[0].tag, "LOC");
1842 }
1843
1844 #[test]
1849 fn test_diagnostics_orphaned_group_segment_without_entry() {
1850 let sg10 = make_mig_group("SG10", vec!["CCI", "CAV"], vec![]);
1851 let sg8 = make_mig_group_with_variant("SG8", vec!["SEQ"], vec![sg10], "Z03");
1852 let sg12 = make_mig_group("SG12", vec!["NAD"], vec![]);
1853 let sg4 = make_mig_group("SG4", vec!["IDE"], vec![sg8, sg12]);
1854 let mig = make_mig_schema(vec!["UNH"], vec![sg4]);
1855
1856 let segments = vec![
1857 make_owned_seg("UNH", vec![vec!["001"]]),
1858 make_owned_seg("IDE", vec![vec!["24"]]),
1859 make_owned_seg("SEQ", vec![vec!["Z03"]]),
1860 make_owned_seg("CAV", vec![vec!["Z30", "", "", "X"]]),
1861 make_owned_seg("FOO", vec![vec!["unknown"]]),
1862 make_owned_seg("NAD", vec![vec!["Z07"]]),
1863 ];
1864
1865 let assembler = Assembler::with_config(
1866 &mig,
1867 AssemblerConfig {
1868 skip_unknown_segments: true,
1869 ..Default::default()
1870 },
1871 );
1872 let (tree, diagnostics) = assembler.assemble_with_diagnostics(&segments);
1873
1874 let sg4 = &tree.groups[0].repetitions[0];
1876 assert!(sg4.child_groups.iter().any(|g| g.group_id == "SG12"));
1877
1878 let orphaned: Vec<_> = diagnostics
1879 .iter()
1880 .filter(|d| d.kind == StructureDiagnosticKind::OrphanedGroupSegment)
1881 .collect();
1882 assert_eq!(orphaned.len(), 1, "{diagnostics:?}");
1883 assert_eq!(orphaned[0].segment_id, "CAV");
1884 assert_eq!(orphaned[0].position, 3);
1885 assert!(
1886 orphaned[0].message.contains("SG10") && orphaned[0].message.contains("'CCI'"),
1887 "message must name the group and its missing entry segment: {}",
1888 orphaned[0].message
1889 );
1890 assert!(
1891 !orphaned[0].message.contains("not defined"),
1892 "CAV is defined in the MIG: {}",
1893 orphaned[0].message
1894 );
1895
1896 let skipped: Vec<_> = diagnostics
1898 .iter()
1899 .filter(|d| d.kind == StructureDiagnosticKind::SkippedUnknownSegment)
1900 .collect();
1901 assert_eq!(skipped.len(), 1, "{diagnostics:?}");
1902 assert_eq!(skipped[0].segment_id, "FOO");
1903 }
1904
1905 #[test]
1906 fn test_skip_unknown_between_nested_group_reps() {
1907 let sg10 = make_mig_group("SG10", vec!["CCI"], vec![]);
1925 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ"], vec![sg10.clone()], "ZD7");
1926 let sg8_z98 = make_mig_group_with_variant("SG8", vec!["SEQ"], vec![], "Z98");
1927 let sg12 = make_mig_group("SG12", vec!["NAD"], vec![]);
1928 let sg4 = make_mig_group("SG4", vec!["IDE", "STS"], vec![sg8_zd7, sg8_z98, sg12]);
1929 let mig = make_mig_schema(vec!["UNH"], vec![sg4]);
1930
1931 let segments = vec![
1932 make_owned_seg("UNH", vec![vec!["001"]]),
1933 make_owned_seg("IDE", vec![vec!["24", "TX001"]]),
1934 make_owned_seg("STS", vec![vec!["7"], vec!["Z33"]]),
1935 make_owned_seg("SEQ", vec![vec!["ZD7"]]),
1936 make_owned_seg("CCI", vec![vec!["Z30"]]),
1937 make_owned_seg("FOO", vec![vec!["orphan"]]),
1938 make_owned_seg("SEQ", vec![vec!["Z98"]]),
1939 make_owned_seg("NAD", vec![vec!["MS", "9900123"]]),
1940 ];
1941
1942 let assembler = Assembler::with_config(
1943 &mig,
1944 AssemblerConfig {
1945 skip_unknown_segments: true,
1946 ..Default::default()
1947 },
1948 );
1949 let tree = assembler.assemble_generic(&segments).unwrap();
1950
1951 assert_eq!(tree.groups.len(), 1);
1952 let sg4_inst = &tree.groups[0].repetitions[0];
1953 assert_eq!(sg4_inst.child_groups.len(), 2, "expected SG8 + SG12");
1955 let sg8_tree = &sg4_inst.child_groups[0];
1956 assert_eq!(sg8_tree.group_id, "SG8");
1957 assert_eq!(
1958 sg8_tree.repetitions.len(),
1959 2,
1960 "SG8 should have both ZD7 and Z98 reps after orphan skip"
1961 );
1962 assert_eq!(sg8_tree.repetitions[0].segments[0].elements[0][0], "ZD7");
1963 assert_eq!(sg8_tree.repetitions[1].segments[0].elements[0][0], "Z98");
1964
1965 let sg12_tree = &sg4_inst.child_groups[1];
1966 assert_eq!(sg12_tree.group_id, "SG12");
1967 assert_eq!(sg12_tree.repetitions.len(), 1);
1968
1969 fn count_foo(inst: &AssembledGroupInstance) -> usize {
1974 let mut n = inst
1975 .skipped_segments
1976 .iter()
1977 .filter(|s| s.tag == "FOO")
1978 .count();
1979 for child in &inst.child_groups {
1980 for rep in &child.repetitions {
1981 n += count_foo(rep);
1982 }
1983 }
1984 n
1985 }
1986 assert_eq!(
1987 count_foo(sg4_inst),
1988 1,
1989 "FOO should be recorded exactly once"
1990 );
1991 }
1992
1993 #[test]
1994 fn test_skip_off_preserves_cascade_behavior() {
1995 let sg10 = make_mig_group("SG10", vec!["CCI"], vec![]);
1999 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ"], vec![sg10], "ZD7");
2000 let sg8_z98 = make_mig_group_with_variant("SG8", vec!["SEQ"], vec![], "Z98");
2001 let sg12 = make_mig_group("SG12", vec!["NAD"], vec![]);
2002 let sg4 = make_mig_group("SG4", vec!["IDE", "STS"], vec![sg8_zd7, sg8_z98, sg12]);
2003 let mig = make_mig_schema(vec!["UNH"], vec![sg4]);
2004
2005 let segments = vec![
2006 make_owned_seg("UNH", vec![vec!["001"]]),
2007 make_owned_seg("IDE", vec![vec!["24", "TX001"]]),
2008 make_owned_seg("STS", vec![vec!["7"], vec!["Z33"]]),
2009 make_owned_seg("SEQ", vec![vec!["ZD7"]]),
2010 make_owned_seg("CCI", vec![vec!["Z30"]]),
2011 make_owned_seg("FOO", vec![vec!["orphan"]]),
2012 make_owned_seg("SEQ", vec![vec!["Z98"]]),
2013 make_owned_seg("NAD", vec![vec!["MS", "9900123"]]),
2014 ];
2015
2016 let assembler = Assembler::new(&mig);
2017 let tree = assembler.assemble_generic(&segments).unwrap();
2018
2019 let sg4_inst = &tree.groups[0].repetitions[0];
2020 let sg8_tree = sg4_inst
2021 .child_groups
2022 .iter()
2023 .find(|g| g.group_id == "SG8")
2024 .expect("SG8 should still be present");
2025 assert_eq!(sg8_tree.repetitions.len(), 1);
2027 assert!(
2028 sg4_inst.skipped_segments.iter().all(|s| s.tag != "FOO"),
2029 "FOO must not be skipped when skip mode is off"
2030 );
2031 }
2032
2033 #[test]
2034 fn test_roundtrip_with_skip() {
2035 use crate::disassembler::Disassembler;
2038 use crate::renderer::render_edifact;
2039
2040 let sg8 = make_mig_group("SG8", vec!["SEQ", "CCI"], vec![]);
2041 let mig = make_mig_schema(vec!["UNH", "UNT"], vec![sg8]);
2042
2043 let segments = vec![
2044 make_owned_seg("UNH", vec![vec!["001"]]),
2045 make_owned_seg("SEQ", vec![vec!["Z98"]]),
2046 make_owned_seg("RFF", vec![vec!["Z38", "REF1"]]),
2047 make_owned_seg("CCI", vec![vec!["Z30"]]),
2048 make_owned_seg("UNT", vec![vec!["4", "001"]]),
2049 ];
2050
2051 let assembler = Assembler::with_config(
2052 &mig,
2053 AssemblerConfig {
2054 skip_unknown_segments: true,
2055 ..Default::default()
2056 },
2057 );
2058 let tree = assembler.assemble_generic(&segments).unwrap();
2059
2060 let disassembler = Disassembler::new(&mig);
2061 let dis = disassembler.disassemble(&tree);
2062 let delimiters = edifact_primitives::EdifactDelimiters::default();
2063 let rendered = render_edifact(&dis, &delimiters);
2064
2065 assert_eq!(dis.len(), 5);
2070 assert_eq!(dis[0].tag, "UNH");
2071 assert_eq!(dis[1].tag, "SEQ");
2072 assert_eq!(dis[2].tag, "CCI");
2073 assert_eq!(dis[3].tag, "RFF"); assert_eq!(dis[4].tag, "UNT");
2075
2076 assert!(rendered.contains("UNH+001"));
2078 assert!(rendered.contains("SEQ+Z98"));
2079 assert!(rendered.contains("RFF+Z38:REF1"));
2080 assert!(rendered.contains("CCI+Z30"));
2081 assert!(rendered.contains("UNT+4:001"));
2082 }
2083
2084 #[test]
2087 fn test_variant_groups_interleaved_reps() {
2088 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ", "CCI"], vec![], "ZD7");
2092 let sg8_z98 = make_mig_group_with_variant("SG8", vec!["SEQ", "RFF"], vec![], "Z98");
2093
2094 let mig = make_mig_schema(vec!["UNH"], vec![sg8_zd7, sg8_z98]);
2095
2096 let segments = vec![
2097 make_owned_seg("UNH", vec![vec!["001"]]),
2098 make_owned_seg("SEQ", vec![vec!["ZD7"]]),
2099 make_owned_seg("CCI", vec![vec!["Z30"]]),
2100 make_owned_seg("SEQ", vec![vec!["Z98"]]),
2101 make_owned_seg("RFF", vec![vec!["Z38", "REF1"]]),
2102 make_owned_seg("SEQ", vec![vec!["ZD7"]]),
2103 make_owned_seg("CCI", vec![vec!["Z31"]]),
2104 make_owned_seg("SEQ", vec![vec!["Z98"]]),
2105 make_owned_seg("RFF", vec![vec!["Z38", "REF2"]]),
2106 ];
2107
2108 let assembler = Assembler::new(&mig);
2109 let result = assembler.assemble_generic(&segments).unwrap();
2110
2111 assert_eq!(result.segments.len(), 1); assert_eq!(result.groups.len(), 1); let sg8 = &result.groups[0];
2114 assert_eq!(sg8.group_id, "SG8");
2115 assert_eq!(sg8.repetitions.len(), 4);
2116
2117 assert_eq!(sg8.repetitions[0].segments[0].elements[0][0], "ZD7");
2119 assert_eq!(sg8.repetitions[0].segments[1].tag, "CCI");
2120 assert_eq!(sg8.repetitions[1].segments[0].elements[0][0], "Z98");
2121 assert_eq!(sg8.repetitions[1].segments[1].tag, "RFF");
2122 assert_eq!(sg8.repetitions[2].segments[0].elements[0][0], "ZD7");
2123 assert_eq!(sg8.repetitions[3].segments[0].elements[0][0], "Z98");
2124 }
2125
2126 #[test]
2127 fn test_variant_groups_single_variant_type() {
2128 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ", "CCI"], vec![], "ZD7");
2130 let sg8_z98 = make_mig_group_with_variant("SG8", vec!["SEQ", "RFF"], vec![], "Z98");
2131
2132 let mig = make_mig_schema(vec!["UNH"], vec![sg8_zd7, sg8_z98]);
2133
2134 let segments = vec![
2135 make_owned_seg("UNH", vec![vec!["001"]]),
2136 make_owned_seg("SEQ", vec![vec!["Z98"]]),
2137 make_owned_seg("RFF", vec![vec!["Z38", "REF1"]]),
2138 make_owned_seg("SEQ", vec![vec!["Z98"]]),
2139 make_owned_seg("RFF", vec![vec!["Z38", "REF2"]]),
2140 ];
2141
2142 let assembler = Assembler::new(&mig);
2143 let result = assembler.assemble_generic(&segments).unwrap();
2144
2145 assert_eq!(result.groups.len(), 1);
2146 assert_eq!(result.groups[0].repetitions.len(), 2);
2147 assert_eq!(
2148 result.groups[0].repetitions[0].segments[0].elements[0][0],
2149 "Z98"
2150 );
2151 assert_eq!(
2152 result.groups[0].repetitions[1].segments[0].elements[0][0],
2153 "Z98"
2154 );
2155 }
2156
2157 #[test]
2158 fn test_non_variant_groups_unchanged() {
2159 let sg2 = make_mig_group("SG2", vec!["NAD"], vec![]);
2161 let sg4 = make_mig_group("SG4", vec!["IDE", "STS"], vec![]);
2162
2163 let mig = make_mig_schema(vec!["UNH", "BGM"], vec![sg2, sg4]);
2164
2165 let segments = vec![
2166 make_owned_seg("UNH", vec![vec!["001"]]),
2167 make_owned_seg("BGM", vec![vec!["E01"]]),
2168 make_owned_seg("NAD", vec![vec!["MS", "9900123"]]),
2169 make_owned_seg("NAD", vec![vec!["MR", "9900456"]]),
2170 make_owned_seg("IDE", vec![vec!["24", "TX001"]]),
2171 make_owned_seg("STS", vec![vec!["7"], vec!["Z33"]]),
2172 ];
2173
2174 let assembler = Assembler::new(&mig);
2175 let result = assembler.assemble_generic(&segments).unwrap();
2176
2177 assert_eq!(result.segments.len(), 2);
2178 assert_eq!(result.groups.len(), 2);
2179 assert_eq!(result.groups[0].group_id, "SG2");
2180 assert_eq!(result.groups[0].repetitions.len(), 2);
2181 assert_eq!(result.groups[1].group_id, "SG4");
2182 assert_eq!(result.groups[1].repetitions.len(), 1);
2183 }
2184
2185 #[test]
2186 fn test_variant_groups_with_nested_children() {
2187 let sg10 = make_mig_group("SG10", vec!["CCI", "CAV"], vec![]);
2189 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ"], vec![sg10.clone()], "ZD7");
2190 let sg8_z98 = make_mig_group_with_variant("SG8", vec!["SEQ"], vec![sg10], "Z98");
2191
2192 let mig = make_mig_schema(vec!["UNH"], vec![sg8_zd7, sg8_z98]);
2193
2194 let segments = vec![
2195 make_owned_seg("UNH", vec![vec!["001"]]),
2196 make_owned_seg("SEQ", vec![vec!["ZD7"]]),
2197 make_owned_seg("CCI", vec![vec!["Z30"]]),
2198 make_owned_seg("CAV", vec![vec!["Z91", "Y"]]),
2199 make_owned_seg("SEQ", vec![vec!["Z98"]]),
2200 make_owned_seg("CCI", vec![vec!["Z31"]]),
2201 make_owned_seg("CAV", vec![vec!["Z91", "N"]]),
2202 ];
2203
2204 let assembler = Assembler::new(&mig);
2205 let result = assembler.assemble_generic(&segments).unwrap();
2206
2207 assert_eq!(result.groups.len(), 1);
2208 let sg8 = &result.groups[0];
2209 assert_eq!(sg8.repetitions.len(), 2);
2210
2211 assert_eq!(sg8.repetitions[0].child_groups.len(), 1);
2213 assert_eq!(sg8.repetitions[0].child_groups[0].group_id, "SG10");
2214 assert_eq!(
2215 sg8.repetitions[0].child_groups[0].repetitions[0].segments[0].elements[0][0],
2216 "Z30"
2217 );
2218
2219 assert_eq!(sg8.repetitions[1].child_groups.len(), 1);
2221 assert_eq!(
2222 sg8.repetitions[1].child_groups[0].repetitions[0].segments[0].elements[0][0],
2223 "Z31"
2224 );
2225 }
2226
2227 #[test]
2228 fn test_variant_qualifier_check_prevents_wrong_variant_consumption() {
2229 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ", "CCI"], vec![], "ZD7");
2232
2233 let mig = make_mig_schema(vec!["UNH"], vec![sg8_zd7]);
2234
2235 let segments = vec![
2236 make_owned_seg("UNH", vec![vec!["001"]]),
2237 make_owned_seg("SEQ", vec![vec!["Z98"]]), make_owned_seg("CCI", vec![vec!["Z30"]]),
2239 ];
2240
2241 let assembler = Assembler::new(&mig);
2242 let result = assembler.assemble_generic(&segments).unwrap();
2243
2244 assert!(result.groups.is_empty());
2246 }
2247
2248 #[test]
2249 fn test_mixed_variant_and_non_variant_groups() {
2250 let sg2 = make_mig_group("SG2", vec!["NAD"], vec![]);
2252 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ", "CCI"], vec![], "ZD7");
2253 let sg8_z98 = make_mig_group_with_variant("SG8", vec!["SEQ", "RFF"], vec![], "Z98");
2254 let sg12 = make_mig_group("SG12", vec!["NAD"], vec![]);
2255
2256 let mig = make_mig_schema(vec!["UNH"], vec![sg2, sg8_zd7, sg8_z98, sg12]);
2257
2258 let segments = vec![
2259 make_owned_seg("UNH", vec![vec!["001"]]),
2260 make_owned_seg("NAD", vec![vec!["MS", "9900123"]]),
2261 make_owned_seg("SEQ", vec![vec!["ZD7"]]),
2262 make_owned_seg("CCI", vec![vec!["Z30"]]),
2263 make_owned_seg("SEQ", vec![vec!["Z98"]]),
2264 make_owned_seg("RFF", vec![vec!["Z38", "REF1"]]),
2265 make_owned_seg("NAD", vec![vec!["Z65", "ID001"]]),
2266 ];
2267
2268 let assembler = Assembler::new(&mig);
2269 let result = assembler.assemble_generic(&segments).unwrap();
2270
2271 assert_eq!(result.groups.len(), 3); assert_eq!(result.groups[0].group_id, "SG2");
2273 assert_eq!(result.groups[0].repetitions.len(), 1);
2274 assert_eq!(result.groups[1].group_id, "SG8");
2275 assert_eq!(result.groups[1].repetitions.len(), 2);
2276 assert_eq!(result.groups[2].group_id, "SG12");
2277 assert_eq!(result.groups[2].repetitions.len(), 1);
2278 }
2279
2280 #[test]
2281 fn test_assembler_disambiguates_shared_qualifier_by_full_code_profile() {
2282 use mig_types::schema::common::CodeDefinition;
2290 use mig_types::schema::mig::{MigComposite, MigDataElement};
2291 use std::collections::HashMap;
2292
2293 fn code(value: &str) -> CodeDefinition {
2294 CodeDefinition {
2295 value: value.to_string(),
2296 name: value.to_string(),
2297 description: None,
2298 }
2299 }
2300
2301 fn pia_slot(number: &str, composite_code: &str) -> MigSegment {
2302 MigSegment {
2303 id: "PIA".to_string(),
2304 name: "PIA".to_string(),
2305 description: None,
2306 counter: None,
2307 level: 1,
2308 number: Some(number.to_string()),
2309 max_rep_std: 1,
2310 max_rep_spec: 1,
2311 status_std: Some("M".to_string()),
2312 status_spec: Some("M".to_string()),
2313 example: None,
2314 data_elements: vec![MigDataElement {
2315 id: "4347".to_string(),
2316 name: "Produkt-ID-Funktion".to_string(),
2317 description: None,
2318 status_std: Some("M".to_string()),
2319 status_spec: Some("M".to_string()),
2320 format_std: None,
2321 format_spec: None,
2322 codes: vec![code("5")],
2323 position: 0,
2324 }],
2325 composites: vec![MigComposite {
2326 id: "C212".to_string(),
2327 name: "Item Identifier".to_string(),
2328 description: None,
2329 status_std: Some("M".to_string()),
2330 status_spec: Some("M".to_string()),
2331 data_elements: vec![MigDataElement {
2332 id: "7143".to_string(),
2333 name: "Artikel/Dienstleistung-ID".to_string(),
2334 description: None,
2335 status_std: Some("M".to_string()),
2336 status_spec: Some("M".to_string()),
2337 format_std: None,
2338 format_spec: None,
2339 codes: vec![code(composite_code)],
2340 position: 0,
2341 }],
2342 position: 1,
2343 }],
2344 }
2345 }
2346
2347 let sg4 = MigSegmentGroup {
2348 segments: vec![
2349 crate::test_support::make_mig_segment_numbered("IDE", "00020"),
2350 pia_slot("00108", "Z12"),
2351 pia_slot("00197", "SRW"),
2352 ],
2353 ..make_mig_group("SG4", vec![], vec![])
2354 };
2355 let mig = make_mig_schema(vec!["UNH"], vec![sg4]);
2356
2357 let segments = vec![
2358 make_owned_seg("UNH", vec![vec!["001"]]),
2359 make_owned_seg("IDE", vec![vec!["24"]]),
2360 make_owned_seg("PIA", vec![vec!["5"], vec!["SRW"]]),
2362 ];
2363
2364 let mut qualifier_map = HashMap::new();
2368 qualifier_map.insert("00108".to_string(), (0, 0, "5".to_string()));
2369 qualifier_map.insert("00197".to_string(), (0, 0, "5".to_string()));
2370
2371 let config = AssemblerConfig {
2372 skip_unknown_segments: false,
2373 qualifier_map,
2374 strict_code_matching: true,
2375 };
2376 let assembler = Assembler::with_config(&mig, config);
2377 let tree = assembler.assemble_generic(&segments).unwrap();
2378
2379 let sg4_instance = &tree.groups[0].repetitions[0];
2380 let pia = sg4_instance
2381 .segments
2382 .iter()
2383 .find(|s| s.tag == "PIA")
2384 .expect("PIA consumed into SG4");
2385 assert_eq!(
2386 pia.mig_number.as_deref(),
2387 Some("00197"),
2388 "PIA+5+:::SRW must be assigned the SRW variant (mig=00197), not the Z12 variant"
2389 );
2390 }
2391
2392 #[test]
2393 fn a_repeatable_slot_keeps_its_repetitions() {
2394 use mig_types::schema::common::CodeDefinition;
2400 use mig_types::schema::mig::{MigComposite, MigDataElement};
2401 use std::collections::HashMap;
2402
2403 fn cav_slot(number: &str, codes: &[&str], max_rep_spec: i32) -> MigSegment {
2404 MigSegment {
2405 id: "CAV".to_string(),
2406 name: "CAV".to_string(),
2407 description: None,
2408 counter: None,
2409 level: 1,
2410 number: Some(number.to_string()),
2411 max_rep_std: 99,
2412 max_rep_spec,
2413 status_std: Some("M".to_string()),
2414 status_spec: Some("M".to_string()),
2415 example: None,
2416 data_elements: vec![],
2417 composites: vec![MigComposite {
2418 id: "C889".to_string(),
2419 name: "Merkmalswert".to_string(),
2420 description: None,
2421 status_std: Some("M".to_string()),
2422 status_spec: Some("M".to_string()),
2423 data_elements: vec![MigDataElement {
2424 id: "7111".to_string(),
2425 name: "Merkmalswert, Code".to_string(),
2426 description: None,
2427 status_std: Some("M".to_string()),
2428 status_spec: Some("M".to_string()),
2429 format_std: None,
2430 format_spec: None,
2431 codes: codes
2432 .iter()
2433 .map(|c| CodeDefinition {
2434 value: c.to_string(),
2435 name: c.to_string(),
2436 description: None,
2437 })
2438 .collect(),
2439 position: 0,
2440 }],
2441 position: 0,
2442 }],
2443 }
2444 }
2445
2446 let sg10 = MigSegmentGroup {
2447 segments: vec![
2448 crate::test_support::make_mig_segment_numbered("CCI", "00340"),
2449 cav_slot("00341", &["Z30"], 3),
2450 cav_slot("00342", &["MIW", "MPW", "MBW", "MUW"], 1),
2451 ],
2452 ..make_mig_group("SG10", vec![], vec![])
2453 };
2454 let mig = make_mig_schema(vec!["UNH"], vec![sg10]);
2455 let segments = vec![
2456 make_owned_seg("UNH", vec![vec!["001"]]),
2457 make_owned_seg("CCI", vec![vec![""], vec![""], vec!["Z25"]]),
2458 make_owned_seg("CAV", vec![vec!["Z30", "", "", "W1"]]),
2459 make_owned_seg("CAV", vec![vec!["Z30", "", "", "W2"]]),
2460 make_owned_seg("CAV", vec![vec!["MIW", "", "", "30"]]),
2461 ];
2462 let config = AssemblerConfig {
2463 skip_unknown_segments: false,
2464 qualifier_map: HashMap::new(),
2465 strict_code_matching: true,
2466 };
2467 let tree = Assembler::with_config(&mig, config)
2468 .assemble_generic(&segments)
2469 .unwrap();
2470 let numbers: Vec<Option<&str>> = tree.groups[0].repetitions[0]
2471 .segments
2472 .iter()
2473 .map(|s| s.mig_number.as_deref())
2474 .collect();
2475 assert_eq!(
2476 numbers,
2477 [Some("00340"), Some("00341"), Some("00341"), Some("00342")]
2478 );
2479 }
2480}