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()];
319 for mig_seg in &mig_group.segments {
320 if let Some(idx) = instance
321 .segments
322 .iter()
323 .enumerate()
324 .position(|(i, s)| !consumed[i] && s.tag == mig_seg.id)
325 {
326 emit.push(&instance.segments[idx]);
327 consumed[idx] = true;
328 }
329 }
330 for (i, seg) in instance.segments.iter().enumerate() {
332 if !consumed[i] {
333 emit.push(seg);
334 }
335 }
336
337 for skipped in &instance.skipped_segments {
339 emit.push(skipped);
340 }
341
342 let mut consumed_child = vec![false; instance.child_groups.len()];
345 let mut nested_idx = 0;
346 while nested_idx < mig_group.nested_groups.len() {
347 let nested_mig = &mig_group.nested_groups[nested_idx];
348
349 if nested_mig.variant_code.is_some() {
350 let variant_count = mig_group.nested_groups[nested_idx..]
351 .iter()
352 .take_while(|g| g.id == nested_mig.id && g.variant_code.is_some())
353 .count();
354 let variant_defs = &mig_group.nested_groups[nested_idx..nested_idx + variant_count];
355
356 if let Some(idx) = instance
357 .child_groups
358 .iter()
359 .enumerate()
360 .position(|(i, g)| !consumed_child[i] && g.group_id == nested_mig.id)
361 {
362 self.emit_variant_group(&instance.child_groups[idx], variant_defs, emit);
363 consumed_child[idx] = true;
364 }
365 nested_idx += variant_count;
366 } else {
367 if let Some(idx) = instance
368 .child_groups
369 .iter()
370 .enumerate()
371 .position(|(i, g)| !consumed_child[i] && g.group_id == nested_mig.id)
372 {
373 self.emit_group(&instance.child_groups[idx], nested_mig, emit);
374 consumed_child[idx] = true;
375 }
376 nested_idx += 1;
377 }
378 }
379
380 emit.path.pop();
381 }
382}
383
384fn assembled_to_disassembled(seg: &AssembledSegment) -> DisassembledSegment {
385 DisassembledSegment {
386 tag: seg.tag.clone(),
387 elements: seg.elements.clone(),
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394 use crate::assembler::{
395 AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree,
396 };
397 use crate::test_support::{make_mig_group, make_mig_segment};
398 use mig_types::schema::mig::MigSchema;
399
400 #[test]
401 fn test_disassemble_top_level_only() {
402 let mig = MigSchema {
403 message_type: "UTILMD".to_string(),
404 variant: Some("Strom".to_string()),
405 version: "S2.1".to_string(),
406 publication_date: "2025-03-20".to_string(),
407 author: "BDEW".to_string(),
408 format_version: "FV2504".to_string(),
409 source_file: "test".to_string(),
410 segments: vec![make_mig_segment("UNH"), make_mig_segment("BGM")],
411 segment_groups: vec![],
412 };
413
414 let tree = AssembledTree {
415 segments: vec![
416 AssembledSegment {
417 tag: "UNH".to_string(),
418 elements: vec![
419 vec!["1".to_string()],
420 vec![
421 "UTILMD".to_string(),
422 "D".to_string(),
423 "11A".to_string(),
424 "UN".to_string(),
425 "S2.1".to_string(),
426 ],
427 ],
428 mig_number: None,
429 segment_number: None,
430 },
431 AssembledSegment {
432 tag: "BGM".to_string(),
433 elements: vec![
434 vec!["E01".to_string()],
435 vec!["MSG001".to_string()],
436 vec!["9".to_string()],
437 ],
438 mig_number: None,
439 segment_number: None,
440 },
441 ],
442 groups: vec![],
443 post_group_start: 2,
444 inter_group_segments: std::collections::BTreeMap::new(),
445 };
446
447 let disassembler = Disassembler::new(&mig);
448 let segments = disassembler.disassemble(&tree);
449
450 assert_eq!(segments.len(), 2);
451 assert_eq!(segments[0].tag, "UNH");
452 assert_eq!(segments[1].tag, "BGM");
453 assert_eq!(segments[0].elements[0], vec!["1"]);
454 }
455
456 #[test]
457 fn test_disassemble_with_groups() {
458 let mig = MigSchema {
459 message_type: "UTILMD".to_string(),
460 variant: None,
461 version: "S2.1".to_string(),
462 publication_date: "".to_string(),
463 author: "".to_string(),
464 format_version: "FV2504".to_string(),
465 source_file: "test".to_string(),
466 segments: vec![make_mig_segment("UNH"), make_mig_segment("BGM")],
467 segment_groups: vec![make_mig_group("SG2", vec!["NAD", "LOC"], vec![])],
468 };
469
470 let tree = AssembledTree {
471 segments: vec![
472 AssembledSegment {
473 tag: "UNH".to_string(),
474 elements: vec![vec!["1".to_string()]],
475 mig_number: None,
476 segment_number: None,
477 },
478 AssembledSegment {
479 tag: "BGM".to_string(),
480 elements: vec![vec!["E01".to_string()]],
481 mig_number: None,
482 segment_number: None,
483 },
484 ],
485 post_group_start: 2,
486 groups: vec![AssembledGroup {
487 group_id: "SG2".to_string(),
488 repetitions: vec![
489 AssembledGroupInstance {
490 segments: vec![AssembledSegment {
491 tag: "NAD".to_string(),
492 elements: vec![vec!["MS".to_string()]],
493 mig_number: None,
494 segment_number: None,
495 }],
496 child_groups: vec![],
497 entry_mig_number: None,
498 variant_mig_numbers: vec![],
499 skipped_segments: vec![],
500 skipped_positions: Vec::new(),
501 },
502 AssembledGroupInstance {
503 segments: vec![AssembledSegment {
504 tag: "NAD".to_string(),
505 elements: vec![vec!["MR".to_string()]],
506 mig_number: None,
507 segment_number: None,
508 }],
509 child_groups: vec![],
510 entry_mig_number: None,
511 variant_mig_numbers: vec![],
512 skipped_segments: vec![],
513 skipped_positions: Vec::new(),
514 },
515 ],
516 }],
517 inter_group_segments: std::collections::BTreeMap::new(),
518 };
519
520 let disassembler = Disassembler::new(&mig);
521 let segments = disassembler.disassemble(&tree);
522
523 assert_eq!(segments.len(), 4); assert_eq!(segments[0].tag, "UNH");
525 assert_eq!(segments[1].tag, "BGM");
526 assert_eq!(segments[2].tag, "NAD");
527 assert_eq!(segments[2].elements[0][0], "MS");
528 assert_eq!(segments[3].tag, "NAD");
529 assert_eq!(segments[3].elements[0][0], "MR");
530 }
531
532 #[test]
533 fn test_disassemble_nested_groups() {
534 let sg3 = make_mig_group("SG3", vec!["CTA", "COM"], vec![]);
535 let mig = MigSchema {
536 message_type: "UTILMD".to_string(),
537 variant: None,
538 version: "S2.1".to_string(),
539 publication_date: "".to_string(),
540 author: "".to_string(),
541 format_version: "FV2504".to_string(),
542 source_file: "test".to_string(),
543 segments: vec![make_mig_segment("UNH")],
544 segment_groups: vec![make_mig_group("SG2", vec!["NAD"], vec![sg3])],
545 };
546
547 let tree = AssembledTree {
548 segments: vec![AssembledSegment {
549 tag: "UNH".to_string(),
550 elements: vec![vec!["1".to_string()]],
551 mig_number: None,
552 segment_number: None,
553 }],
554 post_group_start: 1,
555 groups: vec![AssembledGroup {
556 group_id: "SG2".to_string(),
557 repetitions: vec![AssembledGroupInstance {
558 segments: vec![AssembledSegment {
559 tag: "NAD".to_string(),
560 elements: vec![vec!["MS".to_string()]],
561 mig_number: None,
562 segment_number: None,
563 }],
564 child_groups: vec![AssembledGroup {
565 group_id: "SG3".to_string(),
566 repetitions: vec![AssembledGroupInstance {
567 segments: vec![
568 AssembledSegment {
569 tag: "CTA".to_string(),
570 elements: vec![vec!["IC".to_string()]],
571 mig_number: None,
572 segment_number: None,
573 },
574 AssembledSegment {
575 tag: "COM".to_string(),
576 elements: vec![vec![
577 "040@ex.com".to_string(),
578 "EM".to_string(),
579 ]],
580 mig_number: None,
581 segment_number: None,
582 },
583 ],
584 child_groups: vec![],
585 entry_mig_number: None,
586 variant_mig_numbers: vec![],
587 skipped_segments: vec![],
588 skipped_positions: Vec::new(),
589 }],
590 }],
591 entry_mig_number: None,
592 variant_mig_numbers: vec![],
593 skipped_segments: vec![],
594 skipped_positions: Vec::new(),
595 }],
596 }],
597 inter_group_segments: std::collections::BTreeMap::new(),
598 };
599
600 let disassembler = Disassembler::new(&mig);
601 let segments = disassembler.disassemble(&tree);
602
603 assert_eq!(segments.len(), 4); assert_eq!(segments[0].tag, "UNH");
605 assert_eq!(segments[1].tag, "NAD");
606 assert_eq!(segments[2].tag, "CTA");
607 assert_eq!(segments[3].tag, "COM");
608 }
609
610 #[test]
611 fn test_disassemble_variant_groups_uses_per_variant_ordering() {
612 use crate::test_support::make_mig_group_with_variant;
613
614 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ", "RFF", "DTM"], vec![], "ZD7");
618 let sg8_z98 = make_mig_group_with_variant("SG8", vec!["SEQ", "CCI", "CAV"], vec![], "Z98");
619
620 let mig = MigSchema {
621 message_type: "UTILMD".to_string(),
622 variant: None,
623 version: "S2.1".to_string(),
624 publication_date: "".to_string(),
625 author: "".to_string(),
626 format_version: "FV2504".to_string(),
627 source_file: "test".to_string(),
628 segments: vec![make_mig_segment("UNH")],
629 segment_groups: vec![sg8_zd7, sg8_z98],
630 };
631
632 let tree = AssembledTree {
634 segments: vec![AssembledSegment {
635 tag: "UNH".to_string(),
636 elements: vec![vec!["1".to_string()]],
637 mig_number: None,
638 segment_number: None,
639 }],
640 post_group_start: 1,
641 groups: vec![AssembledGroup {
642 group_id: "SG8".to_string(),
643 repetitions: vec![
644 AssembledGroupInstance {
646 segments: vec![
647 AssembledSegment {
648 tag: "SEQ".to_string(),
649 elements: vec![vec!["ZD7".to_string()]],
650 mig_number: None,
651 segment_number: None,
652 },
653 AssembledSegment {
654 tag: "DTM".to_string(),
655 elements: vec![vec!["303".to_string()]],
656 mig_number: None,
657 segment_number: None,
658 },
659 AssembledSegment {
660 tag: "RFF".to_string(),
661 elements: vec![vec!["Z13".to_string()]],
662 mig_number: None,
663 segment_number: None,
664 },
665 ],
666 child_groups: vec![],
667 entry_mig_number: None,
668 variant_mig_numbers: vec![],
669 skipped_segments: vec![],
670 skipped_positions: Vec::new(),
671 },
672 AssembledGroupInstance {
674 segments: vec![
675 AssembledSegment {
676 tag: "SEQ".to_string(),
677 elements: vec![vec!["Z98".to_string()]],
678 mig_number: None,
679 segment_number: None,
680 },
681 AssembledSegment {
682 tag: "CAV".to_string(),
683 elements: vec![vec!["Z91".to_string()]],
684 mig_number: None,
685 segment_number: None,
686 },
687 AssembledSegment {
688 tag: "CCI".to_string(),
689 elements: vec![vec!["".to_string()]],
690 mig_number: None,
691 segment_number: None,
692 },
693 ],
694 child_groups: vec![],
695 entry_mig_number: None,
696 variant_mig_numbers: vec![],
697 skipped_segments: vec![],
698 skipped_positions: Vec::new(),
699 },
700 AssembledGroupInstance {
702 segments: vec![
703 AssembledSegment {
704 tag: "SEQ".to_string(),
705 elements: vec![vec!["ZD7".to_string()]],
706 mig_number: None,
707 segment_number: None,
708 },
709 AssembledSegment {
710 tag: "RFF".to_string(),
711 elements: vec![vec!["Z34".to_string()]],
712 mig_number: None,
713 segment_number: None,
714 },
715 ],
716 child_groups: vec![],
717 entry_mig_number: None,
718 variant_mig_numbers: vec![],
719 skipped_segments: vec![],
720 skipped_positions: Vec::new(),
721 },
722 ],
723 }],
724 inter_group_segments: std::collections::BTreeMap::new(),
725 };
726
727 let disassembler = Disassembler::new(&mig);
728 let segments = disassembler.disassemble(&tree);
729
730 assert_eq!(segments[0].tag, "UNH");
732
733 assert_eq!(segments[1].tag, "SEQ");
735 assert_eq!(segments[1].elements[0][0], "ZD7");
736 assert_eq!(segments[2].tag, "RFF"); assert_eq!(segments[3].tag, "DTM"); assert_eq!(segments[4].tag, "SEQ");
741 assert_eq!(segments[4].elements[0][0], "Z98");
742 assert_eq!(segments[5].tag, "CCI"); assert_eq!(segments[6].tag, "CAV"); assert_eq!(segments[7].tag, "SEQ");
747 assert_eq!(segments[7].elements[0][0], "ZD7");
748 assert_eq!(segments[8].tag, "RFF");
749
750 assert_eq!(segments.len(), 9);
751 }
752
753 #[test]
761 fn test_disassemble_variant_group_matches_any_allowed_code() {
762 use crate::test_support::make_mig_group_with_variant;
763
764 let seg = |tag: &str, qual: &str| AssembledSegment {
765 tag: tag.to_string(),
766 elements: vec![vec![qual.to_string()]],
767 mig_number: None,
768 segment_number: None,
769 };
770 let instance = |segments: Vec<AssembledSegment>, child_groups: Vec<AssembledGroup>| {
771 AssembledGroupInstance {
772 segments,
773 child_groups,
774 entry_mig_number: None,
775 variant_mig_numbers: vec![],
776 skipped_segments: vec![],
777 skipped_positions: Vec::new(),
778 }
779 };
780 let group = |id: &str, reps: Vec<AssembledGroupInstance>| AssembledGroup {
781 group_id: id.to_string(),
782 repetitions: reps,
783 };
784
785 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ", "RFF"], vec![], "ZD7");
787 let mut sg8_z01 = make_mig_group_with_variant(
788 "SG8",
789 vec!["SEQ"],
790 vec![make_mig_group("SG9", vec!["QTY"], vec![])],
791 "Z01",
792 );
793 sg8_z01.variant_codes = vec!["Z01".to_string(), "Z98".to_string()];
794
795 let mig = MigSchema {
796 message_type: "UTILMD".to_string(),
797 variant: None,
798 version: "S2.1".to_string(),
799 publication_date: String::new(),
800 author: String::new(),
801 format_version: "FV2504".to_string(),
802 source_file: "test".to_string(),
803 segments: vec![],
804 segment_groups: vec![sg8_zd7, sg8_z01],
805 };
806
807 let tree = AssembledTree {
808 segments: vec![],
809 post_group_start: 0,
810 groups: vec![group(
811 "SG8",
812 vec![instance(
813 vec![seg("SEQ", "Z98")],
814 vec![group("SG9", vec![instance(vec![seg("QTY", "31")], vec![])])],
815 )],
816 )],
817 inter_group_segments: std::collections::BTreeMap::new(),
818 };
819
820 let tags: Vec<String> = Disassembler::new(&mig)
821 .disassemble(&tree)
822 .iter()
823 .map(|s| s.tag.clone())
824 .collect();
825 assert_eq!(tags, ["SEQ", "QTY"], "SG9 under SEQ+Z98 must survive");
826 }
827
828 #[test]
829 fn test_disassemble_empty_tree() {
830 let mig = MigSchema {
831 message_type: "UTILMD".to_string(),
832 variant: None,
833 version: "S2.1".to_string(),
834 publication_date: "".to_string(),
835 author: "".to_string(),
836 format_version: "FV2504".to_string(),
837 source_file: "test".to_string(),
838 segments: vec![make_mig_segment("UNH")],
839 segment_groups: vec![],
840 };
841
842 let tree = AssembledTree {
843 segments: vec![],
844 groups: vec![],
845 post_group_start: 0,
846 inter_group_segments: std::collections::BTreeMap::new(),
847 };
848
849 let disassembler = Disassembler::new(&mig);
850 let segments = disassembler.disassemble(&tree);
851 assert!(segments.is_empty());
852 }
853
854 fn seg(tag: &str, first: &str) -> AssembledSegment {
855 AssembledSegment {
856 tag: tag.to_string(),
857 elements: vec![vec![first.to_string()]],
858 mig_number: None,
859 segment_number: None,
860 }
861 }
862
863 fn instance(
864 segments: Vec<AssembledSegment>,
865 child_groups: Vec<AssembledGroup>,
866 ) -> AssembledGroupInstance {
867 AssembledGroupInstance {
868 segments,
869 child_groups,
870 entry_mig_number: None,
871 variant_mig_numbers: vec![],
872 skipped_segments: vec![],
873 skipped_positions: vec![],
874 }
875 }
876
877 fn sg10_mig_and_tree(sg10: AssembledGroupInstance) -> (MigSchema, AssembledTree) {
879 let sg10_mig = make_mig_group("SG10", vec!["CCI", "CAV"], vec![]);
880 let sg8_mig = crate::test_support::make_mig_group_with_variant(
881 "SG8",
882 vec!["SEQ"],
883 vec![sg10_mig],
884 "Z03",
885 );
886 let sg4_mig = make_mig_group("SG4", vec!["IDE"], vec![sg8_mig]);
887 let mig = MigSchema {
888 message_type: "UTILMD".to_string(),
889 variant: None,
890 version: "S2.1".to_string(),
891 publication_date: "".to_string(),
892 author: "".to_string(),
893 format_version: "FV2604".to_string(),
894 source_file: "test".to_string(),
895 segments: vec![make_mig_segment("UNH")],
896 segment_groups: vec![sg4_mig],
897 };
898 let sg8 = instance(
899 vec![seg("SEQ", "Z03")],
900 vec![AssembledGroup {
901 group_id: "SG10".to_string(),
902 repetitions: vec![sg10],
903 }],
904 );
905 let sg4 = instance(
906 vec![seg("IDE", "24")],
907 vec![AssembledGroup {
908 group_id: "SG8".to_string(),
909 repetitions: vec![sg8],
910 }],
911 );
912 let tree = AssembledTree {
913 segments: vec![seg("UNH", "1")],
914 groups: vec![AssembledGroup {
915 group_id: "SG4".to_string(),
916 repetitions: vec![sg4],
917 }],
918 post_group_start: 1,
919 inter_group_segments: std::collections::BTreeMap::new(),
920 };
921 (mig, tree)
922 }
923
924 #[test]
925 fn checked_disassembly_refuses_group_without_entry_segment() {
926 let (mig, tree) = sg10_mig_and_tree(instance(vec![seg("CAV", "Z30")], vec![]));
927 let disassembler = Disassembler::new(&mig);
928
929 let tags: Vec<_> = disassembler
931 .disassemble(&tree)
932 .into_iter()
933 .map(|s| s.tag)
934 .collect();
935 assert_eq!(tags, ["UNH", "IDE", "SEQ", "CAV"]);
936
937 let expected = MissingEntrySegment {
938 group_path: "SG4.SG8.SG10".to_string(),
939 source_path: "sg4.sg8_z03.sg10".to_string(),
940 entry_segment: "CCI".to_string(),
941 present_segments: vec!["CAV".to_string()],
942 };
943 assert_eq!(
944 disassembler.missing_entry_segments(&tree),
945 vec![expected.clone()]
946 );
947 match disassembler.disassemble_checked(&tree) {
948 Err(AssemblyError::MissingGroupEntrySegment {
949 group_path,
950 source_path,
951 entry_segment,
952 present_segments,
953 }) => {
954 assert_eq!(group_path, expected.group_path);
955 assert_eq!(source_path, expected.source_path);
956 assert_eq!(entry_segment, expected.entry_segment);
957 assert_eq!(present_segments, expected.present_segments);
958 }
959 other => panic!("expected MissingGroupEntrySegment, got {other:?}"),
960 }
961 }
962
963 #[test]
964 fn checked_disassembly_accepts_complete_groups() {
965 let (mig, tree) =
966 sg10_mig_and_tree(instance(vec![seg("CCI", ""), seg("CAV", "Z30")], vec![]));
967 let disassembler = Disassembler::new(&mig);
968 assert!(disassembler.missing_entry_segments(&tree).is_empty());
969 let tags: Vec<_> = disassembler
970 .disassemble_checked(&tree)
971 .expect("complete tree")
972 .into_iter()
973 .map(|s| s.tag)
974 .collect();
975 assert_eq!(tags, ["UNH", "IDE", "SEQ", "CCI", "CAV"]);
976 }
977
978 #[test]
979 fn empty_group_instance_is_not_a_missing_entry_segment() {
980 let (mig, tree) = sg10_mig_and_tree(instance(vec![], vec![]));
981 assert!(Disassembler::new(&mig)
982 .missing_entry_segments(&tree)
983 .is_empty());
984 }
985}