1use crate::assembler::{AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree};
7use crate::error::AssemblyError;
8use mig_types::schema::mig::{MigSchema, MigSegmentGroup};
9
10#[derive(Debug, Clone)]
12pub struct DisassembledSegment {
13 pub tag: String,
14 pub elements: Vec<Vec<String>>,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct MissingEntrySegment {
25 pub group_path: String,
27 pub source_path: String,
30 pub entry_segment: String,
32 pub present_segments: Vec<String>,
34}
35
36pub struct Disassembler<'a> {
38 mig: &'a MigSchema,
39}
40
41struct Emit {
43 output: Vec<DisassembledSegment>,
44 path: Vec<(String, String)>,
46 missing: Vec<MissingEntrySegment>,
47}
48
49impl Emit {
50 fn push(&mut self, seg: &AssembledSegment) {
51 self.output.push(assembled_to_disassembled(seg));
52 }
53}
54
55impl<'a> Disassembler<'a> {
56 pub fn new(mig: &'a MigSchema) -> Self {
57 Self { mig }
58 }
59
60 pub fn disassemble(&self, tree: &AssembledTree) -> Vec<DisassembledSegment> {
75 self.walk(tree).output
76 }
77
78 pub fn disassemble_checked(
87 &self,
88 tree: &AssembledTree,
89 ) -> Result<Vec<DisassembledSegment>, AssemblyError> {
90 let emit = self.walk(tree);
91 match emit.missing.into_iter().next() {
92 None => Ok(emit.output),
93 Some(m) => Err(AssemblyError::MissingGroupEntrySegment {
94 group_path: m.group_path,
95 source_path: m.source_path,
96 entry_segment: m.entry_segment,
97 present_segments: m.present_segments,
98 }),
99 }
100 }
101
102 pub fn missing_entry_segments(&self, tree: &AssembledTree) -> Vec<MissingEntrySegment> {
105 self.walk(tree).missing
106 }
107
108 fn walk(&self, tree: &AssembledTree) -> Emit {
109 let mut emit = Emit {
110 output: Vec::new(),
111 path: Vec::new(),
112 missing: Vec::new(),
113 };
114
115 let pre_group = &tree.segments[..tree.post_group_start];
117 let mut consumed = vec![false; pre_group.len()];
118 for mig_seg in &self.mig.segments {
119 if let Some(idx) = pre_group
120 .iter()
121 .enumerate()
122 .position(|(i, s)| !consumed[i] && s.tag == mig_seg.id)
123 {
124 emit.push(&pre_group[idx]);
125 consumed[idx] = true;
126 }
127 }
128
129 let mut consumed_groups = vec![false; tree.groups.len()];
134 let mut mig_group_idx = 0;
135 while mig_group_idx < self.mig.segment_groups.len() {
136 let mig_group = &self.mig.segment_groups[mig_group_idx];
137
138 if mig_group.variant_code.is_some() {
140 let variant_count = self.mig.segment_groups[mig_group_idx..]
141 .iter()
142 .take_while(|g| g.id == mig_group.id && g.variant_code.is_some())
143 .count();
144 let variant_defs =
145 &self.mig.segment_groups[mig_group_idx..mig_group_idx + variant_count];
146
147 if let Some(idx) = tree
148 .groups
149 .iter()
150 .enumerate()
151 .position(|(i, g)| !consumed_groups[i] && g.group_id == mig_group.id)
152 {
153 if let Some(inter_segs) = tree.inter_group_segments.get(&idx) {
154 for seg in inter_segs {
155 emit.push(seg);
156 }
157 }
158 self.emit_variant_group(&tree.groups[idx], variant_defs, &mut emit);
159 consumed_groups[idx] = true;
160 }
161 mig_group_idx += variant_count;
162 } else {
163 if let Some(idx) = tree
164 .groups
165 .iter()
166 .enumerate()
167 .position(|(i, g)| !consumed_groups[i] && g.group_id == mig_group.id)
168 {
169 if let Some(inter_segs) = tree.inter_group_segments.get(&idx) {
171 for seg in inter_segs {
172 emit.push(seg);
173 }
174 }
175 self.emit_group(&tree.groups[idx], mig_group, &mut emit);
176 consumed_groups[idx] = true;
177 }
178 mig_group_idx += 1;
179 }
180 }
181
182 let trailing_idx = tree.groups.len();
185 if let Some(inter_segs) = tree.inter_group_segments.get(&trailing_idx) {
186 for seg in inter_segs {
187 emit.push(seg);
188 }
189 }
190
191 for seg in &tree.segments[tree.post_group_start..] {
193 emit.push(seg);
194 }
195
196 emit
197 }
198
199 fn emit_group(&self, group: &AssembledGroup, mig_group: &MigSegmentGroup, emit: &mut Emit) {
200 for instance in &group.repetitions {
201 self.emit_group_instance(instance, mig_group, emit);
202 }
203 }
204
205 fn emit_variant_group(
220 &self,
221 group: &AssembledGroup,
222 variant_defs: &[MigSegmentGroup],
223 emit: &mut Emit,
224 ) {
225 for instance in &group.repetitions {
226 let matches_variant = |v: &MigSegmentGroup| {
227 let (ei, ci) = v.variant_qualifier_position.unwrap_or((0, 0));
228 let entry_tag = v.segments.first().map(|s| s.id.as_str());
232 let qual = instance
233 .segments
234 .iter()
235 .find(|s| Some(s.tag.as_str()) == entry_tag)
236 .or_else(|| instance.segments.first())
237 .and_then(|s| s.elements.get(ei))
238 .and_then(|e| e.get(ci))
239 .map(|v| v.as_str())
240 .unwrap_or("");
241 if !v.variant_codes.is_empty() {
242 v.variant_codes.iter().any(|c| c.eq_ignore_ascii_case(qual))
243 } else {
244 v.variant_code
245 .as_deref()
246 .is_some_and(|vc| vc.eq_ignore_ascii_case(qual))
247 }
248 };
249
250 let variant_def = variant_defs
251 .iter()
252 .find(|v| matches_variant(v))
253 .unwrap_or(&variant_defs[0]); self.emit_group_instance(instance, variant_def, emit);
256 }
257 }
258
259 fn check_entry_segment(
262 instance: &AssembledGroupInstance,
263 mig_group: &MigSegmentGroup,
264 emit: &mut Emit,
265 ) -> (String, String) {
266 let join = |parent: Option<&String>, child: String| match parent {
267 Some(p) => format!("{p}.{child}"),
268 None => child,
269 };
270 let parent = emit.path.last();
271 let group_path = join(parent.map(|p| &p.0), mig_group.id.clone());
272 let missing_entry = mig_group.segments.first().filter(|entry| {
273 let has_content = !instance.segments.is_empty()
274 || instance
275 .child_groups
276 .iter()
277 .any(|g| !g.repetitions.is_empty());
278 has_content && !instance.segments.iter().any(|s| s.tag == entry.id)
279 });
280 let own_source = match &mig_group.variant_code {
284 Some(vc) if missing_entry.is_none() => {
285 format!("{}_{}", mig_group.id, vc).to_lowercase()
286 }
287 _ => mig_group.id.to_lowercase(),
288 };
289 let source_path = join(parent.map(|p| &p.1), own_source);
290
291 if let Some(entry) = missing_entry {
292 emit.missing.push(MissingEntrySegment {
293 group_path: group_path.clone(),
294 source_path: source_path.clone(),
295 entry_segment: entry.id.clone(),
296 present_segments: instance.segments.iter().map(|s| s.tag.clone()).collect(),
297 });
298 }
299 (group_path, source_path)
300 }
301
302 fn emit_group_instance(
303 &self,
304 instance: &AssembledGroupInstance,
305 mig_group: &MigSegmentGroup,
306 emit: &mut Emit,
307 ) {
308 let paths = Self::check_entry_segment(instance, mig_group, emit);
309 emit.path.push(paths);
310
311 let mut consumed = vec![false; instance.segments.len()];
324 for (slot_idx, mig_seg) in mig_group.segments.iter().enumerate() {
325 if let Some(idx) = instance
326 .segments
327 .iter()
328 .enumerate()
329 .position(|(i, s)| !consumed[i] && s.tag == mig_seg.id)
330 {
331 emit.push(&instance.segments[idx]);
332 consumed[idx] = true;
333 }
334 let rest = &mig_group.segments[slot_idx + 1..];
335 let is_entry_run = mig_group.segments[..=slot_idx]
336 .iter()
337 .all(|s| s.id == mig_seg.id);
338 if is_entry_run || rest.iter().any(|s| s.id == mig_seg.id) {
339 continue;
340 }
341 for (i, seg) in instance.segments.iter().enumerate() {
342 if !consumed[i] && seg.tag == mig_seg.id {
343 emit.push(seg);
344 consumed[i] = true;
345 }
346 }
347 }
348 for (i, seg) in instance.segments.iter().enumerate() {
350 if !consumed[i] {
351 emit.push(seg);
352 }
353 }
354
355 for skipped in &instance.skipped_segments {
357 emit.push(skipped);
358 }
359
360 let mut consumed_child = vec![false; instance.child_groups.len()];
363 let mut nested_idx = 0;
364 while nested_idx < mig_group.nested_groups.len() {
365 let nested_mig = &mig_group.nested_groups[nested_idx];
366
367 if nested_mig.variant_code.is_some() {
368 let variant_count = mig_group.nested_groups[nested_idx..]
369 .iter()
370 .take_while(|g| g.id == nested_mig.id && g.variant_code.is_some())
371 .count();
372 let variant_defs = &mig_group.nested_groups[nested_idx..nested_idx + variant_count];
373
374 if let Some(idx) = instance
375 .child_groups
376 .iter()
377 .enumerate()
378 .position(|(i, g)| !consumed_child[i] && g.group_id == nested_mig.id)
379 {
380 self.emit_variant_group(&instance.child_groups[idx], variant_defs, emit);
381 consumed_child[idx] = true;
382 }
383 nested_idx += variant_count;
384 } else {
385 if let Some(idx) = instance
386 .child_groups
387 .iter()
388 .enumerate()
389 .position(|(i, g)| !consumed_child[i] && g.group_id == nested_mig.id)
390 {
391 self.emit_group(&instance.child_groups[idx], nested_mig, emit);
392 consumed_child[idx] = true;
393 }
394 nested_idx += 1;
395 }
396 }
397
398 emit.path.pop();
399 }
400}
401
402fn assembled_to_disassembled(seg: &AssembledSegment) -> DisassembledSegment {
403 DisassembledSegment {
404 tag: seg.tag.clone(),
405 elements: seg.elements.clone(),
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412 use crate::assembler::{
413 AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree,
414 };
415 use crate::test_support::{make_mig_group, make_mig_segment};
416 use mig_types::schema::mig::MigSchema;
417
418 #[test]
419 fn test_disassemble_top_level_only() {
420 let mig = MigSchema {
421 message_type: "UTILMD".to_string(),
422 variant: Some("Strom".to_string()),
423 version: "S2.1".to_string(),
424 publication_date: "2025-03-20".to_string(),
425 author: "BDEW".to_string(),
426 format_version: "FV2504".to_string(),
427 source_file: "test".to_string(),
428 segments: vec![make_mig_segment("UNH"), make_mig_segment("BGM")],
429 segment_groups: vec![],
430 };
431
432 let tree = AssembledTree {
433 segments: vec![
434 AssembledSegment {
435 tag: "UNH".to_string(),
436 elements: vec![
437 vec!["1".to_string()],
438 vec![
439 "UTILMD".to_string(),
440 "D".to_string(),
441 "11A".to_string(),
442 "UN".to_string(),
443 "S2.1".to_string(),
444 ],
445 ],
446 mig_number: None,
447 segment_number: None,
448 },
449 AssembledSegment {
450 tag: "BGM".to_string(),
451 elements: vec![
452 vec!["E01".to_string()],
453 vec!["MSG001".to_string()],
454 vec!["9".to_string()],
455 ],
456 mig_number: None,
457 segment_number: None,
458 },
459 ],
460 groups: vec![],
461 post_group_start: 2,
462 inter_group_segments: std::collections::BTreeMap::new(),
463 };
464
465 let disassembler = Disassembler::new(&mig);
466 let segments = disassembler.disassemble(&tree);
467
468 assert_eq!(segments.len(), 2);
469 assert_eq!(segments[0].tag, "UNH");
470 assert_eq!(segments[1].tag, "BGM");
471 assert_eq!(segments[0].elements[0], vec!["1"]);
472 }
473
474 #[test]
475 fn test_disassemble_with_groups() {
476 let mig = MigSchema {
477 message_type: "UTILMD".to_string(),
478 variant: None,
479 version: "S2.1".to_string(),
480 publication_date: "".to_string(),
481 author: "".to_string(),
482 format_version: "FV2504".to_string(),
483 source_file: "test".to_string(),
484 segments: vec![make_mig_segment("UNH"), make_mig_segment("BGM")],
485 segment_groups: vec![make_mig_group("SG2", vec!["NAD", "LOC"], vec![])],
486 };
487
488 let tree = AssembledTree {
489 segments: vec![
490 AssembledSegment {
491 tag: "UNH".to_string(),
492 elements: vec![vec!["1".to_string()]],
493 mig_number: None,
494 segment_number: None,
495 },
496 AssembledSegment {
497 tag: "BGM".to_string(),
498 elements: vec![vec!["E01".to_string()]],
499 mig_number: None,
500 segment_number: None,
501 },
502 ],
503 post_group_start: 2,
504 groups: vec![AssembledGroup {
505 group_id: "SG2".to_string(),
506 repetitions: vec![
507 AssembledGroupInstance {
508 segments: vec![AssembledSegment {
509 tag: "NAD".to_string(),
510 elements: vec![vec!["MS".to_string()]],
511 mig_number: None,
512 segment_number: None,
513 }],
514 child_groups: vec![],
515 entry_mig_number: None,
516 variant_mig_numbers: vec![],
517 skipped_segments: vec![],
518 skipped_positions: Vec::new(),
519 },
520 AssembledGroupInstance {
521 segments: vec![AssembledSegment {
522 tag: "NAD".to_string(),
523 elements: vec![vec!["MR".to_string()]],
524 mig_number: None,
525 segment_number: None,
526 }],
527 child_groups: vec![],
528 entry_mig_number: None,
529 variant_mig_numbers: vec![],
530 skipped_segments: vec![],
531 skipped_positions: Vec::new(),
532 },
533 ],
534 }],
535 inter_group_segments: std::collections::BTreeMap::new(),
536 };
537
538 let disassembler = Disassembler::new(&mig);
539 let segments = disassembler.disassemble(&tree);
540
541 assert_eq!(segments.len(), 4); assert_eq!(segments[0].tag, "UNH");
543 assert_eq!(segments[1].tag, "BGM");
544 assert_eq!(segments[2].tag, "NAD");
545 assert_eq!(segments[2].elements[0][0], "MS");
546 assert_eq!(segments[3].tag, "NAD");
547 assert_eq!(segments[3].elements[0][0], "MR");
548 }
549
550 #[test]
551 fn test_disassemble_nested_groups() {
552 let sg3 = make_mig_group("SG3", vec!["CTA", "COM"], vec![]);
553 let mig = MigSchema {
554 message_type: "UTILMD".to_string(),
555 variant: None,
556 version: "S2.1".to_string(),
557 publication_date: "".to_string(),
558 author: "".to_string(),
559 format_version: "FV2504".to_string(),
560 source_file: "test".to_string(),
561 segments: vec![make_mig_segment("UNH")],
562 segment_groups: vec![make_mig_group("SG2", vec!["NAD"], vec![sg3])],
563 };
564
565 let tree = AssembledTree {
566 segments: vec![AssembledSegment {
567 tag: "UNH".to_string(),
568 elements: vec![vec!["1".to_string()]],
569 mig_number: None,
570 segment_number: None,
571 }],
572 post_group_start: 1,
573 groups: vec![AssembledGroup {
574 group_id: "SG2".to_string(),
575 repetitions: vec![AssembledGroupInstance {
576 segments: vec![AssembledSegment {
577 tag: "NAD".to_string(),
578 elements: vec![vec!["MS".to_string()]],
579 mig_number: None,
580 segment_number: None,
581 }],
582 child_groups: vec![AssembledGroup {
583 group_id: "SG3".to_string(),
584 repetitions: vec![AssembledGroupInstance {
585 segments: vec![
586 AssembledSegment {
587 tag: "CTA".to_string(),
588 elements: vec![vec!["IC".to_string()]],
589 mig_number: None,
590 segment_number: None,
591 },
592 AssembledSegment {
593 tag: "COM".to_string(),
594 elements: vec![vec![
595 "040@ex.com".to_string(),
596 "EM".to_string(),
597 ]],
598 mig_number: None,
599 segment_number: None,
600 },
601 ],
602 child_groups: vec![],
603 entry_mig_number: None,
604 variant_mig_numbers: vec![],
605 skipped_segments: vec![],
606 skipped_positions: Vec::new(),
607 }],
608 }],
609 entry_mig_number: None,
610 variant_mig_numbers: vec![],
611 skipped_segments: vec![],
612 skipped_positions: Vec::new(),
613 }],
614 }],
615 inter_group_segments: std::collections::BTreeMap::new(),
616 };
617
618 let disassembler = Disassembler::new(&mig);
619 let segments = disassembler.disassemble(&tree);
620
621 assert_eq!(segments.len(), 4); assert_eq!(segments[0].tag, "UNH");
623 assert_eq!(segments[1].tag, "NAD");
624 assert_eq!(segments[2].tag, "CTA");
625 assert_eq!(segments[3].tag, "COM");
626 }
627
628 #[test]
629 fn test_disassemble_variant_groups_uses_per_variant_ordering() {
630 use crate::test_support::make_mig_group_with_variant;
631
632 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ", "RFF", "DTM"], vec![], "ZD7");
636 let sg8_z98 = make_mig_group_with_variant("SG8", vec!["SEQ", "CCI", "CAV"], vec![], "Z98");
637
638 let mig = MigSchema {
639 message_type: "UTILMD".to_string(),
640 variant: None,
641 version: "S2.1".to_string(),
642 publication_date: "".to_string(),
643 author: "".to_string(),
644 format_version: "FV2504".to_string(),
645 source_file: "test".to_string(),
646 segments: vec![make_mig_segment("UNH")],
647 segment_groups: vec![sg8_zd7, sg8_z98],
648 };
649
650 let tree = AssembledTree {
652 segments: vec![AssembledSegment {
653 tag: "UNH".to_string(),
654 elements: vec![vec!["1".to_string()]],
655 mig_number: None,
656 segment_number: None,
657 }],
658 post_group_start: 1,
659 groups: vec![AssembledGroup {
660 group_id: "SG8".to_string(),
661 repetitions: vec![
662 AssembledGroupInstance {
664 segments: vec![
665 AssembledSegment {
666 tag: "SEQ".to_string(),
667 elements: vec![vec!["ZD7".to_string()]],
668 mig_number: None,
669 segment_number: None,
670 },
671 AssembledSegment {
672 tag: "DTM".to_string(),
673 elements: vec![vec!["303".to_string()]],
674 mig_number: None,
675 segment_number: None,
676 },
677 AssembledSegment {
678 tag: "RFF".to_string(),
679 elements: vec![vec!["Z13".to_string()]],
680 mig_number: None,
681 segment_number: None,
682 },
683 ],
684 child_groups: vec![],
685 entry_mig_number: None,
686 variant_mig_numbers: vec![],
687 skipped_segments: vec![],
688 skipped_positions: Vec::new(),
689 },
690 AssembledGroupInstance {
692 segments: vec![
693 AssembledSegment {
694 tag: "SEQ".to_string(),
695 elements: vec![vec!["Z98".to_string()]],
696 mig_number: None,
697 segment_number: None,
698 },
699 AssembledSegment {
700 tag: "CAV".to_string(),
701 elements: vec![vec!["Z91".to_string()]],
702 mig_number: None,
703 segment_number: None,
704 },
705 AssembledSegment {
706 tag: "CCI".to_string(),
707 elements: vec![vec!["".to_string()]],
708 mig_number: None,
709 segment_number: None,
710 },
711 ],
712 child_groups: vec![],
713 entry_mig_number: None,
714 variant_mig_numbers: vec![],
715 skipped_segments: vec![],
716 skipped_positions: Vec::new(),
717 },
718 AssembledGroupInstance {
720 segments: vec![
721 AssembledSegment {
722 tag: "SEQ".to_string(),
723 elements: vec![vec!["ZD7".to_string()]],
724 mig_number: None,
725 segment_number: None,
726 },
727 AssembledSegment {
728 tag: "RFF".to_string(),
729 elements: vec![vec!["Z34".to_string()]],
730 mig_number: None,
731 segment_number: None,
732 },
733 ],
734 child_groups: vec![],
735 entry_mig_number: None,
736 variant_mig_numbers: vec![],
737 skipped_segments: vec![],
738 skipped_positions: Vec::new(),
739 },
740 ],
741 }],
742 inter_group_segments: std::collections::BTreeMap::new(),
743 };
744
745 let disassembler = Disassembler::new(&mig);
746 let segments = disassembler.disassemble(&tree);
747
748 assert_eq!(segments[0].tag, "UNH");
750
751 assert_eq!(segments[1].tag, "SEQ");
753 assert_eq!(segments[1].elements[0][0], "ZD7");
754 assert_eq!(segments[2].tag, "RFF"); assert_eq!(segments[3].tag, "DTM"); assert_eq!(segments[4].tag, "SEQ");
759 assert_eq!(segments[4].elements[0][0], "Z98");
760 assert_eq!(segments[5].tag, "CCI"); assert_eq!(segments[6].tag, "CAV"); assert_eq!(segments[7].tag, "SEQ");
765 assert_eq!(segments[7].elements[0][0], "ZD7");
766 assert_eq!(segments[8].tag, "RFF");
767
768 assert_eq!(segments.len(), 9);
769 }
770
771 #[test]
779 fn test_disassemble_variant_group_matches_any_allowed_code() {
780 use crate::test_support::make_mig_group_with_variant;
781
782 let seg = |tag: &str, qual: &str| AssembledSegment {
783 tag: tag.to_string(),
784 elements: vec![vec![qual.to_string()]],
785 mig_number: None,
786 segment_number: None,
787 };
788 let instance = |segments: Vec<AssembledSegment>, child_groups: Vec<AssembledGroup>| {
789 AssembledGroupInstance {
790 segments,
791 child_groups,
792 entry_mig_number: None,
793 variant_mig_numbers: vec![],
794 skipped_segments: vec![],
795 skipped_positions: Vec::new(),
796 }
797 };
798 let group = |id: &str, reps: Vec<AssembledGroupInstance>| AssembledGroup {
799 group_id: id.to_string(),
800 repetitions: reps,
801 };
802
803 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ", "RFF"], vec![], "ZD7");
805 let mut sg8_z01 = make_mig_group_with_variant(
806 "SG8",
807 vec!["SEQ"],
808 vec![make_mig_group("SG9", vec!["QTY"], vec![])],
809 "Z01",
810 );
811 sg8_z01.variant_codes = vec!["Z01".to_string(), "Z98".to_string()];
812
813 let mig = MigSchema {
814 message_type: "UTILMD".to_string(),
815 variant: None,
816 version: "S2.1".to_string(),
817 publication_date: String::new(),
818 author: String::new(),
819 format_version: "FV2504".to_string(),
820 source_file: "test".to_string(),
821 segments: vec![],
822 segment_groups: vec![sg8_zd7, sg8_z01],
823 };
824
825 let tree = AssembledTree {
826 segments: vec![],
827 post_group_start: 0,
828 groups: vec![group(
829 "SG8",
830 vec![instance(
831 vec![seg("SEQ", "Z98")],
832 vec![group("SG9", vec![instance(vec![seg("QTY", "31")], vec![])])],
833 )],
834 )],
835 inter_group_segments: std::collections::BTreeMap::new(),
836 };
837
838 let tags: Vec<String> = Disassembler::new(&mig)
839 .disassemble(&tree)
840 .iter()
841 .map(|s| s.tag.clone())
842 .collect();
843 assert_eq!(tags, ["SEQ", "QTY"], "SG9 under SEQ+Z98 must survive");
844 }
845
846 #[test]
847 fn test_disassemble_empty_tree() {
848 let mig = MigSchema {
849 message_type: "UTILMD".to_string(),
850 variant: None,
851 version: "S2.1".to_string(),
852 publication_date: "".to_string(),
853 author: "".to_string(),
854 format_version: "FV2504".to_string(),
855 source_file: "test".to_string(),
856 segments: vec![make_mig_segment("UNH")],
857 segment_groups: vec![],
858 };
859
860 let tree = AssembledTree {
861 segments: vec![],
862 groups: vec![],
863 post_group_start: 0,
864 inter_group_segments: std::collections::BTreeMap::new(),
865 };
866
867 let disassembler = Disassembler::new(&mig);
868 let segments = disassembler.disassemble(&tree);
869 assert!(segments.is_empty());
870 }
871
872 fn seg(tag: &str, first: &str) -> AssembledSegment {
873 AssembledSegment {
874 tag: tag.to_string(),
875 elements: vec![vec![first.to_string()]],
876 mig_number: None,
877 segment_number: None,
878 }
879 }
880
881 fn instance(
882 segments: Vec<AssembledSegment>,
883 child_groups: Vec<AssembledGroup>,
884 ) -> AssembledGroupInstance {
885 AssembledGroupInstance {
886 segments,
887 child_groups,
888 entry_mig_number: None,
889 variant_mig_numbers: vec![],
890 skipped_segments: vec![],
891 skipped_positions: vec![],
892 }
893 }
894
895 fn sg10_mig_and_tree(sg10: AssembledGroupInstance) -> (MigSchema, AssembledTree) {
897 let sg10_mig = make_mig_group("SG10", vec!["CCI", "CAV"], vec![]);
898 let sg8_mig = crate::test_support::make_mig_group_with_variant(
899 "SG8",
900 vec!["SEQ"],
901 vec![sg10_mig],
902 "Z03",
903 );
904 let sg4_mig = make_mig_group("SG4", vec!["IDE"], vec![sg8_mig]);
905 let mig = MigSchema {
906 message_type: "UTILMD".to_string(),
907 variant: None,
908 version: "S2.1".to_string(),
909 publication_date: "".to_string(),
910 author: "".to_string(),
911 format_version: "FV2604".to_string(),
912 source_file: "test".to_string(),
913 segments: vec![make_mig_segment("UNH")],
914 segment_groups: vec![sg4_mig],
915 };
916 let sg8 = instance(
917 vec![seg("SEQ", "Z03")],
918 vec![AssembledGroup {
919 group_id: "SG10".to_string(),
920 repetitions: vec![sg10],
921 }],
922 );
923 let sg4 = instance(
924 vec![seg("IDE", "24")],
925 vec![AssembledGroup {
926 group_id: "SG8".to_string(),
927 repetitions: vec![sg8],
928 }],
929 );
930 let tree = AssembledTree {
931 segments: vec![seg("UNH", "1")],
932 groups: vec![AssembledGroup {
933 group_id: "SG4".to_string(),
934 repetitions: vec![sg4],
935 }],
936 post_group_start: 1,
937 inter_group_segments: std::collections::BTreeMap::new(),
938 };
939 (mig, tree)
940 }
941
942 fn sg5_tags(segments: Vec<AssembledSegment>) -> Vec<String> {
945 let mig = MigSchema {
946 message_type: "UTILTS".to_string(),
947 variant: None,
948 version: "1.1e".to_string(),
949 publication_date: "".to_string(),
950 author: "".to_string(),
951 format_version: "FV2504".to_string(),
952 source_file: "test".to_string(),
953 segments: vec![make_mig_segment("UNH")],
954 segment_groups: vec![make_mig_group(
955 "SG5",
956 vec!["IDE", "STS", "FTX", "RFF", "RFF"],
957 vec![],
958 )],
959 };
960 let tree = AssembledTree {
961 segments: vec![seg("UNH", "1")],
962 groups: vec![AssembledGroup {
963 group_id: "SG5".to_string(),
964 repetitions: vec![instance(segments, vec![])],
965 }],
966 post_group_start: 1,
967 inter_group_segments: std::collections::BTreeMap::new(),
968 };
969 Disassembler::new(&mig)
970 .disassemble(&tree)
971 .into_iter()
972 .map(|s| format!("{}+{}", s.tag, s.elements[0][0]))
973 .collect()
974 }
975
976 #[test]
977 fn repeated_segment_stays_at_its_mig_slot() {
978 let tags = sg5_tags(vec![
979 seg("FTX", "1"),
980 seg("FTX", "2"),
981 seg("IDE", "24"),
982 seg("RFF", "A"),
983 seg("RFF", "B"),
984 seg("RFF", "C"),
985 seg("STS", "1"),
986 seg("STS", "2"),
987 ]);
988 assert_eq!(
989 tags,
990 ["UNH+1", "IDE+24", "STS+1", "STS+2", "FTX+1", "FTX+2", "RFF+A", "RFF+B", "RFF+C"]
991 );
992 }
993
994 #[test]
995 fn repeated_entry_segment_is_not_pulled_into_the_entry_slot() {
996 let tags = sg5_tags(vec![seg("IDE", "1"), seg("IDE", "2"), seg("STS", "1")]);
999 assert_eq!(tags, ["UNH+1", "IDE+1", "STS+1", "IDE+2"]);
1000 }
1001
1002 #[test]
1003 fn checked_disassembly_refuses_group_without_entry_segment() {
1004 let (mig, tree) = sg10_mig_and_tree(instance(vec![seg("CAV", "Z30")], vec![]));
1005 let disassembler = Disassembler::new(&mig);
1006
1007 let tags: Vec<_> = disassembler
1009 .disassemble(&tree)
1010 .into_iter()
1011 .map(|s| s.tag)
1012 .collect();
1013 assert_eq!(tags, ["UNH", "IDE", "SEQ", "CAV"]);
1014
1015 let expected = MissingEntrySegment {
1016 group_path: "SG4.SG8.SG10".to_string(),
1017 source_path: "sg4.sg8_z03.sg10".to_string(),
1018 entry_segment: "CCI".to_string(),
1019 present_segments: vec!["CAV".to_string()],
1020 };
1021 assert_eq!(
1022 disassembler.missing_entry_segments(&tree),
1023 vec![expected.clone()]
1024 );
1025 match disassembler.disassemble_checked(&tree) {
1026 Err(AssemblyError::MissingGroupEntrySegment {
1027 group_path,
1028 source_path,
1029 entry_segment,
1030 present_segments,
1031 }) => {
1032 assert_eq!(group_path, expected.group_path);
1033 assert_eq!(source_path, expected.source_path);
1034 assert_eq!(entry_segment, expected.entry_segment);
1035 assert_eq!(present_segments, expected.present_segments);
1036 }
1037 other => panic!("expected MissingGroupEntrySegment, got {other:?}"),
1038 }
1039 }
1040
1041 #[test]
1042 fn checked_disassembly_accepts_complete_groups() {
1043 let (mig, tree) =
1044 sg10_mig_and_tree(instance(vec![seg("CCI", ""), seg("CAV", "Z30")], vec![]));
1045 let disassembler = Disassembler::new(&mig);
1046 assert!(disassembler.missing_entry_segments(&tree).is_empty());
1047 let tags: Vec<_> = disassembler
1048 .disassemble_checked(&tree)
1049 .expect("complete tree")
1050 .into_iter()
1051 .map(|s| s.tag)
1052 .collect();
1053 assert_eq!(tags, ["UNH", "IDE", "SEQ", "CCI", "CAV"]);
1054 }
1055
1056 #[test]
1057 fn empty_group_instance_is_not_a_missing_entry_segment() {
1058 let (mig, tree) = sg10_mig_and_tree(instance(vec![], vec![]));
1059 assert!(Disassembler::new(&mig)
1060 .missing_entry_segments(&tree)
1061 .is_empty());
1062 }
1063}