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 raw_codes: bool,
48}
49
50impl MappingEngine {
51 pub fn new_empty() -> Self {
53 Self {
54 definitions: Vec::new(),
55 segment_structure: None,
56 code_lookup: None,
57 transaction_group: None,
58 current_pid: None,
59 raw_codes: false,
60 code_lists: std::sync::Arc::new(crate::code_lists::CodeLists::default()),
61 }
62 }
63
64 pub fn load(dir: &Path) -> Result<Self, MappingError> {
66 let mut definitions = Vec::new();
67
68 let mut entries: Vec<_> = std::fs::read_dir(dir)?.filter_map(|e| e.ok()).collect();
69 entries.sort_by_key(|e| e.file_name());
70
71 for entry in entries {
72 let path = entry.path();
73 if path.extension().map(|e| e == "toml").unwrap_or(false) {
74 let content = std::fs::read_to_string(&path)?;
75 let def = MappingDefinition::from_toml_str(&content).map_err(|message| {
76 MappingError::TomlParse {
77 file: path.display().to_string(),
78 message,
79 }
80 })?;
81 definitions.push(def);
82 }
83 }
84
85 definitions.sort_by_key(|d| d.meta.order.unwrap_or(u32::MAX));
91
92 Ok(Self {
93 definitions,
94 segment_structure: None,
95 code_lookup: None,
96 transaction_group: None,
97 current_pid: None,
98 raw_codes: false,
99 code_lists: crate::code_lists::CodeLists::discover(dir),
100 })
101 }
102
103 pub fn load_split(
109 message_dir: &Path,
110 transaction_dir: &Path,
111 ) -> Result<(Self, Self), MappingError> {
112 let msg_engine = Self::load(message_dir)?;
113 let tx_engine = Self::load(transaction_dir)?;
114 Ok((msg_engine, tx_engine))
115 }
116
117 pub fn load_merged(dirs: &[&Path]) -> Result<Self, MappingError> {
122 let mut definitions = Vec::new();
123 for dir in dirs {
124 let engine = Self::load(dir)?;
125 definitions.extend(engine.definitions);
126 }
127 Ok(Self {
128 definitions,
129 segment_structure: None,
130 code_lookup: None,
131 transaction_group: None,
132 current_pid: None,
133 raw_codes: false,
134 code_lists: crate::code_lists::CodeLists::discover(
135 dirs.first().copied().unwrap_or(Path::new("")),
136 ),
137 })
138 }
139
140 pub fn load_with_common(
149 common_dir: &Path,
150 pid_dir: &Path,
151 schema_index: &crate::pid_schema_index::PidSchemaIndex,
152 ) -> Result<Self, MappingError> {
153 let mut common_defs = Self::load(common_dir)?.definitions;
154
155 common_defs.retain(|d| {
157 d.meta
158 .source_path
159 .as_deref()
160 .map(|sp| schema_index.has_group(sp))
161 .unwrap_or(true)
162 });
163
164 let pid_defs = Self::load(pid_dir)?.definitions;
165
166 let normalize_sg = |sg: &str| -> String {
171 sg.split('.')
172 .map(|part| part.split(':').next().unwrap_or(part))
173 .collect::<Vec<_>>()
174 .join(".")
175 };
176 let pid_keys: HashSet<(String, Option<String>)> = pid_defs
177 .iter()
178 .flat_map(|d| {
179 let sg = normalize_sg(&d.meta.source_group);
180 let disc = d.meta.discriminator.clone();
181 let mut keys = vec![(sg.clone(), disc.clone())];
182 if let Some(ref disc_str) = disc {
184 if let Some(base) = disc_str.rsplit_once('#') {
185 if base.1.chars().all(|c| c.is_ascii_digit()) {
186 keys.push((sg, Some(base.0.to_string())));
187 }
188 }
189 }
190 keys
191 })
192 .collect();
193
194 common_defs.retain(|d| {
196 let key = (
197 normalize_sg(&d.meta.source_group),
198 d.meta.discriminator.clone(),
199 );
200 !pid_keys.contains(&key)
201 });
202
203 let mut definitions = common_defs;
205 definitions.extend(pid_defs);
206
207 Ok(Self {
208 definitions,
209 segment_structure: None,
210 code_lookup: None,
211 transaction_group: None,
212 current_pid: None,
213 raw_codes: false,
214 code_lists: crate::code_lists::CodeLists::discover(pid_dir),
215 })
216 }
217
218 pub fn load_common_only(
222 common_dir: &Path,
223 schema_index: &crate::pid_schema_index::PidSchemaIndex,
224 ) -> Result<Self, MappingError> {
225 let mut common_defs = Self::load(common_dir)?.definitions;
226
227 common_defs.retain(|d| {
229 d.meta
230 .source_path
231 .as_deref()
232 .map(|sp| schema_index.has_group(sp))
233 .unwrap_or(true)
234 });
235
236 Ok(Self {
237 definitions: common_defs,
238 segment_structure: None,
239 code_lookup: None,
240 transaction_group: None,
241 current_pid: None,
242 raw_codes: false,
243 code_lists: crate::code_lists::CodeLists::discover(common_dir),
244 })
245 }
246
247 pub fn load_split_with_common(
252 message_dir: &Path,
253 common_dir: &Path,
254 transaction_dir: &Path,
255 schema_index: &crate::pid_schema_index::PidSchemaIndex,
256 ) -> Result<(Self, Self), MappingError> {
257 let msg_engine = Self::load(message_dir)?;
258 let tx_engine = Self::load_with_common(common_dir, transaction_dir, schema_index)?;
259 Ok((msg_engine, tx_engine))
260 }
261
262 fn names_a_code_list(definitions: &[MappingDefinition]) -> bool {
265 definitions.iter().any(|d| {
266 d.fields.values().any(|f| {
267 matches!(f, FieldMapping::Structured(s)
268 if s.code_list.is_some() || s.also_code_list.is_some())
269 })
270 })
271 }
272
273 fn table<'a>(
280 &'a self,
281 inline: Option<&'a BTreeMap<String, String>>,
282 named: Option<&str>,
283 ) -> Option<&'a BTreeMap<String, String>> {
284 self.code_lists.resolve(inline, named)
285 }
286
287 pub fn with_code_lists(
289 mut self,
290 code_lists: std::sync::Arc<crate::code_lists::CodeLists>,
291 ) -> Self {
292 self.code_lists = code_lists;
293 self
294 }
295
296 pub fn code_lists(&self) -> &std::sync::Arc<crate::code_lists::CodeLists> {
298 &self.code_lists
299 }
300
301 pub fn from_definitions_with_code_lists(
304 code_lists: std::sync::Arc<crate::code_lists::CodeLists>,
305 definitions: Vec<MappingDefinition>,
306 ) -> Self {
307 debug_assert!(
312 !(code_lists.is_empty() && Self::names_a_code_list(&definitions)),
313 "definitions name a shared code list but the supplied tables are \
314 empty — whatever produced them (a bundle, a cache) is not carrying \
315 them, and every code they translate will reach the output raw"
316 );
317 Self {
321 definitions,
322 segment_structure: None,
323 code_lookup: None,
324 transaction_group: None,
325 current_pid: None,
326 raw_codes: false,
327 code_lists,
328 }
329 }
330
331 pub fn from_definitions(definitions: Vec<MappingDefinition>) -> Self {
332 debug_assert!(
339 !Self::names_a_code_list(&definitions),
340 "definitions name a shared code list but none were supplied — build \
341 this engine with `from_definitions_with_code_lists`, or the codes \
342 they translate will reach the output untranslated"
343 );
344 Self {
345 definitions,
346 segment_structure: None,
347 code_lookup: None,
348 transaction_group: None,
349 current_pid: None,
350 raw_codes: false,
351 code_lists: std::sync::Arc::new(crate::code_lists::CodeLists::default()),
352 }
353 }
354
355 pub fn save_cached(&self, path: &Path) -> Result<(), MappingError> {
361 let encoded =
362 serde_json::to_vec(&self.definitions).map_err(|e| MappingError::CacheWrite {
363 path: path.display().to_string(),
364 message: e.to_string(),
365 })?;
366 if let Some(parent) = path.parent() {
367 std::fs::create_dir_all(parent)?;
368 }
369 std::fs::write(path, encoded)?;
370 Ok(())
371 }
372
373 pub fn load_cached_or_toml(cache_path: &Path, toml_dir: &Path) -> Result<Self, MappingError> {
378 if cache_path.exists() {
379 Self::load_cached(cache_path)
380 } else {
381 Self::load(toml_dir)
382 }
383 }
384
385 pub fn load_cached(path: &Path) -> Result<Self, MappingError> {
390 let bytes = std::fs::read(path)?;
391 let definitions: Vec<MappingDefinition> =
392 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
393 path: path.display().to_string(),
394 message: e.to_string(),
395 })?;
396 Ok(Self {
397 definitions,
398 segment_structure: None,
399 code_lookup: None,
400 transaction_group: None,
401 current_pid: None,
402 raw_codes: false,
403 code_lists: crate::code_lists::CodeLists::discover(path),
404 })
405 }
406
407 pub fn with_segment_structure(mut self, ss: SegmentStructure) -> Self {
412 self.segment_structure = Some(ss);
413 self
414 }
415
416 pub fn with_code_lookup(mut self, cl: crate::code_lookup::CodeLookup) -> Self {
421 self.code_lookup = Some(cl);
422 self
423 }
424
425 pub fn with_pid(mut self, pid: impl Into<String>) -> Self {
433 self.current_pid = Some(pid.into());
434 self
435 }
436
437 pub fn with_raw_codes(mut self, raw: bool) -> Self {
447 self.raw_codes = raw;
448 self
449 }
450
451 pub fn with_path_resolver(mut self, resolver: crate::path_resolver::PathResolver) -> Self {
457 for def in &mut self.definitions {
458 def.normalize_paths(&resolver);
459 }
460 self
461 }
462
463 pub fn with_transaction_group(mut self, tx: impl Into<String>) -> Self {
470 self.transaction_group = Some(tx.into());
471 self
472 }
473
474 pub fn extend_definitions(mut self, defs: impl IntoIterator<Item = MappingDefinition>) -> Self {
488 fn key(d: &MappingDefinition) -> (String, String, String, String, String) {
489 (
490 d.meta.entity.clone(),
491 d.meta.source_group.clone(),
492 d.meta.source_path.clone().unwrap_or_default(),
493 d.meta.discriminator.clone().unwrap_or_default(),
494 d.meta.parent_field.clone().unwrap_or_default(),
495 )
496 }
497 let mut seen: std::collections::HashSet<_> = self.definitions.iter().map(key).collect();
498 for def in defs {
499 if seen.insert(key(&def)) {
500 self.definitions.push(def);
501 }
502 }
503 self
504 }
505
506 pub fn definitions(&self) -> &[MappingDefinition] {
508 &self.definitions
509 }
510
511 pub fn definition_for_entity(&self, entity: &str) -> Option<&MappingDefinition> {
516 self.definitions
517 .iter()
518 .find(|d| d.meta.entity == entity && d.meta.parent_field.is_none())
519 }
520
521 pub fn extract_field(
530 &self,
531 tree: &AssembledTree,
532 group_path: &str,
533 path: &str,
534 repetition: usize,
535 ) -> Option<String> {
536 let instance = Self::resolve_group_instance(tree, group_path, repetition)?;
537 Self::extract_from_instance(instance, path)
538 }
539
540 pub fn resolve_group_instance<'a>(
549 tree: &'a AssembledTree,
550 group_path: &str,
551 repetition: usize,
552 ) -> Option<&'a AssembledGroupInstance> {
553 let parts: Vec<&str> = group_path.split('.').collect();
554
555 let (first_id, first_rep) = parse_group_spec(parts[0]);
556 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
557
558 if parts.len() == 1 {
559 let rep = first_rep.unwrap_or(repetition);
561 return first_group.repetitions.get(rep);
562 }
563
564 let mut current_instance = first_group.repetitions.get(first_rep.unwrap_or(0))?;
567
568 for (i, part) in parts[1..].iter().enumerate() {
569 let (group_id, explicit_rep) = parse_group_spec(part);
570 let child_group = current_instance
571 .child_groups
572 .iter()
573 .find(|g| g.group_id == group_id)?;
574
575 if i == parts.len() - 2 {
576 let rep = explicit_rep.unwrap_or(repetition);
578 return child_group.repetitions.get(rep);
579 }
580 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
582 }
583
584 None
585 }
586
587 pub fn resolve_by_source_path<'a>(
595 tree: &'a AssembledTree,
596 source_path: &str,
597 ) -> Option<&'a AssembledGroupInstance> {
598 let parts: Vec<&str> = source_path.split('.').collect();
599 if parts.is_empty() {
600 return None;
601 }
602
603 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
604 let first_group = tree
605 .groups
606 .iter()
607 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))?;
608
609 let mut current_instance = if let Some(q) = first_qualifier {
610 find_rep_by_entry_qualifier(&first_group.repetitions, q)?
611 } else {
612 first_group.repetitions.first()?
613 };
614
615 if parts.len() == 1 {
616 return Some(current_instance);
617 }
618
619 for part in &parts[1..] {
620 let (group_id, qualifier) = parse_source_path_part(part);
621 let child_group = current_instance
622 .child_groups
623 .iter()
624 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))?;
625
626 current_instance = if let Some(q) = qualifier {
627 find_rep_by_entry_qualifier(&child_group.repetitions, q)?
628 } else {
629 child_group.repetitions.first()?
630 };
631 }
632
633 Some(current_instance)
634 }
635
636 pub fn resolve_all_by_source_path<'a>(
644 tree: &'a AssembledTree,
645 source_path: &str,
646 ) -> Vec<&'a AssembledGroupInstance> {
647 let parts: Vec<&str> = source_path.split('.').collect();
648 if parts.is_empty() {
649 return vec![];
650 }
651
652 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
654 let first_group = match tree
655 .groups
656 .iter()
657 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
658 {
659 Some(g) => g,
660 None => return vec![],
661 };
662
663 let mut current_instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
664 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
665 } else {
666 first_group.repetitions.iter().collect()
667 };
668
669 for part in &parts[1..] {
672 let (group_id, qualifier) = parse_source_path_part(part);
673 let mut next_instances = Vec::new();
674
675 for instance in ¤t_instances {
676 if let Some(child_group) = instance
677 .child_groups
678 .iter()
679 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
680 {
681 if let Some(q) = qualifier {
682 next_instances.extend(find_all_reps_by_entry_qualifier(
683 &child_group.repetitions,
684 q,
685 ));
686 } else {
687 next_instances.extend(child_group.repetitions.iter());
688 }
689 }
690 }
691
692 current_instances = next_instances;
693 }
694
695 current_instances
696 }
697
698 fn compute_child_indices(
711 tree: &AssembledTree,
712 source_path: &str,
713 indexed: &[(usize, &AssembledGroupInstance)],
714 ) -> Vec<usize> {
715 let parts: Vec<&str> = source_path.split('.').collect();
716 if parts.len() < 2 {
717 return vec![];
718 }
719 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
721 let first_group = match tree
722 .groups
723 .iter()
724 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
725 {
726 Some(g) => g,
727 None => return vec![],
728 };
729 let parent_reps: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
730 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
731 } else {
732 first_group.repetitions.iter().collect()
733 };
734 let (child_id, _child_qualifier) = parse_source_path_part(parts[parts.len() - 1]);
736 let mut result = Vec::new();
737 for (_, inst) in indexed {
738 let mut found = false;
740 for parent in &parent_reps {
741 if let Some(child_group) = parent
742 .child_groups
743 .iter()
744 .find(|g| g.group_id.eq_ignore_ascii_case(child_id))
745 {
746 if let Some(pos) = child_group
747 .repetitions
748 .iter()
749 .position(|r| std::ptr::eq(r, *inst))
750 {
751 result.push(pos);
752 found = true;
753 break;
754 }
755 }
756 }
757 if !found {
758 result.push(usize::MAX); }
760 }
761 result
762 }
763
764 pub fn resolve_all_with_parent_indices<'a>(
766 tree: &'a AssembledTree,
767 source_path: &str,
768 ) -> Vec<(usize, &'a AssembledGroupInstance)> {
769 let parts: Vec<&str> = source_path.split('.').collect();
770 if parts.is_empty() {
771 return vec![];
772 }
773
774 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
776 let first_group = match tree
777 .groups
778 .iter()
779 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
780 {
781 Some(g) => g,
782 None => return vec![],
783 };
784
785 if parts.len() == 1 {
787 let instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
788 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
789 } else {
790 first_group.repetitions.iter().collect()
791 };
792 return instances.into_iter().map(|i| (0, i)).collect();
793 }
794
795 let first_reps: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = first_qualifier {
800 let matching = find_all_reps_by_entry_qualifier(&first_group.repetitions, q);
801 let mut result = Vec::new();
802 for m in matching {
803 let idx = first_group
804 .repetitions
805 .iter()
806 .position(|r| std::ptr::eq(r, m))
807 .unwrap_or(0);
808 result.push((idx, m));
809 }
810 result
811 } else {
812 first_group.repetitions.iter().enumerate().collect()
813 };
814
815 let mut current: Vec<(usize, &AssembledGroupInstance)> = first_reps;
816 let remaining = &parts[1..];
817
818 for (level, part) in remaining.iter().enumerate() {
819 let is_leaf = level == remaining.len() - 1;
820 let (group_id, qualifier) = parse_source_path_part(part);
821 let mut next: Vec<(usize, &AssembledGroupInstance)> = Vec::new();
822
823 for (prev_parent_idx, instance) in ¤t {
824 if let Some(child_group) = instance
825 .child_groups
826 .iter()
827 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
828 {
829 let matching: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = qualifier
830 {
831 let filtered =
832 find_all_reps_by_entry_qualifier(&child_group.repetitions, q);
833 filtered
834 .into_iter()
835 .map(|m| {
836 let idx = child_group
837 .repetitions
838 .iter()
839 .position(|r| std::ptr::eq(r, m))
840 .unwrap_or(0);
841 (idx, m)
842 })
843 .collect()
844 } else {
845 child_group.repetitions.iter().enumerate().collect()
846 };
847
848 for (rep_idx, child_rep) in matching {
849 if is_leaf {
850 next.push((*prev_parent_idx, child_rep));
852 } else {
853 next.push((rep_idx, child_rep));
855 }
856 }
857 }
858 }
859
860 current = next;
861 }
862
863 current
864 }
865
866 pub fn extract_from_instance(instance: &AssembledGroupInstance, path: &str) -> Option<String> {
872 let parts: Vec<&str> = path.split('.').collect();
873 if parts.is_empty() {
874 return None;
875 }
876
877 let (segment_tag, qualifier, occurrence) = parse_tag_qualifier(parts[0]);
880
881 let segment = if let Some(q) = qualifier {
882 instance
883 .segments
884 .iter()
885 .filter(|s| {
886 s.tag.eq_ignore_ascii_case(&segment_tag)
887 && s.elements
888 .first()
889 .and_then(|e| e.first())
890 .map(|v| v.as_str())
891 == Some(q)
892 })
893 .nth(occurrence)?
894 } else {
895 instance
896 .segments
897 .iter()
898 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
899 .nth(occurrence)?
900 };
901
902 Self::resolve_field_path(segment, &parts[1..])
903 }
904
905 pub fn extract_all_from_instance(instance: &AssembledGroupInstance, path: &str) -> Vec<String> {
911 let parts: Vec<&str> = path.split('.').collect();
912 if parts.is_empty() {
913 return vec![];
914 }
915
916 let (segment_tag, qualifier, _) = parse_tag_qualifier(parts[0]);
917
918 let matching_segments: Vec<&AssembledSegment> = if let Some(q) = qualifier {
919 instance
920 .segments
921 .iter()
922 .filter(|s| {
923 s.tag.eq_ignore_ascii_case(&segment_tag)
924 && s.elements
925 .first()
926 .and_then(|e| e.first())
927 .map(|v| v.as_str())
928 == Some(q)
929 })
930 .collect()
931 } else {
932 instance
933 .segments
934 .iter()
935 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
936 .collect()
937 };
938
939 matching_segments
940 .into_iter()
941 .filter_map(|seg| Self::resolve_field_path(seg, &parts[1..]))
942 .collect()
943 }
944
945 pub fn map_forward(
951 &self,
952 tree: &AssembledTree,
953 def: &MappingDefinition,
954 repetition: usize,
955 ) -> serde_json::Value {
956 self.map_forward_inner(tree, def, repetition, true)
957 }
958
959 fn map_forward_inner(
961 &self,
962 tree: &AssembledTree,
963 def: &MappingDefinition,
964 repetition: usize,
965 enrich_codes: bool,
966 ) -> serde_json::Value {
967 let mut result = serde_json::Map::new();
968
969 if def.meta.source_group.is_empty() {
974 let mut all_root_segs = tree.segments.clone();
975 for segs in tree.inter_group_segments.values() {
976 all_root_segs.extend(segs.iter().cloned());
977 }
978 let root_instance = AssembledGroupInstance {
979 segments: all_root_segs,
980 child_groups: vec![],
981 entry_mig_number: None,
982 variant_mig_numbers: vec![],
983 skipped_segments: Vec::new(),
984 skipped_positions: Vec::new(),
985 };
986 self.extract_fields_from_instance(&root_instance, def, &mut result, enrich_codes);
987 return serde_json::Value::Object(result);
988 }
989
990 let instance = if let Some(ref sp) = def.meta.source_path {
996 if has_source_path_qualifiers(sp) && !def.meta.source_group.contains(':') {
997 Self::resolve_by_source_path(tree, sp).or_else(|| {
998 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
999 })
1000 } else {
1001 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
1002 }
1003 } else {
1004 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
1005 };
1006
1007 if let Some(instance) = instance {
1008 if let Some(ref tag) = def.meta.repeat_on_tag {
1010 let matching: Vec<_> = instance
1011 .segments
1012 .iter()
1013 .filter(|s| s.tag.eq_ignore_ascii_case(tag))
1014 .collect();
1015
1016 if matching.len() > 1 {
1017 let mut arr = Vec::new();
1018 for seg in &matching {
1019 let sub_instance = AssembledGroupInstance {
1020 segments: vec![(*seg).clone()],
1021 child_groups: vec![],
1022 entry_mig_number: None,
1023 variant_mig_numbers: vec![],
1024 skipped_segments: Vec::new(),
1025 skipped_positions: Vec::new(),
1026 };
1027 let mut elem_result = serde_json::Map::new();
1028 self.extract_fields_from_instance(
1029 &sub_instance,
1030 def,
1031 &mut elem_result,
1032 enrich_codes,
1033 );
1034 if !elem_result.is_empty() {
1035 arr.push(serde_json::Value::Object(elem_result));
1036 }
1037 }
1038 if !arr.is_empty() {
1039 return serde_json::Value::Array(arr);
1040 }
1041 }
1042 }
1043
1044 self.extract_fields_from_instance(instance, def, &mut result, enrich_codes);
1045 }
1046
1047 serde_json::Value::Object(result)
1048 }
1049
1050 fn extract_fields_from_instance(
1055 &self,
1056 instance: &AssembledGroupInstance,
1057 def: &MappingDefinition,
1058 result: &mut serde_json::Map<String, serde_json::Value>,
1059 enrich_codes: bool,
1060 ) {
1061 for (path, field_mapping) in &def.fields {
1062 let (target, enum_map) = match field_mapping {
1063 FieldMapping::Simple(t) => (t.as_str(), None),
1064 FieldMapping::Structured(s) => (
1065 s.target.as_str(),
1066 self.table(s.enum_map.as_ref(), s.code_list.as_deref()),
1067 ),
1068 FieldMapping::Nested(_) => continue,
1069 };
1070 if target.is_empty() {
1071 continue;
1072 }
1073 if let Some(val) = Self::extract_from_instance(instance, path) {
1074 if let FieldMapping::Structured(s) = field_mapping {
1079 if let (false, Some(also), Some(also_map)) = (
1080 self.raw_codes,
1081 s.also_target.as_deref(),
1082 self.table(s.also_enum_map.as_ref(), s.also_code_list.as_deref()),
1083 ) {
1084 if let Some(also_val) = also_map.get(&val) {
1085 set_nested_value(result, also, also_val.clone());
1086 }
1087 }
1088 }
1089
1090 let mapped_val = match enum_map {
1091 Some(map) if !self.raw_codes => {
1092 map.get(&val).cloned().unwrap_or_else(|| val.clone())
1093 }
1094 _ => val.clone(),
1095 };
1096
1097 if enrich_codes {
1099 if let (Some(ref code_lookup), Some(ref source_path)) =
1100 (&self.code_lookup, &def.meta.source_path)
1101 {
1102 let parts: Vec<&str> = path.split('.').collect();
1103 let (seg_tag, path_qualifier, _occ) = parse_tag_qualifier(parts[0]);
1104 let (element_idx, component_idx) =
1105 Self::parse_element_component(&parts[1..]);
1106 let disc_qualifier = Self::discriminator_qualifier_for_tag(def, &seg_tag);
1107 let q = disc_qualifier.as_deref();
1108
1109 if let Some(codes) = code_lookup.enrichment_codes(
1110 source_path,
1111 &seg_tag,
1112 path_qualifier,
1113 q,
1114 element_idx,
1115 component_idx,
1116 ) {
1117 if let Some(ref pid) = self.current_pid {
1122 if codes.len() == 1 && codes.contains_key(pid.as_str()) {
1123 set_nested_value(result, target, mapped_val);
1124 continue;
1125 }
1126 }
1127
1128 let enrichment = codes.get(&val);
1132 let meaning = enrichment
1133 .map(|e| serde_json::Value::String(e.meaning.clone()))
1134 .unwrap_or(serde_json::Value::Null);
1135
1136 let mut obj = serde_json::Map::new();
1137 obj.insert("code".into(), serde_json::json!(mapped_val));
1138 obj.insert("meaning".into(), meaning);
1139 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
1140 obj.insert("enum".into(), serde_json::json!(enum_key));
1141 }
1142 let enriched = serde_json::Value::Object(obj);
1143 set_nested_value_json(result, target, enriched);
1144 continue;
1145 }
1146 }
1147 }
1148
1149 set_nested_value(result, target, mapped_val);
1150 }
1151 }
1152
1153 if !instance.child_groups.is_empty() {
1156 self.extract_nested_children(instance, def, result, enrich_codes);
1157 }
1158 }
1159
1160 fn extract_nested_children(
1165 &self,
1166 instance: &AssembledGroupInstance,
1167 def: &MappingDefinition,
1168 result: &mut serde_json::Map<String, serde_json::Value>,
1169 enrich_codes: bool,
1170 ) {
1171 for child in self
1172 .definitions
1173 .iter()
1174 .filter(|c| is_nested_child_of(c, def))
1175 {
1176 if nested_parent_qualifier(child).is_some_and(|q| !entry_qualifier_matches(instance, q))
1177 {
1178 continue;
1179 }
1180 let (leaf_id, leaf_qualifier) = nested_child_leaf(child);
1181 let Some(group) = instance
1182 .child_groups
1183 .iter()
1184 .find(|g| g.group_id.eq_ignore_ascii_case(&leaf_id))
1185 else {
1186 continue;
1187 };
1188 let reps: Vec<&AssembledGroupInstance> = match leaf_qualifier {
1189 Some(q) => find_all_reps_by_entry_qualifier(&group.repetitions, q),
1190 None => group.repetitions.iter().collect(),
1191 };
1192
1193 let mut items: Vec<serde_json::Value> = Vec::new();
1194 let mut push_item = |sub: &AssembledGroupInstance| {
1195 let mut obj = serde_json::Map::new();
1196 self.extract_fields_from_instance(sub, child, &mut obj, enrich_codes);
1197 if !obj.is_empty() {
1198 items.push(serde_json::Value::Object(obj));
1199 }
1200 };
1201 for rep in reps {
1202 let repeat_tag = child
1203 .meta
1204 .repeat_on_tag
1205 .as_deref()
1206 .filter(|tag| rep.segments.iter().any(|s| s.tag.eq_ignore_ascii_case(tag)));
1207 let Some(tag) = repeat_tag else {
1208 push_item(rep);
1209 continue;
1210 };
1211 let shared: Vec<AssembledSegment> = rep
1214 .segments
1215 .iter()
1216 .filter(|s| !s.tag.eq_ignore_ascii_case(tag))
1217 .cloned()
1218 .collect();
1219 for seg in rep
1220 .segments
1221 .iter()
1222 .filter(|s| s.tag.eq_ignore_ascii_case(tag))
1223 {
1224 let mut segments = shared.clone();
1225 segments.push(seg.clone());
1226 push_item(&AssembledGroupInstance {
1227 segments,
1228 child_groups: vec![],
1229 entry_mig_number: None,
1230 variant_mig_numbers: vec![],
1231 skipped_segments: Vec::new(),
1232 skipped_positions: Vec::new(),
1233 });
1234 }
1235 }
1236 if items.is_empty() {
1237 continue;
1238 }
1239 let field = child.meta.parent_field.as_deref().unwrap_or_default();
1240 match result.get_mut(field) {
1241 Some(serde_json::Value::Array(existing)) => existing.extend(items),
1242 _ => {
1243 result.insert(field.to_string(), serde_json::Value::Array(items));
1244 }
1245 }
1246 }
1247 }
1248
1249 fn reverse_nested_children(
1253 &self,
1254 bo4e_value: &serde_json::Value,
1255 def: &MappingDefinition,
1256 instance: &mut AssembledGroupInstance,
1257 ) {
1258 let mut handled_fields: Vec<&str> = Vec::new();
1259 for child in self
1260 .definitions
1261 .iter()
1262 .filter(|c| is_nested_child_of(c, def))
1263 {
1264 let field = child.meta.parent_field.as_deref().unwrap_or_default();
1265 if handled_fields.contains(&field) {
1266 continue;
1267 }
1268 if nested_parent_qualifier(child)
1269 .is_some_and(|q| !rebuilt_entry_qualifier_matches(instance, def, q))
1270 {
1271 continue;
1272 }
1273 let elements: Vec<&serde_json::Value> = match bo4e_value.get(field) {
1274 Some(serde_json::Value::Array(arr)) => arr.iter().collect(),
1275 Some(serde_json::Value::Null) | None => continue,
1276 Some(other) => vec![other],
1277 };
1278
1279 let mut reps: Vec<AssembledGroupInstance> = Vec::new();
1280 if let Some(tag) = child.meta.repeat_on_tag.as_deref() {
1281 let mut merged: Option<AssembledGroupInstance> = None;
1284 for element in elements {
1285 let sub = self.map_reverse_single(element, child);
1286 if sub.segments.is_empty() {
1287 continue;
1288 }
1289 match merged.as_mut() {
1290 None => merged = Some(sub),
1291 Some(m) => m.segments.extend(
1292 sub.segments
1293 .into_iter()
1294 .filter(|s| s.tag.eq_ignore_ascii_case(tag)),
1295 ),
1296 }
1297 }
1298 reps.extend(merged);
1299 } else {
1300 for element in elements {
1301 let mut sub = self.map_reverse_single(element, child);
1302 if sub.segments.is_empty() {
1303 continue;
1304 }
1305 self.reverse_nested_children(element, child, &mut sub);
1307 reps.push(sub);
1308 }
1309 }
1310 if reps.is_empty() {
1311 continue;
1312 }
1313 handled_fields.push(field);
1314
1315 let (leaf_id, _) = nested_child_leaf(child);
1316 match instance
1317 .child_groups
1318 .iter_mut()
1319 .find(|g| g.group_id.eq_ignore_ascii_case(&leaf_id))
1320 {
1321 Some(group) => group.repetitions.extend(reps),
1322 None => instance.child_groups.push(AssembledGroup {
1323 group_id: leaf_id,
1324 repetitions: reps,
1325 }),
1326 }
1327 }
1328 }
1329
1330 pub(crate) fn discriminator_qualifier(def: &MappingDefinition) -> Option<String> {
1337 def.meta
1338 .discriminator
1339 .as_deref()
1340 .and_then(|d| d.split_once('=').map(|(_, v)| v.to_string()))
1341 }
1342
1343 pub(crate) fn discriminator_qualifier_for_tag(
1348 def: &MappingDefinition,
1349 segment_tag: &str,
1350 ) -> Option<String> {
1351 let (lhs, value) = def.meta.discriminator.as_deref()?.split_once('=')?;
1352 let disc_tag = lhs.split('.').next().unwrap_or(lhs);
1353 disc_tag
1354 .eq_ignore_ascii_case(segment_tag)
1355 .then(|| value.to_string())
1356 }
1357
1358 pub fn map_forward_from_segments(
1364 &self,
1365 segments: &[OwnedSegment],
1366 def: &MappingDefinition,
1367 ) -> serde_json::Value {
1368 let assembled_segments: Vec<AssembledSegment> = segments
1369 .iter()
1370 .map(|s| AssembledSegment {
1371 tag: s.id.clone(),
1372 elements: s.elements.clone(),
1373 mig_number: None,
1374 segment_number: Some(s.segment_number),
1375 })
1376 .collect();
1377
1378 let instance = AssembledGroupInstance {
1379 segments: assembled_segments,
1380 child_groups: vec![],
1381 entry_mig_number: None,
1382 variant_mig_numbers: vec![],
1383 skipped_segments: Vec::new(),
1384 skipped_positions: Vec::new(),
1385 };
1386
1387 let mut result = serde_json::Map::new();
1388 self.extract_fields_from_instance(&instance, def, &mut result, true);
1389 serde_json::Value::Object(result)
1390 }
1391
1392 pub fn map_reverse(
1405 &self,
1406 bo4e_value: &serde_json::Value,
1407 def: &MappingDefinition,
1408 ) -> AssembledGroupInstance {
1409 if def.meta.repeat_on_tag.is_some() {
1411 if let Some(arr) = bo4e_value.as_array() {
1412 let mut all_segments = Vec::new();
1413 for elem in arr {
1414 let sub = self.map_reverse_single(elem, def);
1415 all_segments.extend(sub.segments);
1416 }
1417 return AssembledGroupInstance {
1418 segments: all_segments,
1419 child_groups: vec![],
1420 entry_mig_number: None,
1421 variant_mig_numbers: vec![],
1422 skipped_segments: Vec::new(),
1423 skipped_positions: Vec::new(),
1424 };
1425 }
1426 }
1427 let mut instance = self.map_reverse_single(bo4e_value, def);
1428 if def.meta.parent_field.is_none() && !instance.segments.is_empty() {
1429 self.reverse_nested_children(bo4e_value, def, &mut instance);
1430 }
1431 instance
1432 }
1433
1434 fn map_reverse_single(
1435 &self,
1436 bo4e_value: &serde_json::Value,
1437 def: &MappingDefinition,
1438 ) -> AssembledGroupInstance {
1439 let mut field_values: Vec<(String, String, usize, usize, String)> =
1442 Vec::with_capacity(def.fields.len());
1443
1444 let mut has_real_data = false;
1451 let mut has_data_fields = false;
1452 let mut seg_has_data_field: HashSet<String> = HashSet::new();
1455 let mut seg_has_real_data: HashSet<String> = HashSet::new();
1456 let mut injected_qualifiers: HashSet<String> = HashSet::new();
1457
1458 for (path, field_mapping) in &def.fields {
1459 let (target, default, enum_map, when_filled, also_target, also_enum_map) =
1460 match field_mapping {
1461 FieldMapping::Simple(t) => (t.as_str(), None, None, None, None, None),
1462 FieldMapping::Structured(s) => (
1463 s.target.as_str(),
1464 s.default.as_ref(),
1465 self.table(s.enum_map.as_ref(), s.code_list.as_deref()),
1466 s.when_filled.as_ref(),
1467 s.also_target.as_deref(),
1468 self.table(s.also_enum_map.as_ref(), s.also_code_list.as_deref()),
1469 ),
1470 FieldMapping::Nested(_) => continue,
1471 };
1472
1473 let parts: Vec<&str> = path.split('.').collect();
1474 if parts.len() < 2 {
1475 continue;
1476 }
1477
1478 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1479 let seg_key = parts[0].to_uppercase();
1482 let sub_path = &parts[1..];
1483
1484 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1486 let ci = if sub_path.len() > 1 {
1487 sub_path[1].parse::<usize>().unwrap_or(0)
1488 } else {
1489 0
1490 };
1491 (ei, ci)
1492 } else {
1493 match sub_path.len() {
1494 1 => (0, 0),
1495 2 => (1, 0),
1496 _ => continue,
1497 }
1498 };
1499
1500 let val = if target.is_empty() {
1502 match (default, when_filled) {
1503 (Some(d), Some(fields)) => {
1505 let any_filled = fields
1506 .iter()
1507 .any(|f| self.populate_field(bo4e_value, f).is_some());
1508 if any_filled {
1509 has_real_data = true;
1512 Some(d.clone())
1513 } else {
1514 None
1515 }
1516 }
1517 (Some(d), None) => Some(d.clone()),
1519 (None, _) => None,
1520 }
1521 } else {
1522 has_data_fields = true;
1523 seg_has_data_field.insert(seg_key.clone());
1524 let bo4e_val = self.populate_field(bo4e_value, target);
1525 if bo4e_val.is_some() {
1526 has_real_data = true;
1527 seg_has_real_data.insert(seg_key.clone());
1528 }
1529 let mapped_val = match (bo4e_val, enum_map) {
1531 (Some(v), Some(map)) => {
1532 let joint = match (also_target, also_enum_map) {
1539 (Some(also), Some(also_map)) => {
1540 self.populate_field(bo4e_value, also).and_then(|also_v| {
1541 map.iter()
1542 .find(|(code, bo4e_v)| {
1543 *bo4e_v == &v && also_map.get(*code) == Some(&also_v)
1544 })
1545 .map(|(code, _)| code.clone())
1546 })
1547 }
1548 _ => None,
1549 };
1550 joint
1551 .or_else(|| {
1552 map.iter()
1554 .find(|(_, bo4e_v)| *bo4e_v == &v)
1555 .map(|(edifact_k, _)| edifact_k.clone())
1556 })
1557 .or(Some(v))
1558 }
1559 (v, _) => v,
1560 };
1561 mapped_val.or_else(|| default.cloned())
1562 };
1563
1564 if let Some(val) = val {
1565 field_values.push((
1566 seg_key.clone(),
1567 seg_tag.clone(),
1568 element_idx,
1569 component_idx,
1570 val,
1571 ));
1572 }
1573
1574 if let Some(q) = qualifier {
1576 if injected_qualifiers.insert(seg_key.clone()) {
1577 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1578 }
1579 }
1580 }
1581
1582 field_values.retain(|(seg_key, _, _, _, _)| {
1590 if !seg_key.contains('[') {
1591 return true; }
1593 !seg_has_data_field.contains(seg_key) || seg_has_real_data.contains(seg_key)
1594 });
1595
1596 if has_data_fields && !has_real_data {
1601 return AssembledGroupInstance {
1602 segments: vec![],
1603 child_groups: vec![],
1604 entry_mig_number: None,
1605 variant_mig_numbers: vec![],
1606 skipped_segments: Vec::new(),
1607 skipped_positions: Vec::new(),
1608 };
1609 }
1610
1611 let mut segments: Vec<AssembledSegment> = Vec::with_capacity(field_values.len());
1614 let mut seen_keys: HashMap<String, usize> = HashMap::new();
1615
1616 for (seg_key, seg_tag, element_idx, component_idx, val) in &field_values {
1617 let seg = if let Some(&pos) = seen_keys.get(seg_key) {
1618 &mut segments[pos]
1619 } else {
1620 let pos = segments.len();
1621 seen_keys.insert(seg_key.clone(), pos);
1622 segments.push(AssembledSegment {
1623 tag: seg_tag.clone(),
1624 elements: vec![],
1625 mig_number: None,
1626 segment_number: None,
1627 });
1628 &mut segments[pos]
1629 };
1630
1631 while seg.elements.len() <= *element_idx {
1632 seg.elements.push(vec![]);
1633 }
1634 while seg.elements[*element_idx].len() <= *component_idx {
1635 seg.elements[*element_idx].push(String::new());
1636 }
1637 seg.elements[*element_idx][*component_idx] = val.clone();
1638 }
1639
1640 for seg in &mut segments {
1643 let last_populated = seg.elements.iter().rposition(|e| !e.is_empty());
1644 if let Some(last_idx) = last_populated {
1645 for i in 0..last_idx {
1646 if seg.elements[i].is_empty() {
1647 seg.elements[i] = vec![String::new()];
1648 }
1649 }
1650 }
1651 }
1652
1653 if let Some(ref ss) = self.segment_structure {
1655 for seg in &mut segments {
1656 if let Some(expected) = ss.element_count(&seg.tag) {
1657 while seg.elements.len() < expected {
1658 seg.elements.push(vec![String::new()]);
1659 }
1660 }
1661 }
1662 }
1663
1664 AssembledGroupInstance {
1665 segments,
1666 child_groups: vec![],
1667 entry_mig_number: None,
1668 variant_mig_numbers: vec![],
1669 skipped_segments: Vec::new(),
1670 skipped_positions: Vec::new(),
1671 }
1672 }
1673
1674 fn resolve_field_path(segment: &AssembledSegment, path: &[&str]) -> Option<String> {
1687 if path.is_empty() {
1688 return None;
1689 }
1690
1691 if let Ok(element_idx) = path[0].parse::<usize>() {
1693 let component_idx = if path.len() > 1 {
1694 path[1].parse::<usize>().unwrap_or(0)
1695 } else {
1696 0
1697 };
1698 return segment
1699 .elements
1700 .get(element_idx)?
1701 .get(component_idx)
1702 .filter(|v| !v.is_empty())
1703 .cloned();
1704 }
1705
1706 None
1712 }
1713
1714 pub(crate) fn parse_element_component(parts: &[&str]) -> (usize, usize) {
1717 if parts.is_empty() {
1718 return (0, 0);
1719 }
1720 let element_idx = parts[0].parse::<usize>().unwrap_or(0);
1721 let component_idx = if parts.len() > 1 {
1722 parts[1].parse::<usize>().unwrap_or(0)
1723 } else {
1724 0
1725 };
1726 (element_idx, component_idx)
1727 }
1728
1729 pub fn populate_field(
1732 &self,
1733 bo4e_value: &serde_json::Value,
1734 target_field: &str,
1735 ) -> Option<String> {
1736 let mut current = bo4e_value;
1737 for part in target_field.split('.') {
1738 current = current.get(part)?;
1739 }
1740 if let Some(code) = current.get("code").and_then(|v| v.as_str()) {
1742 return Some(code.to_string());
1743 }
1744 current.as_str().map(|s| s.to_string())
1745 }
1746
1747 pub fn build_segment_from_bo4e(
1749 &self,
1750 bo4e_value: &serde_json::Value,
1751 segment_tag: &str,
1752 target_field: &str,
1753 ) -> AssembledSegment {
1754 let value = self.populate_field(bo4e_value, target_field);
1755 let elements = if let Some(val) = value {
1756 vec![vec![val]]
1757 } else {
1758 vec![]
1759 };
1760 AssembledSegment {
1761 tag: segment_tag.to_uppercase(),
1762 elements,
1763 mig_number: None,
1764 segment_number: None,
1765 }
1766 }
1767
1768 pub fn resolve_repetition(
1777 tree: &AssembledTree,
1778 group_path: &str,
1779 discriminator: &str,
1780 ) -> Option<usize> {
1781 let (spec, expected) = discriminator.split_once('=')?;
1782 let parts: Vec<&str> = spec.split('.').collect();
1783 if parts.len() != 3 {
1784 return None;
1785 }
1786 let tag = parts[0];
1787 let element_idx: usize = parts[1].parse().ok()?;
1788 let component_idx: usize = parts[2].parse().ok()?;
1789
1790 let path_parts: Vec<&str> = group_path.split('.').collect();
1792
1793 let leaf_group = if path_parts.len() == 1 {
1794 let (group_id, _) = parse_group_spec(path_parts[0]);
1795 tree.groups.iter().find(|g| g.group_id == group_id)?
1796 } else {
1797 let parent_parts = &path_parts[..path_parts.len() - 1];
1799 let mut current_instance = {
1800 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1801 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
1802 first_group.repetitions.get(first_rep.unwrap_or(0))?
1803 };
1804 for part in &parent_parts[1..] {
1805 let (group_id, explicit_rep) = parse_group_spec(part);
1806 let child_group = current_instance
1807 .child_groups
1808 .iter()
1809 .find(|g| g.group_id == group_id)?;
1810 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
1811 }
1812 let (leaf_id, _) = parse_group_spec(path_parts.last()?);
1813 current_instance
1814 .child_groups
1815 .iter()
1816 .find(|g| g.group_id == leaf_id)?
1817 };
1818
1819 let expected_values: Vec<&str> = expected.split('|').collect();
1821 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1822 let matches = instance.segments.iter().any(|s| {
1823 s.tag.eq_ignore_ascii_case(tag)
1824 && s.elements
1825 .get(element_idx)
1826 .and_then(|e| e.get(component_idx))
1827 .map(|v| expected_values.iter().any(|ev| v == ev))
1828 .unwrap_or(false)
1829 });
1830 if matches {
1831 return Some(rep_idx);
1832 }
1833 }
1834
1835 None
1836 }
1837
1838 pub fn resolve_all_repetitions(
1843 tree: &AssembledTree,
1844 group_path: &str,
1845 discriminator: &str,
1846 ) -> Vec<usize> {
1847 let Some((spec, expected)) = discriminator.split_once('=') else {
1848 return Vec::new();
1849 };
1850 let parts: Vec<&str> = spec.split('.').collect();
1851 if parts.len() != 3 {
1852 return Vec::new();
1853 }
1854 let tag = parts[0];
1855 let element_idx: usize = match parts[1].parse() {
1856 Ok(v) => v,
1857 Err(_) => return Vec::new(),
1858 };
1859 let component_idx: usize = match parts[2].parse() {
1860 Ok(v) => v,
1861 Err(_) => return Vec::new(),
1862 };
1863
1864 let path_parts: Vec<&str> = group_path.split('.').collect();
1866
1867 let leaf_group = if path_parts.len() == 1 {
1868 let (group_id, _) = parse_group_spec(path_parts[0]);
1869 match tree.groups.iter().find(|g| g.group_id == group_id) {
1870 Some(g) => g,
1871 None => return Vec::new(),
1872 }
1873 } else {
1874 let parent_parts = &path_parts[..path_parts.len() - 1];
1875 let mut current_instance = {
1876 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1877 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
1878 Some(g) => g,
1879 None => return Vec::new(),
1880 };
1881 match first_group.repetitions.get(first_rep.unwrap_or(0)) {
1882 Some(i) => i,
1883 None => return Vec::new(),
1884 }
1885 };
1886 for part in &parent_parts[1..] {
1887 let (group_id, explicit_rep) = parse_group_spec(part);
1888 let child_group = match current_instance
1889 .child_groups
1890 .iter()
1891 .find(|g| g.group_id == group_id)
1892 {
1893 Some(g) => g,
1894 None => return Vec::new(),
1895 };
1896 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
1897 Some(i) => i,
1898 None => return Vec::new(),
1899 };
1900 }
1901 let (leaf_id, _) = match path_parts.last() {
1902 Some(p) => parse_group_spec(p),
1903 None => return Vec::new(),
1904 };
1905 match current_instance
1906 .child_groups
1907 .iter()
1908 .find(|g| g.group_id == leaf_id)
1909 {
1910 Some(g) => g,
1911 None => return Vec::new(),
1912 }
1913 };
1914
1915 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
1917
1918 let expected_values: Vec<&str> = expected_raw.split('|').collect();
1920 let mut result = Vec::new();
1921 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1922 let matches = instance.segments.iter().any(|s| {
1923 s.tag.eq_ignore_ascii_case(tag)
1924 && s.elements
1925 .get(element_idx)
1926 .and_then(|e| e.get(component_idx))
1927 .map(|v| expected_values.iter().any(|ev| v == ev))
1928 .unwrap_or(false)
1929 });
1930 if matches {
1931 result.push(rep_idx);
1932 }
1933 }
1934
1935 if let Some(occ) = occurrence {
1937 result.into_iter().nth(occ).into_iter().collect()
1938 } else {
1939 result
1940 }
1941 }
1942
1943 pub fn map_all_forward(&self, tree: &AssembledTree) -> serde_json::Value {
1965 self.map_all_forward_inner(tree, true).0
1966 }
1967
1968 pub fn map_all_forward_enriched(
1972 &self,
1973 tree: &AssembledTree,
1974 enrich_codes: bool,
1975 ) -> serde_json::Value {
1976 self.map_all_forward_inner(tree, enrich_codes).0
1977 }
1978
1979 fn map_all_forward_inner(
1985 &self,
1986 tree: &AssembledTree,
1987 enrich_codes: bool,
1988 ) -> (
1989 serde_json::Value,
1990 std::collections::HashMap<String, Vec<usize>>,
1991 ) {
1992 self.map_all_forward_inner_with_tx(tree, enrich_codes, self.transaction_group.as_deref())
1993 }
1994
1995 fn map_all_forward_inner_with_tx(
1999 &self,
2000 tree: &AssembledTree,
2001 enrich_codes: bool,
2002 tx_group_override: Option<&str>,
2003 ) -> (
2004 serde_json::Value,
2005 std::collections::HashMap<String, Vec<usize>>,
2006 ) {
2007 let mut result = serde_json::Map::new();
2008 let mut nesting_info: std::collections::HashMap<String, Vec<usize>> =
2009 std::collections::HashMap::new();
2010
2011 for def in &self.definitions {
2012 if def.meta.parent_field.is_some() {
2015 continue;
2016 }
2017 let entity = &def.meta.entity;
2018
2019 let bo4e = if let Some(ref disc) = def.meta.discriminator {
2020 let use_source_path = def
2025 .meta
2026 .source_path
2027 .as_ref()
2028 .is_some_and(|sp| has_source_path_qualifiers(sp));
2029 if use_source_path {
2030 let sp = def.meta.source_path.as_deref().unwrap();
2032 let all_instances = Self::resolve_all_by_source_path(tree, sp);
2033 let instances: Vec<_> = if let Some(matcher) = DiscriminatorMatcher::parse(disc)
2035 {
2036 matcher.filter_instances(all_instances)
2037 } else {
2038 all_instances
2039 };
2040 let extract = |instance: &AssembledGroupInstance| {
2041 let mut r = serde_json::Map::new();
2042 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
2043 serde_json::Value::Object(r)
2044 };
2045 match instances.len() {
2046 0 => None,
2047 1 => Some(extract(instances[0])),
2048 _ => Some(serde_json::Value::Array(
2049 instances.iter().map(|i| extract(i)).collect(),
2050 )),
2051 }
2052 } else {
2053 let reps = Self::resolve_all_repetitions(tree, &def.meta.source_group, disc);
2054 match reps.len() {
2055 0 => None,
2056 1 => Some(self.map_forward_inner(tree, def, reps[0], enrich_codes)),
2057 _ => Some(serde_json::Value::Array(
2058 reps.iter()
2059 .map(|&rep| self.map_forward_inner(tree, def, rep, enrich_codes))
2060 .collect(),
2061 )),
2062 }
2063 }
2064 } else if def.meta.source_group.is_empty() {
2065 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2067 } else if def.meta.source_path.as_ref().is_some_and(|sp| {
2068 has_source_path_qualifiers(sp) || def.meta.source_group.contains('.')
2069 }) {
2070 let sp = def.meta.source_path.as_deref().unwrap();
2075 let mut indexed = Self::resolve_all_with_parent_indices(tree, sp);
2076
2077 if let Some(last_part) = sp.rsplit('.').next() {
2082 if !last_part.contains('_') {
2083 let base_prefix = if let Some(parent) = sp.rsplit_once('.') {
2087 format!("{}.", parent.0)
2088 } else {
2089 String::new()
2090 };
2091 let sibling_qualifiers: Vec<String> = self
2092 .definitions
2093 .iter()
2094 .filter_map(|d| d.meta.source_path.as_deref())
2095 .filter(|other_sp| {
2096 *other_sp != sp
2097 && other_sp.starts_with(&base_prefix)
2098 && other_sp.split('.').count() == sp.split('.').count()
2099 })
2100 .filter_map(|other_sp| {
2101 let other_last = other_sp.rsplit('.').next()?;
2102 let (base, q) = other_last.split_once('_')?;
2105 if base == last_part {
2106 Some(q.to_string())
2107 } else {
2108 None
2109 }
2110 })
2111 .collect();
2112
2113 if !sibling_qualifiers.is_empty() {
2114 indexed.retain(|(_, inst)| {
2115 let entry_qual = inst
2116 .segments
2117 .first()
2118 .and_then(|seg| seg.elements.first())
2119 .and_then(|el| el.first())
2120 .map(|v| v.to_lowercase());
2121 !entry_qual.is_some_and(|q| {
2124 sibling_qualifiers.iter().any(|sq| {
2125 sq.split('_').any(|part| part.eq_ignore_ascii_case(&q))
2126 })
2127 })
2128 });
2129 }
2130 }
2131 }
2132 let extract = |instance: &AssembledGroupInstance| {
2133 let mut r = serde_json::Map::new();
2134 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
2135 serde_json::Value::Object(r)
2136 };
2137 if def.meta.source_group.contains('.') && !indexed.is_empty() {
2142 if let Some(sp) = &def.meta.source_path {
2143 let parent_indices: Vec<usize> =
2144 indexed.iter().map(|(idx, _)| *idx).collect();
2145 nesting_info.entry(sp.clone()).or_insert(parent_indices);
2146
2147 let child_key = format!("{sp}#child");
2150 if let std::collections::hash_map::Entry::Vacant(e) =
2151 nesting_info.entry(child_key)
2152 {
2153 let child_indices: Vec<usize> =
2154 Self::compute_child_indices(tree, sp, &indexed);
2155 if !child_indices.is_empty() {
2156 e.insert(child_indices);
2157 }
2158 }
2159 }
2160 }
2161 match indexed.len() {
2162 0 => None,
2163 1 => Some(extract(indexed[0].1)),
2164 _ => Some(serde_json::Value::Array(
2165 indexed.iter().map(|(_, i)| extract(i)).collect(),
2166 )),
2167 }
2168 } else {
2169 let num_reps = Self::count_repetitions(tree, &def.meta.source_group);
2170 if num_reps <= 1 {
2171 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2172 } else {
2173 let mut items = Vec::with_capacity(num_reps);
2175 for rep in 0..num_reps {
2176 items.push(self.map_forward_inner(tree, def, rep, enrich_codes));
2177 }
2178 Some(serde_json::Value::Array(items))
2179 }
2180 };
2181
2182 if let Some(bo4e) = bo4e {
2183 let key = to_camel_case(entity);
2184 match def.meta.target_list.as_deref() {
2185 Some(list_field) => append_to_list_field(&mut result, &key, list_field, bo4e),
2186 None => deep_merge_insert(&mut result, &key, bo4e),
2187 }
2188 }
2189 }
2190
2191 nest_child_entities_in_result(
2196 &mut result,
2197 &self.definitions,
2198 &nesting_info,
2199 tx_group_override,
2200 );
2201
2202 (serde_json::Value::Object(result), nesting_info)
2203 }
2204
2205 pub fn map_all_reverse(
2214 &self,
2215 entities: &serde_json::Value,
2216 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2217 ) -> AssembledTree {
2218 self.map_all_reverse_with_mig(entities, nesting_info, None)
2219 }
2220
2221 pub fn map_all_reverse_with_mig(
2225 &self,
2226 entities: &serde_json::Value,
2227 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2228 mig: Option<&MigSchema>,
2229 ) -> AssembledTree {
2230 let mut root_segments: Vec<AssembledSegment> = Vec::new();
2231 let mut groups: Vec<AssembledGroup> = Vec::new();
2232 let mut inferred_nesting: std::collections::HashMap<String, Vec<usize>> =
2235 std::collections::HashMap::new();
2236
2237 for def in &self.definitions {
2238 if def.meta.parent_field.is_some() {
2241 continue;
2242 }
2243 let entity_key = to_camel_case(&def.meta.entity);
2244
2245 let _extracted: Option<serde_json::Value>;
2248 let entity_value = if let Some(list_field) = def.meta.target_list.as_deref() {
2249 match entities.get(&entity_key).and_then(|e| e.get(list_field)) {
2255 Some(v) if v.is_array() => {
2256 _extracted = None;
2257 v
2258 }
2259 _ => continue,
2260 }
2261 } else if let Some(v) = entities.get(&entity_key) {
2262 _extracted = None;
2263 v
2264 } else if def.meta.source_group.contains('.') {
2265 match extract_child_from_parent_with_indices(entities, &self.definitions, def) {
2267 Some((v, parent_indices)) => {
2268 if let Some(sp) = def.meta.source_path.as_deref() {
2270 inferred_nesting
2271 .entry(sp.to_string())
2272 .or_insert(parent_indices);
2273 }
2274 _extracted = Some(v);
2275 _extracted.as_ref().unwrap()
2276 }
2277 None => continue,
2278 }
2279 } else {
2280 continue;
2281 };
2282
2283 let unwrapped: Option<serde_json::Value>;
2291 let entity_value = if entity_value.is_object() && !entity_value.is_array() {
2292 if let Some(disc_value) = def
2293 .meta
2294 .discriminator
2295 .as_deref()
2296 .and_then(|d| d.split_once('='))
2297 .map(|(_, v)| v)
2298 {
2299 if let Some(inner) = entity_value.get(disc_value) {
2301 let mut injected = inner.clone();
2302 if let Some(qualifier_field) =
2305 find_qualifier_companion_field(&self.definitions, &def.meta.entity)
2306 {
2307 if let Some(obj) = injected.as_object_mut() {
2308 let entry = obj
2309 .entry(qualifier_field)
2310 .or_insert(serde_json::Value::Null);
2311 if entry.is_null() {
2312 *entry = serde_json::Value::String(disc_value.to_string());
2313 }
2314 }
2315 }
2316 unwrapped = Some(injected);
2317 unwrapped.as_ref().unwrap()
2318 } else {
2319 entity_value
2320 }
2321 } else if is_map_keyed_object(entity_value) {
2322 let map = entity_value.as_object().unwrap();
2327 let arr: Vec<serde_json::Value> = map
2328 .iter()
2329 .map(|(key, val)| {
2330 let mut item = val.clone();
2331 if let Some(obj) = item.as_object_mut() {
2334 if let Some(qualifier_field) = find_qualifier_companion_field(
2335 &self.definitions,
2336 &def.meta.entity,
2337 ) {
2338 let entry = obj
2339 .entry(qualifier_field)
2340 .or_insert(serde_json::Value::Null);
2341 if entry.is_null() {
2342 *entry = serde_json::Value::String(key.clone());
2343 }
2344 }
2345 }
2346 item
2347 })
2348 .collect();
2349 unwrapped = Some(serde_json::Value::Array(arr));
2350 unwrapped.as_ref().unwrap()
2351 } else {
2352 entity_value
2353 }
2354 } else {
2355 entity_value
2356 };
2357
2358 let leaf_group = def
2360 .meta
2361 .source_group
2362 .rsplit('.')
2363 .next()
2364 .unwrap_or(&def.meta.source_group);
2365
2366 if def.meta.source_group.is_empty() {
2367 let instance = self.map_reverse(entity_value, def);
2369 root_segments.extend(instance.segments);
2370 } else if entity_value.is_array() {
2371 let arr = entity_value.as_array().unwrap();
2373 let reps: Vec<_> = arr.iter().map(|item| self.map_reverse(item, def)).collect();
2374
2375 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2377 existing.repetitions.extend(reps);
2378 } else {
2379 groups.push(AssembledGroup {
2380 group_id: leaf_group.to_string(),
2381 repetitions: reps,
2382 });
2383 }
2384 } else {
2385 let instance = self.map_reverse(entity_value, def);
2387
2388 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2389 existing.repetitions.push(instance);
2390 } else {
2391 groups.push(AssembledGroup {
2392 group_id: leaf_group.to_string(),
2393 repetitions: vec![instance],
2394 });
2395 }
2396 }
2397 }
2398
2399 let nested_specs: Vec<(String, String)> = self
2405 .definitions
2406 .iter()
2407 .filter(|def| def.meta.parent_field.is_none())
2408 .filter_map(|def| {
2409 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
2410 if parts.len() > 1 {
2411 Some((parts[0].to_string(), parts[parts.len() - 1].to_string()))
2412 } else {
2413 None
2414 }
2415 })
2416 .collect();
2417 for (parent_id, child_id) in &nested_specs {
2418 let has_parent = groups.iter().any(|g| g.group_id == *parent_id);
2420 let has_child = groups.iter().any(|g| g.group_id == *child_id);
2421 if has_parent && has_child {
2422 let child_idx = groups.iter().position(|g| g.group_id == *child_id).unwrap();
2423 let child_group = groups.remove(child_idx);
2424 let parent = groups
2425 .iter_mut()
2426 .find(|g| g.group_id == *parent_id)
2427 .unwrap();
2428 let child_source_path = self
2432 .definitions
2433 .iter()
2434 .find(|d| {
2435 let parts: Vec<&str> = d.meta.source_group.split('.').collect();
2436 d.meta.parent_field.is_none()
2437 && parts.len() > 1
2438 && parts[parts.len() - 1] == *child_id
2439 })
2440 .and_then(|d| d.meta.source_path.as_deref());
2441 let distribution = child_source_path.and_then(|key| {
2442 nesting_info
2443 .and_then(|ni| ni.get(key))
2444 .or_else(|| inferred_nesting.get(key))
2445 });
2446 let unlinked_target = mig
2452 .and_then(|m| {
2453 mig_assembly::repetition_order::preferred_parent_repetition(
2454 parent,
2455 &m.segment_groups,
2456 child_id,
2457 )
2458 })
2459 .unwrap_or(0);
2460 for (i, child_rep) in child_group.repetitions.into_iter().enumerate() {
2461 let target_idx = distribution
2462 .and_then(|dist| dist.get(i))
2463 .copied()
2464 .unwrap_or(unlinked_target);
2465
2466 if let Some(target_rep) = parent.repetitions.get_mut(target_idx) {
2467 if let Some(existing) = target_rep
2468 .child_groups
2469 .iter_mut()
2470 .find(|g| g.group_id == *child_id)
2471 {
2472 existing.repetitions.push(child_rep);
2473 } else {
2474 target_rep.child_groups.push(AssembledGroup {
2475 group_id: child_id.clone(),
2476 repetitions: vec![child_rep],
2477 });
2478 }
2479 }
2480 }
2481 }
2482 }
2483
2484 let post_group_start = root_segments.len();
2485 AssembledTree {
2486 segments: root_segments,
2487 groups,
2488 post_group_start,
2489 inter_group_segments: std::collections::BTreeMap::new(),
2490 }
2491 }
2492
2493 fn count_repetitions(tree: &AssembledTree, group_path: &str) -> usize {
2495 let parts: Vec<&str> = group_path.split('.').collect();
2496
2497 let (first_id, first_rep) = parse_group_spec(parts[0]);
2498 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2499 Some(g) => g,
2500 None => return 0,
2501 };
2502
2503 if parts.len() == 1 {
2504 return first_group.repetitions.len();
2505 }
2506
2507 let mut current_instance = match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2509 Some(i) => i,
2510 None => return 0,
2511 };
2512
2513 for (i, part) in parts[1..].iter().enumerate() {
2514 let (group_id, explicit_rep) = parse_group_spec(part);
2515 let child_group = match current_instance
2516 .child_groups
2517 .iter()
2518 .find(|g| g.group_id == group_id)
2519 {
2520 Some(g) => g,
2521 None => return 0,
2522 };
2523
2524 if i == parts.len() - 2 {
2525 return child_group.repetitions.len();
2527 }
2528 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2529 Some(i) => i,
2530 None => return 0,
2531 };
2532 }
2533
2534 0
2535 }
2536
2537 pub fn translate_edifact_to_bo4e(
2545 msg_engine: &MappingEngine,
2546 tx_engine: &MappingEngine,
2547 tree: &AssembledTree,
2548 transaction_group: &str,
2549 ) -> crate::model::MappedMessage {
2550 Self::map_interchange_inner(msg_engine, tx_engine, tree, transaction_group, false)
2551 }
2552
2553 pub fn enrich_bo4e_types(
2567 msg_engine: &MappingEngine,
2568 tx_engine: &MappingEngine,
2569 mapped: &mut crate::model::MappedMessage,
2570 ) {
2571 msg_engine.enrich_entities(&mut mapped.stammdaten);
2572 for tx in &mut mapped.transaktionen {
2573 tx_engine.enrich_entities(&mut tx.stammdaten);
2574 }
2575
2576 msg_engine.enrich_named_entity(
2580 &mut mapped.nachricht_meta,
2581 crate::model::MSG_METADATA_ENTITY,
2582 );
2583 for tx in &mut mapped.transaktionen {
2584 tx_engine
2585 .enrich_named_entity(&mut tx.transaktionsdaten, crate::model::TX_METADATA_ENTITY);
2586 }
2587 }
2588
2589 fn enrich_named_entity(&self, value: &mut serde_json::Value, entity_key: &str) {
2594 if self.code_lookup.is_none() || value.is_null() {
2595 return;
2596 }
2597 let sites = self.code_sites();
2598 if let Some(entity_sites) = sites.get(entity_key) {
2599 Self::apply_sites(self, value, entity_sites);
2600 }
2601 }
2602
2603 fn enrich_entities(&self, value: &mut serde_json::Value) {
2609 if self.code_lookup.is_none() {
2610 return;
2611 }
2612 let sites: HashMap<String, Vec<CodeSite<'_>>> = self.code_sites();
2613 if sites.is_empty() {
2614 return;
2615 }
2616 Self::walk_and_enrich(self, value, &sites);
2617 }
2618
2619 fn code_sites(&self) -> HashMap<String, Vec<CodeSite<'_>>> {
2622 let Some(ref code_lookup) = self.code_lookup else {
2623 return HashMap::new();
2624 };
2625 let mut sites: HashMap<String, Vec<CodeSite<'_>>> = HashMap::new();
2626
2627 for def in &self.definitions {
2628 let Some(ref source_path) = def.meta.source_path else {
2629 continue;
2630 };
2631 let entity_key = to_camel_case(&def.meta.entity);
2632
2633 for (path, field_mapping) in &def.fields {
2634 let (target, enum_map, also_target, also_enum_map) = match field_mapping {
2635 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
2636 FieldMapping::Structured(s) => (
2637 s.target.as_str(),
2638 self.table(s.enum_map.as_ref(), s.code_list.as_deref()),
2639 s.also_target.as_deref(),
2640 self.table(s.also_enum_map.as_ref(), s.also_code_list.as_deref()),
2641 ),
2642 FieldMapping::Nested(_) => continue,
2643 };
2644 if target.is_empty() {
2645 continue;
2646 }
2647
2648 let parts: Vec<&str> = path.split('.').collect();
2649 let (seg_tag, path_qualifier, _occ) = parse_tag_qualifier(parts[0]);
2650 let (element_idx, component_idx) = Self::parse_element_component(&parts[1..]);
2651 let disc_qualifier = Self::discriminator_qualifier_for_tag(def, &seg_tag);
2658 if code_lookup
2659 .enrichment_codes(
2660 source_path,
2661 &seg_tag,
2662 path_qualifier,
2663 disc_qualifier.as_deref(),
2664 element_idx,
2665 component_idx,
2666 )
2667 .is_none()
2668 {
2669 continue;
2670 }
2671
2672 sites.entry(entity_key.clone()).or_default().push(CodeSite {
2673 target,
2674 parent_field: def.meta.parent_field.as_deref(),
2675 source_path,
2676 seg_tag,
2677 path_qualifier: path_qualifier.map(str::to_string),
2678 disc_qualifier,
2679 element_idx,
2680 component_idx,
2681 enum_map,
2682 also_target,
2683 also_enum_map,
2684 });
2685 }
2686 }
2687 sites
2688 }
2689
2690 fn walk_and_enrich(
2693 engine: &MappingEngine,
2694 value: &mut serde_json::Value,
2695 sites: &HashMap<String, Vec<CodeSite<'_>>>,
2696 ) {
2697 match value {
2698 serde_json::Value::Object(map) => {
2699 for (key, child) in map.iter_mut() {
2700 if let Some(entity_sites) = sites.get(key.as_str()) {
2701 Self::apply_sites(engine, child, entity_sites);
2702 }
2703 Self::walk_and_enrich(engine, child, sites);
2704 }
2705 }
2706 serde_json::Value::Array(items) => {
2707 for item in items.iter_mut() {
2708 Self::walk_and_enrich(engine, item, sites);
2709 }
2710 }
2711 _ => {}
2712 }
2713 }
2714
2715 fn apply_sites(engine: &MappingEngine, value: &mut serde_json::Value, sites: &[CodeSite<'_>]) {
2718 match value {
2719 serde_json::Value::Array(items) => {
2720 for item in items.iter_mut() {
2721 Self::apply_sites(engine, item, sites);
2722 }
2723 }
2724 serde_json::Value::Object(_) => {
2725 for site in sites {
2726 match site.parent_field {
2727 None => engine.enrich_one(value, site),
2728 Some(field) => {
2729 if let Some(nested) = value.get_mut(field) {
2730 Self::apply_nested_site(engine, nested, site);
2731 }
2732 }
2733 }
2734 }
2735 }
2736 _ => {}
2737 }
2738 }
2739
2740 fn apply_nested_site(
2742 engine: &MappingEngine,
2743 value: &mut serde_json::Value,
2744 site: &CodeSite<'_>,
2745 ) {
2746 match value {
2747 serde_json::Value::Array(items) => {
2748 for item in items.iter_mut() {
2749 Self::apply_nested_site(engine, item, site);
2750 }
2751 }
2752 serde_json::Value::Object(_) => engine.enrich_one(value, site),
2753 _ => {}
2754 }
2755 }
2756
2757 fn enrich_one(&self, entity: &mut serde_json::Value, site: &CodeSite<'_>) {
2759 let Some(ref code_lookup) = self.code_lookup else {
2760 return;
2761 };
2762 let Some(mapped_val) = Self::read_plain_string(entity, site.target) else {
2764 return;
2765 };
2766
2767 let raw = match site.enum_map {
2770 None => mapped_val.clone(),
2771 Some(map) => {
2772 let joint = match (site.also_target, site.also_enum_map) {
2773 (Some(also), Some(also_map)) => {
2774 Self::read_plain_string(entity, also).and_then(|also_v| {
2775 map.iter()
2776 .find(|(code, bo4e_v)| {
2777 *bo4e_v == &mapped_val && also_map.get(*code) == Some(&also_v)
2778 })
2779 .map(|(code, _)| code.clone())
2780 })
2781 }
2782 _ => None,
2783 };
2784 joint
2785 .or_else(|| {
2786 map.iter()
2787 .find(|(_, bo4e_v)| *bo4e_v == &mapped_val)
2788 .map(|(code, _)| code.clone())
2789 })
2790 .unwrap_or_else(|| mapped_val.clone())
2791 }
2792 };
2793
2794 let Some(codes) = code_lookup.enrichment_codes(
2795 site.source_path,
2796 &site.seg_tag,
2797 site.path_qualifier.as_deref(),
2798 site.disc_qualifier.as_deref(),
2799 site.element_idx,
2800 site.component_idx,
2801 ) else {
2802 return;
2803 };
2804
2805 if let Some(ref pid) = self.current_pid {
2807 if codes.len() == 1 && codes.contains_key(pid.as_str()) {
2808 return;
2809 }
2810 }
2811
2812 let enrichment = codes.get(&raw);
2813 let meaning = enrichment
2814 .map(|e| serde_json::Value::String(e.meaning.clone()))
2815 .unwrap_or(serde_json::Value::Null);
2816
2817 let mut obj = serde_json::Map::new();
2818 obj.insert("code".into(), serde_json::json!(mapped_val));
2819 obj.insert("meaning".into(), meaning);
2820 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
2821 obj.insert("enum".into(), serde_json::json!(enum_key));
2822 }
2823
2824 if let serde_json::Value::Object(map) = entity {
2825 set_nested_value_json(map, site.target, serde_json::Value::Object(obj));
2826 }
2827 }
2828
2829 fn read_plain_string(entity: &serde_json::Value, target: &str) -> Option<String> {
2832 let mut current = entity;
2833 for part in target.split('.') {
2834 current = current.get(part)?;
2835 }
2836 current.as_str().map(str::to_string)
2837 }
2838
2839 pub fn map_interchange(
2848 msg_engine: &MappingEngine,
2849 tx_engine: &MappingEngine,
2850 tree: &AssembledTree,
2851 transaction_group: &str,
2852 enrich_codes: bool,
2853 ) -> crate::model::MappedMessage {
2854 let mut mapped =
2855 Self::translate_edifact_to_bo4e(msg_engine, tx_engine, tree, transaction_group);
2856 if enrich_codes {
2857 Self::enrich_bo4e_types(msg_engine, tx_engine, &mut mapped);
2858 }
2859 mapped
2860 }
2861
2862 #[doc(hidden)]
2871 pub fn map_interchange_inner_for_test(
2872 msg_engine: &MappingEngine,
2873 tx_engine: &MappingEngine,
2874 tree: &AssembledTree,
2875 transaction_group: &str,
2876 enrich_codes: bool,
2877 ) -> crate::model::MappedMessage {
2878 Self::map_interchange_inner(msg_engine, tx_engine, tree, transaction_group, enrich_codes)
2879 }
2880
2881 pub(crate) fn map_interchange_inner(
2882 msg_engine: &MappingEngine,
2883 tx_engine: &MappingEngine,
2884 tree: &AssembledTree,
2885 transaction_group: &str,
2886 enrich_codes: bool,
2887 ) -> crate::model::MappedMessage {
2888 let (stammdaten, nesting_info) = msg_engine.map_all_forward_inner(tree, enrich_codes);
2890
2891 let transaktionen = tree
2893 .groups
2894 .iter()
2895 .find(|g| g.group_id == transaction_group)
2896 .map(|sg| {
2897 sg.repetitions
2898 .iter()
2899 .map(|instance| {
2900 let wrapped_tree = AssembledTree {
2903 segments: vec![],
2904 groups: vec![AssembledGroup {
2905 group_id: transaction_group.to_string(),
2906 repetitions: vec![instance.clone()],
2907 }],
2908 post_group_start: 0,
2909 inter_group_segments: std::collections::BTreeMap::new(),
2910 };
2911
2912 let (tx_result, tx_nesting) = tx_engine.map_all_forward_inner_with_tx(
2916 &wrapped_tree,
2917 enrich_codes,
2918 Some(transaction_group),
2919 );
2920
2921 let mut tx_result = tx_result;
2926 let transaktionsdaten = crate::model::take_entity(
2927 &mut tx_result,
2928 crate::model::TX_METADATA_ENTITY,
2929 );
2930
2931 crate::model::MappedTransaktion {
2932 stammdaten: tx_result,
2933 transaktionsdaten,
2934 nesting_info: tx_nesting,
2935 }
2936 })
2937 .collect()
2938 })
2939 .unwrap_or_default();
2940
2941 let mut stammdaten = stammdaten;
2943 let nachricht_meta =
2944 crate::model::take_entity(&mut stammdaten, crate::model::MSG_METADATA_ENTITY);
2945
2946 crate::model::MappedMessage {
2947 stammdaten,
2948 nachricht_meta,
2949 transaktionen,
2950 nesting_info,
2951 inter_group_segments: tree.inter_group_segments.clone(),
2952 }
2953 }
2954
2955 pub fn map_interchange_reverse(
2965 msg_engine: &MappingEngine,
2966 tx_engine: &MappingEngine,
2967 mapped: &crate::model::MappedMessage,
2968 transaction_group: &str,
2969 filtered_mig: Option<&MigSchema>,
2970 ) -> AssembledTree {
2971 let _owned_msg: Option<serde_json::Value>;
2979 let msg_stammdaten = if !mapped.nachricht_meta.is_null() {
2980 let mut merged = mapped.stammdaten.clone();
2981 crate::model::restore_entity(
2982 &mut merged,
2983 crate::model::MSG_METADATA_ENTITY,
2984 &mapped.nachricht_meta,
2985 );
2986 _owned_msg = Some(merged);
2987 _owned_msg.as_ref().unwrap()
2988 } else {
2989 _owned_msg = None;
2990 &mapped.stammdaten
2991 };
2992
2993 let msg_tree = msg_engine.map_all_reverse_with_mig(
2994 msg_stammdaten,
2995 if mapped.nesting_info.is_empty() {
2996 None
2997 } else {
2998 Some(&mapped.nesting_info)
2999 },
3000 filtered_mig,
3001 );
3002
3003 let mut sg4_reps: Vec<AssembledGroupInstance> = Vec::new();
3005
3006 struct DefWithMeta<'a> {
3010 def: &'a MappingDefinition,
3011 relative: String,
3012 depth: usize,
3013 }
3014
3015 let mut sorted_defs: Vec<DefWithMeta> = tx_engine
3016 .definitions
3017 .iter()
3018 .filter(|def| def.meta.parent_field.is_none())
3021 .map(|def| {
3022 let relative = strip_tx_group_prefix(&def.meta.source_group, transaction_group);
3023 let depth = if relative.is_empty() {
3024 0
3025 } else {
3026 relative.chars().filter(|c| *c == '.').count() + 1
3027 };
3028 DefWithMeta {
3029 def,
3030 relative,
3031 depth,
3032 }
3033 })
3034 .collect();
3035
3036 let mut parent_rep_map: std::collections::HashMap<String, usize> =
3040 std::collections::HashMap::new();
3041 for dm in &sorted_defs {
3042 if dm.depth >= 2 {
3043 let parts: Vec<&str> = dm.relative.split('.').collect();
3044 let (_, parent_rep) = parse_group_spec(parts[0]);
3045 if let Some(rep_idx) = parent_rep {
3046 if let Some(sp) = &dm.def.meta.source_path {
3047 if let Some((parent_path, _)) = sp.rsplit_once('.') {
3048 parent_rep_map
3049 .entry(parent_path.to_string())
3050 .or_insert(rep_idx);
3051 }
3052 }
3053 }
3054 }
3055 }
3056
3057 for dm in &mut sorted_defs {
3060 if dm.depth == 1 && !dm.relative.contains(':') {
3061 if let Some(sp) = &dm.def.meta.source_path {
3062 if let Some(rep_idx) = parent_rep_map.get(sp.as_str()) {
3063 dm.relative = format!("{}:{}", dm.relative, rep_idx);
3064 }
3065 }
3066 }
3067 }
3068
3069 if let Some(mig) = filtered_mig {
3076 let mig_order = build_reverse_mig_group_order(mig, transaction_group);
3077 sorted_defs.sort_by(|a, b| {
3078 a.depth.cmp(&b.depth).then_with(|| {
3079 let a_id = a.relative.split(':').next().unwrap_or(&a.relative);
3080 let b_id = b.relative.split(':').next().unwrap_or(&b.relative);
3081 let a_pos = variant_mig_position(a.def, a_id, &mig_order);
3083 let b_pos = variant_mig_position(b.def, b_id, &mig_order);
3084 a_pos.cmp(&b_pos).then(a.relative.cmp(&b.relative))
3085 })
3086 });
3087 } else {
3088 sorted_defs.sort_by(|a, b| a.depth.cmp(&b.depth).then(a.relative.cmp(&b.relative)));
3089 }
3090
3091 for tx in &mapped.transaktionen {
3092 let mut root_segs: Vec<AssembledSegment> = Vec::new();
3093 let mut child_groups: Vec<AssembledGroup> = Vec::new();
3094
3095 let _owned_tx: Option<serde_json::Value>;
3098 let tx_stammdaten: &serde_json::Value = if !tx.transaktionsdaten.is_null() {
3099 let mut merged = tx.stammdaten.clone();
3100 crate::model::restore_entity(
3101 &mut merged,
3102 crate::model::TX_METADATA_ENTITY,
3103 &tx.transaktionsdaten,
3104 );
3105 _owned_tx = Some(merged);
3106 _owned_tx.as_ref().unwrap()
3107 } else {
3108 _owned_tx = None;
3109 &tx.stammdaten
3110 };
3111
3112 let mut source_path_to_rep: std::collections::HashMap<String, Vec<usize>> =
3117 std::collections::HashMap::new();
3118
3119 for dm in &sorted_defs {
3120 let entity_key = to_camel_case(&dm.def.meta.entity);
3123 let _tx_extracted: Option<serde_json::Value>;
3124 let bo4e_value = if let Some(v) = tx_stammdaten.get(&entity_key) {
3125 _tx_extracted = None;
3126 v
3127 } else if dm.def.meta.source_group.contains('.') {
3128 match extract_child_from_parent(tx_stammdaten, &tx_engine.definitions, dm.def) {
3129 Some(v) => {
3130 _tx_extracted = Some(v);
3131 _tx_extracted.as_ref().unwrap()
3132 }
3133 None => continue,
3134 }
3135 } else {
3136 continue;
3137 };
3138
3139 let unwrapped_value: Option<serde_json::Value>;
3141 let bo4e_value = if bo4e_value.is_object() && !bo4e_value.is_array() {
3142 if let Some(disc_value) = dm
3143 .def
3144 .meta
3145 .discriminator
3146 .as_deref()
3147 .and_then(|d| d.split_once('='))
3148 .map(|(_, v)| v)
3149 {
3150 if let Some(inner) = bo4e_value.get(disc_value) {
3151 let mut injected = inner.clone();
3152 if let Some(qualifier_field) = find_qualifier_companion_field(
3153 &tx_engine.definitions,
3154 &dm.def.meta.entity,
3155 ) {
3156 if let Some(obj) = injected.as_object_mut() {
3157 obj.entry(qualifier_field).or_insert_with(|| {
3158 serde_json::Value::String(disc_value.to_string())
3159 });
3160 }
3161 }
3162 unwrapped_value = Some(injected);
3163 unwrapped_value.as_ref().unwrap()
3164 } else {
3165 bo4e_value
3166 }
3167 } else if is_map_keyed_object(bo4e_value) {
3168 let map = bo4e_value.as_object().unwrap();
3169 let arr: Vec<serde_json::Value> = map
3170 .iter()
3171 .map(|(key, val)| {
3172 let mut item = val.clone();
3173 if let Some(obj) = item.as_object_mut() {
3174 if let Some(qualifier_field) = find_qualifier_companion_field(
3175 &tx_engine.definitions,
3176 &dm.def.meta.entity,
3177 ) {
3178 let entry = obj
3179 .entry(qualifier_field)
3180 .or_insert(serde_json::Value::Null);
3181 if entry.is_null() {
3182 *entry = serde_json::Value::String(key.clone());
3183 }
3184 }
3185 }
3186 item
3187 })
3188 .collect();
3189 unwrapped_value = Some(serde_json::Value::Array(arr));
3190 unwrapped_value.as_ref().unwrap()
3191 } else {
3192 bo4e_value
3193 }
3194 } else {
3195 bo4e_value
3196 };
3197
3198 let items: Vec<&serde_json::Value> = if bo4e_value.is_array() {
3202 bo4e_value.as_array().unwrap().iter().collect()
3203 } else {
3204 vec![bo4e_value]
3205 };
3206
3207 for (item_idx, item) in items.iter().enumerate() {
3208 let instance = tx_engine.map_reverse(item, dm.def);
3209
3210 if instance.segments.is_empty() && instance.child_groups.is_empty() {
3212 continue;
3213 }
3214
3215 if dm.relative.is_empty() {
3216 root_segs.extend(instance.segments);
3223 for child in instance.child_groups {
3224 match child_groups
3225 .iter_mut()
3226 .find(|g| g.group_id == child.group_id)
3227 {
3228 Some(existing) => existing.repetitions.extend(child.repetitions),
3229 None => child_groups.push(child),
3230 }
3231 }
3232 } else {
3233 let effective_relative = if dm.depth >= 2 {
3237 let rel = if items.len() > 1 {
3240 strip_all_rep_indices(&dm.relative)
3241 } else {
3242 dm.relative.clone()
3243 };
3244 let skip_nesting = dm
3251 .def
3252 .meta
3253 .source_path
3254 .as_ref()
3255 .and_then(|sp| sp.rsplit_once('.'))
3256 .and_then(|(parent_path, _)| source_path_to_rep.get(parent_path))
3257 .is_some_and(|reps| reps.len() == 1);
3258 let nesting_idx = if items.len() > 1 && !skip_nesting {
3259 dm.def
3260 .meta
3261 .source_path
3262 .as_ref()
3263 .and_then(|sp| tx.nesting_info.get(sp))
3264 .and_then(|dist| dist.get(item_idx))
3265 .copied()
3266 } else {
3267 None
3268 };
3269 if let Some(parent_rep) = nesting_idx {
3270 let parts: Vec<&str> = rel.split('.').collect();
3272 let parent_id = parts[0].split(':').next().unwrap_or(parts[0]);
3273 let rest = parts[1..].join(".");
3274 format!("{}:{}.{}", parent_id, parent_rep, rest)
3275 } else {
3276 resolve_child_relative(
3277 &rel,
3278 dm.def.meta.source_path.as_deref(),
3279 &source_path_to_rep,
3280 item_idx,
3281 )
3282 }
3283 } else if dm.depth == 1 {
3284 let child_key = dm
3287 .def
3288 .meta
3289 .source_path
3290 .as_ref()
3291 .map(|sp| format!("{sp}#child"));
3292 if let Some(child_indices) =
3293 child_key.as_ref().and_then(|ck| tx.nesting_info.get(ck))
3294 {
3295 if let Some(&target) = child_indices.get(item_idx) {
3296 if target != usize::MAX {
3297 let base =
3298 dm.relative.split(':').next().unwrap_or(&dm.relative);
3299 format!("{}:{}", base, target)
3300 } else {
3301 dm.relative.clone()
3302 }
3303 } else if items.len() > 1 && item_idx > 0 {
3304 strip_rep_index(&dm.relative)
3305 } else {
3306 dm.relative.clone()
3307 }
3308 } else if items.len() > 1 && item_idx > 0 {
3309 strip_rep_index(&dm.relative)
3310 } else {
3311 dm.relative.clone()
3312 }
3313 } else if items.len() > 1 && item_idx > 0 {
3314 strip_rep_index(&dm.relative)
3317 } else {
3318 dm.relative.clone()
3319 };
3320
3321 let rep_used =
3322 place_in_groups(&mut child_groups, &effective_relative, instance);
3323
3324 if dm.depth == 1 {
3326 if let Some(sp) = &dm.def.meta.source_path {
3327 source_path_to_rep
3328 .entry(sp.clone())
3329 .or_default()
3330 .push(rep_used);
3331 }
3332 }
3333 }
3334 }
3335 }
3336
3337 sg4_reps.push(AssembledGroupInstance {
3338 segments: root_segs,
3339 child_groups,
3340 entry_mig_number: None,
3341 variant_mig_numbers: vec![],
3342 skipped_segments: Vec::new(),
3343 skipped_positions: Vec::new(),
3344 });
3345 }
3346
3347 let mut root_segments = Vec::new();
3354 let mut uns_segments = Vec::new();
3355 let mut uns_is_summary = false;
3356 let mut found_uns = false;
3357 for seg in msg_tree.segments {
3358 if seg.tag == "UNS" {
3359 uns_is_summary = seg
3361 .elements
3362 .first()
3363 .and_then(|el| el.first())
3364 .map(|v| v == "S")
3365 .unwrap_or(false);
3366 uns_segments.push(seg);
3367 found_uns = true;
3368 } else if found_uns {
3369 uns_segments.push(seg);
3371 } else {
3372 root_segments.push(seg);
3373 }
3374 }
3375
3376 let pre_group_count = root_segments.len();
3377 let mut all_groups = msg_tree.groups;
3378 let mut inter_group = msg_tree.inter_group_segments;
3379
3380 let sg_num = |id: &str| -> usize {
3382 id.strip_prefix("SG")
3383 .and_then(|n| n.parse::<usize>().ok())
3384 .unwrap_or(0)
3385 };
3386
3387 if !sg4_reps.is_empty() {
3388 if uns_is_summary {
3389 all_groups.push(AssembledGroup {
3391 group_id: transaction_group.to_string(),
3392 repetitions: sg4_reps,
3393 });
3394 if !uns_segments.is_empty() {
3395 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3400 let tx_num = sg_num(transaction_group);
3401 let uns_pos = all_groups
3402 .iter()
3403 .rposition(|g| sg_num(&g.group_id) <= tx_num)
3404 .map(|i| i + 1)
3405 .unwrap_or(all_groups.len());
3406 inter_group.insert(uns_pos, uns_segments);
3407 }
3408 } else {
3409 if !uns_segments.is_empty() {
3411 inter_group.insert(all_groups.len(), uns_segments);
3412 }
3413 all_groups.push(AssembledGroup {
3414 group_id: transaction_group.to_string(),
3415 repetitions: sg4_reps,
3416 });
3417 }
3418 } else if !uns_segments.is_empty() {
3419 if transaction_group.is_empty() {
3420 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3425 if uns_is_summary {
3426 inter_group.insert(all_groups.len(), uns_segments);
3427 } else {
3428 inter_group.insert(0, uns_segments);
3429 }
3430 } else {
3431 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3435 let tx_num = sg_num(transaction_group);
3436 let uns_pos = all_groups
3437 .iter()
3438 .rposition(|g| sg_num(&g.group_id) <= tx_num)
3439 .map(|i| i + 1)
3440 .unwrap_or(all_groups.len());
3441 inter_group.insert(uns_pos, uns_segments);
3442 }
3443 }
3444
3445 for (k, segs) in &mapped.inter_group_segments {
3453 if segs.is_empty() {
3454 continue;
3455 }
3456 let existing_tags: std::collections::HashSet<String> = inter_group
3457 .get(k)
3458 .map(|v| v.iter().map(|s| s.tag.clone()).collect())
3459 .unwrap_or_default();
3460 for seg in segs {
3461 if existing_tags.contains(&seg.tag) {
3462 continue;
3463 }
3464 inter_group.entry(*k).or_default().push(seg.clone());
3465 }
3466 }
3467
3468 let mut tree = AssembledTree {
3469 segments: root_segments,
3470 groups: all_groups,
3471 post_group_start: pre_group_count,
3472 inter_group_segments: inter_group,
3473 };
3474
3475 if let Some(mig) = filtered_mig {
3482 mig_assembly::repetition_order::sort_repetitions_by_mig_variant(
3483 &mut tree,
3484 mig,
3485 (!transaction_group.is_empty()).then_some(transaction_group),
3486 );
3487 }
3488 tree
3489 }
3490
3491 pub fn build_group_from_bo4e(
3493 &self,
3494 bo4e_value: &serde_json::Value,
3495 def: &MappingDefinition,
3496 ) -> AssembledGroup {
3497 let instance = self.map_reverse(bo4e_value, def);
3498 let leaf_group = def
3499 .meta
3500 .source_group
3501 .rsplit('.')
3502 .next()
3503 .unwrap_or(&def.meta.source_group);
3504
3505 AssembledGroup {
3506 group_id: leaf_group.to_string(),
3507 repetitions: vec![instance],
3508 }
3509 }
3510
3511 pub fn map_interchange_typed<M, T>(
3519 msg_engine: &MappingEngine,
3520 tx_engine: &MappingEngine,
3521 tree: &AssembledTree,
3522 tx_group: &str,
3523 enrich_codes: bool,
3524 nachrichtendaten: crate::model::Nachrichtendaten,
3525 interchangedaten: crate::model::Interchangedaten,
3526 ) -> Result<crate::model::Interchange<M, T>, serde_json::Error>
3527 where
3528 M: serde::de::DeserializeOwned,
3529 T: serde::de::DeserializeOwned,
3530 {
3531 let mapped = Self::map_interchange(msg_engine, tx_engine, tree, tx_group, enrich_codes);
3532 let nachricht = mapped.into_dynamic_nachricht(nachrichtendaten);
3533 let dynamic = crate::model::DynamicInterchange {
3534 interchangedaten,
3535 nachrichten: vec![nachricht],
3536 };
3537 let value = serde_json::to_value(&dynamic)?;
3538 serde_json::from_value(value)
3539 }
3540
3541 pub fn map_interchange_reverse_typed<M, T>(
3548 msg_engine: &MappingEngine,
3549 tx_engine: &MappingEngine,
3550 nachricht: &crate::model::Nachricht<M, T>,
3551 tx_group: &str,
3552 ) -> Result<AssembledTree, serde_json::Error>
3553 where
3554 M: serde::Serialize,
3555 T: serde::Serialize,
3556 {
3557 let mut stammdaten = serde_json::to_value(&nachricht.stammdaten)?;
3560 crate::model::restore_message_metadata(&mut stammdaten, &nachricht.nachrichtendaten);
3561 let transaktionen: Vec<crate::model::MappedTransaktion> = nachricht
3562 .transaktionen
3563 .iter()
3564 .map(|t| {
3565 Ok(crate::model::MappedTransaktion {
3566 stammdaten: serde_json::to_value(t)?,
3567 transaktionsdaten: serde_json::Value::Null,
3568 nesting_info: Default::default(),
3569 })
3570 })
3571 .collect::<Result<Vec<_>, serde_json::Error>>()?;
3572 let mapped = crate::model::MappedMessage {
3573 stammdaten,
3574 nachricht_meta: serde_json::Value::Null,
3575 transaktionen,
3576 nesting_info: Default::default(),
3577 inter_group_segments: Default::default(),
3578 };
3579 Ok(Self::map_interchange_reverse(
3580 msg_engine, tx_engine, &mapped, tx_group, None,
3581 ))
3582 }
3583}
3584
3585fn parse_source_path_part(part: &str) -> (&str, Option<&str>) {
3592 if let Some(pos) = part.find('_') {
3596 let group = &part[..pos];
3597 let qualifier = &part[pos + 1..];
3598 if !qualifier.is_empty() {
3599 return (group, Some(qualifier));
3600 }
3601 }
3602 (part, None)
3603}
3604
3605fn build_reverse_mig_group_order(mig: &MigSchema, tx_group_id: &str) -> HashMap<String, usize> {
3613 let mut order = HashMap::new();
3614 if let Some(tg) = mig.segment_groups.iter().find(|g| g.id == tx_group_id) {
3615 for (i, nested) in tg.nested_groups.iter().enumerate() {
3616 if let Some(ref vc) = nested.variant_code {
3618 let variant_key = format!("{}_{}", nested.id, vc.to_uppercase());
3619 order.insert(variant_key, i);
3620 }
3621 order.entry(nested.id.clone()).or_insert(i);
3623 }
3624 }
3625 order
3626}
3627
3628fn variant_mig_position(
3634 def: &MappingDefinition,
3635 base_group_id: &str,
3636 mig_order: &HashMap<String, usize>,
3637) -> usize {
3638 if let Some(ref sp) = def.meta.source_path {
3641 let base_lower = base_group_id.to_lowercase();
3643 for part in sp.split('.') {
3644 if part.starts_with(&base_lower)
3645 || part.starts_with(base_group_id.to_lowercase().as_str())
3646 {
3647 if let Some(underscore_pos) = part.find('_') {
3649 let qualifier = &part[underscore_pos + 1..];
3650 let variant_key = format!("{}_{}", base_group_id, qualifier.to_uppercase());
3651 if let Some(&pos) = mig_order.get(&variant_key) {
3652 return pos;
3653 }
3654 }
3655 }
3656 }
3657 }
3658 mig_order.get(base_group_id).copied().unwrap_or(usize::MAX)
3660}
3661
3662fn find_rep_by_entry_qualifier<'a>(
3667 reps: &'a [AssembledGroupInstance],
3668 qualifier: &str,
3669) -> Option<&'a AssembledGroupInstance> {
3670 let parts: Vec<&str> = qualifier.split('_').collect();
3672 reps.iter().find(|inst| {
3673 inst.segments.first().is_some_and(|seg| {
3674 seg.elements
3675 .first()
3676 .and_then(|e| e.first())
3677 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3678 })
3679 })
3680}
3681
3682fn find_all_reps_by_entry_qualifier<'a>(
3684 reps: &'a [AssembledGroupInstance],
3685 qualifier: &str,
3686) -> Vec<&'a AssembledGroupInstance> {
3687 let parts: Vec<&str> = qualifier.split('_').collect();
3689 reps.iter()
3690 .filter(|inst| {
3691 inst.segments.first().is_some_and(|seg| {
3692 seg.elements
3693 .first()
3694 .and_then(|e| e.first())
3695 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3696 })
3697 })
3698 .collect()
3699}
3700
3701fn has_source_path_qualifiers(source_path: &str) -> bool {
3703 source_path.split('.').any(|part| {
3704 if let Some(pos) = part.find('_') {
3705 pos < part.len() - 1
3706 } else {
3707 false
3708 }
3709 })
3710}
3711
3712fn parse_group_spec(part: &str) -> (&str, Option<usize>) {
3713 if let Some(colon_pos) = part.find(':') {
3714 let id = &part[..colon_pos];
3715 let rep = part[colon_pos + 1..].parse::<usize>().ok();
3716 (id, rep)
3717 } else {
3718 (part, None)
3719 }
3720}
3721
3722fn strip_tx_group_prefix(source_group: &str, tx_group: &str) -> String {
3728 if source_group == tx_group || source_group.is_empty() {
3729 String::new()
3730 } else if let Some(rest) = source_group.strip_prefix(tx_group) {
3731 rest.strip_prefix('.').unwrap_or(rest).to_string()
3732 } else {
3733 source_group.to_string()
3734 }
3735}
3736
3737fn place_in_groups(
3745 groups: &mut Vec<AssembledGroup>,
3746 relative_path: &str,
3747 instance: AssembledGroupInstance,
3748) -> usize {
3749 let parts: Vec<&str> = relative_path.split('.').collect();
3750
3751 if parts.len() == 1 {
3752 let (id, rep) = parse_group_spec(parts[0]);
3754
3755 let group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == id) {
3757 g
3758 } else {
3759 groups.push(AssembledGroup {
3760 group_id: id.to_string(),
3761 repetitions: vec![],
3762 });
3763 groups.last_mut().unwrap()
3764 };
3765
3766 if let Some(rep_idx) = rep {
3767 while group.repetitions.len() <= rep_idx {
3769 group.repetitions.push(AssembledGroupInstance {
3770 segments: vec![],
3771 child_groups: vec![],
3772 entry_mig_number: None,
3773 variant_mig_numbers: vec![],
3774 skipped_segments: Vec::new(),
3775 skipped_positions: Vec::new(),
3776 });
3777 }
3778 group.repetitions[rep_idx]
3779 .segments
3780 .extend(instance.segments);
3781 group.repetitions[rep_idx]
3782 .child_groups
3783 .extend(instance.child_groups);
3784 rep_idx
3785 } else {
3786 let pos = group.repetitions.len();
3788 group.repetitions.push(instance);
3789 pos
3790 }
3791 } else {
3792 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3794 let rep_idx = parent_rep.unwrap_or(0);
3795
3796 let parent_group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == parent_id) {
3798 g
3799 } else {
3800 groups.push(AssembledGroup {
3801 group_id: parent_id.to_string(),
3802 repetitions: vec![],
3803 });
3804 groups.last_mut().unwrap()
3805 };
3806
3807 while parent_group.repetitions.len() <= rep_idx {
3809 parent_group.repetitions.push(AssembledGroupInstance {
3810 segments: vec![],
3811 child_groups: vec![],
3812 entry_mig_number: None,
3813 variant_mig_numbers: vec![],
3814 skipped_segments: Vec::new(),
3815 skipped_positions: Vec::new(),
3816 });
3817 }
3818
3819 let remaining = parts[1..].join(".");
3820 place_in_groups(
3821 &mut parent_group.repetitions[rep_idx].child_groups,
3822 &remaining,
3823 instance,
3824 );
3825 rep_idx
3826 }
3827}
3828
3829fn resolve_child_relative(
3841 relative: &str,
3842 source_path: Option<&str>,
3843 source_path_to_rep: &std::collections::HashMap<String, Vec<usize>>,
3844 item_idx: usize,
3845) -> String {
3846 let parts: Vec<&str> = relative.split('.').collect();
3847 if parts.is_empty() {
3848 return relative.to_string();
3849 }
3850
3851 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3853 if parent_rep.is_some() {
3854 return relative.to_string();
3855 }
3856
3857 if let Some(sp) = source_path {
3859 if let Some((parent_path, _child)) = sp.rsplit_once('.') {
3860 if let Some(rep_indices) = source_path_to_rep.get(parent_path) {
3862 let rep_idx = rep_indices
3863 .get(item_idx)
3864 .or_else(|| rep_indices.last())
3865 .copied()
3866 .unwrap_or(0);
3867 let rest = parts[1..].join(".");
3868 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3869 }
3870 let prefix = format!("{}_", parent_path);
3878 let mut unioned: Vec<usize> = source_path_to_rep
3879 .iter()
3880 .filter(|(k, _)| k.starts_with(&prefix))
3881 .flat_map(|(_, v)| v.iter().copied())
3882 .collect();
3883 if !unioned.is_empty() {
3884 unioned.sort_unstable();
3885 unioned.dedup();
3886 let rep_idx = unioned
3887 .get(item_idx)
3888 .or_else(|| unioned.last())
3889 .copied()
3890 .unwrap_or(0);
3891 let rest = parts[1..].join(".");
3892 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3893 }
3894 }
3895 }
3896
3897 relative.to_string()
3899}
3900
3901struct DiscriminatorMatcher<'a> {
3908 tag: &'a str,
3909 element_idx: usize,
3910 component_idx: usize,
3911 expected_values: Vec<&'a str>,
3912 occurrence: Option<usize>,
3914}
3915
3916impl<'a> DiscriminatorMatcher<'a> {
3917 fn parse(disc: &'a str) -> Option<Self> {
3918 let (spec, expected) = disc.split_once('=')?;
3919 let parts: Vec<&str> = spec.split('.').collect();
3920 if parts.len() != 3 {
3921 return None;
3922 }
3923 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
3924 Some(Self {
3925 tag: parts[0],
3926 element_idx: parts[1].parse().ok()?,
3927 component_idx: parts[2].parse().ok()?,
3928 expected_values: expected_raw.split('|').collect(),
3929 occurrence,
3930 })
3931 }
3932
3933 fn matches(&self, instance: &AssembledGroupInstance) -> bool {
3934 instance.segments.iter().any(|s| {
3935 s.tag.eq_ignore_ascii_case(self.tag)
3936 && s.elements
3937 .get(self.element_idx)
3938 .and_then(|e| e.get(self.component_idx))
3939 .map(|v| self.expected_values.iter().any(|ev| v == ev))
3940 .unwrap_or(false)
3941 })
3942 }
3943
3944 fn filter_instances<'b>(
3946 &self,
3947 instances: Vec<&'b AssembledGroupInstance>,
3948 ) -> Vec<&'b AssembledGroupInstance> {
3949 let matching: Vec<_> = instances
3950 .into_iter()
3951 .filter(|inst| self.matches(inst))
3952 .collect();
3953 if let Some(occ) = self.occurrence {
3954 matching.into_iter().nth(occ).into_iter().collect()
3955 } else {
3956 matching
3957 }
3958 }
3959}
3960
3961fn parse_discriminator_occurrence(expected: &str) -> (&str, Option<usize>) {
3967 if let Some(hash_pos) = expected.rfind('#') {
3968 if let Ok(occ) = expected[hash_pos + 1..].parse::<usize>() {
3969 return (&expected[..hash_pos], Some(occ));
3970 }
3971 }
3972 (expected, None)
3973}
3974
3975fn strip_rep_index(relative: &str) -> String {
3979 let (id, _) = parse_group_spec(relative);
3980 id.to_string()
3981}
3982
3983pub(crate) fn strip_all_rep_indices(relative: &str) -> String {
3988 relative
3989 .split('.')
3990 .map(|part| {
3991 let (id, _) = parse_group_spec(part);
3992 id
3993 })
3994 .collect::<Vec<_>>()
3995 .join(".")
3996}
3997
3998pub fn is_nested_child_of(child: &MappingDefinition, parent: &MappingDefinition) -> bool {
4008 if child.meta.parent_field.is_none() || child.meta.entity != parent.meta.entity {
4009 return false;
4010 }
4011 let child_sg = strip_all_rep_indices(&child.meta.source_group);
4012 let parent_sg = strip_all_rep_indices(&parent.meta.source_group);
4013 match child_sg.rsplit_once('.') {
4014 Some((head, _)) if head.eq_ignore_ascii_case(&parent_sg) => {}
4015 _ => return false,
4016 }
4017 let (Some(child_sp), Some(parent_sp)) = (
4018 child.meta.source_path.as_deref(),
4019 parent.meta.source_path.as_deref(),
4020 ) else {
4021 return true;
4022 };
4023 let Some((child_parent_sp, _)) = child_sp.rsplit_once('.') else {
4024 return false;
4025 };
4026 let child_parts: Vec<&str> = child_parent_sp.split('.').collect();
4027 let parent_parts: Vec<&str> = parent_sp.split('.').collect();
4028 child_parts.len() == parent_parts.len()
4029 && child_parts.iter().zip(&parent_parts).all(|(c, p)| {
4030 let (c_id, c_q) = parse_source_path_part(c);
4031 let (p_id, p_q) = parse_source_path_part(p);
4032 c_id.eq_ignore_ascii_case(p_id)
4033 && match (c_q, p_q) {
4034 (Some(cq), Some(pq)) => cq.eq_ignore_ascii_case(pq),
4035 _ => true,
4036 }
4037 })
4038}
4039
4040fn nested_parent_qualifier(child: &MappingDefinition) -> Option<&str> {
4043 let (parent_path, _) = child.meta.source_path.as_deref()?.rsplit_once('.')?;
4044 let last = parent_path.rsplit('.').next()?;
4045 parse_source_path_part(last).1
4046}
4047
4048fn nested_child_leaf(child: &MappingDefinition) -> (String, Option<&str>) {
4051 let leaf_group = strip_all_rep_indices(
4052 child
4053 .meta
4054 .source_group
4055 .rsplit('.')
4056 .next()
4057 .unwrap_or(&child.meta.source_group),
4058 );
4059 let leaf_qualifier = child
4060 .meta
4061 .source_path
4062 .as_deref()
4063 .and_then(|sp| sp.rsplit('.').next())
4064 .and_then(|part| parse_source_path_part(part).1);
4065 (leaf_group, leaf_qualifier)
4066}
4067
4068fn entry_qualifier_matches(instance: &AssembledGroupInstance, qualifier: &str) -> bool {
4071 segment_qualifier_matches(instance.segments.first(), qualifier)
4072}
4073
4074fn rebuilt_entry_qualifier_matches(
4079 instance: &AssembledGroupInstance,
4080 def: &MappingDefinition,
4081 qualifier: &str,
4082) -> bool {
4083 let entry_tag = def
4084 .meta
4085 .discriminator
4086 .as_deref()
4087 .and_then(|d| d.split('.').next())
4088 .filter(|tag| !tag.is_empty());
4089 let entry = match entry_tag {
4090 Some(tag) => instance
4091 .segments
4092 .iter()
4093 .find(|s| s.tag.eq_ignore_ascii_case(tag)),
4094 None => instance.segments.first(),
4095 };
4096 segment_qualifier_matches(entry, qualifier)
4097}
4098
4099fn segment_qualifier_matches(segment: Option<&AssembledSegment>, qualifier: &str) -> bool {
4100 segment
4101 .and_then(|seg| seg.elements.first())
4102 .and_then(|e| e.first())
4103 .is_some_and(|v| qualifier.split('_').any(|q| v.eq_ignore_ascii_case(q)))
4104}
4105
4106pub(crate) fn parse_tag_qualifier(tag_part: &str) -> (String, Option<&str>, usize) {
4113 if let Some(bracket_start) = tag_part.find('[') {
4114 let tag = tag_part[..bracket_start].to_uppercase();
4115 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
4116 if let Some(comma_pos) = inner.find(',') {
4117 let qualifier = &inner[..comma_pos];
4118 let index = inner[comma_pos + 1..].parse::<usize>().unwrap_or(0);
4119 if qualifier == "*" {
4121 (tag, None, index)
4122 } else {
4123 (tag, Some(qualifier), index)
4124 }
4125 } else {
4126 (tag, Some(inner), 0)
4127 }
4128 } else {
4129 (tag_part.to_uppercase(), None, 0)
4130 }
4131}
4132
4133pub fn deep_merge_insert(
4139 result: &mut serde_json::Map<String, serde_json::Value>,
4140 entity: &str,
4141 bo4e: serde_json::Value,
4142) {
4143 if let Some(existing) = result.get_mut(entity) {
4144 if let (Some(existing_arr), Some(new_arr)) =
4147 (existing.as_array().map(|a| a.len()), bo4e.as_array())
4148 {
4149 if existing_arr == new_arr.len() {
4150 let existing_arr = existing.as_array_mut().unwrap();
4151 for (existing_elem, new_elem) in existing_arr.iter_mut().zip(new_arr) {
4152 if let (Some(existing_map), Some(new_map)) =
4153 (existing_elem.as_object_mut(), new_elem.as_object())
4154 {
4155 for (k, v) in new_map {
4156 if let Some(existing_v) = existing_map.get_mut(k) {
4157 if let (Some(existing_inner), Some(new_inner)) =
4158 (existing_v.as_object_mut(), v.as_object())
4159 {
4160 for (ik, iv) in new_inner {
4161 existing_inner
4162 .entry(ik.clone())
4163 .or_insert_with(|| iv.clone());
4164 }
4165 }
4166 } else {
4167 existing_map.insert(k.clone(), v.clone());
4168 }
4169 }
4170 }
4171 }
4172 return;
4173 }
4174 }
4175 if let (Some(existing_map), serde_json::Value::Object(new_map)) =
4177 (existing.as_object_mut(), &bo4e)
4178 {
4179 for (k, v) in new_map {
4180 if let Some(existing_v) = existing_map.get_mut(k) {
4181 if let (Some(existing_inner), Some(new_inner)) =
4183 (existing_v.as_object_mut(), v.as_object())
4184 {
4185 for (ik, iv) in new_inner {
4186 existing_inner
4187 .entry(ik.clone())
4188 .or_insert_with(|| iv.clone());
4189 }
4190 }
4191 } else {
4193 existing_map.insert(k.clone(), v.clone());
4194 }
4195 }
4196 return;
4197 }
4198 }
4199 result.insert(entity.to_string(), bo4e);
4200}
4201
4202fn append_to_list_field(
4215 result: &mut serde_json::Map<String, serde_json::Value>,
4216 entity: &str,
4217 list_field: &str,
4218 bo4e: serde_json::Value,
4219) {
4220 let mut items = match bo4e {
4221 serde_json::Value::Array(a) => a,
4222 other => vec![other],
4223 };
4224 items.retain(|v| !v.as_object().is_some_and(|o| o.is_empty()));
4225 if items.is_empty() {
4226 return;
4227 }
4228 let entry = result
4229 .entry(entity.to_string())
4230 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
4231 let Some(obj) = entry.as_object_mut() else {
4235 return;
4236 };
4237 match obj.get_mut(list_field).and_then(|v| v.as_array_mut()) {
4238 Some(existing) => existing.extend(items),
4239 None => {
4240 obj.insert(list_field.to_string(), serde_json::Value::Array(items));
4241 }
4242 }
4243}
4244
4245fn is_map_keyed_object(value: &serde_json::Value) -> bool {
4256 let Some(obj) = value.as_object() else {
4257 return false;
4258 };
4259 if obj.is_empty() {
4260 return false;
4261 }
4262 obj.iter().all(|(k, v)| {
4264 k.len() <= 5
4265 && k.chars()
4266 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
4267 && v.is_object()
4268 })
4269}
4270
4271fn find_qualifier_companion_field(
4280 definitions: &[crate::definition::MappingDefinition],
4281 entity: &str,
4282) -> Option<String> {
4283 for def in definitions {
4284 if def.meta.entity != *entity || def.meta.parent_field.is_some() {
4285 continue;
4286 }
4287 let disc = def.meta.discriminator.as_deref()?;
4288 let (disc_path, _) = disc.split_once('=')?;
4289 let disc_path_lower = disc_path.to_lowercase();
4290
4291 for (path, mapping) in &def.fields {
4294 let cf_path = path.to_lowercase();
4295 let matches = cf_path == disc_path_lower || format!("{}.0", cf_path) == disc_path_lower;
4296 if matches {
4297 let target = match mapping {
4298 FieldMapping::Simple(t) => t.as_str(),
4299 FieldMapping::Structured(s) => s.target.as_str(),
4300 FieldMapping::Nested(_) => continue,
4301 };
4302 if !target.is_empty() {
4303 return Some(target.to_string());
4304 }
4305 }
4306 }
4307 }
4308 None
4309}
4310
4311fn extract_child_from_parent(
4320 entities: &serde_json::Value,
4321 definitions: &[MappingDefinition],
4322 child_def: &MappingDefinition,
4323) -> Option<serde_json::Value> {
4324 extract_child_from_parent_with_indices(entities, definitions, child_def).map(|(v, _)| v)
4325}
4326
4327fn extract_child_from_parent_with_indices(
4332 entities: &serde_json::Value,
4333 definitions: &[MappingDefinition],
4334 child_def: &MappingDefinition,
4335) -> Option<(serde_json::Value, Vec<usize>)> {
4336 let parts: Vec<&str> = child_def.meta.source_group.split('.').collect();
4337 if parts.len() < 2 {
4338 return None;
4339 }
4340 let parent_group = parts[0];
4341 let parent_def = definitions
4342 .iter()
4343 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_def.meta.entity)?;
4344 let parent_key = to_camel_case(&parent_def.meta.entity);
4345 let child_key = to_camel_case(&child_def.meta.entity);
4346 let parent_value = entities.get(&parent_key)?;
4347
4348 if let Some(parent_map) = parent_value.as_object() {
4350 if is_map_keyed_value(parent_map) {
4351 let mut children: Vec<serde_json::Value> = Vec::new();
4352 let mut indices: Vec<usize> = Vec::new();
4353 for (i, (_key, inner)) in parent_map.iter().enumerate() {
4354 if let Some(child) = inner.get(&child_key) {
4355 if !child.is_null() {
4356 children.push(child.clone());
4357 indices.push(i);
4358 }
4359 }
4360 }
4361 return match children.len() {
4362 0 => None,
4363 1 => Some((children.into_iter().next().unwrap(), indices)),
4364 _ => Some((serde_json::Value::Array(children), indices)),
4365 };
4366 }
4367 }
4368
4369 if let Some(parent_arr) = parent_value.as_array() {
4371 let mut children: Vec<serde_json::Value> = Vec::new();
4372 let mut indices: Vec<usize> = Vec::new();
4373 for (i, item) in parent_arr.iter().enumerate() {
4374 if let Some(child) = item.get(&child_key) {
4375 if !child.is_null() {
4376 children.push(child.clone());
4377 indices.push(i);
4378 }
4379 }
4380 }
4381 return match children.len() {
4382 0 => None,
4383 1 => Some((children.into_iter().next().unwrap(), indices)),
4384 _ => Some((serde_json::Value::Array(children), indices)),
4385 };
4386 }
4387
4388 let child = parent_value.get(&child_key)?;
4390 if child.is_null() {
4391 return None;
4392 }
4393 Some((child.clone(), vec![0]))
4394}
4395
4396fn nest_child_entities_in_result(
4402 result: &mut serde_json::Map<String, serde_json::Value>,
4403 definitions: &[MappingDefinition],
4404 nesting_info: &std::collections::HashMap<String, Vec<usize>>,
4405 transaction_group: Option<&str>,
4406) {
4407 let nesting_pairs = child_entity_nesting_pairs(definitions, transaction_group);
4408
4409 for (_parent_group, parent_entity, child_entity, child_source_path) in nesting_pairs {
4410 let parent_key = to_camel_case(&parent_entity);
4411 let child_key = to_camel_case(&child_entity);
4412
4413 let child_value = match result.remove(&child_key) {
4415 Some(v) => v,
4416 None => continue,
4417 };
4418
4419 let Some(parent_value) = result.get_mut(&parent_key) else {
4424 result.insert(child_key, child_value);
4426 continue;
4427 };
4428 if parent_value.is_array() {
4429 result.insert(child_key, child_value);
4430 continue;
4431 }
4432
4433 let distribution = child_source_path
4435 .as_deref()
4436 .and_then(|sp| nesting_info.get(sp));
4437
4438 let child_items: Vec<(usize, &serde_json::Value)> = match &child_value {
4440 serde_json::Value::Array(arr) => arr.iter().enumerate().collect(),
4441 other => vec![(0, other)],
4442 };
4443
4444 let insert_or_append = |obj: &mut serde_json::Map<String, serde_json::Value>,
4447 key: &str,
4448 val: &serde_json::Value| {
4449 match obj.get_mut(key) {
4450 Some(existing) => {
4451 if !existing.is_array() {
4453 let prev = existing.take();
4454 *existing = serde_json::Value::Array(vec![prev]);
4455 }
4456 if let Some(arr) = existing.as_array_mut() {
4457 arr.push(val.clone());
4458 }
4459 }
4460 None => {
4461 obj.insert(key.to_string(), val.clone());
4462 }
4463 }
4464 };
4465
4466 if let Some(parent_map) = parent_value.as_object_mut() {
4468 if is_map_keyed_value(parent_map) {
4469 let keys: Vec<String> = parent_map.keys().cloned().collect();
4471 for (i, child_item) in &child_items {
4472 let target_idx = distribution
4473 .and_then(|dist| dist.get(*i))
4474 .copied()
4475 .unwrap_or(0);
4476 if let Some(key) = keys.get(target_idx) {
4477 if let Some(inner) = parent_map.get_mut(key).and_then(|v| v.as_object_mut())
4478 {
4479 insert_or_append(inner, &child_key, child_item);
4480 }
4481 }
4482 }
4483 continue;
4484 }
4485 }
4486
4487 if let Some(parent_arr) = parent_value.as_array_mut() {
4489 for (i, child_item) in &child_items {
4490 let target_idx = distribution
4491 .and_then(|dist| dist.get(*i))
4492 .copied()
4493 .unwrap_or(0);
4494 if let Some(parent_obj) = parent_arr
4495 .get_mut(target_idx)
4496 .and_then(|v| v.as_object_mut())
4497 {
4498 insert_or_append(parent_obj, &child_key, child_item);
4499 }
4500 }
4501 continue;
4502 }
4503
4504 if let Some(parent_obj) = parent_value.as_object_mut() {
4506 for (_i, child_item) in &child_items {
4507 insert_or_append(parent_obj, &child_key, child_item);
4508 }
4509 continue;
4510 }
4511
4512 result.insert(child_key, child_value);
4514 }
4515}
4516
4517pub(crate) fn child_entity_nesting_pairs(
4527 definitions: &[MappingDefinition],
4528 transaction_group: Option<&str>,
4529) -> Vec<(String, String, String, Option<String>)> {
4530 let mut nesting_pairs: Vec<(String, String, String, Option<String>)> = Vec::new();
4533 for def in definitions {
4534 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
4535 if parts.len() < 2 || def.meta.parent_field.is_some() {
4536 continue;
4537 }
4538 let parent_group = parts[0];
4539 if transaction_group.is_some_and(|tx| tx == parent_group) {
4545 continue;
4546 }
4547 let child_entity = def.meta.entity.clone();
4548 let child_has_parent_level_def = definitions
4552 .iter()
4553 .any(|d| d.meta.source_group == parent_group && d.meta.entity == child_entity);
4554 if child_has_parent_level_def {
4555 continue;
4556 }
4557 let parent_entity = definitions
4559 .iter()
4560 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_entity)
4561 .map(|d| d.meta.entity.clone());
4562 if let Some(ref parent_entity) = parent_entity {
4563 let child_key_lc = to_camel_case(&child_entity);
4568 let parent_defs: Vec<_> = definitions
4569 .iter()
4570 .filter(|d| d.meta.entity == *parent_entity)
4571 .collect();
4572 let has_conflicting_field = parent_defs.iter().any(|pd| {
4573 pd.fields.values().any(|fm| {
4574 let target = match fm {
4575 crate::definition::FieldMapping::Simple(t) => t.as_str(),
4576 crate::definition::FieldMapping::Structured(s) => s.target.as_str(),
4577 crate::definition::FieldMapping::Nested(_) => "",
4578 };
4579 target.starts_with(&child_key_lc)
4580 && target.get(child_key_lc.len()..child_key_lc.len() + 1) == Some(".")
4581 })
4582 });
4583 if has_conflicting_field {
4584 continue;
4585 }
4586 if nesting_pairs
4588 .iter()
4589 .any(|(_, pe, ce, _)| *pe == *parent_entity && *ce == child_entity)
4590 {
4591 continue;
4592 }
4593 nesting_pairs.push((
4594 parent_group.to_string(),
4595 parent_entity.clone(),
4596 child_entity,
4597 def.meta.source_path.clone(),
4598 ));
4599 }
4600 }
4601
4602 nesting_pairs
4603}
4604
4605fn is_map_keyed_value(map: &serde_json::Map<String, serde_json::Value>) -> bool {
4607 if map.is_empty() {
4608 return false;
4609 }
4610 map.values().all(|v| v.is_object())
4611 && map.keys().all(|k| {
4612 k.len() <= 5
4613 || k.chars()
4614 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
4615 })
4616}
4617
4618struct CodeSite<'a> {
4621 target: &'a str,
4622 parent_field: Option<&'a str>,
4628 source_path: &'a str,
4629 seg_tag: String,
4630 path_qualifier: Option<String>,
4632 disc_qualifier: Option<String>,
4634 element_idx: usize,
4635 component_idx: usize,
4636 enum_map: Option<&'a std::collections::BTreeMap<String, String>>,
4637 also_target: Option<&'a str>,
4638 also_enum_map: Option<&'a std::collections::BTreeMap<String, String>>,
4639}
4640
4641pub(crate) fn to_camel_case(name: &str) -> String {
4642 let mut chars = name.chars();
4643 match chars.next() {
4644 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
4645 None => String::new(),
4646 }
4647}
4648
4649fn set_nested_value(map: &mut serde_json::Map<String, serde_json::Value>, path: &str, val: String) {
4652 set_nested_value_json(map, path, serde_json::Value::String(val));
4653}
4654
4655fn set_nested_value_json(
4657 map: &mut serde_json::Map<String, serde_json::Value>,
4658 path: &str,
4659 val: serde_json::Value,
4660) {
4661 if let Some((prefix, leaf)) = path.rsplit_once('.') {
4662 let mut current = map;
4663 for part in prefix.split('.') {
4664 let entry = current
4665 .entry(part.to_string())
4666 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
4667 current = entry.as_object_mut().expect("expected object in path");
4668 }
4669 current.insert(leaf.to_string(), val);
4670 } else {
4671 map.insert(path.to_string(), val);
4672 }
4673}
4674
4675#[derive(serde::Serialize, serde::Deserialize)]
4680pub struct VariantCache {
4681 pub message_defs: Vec<MappingDefinition>,
4683 pub transaction_defs: BTreeMap<String, Vec<MappingDefinition>>,
4685 pub combined_defs: BTreeMap<String, Vec<MappingDefinition>>,
4687 #[serde(default)]
4689 pub code_lookups: BTreeMap<String, crate::code_lookup::CodeLookup>,
4690 #[serde(default)]
4692 pub mig_schema: Option<mig_types::schema::mig::MigSchema>,
4693 #[serde(default)]
4695 pub segment_structure: Option<crate::segment_structure::SegmentStructure>,
4696 #[serde(skip)]
4701 pub code_lists: std::sync::Arc<crate::code_lists::CodeLists>,
4702 #[serde(default)]
4705 pub pid_segment_numbers: BTreeMap<String, Vec<String>>,
4706 #[serde(default)]
4709 pub pid_requirements: BTreeMap<String, crate::pid_requirements::PidRequirements>,
4710 #[serde(default)]
4715 pub pid_ahb_workflows: BTreeMap<String, ahb_types::AhbWorkflow>,
4716 #[serde(default)]
4720 pub tx_groups: BTreeMap<String, String>,
4721}
4722
4723impl VariantCache {
4724 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
4726 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
4727 path: path.display().to_string(),
4728 message: e.to_string(),
4729 })?;
4730 if let Some(parent) = path.parent() {
4731 std::fs::create_dir_all(parent)?;
4732 }
4733 std::fs::write(path, encoded)?;
4734 Ok(())
4735 }
4736
4737 pub fn load(path: &Path) -> Result<Self, MappingError> {
4739 let bytes = std::fs::read(path)?;
4740 let mut cache: Self =
4741 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
4742 path: path.display().to_string(),
4743 message: e.to_string(),
4744 })?;
4745 cache.code_lists = crate::code_lists::CodeLists::discover(path);
4746 Ok(cache)
4747 }
4748
4749 pub fn tx_group(&self, pid: &str) -> Option<&str> {
4753 self.tx_groups
4754 .get(&format!("pid_{pid}"))
4755 .map(|s| s.as_str())
4756 }
4757
4758 pub fn msg_engine(&self, pid: &str) -> MappingEngine {
4762 let mut eng = MappingEngine::from_definitions_with_code_lists(
4763 std::sync::Arc::clone(&self.code_lists),
4764 self.message_defs.clone(),
4765 )
4766 .with_pid(pid);
4767 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
4768 eng = eng.with_code_lookup(cl.clone());
4769 }
4770 eng
4771 }
4772
4773 pub fn tx_engine(&self, pid: &str) -> Option<MappingEngine> {
4777 self.transaction_defs
4778 .get(&format!("pid_{pid}"))
4779 .map(|defs| {
4780 let mut eng = MappingEngine::from_definitions_with_code_lists(
4781 std::sync::Arc::clone(&self.code_lists),
4782 defs.clone(),
4783 )
4784 .with_pid(pid);
4785 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
4786 eng = eng.with_code_lookup(cl.clone());
4787 }
4788 eng
4789 })
4790 }
4791
4792 pub fn filtered_mig(&self, pid: &str) -> Option<mig_types::schema::mig::MigSchema> {
4800 let mig = self.mig_schema.as_ref()?;
4801 let numbers = self
4802 .pid_segment_numbers
4803 .get(&format!("pid_{pid}"))
4804 .or_else(|| self.pid_segment_numbers.get("pid_"))?;
4805 let number_set: std::collections::HashSet<String> = numbers.iter().cloned().collect();
4806 Some(mig_assembly::pid_filter::filter_mig_for_pid(
4807 mig,
4808 &number_set,
4809 ))
4810 }
4811}
4812
4813#[derive(serde::Serialize, serde::Deserialize)]
4818pub struct DataBundle {
4819 pub format_version: String,
4820 pub bundle_version: u32,
4821 pub variants: BTreeMap<String, VariantCache>,
4822 #[serde(default)]
4827 pub bo4e_catalog: crate::bo4e_catalog::Bo4eCatalog,
4828
4829 #[serde(default, skip_serializing_if = "Option::is_none")]
4841 pub built_by: Option<String>,
4842 #[serde(default)]
4854 pub code_lists: crate::code_lists::CodeLists,
4855}
4856
4857impl DataBundle {
4858 pub const CURRENT_VERSION: u32 = 2;
4859
4860 pub const PRODUCING_VERSION: &'static str = env!("CARGO_PKG_VERSION");
4870
4871 pub fn variant(&self, name: &str) -> Option<&VariantCache> {
4872 self.variants.get(name)
4873 }
4874
4875 pub fn write_to<W: std::io::Write>(&self, writer: &mut W) -> Result<(), MappingError> {
4876 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
4877 path: "<stream>".to_string(),
4878 message: e.to_string(),
4879 })?;
4880 writer.write_all(&encoded).map_err(MappingError::Io)
4881 }
4882
4883 pub fn read_from<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
4884 let mut bytes = Vec::new();
4885 reader.read_to_end(&mut bytes).map_err(MappingError::Io)?;
4886 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
4887 path: "<stream>".to_string(),
4888 message: e.to_string(),
4889 })
4890 }
4891
4892 pub fn read_from_checked<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
4893 let mut bundle = Self::read_from(reader)?;
4894 let shared = std::sync::Arc::new(std::mem::take(&mut bundle.code_lists));
4897 for variant in bundle.variants.values_mut() {
4898 variant.code_lists = std::sync::Arc::clone(&shared);
4899 }
4900 bundle.code_lists = (*shared).clone();
4901 if bundle.bundle_version != Self::CURRENT_VERSION {
4902 return Err(MappingError::CacheRead {
4903 path: "<stream>".to_string(),
4904 message: format!(
4905 "Incompatible bundle version {}, expected version {}. \
4906 Run `edifact-data update` to fetch compatible bundles.",
4907 bundle.bundle_version,
4908 Self::CURRENT_VERSION
4909 ),
4910 });
4911 }
4912 Ok(bundle)
4913 }
4914
4915 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
4916 if let Some(parent) = path.parent() {
4917 std::fs::create_dir_all(parent)?;
4918 }
4919 let mut file = std::fs::File::create(path).map_err(MappingError::Io)?;
4920 self.write_to(&mut file)
4921 }
4922
4923 pub fn load(path: &Path) -> Result<Self, MappingError> {
4924 let mut file = std::fs::File::open(path).map_err(MappingError::Io)?;
4925 Self::read_from_checked(&mut file)
4926 }
4927}
4928
4929#[cfg(test)]
4930mod variant_cache_helper_tests {
4931 use super::*;
4932
4933 fn make_test_cache() -> VariantCache {
4934 let mut tx_groups = BTreeMap::new();
4935 tx_groups.insert("pid_55001".to_string(), "SG4".to_string());
4936 tx_groups.insert("pid_21007".to_string(), "SG14".to_string());
4937
4938 let mut transaction_defs = BTreeMap::new();
4939 transaction_defs.insert("pid_55001".to_string(), vec![]);
4940 transaction_defs.insert("pid_21007".to_string(), vec![]);
4941
4942 VariantCache {
4943 code_lists: Default::default(),
4944 message_defs: vec![],
4945 transaction_defs,
4946 combined_defs: BTreeMap::new(),
4947 code_lookups: BTreeMap::new(),
4948 mig_schema: None,
4949 segment_structure: None,
4950 pid_segment_numbers: BTreeMap::new(),
4951 pid_requirements: BTreeMap::new(),
4952 pid_ahb_workflows: BTreeMap::new(),
4953 tx_groups,
4954 }
4955 }
4956
4957 #[test]
4958 fn test_tx_group_returns_correct_group() {
4959 let vc = make_test_cache();
4960 assert_eq!(vc.tx_group("55001").unwrap(), "SG4");
4961 assert_eq!(vc.tx_group("21007").unwrap(), "SG14");
4962 }
4963
4964 #[test]
4965 fn test_tx_group_unknown_pid_returns_none() {
4966 let vc = make_test_cache();
4967 assert!(vc.tx_group("99999").is_none());
4968 }
4969
4970 #[test]
4971 fn test_msg_engine_returns_engine() {
4972 let vc = make_test_cache();
4973 let engine = vc.msg_engine("55001");
4974 assert_eq!(engine.definitions().len(), 0);
4975 }
4976
4977 #[test]
4978 fn test_tx_engine_returns_engine_for_known_pid() {
4979 let vc = make_test_cache();
4980 assert!(vc.tx_engine("55001").is_some());
4981 }
4982
4983 #[test]
4984 fn test_tx_engine_returns_none_for_unknown_pid() {
4985 let vc = make_test_cache();
4986 assert!(vc.tx_engine("99999").is_none());
4987 }
4988
4989 fn make_populated_cache() -> VariantCache {
4993 let pids: Vec<String> = (0..40).map(|i| format!("pid_{}", 55000 + i * 7)).collect();
4994 let schema: serde_json::Value = serde_json::from_str(include_str!(
4995 "../../mig-types/src/generated/fv2504/utilmd/pids/pid_55001_schema.json"
4996 ))
4997 .unwrap();
4998 let code_lookup = crate::code_lookup::CodeLookup::from_schema_value(&schema);
4999 let element_counts: serde_json::Map<String, serde_json::Value> = (0..40)
5000 .map(|i| (format!("T{i:02}"), serde_json::json!(i)))
5001 .collect();
5002 let segment_structure: SegmentStructure =
5003 serde_json::from_value(serde_json::json!({ "element_counts": element_counts }))
5004 .unwrap();
5005 let ubs: serde_json::Map<String, serde_json::Value> = (0..40)
5006 .map(|i| (format!("UB{i}"), serde_json::json!({ "Ref": i })))
5007 .collect();
5008 let workflow: ahb_types::AhbWorkflow = serde_json::from_value(serde_json::json!({
5009 "pruefidentifikator": "55001",
5010 "description": "",
5011 "communication_direction": null,
5012 "fields": [],
5013 "ub_definitions": ubs,
5014 }))
5015 .unwrap();
5016
5017 let mut vc = make_test_cache();
5018 vc.segment_structure = Some(segment_structure);
5019 for pid in &pids {
5020 vc.transaction_defs.insert(pid.clone(), vec![]);
5021 vc.combined_defs.insert(pid.clone(), vec![]);
5022 vc.code_lookups.insert(pid.clone(), code_lookup.clone());
5023 vc.pid_segment_numbers
5024 .insert(pid.clone(), vec!["00001".to_string()]);
5025 vc.pid_ahb_workflows.insert(pid.clone(), workflow.clone());
5026 vc.tx_groups.insert(pid.clone(), "SG4".to_string());
5027 }
5028 vc
5029 }
5030
5031 #[test]
5034 fn test_enrichment_uses_codes_of_the_path_qualifier_variant() {
5035 let comp = |sub: u64, id: &str, codes: Option<serde_json::Value>| match codes {
5036 Some(c) => serde_json::json!({"sub_index": sub, "id": id, "type": "code", "codes": c}),
5037 None => serde_json::json!({"sub_index": sub, "id": id, "type": "data"}),
5038 };
5039 let code = |v: &str, n: &str| serde_json::json!([{"value": v, "name": n}]);
5040 let seg = |tag: &str, composite: &str, comps: Vec<serde_json::Value>| serde_json::json!({"id": tag, "elements": [{"index": 0, "composite": composite, "components": comps}]});
5041 let schema = serde_json::json!({"fields": {"sg15": {"segments": [
5042 seg("RFF", "C506", vec![comp(0, "1153", Some(code("Z13", "PID"))), comp(1, "1154", Some(code("21037", "RD / NB-Bewertung")))]),
5043 seg("RFF", "C506", vec![comp(0, "1153", Some(code("ACW", "Referenz"))), comp(1, "1154", None)]),
5044 seg("CAV", "C889", vec![comp(0, "7111", Some(code("Z91", "Z91"))), comp(1, "7110", Some(code("A", "Alpha")))]),
5045 seg("CAV", "C889", vec![comp(0, "7111", Some(code("ZF0", "ZF0"))), comp(1, "7110", Some(code("C", "Gamma")))]),
5046 ]}}});
5047 let engine = MappingEngine::new_empty()
5048 .with_code_lookup(crate::code_lookup::CodeLookup::from_schema_value(&schema));
5049 let def = MappingDefinition::from_toml_str(
5050 r#"
5051[meta]
5052entity = "Status"
5053bo4e_type = "Status"
5054source_group = "SG15"
5055source_path = "sg15"
5056discriminator = "RFF.0.0=Z13"
5057
5058[fields]
5059"rff.0.1" = "pruefidentifikator"
5060"rff[ACW].0.1" = "referenz"
5061"cav[Z91].0.1" = "z91Wert"
5062"cav[ZF0].0.1" = "zf0Wert"
5063"#,
5064 )
5065 .unwrap();
5066 let segment = |tag: &str, elements: &[&[&str]]| OwnedSegment {
5067 id: tag.to_string(),
5068 elements: elements
5069 .iter()
5070 .map(|e| e.iter().map(|c| c.to_string()).collect())
5071 .collect(),
5072 segment_number: 1,
5073 };
5074 let json = engine.map_forward_from_segments(
5075 &[
5076 segment("RFF", &[&["Z13", "21037"]]),
5077 segment("RFF", &[&["ACW", "REF-1"]]),
5078 segment("CAV", &[&["Z91", "C"]]),
5079 segment("CAV", &[&["ZF0", "C"]]),
5080 ],
5081 &def,
5082 );
5083 assert_eq!(
5084 json["referenz"],
5085 serde_json::json!("REF-1"),
5086 "RFF+ACW d1154 is data; RFF+Z13's codes must not apply: {json}"
5087 );
5088 assert_eq!(json["pruefidentifikator"]["meaning"], "RD / NB-Bewertung");
5089 assert_eq!(
5090 json["z91Wert"]["meaning"],
5091 serde_json::Value::Null,
5092 "'C' is a CAV+ZF0 code, unknown to CAV+Z91: {json}"
5093 );
5094 assert_eq!(json["zf0Wert"]["meaning"], "Gamma");
5095 }
5096
5097 #[test]
5098 fn test_variant_cache_serialization_is_deterministic() {
5099 let reference = serde_json::to_vec(&make_populated_cache()).unwrap();
5100 for _ in 0..5 {
5101 let again = serde_json::to_vec(&make_populated_cache()).unwrap();
5102 assert!(
5103 reference == again,
5104 "VariantCache serialization must not depend on HashMap iteration order"
5105 );
5106 }
5107 }
5108
5109 #[test]
5110 fn test_variant_cache_serializes_map_keys_sorted() {
5111 use indexmap::IndexMap;
5112 use serde::de::IgnoredAny;
5113
5114 #[derive(serde::Deserialize)]
5115 struct ProbeWorkflow {
5116 ub_definitions: IndexMap<String, IgnoredAny>,
5117 }
5118 #[derive(serde::Deserialize)]
5119 struct ProbeStructure {
5120 element_counts: IndexMap<String, usize>,
5121 }
5122 #[derive(serde::Deserialize)]
5123 struct Probe {
5124 transaction_defs: IndexMap<String, IgnoredAny>,
5125 combined_defs: IndexMap<String, IgnoredAny>,
5126 code_lookups: IndexMap<String, IndexMap<String, IgnoredAny>>,
5127 segment_structure: ProbeStructure,
5128 pid_segment_numbers: IndexMap<String, IgnoredAny>,
5129 pid_requirements: IndexMap<String, IgnoredAny>,
5130 pid_ahb_workflows: IndexMap<String, ProbeWorkflow>,
5131 tx_groups: IndexMap<String, String>,
5132 }
5133 fn assert_sorted<'a>(what: &str, keys: impl Iterator<Item = &'a String>) {
5134 let keys: Vec<&String> = keys.collect();
5135 let mut sorted = keys.clone();
5136 sorted.sort();
5137 assert_eq!(keys, sorted, "{what} keys must serialize in sorted order");
5138 }
5139
5140 let json = serde_json::to_string(&make_populated_cache()).unwrap();
5141 let probe: Probe = serde_json::from_str(&json).unwrap();
5142 assert_sorted("transaction_defs", probe.transaction_defs.keys());
5143 assert_sorted("combined_defs", probe.combined_defs.keys());
5144 assert_sorted("code_lookups", probe.code_lookups.keys());
5145 let lookup = probe.code_lookups.values().next().unwrap();
5146 assert!(lookup.len() > 10, "fixture lookup should have many entries");
5147 assert_sorted("code_lookup entries", lookup.keys());
5148 assert_sorted(
5149 "segment_structure",
5150 probe.segment_structure.element_counts.keys(),
5151 );
5152 assert_sorted("pid_segment_numbers", probe.pid_segment_numbers.keys());
5153 assert_sorted("pid_requirements", probe.pid_requirements.keys());
5154 assert_sorted("pid_ahb_workflows", probe.pid_ahb_workflows.keys());
5155 let wf = probe.pid_ahb_workflows.values().next().unwrap();
5156 assert_sorted("ub_definitions", wf.ub_definitions.keys());
5157 assert_sorted("tx_groups", probe.tx_groups.keys());
5158 }
5159
5160 #[test]
5161 fn test_data_bundle_serializes_variants_sorted() {
5162 use indexmap::IndexMap;
5163 use serde::de::IgnoredAny;
5164
5165 #[derive(serde::Deserialize)]
5166 struct Probe {
5167 variants: IndexMap<String, IgnoredAny>,
5168 }
5169 let variants: BTreeMap<String, VariantCache> = (0..20)
5170 .map(|i| (format!("VARIANT_{i:02}"), make_test_cache()))
5171 .collect();
5172 let bundle = DataBundle {
5173 format_version: "FV2504".to_string(),
5174 bundle_version: DataBundle::CURRENT_VERSION,
5175 built_by: Some(DataBundle::PRODUCING_VERSION.to_string()),
5176 variants,
5177 bo4e_catalog: Default::default(),
5178 code_lists: Default::default(),
5179 };
5180 let mut bytes = Vec::new();
5181 bundle.write_to(&mut bytes).unwrap();
5182 let probe: Probe = serde_json::from_slice(&bytes).unwrap();
5183 let keys: Vec<&String> = probe.variants.keys().collect();
5184 let mut sorted = keys.clone();
5185 sorted.sort();
5186 assert_eq!(keys, sorted);
5187 }
5188}
5189
5190#[cfg(test)]
5191mod tests {
5192 use super::*;
5193 use crate::definition::{MappingDefinition, MappingMeta, StructuredFieldMapping};
5194 use indexmap::IndexMap;
5195
5196 fn make_def(fields: IndexMap<String, FieldMapping>) -> MappingDefinition {
5197 MappingDefinition {
5198 meta: MappingMeta {
5199 entity: "Test".to_string(),
5200 bo4e_type: "Test".to_string(),
5201 source_group: "SG4".to_string(),
5202 source_path: None,
5203 discriminator: None,
5204 repeat_on_tag: None,
5205 parent_field: None,
5206 target_list: None,
5207 order: None,
5208 },
5209 fields,
5210 complex_handlers: None,
5211 }
5212 }
5213
5214 #[test]
5215 fn test_map_interchange_single_transaction_backward_compat() {
5216 use mig_assembly::assembler::*;
5217
5218 let tree = AssembledTree {
5220 segments: vec![
5221 AssembledSegment {
5222 tag: "UNH".to_string(),
5223 elements: vec![vec!["001".to_string()]],
5224 mig_number: None,
5225 segment_number: None,
5226 },
5227 AssembledSegment {
5228 tag: "BGM".to_string(),
5229 elements: vec![vec!["E01".to_string()], vec!["DOC001".to_string()]],
5230 mig_number: None,
5231 segment_number: None,
5232 },
5233 ],
5234 groups: vec![
5235 AssembledGroup {
5236 group_id: "SG2".to_string(),
5237 repetitions: vec![AssembledGroupInstance {
5238 segments: vec![AssembledSegment {
5239 tag: "NAD".to_string(),
5240 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
5241 mig_number: None,
5242 segment_number: None,
5243 }],
5244 child_groups: vec![],
5245 entry_mig_number: None,
5246 variant_mig_numbers: vec![],
5247 skipped_segments: vec![],
5248 skipped_positions: Vec::new(),
5249 }],
5250 },
5251 AssembledGroup {
5252 group_id: "SG4".to_string(),
5253 repetitions: vec![AssembledGroupInstance {
5254 segments: vec![AssembledSegment {
5255 tag: "IDE".to_string(),
5256 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5257 mig_number: None,
5258 segment_number: None,
5259 }],
5260 child_groups: vec![AssembledGroup {
5261 group_id: "SG5".to_string(),
5262 repetitions: vec![AssembledGroupInstance {
5263 segments: vec![AssembledSegment {
5264 tag: "LOC".to_string(),
5265 elements: vec![
5266 vec!["Z16".to_string()],
5267 vec!["DE000111222333".to_string()],
5268 ],
5269 mig_number: None,
5270 segment_number: None,
5271 }],
5272 child_groups: vec![],
5273 entry_mig_number: None,
5274 variant_mig_numbers: vec![],
5275 skipped_segments: vec![],
5276 skipped_positions: Vec::new(),
5277 }],
5278 }],
5279 entry_mig_number: None,
5280 variant_mig_numbers: vec![],
5281 skipped_segments: vec![],
5282 skipped_positions: Vec::new(),
5283 }],
5284 },
5285 ],
5286 post_group_start: 2,
5287 inter_group_segments: std::collections::BTreeMap::new(),
5288 };
5289
5290 let msg_engine = MappingEngine::from_definitions(vec![]);
5292
5293 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5295 tx_fields.insert(
5296 "ide.1".to_string(),
5297 FieldMapping::Simple("vorgangId".to_string()),
5298 );
5299 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5300 malo_fields.insert(
5301 "loc.1".to_string(),
5302 FieldMapping::Simple("marktlokationsId".to_string()),
5303 );
5304
5305 let tx_engine = MappingEngine::from_definitions(vec![
5306 MappingDefinition {
5307 meta: MappingMeta {
5308 entity: "Prozessdaten".to_string(),
5309 bo4e_type: "Prozessdaten".to_string(),
5310 source_group: "SG4".to_string(),
5311 source_path: None,
5312 discriminator: None,
5313 repeat_on_tag: None,
5314 parent_field: None,
5315 target_list: None,
5316 order: None,
5317 },
5318 fields: tx_fields,
5319 complex_handlers: None,
5320 },
5321 MappingDefinition {
5322 meta: MappingMeta {
5323 entity: "Marktlokation".to_string(),
5324 bo4e_type: "Marktlokation".to_string(),
5325 source_group: "SG4.SG5".to_string(),
5326 source_path: None,
5327 discriminator: None,
5328 repeat_on_tag: None,
5329 parent_field: None,
5330 target_list: None,
5331 order: None,
5332 },
5333 fields: malo_fields,
5334 complex_handlers: None,
5335 },
5336 ]);
5337
5338 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
5339
5340 assert_eq!(result.transaktionen.len(), 1);
5341 assert_eq!(
5342 result.transaktionen[0].transaktionsdaten["vorgangId"]
5343 .as_str()
5344 .unwrap(),
5345 "TX001"
5346 );
5347 assert_eq!(
5350 result.transaktionen[0].stammdaten["marktlokation"]["marktlokationsId"]
5351 .as_str()
5352 .unwrap(),
5353 "DE000111222333"
5354 );
5355 }
5356
5357 #[test]
5358 fn test_map_reverse_pads_intermediate_empty_elements() {
5359 let mut fields = IndexMap::new();
5361 fields.insert(
5362 "nad.0".to_string(),
5363 FieldMapping::Structured(StructuredFieldMapping {
5364 target: String::new(),
5365 transform: None,
5366 when: None,
5367 default: Some("Z09".to_string()),
5368 enum_map: None,
5369 code_list: None,
5370 also_code_list: None,
5371 when_filled: None,
5372 also_target: None,
5373 also_enum_map: None,
5374 }),
5375 );
5376 fields.insert(
5377 "nad.3.0".to_string(),
5378 FieldMapping::Simple("name".to_string()),
5379 );
5380 fields.insert(
5381 "nad.3.1".to_string(),
5382 FieldMapping::Simple("vorname".to_string()),
5383 );
5384
5385 let def = make_def(fields);
5386 let engine = MappingEngine::from_definitions(vec![]);
5387
5388 let bo4e = serde_json::json!({
5389 "name": "Muster",
5390 "vorname": "Max"
5391 });
5392
5393 let instance = engine.map_reverse(&bo4e, &def);
5394 assert_eq!(instance.segments.len(), 1);
5395
5396 let nad = &instance.segments[0];
5397 assert_eq!(nad.tag, "NAD");
5398 assert_eq!(nad.elements.len(), 4);
5399 assert_eq!(nad.elements[0], vec!["Z09"]);
5400 assert_eq!(nad.elements[1], vec![""]);
5402 assert_eq!(nad.elements[2], vec![""]);
5403 assert_eq!(nad.elements[3][0], "Muster");
5404 assert_eq!(nad.elements[3][1], "Max");
5405 }
5406
5407 #[test]
5408 fn test_map_reverse_no_padding_when_contiguous() {
5409 let mut fields = IndexMap::new();
5411 fields.insert(
5412 "dtm.0.0".to_string(),
5413 FieldMapping::Structured(StructuredFieldMapping {
5414 target: String::new(),
5415 transform: None,
5416 when: None,
5417 default: Some("92".to_string()),
5418 enum_map: None,
5419 code_list: None,
5420 also_code_list: None,
5421 when_filled: None,
5422 also_target: None,
5423 also_enum_map: None,
5424 }),
5425 );
5426 fields.insert(
5427 "dtm.0.1".to_string(),
5428 FieldMapping::Simple("value".to_string()),
5429 );
5430 fields.insert(
5431 "dtm.0.2".to_string(),
5432 FieldMapping::Structured(StructuredFieldMapping {
5433 target: String::new(),
5434 transform: None,
5435 when: None,
5436 default: Some("303".to_string()),
5437 enum_map: None,
5438 code_list: None,
5439 also_code_list: None,
5440 when_filled: None,
5441 also_target: None,
5442 also_enum_map: None,
5443 }),
5444 );
5445
5446 let def = make_def(fields);
5447 let engine = MappingEngine::from_definitions(vec![]);
5448
5449 let bo4e = serde_json::json!({ "value": "20250531" });
5450
5451 let instance = engine.map_reverse(&bo4e, &def);
5452 let dtm = &instance.segments[0];
5453 assert_eq!(dtm.elements.len(), 1);
5455 assert_eq!(dtm.elements[0], vec!["92", "20250531", "303"]);
5456 }
5457
5458 #[test]
5459 fn test_map_message_level_extracts_sg2_only() {
5460 use mig_assembly::assembler::*;
5461
5462 let tree = AssembledTree {
5464 segments: vec![
5465 AssembledSegment {
5466 tag: "UNH".to_string(),
5467 elements: vec![vec!["001".to_string()]],
5468 mig_number: None,
5469 segment_number: None,
5470 },
5471 AssembledSegment {
5472 tag: "BGM".to_string(),
5473 elements: vec![vec!["E01".to_string()]],
5474 mig_number: None,
5475 segment_number: None,
5476 },
5477 ],
5478 groups: vec![
5479 AssembledGroup {
5480 group_id: "SG2".to_string(),
5481 repetitions: vec![AssembledGroupInstance {
5482 segments: vec![AssembledSegment {
5483 tag: "NAD".to_string(),
5484 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
5485 mig_number: None,
5486 segment_number: None,
5487 }],
5488 child_groups: vec![],
5489 entry_mig_number: None,
5490 variant_mig_numbers: vec![],
5491 skipped_segments: vec![],
5492 skipped_positions: Vec::new(),
5493 }],
5494 },
5495 AssembledGroup {
5496 group_id: "SG4".to_string(),
5497 repetitions: vec![AssembledGroupInstance {
5498 segments: vec![AssembledSegment {
5499 tag: "IDE".to_string(),
5500 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5501 mig_number: None,
5502 segment_number: None,
5503 }],
5504 child_groups: vec![],
5505 entry_mig_number: None,
5506 variant_mig_numbers: vec![],
5507 skipped_segments: vec![],
5508 skipped_positions: Vec::new(),
5509 }],
5510 },
5511 ],
5512 post_group_start: 2,
5513 inter_group_segments: std::collections::BTreeMap::new(),
5514 };
5515
5516 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5518 msg_fields.insert(
5519 "nad.0".to_string(),
5520 FieldMapping::Simple("marktrolle".to_string()),
5521 );
5522 msg_fields.insert(
5523 "nad.1".to_string(),
5524 FieldMapping::Simple("rollencodenummer".to_string()),
5525 );
5526 let msg_def = MappingDefinition {
5527 meta: MappingMeta {
5528 entity: "Marktteilnehmer".to_string(),
5529 bo4e_type: "Marktteilnehmer".to_string(),
5530 source_group: "SG2".to_string(),
5531 source_path: None,
5532 discriminator: None,
5533 repeat_on_tag: None,
5534 parent_field: None,
5535 target_list: None,
5536 order: None,
5537 },
5538 fields: msg_fields,
5539 complex_handlers: None,
5540 };
5541
5542 let engine = MappingEngine::from_definitions(vec![msg_def.clone()]);
5543 let result = engine.map_all_forward(&tree);
5544
5545 assert!(result.get("marktteilnehmer").is_some());
5547 let mt = &result["marktteilnehmer"];
5548 assert_eq!(mt["marktrolle"].as_str().unwrap(), "MS");
5549 assert_eq!(mt["rollencodenummer"].as_str().unwrap(), "9900123");
5550 }
5551
5552 #[test]
5553 fn test_map_transaction_scoped_to_sg4_instance() {
5554 use mig_assembly::assembler::*;
5555
5556 let tree = AssembledTree {
5558 segments: vec![
5559 AssembledSegment {
5560 tag: "UNH".to_string(),
5561 elements: vec![vec!["001".to_string()]],
5562 mig_number: None,
5563 segment_number: None,
5564 },
5565 AssembledSegment {
5566 tag: "BGM".to_string(),
5567 elements: vec![vec!["E01".to_string()]],
5568 mig_number: None,
5569 segment_number: None,
5570 },
5571 ],
5572 groups: vec![AssembledGroup {
5573 group_id: "SG4".to_string(),
5574 repetitions: vec![AssembledGroupInstance {
5575 segments: vec![AssembledSegment {
5576 tag: "IDE".to_string(),
5577 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5578 mig_number: None,
5579 segment_number: None,
5580 }],
5581 child_groups: vec![AssembledGroup {
5582 group_id: "SG5".to_string(),
5583 repetitions: vec![AssembledGroupInstance {
5584 segments: vec![AssembledSegment {
5585 tag: "LOC".to_string(),
5586 elements: vec![
5587 vec!["Z16".to_string()],
5588 vec!["DE000111222333".to_string()],
5589 ],
5590 mig_number: None,
5591 segment_number: None,
5592 }],
5593 child_groups: vec![],
5594 entry_mig_number: None,
5595 variant_mig_numbers: vec![],
5596 skipped_segments: vec![],
5597 skipped_positions: Vec::new(),
5598 }],
5599 }],
5600 entry_mig_number: None,
5601 variant_mig_numbers: vec![],
5602 skipped_segments: vec![],
5603 skipped_positions: Vec::new(),
5604 }],
5605 }],
5606 post_group_start: 2,
5607 inter_group_segments: std::collections::BTreeMap::new(),
5608 };
5609
5610 let mut proz_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5612 proz_fields.insert(
5613 "ide.1".to_string(),
5614 FieldMapping::Simple("vorgangId".to_string()),
5615 );
5616 let proz_def = MappingDefinition {
5617 meta: MappingMeta {
5618 entity: "Prozessdaten".to_string(),
5619 bo4e_type: "Prozessdaten".to_string(),
5620 source_group: "".to_string(), source_path: None,
5622 discriminator: None,
5623 repeat_on_tag: None,
5624 parent_field: None,
5625 target_list: None,
5626 order: None,
5627 },
5628 fields: proz_fields,
5629 complex_handlers: None,
5630 };
5631
5632 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5633 malo_fields.insert(
5634 "loc.1".to_string(),
5635 FieldMapping::Simple("marktlokationsId".to_string()),
5636 );
5637 let malo_def = MappingDefinition {
5638 meta: MappingMeta {
5639 entity: "Marktlokation".to_string(),
5640 bo4e_type: "Marktlokation".to_string(),
5641 source_group: "SG5".to_string(), source_path: None,
5643 discriminator: None,
5644 repeat_on_tag: None,
5645 parent_field: None,
5646 target_list: None,
5647 order: None,
5648 },
5649 fields: malo_fields,
5650 complex_handlers: None,
5651 };
5652
5653 let tx_engine = MappingEngine::from_definitions(vec![proz_def, malo_def]);
5654
5655 let sg4 = &tree.groups[0]; let sg4_instance = &sg4.repetitions[0];
5658 let sub_tree = sg4_instance.as_assembled_tree();
5659
5660 let result = tx_engine.map_all_forward(&sub_tree);
5661
5662 assert_eq!(
5664 result["prozessdaten"]["vorgangId"].as_str().unwrap(),
5665 "TX001"
5666 );
5667
5668 assert_eq!(
5670 result["marktlokation"]["marktlokationsId"]
5671 .as_str()
5672 .unwrap(),
5673 "DE000111222333"
5674 );
5675 }
5676
5677 #[test]
5678 fn test_map_interchange_produces_full_hierarchy() {
5679 use mig_assembly::assembler::*;
5680
5681 let tree = AssembledTree {
5683 segments: vec![
5684 AssembledSegment {
5685 tag: "UNH".to_string(),
5686 elements: vec![vec!["001".to_string()]],
5687 mig_number: None,
5688 segment_number: None,
5689 },
5690 AssembledSegment {
5691 tag: "BGM".to_string(),
5692 elements: vec![vec!["E01".to_string()]],
5693 mig_number: None,
5694 segment_number: None,
5695 },
5696 ],
5697 groups: vec![
5698 AssembledGroup {
5699 group_id: "SG2".to_string(),
5700 repetitions: vec![AssembledGroupInstance {
5701 segments: vec![AssembledSegment {
5702 tag: "NAD".to_string(),
5703 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
5704 mig_number: None,
5705 segment_number: None,
5706 }],
5707 child_groups: vec![],
5708 entry_mig_number: None,
5709 variant_mig_numbers: vec![],
5710 skipped_segments: vec![],
5711 skipped_positions: Vec::new(),
5712 }],
5713 },
5714 AssembledGroup {
5715 group_id: "SG4".to_string(),
5716 repetitions: vec![
5717 AssembledGroupInstance {
5718 segments: vec![AssembledSegment {
5719 tag: "IDE".to_string(),
5720 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5721 mig_number: None,
5722 segment_number: None,
5723 }],
5724 child_groups: vec![],
5725 entry_mig_number: None,
5726 variant_mig_numbers: vec![],
5727 skipped_segments: vec![],
5728 skipped_positions: Vec::new(),
5729 },
5730 AssembledGroupInstance {
5731 segments: vec![AssembledSegment {
5732 tag: "IDE".to_string(),
5733 elements: vec![vec!["24".to_string()], vec!["TX002".to_string()]],
5734 mig_number: None,
5735 segment_number: None,
5736 }],
5737 child_groups: vec![],
5738 entry_mig_number: None,
5739 variant_mig_numbers: vec![],
5740 skipped_segments: vec![],
5741 skipped_positions: Vec::new(),
5742 },
5743 ],
5744 },
5745 ],
5746 post_group_start: 2,
5747 inter_group_segments: std::collections::BTreeMap::new(),
5748 };
5749
5750 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5752 msg_fields.insert(
5753 "nad.0".to_string(),
5754 FieldMapping::Simple("marktrolle".to_string()),
5755 );
5756 let msg_defs = vec![MappingDefinition {
5757 meta: MappingMeta {
5758 entity: "Marktteilnehmer".to_string(),
5759 bo4e_type: "Marktteilnehmer".to_string(),
5760 source_group: "SG2".to_string(),
5761 source_path: None,
5762 discriminator: None,
5763 repeat_on_tag: None,
5764 parent_field: None,
5765 target_list: None,
5766 order: None,
5767 },
5768 fields: msg_fields,
5769 complex_handlers: None,
5770 }];
5771
5772 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5774 tx_fields.insert(
5775 "ide.1".to_string(),
5776 FieldMapping::Simple("vorgangId".to_string()),
5777 );
5778 let tx_defs = vec![MappingDefinition {
5779 meta: MappingMeta {
5780 entity: "Prozessdaten".to_string(),
5781 bo4e_type: "Prozessdaten".to_string(),
5782 source_group: "SG4".to_string(),
5783 source_path: None,
5784 discriminator: None,
5785 repeat_on_tag: None,
5786 parent_field: None,
5787 target_list: None,
5788 order: None,
5789 },
5790 fields: tx_fields,
5791 complex_handlers: None,
5792 }];
5793
5794 let msg_engine = MappingEngine::from_definitions(msg_defs);
5795 let tx_engine = MappingEngine::from_definitions(tx_defs);
5796
5797 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
5798
5799 assert!(result.stammdaten["marktteilnehmer"].is_object());
5801 assert_eq!(
5802 result.stammdaten["marktteilnehmer"]["marktrolle"]
5803 .as_str()
5804 .unwrap(),
5805 "MS"
5806 );
5807
5808 assert_eq!(result.transaktionen.len(), 2);
5810 assert_eq!(
5811 result.transaktionen[0].transaktionsdaten["vorgangId"]
5812 .as_str()
5813 .unwrap(),
5814 "TX001"
5815 );
5816 assert_eq!(
5817 result.transaktionen[1].transaktionsdaten["vorgangId"]
5818 .as_str()
5819 .unwrap(),
5820 "TX002"
5821 );
5822 }
5823
5824 #[test]
5825 fn test_map_reverse_with_segment_structure_pads_trailing() {
5826 let mut fields = IndexMap::new();
5828 fields.insert(
5829 "sts.0".to_string(),
5830 FieldMapping::Structured(StructuredFieldMapping {
5831 target: String::new(),
5832 transform: None,
5833 when: None,
5834 default: Some("7".to_string()),
5835 enum_map: None,
5836 code_list: None,
5837 also_code_list: None,
5838 when_filled: None,
5839 also_target: None,
5840 also_enum_map: None,
5841 }),
5842 );
5843 fields.insert(
5844 "sts.2".to_string(),
5845 FieldMapping::Simple("grund".to_string()),
5846 );
5847
5848 let def = make_def(fields);
5849
5850 let mut counts = std::collections::BTreeMap::new();
5852 counts.insert("STS".to_string(), 5usize);
5853 let ss = SegmentStructure {
5854 element_counts: counts,
5855 };
5856
5857 let engine = MappingEngine::from_definitions(vec![]).with_segment_structure(ss);
5858
5859 let bo4e = serde_json::json!({ "grund": "E01" });
5860
5861 let instance = engine.map_reverse(&bo4e, &def);
5862 let sts = &instance.segments[0];
5863 assert_eq!(sts.elements.len(), 5);
5866 assert_eq!(sts.elements[0], vec!["7"]);
5867 assert_eq!(sts.elements[1], vec![""]);
5868 assert_eq!(sts.elements[2], vec!["E01"]);
5869 assert_eq!(sts.elements[3], vec![""]);
5870 assert_eq!(sts.elements[4], vec![""]);
5871 }
5872
5873 #[test]
5874 fn test_resolve_child_relative_with_source_path() {
5875 let mut map: std::collections::HashMap<String, Vec<usize>> =
5876 std::collections::HashMap::new();
5877 map.insert("sg4.sg8_ze1".to_string(), vec![6]);
5878 map.insert("sg4.sg8_z98".to_string(), vec![0]);
5879
5880 assert_eq!(
5882 resolve_child_relative("SG8.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5883 "SG8:6.SG10"
5884 );
5885
5886 assert_eq!(
5888 resolve_child_relative("SG8:3.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5889 "SG8:3.SG10"
5890 );
5891
5892 assert_eq!(
5894 resolve_child_relative("SG8.SG10", Some("sg4.sg8_unknown.sg10"), &map, 0),
5895 "SG8.SG10"
5896 );
5897
5898 assert_eq!(
5900 resolve_child_relative("SG8.SG10", None, &map, 0),
5901 "SG8.SG10"
5902 );
5903
5904 assert_eq!(
5906 resolve_child_relative("SG8.SG9", Some("sg4.sg8_z98.sg9"), &map, 0),
5907 "SG8:0.SG9"
5908 );
5909
5910 map.insert("sg4.sg8_zf3".to_string(), vec![3, 4]);
5912 assert_eq!(
5913 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 0),
5914 "SG8:3.SG10"
5915 );
5916 assert_eq!(
5917 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 1),
5918 "SG8:4.SG10"
5919 );
5920 }
5921
5922 #[test]
5923 fn test_place_in_groups_returns_rep_index() {
5924 let mut groups: Vec<AssembledGroup> = Vec::new();
5925
5926 let instance = AssembledGroupInstance {
5928 segments: vec![],
5929 child_groups: vec![],
5930 entry_mig_number: None,
5931 variant_mig_numbers: vec![],
5932 skipped_segments: vec![],
5933 skipped_positions: Vec::new(),
5934 };
5935 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 0);
5936
5937 let instance = AssembledGroupInstance {
5939 segments: vec![],
5940 child_groups: vec![],
5941 entry_mig_number: None,
5942 variant_mig_numbers: vec![],
5943 skipped_segments: vec![],
5944 skipped_positions: Vec::new(),
5945 };
5946 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 1);
5947
5948 let instance = AssembledGroupInstance {
5950 segments: vec![],
5951 child_groups: vec![],
5952 entry_mig_number: None,
5953 variant_mig_numbers: vec![],
5954 skipped_segments: vec![],
5955 skipped_positions: Vec::new(),
5956 };
5957 assert_eq!(place_in_groups(&mut groups, "SG8:5", instance), 5);
5958 }
5959
5960 #[test]
5961 fn test_resolve_by_source_path() {
5962 use mig_assembly::assembler::*;
5963
5964 let tree = AssembledTree {
5966 segments: vec![],
5967 groups: vec![AssembledGroup {
5968 group_id: "SG4".to_string(),
5969 repetitions: vec![AssembledGroupInstance {
5970 segments: vec![],
5971 child_groups: vec![AssembledGroup {
5972 group_id: "SG8".to_string(),
5973 repetitions: vec![
5974 AssembledGroupInstance {
5975 segments: vec![AssembledSegment {
5976 tag: "SEQ".to_string(),
5977 elements: vec![vec!["Z98".to_string()]],
5978 mig_number: None,
5979 segment_number: None,
5980 }],
5981 child_groups: vec![AssembledGroup {
5982 group_id: "SG10".to_string(),
5983 repetitions: vec![AssembledGroupInstance {
5984 segments: vec![AssembledSegment {
5985 tag: "CCI".to_string(),
5986 elements: vec![vec![], vec![], vec!["ZB3".to_string()]],
5987 mig_number: None,
5988 segment_number: None,
5989 }],
5990 child_groups: vec![],
5991 entry_mig_number: None,
5992 variant_mig_numbers: vec![],
5993 skipped_segments: vec![],
5994 skipped_positions: Vec::new(),
5995 }],
5996 }],
5997 entry_mig_number: None,
5998 variant_mig_numbers: vec![],
5999 skipped_segments: vec![],
6000 skipped_positions: Vec::new(),
6001 },
6002 AssembledGroupInstance {
6003 segments: vec![AssembledSegment {
6004 tag: "SEQ".to_string(),
6005 elements: vec![vec!["ZD7".to_string()]],
6006 mig_number: None,
6007 segment_number: None,
6008 }],
6009 child_groups: vec![AssembledGroup {
6010 group_id: "SG10".to_string(),
6011 repetitions: vec![AssembledGroupInstance {
6012 segments: vec![AssembledSegment {
6013 tag: "CCI".to_string(),
6014 elements: vec![vec![], vec![], vec!["ZE6".to_string()]],
6015 mig_number: None,
6016 segment_number: None,
6017 }],
6018 child_groups: vec![],
6019 entry_mig_number: None,
6020 variant_mig_numbers: vec![],
6021 skipped_segments: vec![],
6022 skipped_positions: Vec::new(),
6023 }],
6024 }],
6025 entry_mig_number: None,
6026 variant_mig_numbers: vec![],
6027 skipped_segments: vec![],
6028 skipped_positions: Vec::new(),
6029 },
6030 ],
6031 }],
6032 entry_mig_number: None,
6033 variant_mig_numbers: vec![],
6034 skipped_segments: vec![],
6035 skipped_positions: Vec::new(),
6036 }],
6037 }],
6038 post_group_start: 0,
6039 inter_group_segments: std::collections::BTreeMap::new(),
6040 };
6041
6042 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_z98.sg10");
6044 assert!(inst.is_some());
6045 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
6046
6047 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zd7.sg10");
6049 assert!(inst.is_some());
6050 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZE6");
6051
6052 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zzz.sg10");
6054 assert!(inst.is_none());
6055
6056 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8.sg10");
6058 assert!(inst.is_some());
6059 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
6060 }
6061
6062 #[test]
6063 fn test_parse_source_path_part() {
6064 assert_eq!(parse_source_path_part("sg4"), ("sg4", None));
6065 assert_eq!(parse_source_path_part("sg8_z98"), ("sg8", Some("z98")));
6066 assert_eq!(parse_source_path_part("sg10"), ("sg10", None));
6067 assert_eq!(parse_source_path_part("sg12_z04"), ("sg12", Some("z04")));
6068 }
6069
6070 #[test]
6071 fn test_has_source_path_qualifiers() {
6072 assert!(has_source_path_qualifiers("sg4.sg8_z98.sg10"));
6073 assert!(has_source_path_qualifiers("sg4.sg8_ze1.sg9"));
6074 assert!(!has_source_path_qualifiers("sg4.sg6"));
6075 assert!(!has_source_path_qualifiers("sg4.sg8.sg10"));
6076 }
6077
6078 #[test]
6079 fn test_extract_all_from_instance_collects_all_qualifier_matches() {
6080 use mig_assembly::assembler::*;
6081
6082 let instance = AssembledGroupInstance {
6084 segments: vec![
6085 AssembledSegment {
6086 tag: "SEQ".to_string(),
6087 elements: vec![vec!["ZD6".to_string()]],
6088 mig_number: None,
6089 segment_number: None,
6090 },
6091 AssembledSegment {
6092 tag: "RFF".to_string(),
6093 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
6094 mig_number: None,
6095 segment_number: None,
6096 },
6097 AssembledSegment {
6098 tag: "RFF".to_string(),
6099 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
6100 mig_number: None,
6101 segment_number: None,
6102 },
6103 AssembledSegment {
6104 tag: "RFF".to_string(),
6105 elements: vec![vec!["Z34".to_string(), "REF_C".to_string()]],
6106 mig_number: None,
6107 segment_number: None,
6108 },
6109 AssembledSegment {
6110 tag: "RFF".to_string(),
6111 elements: vec![vec!["Z35".to_string(), "OTHER".to_string()]],
6112 mig_number: None,
6113 segment_number: None,
6114 },
6115 ],
6116 child_groups: vec![],
6117 entry_mig_number: None,
6118 variant_mig_numbers: vec![],
6119 skipped_segments: vec![],
6120 skipped_positions: Vec::new(),
6121 };
6122
6123 let all = MappingEngine::extract_all_from_instance(&instance, "rff[Z34,*].0.1");
6125 assert_eq!(all, vec!["REF_A", "REF_B", "REF_C"]);
6126
6127 let single = MappingEngine::extract_from_instance(&instance, "rff[Z34].0.1");
6129 assert_eq!(single, Some("REF_A".to_string()));
6130
6131 let second = MappingEngine::extract_from_instance(&instance, "rff[Z34,1].0.1");
6132 assert_eq!(second, Some("REF_B".to_string()));
6133 }
6134}