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 let pos = match seg.segment_number {
960 0 => i,
961 n => n as usize,
962 };
963 diagnostics.push(StructureDiagnostic {
964 kind: StructureDiagnosticKind::UnexpectedSegment,
965 segment_id: seg.id.clone(),
966 position: pos,
967 message: format!(
968 "Segment '{}' at position {pos} was not consumed by MIG-guided assembly",
969 seg.id
970 ),
971 });
972 }
973
974 let mut skipped: Vec<SkippedSegment> = Vec::new();
978 let top_level: Vec<&MigSegmentGroup> = self.mig.segment_groups.iter().collect();
979 for group in &tree.groups {
980 collect_skipped_from_group(group, &top_level, &mut skipped);
981 }
982 skipped.sort_by_key(|s| s.position);
983 for SkippedSegment {
984 position: pos,
985 tag,
986 orphaned_in,
987 } in skipped
988 {
989 diagnostics.push(match orphaned_in {
990 Some((group_id, entry)) => StructureDiagnostic {
993 kind: StructureDiagnosticKind::OrphanedGroupSegment,
994 segment_id: tag.clone(),
995 position: pos,
996 message: format!(
997 "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",
998 ),
999 },
1000 None if mig_defines_tag(self.mig, &tag) => StructureDiagnostic {
1001 kind: StructureDiagnosticKind::SkippedUnknownSegment,
1002 segment_id: tag.clone(),
1003 position: pos,
1004 message: format!(
1005 "Segment '{tag}' at position {pos} has no slot at this point of the PID-filtered MIG; the assembler advanced past it",
1006 ),
1007 },
1008 None => StructureDiagnostic {
1009 kind: StructureDiagnosticKind::SkippedUnknownSegment,
1010 segment_id: tag.clone(),
1011 position: pos,
1012 message: format!(
1013 "Segment '{tag}' at position {pos} is not defined in the PID-filtered MIG; the assembler advanced past it",
1014 ),
1015 },
1016 });
1017 }
1018
1019 (tree, diagnostics)
1020 }
1021}
1022
1023struct SkippedSegment {
1026 position: usize,
1027 tag: String,
1028 orphaned_in: Option<(String, String)>,
1030}
1031
1032fn collect_skipped_from_group(
1035 group: &AssembledGroup,
1036 mig_candidates: &[&MigSegmentGroup],
1037 out: &mut Vec<SkippedSegment>,
1038) {
1039 let defs: Vec<&MigSegmentGroup> = mig_candidates
1040 .iter()
1041 .copied()
1042 .filter(|g| g.id == group.group_id)
1043 .collect();
1044 let nested: Vec<&MigSegmentGroup> = defs.iter().flat_map(|g| &g.nested_groups).collect();
1045 for rep in &group.repetitions {
1046 for (i, seg) in rep.skipped_segments.iter().enumerate() {
1047 let pos = seg
1050 .segment_number
1051 .filter(|&n| n > 0)
1052 .map(|n| n as usize)
1053 .or_else(|| rep.skipped_positions.get(i).copied())
1054 .unwrap_or(0);
1055 out.push(SkippedSegment {
1056 position: pos,
1057 tag: seg.tag.clone(),
1058 orphaned_in: orphaning_group(&nested, &seg.tag),
1059 });
1060 }
1061 for child in &rep.child_groups {
1062 collect_skipped_from_group(child, &nested, out);
1063 }
1064 }
1065}
1066
1067fn orphaning_group(nested: &[&MigSegmentGroup], tag: &str) -> Option<(String, String)> {
1073 if nested
1074 .iter()
1075 .any(|g| g.segments.first().is_some_and(|e| e.id == tag))
1076 {
1077 return None;
1078 }
1079 nested.iter().find_map(|g| {
1080 let (entry, rest) = g.segments.split_first()?;
1081 rest.iter()
1082 .any(|s| s.id == tag)
1083 .then(|| (g.id.clone(), entry.id.clone()))
1084 })
1085}
1086
1087fn mig_defines_tag(mig: &MigSchema, tag: &str) -> bool {
1089 fn in_group(g: &MigSegmentGroup, tag: &str) -> bool {
1090 g.segments.iter().any(|s| s.id == tag) || g.nested_groups.iter().any(|n| in_group(n, tag))
1091 }
1092 mig.segments.iter().any(|s| s.id == tag) || mig.segment_groups.iter().any(|g| in_group(g, tag))
1093}
1094
1095fn count_tree_segments(tree: &AssembledTree) -> usize {
1096 let mut count = tree.segments.len();
1097 for group in &tree.groups {
1098 count += count_group_segments(group);
1099 }
1100 for segs in tree.inter_group_segments.values() {
1102 count += segs.len();
1103 }
1104 count
1105}
1106
1107fn count_group_segments(group: &AssembledGroup) -> usize {
1108 let mut count = 0;
1109 for rep in &group.repetitions {
1110 count += rep.segments.len();
1111 count += rep.skipped_segments.len();
1112 for child in &rep.child_groups {
1113 count += count_group_segments(child);
1114 }
1115 }
1116 count
1117}
1118
1119fn group_local_scope(mig_group: &MigSegmentGroup) -> std::collections::HashSet<String> {
1125 let mut tags = std::collections::HashSet::new();
1126 for seg in &mig_group.segments {
1127 tags.insert(seg.id.clone());
1128 }
1129 for nested in &mig_group.nested_groups {
1130 if let Some(entry) = nested.segments.first() {
1131 tags.insert(entry.id.clone());
1132 }
1133 }
1134 tags
1135}
1136
1137fn push_or_merge_child(child_groups: &mut Vec<AssembledGroup>, new: AssembledGroup) {
1142 if let Some(existing) = child_groups.iter_mut().find(|g| g.group_id == new.group_id) {
1143 existing.repetitions.extend(new.repetitions);
1144 } else {
1145 child_groups.push(new);
1146 }
1147}
1148
1149fn collect_mig_numbers(group: &MigSegmentGroup) -> Vec<String> {
1154 let mut numbers = Vec::new();
1155 for seg in &group.segments {
1156 if let Some(ref num) = seg.number {
1157 numbers.push(num.clone());
1158 }
1159 }
1160 for nested in &group.nested_groups {
1161 numbers.extend(collect_mig_numbers(nested));
1162 for q in &nested.variant_entry_qualifiers {
1165 for n in &q.mig_numbers {
1166 if !numbers.contains(n) {
1167 numbers.push(n.clone());
1168 }
1169 }
1170 }
1171 }
1172 numbers
1173}
1174
1175pub fn owned_to_assembled(seg: &OwnedSegment) -> AssembledSegment {
1176 AssembledSegment {
1177 tag: seg.id.clone(),
1178 elements: seg.elements.clone(),
1179 mig_number: None,
1180 segment_number: Some(seg.segment_number),
1181 }
1182}
1183
1184fn segment_matches_mig_codes(seg: &OwnedSegment, mig_seg: &MigSegment) -> bool {
1192 let actual_at = |el: usize, c: usize| -> &str {
1193 seg.elements
1194 .get(el)
1195 .and_then(|e| e.get(c))
1196 .map(|s| s.as_str())
1197 .unwrap_or("")
1198 };
1199 for de in &mig_seg.data_elements {
1200 if !de.codes.is_empty() {
1201 let actual = actual_at(de.position, 0);
1202 if !actual.is_empty() && !de.codes.iter().any(|c| c.value == actual) {
1203 return false;
1204 }
1205 }
1206 }
1207 for comp in &mig_seg.composites {
1208 for de in &comp.data_elements {
1209 if !de.codes.is_empty() {
1210 let actual = actual_at(comp.position, de.position);
1211 if !actual.is_empty() && !de.codes.iter().any(|c| c.value == actual) {
1212 return false;
1213 }
1214 }
1215 }
1216 }
1217 true
1218}
1219
1220#[cfg(test)]
1221mod tests {
1222 use super::*;
1223 use crate::test_support::{make_mig_group, make_mig_group_with_variant, make_mig_segment};
1224
1225 fn make_owned_seg(id: &str, elements: Vec<Vec<&str>>) -> OwnedSegment {
1226 OwnedSegment {
1227 id: id.to_string(),
1228 elements: elements
1229 .into_iter()
1230 .map(|e| e.into_iter().map(|c| c.to_string()).collect())
1231 .collect(),
1232 segment_number: 0,
1233 }
1234 }
1235
1236 fn make_mig_schema(segments: Vec<&str>, groups: Vec<MigSegmentGroup>) -> MigSchema {
1237 MigSchema {
1238 message_type: "UTILMD".to_string(),
1239 variant: Some("Strom".to_string()),
1240 version: "S2.1".to_string(),
1241 publication_date: "2025-03-20".to_string(),
1242 author: "BDEW".to_string(),
1243 format_version: "FV2504".to_string(),
1244 source_file: "test".to_string(),
1245 segments: segments.into_iter().map(make_mig_segment).collect(),
1246 segment_groups: groups,
1247 }
1248 }
1249
1250 #[test]
1251 fn test_assembler_top_level_segments_only() {
1252 let mig = make_mig_schema(vec!["UNH", "BGM", "DTM", "UNT"], vec![]);
1253
1254 let segments = vec![
1255 make_owned_seg("UNH", vec![vec!["001", "UTILMD:D:11A:UN:S2.1"]]),
1256 make_owned_seg("BGM", vec![vec!["E01", "DOC001"]]),
1257 make_owned_seg("DTM", vec![vec!["137", "20250101", "102"]]),
1258 make_owned_seg("UNT", vec![vec!["4", "001"]]),
1259 ];
1260
1261 let assembler = Assembler::new(&mig);
1262 let result = assembler.assemble_generic(&segments).unwrap();
1263
1264 assert_eq!(result.segments.len(), 4);
1265 assert_eq!(result.segments[0].tag, "UNH");
1266 assert_eq!(result.segments[1].tag, "BGM");
1267 assert_eq!(result.segments[2].tag, "DTM");
1268 assert_eq!(result.segments[3].tag, "UNT");
1269 assert!(result.groups.is_empty());
1270 }
1271
1272 #[test]
1273 fn test_assembler_with_segment_group() {
1274 let mig = make_mig_schema(
1275 vec!["UNH", "BGM"],
1276 vec![
1277 make_mig_group("SG2", vec!["NAD"], vec![]),
1278 make_mig_group("SG4", vec!["IDE", "STS"], vec![]),
1279 ],
1280 );
1281
1282 let segments = vec![
1283 make_owned_seg("UNH", vec![vec!["001"]]),
1284 make_owned_seg("BGM", vec![vec!["E01"]]),
1285 make_owned_seg("NAD", vec![vec!["MS", "9900123"]]),
1286 make_owned_seg("NAD", vec![vec!["MR", "9900456"]]),
1287 make_owned_seg("IDE", vec![vec!["24", "TX001"]]),
1288 make_owned_seg("STS", vec![vec!["7"], vec!["Z33"]]),
1289 ];
1290
1291 let assembler = Assembler::new(&mig);
1292 let result = assembler.assemble_generic(&segments).unwrap();
1293
1294 assert_eq!(result.segments.len(), 2);
1296 assert_eq!(result.groups.len(), 2);
1298 assert_eq!(result.groups[0].group_id, "SG2");
1299 assert_eq!(result.groups[0].repetitions.len(), 2);
1300 assert_eq!(result.groups[0].repetitions[0].segments[0].tag, "NAD");
1301 assert_eq!(result.groups[0].repetitions[1].segments[0].tag, "NAD");
1302 assert_eq!(result.groups[1].group_id, "SG4");
1304 assert_eq!(result.groups[1].repetitions.len(), 1);
1305 assert_eq!(result.groups[1].repetitions[0].segments.len(), 2);
1306 }
1307
1308 #[test]
1309 fn test_assembler_nested_groups() {
1310 let sg3 = make_mig_group("SG3", vec!["CTA", "COM"], vec![]);
1311 let mig = make_mig_schema(
1312 vec!["UNH", "BGM"],
1313 vec![make_mig_group("SG2", vec!["NAD"], vec![sg3])],
1314 );
1315
1316 let segments = vec![
1317 make_owned_seg("UNH", vec![vec!["001"]]),
1318 make_owned_seg("BGM", vec![vec!["E01"]]),
1319 make_owned_seg("NAD", vec![vec!["MS", "9900123"]]),
1320 make_owned_seg("CTA", vec![vec!["IC", "Kontakt"]]),
1321 make_owned_seg("COM", vec![vec!["040@example.com", "EM"]]),
1322 ];
1323
1324 let assembler = Assembler::new(&mig);
1325 let result = assembler.assemble_generic(&segments).unwrap();
1326
1327 let sg2 = &result.groups[0];
1329 assert_eq!(sg2.group_id, "SG2");
1330 assert_eq!(sg2.repetitions.len(), 1);
1331
1332 let sg2_inst = &sg2.repetitions[0];
1333 assert_eq!(sg2_inst.segments[0].tag, "NAD");
1334
1335 assert_eq!(sg2_inst.child_groups.len(), 1);
1337 let sg3 = &sg2_inst.child_groups[0];
1338 assert_eq!(sg3.group_id, "SG3");
1339 assert_eq!(sg3.repetitions[0].segments.len(), 2);
1340 assert_eq!(sg3.repetitions[0].segments[0].tag, "CTA");
1341 assert_eq!(sg3.repetitions[0].segments[1].tag, "COM");
1342 }
1343
1344 #[test]
1345 fn test_assembler_optional_segments_skipped() {
1346 let mig = make_mig_schema(vec!["UNH", "BGM", "DTM", "UNT"], vec![]);
1348
1349 let segments = vec![
1350 make_owned_seg("UNH", vec![vec!["001"]]),
1351 make_owned_seg("BGM", vec![vec!["E01"]]),
1352 make_owned_seg("UNT", vec![vec!["2", "001"]]),
1353 ];
1354
1355 let assembler = Assembler::new(&mig);
1356 let result = assembler.assemble_generic(&segments).unwrap();
1357
1358 assert_eq!(result.segments.len(), 3);
1360 assert_eq!(result.segments[0].tag, "UNH");
1361 assert_eq!(result.segments[1].tag, "BGM");
1362 assert_eq!(result.segments[2].tag, "UNT");
1363 }
1364
1365 #[test]
1366 fn test_assembler_empty_segments() {
1367 let mig = make_mig_schema(vec!["UNH"], vec![]);
1368 let assembler = Assembler::new(&mig);
1369 let result = assembler.assemble_generic(&[]).unwrap();
1370 assert!(result.segments.is_empty());
1371 assert!(result.groups.is_empty());
1372 }
1373
1374 #[test]
1375 fn test_assembler_preserves_element_data() {
1376 let mig = make_mig_schema(vec!["DTM"], vec![]);
1377
1378 let segments = vec![make_owned_seg(
1379 "DTM",
1380 vec![vec!["137", "202501010000+01", "303"]],
1381 )];
1382
1383 let assembler = Assembler::new(&mig);
1384 let result = assembler.assemble_generic(&segments).unwrap();
1385
1386 let dtm = &result.segments[0];
1387 assert_eq!(dtm.elements[0][0], "137");
1388 assert_eq!(dtm.elements[0][1], "202501010000+01");
1389 assert_eq!(dtm.elements[0][2], "303");
1390 }
1391
1392 #[test]
1393 fn test_group_instance_as_assembled_tree() {
1394 let sg5 = AssembledGroup {
1396 group_id: "SG5".to_string(),
1397 repetitions: vec![AssembledGroupInstance {
1398 segments: vec![AssembledSegment {
1399 tag: "LOC".to_string(),
1400 elements: vec![vec!["Z16".to_string(), "DE000111222333".to_string()]],
1401 mig_number: None,
1402 segment_number: None,
1403 }],
1404 child_groups: vec![],
1405 entry_mig_number: None,
1406 variant_mig_numbers: vec![],
1407 skipped_segments: vec![],
1408 skipped_positions: Vec::new(),
1409 }],
1410 };
1411
1412 let sg4_instance = AssembledGroupInstance {
1413 segments: vec![
1414 AssembledSegment {
1415 tag: "IDE".to_string(),
1416 elements: vec![vec!["24".to_string(), "TX001".to_string()]],
1417 mig_number: None,
1418 segment_number: None,
1419 },
1420 AssembledSegment {
1421 tag: "STS".to_string(),
1422 elements: vec![vec!["7".to_string()]],
1423 mig_number: None,
1424 segment_number: None,
1425 },
1426 ],
1427 child_groups: vec![sg5],
1428 entry_mig_number: None,
1429 variant_mig_numbers: vec![],
1430 skipped_segments: vec![],
1431 skipped_positions: Vec::new(),
1432 };
1433
1434 let sub_tree = sg4_instance.as_assembled_tree();
1435
1436 assert_eq!(sub_tree.segments.len(), 2);
1438 assert_eq!(sub_tree.segments[0].tag, "IDE");
1439 assert_eq!(sub_tree.segments[1].tag, "STS");
1440
1441 assert_eq!(sub_tree.groups.len(), 1);
1443 assert_eq!(sub_tree.groups[0].group_id, "SG5");
1444
1445 assert_eq!(sub_tree.post_group_start, 2);
1447 }
1448
1449 #[test]
1450 fn test_assembler_from_parsed_edifact() {
1451 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'";
1453 let segments = crate::tokenize::parse_to_segments(input).unwrap();
1454
1455 let mig = make_mig_schema(vec!["UNB", "UNH", "BGM", "DTM", "UNT", "UNZ"], vec![]);
1456
1457 let assembler = Assembler::new(&mig);
1458 let result = assembler.assemble_generic(&segments).unwrap();
1459
1460 assert!(result.segments.iter().any(|s| s.tag == "UNH"));
1461 assert!(result.segments.iter().any(|s| s.tag == "BGM"));
1462 assert!(result.segments.iter().any(|s| s.tag == "DTM"));
1463 }
1464
1465 #[test]
1466 fn test_assemble_with_diagnostics_clean_input() {
1467 let mig = make_mig_schema(vec!["UNH", "BGM", "UNT"], vec![]);
1468 let segments = vec![
1469 make_owned_seg("UNH", vec![vec!["001"]]),
1470 make_owned_seg("BGM", vec![vec!["E01"]]),
1471 make_owned_seg("UNT", vec![vec!["2", "001"]]),
1472 ];
1473 let assembler = Assembler::new(&mig);
1474 let (tree, diagnostics) = assembler.assemble_with_diagnostics(&segments);
1475 assert_eq!(tree.segments.len(), 3);
1476 assert!(
1477 diagnostics.is_empty(),
1478 "Clean input should have no diagnostics"
1479 );
1480 }
1481
1482 #[test]
1483 fn test_assemble_with_diagnostics_unconsumed_segments() {
1484 let mig = make_mig_schema(vec!["UNH", "BGM"], vec![]);
1485 let segments = vec![
1486 make_owned_seg("UNH", vec![vec!["001"]]),
1487 make_owned_seg("BGM", vec![vec!["E01"]]),
1488 make_owned_seg("FTX", vec![vec!["AAA", "extra text"]]),
1489 ];
1490 let assembler = Assembler::new(&mig);
1491 let (tree, diagnostics) = assembler.assemble_with_diagnostics(&segments);
1492 assert_eq!(tree.segments.len(), 2);
1493 assert_eq!(diagnostics.len(), 1);
1494 assert_eq!(
1495 diagnostics[0].kind,
1496 StructureDiagnosticKind::UnexpectedSegment
1497 );
1498 assert_eq!(diagnostics[0].segment_id, "FTX");
1499 assert_eq!(diagnostics[0].position, 2);
1500 }
1501
1502 #[test]
1503 fn test_assemble_with_diagnostics_multiple_unconsumed() {
1504 let mig = make_mig_schema(vec!["UNH"], vec![]);
1505 let segments = vec![
1506 make_owned_seg("UNH", vec![vec!["001"]]),
1507 make_owned_seg("FOO", vec![]),
1508 make_owned_seg("BAR", vec![]),
1509 make_owned_seg("BAZ", vec![]),
1510 ];
1511 let assembler = Assembler::new(&mig);
1512 let (tree, diagnostics) = assembler.assemble_with_diagnostics(&segments);
1513 assert_eq!(tree.segments.len(), 1);
1514 assert_eq!(diagnostics.len(), 3);
1515 assert_eq!(diagnostics[0].segment_id, "FOO");
1516 assert_eq!(diagnostics[1].segment_id, "BAR");
1517 assert_eq!(diagnostics[2].segment_id, "BAZ");
1518 }
1519
1520 #[test]
1523 fn test_non_entry_segments_get_mig_number_from_bounded_slots() {
1524 use crate::test_support::make_mig_segment_numbered;
1528
1529 let sg4 = MigSegmentGroup {
1530 segments: vec![
1531 make_mig_segment_numbered("IDE", "00020"),
1532 make_mig_segment_numbered("DTM", "00023"),
1533 make_mig_segment_numbered("DTM", "00024"),
1534 make_mig_segment_numbered("STS", "00035"),
1535 ],
1536 ..make_mig_group("SG4", vec![], vec![])
1537 };
1538 let mig = make_mig_schema(vec!["UNH"], vec![sg4]);
1539
1540 let segments = vec![
1541 make_owned_seg("UNH", vec![vec!["001"]]),
1542 make_owned_seg("IDE", vec![vec!["24", "TX001"]]),
1543 make_owned_seg("DTM", vec![vec!["92", "202505312200+00", "303"]]),
1544 make_owned_seg("DTM", vec![vec!["93", "202512312300+00", "303"]]),
1545 make_owned_seg("STS", vec![vec!["7"], vec![], vec!["E01"]]),
1546 ];
1547
1548 let assembler = Assembler::new(&mig);
1549 let tree = assembler.assemble_generic(&segments).unwrap();
1550
1551 let sg4_instance = &tree.groups[0].repetitions[0];
1552
1553 assert_eq!(sg4_instance.segments[0].tag, "IDE");
1555 assert_eq!(
1556 sg4_instance.segments[0].mig_number.as_deref(),
1557 Some("00020")
1558 );
1559
1560 assert_eq!(sg4_instance.segments[1].tag, "DTM");
1562 assert_eq!(
1563 sg4_instance.segments[1].mig_number.as_deref(),
1564 Some("00023")
1565 );
1566
1567 assert_eq!(sg4_instance.segments[2].tag, "DTM");
1569 assert_eq!(
1570 sg4_instance.segments[2].mig_number.as_deref(),
1571 Some("00024")
1572 );
1573
1574 assert_eq!(sg4_instance.segments[3].tag, "STS");
1576 assert_eq!(
1577 sg4_instance.segments[3].mig_number.as_deref(),
1578 Some("00035")
1579 );
1580
1581 assert!(sg4_instance
1583 .variant_mig_numbers
1584 .contains(&"00020".to_string()));
1585 assert!(sg4_instance
1586 .variant_mig_numbers
1587 .contains(&"00023".to_string()));
1588 assert!(sg4_instance
1589 .variant_mig_numbers
1590 .contains(&"00024".to_string()));
1591 assert!(sg4_instance
1592 .variant_mig_numbers
1593 .contains(&"00035".to_string()));
1594 }
1595
1596 #[test]
1597 fn test_greedy_extra_segments_get_no_mig_number() {
1598 use crate::test_support::make_mig_segment_numbered;
1601
1602 let sg4 = MigSegmentGroup {
1603 segments: vec![
1604 make_mig_segment_numbered("IDE", "00020"),
1605 make_mig_segment_numbered("DTM", "00023"),
1606 ],
1607 ..make_mig_group("SG4", vec![], vec![])
1608 };
1609 let mig = make_mig_schema(vec!["UNH"], vec![sg4]);
1610
1611 let segments = vec![
1612 make_owned_seg("UNH", vec![vec!["001"]]),
1613 make_owned_seg("IDE", vec![vec!["24"]]),
1614 make_owned_seg("DTM", vec![vec!["92", "20250531"]]),
1615 make_owned_seg("DTM", vec![vec!["93", "20251231"]]), ];
1617
1618 let assembler = Assembler::new(&mig);
1619 let tree = assembler.assemble_generic(&segments).unwrap();
1620
1621 let sg4_instance = &tree.groups[0].repetitions[0];
1622 assert_eq!(sg4_instance.segments.len(), 3); assert_eq!(
1626 sg4_instance.segments[1].mig_number.as_deref(),
1627 Some("00023")
1628 );
1629
1630 assert_eq!(sg4_instance.segments[2].mig_number, None);
1632 }
1633
1634 #[test]
1637 fn test_qualifier_map_prevents_wrong_slot_consumption() {
1638 use crate::test_support::make_mig_segment_numbered;
1643 use std::collections::HashMap;
1644
1645 let sg4 = MigSegmentGroup {
1646 segments: vec![
1647 make_mig_segment_numbered("IDE", "00020"),
1648 make_mig_segment_numbered("DTM", "00023"),
1649 make_mig_segment_numbered("DTM", "00024"),
1650 ],
1651 ..make_mig_group("SG4", vec![], vec![])
1652 };
1653 let mig = make_mig_schema(vec!["UNH"], vec![sg4]);
1654
1655 let segments = vec![
1656 make_owned_seg("UNH", vec![vec!["001"]]),
1657 make_owned_seg("IDE", vec![vec!["24"]]),
1658 make_owned_seg("DTM", vec![vec!["93", "202512312300+00", "303"]]),
1659 ];
1660
1661 let mut qualifier_map = HashMap::new();
1662 qualifier_map.insert("00023".to_string(), (0, 0, "92".to_string()));
1663 qualifier_map.insert("00024".to_string(), (0, 0, "93".to_string()));
1664
1665 let config = AssemblerConfig {
1666 skip_unknown_segments: false,
1667 qualifier_map,
1668 ..Default::default()
1669 };
1670 let assembler = Assembler::with_config(&mig, config);
1671 let tree = assembler.assemble_generic(&segments).unwrap();
1672
1673 let sg4_instance = &tree.groups[0].repetitions[0];
1674
1675 assert_eq!(sg4_instance.segments.len(), 2); let dtm = &sg4_instance.segments[1];
1678 assert_eq!(dtm.tag, "DTM");
1679 assert_eq!(
1680 dtm.mig_number.as_deref(),
1681 Some("00024"),
1682 "DTM+93 should get mig_number 00024 (not 00023)"
1683 );
1684 }
1685
1686 #[test]
1687 fn test_group_entry_qualifier_mismatch_does_not_infinite_loop() {
1688 use crate::test_support::make_mig_segment_numbered;
1698 use std::collections::HashMap;
1699
1700 let sg5 = MigSegmentGroup {
1701 segments: vec![make_mig_segment_numbered("LOC", "00050")],
1702 ..make_mig_group("SG5", vec![], vec![])
1703 };
1704 let mig = make_mig_schema(vec!["UNH"], vec![sg5]);
1705
1706 let segments = vec![
1707 make_owned_seg("UNH", vec![vec!["001"]]),
1708 make_owned_seg("LOC", vec![vec!["172"], vec!["92003964705"]]),
1710 ];
1711
1712 let mut qualifier_map = HashMap::new();
1713 qualifier_map.insert("00050".to_string(), (0, 0, "Z16".to_string()));
1714
1715 let config = AssemblerConfig {
1716 skip_unknown_segments: false,
1717 qualifier_map,
1718 ..Default::default()
1719 };
1720 let assembler = Assembler::with_config(&mig, config);
1721
1722 let start = std::time::Instant::now();
1726 let tree = assembler.assemble_generic(&segments).unwrap();
1727 assert!(
1728 start.elapsed() < std::time::Duration::from_secs(5),
1729 "assembly took {:?} — suspected infinite-loop regression",
1730 start.elapsed()
1731 );
1732
1733 assert!(tree.groups.is_empty());
1737 }
1738
1739 #[test]
1742 fn test_skip_unknown_segment_between_slots() {
1743 let sg8 = make_mig_group("SG8", vec!["SEQ", "CCI"], vec![]);
1747 let mig = make_mig_schema(vec!["UNH"], vec![sg8.clone()]);
1748
1749 let segments = vec![
1750 make_owned_seg("UNH", vec![vec!["001"]]),
1751 make_owned_seg("SEQ", vec![vec!["Z98"]]),
1752 make_owned_seg("RFF", vec![vec!["Z38", "CROSSREF"]]),
1753 make_owned_seg("CCI", vec![vec!["Z30"]]),
1754 ];
1755
1756 let off = Assembler::new(&mig);
1758 let tree_off = off.assemble_generic(&segments).unwrap();
1759 let sg8_off = &tree_off.groups[0];
1760 assert_eq!(sg8_off.repetitions[0].segments.len(), 1); assert_eq!(sg8_off.repetitions[0].segments[0].tag, "SEQ");
1762
1763 let on = Assembler::with_config(
1765 &mig,
1766 AssemblerConfig {
1767 skip_unknown_segments: true,
1768 ..Default::default()
1769 },
1770 );
1771 let tree_on = on.assemble_generic(&segments).unwrap();
1772 let sg8_on = &tree_on.groups[0];
1773 assert_eq!(sg8_on.repetitions[0].segments.len(), 2); assert_eq!(sg8_on.repetitions[0].segments[0].tag, "SEQ");
1775 assert_eq!(sg8_on.repetitions[0].segments[1].tag, "CCI");
1776 }
1777
1778 #[test]
1779 fn test_skip_preserves_on_instance() {
1780 let sg8 = make_mig_group("SG8", vec!["SEQ", "CCI"], vec![]);
1782 let mig = make_mig_schema(vec!["UNH"], vec![sg8]);
1783
1784 let segments = vec![
1785 make_owned_seg("UNH", vec![vec!["001"]]),
1786 make_owned_seg("SEQ", vec![vec!["Z98"]]),
1787 make_owned_seg("RFF", vec![vec!["Z38", "REF1"]]),
1788 make_owned_seg("DTM", vec![vec!["92", "20250101"]]),
1789 make_owned_seg("CCI", vec![vec!["Z30"]]),
1790 ];
1791
1792 let assembler = Assembler::with_config(
1793 &mig,
1794 AssemblerConfig {
1795 skip_unknown_segments: true,
1796 ..Default::default()
1797 },
1798 );
1799 let tree = assembler.assemble_generic(&segments).unwrap();
1800 let instance = &tree.groups[0].repetitions[0];
1801
1802 assert_eq!(instance.segments.len(), 2); assert_eq!(instance.skipped_segments.len(), 2); assert_eq!(instance.skipped_segments[0].tag, "RFF");
1805 assert_eq!(instance.skipped_segments[1].tag, "DTM");
1806 }
1807
1808 #[test]
1809 fn test_skip_mode_off_default() {
1810 let mig = make_mig_schema(vec![], vec![]);
1812 let assembler = Assembler::new(&mig);
1813 assert!(!assembler.config.skip_unknown_segments);
1814 }
1815
1816 #[test]
1817 fn test_skip_does_not_consume_nested_group_entry() {
1818 let sg5 = make_mig_group("SG5", vec!["LOC"], vec![]);
1822 let sg4 = make_mig_group("SG4", vec!["IDE", "STS"], vec![sg5]);
1823 let mig = make_mig_schema(vec!["UNH"], vec![sg4]);
1824
1825 let segments = vec![
1826 make_owned_seg("UNH", vec![vec!["001"]]),
1827 make_owned_seg("IDE", vec![vec!["24"]]),
1828 make_owned_seg("FOO", vec![vec!["unknown"]]),
1829 make_owned_seg("STS", vec![vec!["7"]]),
1830 make_owned_seg("LOC", vec![vec!["Z16"]]),
1831 ];
1832
1833 let assembler = Assembler::with_config(
1834 &mig,
1835 AssemblerConfig {
1836 skip_unknown_segments: true,
1837 ..Default::default()
1838 },
1839 );
1840 let tree = assembler.assemble_generic(&segments).unwrap();
1841 let sg4 = &tree.groups[0];
1842 let inst = &sg4.repetitions[0];
1843
1844 assert_eq!(inst.segments.len(), 2);
1846 assert_eq!(inst.segments[0].tag, "IDE");
1847 assert_eq!(inst.segments[1].tag, "STS");
1848 assert_eq!(inst.skipped_segments.len(), 1);
1849 assert_eq!(inst.skipped_segments[0].tag, "FOO");
1850
1851 assert_eq!(inst.child_groups.len(), 1);
1853 assert_eq!(inst.child_groups[0].group_id, "SG5");
1854 assert_eq!(inst.child_groups[0].repetitions[0].segments[0].tag, "LOC");
1855 }
1856
1857 #[test]
1862 fn test_diagnostics_orphaned_group_segment_without_entry() {
1863 let sg10 = make_mig_group("SG10", vec!["CCI", "CAV"], vec![]);
1864 let sg8 = make_mig_group_with_variant("SG8", vec!["SEQ"], vec![sg10], "Z03");
1865 let sg12 = make_mig_group("SG12", vec!["NAD"], vec![]);
1866 let sg4 = make_mig_group("SG4", vec!["IDE"], vec![sg8, sg12]);
1867 let mig = make_mig_schema(vec!["UNH"], vec![sg4]);
1868
1869 let segments = vec![
1870 make_owned_seg("UNH", vec![vec!["001"]]),
1871 make_owned_seg("IDE", vec![vec!["24"]]),
1872 make_owned_seg("SEQ", vec![vec!["Z03"]]),
1873 make_owned_seg("CAV", vec![vec!["Z30", "", "", "X"]]),
1874 make_owned_seg("FOO", vec![vec!["unknown"]]),
1875 make_owned_seg("NAD", vec![vec!["Z07"]]),
1876 ];
1877
1878 let assembler = Assembler::with_config(
1879 &mig,
1880 AssemblerConfig {
1881 skip_unknown_segments: true,
1882 ..Default::default()
1883 },
1884 );
1885 let (tree, diagnostics) = assembler.assemble_with_diagnostics(&segments);
1886
1887 let sg4 = &tree.groups[0].repetitions[0];
1889 assert!(sg4.child_groups.iter().any(|g| g.group_id == "SG12"));
1890
1891 let orphaned: Vec<_> = diagnostics
1892 .iter()
1893 .filter(|d| d.kind == StructureDiagnosticKind::OrphanedGroupSegment)
1894 .collect();
1895 assert_eq!(orphaned.len(), 1, "{diagnostics:?}");
1896 assert_eq!(orphaned[0].segment_id, "CAV");
1897 assert_eq!(orphaned[0].position, 3);
1898 assert!(
1899 orphaned[0].message.contains("SG10") && orphaned[0].message.contains("'CCI'"),
1900 "message must name the group and its missing entry segment: {}",
1901 orphaned[0].message
1902 );
1903 assert!(
1904 !orphaned[0].message.contains("not defined"),
1905 "CAV is defined in the MIG: {}",
1906 orphaned[0].message
1907 );
1908
1909 let skipped: Vec<_> = diagnostics
1911 .iter()
1912 .filter(|d| d.kind == StructureDiagnosticKind::SkippedUnknownSegment)
1913 .collect();
1914 assert_eq!(skipped.len(), 1, "{diagnostics:?}");
1915 assert_eq!(skipped[0].segment_id, "FOO");
1916 }
1917
1918 #[test]
1919 fn test_skip_unknown_between_nested_group_reps() {
1920 let sg10 = make_mig_group("SG10", vec!["CCI"], vec![]);
1938 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ"], vec![sg10.clone()], "ZD7");
1939 let sg8_z98 = make_mig_group_with_variant("SG8", vec!["SEQ"], vec![], "Z98");
1940 let sg12 = make_mig_group("SG12", vec!["NAD"], vec![]);
1941 let sg4 = make_mig_group("SG4", vec!["IDE", "STS"], vec![sg8_zd7, sg8_z98, sg12]);
1942 let mig = make_mig_schema(vec!["UNH"], vec![sg4]);
1943
1944 let segments = vec![
1945 make_owned_seg("UNH", vec![vec!["001"]]),
1946 make_owned_seg("IDE", vec![vec!["24", "TX001"]]),
1947 make_owned_seg("STS", vec![vec!["7"], vec!["Z33"]]),
1948 make_owned_seg("SEQ", vec![vec!["ZD7"]]),
1949 make_owned_seg("CCI", vec![vec!["Z30"]]),
1950 make_owned_seg("FOO", vec![vec!["orphan"]]),
1951 make_owned_seg("SEQ", vec![vec!["Z98"]]),
1952 make_owned_seg("NAD", vec![vec!["MS", "9900123"]]),
1953 ];
1954
1955 let assembler = Assembler::with_config(
1956 &mig,
1957 AssemblerConfig {
1958 skip_unknown_segments: true,
1959 ..Default::default()
1960 },
1961 );
1962 let tree = assembler.assemble_generic(&segments).unwrap();
1963
1964 assert_eq!(tree.groups.len(), 1);
1965 let sg4_inst = &tree.groups[0].repetitions[0];
1966 assert_eq!(sg4_inst.child_groups.len(), 2, "expected SG8 + SG12");
1968 let sg8_tree = &sg4_inst.child_groups[0];
1969 assert_eq!(sg8_tree.group_id, "SG8");
1970 assert_eq!(
1971 sg8_tree.repetitions.len(),
1972 2,
1973 "SG8 should have both ZD7 and Z98 reps after orphan skip"
1974 );
1975 assert_eq!(sg8_tree.repetitions[0].segments[0].elements[0][0], "ZD7");
1976 assert_eq!(sg8_tree.repetitions[1].segments[0].elements[0][0], "Z98");
1977
1978 let sg12_tree = &sg4_inst.child_groups[1];
1979 assert_eq!(sg12_tree.group_id, "SG12");
1980 assert_eq!(sg12_tree.repetitions.len(), 1);
1981
1982 fn count_foo(inst: &AssembledGroupInstance) -> usize {
1987 let mut n = inst
1988 .skipped_segments
1989 .iter()
1990 .filter(|s| s.tag == "FOO")
1991 .count();
1992 for child in &inst.child_groups {
1993 for rep in &child.repetitions {
1994 n += count_foo(rep);
1995 }
1996 }
1997 n
1998 }
1999 assert_eq!(
2000 count_foo(sg4_inst),
2001 1,
2002 "FOO should be recorded exactly once"
2003 );
2004 }
2005
2006 #[test]
2007 fn test_skip_off_preserves_cascade_behavior() {
2008 let sg10 = make_mig_group("SG10", vec!["CCI"], vec![]);
2012 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ"], vec![sg10], "ZD7");
2013 let sg8_z98 = make_mig_group_with_variant("SG8", vec!["SEQ"], vec![], "Z98");
2014 let sg12 = make_mig_group("SG12", vec!["NAD"], vec![]);
2015 let sg4 = make_mig_group("SG4", vec!["IDE", "STS"], vec![sg8_zd7, sg8_z98, sg12]);
2016 let mig = make_mig_schema(vec!["UNH"], vec![sg4]);
2017
2018 let segments = vec![
2019 make_owned_seg("UNH", vec![vec!["001"]]),
2020 make_owned_seg("IDE", vec![vec!["24", "TX001"]]),
2021 make_owned_seg("STS", vec![vec!["7"], vec!["Z33"]]),
2022 make_owned_seg("SEQ", vec![vec!["ZD7"]]),
2023 make_owned_seg("CCI", vec![vec!["Z30"]]),
2024 make_owned_seg("FOO", vec![vec!["orphan"]]),
2025 make_owned_seg("SEQ", vec![vec!["Z98"]]),
2026 make_owned_seg("NAD", vec![vec!["MS", "9900123"]]),
2027 ];
2028
2029 let assembler = Assembler::new(&mig);
2030 let tree = assembler.assemble_generic(&segments).unwrap();
2031
2032 let sg4_inst = &tree.groups[0].repetitions[0];
2033 let sg8_tree = sg4_inst
2034 .child_groups
2035 .iter()
2036 .find(|g| g.group_id == "SG8")
2037 .expect("SG8 should still be present");
2038 assert_eq!(sg8_tree.repetitions.len(), 1);
2040 assert!(
2041 sg4_inst.skipped_segments.iter().all(|s| s.tag != "FOO"),
2042 "FOO must not be skipped when skip mode is off"
2043 );
2044 }
2045
2046 #[test]
2047 fn test_roundtrip_with_skip() {
2048 use crate::disassembler::Disassembler;
2051 use crate::renderer::render_edifact;
2052
2053 let sg8 = make_mig_group("SG8", vec!["SEQ", "CCI"], vec![]);
2054 let mig = make_mig_schema(vec!["UNH", "UNT"], vec![sg8]);
2055
2056 let segments = vec![
2057 make_owned_seg("UNH", vec![vec!["001"]]),
2058 make_owned_seg("SEQ", vec![vec!["Z98"]]),
2059 make_owned_seg("RFF", vec![vec!["Z38", "REF1"]]),
2060 make_owned_seg("CCI", vec![vec!["Z30"]]),
2061 make_owned_seg("UNT", vec![vec!["4", "001"]]),
2062 ];
2063
2064 let assembler = Assembler::with_config(
2065 &mig,
2066 AssemblerConfig {
2067 skip_unknown_segments: true,
2068 ..Default::default()
2069 },
2070 );
2071 let tree = assembler.assemble_generic(&segments).unwrap();
2072
2073 let disassembler = Disassembler::new(&mig);
2074 let dis = disassembler.disassemble(&tree);
2075 let delimiters = edifact_primitives::EdifactDelimiters::default();
2076 let rendered = render_edifact(&dis, &delimiters);
2077
2078 assert_eq!(dis.len(), 5);
2083 assert_eq!(dis[0].tag, "UNH");
2084 assert_eq!(dis[1].tag, "SEQ");
2085 assert_eq!(dis[2].tag, "CCI");
2086 assert_eq!(dis[3].tag, "RFF"); assert_eq!(dis[4].tag, "UNT");
2088
2089 assert!(rendered.contains("UNH+001"));
2091 assert!(rendered.contains("SEQ+Z98"));
2092 assert!(rendered.contains("RFF+Z38:REF1"));
2093 assert!(rendered.contains("CCI+Z30"));
2094 assert!(rendered.contains("UNT+4:001"));
2095 }
2096
2097 #[test]
2100 fn test_variant_groups_interleaved_reps() {
2101 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ", "CCI"], vec![], "ZD7");
2105 let sg8_z98 = make_mig_group_with_variant("SG8", vec!["SEQ", "RFF"], vec![], "Z98");
2106
2107 let mig = make_mig_schema(vec!["UNH"], vec![sg8_zd7, sg8_z98]);
2108
2109 let segments = vec![
2110 make_owned_seg("UNH", vec![vec!["001"]]),
2111 make_owned_seg("SEQ", vec![vec!["ZD7"]]),
2112 make_owned_seg("CCI", vec![vec!["Z30"]]),
2113 make_owned_seg("SEQ", vec![vec!["Z98"]]),
2114 make_owned_seg("RFF", vec![vec!["Z38", "REF1"]]),
2115 make_owned_seg("SEQ", vec![vec!["ZD7"]]),
2116 make_owned_seg("CCI", vec![vec!["Z31"]]),
2117 make_owned_seg("SEQ", vec![vec!["Z98"]]),
2118 make_owned_seg("RFF", vec![vec!["Z38", "REF2"]]),
2119 ];
2120
2121 let assembler = Assembler::new(&mig);
2122 let result = assembler.assemble_generic(&segments).unwrap();
2123
2124 assert_eq!(result.segments.len(), 1); assert_eq!(result.groups.len(), 1); let sg8 = &result.groups[0];
2127 assert_eq!(sg8.group_id, "SG8");
2128 assert_eq!(sg8.repetitions.len(), 4);
2129
2130 assert_eq!(sg8.repetitions[0].segments[0].elements[0][0], "ZD7");
2132 assert_eq!(sg8.repetitions[0].segments[1].tag, "CCI");
2133 assert_eq!(sg8.repetitions[1].segments[0].elements[0][0], "Z98");
2134 assert_eq!(sg8.repetitions[1].segments[1].tag, "RFF");
2135 assert_eq!(sg8.repetitions[2].segments[0].elements[0][0], "ZD7");
2136 assert_eq!(sg8.repetitions[3].segments[0].elements[0][0], "Z98");
2137 }
2138
2139 #[test]
2140 fn test_variant_groups_single_variant_type() {
2141 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ", "CCI"], vec![], "ZD7");
2143 let sg8_z98 = make_mig_group_with_variant("SG8", vec!["SEQ", "RFF"], vec![], "Z98");
2144
2145 let mig = make_mig_schema(vec!["UNH"], vec![sg8_zd7, sg8_z98]);
2146
2147 let segments = vec![
2148 make_owned_seg("UNH", vec![vec!["001"]]),
2149 make_owned_seg("SEQ", vec![vec!["Z98"]]),
2150 make_owned_seg("RFF", vec![vec!["Z38", "REF1"]]),
2151 make_owned_seg("SEQ", vec![vec!["Z98"]]),
2152 make_owned_seg("RFF", vec![vec!["Z38", "REF2"]]),
2153 ];
2154
2155 let assembler = Assembler::new(&mig);
2156 let result = assembler.assemble_generic(&segments).unwrap();
2157
2158 assert_eq!(result.groups.len(), 1);
2159 assert_eq!(result.groups[0].repetitions.len(), 2);
2160 assert_eq!(
2161 result.groups[0].repetitions[0].segments[0].elements[0][0],
2162 "Z98"
2163 );
2164 assert_eq!(
2165 result.groups[0].repetitions[1].segments[0].elements[0][0],
2166 "Z98"
2167 );
2168 }
2169
2170 #[test]
2171 fn test_non_variant_groups_unchanged() {
2172 let sg2 = make_mig_group("SG2", vec!["NAD"], vec![]);
2174 let sg4 = make_mig_group("SG4", vec!["IDE", "STS"], vec![]);
2175
2176 let mig = make_mig_schema(vec!["UNH", "BGM"], vec![sg2, sg4]);
2177
2178 let segments = vec![
2179 make_owned_seg("UNH", vec![vec!["001"]]),
2180 make_owned_seg("BGM", vec![vec!["E01"]]),
2181 make_owned_seg("NAD", vec![vec!["MS", "9900123"]]),
2182 make_owned_seg("NAD", vec![vec!["MR", "9900456"]]),
2183 make_owned_seg("IDE", vec![vec!["24", "TX001"]]),
2184 make_owned_seg("STS", vec![vec!["7"], vec!["Z33"]]),
2185 ];
2186
2187 let assembler = Assembler::new(&mig);
2188 let result = assembler.assemble_generic(&segments).unwrap();
2189
2190 assert_eq!(result.segments.len(), 2);
2191 assert_eq!(result.groups.len(), 2);
2192 assert_eq!(result.groups[0].group_id, "SG2");
2193 assert_eq!(result.groups[0].repetitions.len(), 2);
2194 assert_eq!(result.groups[1].group_id, "SG4");
2195 assert_eq!(result.groups[1].repetitions.len(), 1);
2196 }
2197
2198 #[test]
2199 fn test_variant_groups_with_nested_children() {
2200 let sg10 = make_mig_group("SG10", vec!["CCI", "CAV"], vec![]);
2202 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ"], vec![sg10.clone()], "ZD7");
2203 let sg8_z98 = make_mig_group_with_variant("SG8", vec!["SEQ"], vec![sg10], "Z98");
2204
2205 let mig = make_mig_schema(vec!["UNH"], vec![sg8_zd7, sg8_z98]);
2206
2207 let segments = vec![
2208 make_owned_seg("UNH", vec![vec!["001"]]),
2209 make_owned_seg("SEQ", vec![vec!["ZD7"]]),
2210 make_owned_seg("CCI", vec![vec!["Z30"]]),
2211 make_owned_seg("CAV", vec![vec!["Z91", "Y"]]),
2212 make_owned_seg("SEQ", vec![vec!["Z98"]]),
2213 make_owned_seg("CCI", vec![vec!["Z31"]]),
2214 make_owned_seg("CAV", vec![vec!["Z91", "N"]]),
2215 ];
2216
2217 let assembler = Assembler::new(&mig);
2218 let result = assembler.assemble_generic(&segments).unwrap();
2219
2220 assert_eq!(result.groups.len(), 1);
2221 let sg8 = &result.groups[0];
2222 assert_eq!(sg8.repetitions.len(), 2);
2223
2224 assert_eq!(sg8.repetitions[0].child_groups.len(), 1);
2226 assert_eq!(sg8.repetitions[0].child_groups[0].group_id, "SG10");
2227 assert_eq!(
2228 sg8.repetitions[0].child_groups[0].repetitions[0].segments[0].elements[0][0],
2229 "Z30"
2230 );
2231
2232 assert_eq!(sg8.repetitions[1].child_groups.len(), 1);
2234 assert_eq!(
2235 sg8.repetitions[1].child_groups[0].repetitions[0].segments[0].elements[0][0],
2236 "Z31"
2237 );
2238 }
2239
2240 #[test]
2241 fn test_variant_qualifier_check_prevents_wrong_variant_consumption() {
2242 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ", "CCI"], vec![], "ZD7");
2245
2246 let mig = make_mig_schema(vec!["UNH"], vec![sg8_zd7]);
2247
2248 let segments = vec![
2249 make_owned_seg("UNH", vec![vec!["001"]]),
2250 make_owned_seg("SEQ", vec![vec!["Z98"]]), make_owned_seg("CCI", vec![vec!["Z30"]]),
2252 ];
2253
2254 let assembler = Assembler::new(&mig);
2255 let result = assembler.assemble_generic(&segments).unwrap();
2256
2257 assert!(result.groups.is_empty());
2259 }
2260
2261 #[test]
2262 fn test_mixed_variant_and_non_variant_groups() {
2263 let sg2 = make_mig_group("SG2", vec!["NAD"], vec![]);
2265 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ", "CCI"], vec![], "ZD7");
2266 let sg8_z98 = make_mig_group_with_variant("SG8", vec!["SEQ", "RFF"], vec![], "Z98");
2267 let sg12 = make_mig_group("SG12", vec!["NAD"], vec![]);
2268
2269 let mig = make_mig_schema(vec!["UNH"], vec![sg2, sg8_zd7, sg8_z98, sg12]);
2270
2271 let segments = vec![
2272 make_owned_seg("UNH", vec![vec!["001"]]),
2273 make_owned_seg("NAD", vec![vec!["MS", "9900123"]]),
2274 make_owned_seg("SEQ", vec![vec!["ZD7"]]),
2275 make_owned_seg("CCI", vec![vec!["Z30"]]),
2276 make_owned_seg("SEQ", vec![vec!["Z98"]]),
2277 make_owned_seg("RFF", vec![vec!["Z38", "REF1"]]),
2278 make_owned_seg("NAD", vec![vec!["Z65", "ID001"]]),
2279 ];
2280
2281 let assembler = Assembler::new(&mig);
2282 let result = assembler.assemble_generic(&segments).unwrap();
2283
2284 assert_eq!(result.groups.len(), 3); assert_eq!(result.groups[0].group_id, "SG2");
2286 assert_eq!(result.groups[0].repetitions.len(), 1);
2287 assert_eq!(result.groups[1].group_id, "SG8");
2288 assert_eq!(result.groups[1].repetitions.len(), 2);
2289 assert_eq!(result.groups[2].group_id, "SG12");
2290 assert_eq!(result.groups[2].repetitions.len(), 1);
2291 }
2292
2293 #[test]
2294 fn test_assembler_disambiguates_shared_qualifier_by_full_code_profile() {
2295 use mig_types::schema::common::CodeDefinition;
2303 use mig_types::schema::mig::{MigComposite, MigDataElement};
2304 use std::collections::HashMap;
2305
2306 fn code(value: &str) -> CodeDefinition {
2307 CodeDefinition {
2308 value: value.to_string(),
2309 name: value.to_string(),
2310 description: None,
2311 }
2312 }
2313
2314 fn pia_slot(number: &str, composite_code: &str) -> MigSegment {
2315 MigSegment {
2316 id: "PIA".to_string(),
2317 name: "PIA".to_string(),
2318 description: None,
2319 counter: None,
2320 level: 1,
2321 number: Some(number.to_string()),
2322 max_rep_std: 1,
2323 max_rep_spec: 1,
2324 status_std: Some("M".to_string()),
2325 status_spec: Some("M".to_string()),
2326 example: None,
2327 data_elements: vec![MigDataElement {
2328 id: "4347".to_string(),
2329 name: "Produkt-ID-Funktion".to_string(),
2330 description: None,
2331 status_std: Some("M".to_string()),
2332 status_spec: Some("M".to_string()),
2333 format_std: None,
2334 format_spec: None,
2335 codes: vec![code("5")],
2336 position: 0,
2337 }],
2338 composites: vec![MigComposite {
2339 id: "C212".to_string(),
2340 name: "Item Identifier".to_string(),
2341 description: None,
2342 status_std: Some("M".to_string()),
2343 status_spec: Some("M".to_string()),
2344 data_elements: vec![MigDataElement {
2345 id: "7143".to_string(),
2346 name: "Artikel/Dienstleistung-ID".to_string(),
2347 description: None,
2348 status_std: Some("M".to_string()),
2349 status_spec: Some("M".to_string()),
2350 format_std: None,
2351 format_spec: None,
2352 codes: vec![code(composite_code)],
2353 position: 0,
2354 }],
2355 position: 1,
2356 }],
2357 }
2358 }
2359
2360 let sg4 = MigSegmentGroup {
2361 segments: vec![
2362 crate::test_support::make_mig_segment_numbered("IDE", "00020"),
2363 pia_slot("00108", "Z12"),
2364 pia_slot("00197", "SRW"),
2365 ],
2366 ..make_mig_group("SG4", vec![], vec![])
2367 };
2368 let mig = make_mig_schema(vec!["UNH"], vec![sg4]);
2369
2370 let segments = vec![
2371 make_owned_seg("UNH", vec![vec!["001"]]),
2372 make_owned_seg("IDE", vec![vec!["24"]]),
2373 make_owned_seg("PIA", vec![vec!["5"], vec!["SRW"]]),
2375 ];
2376
2377 let mut qualifier_map = HashMap::new();
2381 qualifier_map.insert("00108".to_string(), (0, 0, "5".to_string()));
2382 qualifier_map.insert("00197".to_string(), (0, 0, "5".to_string()));
2383
2384 let config = AssemblerConfig {
2385 skip_unknown_segments: false,
2386 qualifier_map,
2387 strict_code_matching: true,
2388 };
2389 let assembler = Assembler::with_config(&mig, config);
2390 let tree = assembler.assemble_generic(&segments).unwrap();
2391
2392 let sg4_instance = &tree.groups[0].repetitions[0];
2393 let pia = sg4_instance
2394 .segments
2395 .iter()
2396 .find(|s| s.tag == "PIA")
2397 .expect("PIA consumed into SG4");
2398 assert_eq!(
2399 pia.mig_number.as_deref(),
2400 Some("00197"),
2401 "PIA+5+:::SRW must be assigned the SRW variant (mig=00197), not the Z12 variant"
2402 );
2403 }
2404
2405 #[test]
2406 fn a_repeatable_slot_keeps_its_repetitions() {
2407 use mig_types::schema::common::CodeDefinition;
2413 use mig_types::schema::mig::{MigComposite, MigDataElement};
2414 use std::collections::HashMap;
2415
2416 fn cav_slot(number: &str, codes: &[&str], max_rep_spec: i32) -> MigSegment {
2417 MigSegment {
2418 id: "CAV".to_string(),
2419 name: "CAV".to_string(),
2420 description: None,
2421 counter: None,
2422 level: 1,
2423 number: Some(number.to_string()),
2424 max_rep_std: 99,
2425 max_rep_spec,
2426 status_std: Some("M".to_string()),
2427 status_spec: Some("M".to_string()),
2428 example: None,
2429 data_elements: vec![],
2430 composites: vec![MigComposite {
2431 id: "C889".to_string(),
2432 name: "Merkmalswert".to_string(),
2433 description: None,
2434 status_std: Some("M".to_string()),
2435 status_spec: Some("M".to_string()),
2436 data_elements: vec![MigDataElement {
2437 id: "7111".to_string(),
2438 name: "Merkmalswert, Code".to_string(),
2439 description: None,
2440 status_std: Some("M".to_string()),
2441 status_spec: Some("M".to_string()),
2442 format_std: None,
2443 format_spec: None,
2444 codes: codes
2445 .iter()
2446 .map(|c| CodeDefinition {
2447 value: c.to_string(),
2448 name: c.to_string(),
2449 description: None,
2450 })
2451 .collect(),
2452 position: 0,
2453 }],
2454 position: 0,
2455 }],
2456 }
2457 }
2458
2459 let sg10 = MigSegmentGroup {
2460 segments: vec![
2461 crate::test_support::make_mig_segment_numbered("CCI", "00340"),
2462 cav_slot("00341", &["Z30"], 3),
2463 cav_slot("00342", &["MIW", "MPW", "MBW", "MUW"], 1),
2464 ],
2465 ..make_mig_group("SG10", vec![], vec![])
2466 };
2467 let mig = make_mig_schema(vec!["UNH"], vec![sg10]);
2468 let segments = vec![
2469 make_owned_seg("UNH", vec![vec!["001"]]),
2470 make_owned_seg("CCI", vec![vec![""], vec![""], vec!["Z25"]]),
2471 make_owned_seg("CAV", vec![vec!["Z30", "", "", "W1"]]),
2472 make_owned_seg("CAV", vec![vec!["Z30", "", "", "W2"]]),
2473 make_owned_seg("CAV", vec![vec!["MIW", "", "", "30"]]),
2474 ];
2475 let config = AssemblerConfig {
2476 skip_unknown_segments: false,
2477 qualifier_map: HashMap::new(),
2478 strict_code_matching: true,
2479 };
2480 let tree = Assembler::with_config(&mig, config)
2481 .assemble_generic(&segments)
2482 .unwrap();
2483 let numbers: Vec<Option<&str>> = tree.groups[0].repetitions[0]
2484 .segments
2485 .iter()
2486 .map(|s| s.mig_number.as_deref())
2487 .collect();
2488 assert_eq!(
2489 numbers,
2490 [Some("00340"), Some("00341"), Some("00341"), Some("00342")]
2491 );
2492 }
2493}