1use std::collections::{BTreeMap, HashMap, HashSet};
7use std::path::Path;
8
9use mig_assembly::assembler::{
10 AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree,
11};
12use mig_types::schema::mig::MigSchema;
13use mig_types::segment::OwnedSegment;
14
15use crate::definition::{FieldMapping, MappingDefinition};
16use crate::error::MappingError;
17use crate::segment_structure::SegmentStructure;
18
19pub struct MappingEngine {
22 definitions: Vec<MappingDefinition>,
23 segment_structure: Option<SegmentStructure>,
24 code_lookup: Option<crate::code_lookup::CodeLookup>,
25 transaction_group: Option<String>,
36 current_pid: Option<String>,
40 code_lists: std::sync::Arc<crate::code_lists::CodeLists>,
45}
46
47impl MappingEngine {
48 pub fn new_empty() -> Self {
50 Self {
51 definitions: Vec::new(),
52 segment_structure: None,
53 code_lookup: None,
54 transaction_group: None,
55 current_pid: None,
56 code_lists: std::sync::Arc::new(crate::code_lists::CodeLists::default()),
57 }
58 }
59
60 pub fn load(dir: &Path) -> Result<Self, MappingError> {
62 let mut definitions = Vec::new();
63
64 let mut entries: Vec<_> = std::fs::read_dir(dir)?.filter_map(|e| e.ok()).collect();
65 entries.sort_by_key(|e| e.file_name());
66
67 for entry in entries {
68 let path = entry.path();
69 if path.extension().map(|e| e == "toml").unwrap_or(false) {
70 let content = std::fs::read_to_string(&path)?;
71 let def = MappingDefinition::from_toml_str(&content).map_err(|message| {
72 MappingError::TomlParse {
73 file: path.display().to_string(),
74 message,
75 }
76 })?;
77 definitions.push(def);
78 }
79 }
80
81 definitions.sort_by_key(|d| d.meta.order.unwrap_or(u32::MAX));
87
88 Ok(Self {
89 definitions,
90 segment_structure: None,
91 code_lookup: None,
92 transaction_group: None,
93 current_pid: None,
94 code_lists: crate::code_lists::CodeLists::discover(dir),
95 })
96 }
97
98 pub fn load_split(
104 message_dir: &Path,
105 transaction_dir: &Path,
106 ) -> Result<(Self, Self), MappingError> {
107 let msg_engine = Self::load(message_dir)?;
108 let tx_engine = Self::load(transaction_dir)?;
109 Ok((msg_engine, tx_engine))
110 }
111
112 pub fn load_merged(dirs: &[&Path]) -> Result<Self, MappingError> {
117 let mut definitions = Vec::new();
118 for dir in dirs {
119 let engine = Self::load(dir)?;
120 definitions.extend(engine.definitions);
121 }
122 Ok(Self {
123 definitions,
124 segment_structure: None,
125 code_lookup: None,
126 transaction_group: None,
127 current_pid: None,
128 code_lists: crate::code_lists::CodeLists::discover(
129 dirs.first().copied().unwrap_or(Path::new("")),
130 ),
131 })
132 }
133
134 pub fn load_with_common(
143 common_dir: &Path,
144 pid_dir: &Path,
145 schema_index: &crate::pid_schema_index::PidSchemaIndex,
146 ) -> Result<Self, MappingError> {
147 let mut common_defs = Self::load(common_dir)?.definitions;
148
149 common_defs.retain(|d| {
151 d.meta
152 .source_path
153 .as_deref()
154 .map(|sp| schema_index.has_group(sp))
155 .unwrap_or(true)
156 });
157
158 let pid_defs = Self::load(pid_dir)?.definitions;
159
160 let normalize_sg = |sg: &str| -> String {
165 sg.split('.')
166 .map(|part| part.split(':').next().unwrap_or(part))
167 .collect::<Vec<_>>()
168 .join(".")
169 };
170 let pid_keys: HashSet<(String, Option<String>)> = pid_defs
171 .iter()
172 .flat_map(|d| {
173 let sg = normalize_sg(&d.meta.source_group);
174 let disc = d.meta.discriminator.clone();
175 let mut keys = vec![(sg.clone(), disc.clone())];
176 if let Some(ref disc_str) = disc {
178 if let Some(base) = disc_str.rsplit_once('#') {
179 if base.1.chars().all(|c| c.is_ascii_digit()) {
180 keys.push((sg, Some(base.0.to_string())));
181 }
182 }
183 }
184 keys
185 })
186 .collect();
187
188 common_defs.retain(|d| {
190 let key = (
191 normalize_sg(&d.meta.source_group),
192 d.meta.discriminator.clone(),
193 );
194 !pid_keys.contains(&key)
195 });
196
197 let mut definitions = common_defs;
199 definitions.extend(pid_defs);
200
201 Ok(Self {
202 definitions,
203 segment_structure: None,
204 code_lookup: None,
205 transaction_group: None,
206 current_pid: None,
207 code_lists: crate::code_lists::CodeLists::discover(pid_dir),
208 })
209 }
210
211 pub fn load_common_only(
215 common_dir: &Path,
216 schema_index: &crate::pid_schema_index::PidSchemaIndex,
217 ) -> Result<Self, MappingError> {
218 let mut common_defs = Self::load(common_dir)?.definitions;
219
220 common_defs.retain(|d| {
222 d.meta
223 .source_path
224 .as_deref()
225 .map(|sp| schema_index.has_group(sp))
226 .unwrap_or(true)
227 });
228
229 Ok(Self {
230 definitions: common_defs,
231 segment_structure: None,
232 code_lookup: None,
233 transaction_group: None,
234 current_pid: None,
235 code_lists: crate::code_lists::CodeLists::discover(common_dir),
236 })
237 }
238
239 pub fn load_split_with_common(
244 message_dir: &Path,
245 common_dir: &Path,
246 transaction_dir: &Path,
247 schema_index: &crate::pid_schema_index::PidSchemaIndex,
248 ) -> Result<(Self, Self), MappingError> {
249 let msg_engine = Self::load(message_dir)?;
250 let tx_engine = Self::load_with_common(common_dir, transaction_dir, schema_index)?;
251 Ok((msg_engine, tx_engine))
252 }
253
254 fn names_a_code_list(definitions: &[MappingDefinition]) -> bool {
257 definitions.iter().any(|d| {
258 d.fields.values().any(|f| {
259 matches!(f, FieldMapping::Structured(s)
260 if s.code_list.is_some() || s.also_code_list.is_some())
261 })
262 })
263 }
264
265 fn table<'a>(
272 &'a self,
273 inline: Option<&'a BTreeMap<String, String>>,
274 named: Option<&str>,
275 ) -> Option<&'a BTreeMap<String, String>> {
276 inline.or_else(|| named.and_then(|n| self.code_lists.get(n)))
277 }
278
279 pub fn with_code_lists(
281 mut self,
282 code_lists: std::sync::Arc<crate::code_lists::CodeLists>,
283 ) -> Self {
284 self.code_lists = code_lists;
285 self
286 }
287
288 pub fn code_lists(&self) -> &std::sync::Arc<crate::code_lists::CodeLists> {
290 &self.code_lists
291 }
292
293 pub fn from_definitions_with_code_lists(
296 code_lists: std::sync::Arc<crate::code_lists::CodeLists>,
297 definitions: Vec<MappingDefinition>,
298 ) -> Self {
299 debug_assert!(
304 !(code_lists.is_empty() && Self::names_a_code_list(&definitions)),
305 "definitions name a shared code list but the supplied tables are \
306 empty — whatever produced them (a bundle, a cache) is not carrying \
307 them, and every code they translate will reach the output raw"
308 );
309 Self {
313 definitions,
314 segment_structure: None,
315 code_lookup: None,
316 transaction_group: None,
317 current_pid: None,
318 code_lists,
319 }
320 }
321
322 pub fn from_definitions(definitions: Vec<MappingDefinition>) -> Self {
323 debug_assert!(
330 !Self::names_a_code_list(&definitions),
331 "definitions name a shared code list but none were supplied — build \
332 this engine with `from_definitions_with_code_lists`, or the codes \
333 they translate will reach the output untranslated"
334 );
335 Self {
336 definitions,
337 segment_structure: None,
338 code_lookup: None,
339 transaction_group: None,
340 current_pid: None,
341 code_lists: std::sync::Arc::new(crate::code_lists::CodeLists::default()),
342 }
343 }
344
345 pub fn save_cached(&self, path: &Path) -> Result<(), MappingError> {
351 let encoded =
352 serde_json::to_vec(&self.definitions).map_err(|e| MappingError::CacheWrite {
353 path: path.display().to_string(),
354 message: e.to_string(),
355 })?;
356 if let Some(parent) = path.parent() {
357 std::fs::create_dir_all(parent)?;
358 }
359 std::fs::write(path, encoded)?;
360 Ok(())
361 }
362
363 pub fn load_cached_or_toml(cache_path: &Path, toml_dir: &Path) -> Result<Self, MappingError> {
368 if cache_path.exists() {
369 Self::load_cached(cache_path)
370 } else {
371 Self::load(toml_dir)
372 }
373 }
374
375 pub fn load_cached(path: &Path) -> Result<Self, MappingError> {
380 let bytes = std::fs::read(path)?;
381 let definitions: Vec<MappingDefinition> =
382 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
383 path: path.display().to_string(),
384 message: e.to_string(),
385 })?;
386 Ok(Self {
387 definitions,
388 segment_structure: None,
389 code_lookup: None,
390 transaction_group: None,
391 current_pid: None,
392 code_lists: crate::code_lists::CodeLists::discover(path),
393 })
394 }
395
396 pub fn with_segment_structure(mut self, ss: SegmentStructure) -> Self {
401 self.segment_structure = Some(ss);
402 self
403 }
404
405 pub fn with_code_lookup(mut self, cl: crate::code_lookup::CodeLookup) -> Self {
410 self.code_lookup = Some(cl);
411 self
412 }
413
414 pub fn with_pid(mut self, pid: impl Into<String>) -> Self {
422 self.current_pid = Some(pid.into());
423 self
424 }
425
426 pub fn with_path_resolver(mut self, resolver: crate::path_resolver::PathResolver) -> Self {
432 for def in &mut self.definitions {
433 def.normalize_paths(&resolver);
434 }
435 self
436 }
437
438 pub fn with_transaction_group(mut self, tx: impl Into<String>) -> Self {
445 self.transaction_group = Some(tx.into());
446 self
447 }
448
449 pub fn extend_definitions(mut self, defs: impl IntoIterator<Item = MappingDefinition>) -> Self {
463 fn key(d: &MappingDefinition) -> (String, String, String, String, String) {
464 (
465 d.meta.entity.clone(),
466 d.meta.source_group.clone(),
467 d.meta.source_path.clone().unwrap_or_default(),
468 d.meta.discriminator.clone().unwrap_or_default(),
469 d.meta.parent_field.clone().unwrap_or_default(),
470 )
471 }
472 let mut seen: std::collections::HashSet<_> = self.definitions.iter().map(key).collect();
473 for def in defs {
474 if seen.insert(key(&def)) {
475 self.definitions.push(def);
476 }
477 }
478 self
479 }
480
481 pub fn definitions(&self) -> &[MappingDefinition] {
483 &self.definitions
484 }
485
486 pub fn definition_for_entity(&self, entity: &str) -> Option<&MappingDefinition> {
491 self.definitions
492 .iter()
493 .find(|d| d.meta.entity == entity && d.meta.parent_field.is_none())
494 }
495
496 pub fn extract_field(
505 &self,
506 tree: &AssembledTree,
507 group_path: &str,
508 path: &str,
509 repetition: usize,
510 ) -> Option<String> {
511 let instance = Self::resolve_group_instance(tree, group_path, repetition)?;
512 Self::extract_from_instance(instance, path)
513 }
514
515 pub fn resolve_group_instance<'a>(
524 tree: &'a AssembledTree,
525 group_path: &str,
526 repetition: usize,
527 ) -> Option<&'a AssembledGroupInstance> {
528 let parts: Vec<&str> = group_path.split('.').collect();
529
530 let (first_id, first_rep) = parse_group_spec(parts[0]);
531 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
532
533 if parts.len() == 1 {
534 let rep = first_rep.unwrap_or(repetition);
536 return first_group.repetitions.get(rep);
537 }
538
539 let mut current_instance = first_group.repetitions.get(first_rep.unwrap_or(0))?;
542
543 for (i, part) in parts[1..].iter().enumerate() {
544 let (group_id, explicit_rep) = parse_group_spec(part);
545 let child_group = current_instance
546 .child_groups
547 .iter()
548 .find(|g| g.group_id == group_id)?;
549
550 if i == parts.len() - 2 {
551 let rep = explicit_rep.unwrap_or(repetition);
553 return child_group.repetitions.get(rep);
554 }
555 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
557 }
558
559 None
560 }
561
562 pub fn resolve_by_source_path<'a>(
570 tree: &'a AssembledTree,
571 source_path: &str,
572 ) -> Option<&'a AssembledGroupInstance> {
573 let parts: Vec<&str> = source_path.split('.').collect();
574 if parts.is_empty() {
575 return None;
576 }
577
578 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
579 let first_group = tree
580 .groups
581 .iter()
582 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))?;
583
584 let mut current_instance = if let Some(q) = first_qualifier {
585 find_rep_by_entry_qualifier(&first_group.repetitions, q)?
586 } else {
587 first_group.repetitions.first()?
588 };
589
590 if parts.len() == 1 {
591 return Some(current_instance);
592 }
593
594 for part in &parts[1..] {
595 let (group_id, qualifier) = parse_source_path_part(part);
596 let child_group = current_instance
597 .child_groups
598 .iter()
599 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))?;
600
601 current_instance = if let Some(q) = qualifier {
602 find_rep_by_entry_qualifier(&child_group.repetitions, q)?
603 } else {
604 child_group.repetitions.first()?
605 };
606 }
607
608 Some(current_instance)
609 }
610
611 pub fn resolve_all_by_source_path<'a>(
619 tree: &'a AssembledTree,
620 source_path: &str,
621 ) -> Vec<&'a AssembledGroupInstance> {
622 let parts: Vec<&str> = source_path.split('.').collect();
623 if parts.is_empty() {
624 return vec![];
625 }
626
627 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
629 let first_group = match tree
630 .groups
631 .iter()
632 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
633 {
634 Some(g) => g,
635 None => return vec![],
636 };
637
638 let mut current_instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
639 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
640 } else {
641 first_group.repetitions.iter().collect()
642 };
643
644 for part in &parts[1..] {
647 let (group_id, qualifier) = parse_source_path_part(part);
648 let mut next_instances = Vec::new();
649
650 for instance in ¤t_instances {
651 if let Some(child_group) = instance
652 .child_groups
653 .iter()
654 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
655 {
656 if let Some(q) = qualifier {
657 next_instances.extend(find_all_reps_by_entry_qualifier(
658 &child_group.repetitions,
659 q,
660 ));
661 } else {
662 next_instances.extend(child_group.repetitions.iter());
663 }
664 }
665 }
666
667 current_instances = next_instances;
668 }
669
670 current_instances
671 }
672
673 fn compute_child_indices(
686 tree: &AssembledTree,
687 source_path: &str,
688 indexed: &[(usize, &AssembledGroupInstance)],
689 ) -> Vec<usize> {
690 let parts: Vec<&str> = source_path.split('.').collect();
691 if parts.len() < 2 {
692 return vec![];
693 }
694 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
696 let first_group = match tree
697 .groups
698 .iter()
699 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
700 {
701 Some(g) => g,
702 None => return vec![],
703 };
704 let parent_reps: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
705 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
706 } else {
707 first_group.repetitions.iter().collect()
708 };
709 let (child_id, _child_qualifier) = parse_source_path_part(parts[parts.len() - 1]);
711 let mut result = Vec::new();
712 for (_, inst) in indexed {
713 let mut found = false;
715 for parent in &parent_reps {
716 if let Some(child_group) = parent
717 .child_groups
718 .iter()
719 .find(|g| g.group_id.eq_ignore_ascii_case(child_id))
720 {
721 if let Some(pos) = child_group
722 .repetitions
723 .iter()
724 .position(|r| std::ptr::eq(r, *inst))
725 {
726 result.push(pos);
727 found = true;
728 break;
729 }
730 }
731 }
732 if !found {
733 result.push(usize::MAX); }
735 }
736 result
737 }
738
739 pub fn resolve_all_with_parent_indices<'a>(
741 tree: &'a AssembledTree,
742 source_path: &str,
743 ) -> Vec<(usize, &'a AssembledGroupInstance)> {
744 let parts: Vec<&str> = source_path.split('.').collect();
745 if parts.is_empty() {
746 return vec![];
747 }
748
749 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
751 let first_group = match tree
752 .groups
753 .iter()
754 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
755 {
756 Some(g) => g,
757 None => return vec![],
758 };
759
760 if parts.len() == 1 {
762 let instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
763 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
764 } else {
765 first_group.repetitions.iter().collect()
766 };
767 return instances.into_iter().map(|i| (0, i)).collect();
768 }
769
770 let first_reps: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = first_qualifier {
775 let matching = find_all_reps_by_entry_qualifier(&first_group.repetitions, q);
776 let mut result = Vec::new();
777 for m in matching {
778 let idx = first_group
779 .repetitions
780 .iter()
781 .position(|r| std::ptr::eq(r, m))
782 .unwrap_or(0);
783 result.push((idx, m));
784 }
785 result
786 } else {
787 first_group.repetitions.iter().enumerate().collect()
788 };
789
790 let mut current: Vec<(usize, &AssembledGroupInstance)> = first_reps;
791 let remaining = &parts[1..];
792
793 for (level, part) in remaining.iter().enumerate() {
794 let is_leaf = level == remaining.len() - 1;
795 let (group_id, qualifier) = parse_source_path_part(part);
796 let mut next: Vec<(usize, &AssembledGroupInstance)> = Vec::new();
797
798 for (prev_parent_idx, instance) in ¤t {
799 if let Some(child_group) = instance
800 .child_groups
801 .iter()
802 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
803 {
804 let matching: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = qualifier
805 {
806 let filtered =
807 find_all_reps_by_entry_qualifier(&child_group.repetitions, q);
808 filtered
809 .into_iter()
810 .map(|m| {
811 let idx = child_group
812 .repetitions
813 .iter()
814 .position(|r| std::ptr::eq(r, m))
815 .unwrap_or(0);
816 (idx, m)
817 })
818 .collect()
819 } else {
820 child_group.repetitions.iter().enumerate().collect()
821 };
822
823 for (rep_idx, child_rep) in matching {
824 if is_leaf {
825 next.push((*prev_parent_idx, child_rep));
827 } else {
828 next.push((rep_idx, child_rep));
830 }
831 }
832 }
833 }
834
835 current = next;
836 }
837
838 current
839 }
840
841 pub fn extract_from_instance(instance: &AssembledGroupInstance, path: &str) -> Option<String> {
847 let parts: Vec<&str> = path.split('.').collect();
848 if parts.is_empty() {
849 return None;
850 }
851
852 let (segment_tag, qualifier, occurrence) = parse_tag_qualifier(parts[0]);
855
856 let segment = if let Some(q) = qualifier {
857 instance
858 .segments
859 .iter()
860 .filter(|s| {
861 s.tag.eq_ignore_ascii_case(&segment_tag)
862 && s.elements
863 .first()
864 .and_then(|e| e.first())
865 .map(|v| v.as_str())
866 == Some(q)
867 })
868 .nth(occurrence)?
869 } else {
870 instance
871 .segments
872 .iter()
873 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
874 .nth(occurrence)?
875 };
876
877 Self::resolve_field_path(segment, &parts[1..])
878 }
879
880 pub fn extract_all_from_instance(instance: &AssembledGroupInstance, path: &str) -> Vec<String> {
886 let parts: Vec<&str> = path.split('.').collect();
887 if parts.is_empty() {
888 return vec![];
889 }
890
891 let (segment_tag, qualifier, _) = parse_tag_qualifier(parts[0]);
892
893 let matching_segments: Vec<&AssembledSegment> = if let Some(q) = qualifier {
894 instance
895 .segments
896 .iter()
897 .filter(|s| {
898 s.tag.eq_ignore_ascii_case(&segment_tag)
899 && s.elements
900 .first()
901 .and_then(|e| e.first())
902 .map(|v| v.as_str())
903 == Some(q)
904 })
905 .collect()
906 } else {
907 instance
908 .segments
909 .iter()
910 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
911 .collect()
912 };
913
914 matching_segments
915 .into_iter()
916 .filter_map(|seg| Self::resolve_field_path(seg, &parts[1..]))
917 .collect()
918 }
919
920 pub fn map_forward(
926 &self,
927 tree: &AssembledTree,
928 def: &MappingDefinition,
929 repetition: usize,
930 ) -> serde_json::Value {
931 self.map_forward_inner(tree, def, repetition, true)
932 }
933
934 fn map_forward_inner(
936 &self,
937 tree: &AssembledTree,
938 def: &MappingDefinition,
939 repetition: usize,
940 enrich_codes: bool,
941 ) -> serde_json::Value {
942 let mut result = serde_json::Map::new();
943
944 if def.meta.source_group.is_empty() {
949 let mut all_root_segs = tree.segments.clone();
950 for segs in tree.inter_group_segments.values() {
951 all_root_segs.extend(segs.iter().cloned());
952 }
953 let root_instance = AssembledGroupInstance {
954 segments: all_root_segs,
955 child_groups: vec![],
956 entry_mig_number: None,
957 variant_mig_numbers: vec![],
958 skipped_segments: Vec::new(),
959 skipped_positions: Vec::new(),
960 };
961 self.extract_fields_from_instance(&root_instance, def, &mut result, enrich_codes);
962 return serde_json::Value::Object(result);
963 }
964
965 let instance = if let Some(ref sp) = def.meta.source_path {
971 if has_source_path_qualifiers(sp) && !def.meta.source_group.contains(':') {
972 Self::resolve_by_source_path(tree, sp).or_else(|| {
973 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
974 })
975 } else {
976 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
977 }
978 } else {
979 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
980 };
981
982 if let Some(instance) = instance {
983 if let Some(ref tag) = def.meta.repeat_on_tag {
985 let matching: Vec<_> = instance
986 .segments
987 .iter()
988 .filter(|s| s.tag.eq_ignore_ascii_case(tag))
989 .collect();
990
991 if matching.len() > 1 {
992 let mut arr = Vec::new();
993 for seg in &matching {
994 let sub_instance = AssembledGroupInstance {
995 segments: vec![(*seg).clone()],
996 child_groups: vec![],
997 entry_mig_number: None,
998 variant_mig_numbers: vec![],
999 skipped_segments: Vec::new(),
1000 skipped_positions: Vec::new(),
1001 };
1002 let mut elem_result = serde_json::Map::new();
1003 self.extract_fields_from_instance(
1004 &sub_instance,
1005 def,
1006 &mut elem_result,
1007 enrich_codes,
1008 );
1009 if !elem_result.is_empty() {
1010 arr.push(serde_json::Value::Object(elem_result));
1011 }
1012 }
1013 if !arr.is_empty() {
1014 return serde_json::Value::Array(arr);
1015 }
1016 }
1017 }
1018
1019 self.extract_fields_from_instance(instance, def, &mut result, enrich_codes);
1020 }
1021
1022 serde_json::Value::Object(result)
1023 }
1024
1025 fn extract_fields_from_instance(
1030 &self,
1031 instance: &AssembledGroupInstance,
1032 def: &MappingDefinition,
1033 result: &mut serde_json::Map<String, serde_json::Value>,
1034 enrich_codes: bool,
1035 ) {
1036 for (path, field_mapping) in &def.fields {
1037 let (target, enum_map) = match field_mapping {
1038 FieldMapping::Simple(t) => (t.as_str(), None),
1039 FieldMapping::Structured(s) => (
1040 s.target.as_str(),
1041 self.table(s.enum_map.as_ref(), s.code_list.as_deref()),
1042 ),
1043 FieldMapping::Nested(_) => continue,
1044 };
1045 if target.is_empty() {
1046 continue;
1047 }
1048 if let Some(val) = Self::extract_from_instance(instance, path) {
1049 if let FieldMapping::Structured(s) = field_mapping {
1054 if let (Some(also), Some(also_map)) = (
1055 s.also_target.as_deref(),
1056 self.table(s.also_enum_map.as_ref(), s.also_code_list.as_deref()),
1057 ) {
1058 if let Some(also_val) = also_map.get(&val) {
1059 set_nested_value(result, also, also_val.clone());
1060 }
1061 }
1062 }
1063
1064 let mapped_val = if let Some(map) = enum_map {
1065 map.get(&val).cloned().unwrap_or_else(|| val.clone())
1066 } else {
1067 val.clone()
1068 };
1069
1070 if enrich_codes {
1072 if let (Some(ref code_lookup), Some(ref source_path)) =
1073 (&self.code_lookup, &def.meta.source_path)
1074 {
1075 let parts: Vec<&str> = path.split('.').collect();
1076 let (seg_tag, path_qualifier, _occ) = parse_tag_qualifier(parts[0]);
1077 let (element_idx, component_idx) =
1078 Self::parse_element_component(&parts[1..]);
1079 let disc_qualifier = Self::discriminator_qualifier_for_tag(def, &seg_tag);
1080 let q = disc_qualifier.as_deref();
1081
1082 if let Some(codes) = code_lookup.enrichment_codes(
1083 source_path,
1084 &seg_tag,
1085 path_qualifier,
1086 q,
1087 element_idx,
1088 component_idx,
1089 ) {
1090 if let Some(ref pid) = self.current_pid {
1095 if codes.len() == 1 && codes.contains_key(pid.as_str()) {
1096 set_nested_value(result, target, mapped_val);
1097 continue;
1098 }
1099 }
1100
1101 let enrichment = codes.get(&val);
1105 let meaning = enrichment
1106 .map(|e| serde_json::Value::String(e.meaning.clone()))
1107 .unwrap_or(serde_json::Value::Null);
1108
1109 let mut obj = serde_json::Map::new();
1110 obj.insert("code".into(), serde_json::json!(mapped_val));
1111 obj.insert("meaning".into(), meaning);
1112 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
1113 obj.insert("enum".into(), serde_json::json!(enum_key));
1114 }
1115 let enriched = serde_json::Value::Object(obj);
1116 set_nested_value_json(result, target, enriched);
1117 continue;
1118 }
1119 }
1120 }
1121
1122 set_nested_value(result, target, mapped_val);
1123 }
1124 }
1125
1126 if !instance.child_groups.is_empty() {
1129 self.extract_nested_children(instance, def, result, enrich_codes);
1130 }
1131 }
1132
1133 fn extract_nested_children(
1138 &self,
1139 instance: &AssembledGroupInstance,
1140 def: &MappingDefinition,
1141 result: &mut serde_json::Map<String, serde_json::Value>,
1142 enrich_codes: bool,
1143 ) {
1144 for child in self
1145 .definitions
1146 .iter()
1147 .filter(|c| is_nested_child_of(c, def))
1148 {
1149 if nested_parent_qualifier(child).is_some_and(|q| !entry_qualifier_matches(instance, q))
1150 {
1151 continue;
1152 }
1153 let (leaf_id, leaf_qualifier) = nested_child_leaf(child);
1154 let Some(group) = instance
1155 .child_groups
1156 .iter()
1157 .find(|g| g.group_id.eq_ignore_ascii_case(&leaf_id))
1158 else {
1159 continue;
1160 };
1161 let reps: Vec<&AssembledGroupInstance> = match leaf_qualifier {
1162 Some(q) => find_all_reps_by_entry_qualifier(&group.repetitions, q),
1163 None => group.repetitions.iter().collect(),
1164 };
1165
1166 let mut items: Vec<serde_json::Value> = Vec::new();
1167 let mut push_item = |sub: &AssembledGroupInstance| {
1168 let mut obj = serde_json::Map::new();
1169 self.extract_fields_from_instance(sub, child, &mut obj, enrich_codes);
1170 if !obj.is_empty() {
1171 items.push(serde_json::Value::Object(obj));
1172 }
1173 };
1174 for rep in reps {
1175 let repeat_tag = child
1176 .meta
1177 .repeat_on_tag
1178 .as_deref()
1179 .filter(|tag| rep.segments.iter().any(|s| s.tag.eq_ignore_ascii_case(tag)));
1180 let Some(tag) = repeat_tag else {
1181 push_item(rep);
1182 continue;
1183 };
1184 let shared: Vec<AssembledSegment> = rep
1187 .segments
1188 .iter()
1189 .filter(|s| !s.tag.eq_ignore_ascii_case(tag))
1190 .cloned()
1191 .collect();
1192 for seg in rep
1193 .segments
1194 .iter()
1195 .filter(|s| s.tag.eq_ignore_ascii_case(tag))
1196 {
1197 let mut segments = shared.clone();
1198 segments.push(seg.clone());
1199 push_item(&AssembledGroupInstance {
1200 segments,
1201 child_groups: vec![],
1202 entry_mig_number: None,
1203 variant_mig_numbers: vec![],
1204 skipped_segments: Vec::new(),
1205 skipped_positions: Vec::new(),
1206 });
1207 }
1208 }
1209 if items.is_empty() {
1210 continue;
1211 }
1212 let field = child.meta.parent_field.as_deref().unwrap_or_default();
1213 match result.get_mut(field) {
1214 Some(serde_json::Value::Array(existing)) => existing.extend(items),
1215 _ => {
1216 result.insert(field.to_string(), serde_json::Value::Array(items));
1217 }
1218 }
1219 }
1220 }
1221
1222 fn reverse_nested_children(
1226 &self,
1227 bo4e_value: &serde_json::Value,
1228 def: &MappingDefinition,
1229 instance: &mut AssembledGroupInstance,
1230 ) {
1231 let mut handled_fields: Vec<&str> = Vec::new();
1232 for child in self
1233 .definitions
1234 .iter()
1235 .filter(|c| is_nested_child_of(c, def))
1236 {
1237 let field = child.meta.parent_field.as_deref().unwrap_or_default();
1238 if handled_fields.contains(&field) {
1239 continue;
1240 }
1241 if nested_parent_qualifier(child)
1242 .is_some_and(|q| !rebuilt_entry_qualifier_matches(instance, def, q))
1243 {
1244 continue;
1245 }
1246 let elements: Vec<&serde_json::Value> = match bo4e_value.get(field) {
1247 Some(serde_json::Value::Array(arr)) => arr.iter().collect(),
1248 Some(serde_json::Value::Null) | None => continue,
1249 Some(other) => vec![other],
1250 };
1251
1252 let mut reps: Vec<AssembledGroupInstance> = Vec::new();
1253 if let Some(tag) = child.meta.repeat_on_tag.as_deref() {
1254 let mut merged: Option<AssembledGroupInstance> = None;
1257 for element in elements {
1258 let sub = self.map_reverse_single(element, child);
1259 if sub.segments.is_empty() {
1260 continue;
1261 }
1262 match merged.as_mut() {
1263 None => merged = Some(sub),
1264 Some(m) => m.segments.extend(
1265 sub.segments
1266 .into_iter()
1267 .filter(|s| s.tag.eq_ignore_ascii_case(tag)),
1268 ),
1269 }
1270 }
1271 reps.extend(merged);
1272 } else {
1273 for element in elements {
1274 let mut sub = self.map_reverse_single(element, child);
1275 if sub.segments.is_empty() {
1276 continue;
1277 }
1278 self.reverse_nested_children(element, child, &mut sub);
1280 reps.push(sub);
1281 }
1282 }
1283 if reps.is_empty() {
1284 continue;
1285 }
1286 handled_fields.push(field);
1287
1288 let (leaf_id, _) = nested_child_leaf(child);
1289 match instance
1290 .child_groups
1291 .iter_mut()
1292 .find(|g| g.group_id.eq_ignore_ascii_case(&leaf_id))
1293 {
1294 Some(group) => group.repetitions.extend(reps),
1295 None => instance.child_groups.push(AssembledGroup {
1296 group_id: leaf_id,
1297 repetitions: reps,
1298 }),
1299 }
1300 }
1301 }
1302
1303 pub(crate) fn discriminator_qualifier(def: &MappingDefinition) -> Option<String> {
1310 def.meta
1311 .discriminator
1312 .as_deref()
1313 .and_then(|d| d.split_once('=').map(|(_, v)| v.to_string()))
1314 }
1315
1316 pub(crate) fn discriminator_qualifier_for_tag(
1321 def: &MappingDefinition,
1322 segment_tag: &str,
1323 ) -> Option<String> {
1324 let (lhs, value) = def.meta.discriminator.as_deref()?.split_once('=')?;
1325 let disc_tag = lhs.split('.').next().unwrap_or(lhs);
1326 disc_tag
1327 .eq_ignore_ascii_case(segment_tag)
1328 .then(|| value.to_string())
1329 }
1330
1331 pub fn map_forward_from_segments(
1337 &self,
1338 segments: &[OwnedSegment],
1339 def: &MappingDefinition,
1340 ) -> serde_json::Value {
1341 let assembled_segments: Vec<AssembledSegment> = segments
1342 .iter()
1343 .map(|s| AssembledSegment {
1344 tag: s.id.clone(),
1345 elements: s.elements.clone(),
1346 mig_number: None,
1347 segment_number: Some(s.segment_number),
1348 })
1349 .collect();
1350
1351 let instance = AssembledGroupInstance {
1352 segments: assembled_segments,
1353 child_groups: vec![],
1354 entry_mig_number: None,
1355 variant_mig_numbers: vec![],
1356 skipped_segments: Vec::new(),
1357 skipped_positions: Vec::new(),
1358 };
1359
1360 let mut result = serde_json::Map::new();
1361 self.extract_fields_from_instance(&instance, def, &mut result, true);
1362 serde_json::Value::Object(result)
1363 }
1364
1365 pub fn map_reverse(
1378 &self,
1379 bo4e_value: &serde_json::Value,
1380 def: &MappingDefinition,
1381 ) -> AssembledGroupInstance {
1382 if def.meta.repeat_on_tag.is_some() {
1384 if let Some(arr) = bo4e_value.as_array() {
1385 let mut all_segments = Vec::new();
1386 for elem in arr {
1387 let sub = self.map_reverse_single(elem, def);
1388 all_segments.extend(sub.segments);
1389 }
1390 return AssembledGroupInstance {
1391 segments: all_segments,
1392 child_groups: vec![],
1393 entry_mig_number: None,
1394 variant_mig_numbers: vec![],
1395 skipped_segments: Vec::new(),
1396 skipped_positions: Vec::new(),
1397 };
1398 }
1399 }
1400 let mut instance = self.map_reverse_single(bo4e_value, def);
1401 if def.meta.parent_field.is_none() && !instance.segments.is_empty() {
1402 self.reverse_nested_children(bo4e_value, def, &mut instance);
1403 }
1404 instance
1405 }
1406
1407 fn map_reverse_single(
1408 &self,
1409 bo4e_value: &serde_json::Value,
1410 def: &MappingDefinition,
1411 ) -> AssembledGroupInstance {
1412 let mut field_values: Vec<(String, String, usize, usize, String)> =
1415 Vec::with_capacity(def.fields.len());
1416
1417 let mut has_real_data = false;
1424 let mut has_data_fields = false;
1425 let mut seg_has_data_field: HashSet<String> = HashSet::new();
1428 let mut seg_has_real_data: HashSet<String> = HashSet::new();
1429 let mut injected_qualifiers: HashSet<String> = HashSet::new();
1430
1431 for (path, field_mapping) in &def.fields {
1432 let (target, default, enum_map, when_filled, also_target, also_enum_map) =
1433 match field_mapping {
1434 FieldMapping::Simple(t) => (t.as_str(), None, None, None, None, None),
1435 FieldMapping::Structured(s) => (
1436 s.target.as_str(),
1437 s.default.as_ref(),
1438 self.table(s.enum_map.as_ref(), s.code_list.as_deref()),
1439 s.when_filled.as_ref(),
1440 s.also_target.as_deref(),
1441 self.table(s.also_enum_map.as_ref(), s.also_code_list.as_deref()),
1442 ),
1443 FieldMapping::Nested(_) => continue,
1444 };
1445
1446 let parts: Vec<&str> = path.split('.').collect();
1447 if parts.len() < 2 {
1448 continue;
1449 }
1450
1451 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1452 let seg_key = parts[0].to_uppercase();
1455 let sub_path = &parts[1..];
1456
1457 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1459 let ci = if sub_path.len() > 1 {
1460 sub_path[1].parse::<usize>().unwrap_or(0)
1461 } else {
1462 0
1463 };
1464 (ei, ci)
1465 } else {
1466 match sub_path.len() {
1467 1 => (0, 0),
1468 2 => (1, 0),
1469 _ => continue,
1470 }
1471 };
1472
1473 let val = if target.is_empty() {
1475 match (default, when_filled) {
1476 (Some(d), Some(fields)) => {
1478 let any_filled = fields
1479 .iter()
1480 .any(|f| self.populate_field(bo4e_value, f).is_some());
1481 if any_filled {
1482 has_real_data = true;
1485 Some(d.clone())
1486 } else {
1487 None
1488 }
1489 }
1490 (Some(d), None) => Some(d.clone()),
1492 (None, _) => None,
1493 }
1494 } else {
1495 has_data_fields = true;
1496 seg_has_data_field.insert(seg_key.clone());
1497 let bo4e_val = self.populate_field(bo4e_value, target);
1498 if bo4e_val.is_some() {
1499 has_real_data = true;
1500 seg_has_real_data.insert(seg_key.clone());
1501 }
1502 let mapped_val = match (bo4e_val, enum_map) {
1504 (Some(v), Some(map)) => {
1505 let joint = match (also_target, also_enum_map) {
1512 (Some(also), Some(also_map)) => {
1513 self.populate_field(bo4e_value, also).and_then(|also_v| {
1514 map.iter()
1515 .find(|(code, bo4e_v)| {
1516 *bo4e_v == &v && also_map.get(*code) == Some(&also_v)
1517 })
1518 .map(|(code, _)| code.clone())
1519 })
1520 }
1521 _ => None,
1522 };
1523 joint
1524 .or_else(|| {
1525 map.iter()
1527 .find(|(_, bo4e_v)| *bo4e_v == &v)
1528 .map(|(edifact_k, _)| edifact_k.clone())
1529 })
1530 .or(Some(v))
1531 }
1532 (v, _) => v,
1533 };
1534 mapped_val.or_else(|| default.cloned())
1535 };
1536
1537 if let Some(val) = val {
1538 field_values.push((
1539 seg_key.clone(),
1540 seg_tag.clone(),
1541 element_idx,
1542 component_idx,
1543 val,
1544 ));
1545 }
1546
1547 if let Some(q) = qualifier {
1549 if injected_qualifiers.insert(seg_key.clone()) {
1550 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1551 }
1552 }
1553 }
1554
1555 field_values.retain(|(seg_key, _, _, _, _)| {
1563 if !seg_key.contains('[') {
1564 return true; }
1566 !seg_has_data_field.contains(seg_key) || seg_has_real_data.contains(seg_key)
1567 });
1568
1569 if has_data_fields && !has_real_data {
1574 return AssembledGroupInstance {
1575 segments: vec![],
1576 child_groups: vec![],
1577 entry_mig_number: None,
1578 variant_mig_numbers: vec![],
1579 skipped_segments: Vec::new(),
1580 skipped_positions: Vec::new(),
1581 };
1582 }
1583
1584 let mut segments: Vec<AssembledSegment> = Vec::with_capacity(field_values.len());
1587 let mut seen_keys: HashMap<String, usize> = HashMap::new();
1588
1589 for (seg_key, seg_tag, element_idx, component_idx, val) in &field_values {
1590 let seg = if let Some(&pos) = seen_keys.get(seg_key) {
1591 &mut segments[pos]
1592 } else {
1593 let pos = segments.len();
1594 seen_keys.insert(seg_key.clone(), pos);
1595 segments.push(AssembledSegment {
1596 tag: seg_tag.clone(),
1597 elements: vec![],
1598 mig_number: None,
1599 segment_number: None,
1600 });
1601 &mut segments[pos]
1602 };
1603
1604 while seg.elements.len() <= *element_idx {
1605 seg.elements.push(vec![]);
1606 }
1607 while seg.elements[*element_idx].len() <= *component_idx {
1608 seg.elements[*element_idx].push(String::new());
1609 }
1610 seg.elements[*element_idx][*component_idx] = val.clone();
1611 }
1612
1613 for seg in &mut segments {
1616 let last_populated = seg.elements.iter().rposition(|e| !e.is_empty());
1617 if let Some(last_idx) = last_populated {
1618 for i in 0..last_idx {
1619 if seg.elements[i].is_empty() {
1620 seg.elements[i] = vec![String::new()];
1621 }
1622 }
1623 }
1624 }
1625
1626 if let Some(ref ss) = self.segment_structure {
1628 for seg in &mut segments {
1629 if let Some(expected) = ss.element_count(&seg.tag) {
1630 while seg.elements.len() < expected {
1631 seg.elements.push(vec![String::new()]);
1632 }
1633 }
1634 }
1635 }
1636
1637 AssembledGroupInstance {
1638 segments,
1639 child_groups: vec![],
1640 entry_mig_number: None,
1641 variant_mig_numbers: vec![],
1642 skipped_segments: Vec::new(),
1643 skipped_positions: Vec::new(),
1644 }
1645 }
1646
1647 fn resolve_field_path(segment: &AssembledSegment, path: &[&str]) -> Option<String> {
1660 if path.is_empty() {
1661 return None;
1662 }
1663
1664 if let Ok(element_idx) = path[0].parse::<usize>() {
1666 let component_idx = if path.len() > 1 {
1667 path[1].parse::<usize>().unwrap_or(0)
1668 } else {
1669 0
1670 };
1671 return segment
1672 .elements
1673 .get(element_idx)?
1674 .get(component_idx)
1675 .filter(|v| !v.is_empty())
1676 .cloned();
1677 }
1678
1679 None
1685 }
1686
1687 pub(crate) fn parse_element_component(parts: &[&str]) -> (usize, usize) {
1690 if parts.is_empty() {
1691 return (0, 0);
1692 }
1693 let element_idx = parts[0].parse::<usize>().unwrap_or(0);
1694 let component_idx = if parts.len() > 1 {
1695 parts[1].parse::<usize>().unwrap_or(0)
1696 } else {
1697 0
1698 };
1699 (element_idx, component_idx)
1700 }
1701
1702 pub fn populate_field(
1705 &self,
1706 bo4e_value: &serde_json::Value,
1707 target_field: &str,
1708 ) -> Option<String> {
1709 let mut current = bo4e_value;
1710 for part in target_field.split('.') {
1711 current = current.get(part)?;
1712 }
1713 if let Some(code) = current.get("code").and_then(|v| v.as_str()) {
1715 return Some(code.to_string());
1716 }
1717 current.as_str().map(|s| s.to_string())
1718 }
1719
1720 pub fn build_segment_from_bo4e(
1722 &self,
1723 bo4e_value: &serde_json::Value,
1724 segment_tag: &str,
1725 target_field: &str,
1726 ) -> AssembledSegment {
1727 let value = self.populate_field(bo4e_value, target_field);
1728 let elements = if let Some(val) = value {
1729 vec![vec![val]]
1730 } else {
1731 vec![]
1732 };
1733 AssembledSegment {
1734 tag: segment_tag.to_uppercase(),
1735 elements,
1736 mig_number: None,
1737 segment_number: None,
1738 }
1739 }
1740
1741 pub fn resolve_repetition(
1750 tree: &AssembledTree,
1751 group_path: &str,
1752 discriminator: &str,
1753 ) -> Option<usize> {
1754 let (spec, expected) = discriminator.split_once('=')?;
1755 let parts: Vec<&str> = spec.split('.').collect();
1756 if parts.len() != 3 {
1757 return None;
1758 }
1759 let tag = parts[0];
1760 let element_idx: usize = parts[1].parse().ok()?;
1761 let component_idx: usize = parts[2].parse().ok()?;
1762
1763 let path_parts: Vec<&str> = group_path.split('.').collect();
1765
1766 let leaf_group = if path_parts.len() == 1 {
1767 let (group_id, _) = parse_group_spec(path_parts[0]);
1768 tree.groups.iter().find(|g| g.group_id == group_id)?
1769 } else {
1770 let parent_parts = &path_parts[..path_parts.len() - 1];
1772 let mut current_instance = {
1773 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1774 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
1775 first_group.repetitions.get(first_rep.unwrap_or(0))?
1776 };
1777 for part in &parent_parts[1..] {
1778 let (group_id, explicit_rep) = parse_group_spec(part);
1779 let child_group = current_instance
1780 .child_groups
1781 .iter()
1782 .find(|g| g.group_id == group_id)?;
1783 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
1784 }
1785 let (leaf_id, _) = parse_group_spec(path_parts.last()?);
1786 current_instance
1787 .child_groups
1788 .iter()
1789 .find(|g| g.group_id == leaf_id)?
1790 };
1791
1792 let expected_values: Vec<&str> = expected.split('|').collect();
1794 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1795 let matches = instance.segments.iter().any(|s| {
1796 s.tag.eq_ignore_ascii_case(tag)
1797 && s.elements
1798 .get(element_idx)
1799 .and_then(|e| e.get(component_idx))
1800 .map(|v| expected_values.iter().any(|ev| v == ev))
1801 .unwrap_or(false)
1802 });
1803 if matches {
1804 return Some(rep_idx);
1805 }
1806 }
1807
1808 None
1809 }
1810
1811 pub fn resolve_all_repetitions(
1816 tree: &AssembledTree,
1817 group_path: &str,
1818 discriminator: &str,
1819 ) -> Vec<usize> {
1820 let Some((spec, expected)) = discriminator.split_once('=') else {
1821 return Vec::new();
1822 };
1823 let parts: Vec<&str> = spec.split('.').collect();
1824 if parts.len() != 3 {
1825 return Vec::new();
1826 }
1827 let tag = parts[0];
1828 let element_idx: usize = match parts[1].parse() {
1829 Ok(v) => v,
1830 Err(_) => return Vec::new(),
1831 };
1832 let component_idx: usize = match parts[2].parse() {
1833 Ok(v) => v,
1834 Err(_) => return Vec::new(),
1835 };
1836
1837 let path_parts: Vec<&str> = group_path.split('.').collect();
1839
1840 let leaf_group = if path_parts.len() == 1 {
1841 let (group_id, _) = parse_group_spec(path_parts[0]);
1842 match tree.groups.iter().find(|g| g.group_id == group_id) {
1843 Some(g) => g,
1844 None => return Vec::new(),
1845 }
1846 } else {
1847 let parent_parts = &path_parts[..path_parts.len() - 1];
1848 let mut current_instance = {
1849 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1850 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
1851 Some(g) => g,
1852 None => return Vec::new(),
1853 };
1854 match first_group.repetitions.get(first_rep.unwrap_or(0)) {
1855 Some(i) => i,
1856 None => return Vec::new(),
1857 }
1858 };
1859 for part in &parent_parts[1..] {
1860 let (group_id, explicit_rep) = parse_group_spec(part);
1861 let child_group = match current_instance
1862 .child_groups
1863 .iter()
1864 .find(|g| g.group_id == group_id)
1865 {
1866 Some(g) => g,
1867 None => return Vec::new(),
1868 };
1869 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
1870 Some(i) => i,
1871 None => return Vec::new(),
1872 };
1873 }
1874 let (leaf_id, _) = match path_parts.last() {
1875 Some(p) => parse_group_spec(p),
1876 None => return Vec::new(),
1877 };
1878 match current_instance
1879 .child_groups
1880 .iter()
1881 .find(|g| g.group_id == leaf_id)
1882 {
1883 Some(g) => g,
1884 None => return Vec::new(),
1885 }
1886 };
1887
1888 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
1890
1891 let expected_values: Vec<&str> = expected_raw.split('|').collect();
1893 let mut result = Vec::new();
1894 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1895 let matches = instance.segments.iter().any(|s| {
1896 s.tag.eq_ignore_ascii_case(tag)
1897 && s.elements
1898 .get(element_idx)
1899 .and_then(|e| e.get(component_idx))
1900 .map(|v| expected_values.iter().any(|ev| v == ev))
1901 .unwrap_or(false)
1902 });
1903 if matches {
1904 result.push(rep_idx);
1905 }
1906 }
1907
1908 if let Some(occ) = occurrence {
1910 result.into_iter().nth(occ).into_iter().collect()
1911 } else {
1912 result
1913 }
1914 }
1915
1916 pub fn map_all_forward(&self, tree: &AssembledTree) -> serde_json::Value {
1938 self.map_all_forward_inner(tree, true).0
1939 }
1940
1941 pub fn map_all_forward_enriched(
1945 &self,
1946 tree: &AssembledTree,
1947 enrich_codes: bool,
1948 ) -> serde_json::Value {
1949 self.map_all_forward_inner(tree, enrich_codes).0
1950 }
1951
1952 fn map_all_forward_inner(
1958 &self,
1959 tree: &AssembledTree,
1960 enrich_codes: bool,
1961 ) -> (
1962 serde_json::Value,
1963 std::collections::HashMap<String, Vec<usize>>,
1964 ) {
1965 self.map_all_forward_inner_with_tx(tree, enrich_codes, self.transaction_group.as_deref())
1966 }
1967
1968 fn map_all_forward_inner_with_tx(
1972 &self,
1973 tree: &AssembledTree,
1974 enrich_codes: bool,
1975 tx_group_override: Option<&str>,
1976 ) -> (
1977 serde_json::Value,
1978 std::collections::HashMap<String, Vec<usize>>,
1979 ) {
1980 let mut result = serde_json::Map::new();
1981 let mut nesting_info: std::collections::HashMap<String, Vec<usize>> =
1982 std::collections::HashMap::new();
1983
1984 for def in &self.definitions {
1985 if def.meta.parent_field.is_some() {
1988 continue;
1989 }
1990 let entity = &def.meta.entity;
1991
1992 let bo4e = if let Some(ref disc) = def.meta.discriminator {
1993 let use_source_path = def
1998 .meta
1999 .source_path
2000 .as_ref()
2001 .is_some_and(|sp| has_source_path_qualifiers(sp));
2002 if use_source_path {
2003 let sp = def.meta.source_path.as_deref().unwrap();
2005 let all_instances = Self::resolve_all_by_source_path(tree, sp);
2006 let instances: Vec<_> = if let Some(matcher) = DiscriminatorMatcher::parse(disc)
2008 {
2009 matcher.filter_instances(all_instances)
2010 } else {
2011 all_instances
2012 };
2013 let extract = |instance: &AssembledGroupInstance| {
2014 let mut r = serde_json::Map::new();
2015 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
2016 serde_json::Value::Object(r)
2017 };
2018 match instances.len() {
2019 0 => None,
2020 1 => Some(extract(instances[0])),
2021 _ => Some(serde_json::Value::Array(
2022 instances.iter().map(|i| extract(i)).collect(),
2023 )),
2024 }
2025 } else {
2026 let reps = Self::resolve_all_repetitions(tree, &def.meta.source_group, disc);
2027 match reps.len() {
2028 0 => None,
2029 1 => Some(self.map_forward_inner(tree, def, reps[0], enrich_codes)),
2030 _ => Some(serde_json::Value::Array(
2031 reps.iter()
2032 .map(|&rep| self.map_forward_inner(tree, def, rep, enrich_codes))
2033 .collect(),
2034 )),
2035 }
2036 }
2037 } else if def.meta.source_group.is_empty() {
2038 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2040 } else if def.meta.source_path.as_ref().is_some_and(|sp| {
2041 has_source_path_qualifiers(sp) || def.meta.source_group.contains('.')
2042 }) {
2043 let sp = def.meta.source_path.as_deref().unwrap();
2048 let mut indexed = Self::resolve_all_with_parent_indices(tree, sp);
2049
2050 if let Some(last_part) = sp.rsplit('.').next() {
2055 if !last_part.contains('_') {
2056 let base_prefix = if let Some(parent) = sp.rsplit_once('.') {
2060 format!("{}.", parent.0)
2061 } else {
2062 String::new()
2063 };
2064 let sibling_qualifiers: Vec<String> = self
2065 .definitions
2066 .iter()
2067 .filter_map(|d| d.meta.source_path.as_deref())
2068 .filter(|other_sp| {
2069 *other_sp != sp
2070 && other_sp.starts_with(&base_prefix)
2071 && other_sp.split('.').count() == sp.split('.').count()
2072 })
2073 .filter_map(|other_sp| {
2074 let other_last = other_sp.rsplit('.').next()?;
2075 let (base, q) = other_last.split_once('_')?;
2078 if base == last_part {
2079 Some(q.to_string())
2080 } else {
2081 None
2082 }
2083 })
2084 .collect();
2085
2086 if !sibling_qualifiers.is_empty() {
2087 indexed.retain(|(_, inst)| {
2088 let entry_qual = inst
2089 .segments
2090 .first()
2091 .and_then(|seg| seg.elements.first())
2092 .and_then(|el| el.first())
2093 .map(|v| v.to_lowercase());
2094 !entry_qual.is_some_and(|q| {
2097 sibling_qualifiers.iter().any(|sq| {
2098 sq.split('_').any(|part| part.eq_ignore_ascii_case(&q))
2099 })
2100 })
2101 });
2102 }
2103 }
2104 }
2105 let extract = |instance: &AssembledGroupInstance| {
2106 let mut r = serde_json::Map::new();
2107 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
2108 serde_json::Value::Object(r)
2109 };
2110 if def.meta.source_group.contains('.') && !indexed.is_empty() {
2115 if let Some(sp) = &def.meta.source_path {
2116 let parent_indices: Vec<usize> =
2117 indexed.iter().map(|(idx, _)| *idx).collect();
2118 nesting_info.entry(sp.clone()).or_insert(parent_indices);
2119
2120 let child_key = format!("{sp}#child");
2123 if let std::collections::hash_map::Entry::Vacant(e) =
2124 nesting_info.entry(child_key)
2125 {
2126 let child_indices: Vec<usize> =
2127 Self::compute_child_indices(tree, sp, &indexed);
2128 if !child_indices.is_empty() {
2129 e.insert(child_indices);
2130 }
2131 }
2132 }
2133 }
2134 match indexed.len() {
2135 0 => None,
2136 1 => Some(extract(indexed[0].1)),
2137 _ => Some(serde_json::Value::Array(
2138 indexed.iter().map(|(_, i)| extract(i)).collect(),
2139 )),
2140 }
2141 } else {
2142 let num_reps = Self::count_repetitions(tree, &def.meta.source_group);
2143 if num_reps <= 1 {
2144 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2145 } else {
2146 let mut items = Vec::with_capacity(num_reps);
2148 for rep in 0..num_reps {
2149 items.push(self.map_forward_inner(tree, def, rep, enrich_codes));
2150 }
2151 Some(serde_json::Value::Array(items))
2152 }
2153 };
2154
2155 if let Some(bo4e) = bo4e {
2156 let key = to_camel_case(entity);
2157 match def.meta.target_list.as_deref() {
2158 Some(list_field) => append_to_list_field(&mut result, &key, list_field, bo4e),
2159 None => deep_merge_insert(&mut result, &key, bo4e),
2160 }
2161 }
2162 }
2163
2164 nest_child_entities_in_result(
2169 &mut result,
2170 &self.definitions,
2171 &nesting_info,
2172 tx_group_override,
2173 );
2174
2175 (serde_json::Value::Object(result), nesting_info)
2176 }
2177
2178 pub fn map_all_reverse(
2187 &self,
2188 entities: &serde_json::Value,
2189 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2190 ) -> AssembledTree {
2191 self.map_all_reverse_with_mig(entities, nesting_info, None)
2192 }
2193
2194 pub fn map_all_reverse_with_mig(
2198 &self,
2199 entities: &serde_json::Value,
2200 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2201 mig: Option<&MigSchema>,
2202 ) -> AssembledTree {
2203 let mut root_segments: Vec<AssembledSegment> = Vec::new();
2204 let mut groups: Vec<AssembledGroup> = Vec::new();
2205 let mut inferred_nesting: std::collections::HashMap<String, Vec<usize>> =
2208 std::collections::HashMap::new();
2209
2210 for def in &self.definitions {
2211 if def.meta.parent_field.is_some() {
2214 continue;
2215 }
2216 let entity_key = to_camel_case(&def.meta.entity);
2217
2218 let _extracted: Option<serde_json::Value>;
2221 let entity_value = if let Some(list_field) = def.meta.target_list.as_deref() {
2222 match entities.get(&entity_key).and_then(|e| e.get(list_field)) {
2228 Some(v) if v.is_array() => {
2229 _extracted = None;
2230 v
2231 }
2232 _ => continue,
2233 }
2234 } else if let Some(v) = entities.get(&entity_key) {
2235 _extracted = None;
2236 v
2237 } else if def.meta.source_group.contains('.') {
2238 match extract_child_from_parent_with_indices(entities, &self.definitions, def) {
2240 Some((v, parent_indices)) => {
2241 if let Some(sp) = def.meta.source_path.as_deref() {
2243 inferred_nesting
2244 .entry(sp.to_string())
2245 .or_insert(parent_indices);
2246 }
2247 _extracted = Some(v);
2248 _extracted.as_ref().unwrap()
2249 }
2250 None => continue,
2251 }
2252 } else {
2253 continue;
2254 };
2255
2256 let unwrapped: Option<serde_json::Value>;
2264 let entity_value = if entity_value.is_object() && !entity_value.is_array() {
2265 if let Some(disc_value) = def
2266 .meta
2267 .discriminator
2268 .as_deref()
2269 .and_then(|d| d.split_once('='))
2270 .map(|(_, v)| v)
2271 {
2272 if let Some(inner) = entity_value.get(disc_value) {
2274 let mut injected = inner.clone();
2275 if let Some(qualifier_field) =
2278 find_qualifier_companion_field(&self.definitions, &def.meta.entity)
2279 {
2280 if let Some(obj) = injected.as_object_mut() {
2281 let entry = obj
2282 .entry(qualifier_field)
2283 .or_insert(serde_json::Value::Null);
2284 if entry.is_null() {
2285 *entry = serde_json::Value::String(disc_value.to_string());
2286 }
2287 }
2288 }
2289 unwrapped = Some(injected);
2290 unwrapped.as_ref().unwrap()
2291 } else {
2292 entity_value
2293 }
2294 } else if is_map_keyed_object(entity_value) {
2295 let map = entity_value.as_object().unwrap();
2300 let arr: Vec<serde_json::Value> = map
2301 .iter()
2302 .map(|(key, val)| {
2303 let mut item = val.clone();
2304 if let Some(obj) = item.as_object_mut() {
2307 if let Some(qualifier_field) = find_qualifier_companion_field(
2308 &self.definitions,
2309 &def.meta.entity,
2310 ) {
2311 let entry = obj
2312 .entry(qualifier_field)
2313 .or_insert(serde_json::Value::Null);
2314 if entry.is_null() {
2315 *entry = serde_json::Value::String(key.clone());
2316 }
2317 }
2318 }
2319 item
2320 })
2321 .collect();
2322 unwrapped = Some(serde_json::Value::Array(arr));
2323 unwrapped.as_ref().unwrap()
2324 } else {
2325 entity_value
2326 }
2327 } else {
2328 entity_value
2329 };
2330
2331 let leaf_group = def
2333 .meta
2334 .source_group
2335 .rsplit('.')
2336 .next()
2337 .unwrap_or(&def.meta.source_group);
2338
2339 if def.meta.source_group.is_empty() {
2340 let instance = self.map_reverse(entity_value, def);
2342 root_segments.extend(instance.segments);
2343 } else if entity_value.is_array() {
2344 let arr = entity_value.as_array().unwrap();
2346 let reps: Vec<_> = arr.iter().map(|item| self.map_reverse(item, def)).collect();
2347
2348 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2350 existing.repetitions.extend(reps);
2351 } else {
2352 groups.push(AssembledGroup {
2353 group_id: leaf_group.to_string(),
2354 repetitions: reps,
2355 });
2356 }
2357 } else {
2358 let instance = self.map_reverse(entity_value, def);
2360
2361 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2362 existing.repetitions.push(instance);
2363 } else {
2364 groups.push(AssembledGroup {
2365 group_id: leaf_group.to_string(),
2366 repetitions: vec![instance],
2367 });
2368 }
2369 }
2370 }
2371
2372 let nested_specs: Vec<(String, String)> = self
2378 .definitions
2379 .iter()
2380 .filter(|def| def.meta.parent_field.is_none())
2381 .filter_map(|def| {
2382 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
2383 if parts.len() > 1 {
2384 Some((parts[0].to_string(), parts[parts.len() - 1].to_string()))
2385 } else {
2386 None
2387 }
2388 })
2389 .collect();
2390 for (parent_id, child_id) in &nested_specs {
2391 let has_parent = groups.iter().any(|g| g.group_id == *parent_id);
2393 let has_child = groups.iter().any(|g| g.group_id == *child_id);
2394 if has_parent && has_child {
2395 let child_idx = groups.iter().position(|g| g.group_id == *child_id).unwrap();
2396 let child_group = groups.remove(child_idx);
2397 let parent = groups
2398 .iter_mut()
2399 .find(|g| g.group_id == *parent_id)
2400 .unwrap();
2401 let child_source_path = self
2405 .definitions
2406 .iter()
2407 .find(|d| {
2408 let parts: Vec<&str> = d.meta.source_group.split('.').collect();
2409 d.meta.parent_field.is_none()
2410 && parts.len() > 1
2411 && parts[parts.len() - 1] == *child_id
2412 })
2413 .and_then(|d| d.meta.source_path.as_deref());
2414 let distribution = child_source_path.and_then(|key| {
2415 nesting_info
2416 .and_then(|ni| ni.get(key))
2417 .or_else(|| inferred_nesting.get(key))
2418 });
2419 let unlinked_target = mig
2425 .and_then(|m| {
2426 mig_assembly::repetition_order::preferred_parent_repetition(
2427 parent,
2428 &m.segment_groups,
2429 child_id,
2430 )
2431 })
2432 .unwrap_or(0);
2433 for (i, child_rep) in child_group.repetitions.into_iter().enumerate() {
2434 let target_idx = distribution
2435 .and_then(|dist| dist.get(i))
2436 .copied()
2437 .unwrap_or(unlinked_target);
2438
2439 if let Some(target_rep) = parent.repetitions.get_mut(target_idx) {
2440 if let Some(existing) = target_rep
2441 .child_groups
2442 .iter_mut()
2443 .find(|g| g.group_id == *child_id)
2444 {
2445 existing.repetitions.push(child_rep);
2446 } else {
2447 target_rep.child_groups.push(AssembledGroup {
2448 group_id: child_id.clone(),
2449 repetitions: vec![child_rep],
2450 });
2451 }
2452 }
2453 }
2454 }
2455 }
2456
2457 let post_group_start = root_segments.len();
2458 AssembledTree {
2459 segments: root_segments,
2460 groups,
2461 post_group_start,
2462 inter_group_segments: std::collections::BTreeMap::new(),
2463 }
2464 }
2465
2466 fn count_repetitions(tree: &AssembledTree, group_path: &str) -> usize {
2468 let parts: Vec<&str> = group_path.split('.').collect();
2469
2470 let (first_id, first_rep) = parse_group_spec(parts[0]);
2471 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2472 Some(g) => g,
2473 None => return 0,
2474 };
2475
2476 if parts.len() == 1 {
2477 return first_group.repetitions.len();
2478 }
2479
2480 let mut current_instance = match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2482 Some(i) => i,
2483 None => return 0,
2484 };
2485
2486 for (i, part) in parts[1..].iter().enumerate() {
2487 let (group_id, explicit_rep) = parse_group_spec(part);
2488 let child_group = match current_instance
2489 .child_groups
2490 .iter()
2491 .find(|g| g.group_id == group_id)
2492 {
2493 Some(g) => g,
2494 None => return 0,
2495 };
2496
2497 if i == parts.len() - 2 {
2498 return child_group.repetitions.len();
2500 }
2501 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2502 Some(i) => i,
2503 None => return 0,
2504 };
2505 }
2506
2507 0
2508 }
2509
2510 pub fn translate_edifact_to_bo4e(
2518 msg_engine: &MappingEngine,
2519 tx_engine: &MappingEngine,
2520 tree: &AssembledTree,
2521 transaction_group: &str,
2522 ) -> crate::model::MappedMessage {
2523 Self::map_interchange_inner(msg_engine, tx_engine, tree, transaction_group, false)
2524 }
2525
2526 pub fn enrich_bo4e_types(
2540 msg_engine: &MappingEngine,
2541 tx_engine: &MappingEngine,
2542 mapped: &mut crate::model::MappedMessage,
2543 ) {
2544 msg_engine.enrich_entities(&mut mapped.stammdaten);
2545 for tx in &mut mapped.transaktionen {
2546 tx_engine.enrich_entities(&mut tx.stammdaten);
2547 }
2548
2549 msg_engine.enrich_named_entity(
2553 &mut mapped.nachricht_meta,
2554 crate::model::MSG_METADATA_ENTITY,
2555 );
2556 for tx in &mut mapped.transaktionen {
2557 tx_engine
2558 .enrich_named_entity(&mut tx.transaktionsdaten, crate::model::TX_METADATA_ENTITY);
2559 }
2560 }
2561
2562 fn enrich_named_entity(&self, value: &mut serde_json::Value, entity_key: &str) {
2567 if self.code_lookup.is_none() || value.is_null() {
2568 return;
2569 }
2570 let sites = self.code_sites();
2571 if let Some(entity_sites) = sites.get(entity_key) {
2572 Self::apply_sites(self, value, entity_sites);
2573 }
2574 }
2575
2576 fn enrich_entities(&self, value: &mut serde_json::Value) {
2582 if self.code_lookup.is_none() {
2583 return;
2584 }
2585 let sites: HashMap<String, Vec<CodeSite<'_>>> = self.code_sites();
2586 if sites.is_empty() {
2587 return;
2588 }
2589 Self::walk_and_enrich(self, value, &sites);
2590 }
2591
2592 fn code_sites(&self) -> HashMap<String, Vec<CodeSite<'_>>> {
2595 let Some(ref code_lookup) = self.code_lookup else {
2596 return HashMap::new();
2597 };
2598 let mut sites: HashMap<String, Vec<CodeSite<'_>>> = HashMap::new();
2599
2600 for def in &self.definitions {
2601 let Some(ref source_path) = def.meta.source_path else {
2602 continue;
2603 };
2604 let entity_key = to_camel_case(&def.meta.entity);
2605
2606 for (path, field_mapping) in &def.fields {
2607 let (target, enum_map, also_target, also_enum_map) = match field_mapping {
2608 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
2609 FieldMapping::Structured(s) => (
2610 s.target.as_str(),
2611 self.table(s.enum_map.as_ref(), s.code_list.as_deref()),
2612 s.also_target.as_deref(),
2613 self.table(s.also_enum_map.as_ref(), s.also_code_list.as_deref()),
2614 ),
2615 FieldMapping::Nested(_) => continue,
2616 };
2617 if target.is_empty() {
2618 continue;
2619 }
2620
2621 let parts: Vec<&str> = path.split('.').collect();
2622 let (seg_tag, path_qualifier, _occ) = parse_tag_qualifier(parts[0]);
2623 let (element_idx, component_idx) = Self::parse_element_component(&parts[1..]);
2624 let disc_qualifier = Self::discriminator_qualifier_for_tag(def, &seg_tag);
2631 if code_lookup
2632 .enrichment_codes(
2633 source_path,
2634 &seg_tag,
2635 path_qualifier,
2636 disc_qualifier.as_deref(),
2637 element_idx,
2638 component_idx,
2639 )
2640 .is_none()
2641 {
2642 continue;
2643 }
2644
2645 sites.entry(entity_key.clone()).or_default().push(CodeSite {
2646 target,
2647 parent_field: def.meta.parent_field.as_deref(),
2648 source_path,
2649 seg_tag,
2650 path_qualifier: path_qualifier.map(str::to_string),
2651 disc_qualifier,
2652 element_idx,
2653 component_idx,
2654 enum_map,
2655 also_target,
2656 also_enum_map,
2657 });
2658 }
2659 }
2660 sites
2661 }
2662
2663 fn walk_and_enrich(
2666 engine: &MappingEngine,
2667 value: &mut serde_json::Value,
2668 sites: &HashMap<String, Vec<CodeSite<'_>>>,
2669 ) {
2670 match value {
2671 serde_json::Value::Object(map) => {
2672 for (key, child) in map.iter_mut() {
2673 if let Some(entity_sites) = sites.get(key.as_str()) {
2674 Self::apply_sites(engine, child, entity_sites);
2675 }
2676 Self::walk_and_enrich(engine, child, sites);
2677 }
2678 }
2679 serde_json::Value::Array(items) => {
2680 for item in items.iter_mut() {
2681 Self::walk_and_enrich(engine, item, sites);
2682 }
2683 }
2684 _ => {}
2685 }
2686 }
2687
2688 fn apply_sites(engine: &MappingEngine, value: &mut serde_json::Value, sites: &[CodeSite<'_>]) {
2691 match value {
2692 serde_json::Value::Array(items) => {
2693 for item in items.iter_mut() {
2694 Self::apply_sites(engine, item, sites);
2695 }
2696 }
2697 serde_json::Value::Object(_) => {
2698 for site in sites {
2699 match site.parent_field {
2700 None => engine.enrich_one(value, site),
2701 Some(field) => {
2702 if let Some(nested) = value.get_mut(field) {
2703 Self::apply_nested_site(engine, nested, site);
2704 }
2705 }
2706 }
2707 }
2708 }
2709 _ => {}
2710 }
2711 }
2712
2713 fn apply_nested_site(
2715 engine: &MappingEngine,
2716 value: &mut serde_json::Value,
2717 site: &CodeSite<'_>,
2718 ) {
2719 match value {
2720 serde_json::Value::Array(items) => {
2721 for item in items.iter_mut() {
2722 Self::apply_nested_site(engine, item, site);
2723 }
2724 }
2725 serde_json::Value::Object(_) => engine.enrich_one(value, site),
2726 _ => {}
2727 }
2728 }
2729
2730 fn enrich_one(&self, entity: &mut serde_json::Value, site: &CodeSite<'_>) {
2732 let Some(ref code_lookup) = self.code_lookup else {
2733 return;
2734 };
2735 let Some(mapped_val) = Self::read_plain_string(entity, site.target) else {
2737 return;
2738 };
2739
2740 let raw = match site.enum_map {
2743 None => mapped_val.clone(),
2744 Some(map) => {
2745 let joint = match (site.also_target, site.also_enum_map) {
2746 (Some(also), Some(also_map)) => {
2747 Self::read_plain_string(entity, also).and_then(|also_v| {
2748 map.iter()
2749 .find(|(code, bo4e_v)| {
2750 *bo4e_v == &mapped_val && also_map.get(*code) == Some(&also_v)
2751 })
2752 .map(|(code, _)| code.clone())
2753 })
2754 }
2755 _ => None,
2756 };
2757 joint
2758 .or_else(|| {
2759 map.iter()
2760 .find(|(_, bo4e_v)| *bo4e_v == &mapped_val)
2761 .map(|(code, _)| code.clone())
2762 })
2763 .unwrap_or_else(|| mapped_val.clone())
2764 }
2765 };
2766
2767 let Some(codes) = code_lookup.enrichment_codes(
2768 site.source_path,
2769 &site.seg_tag,
2770 site.path_qualifier.as_deref(),
2771 site.disc_qualifier.as_deref(),
2772 site.element_idx,
2773 site.component_idx,
2774 ) else {
2775 return;
2776 };
2777
2778 if let Some(ref pid) = self.current_pid {
2780 if codes.len() == 1 && codes.contains_key(pid.as_str()) {
2781 return;
2782 }
2783 }
2784
2785 let enrichment = codes.get(&raw);
2786 let meaning = enrichment
2787 .map(|e| serde_json::Value::String(e.meaning.clone()))
2788 .unwrap_or(serde_json::Value::Null);
2789
2790 let mut obj = serde_json::Map::new();
2791 obj.insert("code".into(), serde_json::json!(mapped_val));
2792 obj.insert("meaning".into(), meaning);
2793 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
2794 obj.insert("enum".into(), serde_json::json!(enum_key));
2795 }
2796
2797 if let serde_json::Value::Object(map) = entity {
2798 set_nested_value_json(map, site.target, serde_json::Value::Object(obj));
2799 }
2800 }
2801
2802 fn read_plain_string(entity: &serde_json::Value, target: &str) -> Option<String> {
2805 let mut current = entity;
2806 for part in target.split('.') {
2807 current = current.get(part)?;
2808 }
2809 current.as_str().map(str::to_string)
2810 }
2811
2812 pub fn map_interchange(
2821 msg_engine: &MappingEngine,
2822 tx_engine: &MappingEngine,
2823 tree: &AssembledTree,
2824 transaction_group: &str,
2825 enrich_codes: bool,
2826 ) -> crate::model::MappedMessage {
2827 let mut mapped =
2828 Self::translate_edifact_to_bo4e(msg_engine, tx_engine, tree, transaction_group);
2829 if enrich_codes {
2830 Self::enrich_bo4e_types(msg_engine, tx_engine, &mut mapped);
2831 }
2832 mapped
2833 }
2834
2835 #[doc(hidden)]
2844 pub fn map_interchange_inner_for_test(
2845 msg_engine: &MappingEngine,
2846 tx_engine: &MappingEngine,
2847 tree: &AssembledTree,
2848 transaction_group: &str,
2849 enrich_codes: bool,
2850 ) -> crate::model::MappedMessage {
2851 Self::map_interchange_inner(msg_engine, tx_engine, tree, transaction_group, enrich_codes)
2852 }
2853
2854 pub(crate) fn map_interchange_inner(
2855 msg_engine: &MappingEngine,
2856 tx_engine: &MappingEngine,
2857 tree: &AssembledTree,
2858 transaction_group: &str,
2859 enrich_codes: bool,
2860 ) -> crate::model::MappedMessage {
2861 let (stammdaten, nesting_info) = msg_engine.map_all_forward_inner(tree, enrich_codes);
2863
2864 let transaktionen = tree
2866 .groups
2867 .iter()
2868 .find(|g| g.group_id == transaction_group)
2869 .map(|sg| {
2870 sg.repetitions
2871 .iter()
2872 .map(|instance| {
2873 let wrapped_tree = AssembledTree {
2876 segments: vec![],
2877 groups: vec![AssembledGroup {
2878 group_id: transaction_group.to_string(),
2879 repetitions: vec![instance.clone()],
2880 }],
2881 post_group_start: 0,
2882 inter_group_segments: std::collections::BTreeMap::new(),
2883 };
2884
2885 let (tx_result, tx_nesting) = tx_engine.map_all_forward_inner_with_tx(
2889 &wrapped_tree,
2890 enrich_codes,
2891 Some(transaction_group),
2892 );
2893
2894 let mut tx_result = tx_result;
2899 let transaktionsdaten = crate::model::take_entity(
2900 &mut tx_result,
2901 crate::model::TX_METADATA_ENTITY,
2902 );
2903
2904 crate::model::MappedTransaktion {
2905 stammdaten: tx_result,
2906 transaktionsdaten,
2907 nesting_info: tx_nesting,
2908 }
2909 })
2910 .collect()
2911 })
2912 .unwrap_or_default();
2913
2914 let mut stammdaten = stammdaten;
2916 let nachricht_meta =
2917 crate::model::take_entity(&mut stammdaten, crate::model::MSG_METADATA_ENTITY);
2918
2919 crate::model::MappedMessage {
2920 stammdaten,
2921 nachricht_meta,
2922 transaktionen,
2923 nesting_info,
2924 inter_group_segments: tree.inter_group_segments.clone(),
2925 }
2926 }
2927
2928 pub fn map_interchange_reverse(
2938 msg_engine: &MappingEngine,
2939 tx_engine: &MappingEngine,
2940 mapped: &crate::model::MappedMessage,
2941 transaction_group: &str,
2942 filtered_mig: Option<&MigSchema>,
2943 ) -> AssembledTree {
2944 let _owned_msg: Option<serde_json::Value>;
2952 let msg_stammdaten = if !mapped.nachricht_meta.is_null() {
2953 let mut merged = mapped.stammdaten.clone();
2954 crate::model::restore_entity(
2955 &mut merged,
2956 crate::model::MSG_METADATA_ENTITY,
2957 &mapped.nachricht_meta,
2958 );
2959 _owned_msg = Some(merged);
2960 _owned_msg.as_ref().unwrap()
2961 } else {
2962 _owned_msg = None;
2963 &mapped.stammdaten
2964 };
2965
2966 let msg_tree = msg_engine.map_all_reverse_with_mig(
2967 msg_stammdaten,
2968 if mapped.nesting_info.is_empty() {
2969 None
2970 } else {
2971 Some(&mapped.nesting_info)
2972 },
2973 filtered_mig,
2974 );
2975
2976 let mut sg4_reps: Vec<AssembledGroupInstance> = Vec::new();
2978
2979 struct DefWithMeta<'a> {
2983 def: &'a MappingDefinition,
2984 relative: String,
2985 depth: usize,
2986 }
2987
2988 let mut sorted_defs: Vec<DefWithMeta> = tx_engine
2989 .definitions
2990 .iter()
2991 .filter(|def| def.meta.parent_field.is_none())
2994 .map(|def| {
2995 let relative = strip_tx_group_prefix(&def.meta.source_group, transaction_group);
2996 let depth = if relative.is_empty() {
2997 0
2998 } else {
2999 relative.chars().filter(|c| *c == '.').count() + 1
3000 };
3001 DefWithMeta {
3002 def,
3003 relative,
3004 depth,
3005 }
3006 })
3007 .collect();
3008
3009 let mut parent_rep_map: std::collections::HashMap<String, usize> =
3013 std::collections::HashMap::new();
3014 for dm in &sorted_defs {
3015 if dm.depth >= 2 {
3016 let parts: Vec<&str> = dm.relative.split('.').collect();
3017 let (_, parent_rep) = parse_group_spec(parts[0]);
3018 if let Some(rep_idx) = parent_rep {
3019 if let Some(sp) = &dm.def.meta.source_path {
3020 if let Some((parent_path, _)) = sp.rsplit_once('.') {
3021 parent_rep_map
3022 .entry(parent_path.to_string())
3023 .or_insert(rep_idx);
3024 }
3025 }
3026 }
3027 }
3028 }
3029
3030 for dm in &mut sorted_defs {
3033 if dm.depth == 1 && !dm.relative.contains(':') {
3034 if let Some(sp) = &dm.def.meta.source_path {
3035 if let Some(rep_idx) = parent_rep_map.get(sp.as_str()) {
3036 dm.relative = format!("{}:{}", dm.relative, rep_idx);
3037 }
3038 }
3039 }
3040 }
3041
3042 if let Some(mig) = filtered_mig {
3049 let mig_order = build_reverse_mig_group_order(mig, transaction_group);
3050 sorted_defs.sort_by(|a, b| {
3051 a.depth.cmp(&b.depth).then_with(|| {
3052 let a_id = a.relative.split(':').next().unwrap_or(&a.relative);
3053 let b_id = b.relative.split(':').next().unwrap_or(&b.relative);
3054 let a_pos = variant_mig_position(a.def, a_id, &mig_order);
3056 let b_pos = variant_mig_position(b.def, b_id, &mig_order);
3057 a_pos.cmp(&b_pos).then(a.relative.cmp(&b.relative))
3058 })
3059 });
3060 } else {
3061 sorted_defs.sort_by(|a, b| a.depth.cmp(&b.depth).then(a.relative.cmp(&b.relative)));
3062 }
3063
3064 for tx in &mapped.transaktionen {
3065 let mut root_segs: Vec<AssembledSegment> = Vec::new();
3066 let mut child_groups: Vec<AssembledGroup> = Vec::new();
3067
3068 let _owned_tx: Option<serde_json::Value>;
3071 let tx_stammdaten: &serde_json::Value = if !tx.transaktionsdaten.is_null() {
3072 let mut merged = tx.stammdaten.clone();
3073 crate::model::restore_entity(
3074 &mut merged,
3075 crate::model::TX_METADATA_ENTITY,
3076 &tx.transaktionsdaten,
3077 );
3078 _owned_tx = Some(merged);
3079 _owned_tx.as_ref().unwrap()
3080 } else {
3081 _owned_tx = None;
3082 &tx.stammdaten
3083 };
3084
3085 let mut source_path_to_rep: std::collections::HashMap<String, Vec<usize>> =
3090 std::collections::HashMap::new();
3091
3092 for dm in &sorted_defs {
3093 let entity_key = to_camel_case(&dm.def.meta.entity);
3096 let _tx_extracted: Option<serde_json::Value>;
3097 let bo4e_value = if let Some(v) = tx_stammdaten.get(&entity_key) {
3098 _tx_extracted = None;
3099 v
3100 } else if dm.def.meta.source_group.contains('.') {
3101 match extract_child_from_parent(tx_stammdaten, &tx_engine.definitions, dm.def) {
3102 Some(v) => {
3103 _tx_extracted = Some(v);
3104 _tx_extracted.as_ref().unwrap()
3105 }
3106 None => continue,
3107 }
3108 } else {
3109 continue;
3110 };
3111
3112 let unwrapped_value: Option<serde_json::Value>;
3114 let bo4e_value = if bo4e_value.is_object() && !bo4e_value.is_array() {
3115 if let Some(disc_value) = dm
3116 .def
3117 .meta
3118 .discriminator
3119 .as_deref()
3120 .and_then(|d| d.split_once('='))
3121 .map(|(_, v)| v)
3122 {
3123 if let Some(inner) = bo4e_value.get(disc_value) {
3124 let mut injected = inner.clone();
3125 if let Some(qualifier_field) = find_qualifier_companion_field(
3126 &tx_engine.definitions,
3127 &dm.def.meta.entity,
3128 ) {
3129 if let Some(obj) = injected.as_object_mut() {
3130 obj.entry(qualifier_field).or_insert_with(|| {
3131 serde_json::Value::String(disc_value.to_string())
3132 });
3133 }
3134 }
3135 unwrapped_value = Some(injected);
3136 unwrapped_value.as_ref().unwrap()
3137 } else {
3138 bo4e_value
3139 }
3140 } else if is_map_keyed_object(bo4e_value) {
3141 let map = bo4e_value.as_object().unwrap();
3142 let arr: Vec<serde_json::Value> = map
3143 .iter()
3144 .map(|(key, val)| {
3145 let mut item = val.clone();
3146 if let Some(obj) = item.as_object_mut() {
3147 if let Some(qualifier_field) = find_qualifier_companion_field(
3148 &tx_engine.definitions,
3149 &dm.def.meta.entity,
3150 ) {
3151 let entry = obj
3152 .entry(qualifier_field)
3153 .or_insert(serde_json::Value::Null);
3154 if entry.is_null() {
3155 *entry = serde_json::Value::String(key.clone());
3156 }
3157 }
3158 }
3159 item
3160 })
3161 .collect();
3162 unwrapped_value = Some(serde_json::Value::Array(arr));
3163 unwrapped_value.as_ref().unwrap()
3164 } else {
3165 bo4e_value
3166 }
3167 } else {
3168 bo4e_value
3169 };
3170
3171 let items: Vec<&serde_json::Value> = if bo4e_value.is_array() {
3175 bo4e_value.as_array().unwrap().iter().collect()
3176 } else {
3177 vec![bo4e_value]
3178 };
3179
3180 for (item_idx, item) in items.iter().enumerate() {
3181 let instance = tx_engine.map_reverse(item, dm.def);
3182
3183 if instance.segments.is_empty() && instance.child_groups.is_empty() {
3185 continue;
3186 }
3187
3188 if dm.relative.is_empty() {
3189 root_segs.extend(instance.segments);
3196 for child in instance.child_groups {
3197 match child_groups
3198 .iter_mut()
3199 .find(|g| g.group_id == child.group_id)
3200 {
3201 Some(existing) => existing.repetitions.extend(child.repetitions),
3202 None => child_groups.push(child),
3203 }
3204 }
3205 } else {
3206 let effective_relative = if dm.depth >= 2 {
3210 let rel = if items.len() > 1 {
3213 strip_all_rep_indices(&dm.relative)
3214 } else {
3215 dm.relative.clone()
3216 };
3217 let skip_nesting = dm
3224 .def
3225 .meta
3226 .source_path
3227 .as_ref()
3228 .and_then(|sp| sp.rsplit_once('.'))
3229 .and_then(|(parent_path, _)| source_path_to_rep.get(parent_path))
3230 .is_some_and(|reps| reps.len() == 1);
3231 let nesting_idx = if items.len() > 1 && !skip_nesting {
3232 dm.def
3233 .meta
3234 .source_path
3235 .as_ref()
3236 .and_then(|sp| tx.nesting_info.get(sp))
3237 .and_then(|dist| dist.get(item_idx))
3238 .copied()
3239 } else {
3240 None
3241 };
3242 if let Some(parent_rep) = nesting_idx {
3243 let parts: Vec<&str> = rel.split('.').collect();
3245 let parent_id = parts[0].split(':').next().unwrap_or(parts[0]);
3246 let rest = parts[1..].join(".");
3247 format!("{}:{}.{}", parent_id, parent_rep, rest)
3248 } else {
3249 resolve_child_relative(
3250 &rel,
3251 dm.def.meta.source_path.as_deref(),
3252 &source_path_to_rep,
3253 item_idx,
3254 )
3255 }
3256 } else if dm.depth == 1 {
3257 let child_key = dm
3260 .def
3261 .meta
3262 .source_path
3263 .as_ref()
3264 .map(|sp| format!("{sp}#child"));
3265 if let Some(child_indices) =
3266 child_key.as_ref().and_then(|ck| tx.nesting_info.get(ck))
3267 {
3268 if let Some(&target) = child_indices.get(item_idx) {
3269 if target != usize::MAX {
3270 let base =
3271 dm.relative.split(':').next().unwrap_or(&dm.relative);
3272 format!("{}:{}", base, target)
3273 } else {
3274 dm.relative.clone()
3275 }
3276 } else if items.len() > 1 && item_idx > 0 {
3277 strip_rep_index(&dm.relative)
3278 } else {
3279 dm.relative.clone()
3280 }
3281 } else if items.len() > 1 && item_idx > 0 {
3282 strip_rep_index(&dm.relative)
3283 } else {
3284 dm.relative.clone()
3285 }
3286 } else if items.len() > 1 && item_idx > 0 {
3287 strip_rep_index(&dm.relative)
3290 } else {
3291 dm.relative.clone()
3292 };
3293
3294 let rep_used =
3295 place_in_groups(&mut child_groups, &effective_relative, instance);
3296
3297 if dm.depth == 1 {
3299 if let Some(sp) = &dm.def.meta.source_path {
3300 source_path_to_rep
3301 .entry(sp.clone())
3302 .or_default()
3303 .push(rep_used);
3304 }
3305 }
3306 }
3307 }
3308 }
3309
3310 sg4_reps.push(AssembledGroupInstance {
3311 segments: root_segs,
3312 child_groups,
3313 entry_mig_number: None,
3314 variant_mig_numbers: vec![],
3315 skipped_segments: Vec::new(),
3316 skipped_positions: Vec::new(),
3317 });
3318 }
3319
3320 let mut root_segments = Vec::new();
3327 let mut uns_segments = Vec::new();
3328 let mut uns_is_summary = false;
3329 let mut found_uns = false;
3330 for seg in msg_tree.segments {
3331 if seg.tag == "UNS" {
3332 uns_is_summary = seg
3334 .elements
3335 .first()
3336 .and_then(|el| el.first())
3337 .map(|v| v == "S")
3338 .unwrap_or(false);
3339 uns_segments.push(seg);
3340 found_uns = true;
3341 } else if found_uns {
3342 uns_segments.push(seg);
3344 } else {
3345 root_segments.push(seg);
3346 }
3347 }
3348
3349 let pre_group_count = root_segments.len();
3350 let mut all_groups = msg_tree.groups;
3351 let mut inter_group = msg_tree.inter_group_segments;
3352
3353 let sg_num = |id: &str| -> usize {
3355 id.strip_prefix("SG")
3356 .and_then(|n| n.parse::<usize>().ok())
3357 .unwrap_or(0)
3358 };
3359
3360 if !sg4_reps.is_empty() {
3361 if uns_is_summary {
3362 all_groups.push(AssembledGroup {
3364 group_id: transaction_group.to_string(),
3365 repetitions: sg4_reps,
3366 });
3367 if !uns_segments.is_empty() {
3368 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3373 let tx_num = sg_num(transaction_group);
3374 let uns_pos = all_groups
3375 .iter()
3376 .rposition(|g| sg_num(&g.group_id) <= tx_num)
3377 .map(|i| i + 1)
3378 .unwrap_or(all_groups.len());
3379 inter_group.insert(uns_pos, uns_segments);
3380 }
3381 } else {
3382 if !uns_segments.is_empty() {
3384 inter_group.insert(all_groups.len(), uns_segments);
3385 }
3386 all_groups.push(AssembledGroup {
3387 group_id: transaction_group.to_string(),
3388 repetitions: sg4_reps,
3389 });
3390 }
3391 } else if !uns_segments.is_empty() {
3392 if transaction_group.is_empty() {
3393 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3398 if uns_is_summary {
3399 inter_group.insert(all_groups.len(), uns_segments);
3400 } else {
3401 inter_group.insert(0, uns_segments);
3402 }
3403 } else {
3404 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3408 let tx_num = sg_num(transaction_group);
3409 let uns_pos = all_groups
3410 .iter()
3411 .rposition(|g| sg_num(&g.group_id) <= tx_num)
3412 .map(|i| i + 1)
3413 .unwrap_or(all_groups.len());
3414 inter_group.insert(uns_pos, uns_segments);
3415 }
3416 }
3417
3418 for (k, segs) in &mapped.inter_group_segments {
3426 if segs.is_empty() {
3427 continue;
3428 }
3429 let existing_tags: std::collections::HashSet<String> = inter_group
3430 .get(k)
3431 .map(|v| v.iter().map(|s| s.tag.clone()).collect())
3432 .unwrap_or_default();
3433 for seg in segs {
3434 if existing_tags.contains(&seg.tag) {
3435 continue;
3436 }
3437 inter_group.entry(*k).or_default().push(seg.clone());
3438 }
3439 }
3440
3441 let mut tree = AssembledTree {
3442 segments: root_segments,
3443 groups: all_groups,
3444 post_group_start: pre_group_count,
3445 inter_group_segments: inter_group,
3446 };
3447
3448 if let Some(mig) = filtered_mig {
3455 mig_assembly::repetition_order::sort_repetitions_by_mig_variant(
3456 &mut tree,
3457 mig,
3458 (!transaction_group.is_empty()).then_some(transaction_group),
3459 );
3460 }
3461 tree
3462 }
3463
3464 pub fn build_group_from_bo4e(
3466 &self,
3467 bo4e_value: &serde_json::Value,
3468 def: &MappingDefinition,
3469 ) -> AssembledGroup {
3470 let instance = self.map_reverse(bo4e_value, def);
3471 let leaf_group = def
3472 .meta
3473 .source_group
3474 .rsplit('.')
3475 .next()
3476 .unwrap_or(&def.meta.source_group);
3477
3478 AssembledGroup {
3479 group_id: leaf_group.to_string(),
3480 repetitions: vec![instance],
3481 }
3482 }
3483
3484 pub fn map_interchange_typed<M, T>(
3492 msg_engine: &MappingEngine,
3493 tx_engine: &MappingEngine,
3494 tree: &AssembledTree,
3495 tx_group: &str,
3496 enrich_codes: bool,
3497 nachrichtendaten: crate::model::Nachrichtendaten,
3498 interchangedaten: crate::model::Interchangedaten,
3499 ) -> Result<crate::model::Interchange<M, T>, serde_json::Error>
3500 where
3501 M: serde::de::DeserializeOwned,
3502 T: serde::de::DeserializeOwned,
3503 {
3504 let mapped = Self::map_interchange(msg_engine, tx_engine, tree, tx_group, enrich_codes);
3505 let nachricht = mapped.into_dynamic_nachricht(nachrichtendaten);
3506 let dynamic = crate::model::DynamicInterchange {
3507 interchangedaten,
3508 nachrichten: vec![nachricht],
3509 };
3510 let value = serde_json::to_value(&dynamic)?;
3511 serde_json::from_value(value)
3512 }
3513
3514 pub fn map_interchange_reverse_typed<M, T>(
3521 msg_engine: &MappingEngine,
3522 tx_engine: &MappingEngine,
3523 nachricht: &crate::model::Nachricht<M, T>,
3524 tx_group: &str,
3525 ) -> Result<AssembledTree, serde_json::Error>
3526 where
3527 M: serde::Serialize,
3528 T: serde::Serialize,
3529 {
3530 let mut stammdaten = serde_json::to_value(&nachricht.stammdaten)?;
3533 crate::model::restore_message_metadata(&mut stammdaten, &nachricht.nachrichtendaten);
3534 let transaktionen: Vec<crate::model::MappedTransaktion> = nachricht
3535 .transaktionen
3536 .iter()
3537 .map(|t| {
3538 Ok(crate::model::MappedTransaktion {
3539 stammdaten: serde_json::to_value(t)?,
3540 transaktionsdaten: serde_json::Value::Null,
3541 nesting_info: Default::default(),
3542 })
3543 })
3544 .collect::<Result<Vec<_>, serde_json::Error>>()?;
3545 let mapped = crate::model::MappedMessage {
3546 stammdaten,
3547 nachricht_meta: serde_json::Value::Null,
3548 transaktionen,
3549 nesting_info: Default::default(),
3550 inter_group_segments: Default::default(),
3551 };
3552 Ok(Self::map_interchange_reverse(
3553 msg_engine, tx_engine, &mapped, tx_group, None,
3554 ))
3555 }
3556}
3557
3558fn parse_source_path_part(part: &str) -> (&str, Option<&str>) {
3565 if let Some(pos) = part.find('_') {
3569 let group = &part[..pos];
3570 let qualifier = &part[pos + 1..];
3571 if !qualifier.is_empty() {
3572 return (group, Some(qualifier));
3573 }
3574 }
3575 (part, None)
3576}
3577
3578fn build_reverse_mig_group_order(mig: &MigSchema, tx_group_id: &str) -> HashMap<String, usize> {
3586 let mut order = HashMap::new();
3587 if let Some(tg) = mig.segment_groups.iter().find(|g| g.id == tx_group_id) {
3588 for (i, nested) in tg.nested_groups.iter().enumerate() {
3589 if let Some(ref vc) = nested.variant_code {
3591 let variant_key = format!("{}_{}", nested.id, vc.to_uppercase());
3592 order.insert(variant_key, i);
3593 }
3594 order.entry(nested.id.clone()).or_insert(i);
3596 }
3597 }
3598 order
3599}
3600
3601fn variant_mig_position(
3607 def: &MappingDefinition,
3608 base_group_id: &str,
3609 mig_order: &HashMap<String, usize>,
3610) -> usize {
3611 if let Some(ref sp) = def.meta.source_path {
3614 let base_lower = base_group_id.to_lowercase();
3616 for part in sp.split('.') {
3617 if part.starts_with(&base_lower)
3618 || part.starts_with(base_group_id.to_lowercase().as_str())
3619 {
3620 if let Some(underscore_pos) = part.find('_') {
3622 let qualifier = &part[underscore_pos + 1..];
3623 let variant_key = format!("{}_{}", base_group_id, qualifier.to_uppercase());
3624 if let Some(&pos) = mig_order.get(&variant_key) {
3625 return pos;
3626 }
3627 }
3628 }
3629 }
3630 }
3631 mig_order.get(base_group_id).copied().unwrap_or(usize::MAX)
3633}
3634
3635fn find_rep_by_entry_qualifier<'a>(
3640 reps: &'a [AssembledGroupInstance],
3641 qualifier: &str,
3642) -> Option<&'a AssembledGroupInstance> {
3643 let parts: Vec<&str> = qualifier.split('_').collect();
3645 reps.iter().find(|inst| {
3646 inst.segments.first().is_some_and(|seg| {
3647 seg.elements
3648 .first()
3649 .and_then(|e| e.first())
3650 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3651 })
3652 })
3653}
3654
3655fn find_all_reps_by_entry_qualifier<'a>(
3657 reps: &'a [AssembledGroupInstance],
3658 qualifier: &str,
3659) -> Vec<&'a AssembledGroupInstance> {
3660 let parts: Vec<&str> = qualifier.split('_').collect();
3662 reps.iter()
3663 .filter(|inst| {
3664 inst.segments.first().is_some_and(|seg| {
3665 seg.elements
3666 .first()
3667 .and_then(|e| e.first())
3668 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3669 })
3670 })
3671 .collect()
3672}
3673
3674fn has_source_path_qualifiers(source_path: &str) -> bool {
3676 source_path.split('.').any(|part| {
3677 if let Some(pos) = part.find('_') {
3678 pos < part.len() - 1
3679 } else {
3680 false
3681 }
3682 })
3683}
3684
3685fn parse_group_spec(part: &str) -> (&str, Option<usize>) {
3686 if let Some(colon_pos) = part.find(':') {
3687 let id = &part[..colon_pos];
3688 let rep = part[colon_pos + 1..].parse::<usize>().ok();
3689 (id, rep)
3690 } else {
3691 (part, None)
3692 }
3693}
3694
3695fn strip_tx_group_prefix(source_group: &str, tx_group: &str) -> String {
3701 if source_group == tx_group || source_group.is_empty() {
3702 String::new()
3703 } else if let Some(rest) = source_group.strip_prefix(tx_group) {
3704 rest.strip_prefix('.').unwrap_or(rest).to_string()
3705 } else {
3706 source_group.to_string()
3707 }
3708}
3709
3710fn place_in_groups(
3718 groups: &mut Vec<AssembledGroup>,
3719 relative_path: &str,
3720 instance: AssembledGroupInstance,
3721) -> usize {
3722 let parts: Vec<&str> = relative_path.split('.').collect();
3723
3724 if parts.len() == 1 {
3725 let (id, rep) = parse_group_spec(parts[0]);
3727
3728 let group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == id) {
3730 g
3731 } else {
3732 groups.push(AssembledGroup {
3733 group_id: id.to_string(),
3734 repetitions: vec![],
3735 });
3736 groups.last_mut().unwrap()
3737 };
3738
3739 if let Some(rep_idx) = rep {
3740 while group.repetitions.len() <= rep_idx {
3742 group.repetitions.push(AssembledGroupInstance {
3743 segments: vec![],
3744 child_groups: vec![],
3745 entry_mig_number: None,
3746 variant_mig_numbers: vec![],
3747 skipped_segments: Vec::new(),
3748 skipped_positions: Vec::new(),
3749 });
3750 }
3751 group.repetitions[rep_idx]
3752 .segments
3753 .extend(instance.segments);
3754 group.repetitions[rep_idx]
3755 .child_groups
3756 .extend(instance.child_groups);
3757 rep_idx
3758 } else {
3759 let pos = group.repetitions.len();
3761 group.repetitions.push(instance);
3762 pos
3763 }
3764 } else {
3765 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3767 let rep_idx = parent_rep.unwrap_or(0);
3768
3769 let parent_group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == parent_id) {
3771 g
3772 } else {
3773 groups.push(AssembledGroup {
3774 group_id: parent_id.to_string(),
3775 repetitions: vec![],
3776 });
3777 groups.last_mut().unwrap()
3778 };
3779
3780 while parent_group.repetitions.len() <= rep_idx {
3782 parent_group.repetitions.push(AssembledGroupInstance {
3783 segments: vec![],
3784 child_groups: vec![],
3785 entry_mig_number: None,
3786 variant_mig_numbers: vec![],
3787 skipped_segments: Vec::new(),
3788 skipped_positions: Vec::new(),
3789 });
3790 }
3791
3792 let remaining = parts[1..].join(".");
3793 place_in_groups(
3794 &mut parent_group.repetitions[rep_idx].child_groups,
3795 &remaining,
3796 instance,
3797 );
3798 rep_idx
3799 }
3800}
3801
3802fn resolve_child_relative(
3814 relative: &str,
3815 source_path: Option<&str>,
3816 source_path_to_rep: &std::collections::HashMap<String, Vec<usize>>,
3817 item_idx: usize,
3818) -> String {
3819 let parts: Vec<&str> = relative.split('.').collect();
3820 if parts.is_empty() {
3821 return relative.to_string();
3822 }
3823
3824 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3826 if parent_rep.is_some() {
3827 return relative.to_string();
3828 }
3829
3830 if let Some(sp) = source_path {
3832 if let Some((parent_path, _child)) = sp.rsplit_once('.') {
3833 if let Some(rep_indices) = source_path_to_rep.get(parent_path) {
3835 let rep_idx = rep_indices
3836 .get(item_idx)
3837 .or_else(|| rep_indices.last())
3838 .copied()
3839 .unwrap_or(0);
3840 let rest = parts[1..].join(".");
3841 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3842 }
3843 let prefix = format!("{}_", parent_path);
3851 let mut unioned: Vec<usize> = source_path_to_rep
3852 .iter()
3853 .filter(|(k, _)| k.starts_with(&prefix))
3854 .flat_map(|(_, v)| v.iter().copied())
3855 .collect();
3856 if !unioned.is_empty() {
3857 unioned.sort_unstable();
3858 unioned.dedup();
3859 let rep_idx = unioned
3860 .get(item_idx)
3861 .or_else(|| unioned.last())
3862 .copied()
3863 .unwrap_or(0);
3864 let rest = parts[1..].join(".");
3865 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3866 }
3867 }
3868 }
3869
3870 relative.to_string()
3872}
3873
3874struct DiscriminatorMatcher<'a> {
3881 tag: &'a str,
3882 element_idx: usize,
3883 component_idx: usize,
3884 expected_values: Vec<&'a str>,
3885 occurrence: Option<usize>,
3887}
3888
3889impl<'a> DiscriminatorMatcher<'a> {
3890 fn parse(disc: &'a str) -> Option<Self> {
3891 let (spec, expected) = disc.split_once('=')?;
3892 let parts: Vec<&str> = spec.split('.').collect();
3893 if parts.len() != 3 {
3894 return None;
3895 }
3896 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
3897 Some(Self {
3898 tag: parts[0],
3899 element_idx: parts[1].parse().ok()?,
3900 component_idx: parts[2].parse().ok()?,
3901 expected_values: expected_raw.split('|').collect(),
3902 occurrence,
3903 })
3904 }
3905
3906 fn matches(&self, instance: &AssembledGroupInstance) -> bool {
3907 instance.segments.iter().any(|s| {
3908 s.tag.eq_ignore_ascii_case(self.tag)
3909 && s.elements
3910 .get(self.element_idx)
3911 .and_then(|e| e.get(self.component_idx))
3912 .map(|v| self.expected_values.iter().any(|ev| v == ev))
3913 .unwrap_or(false)
3914 })
3915 }
3916
3917 fn filter_instances<'b>(
3919 &self,
3920 instances: Vec<&'b AssembledGroupInstance>,
3921 ) -> Vec<&'b AssembledGroupInstance> {
3922 let matching: Vec<_> = instances
3923 .into_iter()
3924 .filter(|inst| self.matches(inst))
3925 .collect();
3926 if let Some(occ) = self.occurrence {
3927 matching.into_iter().nth(occ).into_iter().collect()
3928 } else {
3929 matching
3930 }
3931 }
3932}
3933
3934fn parse_discriminator_occurrence(expected: &str) -> (&str, Option<usize>) {
3940 if let Some(hash_pos) = expected.rfind('#') {
3941 if let Ok(occ) = expected[hash_pos + 1..].parse::<usize>() {
3942 return (&expected[..hash_pos], Some(occ));
3943 }
3944 }
3945 (expected, None)
3946}
3947
3948fn strip_rep_index(relative: &str) -> String {
3952 let (id, _) = parse_group_spec(relative);
3953 id.to_string()
3954}
3955
3956pub(crate) fn strip_all_rep_indices(relative: &str) -> String {
3961 relative
3962 .split('.')
3963 .map(|part| {
3964 let (id, _) = parse_group_spec(part);
3965 id
3966 })
3967 .collect::<Vec<_>>()
3968 .join(".")
3969}
3970
3971pub fn is_nested_child_of(child: &MappingDefinition, parent: &MappingDefinition) -> bool {
3981 if child.meta.parent_field.is_none() || child.meta.entity != parent.meta.entity {
3982 return false;
3983 }
3984 let child_sg = strip_all_rep_indices(&child.meta.source_group);
3985 let parent_sg = strip_all_rep_indices(&parent.meta.source_group);
3986 match child_sg.rsplit_once('.') {
3987 Some((head, _)) if head.eq_ignore_ascii_case(&parent_sg) => {}
3988 _ => return false,
3989 }
3990 let (Some(child_sp), Some(parent_sp)) = (
3991 child.meta.source_path.as_deref(),
3992 parent.meta.source_path.as_deref(),
3993 ) else {
3994 return true;
3995 };
3996 let Some((child_parent_sp, _)) = child_sp.rsplit_once('.') else {
3997 return false;
3998 };
3999 let child_parts: Vec<&str> = child_parent_sp.split('.').collect();
4000 let parent_parts: Vec<&str> = parent_sp.split('.').collect();
4001 child_parts.len() == parent_parts.len()
4002 && child_parts.iter().zip(&parent_parts).all(|(c, p)| {
4003 let (c_id, c_q) = parse_source_path_part(c);
4004 let (p_id, p_q) = parse_source_path_part(p);
4005 c_id.eq_ignore_ascii_case(p_id)
4006 && match (c_q, p_q) {
4007 (Some(cq), Some(pq)) => cq.eq_ignore_ascii_case(pq),
4008 _ => true,
4009 }
4010 })
4011}
4012
4013fn nested_parent_qualifier(child: &MappingDefinition) -> Option<&str> {
4016 let (parent_path, _) = child.meta.source_path.as_deref()?.rsplit_once('.')?;
4017 let last = parent_path.rsplit('.').next()?;
4018 parse_source_path_part(last).1
4019}
4020
4021fn nested_child_leaf(child: &MappingDefinition) -> (String, Option<&str>) {
4024 let leaf_group = strip_all_rep_indices(
4025 child
4026 .meta
4027 .source_group
4028 .rsplit('.')
4029 .next()
4030 .unwrap_or(&child.meta.source_group),
4031 );
4032 let leaf_qualifier = child
4033 .meta
4034 .source_path
4035 .as_deref()
4036 .and_then(|sp| sp.rsplit('.').next())
4037 .and_then(|part| parse_source_path_part(part).1);
4038 (leaf_group, leaf_qualifier)
4039}
4040
4041fn entry_qualifier_matches(instance: &AssembledGroupInstance, qualifier: &str) -> bool {
4044 segment_qualifier_matches(instance.segments.first(), qualifier)
4045}
4046
4047fn rebuilt_entry_qualifier_matches(
4052 instance: &AssembledGroupInstance,
4053 def: &MappingDefinition,
4054 qualifier: &str,
4055) -> bool {
4056 let entry_tag = def
4057 .meta
4058 .discriminator
4059 .as_deref()
4060 .and_then(|d| d.split('.').next())
4061 .filter(|tag| !tag.is_empty());
4062 let entry = match entry_tag {
4063 Some(tag) => instance
4064 .segments
4065 .iter()
4066 .find(|s| s.tag.eq_ignore_ascii_case(tag)),
4067 None => instance.segments.first(),
4068 };
4069 segment_qualifier_matches(entry, qualifier)
4070}
4071
4072fn segment_qualifier_matches(segment: Option<&AssembledSegment>, qualifier: &str) -> bool {
4073 segment
4074 .and_then(|seg| seg.elements.first())
4075 .and_then(|e| e.first())
4076 .is_some_and(|v| qualifier.split('_').any(|q| v.eq_ignore_ascii_case(q)))
4077}
4078
4079pub(crate) fn parse_tag_qualifier(tag_part: &str) -> (String, Option<&str>, usize) {
4086 if let Some(bracket_start) = tag_part.find('[') {
4087 let tag = tag_part[..bracket_start].to_uppercase();
4088 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
4089 if let Some(comma_pos) = inner.find(',') {
4090 let qualifier = &inner[..comma_pos];
4091 let index = inner[comma_pos + 1..].parse::<usize>().unwrap_or(0);
4092 if qualifier == "*" {
4094 (tag, None, index)
4095 } else {
4096 (tag, Some(qualifier), index)
4097 }
4098 } else {
4099 (tag, Some(inner), 0)
4100 }
4101 } else {
4102 (tag_part.to_uppercase(), None, 0)
4103 }
4104}
4105
4106pub fn deep_merge_insert(
4112 result: &mut serde_json::Map<String, serde_json::Value>,
4113 entity: &str,
4114 bo4e: serde_json::Value,
4115) {
4116 if let Some(existing) = result.get_mut(entity) {
4117 if let (Some(existing_arr), Some(new_arr)) =
4120 (existing.as_array().map(|a| a.len()), bo4e.as_array())
4121 {
4122 if existing_arr == new_arr.len() {
4123 let existing_arr = existing.as_array_mut().unwrap();
4124 for (existing_elem, new_elem) in existing_arr.iter_mut().zip(new_arr) {
4125 if let (Some(existing_map), Some(new_map)) =
4126 (existing_elem.as_object_mut(), new_elem.as_object())
4127 {
4128 for (k, v) in new_map {
4129 if let Some(existing_v) = existing_map.get_mut(k) {
4130 if let (Some(existing_inner), Some(new_inner)) =
4131 (existing_v.as_object_mut(), v.as_object())
4132 {
4133 for (ik, iv) in new_inner {
4134 existing_inner
4135 .entry(ik.clone())
4136 .or_insert_with(|| iv.clone());
4137 }
4138 }
4139 } else {
4140 existing_map.insert(k.clone(), v.clone());
4141 }
4142 }
4143 }
4144 }
4145 return;
4146 }
4147 }
4148 if let (Some(existing_map), serde_json::Value::Object(new_map)) =
4150 (existing.as_object_mut(), &bo4e)
4151 {
4152 for (k, v) in new_map {
4153 if let Some(existing_v) = existing_map.get_mut(k) {
4154 if let (Some(existing_inner), Some(new_inner)) =
4156 (existing_v.as_object_mut(), v.as_object())
4157 {
4158 for (ik, iv) in new_inner {
4159 existing_inner
4160 .entry(ik.clone())
4161 .or_insert_with(|| iv.clone());
4162 }
4163 }
4164 } else {
4166 existing_map.insert(k.clone(), v.clone());
4167 }
4168 }
4169 return;
4170 }
4171 }
4172 result.insert(entity.to_string(), bo4e);
4173}
4174
4175fn append_to_list_field(
4188 result: &mut serde_json::Map<String, serde_json::Value>,
4189 entity: &str,
4190 list_field: &str,
4191 bo4e: serde_json::Value,
4192) {
4193 let mut items = match bo4e {
4194 serde_json::Value::Array(a) => a,
4195 other => vec![other],
4196 };
4197 items.retain(|v| !v.as_object().is_some_and(|o| o.is_empty()));
4198 if items.is_empty() {
4199 return;
4200 }
4201 let entry = result
4202 .entry(entity.to_string())
4203 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
4204 let Some(obj) = entry.as_object_mut() else {
4208 return;
4209 };
4210 match obj.get_mut(list_field).and_then(|v| v.as_array_mut()) {
4211 Some(existing) => existing.extend(items),
4212 None => {
4213 obj.insert(list_field.to_string(), serde_json::Value::Array(items));
4214 }
4215 }
4216}
4217
4218fn is_map_keyed_object(value: &serde_json::Value) -> bool {
4229 let Some(obj) = value.as_object() else {
4230 return false;
4231 };
4232 if obj.is_empty() {
4233 return false;
4234 }
4235 obj.iter().all(|(k, v)| {
4237 k.len() <= 5
4238 && k.chars()
4239 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
4240 && v.is_object()
4241 })
4242}
4243
4244fn find_qualifier_companion_field(
4253 definitions: &[crate::definition::MappingDefinition],
4254 entity: &str,
4255) -> Option<String> {
4256 for def in definitions {
4257 if def.meta.entity != *entity || def.meta.parent_field.is_some() {
4258 continue;
4259 }
4260 let disc = def.meta.discriminator.as_deref()?;
4261 let (disc_path, _) = disc.split_once('=')?;
4262 let disc_path_lower = disc_path.to_lowercase();
4263
4264 for (path, mapping) in &def.fields {
4267 let cf_path = path.to_lowercase();
4268 let matches = cf_path == disc_path_lower || format!("{}.0", cf_path) == disc_path_lower;
4269 if matches {
4270 let target = match mapping {
4271 FieldMapping::Simple(t) => t.as_str(),
4272 FieldMapping::Structured(s) => s.target.as_str(),
4273 FieldMapping::Nested(_) => continue,
4274 };
4275 if !target.is_empty() {
4276 return Some(target.to_string());
4277 }
4278 }
4279 }
4280 }
4281 None
4282}
4283
4284fn extract_child_from_parent(
4293 entities: &serde_json::Value,
4294 definitions: &[MappingDefinition],
4295 child_def: &MappingDefinition,
4296) -> Option<serde_json::Value> {
4297 extract_child_from_parent_with_indices(entities, definitions, child_def).map(|(v, _)| v)
4298}
4299
4300fn extract_child_from_parent_with_indices(
4305 entities: &serde_json::Value,
4306 definitions: &[MappingDefinition],
4307 child_def: &MappingDefinition,
4308) -> Option<(serde_json::Value, Vec<usize>)> {
4309 let parts: Vec<&str> = child_def.meta.source_group.split('.').collect();
4310 if parts.len() < 2 {
4311 return None;
4312 }
4313 let parent_group = parts[0];
4314 let parent_def = definitions
4315 .iter()
4316 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_def.meta.entity)?;
4317 let parent_key = to_camel_case(&parent_def.meta.entity);
4318 let child_key = to_camel_case(&child_def.meta.entity);
4319 let parent_value = entities.get(&parent_key)?;
4320
4321 if let Some(parent_map) = parent_value.as_object() {
4323 if is_map_keyed_value(parent_map) {
4324 let mut children: Vec<serde_json::Value> = Vec::new();
4325 let mut indices: Vec<usize> = Vec::new();
4326 for (i, (_key, inner)) in parent_map.iter().enumerate() {
4327 if let Some(child) = inner.get(&child_key) {
4328 if !child.is_null() {
4329 children.push(child.clone());
4330 indices.push(i);
4331 }
4332 }
4333 }
4334 return match children.len() {
4335 0 => None,
4336 1 => Some((children.into_iter().next().unwrap(), indices)),
4337 _ => Some((serde_json::Value::Array(children), indices)),
4338 };
4339 }
4340 }
4341
4342 if let Some(parent_arr) = parent_value.as_array() {
4344 let mut children: Vec<serde_json::Value> = Vec::new();
4345 let mut indices: Vec<usize> = Vec::new();
4346 for (i, item) in parent_arr.iter().enumerate() {
4347 if let Some(child) = item.get(&child_key) {
4348 if !child.is_null() {
4349 children.push(child.clone());
4350 indices.push(i);
4351 }
4352 }
4353 }
4354 return match children.len() {
4355 0 => None,
4356 1 => Some((children.into_iter().next().unwrap(), indices)),
4357 _ => Some((serde_json::Value::Array(children), indices)),
4358 };
4359 }
4360
4361 let child = parent_value.get(&child_key)?;
4363 if child.is_null() {
4364 return None;
4365 }
4366 Some((child.clone(), vec![0]))
4367}
4368
4369fn nest_child_entities_in_result(
4375 result: &mut serde_json::Map<String, serde_json::Value>,
4376 definitions: &[MappingDefinition],
4377 nesting_info: &std::collections::HashMap<String, Vec<usize>>,
4378 transaction_group: Option<&str>,
4379) {
4380 let nesting_pairs = child_entity_nesting_pairs(definitions, transaction_group);
4381
4382 for (_parent_group, parent_entity, child_entity, child_source_path) in nesting_pairs {
4383 let parent_key = to_camel_case(&parent_entity);
4384 let child_key = to_camel_case(&child_entity);
4385
4386 let child_value = match result.remove(&child_key) {
4388 Some(v) => v,
4389 None => continue,
4390 };
4391
4392 let Some(parent_value) = result.get_mut(&parent_key) else {
4397 result.insert(child_key, child_value);
4399 continue;
4400 };
4401 if parent_value.is_array() {
4402 result.insert(child_key, child_value);
4403 continue;
4404 }
4405
4406 let distribution = child_source_path
4408 .as_deref()
4409 .and_then(|sp| nesting_info.get(sp));
4410
4411 let child_items: Vec<(usize, &serde_json::Value)> = match &child_value {
4413 serde_json::Value::Array(arr) => arr.iter().enumerate().collect(),
4414 other => vec![(0, other)],
4415 };
4416
4417 let insert_or_append = |obj: &mut serde_json::Map<String, serde_json::Value>,
4420 key: &str,
4421 val: &serde_json::Value| {
4422 match obj.get_mut(key) {
4423 Some(existing) => {
4424 if !existing.is_array() {
4426 let prev = existing.take();
4427 *existing = serde_json::Value::Array(vec![prev]);
4428 }
4429 if let Some(arr) = existing.as_array_mut() {
4430 arr.push(val.clone());
4431 }
4432 }
4433 None => {
4434 obj.insert(key.to_string(), val.clone());
4435 }
4436 }
4437 };
4438
4439 if let Some(parent_map) = parent_value.as_object_mut() {
4441 if is_map_keyed_value(parent_map) {
4442 let keys: Vec<String> = parent_map.keys().cloned().collect();
4444 for (i, child_item) in &child_items {
4445 let target_idx = distribution
4446 .and_then(|dist| dist.get(*i))
4447 .copied()
4448 .unwrap_or(0);
4449 if let Some(key) = keys.get(target_idx) {
4450 if let Some(inner) = parent_map.get_mut(key).and_then(|v| v.as_object_mut())
4451 {
4452 insert_or_append(inner, &child_key, child_item);
4453 }
4454 }
4455 }
4456 continue;
4457 }
4458 }
4459
4460 if let Some(parent_arr) = parent_value.as_array_mut() {
4462 for (i, child_item) in &child_items {
4463 let target_idx = distribution
4464 .and_then(|dist| dist.get(*i))
4465 .copied()
4466 .unwrap_or(0);
4467 if let Some(parent_obj) = parent_arr
4468 .get_mut(target_idx)
4469 .and_then(|v| v.as_object_mut())
4470 {
4471 insert_or_append(parent_obj, &child_key, child_item);
4472 }
4473 }
4474 continue;
4475 }
4476
4477 if let Some(parent_obj) = parent_value.as_object_mut() {
4479 for (_i, child_item) in &child_items {
4480 insert_or_append(parent_obj, &child_key, child_item);
4481 }
4482 continue;
4483 }
4484
4485 result.insert(child_key, child_value);
4487 }
4488}
4489
4490pub(crate) fn child_entity_nesting_pairs(
4500 definitions: &[MappingDefinition],
4501 transaction_group: Option<&str>,
4502) -> Vec<(String, String, String, Option<String>)> {
4503 let mut nesting_pairs: Vec<(String, String, String, Option<String>)> = Vec::new();
4506 for def in definitions {
4507 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
4508 if parts.len() < 2 || def.meta.parent_field.is_some() {
4509 continue;
4510 }
4511 let parent_group = parts[0];
4512 if transaction_group.is_some_and(|tx| tx == parent_group) {
4518 continue;
4519 }
4520 let child_entity = def.meta.entity.clone();
4521 let child_has_parent_level_def = definitions
4525 .iter()
4526 .any(|d| d.meta.source_group == parent_group && d.meta.entity == child_entity);
4527 if child_has_parent_level_def {
4528 continue;
4529 }
4530 let parent_entity = definitions
4532 .iter()
4533 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_entity)
4534 .map(|d| d.meta.entity.clone());
4535 if let Some(ref parent_entity) = parent_entity {
4536 let child_key_lc = to_camel_case(&child_entity);
4541 let parent_defs: Vec<_> = definitions
4542 .iter()
4543 .filter(|d| d.meta.entity == *parent_entity)
4544 .collect();
4545 let has_conflicting_field = parent_defs.iter().any(|pd| {
4546 pd.fields.values().any(|fm| {
4547 let target = match fm {
4548 crate::definition::FieldMapping::Simple(t) => t.as_str(),
4549 crate::definition::FieldMapping::Structured(s) => s.target.as_str(),
4550 crate::definition::FieldMapping::Nested(_) => "",
4551 };
4552 target.starts_with(&child_key_lc)
4553 && target.get(child_key_lc.len()..child_key_lc.len() + 1) == Some(".")
4554 })
4555 });
4556 if has_conflicting_field {
4557 continue;
4558 }
4559 if nesting_pairs
4561 .iter()
4562 .any(|(_, pe, ce, _)| *pe == *parent_entity && *ce == child_entity)
4563 {
4564 continue;
4565 }
4566 nesting_pairs.push((
4567 parent_group.to_string(),
4568 parent_entity.clone(),
4569 child_entity,
4570 def.meta.source_path.clone(),
4571 ));
4572 }
4573 }
4574
4575 nesting_pairs
4576}
4577
4578fn is_map_keyed_value(map: &serde_json::Map<String, serde_json::Value>) -> bool {
4580 if map.is_empty() {
4581 return false;
4582 }
4583 map.values().all(|v| v.is_object())
4584 && map.keys().all(|k| {
4585 k.len() <= 5
4586 || k.chars()
4587 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
4588 })
4589}
4590
4591struct CodeSite<'a> {
4594 target: &'a str,
4595 parent_field: Option<&'a str>,
4601 source_path: &'a str,
4602 seg_tag: String,
4603 path_qualifier: Option<String>,
4605 disc_qualifier: Option<String>,
4607 element_idx: usize,
4608 component_idx: usize,
4609 enum_map: Option<&'a std::collections::BTreeMap<String, String>>,
4610 also_target: Option<&'a str>,
4611 also_enum_map: Option<&'a std::collections::BTreeMap<String, String>>,
4612}
4613
4614pub(crate) fn to_camel_case(name: &str) -> String {
4615 let mut chars = name.chars();
4616 match chars.next() {
4617 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
4618 None => String::new(),
4619 }
4620}
4621
4622fn set_nested_value(map: &mut serde_json::Map<String, serde_json::Value>, path: &str, val: String) {
4625 set_nested_value_json(map, path, serde_json::Value::String(val));
4626}
4627
4628fn set_nested_value_json(
4630 map: &mut serde_json::Map<String, serde_json::Value>,
4631 path: &str,
4632 val: serde_json::Value,
4633) {
4634 if let Some((prefix, leaf)) = path.rsplit_once('.') {
4635 let mut current = map;
4636 for part in prefix.split('.') {
4637 let entry = current
4638 .entry(part.to_string())
4639 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
4640 current = entry.as_object_mut().expect("expected object in path");
4641 }
4642 current.insert(leaf.to_string(), val);
4643 } else {
4644 map.insert(path.to_string(), val);
4645 }
4646}
4647
4648#[derive(serde::Serialize, serde::Deserialize)]
4653pub struct VariantCache {
4654 pub message_defs: Vec<MappingDefinition>,
4656 pub transaction_defs: BTreeMap<String, Vec<MappingDefinition>>,
4658 pub combined_defs: BTreeMap<String, Vec<MappingDefinition>>,
4660 #[serde(default)]
4662 pub code_lookups: BTreeMap<String, crate::code_lookup::CodeLookup>,
4663 #[serde(default)]
4665 pub mig_schema: Option<mig_types::schema::mig::MigSchema>,
4666 #[serde(default)]
4668 pub segment_structure: Option<crate::segment_structure::SegmentStructure>,
4669 #[serde(skip)]
4674 pub code_lists: std::sync::Arc<crate::code_lists::CodeLists>,
4675 #[serde(default)]
4678 pub pid_segment_numbers: BTreeMap<String, Vec<String>>,
4679 #[serde(default)]
4682 pub pid_requirements: BTreeMap<String, crate::pid_requirements::PidRequirements>,
4683 #[serde(default)]
4688 pub pid_ahb_workflows: BTreeMap<String, ahb_types::AhbWorkflow>,
4689 #[serde(default)]
4693 pub tx_groups: BTreeMap<String, String>,
4694}
4695
4696impl VariantCache {
4697 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
4699 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
4700 path: path.display().to_string(),
4701 message: e.to_string(),
4702 })?;
4703 if let Some(parent) = path.parent() {
4704 std::fs::create_dir_all(parent)?;
4705 }
4706 std::fs::write(path, encoded)?;
4707 Ok(())
4708 }
4709
4710 pub fn load(path: &Path) -> Result<Self, MappingError> {
4712 let bytes = std::fs::read(path)?;
4713 let mut cache: Self =
4714 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
4715 path: path.display().to_string(),
4716 message: e.to_string(),
4717 })?;
4718 cache.code_lists = crate::code_lists::CodeLists::discover(path);
4719 Ok(cache)
4720 }
4721
4722 pub fn tx_group(&self, pid: &str) -> Option<&str> {
4726 self.tx_groups
4727 .get(&format!("pid_{pid}"))
4728 .map(|s| s.as_str())
4729 }
4730
4731 pub fn msg_engine(&self, pid: &str) -> MappingEngine {
4735 let mut eng = MappingEngine::from_definitions_with_code_lists(
4736 std::sync::Arc::clone(&self.code_lists),
4737 self.message_defs.clone(),
4738 )
4739 .with_pid(pid);
4740 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
4741 eng = eng.with_code_lookup(cl.clone());
4742 }
4743 eng
4744 }
4745
4746 pub fn tx_engine(&self, pid: &str) -> Option<MappingEngine> {
4750 self.transaction_defs
4751 .get(&format!("pid_{pid}"))
4752 .map(|defs| {
4753 let mut eng = MappingEngine::from_definitions_with_code_lists(
4754 std::sync::Arc::clone(&self.code_lists),
4755 defs.clone(),
4756 )
4757 .with_pid(pid);
4758 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
4759 eng = eng.with_code_lookup(cl.clone());
4760 }
4761 eng
4762 })
4763 }
4764
4765 pub fn filtered_mig(&self, pid: &str) -> Option<mig_types::schema::mig::MigSchema> {
4773 let mig = self.mig_schema.as_ref()?;
4774 let numbers = self
4775 .pid_segment_numbers
4776 .get(&format!("pid_{pid}"))
4777 .or_else(|| self.pid_segment_numbers.get("pid_"))?;
4778 let number_set: std::collections::HashSet<String> = numbers.iter().cloned().collect();
4779 Some(mig_assembly::pid_filter::filter_mig_for_pid(
4780 mig,
4781 &number_set,
4782 ))
4783 }
4784}
4785
4786#[derive(serde::Serialize, serde::Deserialize)]
4791pub struct DataBundle {
4792 pub format_version: String,
4793 pub bundle_version: u32,
4794 pub variants: BTreeMap<String, VariantCache>,
4795 #[serde(default)]
4800 pub bo4e_catalog: crate::bo4e_catalog::Bo4eCatalog,
4801
4802 #[serde(default, skip_serializing_if = "Option::is_none")]
4814 pub built_by: Option<String>,
4815 #[serde(default)]
4827 pub code_lists: crate::code_lists::CodeLists,
4828}
4829
4830impl DataBundle {
4831 pub const CURRENT_VERSION: u32 = 2;
4832
4833 pub const PRODUCING_VERSION: &'static str = env!("CARGO_PKG_VERSION");
4843
4844 pub fn variant(&self, name: &str) -> Option<&VariantCache> {
4845 self.variants.get(name)
4846 }
4847
4848 pub fn write_to<W: std::io::Write>(&self, writer: &mut W) -> Result<(), MappingError> {
4849 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
4850 path: "<stream>".to_string(),
4851 message: e.to_string(),
4852 })?;
4853 writer.write_all(&encoded).map_err(MappingError::Io)
4854 }
4855
4856 pub fn read_from<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
4857 let mut bytes = Vec::new();
4858 reader.read_to_end(&mut bytes).map_err(MappingError::Io)?;
4859 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
4860 path: "<stream>".to_string(),
4861 message: e.to_string(),
4862 })
4863 }
4864
4865 pub fn read_from_checked<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
4866 let mut bundle = Self::read_from(reader)?;
4867 let shared = std::sync::Arc::new(std::mem::take(&mut bundle.code_lists));
4870 for variant in bundle.variants.values_mut() {
4871 variant.code_lists = std::sync::Arc::clone(&shared);
4872 }
4873 bundle.code_lists = (*shared).clone();
4874 if bundle.bundle_version != Self::CURRENT_VERSION {
4875 return Err(MappingError::CacheRead {
4876 path: "<stream>".to_string(),
4877 message: format!(
4878 "Incompatible bundle version {}, expected version {}. \
4879 Run `edifact-data update` to fetch compatible bundles.",
4880 bundle.bundle_version,
4881 Self::CURRENT_VERSION
4882 ),
4883 });
4884 }
4885 Ok(bundle)
4886 }
4887
4888 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
4889 if let Some(parent) = path.parent() {
4890 std::fs::create_dir_all(parent)?;
4891 }
4892 let mut file = std::fs::File::create(path).map_err(MappingError::Io)?;
4893 self.write_to(&mut file)
4894 }
4895
4896 pub fn load(path: &Path) -> Result<Self, MappingError> {
4897 let mut file = std::fs::File::open(path).map_err(MappingError::Io)?;
4898 Self::read_from_checked(&mut file)
4899 }
4900}
4901
4902#[cfg(test)]
4903mod variant_cache_helper_tests {
4904 use super::*;
4905
4906 fn make_test_cache() -> VariantCache {
4907 let mut tx_groups = BTreeMap::new();
4908 tx_groups.insert("pid_55001".to_string(), "SG4".to_string());
4909 tx_groups.insert("pid_21007".to_string(), "SG14".to_string());
4910
4911 let mut transaction_defs = BTreeMap::new();
4912 transaction_defs.insert("pid_55001".to_string(), vec![]);
4913 transaction_defs.insert("pid_21007".to_string(), vec![]);
4914
4915 VariantCache {
4916 code_lists: Default::default(),
4917 message_defs: vec![],
4918 transaction_defs,
4919 combined_defs: BTreeMap::new(),
4920 code_lookups: BTreeMap::new(),
4921 mig_schema: None,
4922 segment_structure: None,
4923 pid_segment_numbers: BTreeMap::new(),
4924 pid_requirements: BTreeMap::new(),
4925 pid_ahb_workflows: BTreeMap::new(),
4926 tx_groups,
4927 }
4928 }
4929
4930 #[test]
4931 fn test_tx_group_returns_correct_group() {
4932 let vc = make_test_cache();
4933 assert_eq!(vc.tx_group("55001").unwrap(), "SG4");
4934 assert_eq!(vc.tx_group("21007").unwrap(), "SG14");
4935 }
4936
4937 #[test]
4938 fn test_tx_group_unknown_pid_returns_none() {
4939 let vc = make_test_cache();
4940 assert!(vc.tx_group("99999").is_none());
4941 }
4942
4943 #[test]
4944 fn test_msg_engine_returns_engine() {
4945 let vc = make_test_cache();
4946 let engine = vc.msg_engine("55001");
4947 assert_eq!(engine.definitions().len(), 0);
4948 }
4949
4950 #[test]
4951 fn test_tx_engine_returns_engine_for_known_pid() {
4952 let vc = make_test_cache();
4953 assert!(vc.tx_engine("55001").is_some());
4954 }
4955
4956 #[test]
4957 fn test_tx_engine_returns_none_for_unknown_pid() {
4958 let vc = make_test_cache();
4959 assert!(vc.tx_engine("99999").is_none());
4960 }
4961
4962 fn make_populated_cache() -> VariantCache {
4966 let pids: Vec<String> = (0..40).map(|i| format!("pid_{}", 55000 + i * 7)).collect();
4967 let schema: serde_json::Value = serde_json::from_str(include_str!(
4968 "../../mig-types/src/generated/fv2504/utilmd/pids/pid_55001_schema.json"
4969 ))
4970 .unwrap();
4971 let code_lookup = crate::code_lookup::CodeLookup::from_schema_value(&schema);
4972 let element_counts: serde_json::Map<String, serde_json::Value> = (0..40)
4973 .map(|i| (format!("T{i:02}"), serde_json::json!(i)))
4974 .collect();
4975 let segment_structure: SegmentStructure =
4976 serde_json::from_value(serde_json::json!({ "element_counts": element_counts }))
4977 .unwrap();
4978 let ubs: serde_json::Map<String, serde_json::Value> = (0..40)
4979 .map(|i| (format!("UB{i}"), serde_json::json!({ "Ref": i })))
4980 .collect();
4981 let workflow: ahb_types::AhbWorkflow = serde_json::from_value(serde_json::json!({
4982 "pruefidentifikator": "55001",
4983 "description": "",
4984 "communication_direction": null,
4985 "fields": [],
4986 "ub_definitions": ubs,
4987 }))
4988 .unwrap();
4989
4990 let mut vc = make_test_cache();
4991 vc.segment_structure = Some(segment_structure);
4992 for pid in &pids {
4993 vc.transaction_defs.insert(pid.clone(), vec![]);
4994 vc.combined_defs.insert(pid.clone(), vec![]);
4995 vc.code_lookups.insert(pid.clone(), code_lookup.clone());
4996 vc.pid_segment_numbers
4997 .insert(pid.clone(), vec!["00001".to_string()]);
4998 vc.pid_ahb_workflows.insert(pid.clone(), workflow.clone());
4999 vc.tx_groups.insert(pid.clone(), "SG4".to_string());
5000 }
5001 vc
5002 }
5003
5004 #[test]
5007 fn test_enrichment_uses_codes_of_the_path_qualifier_variant() {
5008 let comp = |sub: u64, id: &str, codes: Option<serde_json::Value>| match codes {
5009 Some(c) => serde_json::json!({"sub_index": sub, "id": id, "type": "code", "codes": c}),
5010 None => serde_json::json!({"sub_index": sub, "id": id, "type": "data"}),
5011 };
5012 let code = |v: &str, n: &str| serde_json::json!([{"value": v, "name": n}]);
5013 let seg = |tag: &str, composite: &str, comps: Vec<serde_json::Value>| serde_json::json!({"id": tag, "elements": [{"index": 0, "composite": composite, "components": comps}]});
5014 let schema = serde_json::json!({"fields": {"sg15": {"segments": [
5015 seg("RFF", "C506", vec![comp(0, "1153", Some(code("Z13", "PID"))), comp(1, "1154", Some(code("21037", "RD / NB-Bewertung")))]),
5016 seg("RFF", "C506", vec![comp(0, "1153", Some(code("ACW", "Referenz"))), comp(1, "1154", None)]),
5017 seg("CAV", "C889", vec![comp(0, "7111", Some(code("Z91", "Z91"))), comp(1, "7110", Some(code("A", "Alpha")))]),
5018 seg("CAV", "C889", vec![comp(0, "7111", Some(code("ZF0", "ZF0"))), comp(1, "7110", Some(code("C", "Gamma")))]),
5019 ]}}});
5020 let engine = MappingEngine::new_empty()
5021 .with_code_lookup(crate::code_lookup::CodeLookup::from_schema_value(&schema));
5022 let def = MappingDefinition::from_toml_str(
5023 r#"
5024[meta]
5025entity = "Status"
5026bo4e_type = "Status"
5027source_group = "SG15"
5028source_path = "sg15"
5029discriminator = "RFF.0.0=Z13"
5030
5031[fields]
5032"rff.0.1" = "pruefidentifikator"
5033"rff[ACW].0.1" = "referenz"
5034"cav[Z91].0.1" = "z91Wert"
5035"cav[ZF0].0.1" = "zf0Wert"
5036"#,
5037 )
5038 .unwrap();
5039 let segment = |tag: &str, elements: &[&[&str]]| OwnedSegment {
5040 id: tag.to_string(),
5041 elements: elements
5042 .iter()
5043 .map(|e| e.iter().map(|c| c.to_string()).collect())
5044 .collect(),
5045 segment_number: 1,
5046 };
5047 let json = engine.map_forward_from_segments(
5048 &[
5049 segment("RFF", &[&["Z13", "21037"]]),
5050 segment("RFF", &[&["ACW", "REF-1"]]),
5051 segment("CAV", &[&["Z91", "C"]]),
5052 segment("CAV", &[&["ZF0", "C"]]),
5053 ],
5054 &def,
5055 );
5056 assert_eq!(
5057 json["referenz"],
5058 serde_json::json!("REF-1"),
5059 "RFF+ACW d1154 is data; RFF+Z13's codes must not apply: {json}"
5060 );
5061 assert_eq!(json["pruefidentifikator"]["meaning"], "RD / NB-Bewertung");
5062 assert_eq!(
5063 json["z91Wert"]["meaning"],
5064 serde_json::Value::Null,
5065 "'C' is a CAV+ZF0 code, unknown to CAV+Z91: {json}"
5066 );
5067 assert_eq!(json["zf0Wert"]["meaning"], "Gamma");
5068 }
5069
5070 #[test]
5071 fn test_variant_cache_serialization_is_deterministic() {
5072 let reference = serde_json::to_vec(&make_populated_cache()).unwrap();
5073 for _ in 0..5 {
5074 let again = serde_json::to_vec(&make_populated_cache()).unwrap();
5075 assert!(
5076 reference == again,
5077 "VariantCache serialization must not depend on HashMap iteration order"
5078 );
5079 }
5080 }
5081
5082 #[test]
5083 fn test_variant_cache_serializes_map_keys_sorted() {
5084 use indexmap::IndexMap;
5085 use serde::de::IgnoredAny;
5086
5087 #[derive(serde::Deserialize)]
5088 struct ProbeWorkflow {
5089 ub_definitions: IndexMap<String, IgnoredAny>,
5090 }
5091 #[derive(serde::Deserialize)]
5092 struct ProbeStructure {
5093 element_counts: IndexMap<String, usize>,
5094 }
5095 #[derive(serde::Deserialize)]
5096 struct Probe {
5097 transaction_defs: IndexMap<String, IgnoredAny>,
5098 combined_defs: IndexMap<String, IgnoredAny>,
5099 code_lookups: IndexMap<String, IndexMap<String, IgnoredAny>>,
5100 segment_structure: ProbeStructure,
5101 pid_segment_numbers: IndexMap<String, IgnoredAny>,
5102 pid_requirements: IndexMap<String, IgnoredAny>,
5103 pid_ahb_workflows: IndexMap<String, ProbeWorkflow>,
5104 tx_groups: IndexMap<String, String>,
5105 }
5106 fn assert_sorted<'a>(what: &str, keys: impl Iterator<Item = &'a String>) {
5107 let keys: Vec<&String> = keys.collect();
5108 let mut sorted = keys.clone();
5109 sorted.sort();
5110 assert_eq!(keys, sorted, "{what} keys must serialize in sorted order");
5111 }
5112
5113 let json = serde_json::to_string(&make_populated_cache()).unwrap();
5114 let probe: Probe = serde_json::from_str(&json).unwrap();
5115 assert_sorted("transaction_defs", probe.transaction_defs.keys());
5116 assert_sorted("combined_defs", probe.combined_defs.keys());
5117 assert_sorted("code_lookups", probe.code_lookups.keys());
5118 let lookup = probe.code_lookups.values().next().unwrap();
5119 assert!(lookup.len() > 10, "fixture lookup should have many entries");
5120 assert_sorted("code_lookup entries", lookup.keys());
5121 assert_sorted(
5122 "segment_structure",
5123 probe.segment_structure.element_counts.keys(),
5124 );
5125 assert_sorted("pid_segment_numbers", probe.pid_segment_numbers.keys());
5126 assert_sorted("pid_requirements", probe.pid_requirements.keys());
5127 assert_sorted("pid_ahb_workflows", probe.pid_ahb_workflows.keys());
5128 let wf = probe.pid_ahb_workflows.values().next().unwrap();
5129 assert_sorted("ub_definitions", wf.ub_definitions.keys());
5130 assert_sorted("tx_groups", probe.tx_groups.keys());
5131 }
5132
5133 #[test]
5134 fn test_data_bundle_serializes_variants_sorted() {
5135 use indexmap::IndexMap;
5136 use serde::de::IgnoredAny;
5137
5138 #[derive(serde::Deserialize)]
5139 struct Probe {
5140 variants: IndexMap<String, IgnoredAny>,
5141 }
5142 let variants: BTreeMap<String, VariantCache> = (0..20)
5143 .map(|i| (format!("VARIANT_{i:02}"), make_test_cache()))
5144 .collect();
5145 let bundle = DataBundle {
5146 format_version: "FV2504".to_string(),
5147 bundle_version: DataBundle::CURRENT_VERSION,
5148 built_by: Some(DataBundle::PRODUCING_VERSION.to_string()),
5149 variants,
5150 bo4e_catalog: Default::default(),
5151 code_lists: Default::default(),
5152 };
5153 let mut bytes = Vec::new();
5154 bundle.write_to(&mut bytes).unwrap();
5155 let probe: Probe = serde_json::from_slice(&bytes).unwrap();
5156 let keys: Vec<&String> = probe.variants.keys().collect();
5157 let mut sorted = keys.clone();
5158 sorted.sort();
5159 assert_eq!(keys, sorted);
5160 }
5161}
5162
5163#[cfg(test)]
5164mod tests {
5165 use super::*;
5166 use crate::definition::{MappingDefinition, MappingMeta, StructuredFieldMapping};
5167 use indexmap::IndexMap;
5168
5169 fn make_def(fields: IndexMap<String, FieldMapping>) -> MappingDefinition {
5170 MappingDefinition {
5171 meta: MappingMeta {
5172 entity: "Test".to_string(),
5173 bo4e_type: "Test".to_string(),
5174 source_group: "SG4".to_string(),
5175 source_path: None,
5176 discriminator: None,
5177 repeat_on_tag: None,
5178 parent_field: None,
5179 target_list: None,
5180 order: None,
5181 },
5182 fields,
5183 complex_handlers: None,
5184 }
5185 }
5186
5187 #[test]
5188 fn test_map_interchange_single_transaction_backward_compat() {
5189 use mig_assembly::assembler::*;
5190
5191 let tree = AssembledTree {
5193 segments: vec![
5194 AssembledSegment {
5195 tag: "UNH".to_string(),
5196 elements: vec![vec!["001".to_string()]],
5197 mig_number: None,
5198 segment_number: None,
5199 },
5200 AssembledSegment {
5201 tag: "BGM".to_string(),
5202 elements: vec![vec!["E01".to_string()], vec!["DOC001".to_string()]],
5203 mig_number: None,
5204 segment_number: None,
5205 },
5206 ],
5207 groups: vec![
5208 AssembledGroup {
5209 group_id: "SG2".to_string(),
5210 repetitions: vec![AssembledGroupInstance {
5211 segments: vec![AssembledSegment {
5212 tag: "NAD".to_string(),
5213 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
5214 mig_number: None,
5215 segment_number: None,
5216 }],
5217 child_groups: vec![],
5218 entry_mig_number: None,
5219 variant_mig_numbers: vec![],
5220 skipped_segments: vec![],
5221 skipped_positions: Vec::new(),
5222 }],
5223 },
5224 AssembledGroup {
5225 group_id: "SG4".to_string(),
5226 repetitions: vec![AssembledGroupInstance {
5227 segments: vec![AssembledSegment {
5228 tag: "IDE".to_string(),
5229 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5230 mig_number: None,
5231 segment_number: None,
5232 }],
5233 child_groups: vec![AssembledGroup {
5234 group_id: "SG5".to_string(),
5235 repetitions: vec![AssembledGroupInstance {
5236 segments: vec![AssembledSegment {
5237 tag: "LOC".to_string(),
5238 elements: vec![
5239 vec!["Z16".to_string()],
5240 vec!["DE000111222333".to_string()],
5241 ],
5242 mig_number: None,
5243 segment_number: None,
5244 }],
5245 child_groups: vec![],
5246 entry_mig_number: None,
5247 variant_mig_numbers: vec![],
5248 skipped_segments: vec![],
5249 skipped_positions: Vec::new(),
5250 }],
5251 }],
5252 entry_mig_number: None,
5253 variant_mig_numbers: vec![],
5254 skipped_segments: vec![],
5255 skipped_positions: Vec::new(),
5256 }],
5257 },
5258 ],
5259 post_group_start: 2,
5260 inter_group_segments: std::collections::BTreeMap::new(),
5261 };
5262
5263 let msg_engine = MappingEngine::from_definitions(vec![]);
5265
5266 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5268 tx_fields.insert(
5269 "ide.1".to_string(),
5270 FieldMapping::Simple("vorgangId".to_string()),
5271 );
5272 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5273 malo_fields.insert(
5274 "loc.1".to_string(),
5275 FieldMapping::Simple("marktlokationsId".to_string()),
5276 );
5277
5278 let tx_engine = MappingEngine::from_definitions(vec![
5279 MappingDefinition {
5280 meta: MappingMeta {
5281 entity: "Prozessdaten".to_string(),
5282 bo4e_type: "Prozessdaten".to_string(),
5283 source_group: "SG4".to_string(),
5284 source_path: None,
5285 discriminator: None,
5286 repeat_on_tag: None,
5287 parent_field: None,
5288 target_list: None,
5289 order: None,
5290 },
5291 fields: tx_fields,
5292 complex_handlers: None,
5293 },
5294 MappingDefinition {
5295 meta: MappingMeta {
5296 entity: "Marktlokation".to_string(),
5297 bo4e_type: "Marktlokation".to_string(),
5298 source_group: "SG4.SG5".to_string(),
5299 source_path: None,
5300 discriminator: None,
5301 repeat_on_tag: None,
5302 parent_field: None,
5303 target_list: None,
5304 order: None,
5305 },
5306 fields: malo_fields,
5307 complex_handlers: None,
5308 },
5309 ]);
5310
5311 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
5312
5313 assert_eq!(result.transaktionen.len(), 1);
5314 assert_eq!(
5315 result.transaktionen[0].transaktionsdaten["vorgangId"]
5316 .as_str()
5317 .unwrap(),
5318 "TX001"
5319 );
5320 assert_eq!(
5323 result.transaktionen[0].stammdaten["marktlokation"]["marktlokationsId"]
5324 .as_str()
5325 .unwrap(),
5326 "DE000111222333"
5327 );
5328 }
5329
5330 #[test]
5331 fn test_map_reverse_pads_intermediate_empty_elements() {
5332 let mut fields = IndexMap::new();
5334 fields.insert(
5335 "nad.0".to_string(),
5336 FieldMapping::Structured(StructuredFieldMapping {
5337 target: String::new(),
5338 transform: None,
5339 when: None,
5340 default: Some("Z09".to_string()),
5341 enum_map: None,
5342 code_list: None,
5343 also_code_list: None,
5344 when_filled: None,
5345 also_target: None,
5346 also_enum_map: None,
5347 }),
5348 );
5349 fields.insert(
5350 "nad.3.0".to_string(),
5351 FieldMapping::Simple("name".to_string()),
5352 );
5353 fields.insert(
5354 "nad.3.1".to_string(),
5355 FieldMapping::Simple("vorname".to_string()),
5356 );
5357
5358 let def = make_def(fields);
5359 let engine = MappingEngine::from_definitions(vec![]);
5360
5361 let bo4e = serde_json::json!({
5362 "name": "Muster",
5363 "vorname": "Max"
5364 });
5365
5366 let instance = engine.map_reverse(&bo4e, &def);
5367 assert_eq!(instance.segments.len(), 1);
5368
5369 let nad = &instance.segments[0];
5370 assert_eq!(nad.tag, "NAD");
5371 assert_eq!(nad.elements.len(), 4);
5372 assert_eq!(nad.elements[0], vec!["Z09"]);
5373 assert_eq!(nad.elements[1], vec![""]);
5375 assert_eq!(nad.elements[2], vec![""]);
5376 assert_eq!(nad.elements[3][0], "Muster");
5377 assert_eq!(nad.elements[3][1], "Max");
5378 }
5379
5380 #[test]
5381 fn test_map_reverse_no_padding_when_contiguous() {
5382 let mut fields = IndexMap::new();
5384 fields.insert(
5385 "dtm.0.0".to_string(),
5386 FieldMapping::Structured(StructuredFieldMapping {
5387 target: String::new(),
5388 transform: None,
5389 when: None,
5390 default: Some("92".to_string()),
5391 enum_map: None,
5392 code_list: None,
5393 also_code_list: None,
5394 when_filled: None,
5395 also_target: None,
5396 also_enum_map: None,
5397 }),
5398 );
5399 fields.insert(
5400 "dtm.0.1".to_string(),
5401 FieldMapping::Simple("value".to_string()),
5402 );
5403 fields.insert(
5404 "dtm.0.2".to_string(),
5405 FieldMapping::Structured(StructuredFieldMapping {
5406 target: String::new(),
5407 transform: None,
5408 when: None,
5409 default: Some("303".to_string()),
5410 enum_map: None,
5411 code_list: None,
5412 also_code_list: None,
5413 when_filled: None,
5414 also_target: None,
5415 also_enum_map: None,
5416 }),
5417 );
5418
5419 let def = make_def(fields);
5420 let engine = MappingEngine::from_definitions(vec![]);
5421
5422 let bo4e = serde_json::json!({ "value": "20250531" });
5423
5424 let instance = engine.map_reverse(&bo4e, &def);
5425 let dtm = &instance.segments[0];
5426 assert_eq!(dtm.elements.len(), 1);
5428 assert_eq!(dtm.elements[0], vec!["92", "20250531", "303"]);
5429 }
5430
5431 #[test]
5432 fn test_map_message_level_extracts_sg2_only() {
5433 use mig_assembly::assembler::*;
5434
5435 let tree = AssembledTree {
5437 segments: vec![
5438 AssembledSegment {
5439 tag: "UNH".to_string(),
5440 elements: vec![vec!["001".to_string()]],
5441 mig_number: None,
5442 segment_number: None,
5443 },
5444 AssembledSegment {
5445 tag: "BGM".to_string(),
5446 elements: vec![vec!["E01".to_string()]],
5447 mig_number: None,
5448 segment_number: None,
5449 },
5450 ],
5451 groups: vec![
5452 AssembledGroup {
5453 group_id: "SG2".to_string(),
5454 repetitions: vec![AssembledGroupInstance {
5455 segments: vec![AssembledSegment {
5456 tag: "NAD".to_string(),
5457 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
5458 mig_number: None,
5459 segment_number: None,
5460 }],
5461 child_groups: vec![],
5462 entry_mig_number: None,
5463 variant_mig_numbers: vec![],
5464 skipped_segments: vec![],
5465 skipped_positions: Vec::new(),
5466 }],
5467 },
5468 AssembledGroup {
5469 group_id: "SG4".to_string(),
5470 repetitions: vec![AssembledGroupInstance {
5471 segments: vec![AssembledSegment {
5472 tag: "IDE".to_string(),
5473 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5474 mig_number: None,
5475 segment_number: None,
5476 }],
5477 child_groups: vec![],
5478 entry_mig_number: None,
5479 variant_mig_numbers: vec![],
5480 skipped_segments: vec![],
5481 skipped_positions: Vec::new(),
5482 }],
5483 },
5484 ],
5485 post_group_start: 2,
5486 inter_group_segments: std::collections::BTreeMap::new(),
5487 };
5488
5489 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5491 msg_fields.insert(
5492 "nad.0".to_string(),
5493 FieldMapping::Simple("marktrolle".to_string()),
5494 );
5495 msg_fields.insert(
5496 "nad.1".to_string(),
5497 FieldMapping::Simple("rollencodenummer".to_string()),
5498 );
5499 let msg_def = MappingDefinition {
5500 meta: MappingMeta {
5501 entity: "Marktteilnehmer".to_string(),
5502 bo4e_type: "Marktteilnehmer".to_string(),
5503 source_group: "SG2".to_string(),
5504 source_path: None,
5505 discriminator: None,
5506 repeat_on_tag: None,
5507 parent_field: None,
5508 target_list: None,
5509 order: None,
5510 },
5511 fields: msg_fields,
5512 complex_handlers: None,
5513 };
5514
5515 let engine = MappingEngine::from_definitions(vec![msg_def.clone()]);
5516 let result = engine.map_all_forward(&tree);
5517
5518 assert!(result.get("marktteilnehmer").is_some());
5520 let mt = &result["marktteilnehmer"];
5521 assert_eq!(mt["marktrolle"].as_str().unwrap(), "MS");
5522 assert_eq!(mt["rollencodenummer"].as_str().unwrap(), "9900123");
5523 }
5524
5525 #[test]
5526 fn test_map_transaction_scoped_to_sg4_instance() {
5527 use mig_assembly::assembler::*;
5528
5529 let tree = AssembledTree {
5531 segments: vec![
5532 AssembledSegment {
5533 tag: "UNH".to_string(),
5534 elements: vec![vec!["001".to_string()]],
5535 mig_number: None,
5536 segment_number: None,
5537 },
5538 AssembledSegment {
5539 tag: "BGM".to_string(),
5540 elements: vec![vec!["E01".to_string()]],
5541 mig_number: None,
5542 segment_number: None,
5543 },
5544 ],
5545 groups: vec![AssembledGroup {
5546 group_id: "SG4".to_string(),
5547 repetitions: vec![AssembledGroupInstance {
5548 segments: vec![AssembledSegment {
5549 tag: "IDE".to_string(),
5550 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5551 mig_number: None,
5552 segment_number: None,
5553 }],
5554 child_groups: vec![AssembledGroup {
5555 group_id: "SG5".to_string(),
5556 repetitions: vec![AssembledGroupInstance {
5557 segments: vec![AssembledSegment {
5558 tag: "LOC".to_string(),
5559 elements: vec![
5560 vec!["Z16".to_string()],
5561 vec!["DE000111222333".to_string()],
5562 ],
5563 mig_number: None,
5564 segment_number: None,
5565 }],
5566 child_groups: vec![],
5567 entry_mig_number: None,
5568 variant_mig_numbers: vec![],
5569 skipped_segments: vec![],
5570 skipped_positions: Vec::new(),
5571 }],
5572 }],
5573 entry_mig_number: None,
5574 variant_mig_numbers: vec![],
5575 skipped_segments: vec![],
5576 skipped_positions: Vec::new(),
5577 }],
5578 }],
5579 post_group_start: 2,
5580 inter_group_segments: std::collections::BTreeMap::new(),
5581 };
5582
5583 let mut proz_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5585 proz_fields.insert(
5586 "ide.1".to_string(),
5587 FieldMapping::Simple("vorgangId".to_string()),
5588 );
5589 let proz_def = MappingDefinition {
5590 meta: MappingMeta {
5591 entity: "Prozessdaten".to_string(),
5592 bo4e_type: "Prozessdaten".to_string(),
5593 source_group: "".to_string(), source_path: None,
5595 discriminator: None,
5596 repeat_on_tag: None,
5597 parent_field: None,
5598 target_list: None,
5599 order: None,
5600 },
5601 fields: proz_fields,
5602 complex_handlers: None,
5603 };
5604
5605 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5606 malo_fields.insert(
5607 "loc.1".to_string(),
5608 FieldMapping::Simple("marktlokationsId".to_string()),
5609 );
5610 let malo_def = MappingDefinition {
5611 meta: MappingMeta {
5612 entity: "Marktlokation".to_string(),
5613 bo4e_type: "Marktlokation".to_string(),
5614 source_group: "SG5".to_string(), source_path: None,
5616 discriminator: None,
5617 repeat_on_tag: None,
5618 parent_field: None,
5619 target_list: None,
5620 order: None,
5621 },
5622 fields: malo_fields,
5623 complex_handlers: None,
5624 };
5625
5626 let tx_engine = MappingEngine::from_definitions(vec![proz_def, malo_def]);
5627
5628 let sg4 = &tree.groups[0]; let sg4_instance = &sg4.repetitions[0];
5631 let sub_tree = sg4_instance.as_assembled_tree();
5632
5633 let result = tx_engine.map_all_forward(&sub_tree);
5634
5635 assert_eq!(
5637 result["prozessdaten"]["vorgangId"].as_str().unwrap(),
5638 "TX001"
5639 );
5640
5641 assert_eq!(
5643 result["marktlokation"]["marktlokationsId"]
5644 .as_str()
5645 .unwrap(),
5646 "DE000111222333"
5647 );
5648 }
5649
5650 #[test]
5651 fn test_map_interchange_produces_full_hierarchy() {
5652 use mig_assembly::assembler::*;
5653
5654 let tree = AssembledTree {
5656 segments: vec![
5657 AssembledSegment {
5658 tag: "UNH".to_string(),
5659 elements: vec![vec!["001".to_string()]],
5660 mig_number: None,
5661 segment_number: None,
5662 },
5663 AssembledSegment {
5664 tag: "BGM".to_string(),
5665 elements: vec![vec!["E01".to_string()]],
5666 mig_number: None,
5667 segment_number: None,
5668 },
5669 ],
5670 groups: vec![
5671 AssembledGroup {
5672 group_id: "SG2".to_string(),
5673 repetitions: vec![AssembledGroupInstance {
5674 segments: vec![AssembledSegment {
5675 tag: "NAD".to_string(),
5676 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
5677 mig_number: None,
5678 segment_number: None,
5679 }],
5680 child_groups: vec![],
5681 entry_mig_number: None,
5682 variant_mig_numbers: vec![],
5683 skipped_segments: vec![],
5684 skipped_positions: Vec::new(),
5685 }],
5686 },
5687 AssembledGroup {
5688 group_id: "SG4".to_string(),
5689 repetitions: vec![
5690 AssembledGroupInstance {
5691 segments: vec![AssembledSegment {
5692 tag: "IDE".to_string(),
5693 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5694 mig_number: None,
5695 segment_number: None,
5696 }],
5697 child_groups: vec![],
5698 entry_mig_number: None,
5699 variant_mig_numbers: vec![],
5700 skipped_segments: vec![],
5701 skipped_positions: Vec::new(),
5702 },
5703 AssembledGroupInstance {
5704 segments: vec![AssembledSegment {
5705 tag: "IDE".to_string(),
5706 elements: vec![vec!["24".to_string()], vec!["TX002".to_string()]],
5707 mig_number: None,
5708 segment_number: None,
5709 }],
5710 child_groups: vec![],
5711 entry_mig_number: None,
5712 variant_mig_numbers: vec![],
5713 skipped_segments: vec![],
5714 skipped_positions: Vec::new(),
5715 },
5716 ],
5717 },
5718 ],
5719 post_group_start: 2,
5720 inter_group_segments: std::collections::BTreeMap::new(),
5721 };
5722
5723 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5725 msg_fields.insert(
5726 "nad.0".to_string(),
5727 FieldMapping::Simple("marktrolle".to_string()),
5728 );
5729 let msg_defs = vec![MappingDefinition {
5730 meta: MappingMeta {
5731 entity: "Marktteilnehmer".to_string(),
5732 bo4e_type: "Marktteilnehmer".to_string(),
5733 source_group: "SG2".to_string(),
5734 source_path: None,
5735 discriminator: None,
5736 repeat_on_tag: None,
5737 parent_field: None,
5738 target_list: None,
5739 order: None,
5740 },
5741 fields: msg_fields,
5742 complex_handlers: None,
5743 }];
5744
5745 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5747 tx_fields.insert(
5748 "ide.1".to_string(),
5749 FieldMapping::Simple("vorgangId".to_string()),
5750 );
5751 let tx_defs = vec![MappingDefinition {
5752 meta: MappingMeta {
5753 entity: "Prozessdaten".to_string(),
5754 bo4e_type: "Prozessdaten".to_string(),
5755 source_group: "SG4".to_string(),
5756 source_path: None,
5757 discriminator: None,
5758 repeat_on_tag: None,
5759 parent_field: None,
5760 target_list: None,
5761 order: None,
5762 },
5763 fields: tx_fields,
5764 complex_handlers: None,
5765 }];
5766
5767 let msg_engine = MappingEngine::from_definitions(msg_defs);
5768 let tx_engine = MappingEngine::from_definitions(tx_defs);
5769
5770 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
5771
5772 assert!(result.stammdaten["marktteilnehmer"].is_object());
5774 assert_eq!(
5775 result.stammdaten["marktteilnehmer"]["marktrolle"]
5776 .as_str()
5777 .unwrap(),
5778 "MS"
5779 );
5780
5781 assert_eq!(result.transaktionen.len(), 2);
5783 assert_eq!(
5784 result.transaktionen[0].transaktionsdaten["vorgangId"]
5785 .as_str()
5786 .unwrap(),
5787 "TX001"
5788 );
5789 assert_eq!(
5790 result.transaktionen[1].transaktionsdaten["vorgangId"]
5791 .as_str()
5792 .unwrap(),
5793 "TX002"
5794 );
5795 }
5796
5797 #[test]
5798 fn test_map_reverse_with_segment_structure_pads_trailing() {
5799 let mut fields = IndexMap::new();
5801 fields.insert(
5802 "sts.0".to_string(),
5803 FieldMapping::Structured(StructuredFieldMapping {
5804 target: String::new(),
5805 transform: None,
5806 when: None,
5807 default: Some("7".to_string()),
5808 enum_map: None,
5809 code_list: None,
5810 also_code_list: None,
5811 when_filled: None,
5812 also_target: None,
5813 also_enum_map: None,
5814 }),
5815 );
5816 fields.insert(
5817 "sts.2".to_string(),
5818 FieldMapping::Simple("grund".to_string()),
5819 );
5820
5821 let def = make_def(fields);
5822
5823 let mut counts = std::collections::BTreeMap::new();
5825 counts.insert("STS".to_string(), 5usize);
5826 let ss = SegmentStructure {
5827 element_counts: counts,
5828 };
5829
5830 let engine = MappingEngine::from_definitions(vec![]).with_segment_structure(ss);
5831
5832 let bo4e = serde_json::json!({ "grund": "E01" });
5833
5834 let instance = engine.map_reverse(&bo4e, &def);
5835 let sts = &instance.segments[0];
5836 assert_eq!(sts.elements.len(), 5);
5839 assert_eq!(sts.elements[0], vec!["7"]);
5840 assert_eq!(sts.elements[1], vec![""]);
5841 assert_eq!(sts.elements[2], vec!["E01"]);
5842 assert_eq!(sts.elements[3], vec![""]);
5843 assert_eq!(sts.elements[4], vec![""]);
5844 }
5845
5846 #[test]
5847 fn test_resolve_child_relative_with_source_path() {
5848 let mut map: std::collections::HashMap<String, Vec<usize>> =
5849 std::collections::HashMap::new();
5850 map.insert("sg4.sg8_ze1".to_string(), vec![6]);
5851 map.insert("sg4.sg8_z98".to_string(), vec![0]);
5852
5853 assert_eq!(
5855 resolve_child_relative("SG8.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5856 "SG8:6.SG10"
5857 );
5858
5859 assert_eq!(
5861 resolve_child_relative("SG8:3.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5862 "SG8:3.SG10"
5863 );
5864
5865 assert_eq!(
5867 resolve_child_relative("SG8.SG10", Some("sg4.sg8_unknown.sg10"), &map, 0),
5868 "SG8.SG10"
5869 );
5870
5871 assert_eq!(
5873 resolve_child_relative("SG8.SG10", None, &map, 0),
5874 "SG8.SG10"
5875 );
5876
5877 assert_eq!(
5879 resolve_child_relative("SG8.SG9", Some("sg4.sg8_z98.sg9"), &map, 0),
5880 "SG8:0.SG9"
5881 );
5882
5883 map.insert("sg4.sg8_zf3".to_string(), vec![3, 4]);
5885 assert_eq!(
5886 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 0),
5887 "SG8:3.SG10"
5888 );
5889 assert_eq!(
5890 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 1),
5891 "SG8:4.SG10"
5892 );
5893 }
5894
5895 #[test]
5896 fn test_place_in_groups_returns_rep_index() {
5897 let mut groups: Vec<AssembledGroup> = Vec::new();
5898
5899 let instance = AssembledGroupInstance {
5901 segments: vec![],
5902 child_groups: vec![],
5903 entry_mig_number: None,
5904 variant_mig_numbers: vec![],
5905 skipped_segments: vec![],
5906 skipped_positions: Vec::new(),
5907 };
5908 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 0);
5909
5910 let instance = AssembledGroupInstance {
5912 segments: vec![],
5913 child_groups: vec![],
5914 entry_mig_number: None,
5915 variant_mig_numbers: vec![],
5916 skipped_segments: vec![],
5917 skipped_positions: Vec::new(),
5918 };
5919 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 1);
5920
5921 let instance = AssembledGroupInstance {
5923 segments: vec![],
5924 child_groups: vec![],
5925 entry_mig_number: None,
5926 variant_mig_numbers: vec![],
5927 skipped_segments: vec![],
5928 skipped_positions: Vec::new(),
5929 };
5930 assert_eq!(place_in_groups(&mut groups, "SG8:5", instance), 5);
5931 }
5932
5933 #[test]
5934 fn test_resolve_by_source_path() {
5935 use mig_assembly::assembler::*;
5936
5937 let tree = AssembledTree {
5939 segments: vec![],
5940 groups: vec![AssembledGroup {
5941 group_id: "SG4".to_string(),
5942 repetitions: vec![AssembledGroupInstance {
5943 segments: vec![],
5944 child_groups: vec![AssembledGroup {
5945 group_id: "SG8".to_string(),
5946 repetitions: vec![
5947 AssembledGroupInstance {
5948 segments: vec![AssembledSegment {
5949 tag: "SEQ".to_string(),
5950 elements: vec![vec!["Z98".to_string()]],
5951 mig_number: None,
5952 segment_number: None,
5953 }],
5954 child_groups: vec![AssembledGroup {
5955 group_id: "SG10".to_string(),
5956 repetitions: vec![AssembledGroupInstance {
5957 segments: vec![AssembledSegment {
5958 tag: "CCI".to_string(),
5959 elements: vec![vec![], vec![], vec!["ZB3".to_string()]],
5960 mig_number: None,
5961 segment_number: None,
5962 }],
5963 child_groups: vec![],
5964 entry_mig_number: None,
5965 variant_mig_numbers: vec![],
5966 skipped_segments: vec![],
5967 skipped_positions: Vec::new(),
5968 }],
5969 }],
5970 entry_mig_number: None,
5971 variant_mig_numbers: vec![],
5972 skipped_segments: vec![],
5973 skipped_positions: Vec::new(),
5974 },
5975 AssembledGroupInstance {
5976 segments: vec![AssembledSegment {
5977 tag: "SEQ".to_string(),
5978 elements: vec![vec!["ZD7".to_string()]],
5979 mig_number: None,
5980 segment_number: None,
5981 }],
5982 child_groups: vec![AssembledGroup {
5983 group_id: "SG10".to_string(),
5984 repetitions: vec![AssembledGroupInstance {
5985 segments: vec![AssembledSegment {
5986 tag: "CCI".to_string(),
5987 elements: vec![vec![], vec![], vec!["ZE6".to_string()]],
5988 mig_number: None,
5989 segment_number: None,
5990 }],
5991 child_groups: vec![],
5992 entry_mig_number: None,
5993 variant_mig_numbers: vec![],
5994 skipped_segments: vec![],
5995 skipped_positions: Vec::new(),
5996 }],
5997 }],
5998 entry_mig_number: None,
5999 variant_mig_numbers: vec![],
6000 skipped_segments: vec![],
6001 skipped_positions: Vec::new(),
6002 },
6003 ],
6004 }],
6005 entry_mig_number: None,
6006 variant_mig_numbers: vec![],
6007 skipped_segments: vec![],
6008 skipped_positions: Vec::new(),
6009 }],
6010 }],
6011 post_group_start: 0,
6012 inter_group_segments: std::collections::BTreeMap::new(),
6013 };
6014
6015 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_z98.sg10");
6017 assert!(inst.is_some());
6018 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
6019
6020 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zd7.sg10");
6022 assert!(inst.is_some());
6023 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZE6");
6024
6025 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zzz.sg10");
6027 assert!(inst.is_none());
6028
6029 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8.sg10");
6031 assert!(inst.is_some());
6032 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
6033 }
6034
6035 #[test]
6036 fn test_parse_source_path_part() {
6037 assert_eq!(parse_source_path_part("sg4"), ("sg4", None));
6038 assert_eq!(parse_source_path_part("sg8_z98"), ("sg8", Some("z98")));
6039 assert_eq!(parse_source_path_part("sg10"), ("sg10", None));
6040 assert_eq!(parse_source_path_part("sg12_z04"), ("sg12", Some("z04")));
6041 }
6042
6043 #[test]
6044 fn test_has_source_path_qualifiers() {
6045 assert!(has_source_path_qualifiers("sg4.sg8_z98.sg10"));
6046 assert!(has_source_path_qualifiers("sg4.sg8_ze1.sg9"));
6047 assert!(!has_source_path_qualifiers("sg4.sg6"));
6048 assert!(!has_source_path_qualifiers("sg4.sg8.sg10"));
6049 }
6050
6051 #[test]
6052 fn test_extract_all_from_instance_collects_all_qualifier_matches() {
6053 use mig_assembly::assembler::*;
6054
6055 let instance = AssembledGroupInstance {
6057 segments: vec![
6058 AssembledSegment {
6059 tag: "SEQ".to_string(),
6060 elements: vec![vec!["ZD6".to_string()]],
6061 mig_number: None,
6062 segment_number: None,
6063 },
6064 AssembledSegment {
6065 tag: "RFF".to_string(),
6066 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
6067 mig_number: None,
6068 segment_number: None,
6069 },
6070 AssembledSegment {
6071 tag: "RFF".to_string(),
6072 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
6073 mig_number: None,
6074 segment_number: None,
6075 },
6076 AssembledSegment {
6077 tag: "RFF".to_string(),
6078 elements: vec![vec!["Z34".to_string(), "REF_C".to_string()]],
6079 mig_number: None,
6080 segment_number: None,
6081 },
6082 AssembledSegment {
6083 tag: "RFF".to_string(),
6084 elements: vec![vec!["Z35".to_string(), "OTHER".to_string()]],
6085 mig_number: None,
6086 segment_number: None,
6087 },
6088 ],
6089 child_groups: vec![],
6090 entry_mig_number: None,
6091 variant_mig_numbers: vec![],
6092 skipped_segments: vec![],
6093 skipped_positions: Vec::new(),
6094 };
6095
6096 let all = MappingEngine::extract_all_from_instance(&instance, "rff[Z34,*].0.1");
6098 assert_eq!(all, vec!["REF_A", "REF_B", "REF_C"]);
6099
6100 let single = MappingEngine::extract_from_instance(&instance, "rff[Z34].0.1");
6102 assert_eq!(single, Some("REF_A".to_string()));
6103
6104 let second = MappingEngine::extract_from_instance(&instance, "rff[Z34,1].0.1");
6105 assert_eq!(second, Some("REF_B".to_string()));
6106 }
6107}