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((list, sub)) = list_target(target) {
1074 self.extract_list_field(
1075 instance,
1076 def,
1077 path,
1078 list,
1079 sub,
1080 enum_map,
1081 enrich_codes,
1082 result,
1083 );
1084 continue;
1085 }
1086 if let Some(val) = Self::extract_from_instance(instance, path) {
1087 if let FieldMapping::Structured(s) = field_mapping {
1092 if let (false, Some(also), Some(also_map)) = (
1093 self.raw_codes,
1094 s.also_target.as_deref(),
1095 self.table(s.also_enum_map.as_ref(), s.also_code_list.as_deref()),
1096 ) {
1097 if let Some(also_val) = also_map.get(&val) {
1098 set_nested_value(result, also, also_val.clone());
1099 }
1100 }
1101 }
1102
1103 let mapped_val = match enum_map {
1104 Some(map) if !self.raw_codes => {
1105 map.get(&val).cloned().unwrap_or_else(|| val.clone())
1106 }
1107 _ => val.clone(),
1108 };
1109
1110 if enrich_codes {
1112 if let (Some(ref code_lookup), Some(ref source_path)) =
1113 (&self.code_lookup, &def.meta.source_path)
1114 {
1115 let parts: Vec<&str> = path.split('.').collect();
1116 let (seg_tag, path_qualifier, _occ) = parse_tag_qualifier(parts[0]);
1117 let (element_idx, component_idx) =
1118 Self::parse_element_component(&parts[1..]);
1119 let disc_qualifier = Self::discriminator_qualifier_for_tag(def, &seg_tag);
1120 let q = disc_qualifier.as_deref();
1121
1122 if let Some(codes) = code_lookup.enrichment_codes(
1123 source_path,
1124 &seg_tag,
1125 path_qualifier,
1126 q,
1127 element_idx,
1128 component_idx,
1129 ) {
1130 if let Some(ref pid) = self.current_pid {
1135 if codes.len() == 1 && codes.contains_key(pid.as_str()) {
1136 set_nested_value(result, target, mapped_val);
1137 continue;
1138 }
1139 }
1140
1141 let enrichment = codes.get(&val);
1145 let meaning = enrichment
1146 .map(|e| serde_json::Value::String(e.meaning.clone()))
1147 .unwrap_or(serde_json::Value::Null);
1148
1149 let mut obj = serde_json::Map::new();
1150 obj.insert("code".into(), serde_json::json!(mapped_val));
1151 obj.insert("meaning".into(), meaning);
1152 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
1153 obj.insert("enum".into(), serde_json::json!(enum_key));
1154 }
1155 let enriched = serde_json::Value::Object(obj);
1156 set_nested_value_json(result, target, enriched);
1157 continue;
1158 }
1159 }
1160 }
1161
1162 set_nested_value(result, target, mapped_val);
1163 }
1164 }
1165
1166 if !instance.child_groups.is_empty() {
1169 self.extract_nested_children(instance, def, result, enrich_codes);
1170 }
1171 }
1172
1173 #[allow(clippy::too_many_arguments)]
1181 fn extract_list_field(
1182 &self,
1183 instance: &AssembledGroupInstance,
1184 def: &MappingDefinition,
1185 path: &str,
1186 list: &str,
1187 sub: &str,
1188 enum_map: Option<&std::collections::BTreeMap<String, String>>,
1189 enrich_codes: bool,
1190 result: &mut serde_json::Map<String, serde_json::Value>,
1191 ) {
1192 let parts: Vec<&str> = path.split('.').collect();
1193 if parts.len() < 2 {
1194 return;
1195 }
1196 let (seg_tag, qualifier, _) = parse_tag_qualifier(parts[0]);
1197 let segments: Vec<&AssembledSegment> = instance
1198 .segments
1199 .iter()
1200 .filter(|s| {
1201 s.tag.eq_ignore_ascii_case(&seg_tag)
1202 && qualifier.map_or(true, |q| {
1203 s.elements
1204 .first()
1205 .and_then(|e| e.first())
1206 .map(|v| v.as_str())
1207 == Some(q)
1208 })
1209 })
1210 .collect();
1211 if segments.is_empty() {
1212 return;
1213 }
1214 let items = result
1215 .entry(list.to_string())
1216 .or_insert_with(|| serde_json::Value::Array(Vec::new()));
1217 let Some(items) = items.as_array_mut() else {
1218 return;
1219 };
1220 while items.len() < segments.len() {
1221 items.push(serde_json::Value::Object(serde_json::Map::new()));
1222 }
1223 let codes = if enrich_codes {
1224 match (&self.code_lookup, &def.meta.source_path) {
1225 (Some(lookup), Some(source_path)) => {
1226 let (element_idx, component_idx) = Self::parse_element_component(&parts[1..]);
1227 let disc = Self::discriminator_qualifier_for_tag(def, &seg_tag);
1228 lookup
1229 .enrichment_codes(
1230 source_path,
1231 &seg_tag,
1232 qualifier,
1233 disc.as_deref(),
1234 element_idx,
1235 component_idx,
1236 )
1237 .cloned()
1238 }
1239 _ => None,
1240 }
1241 } else {
1242 None
1243 };
1244 for (item, segment) in items.iter_mut().zip(segments) {
1245 let Some(val) = Self::resolve_field_path(segment, &parts[1..]) else {
1246 continue;
1247 };
1248 let mapped = match enum_map {
1249 Some(map) if !self.raw_codes => {
1250 map.get(&val).cloned().unwrap_or_else(|| val.clone())
1251 }
1252 _ => val.clone(),
1253 };
1254 let value = match &codes {
1255 Some(codes) => {
1256 let enrichment = codes.get(&val);
1257 let mut obj = serde_json::Map::new();
1258 obj.insert("code".into(), serde_json::json!(mapped));
1259 obj.insert(
1260 "meaning".into(),
1261 enrichment
1262 .map(|e| serde_json::Value::String(e.meaning.clone()))
1263 .unwrap_or(serde_json::Value::Null),
1264 );
1265 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
1266 obj.insert("enum".into(), serde_json::json!(enum_key));
1267 }
1268 serde_json::Value::Object(obj)
1269 }
1270 None => serde_json::Value::String(mapped),
1271 };
1272 if let Some(obj) = item.as_object_mut() {
1273 set_nested_value_json(obj, sub, value);
1274 }
1275 }
1276 }
1277
1278 fn extract_nested_children(
1283 &self,
1284 instance: &AssembledGroupInstance,
1285 def: &MappingDefinition,
1286 result: &mut serde_json::Map<String, serde_json::Value>,
1287 enrich_codes: bool,
1288 ) {
1289 for child in self
1290 .definitions
1291 .iter()
1292 .filter(|c| is_nested_child_of(c, def))
1293 {
1294 if nested_parent_qualifier(child).is_some_and(|q| !entry_qualifier_matches(instance, q))
1295 {
1296 continue;
1297 }
1298 let (leaf_id, leaf_qualifier) = nested_child_leaf(child);
1299 let Some(group) = instance
1300 .child_groups
1301 .iter()
1302 .find(|g| g.group_id.eq_ignore_ascii_case(&leaf_id))
1303 else {
1304 continue;
1305 };
1306 let reps: Vec<&AssembledGroupInstance> = match leaf_qualifier {
1307 Some(q) => find_all_reps_by_entry_qualifier(&group.repetitions, q),
1308 None => group.repetitions.iter().collect(),
1309 };
1310
1311 let mut items: Vec<serde_json::Value> = Vec::new();
1312 let mut push_item = |sub: &AssembledGroupInstance| {
1313 let mut obj = serde_json::Map::new();
1314 self.extract_fields_from_instance(sub, child, &mut obj, enrich_codes);
1315 if !obj.is_empty() {
1316 items.push(serde_json::Value::Object(obj));
1317 }
1318 };
1319 for rep in reps {
1320 let repeat_tag = child
1321 .meta
1322 .repeat_on_tag
1323 .as_deref()
1324 .filter(|tag| rep.segments.iter().any(|s| s.tag.eq_ignore_ascii_case(tag)));
1325 let Some(tag) = repeat_tag else {
1326 push_item(rep);
1327 continue;
1328 };
1329 let shared: Vec<AssembledSegment> = rep
1332 .segments
1333 .iter()
1334 .filter(|s| !s.tag.eq_ignore_ascii_case(tag))
1335 .cloned()
1336 .collect();
1337 for seg in rep
1338 .segments
1339 .iter()
1340 .filter(|s| s.tag.eq_ignore_ascii_case(tag))
1341 {
1342 let mut segments = shared.clone();
1343 segments.push(seg.clone());
1344 push_item(&AssembledGroupInstance {
1345 segments,
1346 child_groups: vec![],
1347 entry_mig_number: None,
1348 variant_mig_numbers: vec![],
1349 skipped_segments: Vec::new(),
1350 skipped_positions: Vec::new(),
1351 });
1352 }
1353 }
1354 if items.is_empty() {
1355 continue;
1356 }
1357 let field = child.meta.parent_field.as_deref().unwrap_or_default();
1358 match result.get_mut(field) {
1359 Some(serde_json::Value::Array(existing)) => existing.extend(items),
1360 _ => {
1361 result.insert(field.to_string(), serde_json::Value::Array(items));
1362 }
1363 }
1364 }
1365 }
1366
1367 fn reverse_nested_children(
1371 &self,
1372 bo4e_value: &serde_json::Value,
1373 def: &MappingDefinition,
1374 instance: &mut AssembledGroupInstance,
1375 ) {
1376 let mut handled_fields: Vec<&str> = Vec::new();
1377 for child in self
1378 .definitions
1379 .iter()
1380 .filter(|c| is_nested_child_of(c, def))
1381 {
1382 let field = child.meta.parent_field.as_deref().unwrap_or_default();
1383 if handled_fields.contains(&field) {
1384 continue;
1385 }
1386 if nested_parent_qualifier(child)
1387 .is_some_and(|q| !rebuilt_entry_qualifier_matches(instance, def, q))
1388 {
1389 continue;
1390 }
1391 let elements: Vec<&serde_json::Value> = match bo4e_value.get(field) {
1392 Some(serde_json::Value::Array(arr)) => arr.iter().collect(),
1393 Some(serde_json::Value::Null) | None => continue,
1394 Some(other) => vec![other],
1395 };
1396
1397 let mut reps: Vec<AssembledGroupInstance> = Vec::new();
1398 if let Some(tag) = child.meta.repeat_on_tag.as_deref() {
1399 let mut merged: Option<AssembledGroupInstance> = None;
1402 for element in elements {
1403 let sub = self.map_reverse_single(element, child);
1404 if sub.segments.is_empty() {
1405 continue;
1406 }
1407 match merged.as_mut() {
1408 None => merged = Some(sub),
1409 Some(m) => m.segments.extend(
1410 sub.segments
1411 .into_iter()
1412 .filter(|s| s.tag.eq_ignore_ascii_case(tag)),
1413 ),
1414 }
1415 }
1416 reps.extend(merged);
1417 } else {
1418 for element in elements {
1419 let mut sub = self.map_reverse_single(element, child);
1420 if sub.segments.is_empty() {
1421 continue;
1422 }
1423 self.reverse_nested_children(element, child, &mut sub);
1425 reps.push(sub);
1426 }
1427 }
1428 if reps.is_empty() {
1429 continue;
1430 }
1431 handled_fields.push(field);
1432
1433 let (leaf_id, _) = nested_child_leaf(child);
1434 match instance
1435 .child_groups
1436 .iter_mut()
1437 .find(|g| g.group_id.eq_ignore_ascii_case(&leaf_id))
1438 {
1439 Some(group) => group.repetitions.extend(reps),
1440 None => instance.child_groups.push(AssembledGroup {
1441 group_id: leaf_id,
1442 repetitions: reps,
1443 }),
1444 }
1445 }
1446 }
1447
1448 pub(crate) fn discriminator_qualifier(def: &MappingDefinition) -> Option<String> {
1455 def.meta
1456 .discriminator
1457 .as_deref()
1458 .and_then(|d| d.split_once('=').map(|(_, v)| v.to_string()))
1459 }
1460
1461 pub(crate) fn discriminator_qualifier_for_tag(
1466 def: &MappingDefinition,
1467 segment_tag: &str,
1468 ) -> Option<String> {
1469 let (lhs, value) = def.meta.discriminator.as_deref()?.split_once('=')?;
1470 let disc_tag = lhs.split('.').next().unwrap_or(lhs);
1471 disc_tag
1472 .eq_ignore_ascii_case(segment_tag)
1473 .then(|| value.to_string())
1474 }
1475
1476 pub fn map_forward_from_segments(
1482 &self,
1483 segments: &[OwnedSegment],
1484 def: &MappingDefinition,
1485 ) -> serde_json::Value {
1486 let assembled_segments: Vec<AssembledSegment> = segments
1487 .iter()
1488 .map(|s| AssembledSegment {
1489 tag: s.id.clone(),
1490 elements: s.elements.clone(),
1491 mig_number: None,
1492 segment_number: Some(s.segment_number),
1493 })
1494 .collect();
1495
1496 let instance = AssembledGroupInstance {
1497 segments: assembled_segments,
1498 child_groups: vec![],
1499 entry_mig_number: None,
1500 variant_mig_numbers: vec![],
1501 skipped_segments: Vec::new(),
1502 skipped_positions: Vec::new(),
1503 };
1504
1505 let mut result = serde_json::Map::new();
1506 self.extract_fields_from_instance(&instance, def, &mut result, true);
1507 serde_json::Value::Object(result)
1508 }
1509
1510 pub fn map_reverse(
1523 &self,
1524 bo4e_value: &serde_json::Value,
1525 def: &MappingDefinition,
1526 ) -> AssembledGroupInstance {
1527 if def.meta.repeat_on_tag.is_some() {
1529 if let Some(arr) = bo4e_value.as_array() {
1530 let mut all_segments = Vec::new();
1531 for elem in arr {
1532 let sub = self.map_reverse_single(elem, def);
1533 all_segments.extend(sub.segments);
1534 }
1535 return AssembledGroupInstance {
1536 segments: all_segments,
1537 child_groups: vec![],
1538 entry_mig_number: None,
1539 variant_mig_numbers: vec![],
1540 skipped_segments: Vec::new(),
1541 skipped_positions: Vec::new(),
1542 };
1543 }
1544 }
1545 let mut instance = self.map_reverse_single(bo4e_value, def);
1546 if def.meta.parent_field.is_none() && !instance.segments.is_empty() {
1547 self.reverse_nested_children(bo4e_value, def, &mut instance);
1548 }
1549 instance
1550 }
1551
1552 fn map_reverse_single(
1553 &self,
1554 bo4e_value: &serde_json::Value,
1555 def: &MappingDefinition,
1556 ) -> AssembledGroupInstance {
1557 let mut field_values: Vec<(String, String, usize, usize, String)> =
1560 Vec::with_capacity(def.fields.len());
1561
1562 let mut has_real_data = false;
1569 let mut has_data_fields = false;
1570 let mut seg_has_data_field: HashSet<String> = HashSet::new();
1573 let mut seg_has_real_data: HashSet<String> = HashSet::new();
1574 let mut injected_qualifiers: HashSet<String> = HashSet::new();
1575 type ListField<'a> = (
1578 &'a str,
1579 &'a str,
1580 String,
1581 Option<String>,
1582 usize,
1583 usize,
1584 Option<&'a std::collections::BTreeMap<String, String>>,
1585 );
1586 let mut list_fields: Vec<ListField<'_>> = Vec::new();
1587
1588 for (path, field_mapping) in &def.fields {
1589 let (target, default, enum_map, when_filled, also_target, also_enum_map) =
1590 match field_mapping {
1591 FieldMapping::Simple(t) => (t.as_str(), None, None, None, None, None),
1592 FieldMapping::Structured(s) => (
1593 s.target.as_str(),
1594 s.default.as_ref(),
1595 self.table(s.enum_map.as_ref(), s.code_list.as_deref()),
1596 s.when_filled.as_ref(),
1597 s.also_target.as_deref(),
1598 self.table(s.also_enum_map.as_ref(), s.also_code_list.as_deref()),
1599 ),
1600 FieldMapping::Nested(_) => continue,
1601 };
1602
1603 let parts: Vec<&str> = path.split('.').collect();
1604 if parts.len() < 2 {
1605 continue;
1606 }
1607
1608 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1609 let seg_key = parts[0].to_uppercase();
1612 let sub_path = &parts[1..];
1613
1614 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1616 let ci = if sub_path.len() > 1 {
1617 sub_path[1].parse::<usize>().unwrap_or(0)
1618 } else {
1619 0
1620 };
1621 (ei, ci)
1622 } else {
1623 match sub_path.len() {
1624 1 => (0, 0),
1625 2 => (1, 0),
1626 _ => continue,
1627 }
1628 };
1629
1630 if let Some((list, sub)) = list_target(target) {
1631 list_fields.push((
1632 list,
1633 sub,
1634 seg_tag.clone(),
1635 qualifier.map(str::to_string),
1636 element_idx,
1637 component_idx,
1638 enum_map,
1639 ));
1640 continue;
1641 }
1642
1643 let val = if target.is_empty() {
1645 match (default, when_filled) {
1646 (Some(d), Some(fields)) => {
1648 let any_filled = fields.iter().any(|f| field_is_filled(bo4e_value, f));
1649 if any_filled {
1650 has_real_data = true;
1653 Some(d.clone())
1654 } else {
1655 None
1656 }
1657 }
1658 (Some(d), None) => Some(d.clone()),
1660 (None, _) => None,
1661 }
1662 } else {
1663 has_data_fields = true;
1664 seg_has_data_field.insert(seg_key.clone());
1665 let bo4e_val = self.populate_field(bo4e_value, target);
1666 if bo4e_val.is_some() {
1667 has_real_data = true;
1668 seg_has_real_data.insert(seg_key.clone());
1669 }
1670 let mapped_val = match (bo4e_val, enum_map) {
1672 (Some(v), Some(map)) => {
1673 let joint = match (also_target, also_enum_map) {
1680 (Some(also), Some(also_map)) => {
1681 self.populate_field(bo4e_value, also).and_then(|also_v| {
1682 map.iter()
1683 .find(|(code, bo4e_v)| {
1684 *bo4e_v == &v && also_map.get(*code) == Some(&also_v)
1685 })
1686 .map(|(code, _)| code.clone())
1687 })
1688 }
1689 _ => None,
1690 };
1691 joint
1692 .or_else(|| {
1693 map.iter()
1695 .find(|(_, bo4e_v)| *bo4e_v == &v)
1696 .map(|(edifact_k, _)| edifact_k.clone())
1697 })
1698 .or(Some(v))
1699 }
1700 (v, _) => v,
1701 };
1702 mapped_val.or_else(|| default.cloned())
1703 };
1704
1705 if let Some(val) = val {
1706 field_values.push((
1707 seg_key.clone(),
1708 seg_tag.clone(),
1709 element_idx,
1710 component_idx,
1711 val,
1712 ));
1713 }
1714
1715 if let Some(q) = qualifier {
1717 if injected_qualifiers.insert(seg_key.clone()) {
1718 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1719 }
1720 }
1721 }
1722
1723 let longest = list_fields
1726 .iter()
1727 .filter_map(|(list, ..)| bo4e_value.get(*list).and_then(|v| v.as_array()))
1728 .map(|a| a.len())
1729 .max()
1730 .unwrap_or(0);
1731 if !list_fields.is_empty() {
1732 has_data_fields = true;
1733 }
1734 for i in 0..longest {
1735 for (list, sub, seg_tag, qualifier, element_idx, component_idx, enum_map) in
1736 &list_fields
1737 {
1738 let key = match qualifier {
1739 Some(q) => format!("{seg_tag}[{q},{i}]"),
1740 None => format!("{seg_tag}[*,{i}]"),
1741 };
1742 seg_has_data_field.insert(key.clone());
1743 let Some(item) = bo4e_value
1744 .get(*list)
1745 .and_then(|v| v.as_array())
1746 .and_then(|a| a.get(i))
1747 else {
1748 continue;
1749 };
1750 let Some(value) = self.populate_field(item, sub) else {
1751 continue;
1752 };
1753 let value = match enum_map {
1754 Some(map) => map
1755 .iter()
1756 .find(|(_, name)| **name == value)
1757 .map(|(code, _)| code.clone())
1758 .unwrap_or(value),
1759 None => value,
1760 };
1761 has_real_data = true;
1762 seg_has_real_data.insert(key.clone());
1763 field_values.push((
1764 key.clone(),
1765 seg_tag.clone(),
1766 *element_idx,
1767 *component_idx,
1768 value,
1769 ));
1770 if let Some(q) = qualifier {
1771 if injected_qualifiers.insert(key.clone()) {
1772 field_values.push((key, seg_tag.clone(), 0, 0, q.clone()));
1773 }
1774 }
1775 }
1776 }
1777
1778 field_values.retain(|(seg_key, _, _, _, _)| {
1786 if !seg_key.contains('[') {
1787 return true; }
1789 !seg_has_data_field.contains(seg_key) || seg_has_real_data.contains(seg_key)
1790 });
1791
1792 if has_data_fields && !has_real_data {
1797 return AssembledGroupInstance {
1798 segments: vec![],
1799 child_groups: vec![],
1800 entry_mig_number: None,
1801 variant_mig_numbers: vec![],
1802 skipped_segments: Vec::new(),
1803 skipped_positions: Vec::new(),
1804 };
1805 }
1806
1807 let mut segments: Vec<AssembledSegment> = Vec::with_capacity(field_values.len());
1810 let mut seen_keys: HashMap<String, usize> = HashMap::new();
1811
1812 for (seg_key, seg_tag, element_idx, component_idx, val) in &field_values {
1813 let seg = if let Some(&pos) = seen_keys.get(seg_key) {
1814 &mut segments[pos]
1815 } else {
1816 let pos = segments.len();
1817 seen_keys.insert(seg_key.clone(), pos);
1818 segments.push(AssembledSegment {
1819 tag: seg_tag.clone(),
1820 elements: vec![],
1821 mig_number: None,
1822 segment_number: None,
1823 });
1824 &mut segments[pos]
1825 };
1826
1827 while seg.elements.len() <= *element_idx {
1828 seg.elements.push(vec![]);
1829 }
1830 while seg.elements[*element_idx].len() <= *component_idx {
1831 seg.elements[*element_idx].push(String::new());
1832 }
1833 seg.elements[*element_idx][*component_idx] = val.clone();
1834 }
1835
1836 for seg in &mut segments {
1839 let last_populated = seg.elements.iter().rposition(|e| !e.is_empty());
1840 if let Some(last_idx) = last_populated {
1841 for i in 0..last_idx {
1842 if seg.elements[i].is_empty() {
1843 seg.elements[i] = vec![String::new()];
1844 }
1845 }
1846 }
1847 }
1848
1849 if let Some(ref ss) = self.segment_structure {
1851 for seg in &mut segments {
1852 if let Some(expected) = ss.element_count(&seg.tag) {
1853 while seg.elements.len() < expected {
1854 seg.elements.push(vec![String::new()]);
1855 }
1856 }
1857 }
1858 }
1859
1860 AssembledGroupInstance {
1861 segments,
1862 child_groups: vec![],
1863 entry_mig_number: None,
1864 variant_mig_numbers: vec![],
1865 skipped_segments: Vec::new(),
1866 skipped_positions: Vec::new(),
1867 }
1868 }
1869
1870 fn resolve_field_path(segment: &AssembledSegment, path: &[&str]) -> Option<String> {
1883 if path.is_empty() {
1884 return None;
1885 }
1886
1887 if let Ok(element_idx) = path[0].parse::<usize>() {
1889 let component_idx = if path.len() > 1 {
1890 path[1].parse::<usize>().unwrap_or(0)
1891 } else {
1892 0
1893 };
1894 return segment
1895 .elements
1896 .get(element_idx)?
1897 .get(component_idx)
1898 .filter(|v| !v.is_empty())
1899 .cloned();
1900 }
1901
1902 None
1908 }
1909
1910 pub(crate) fn parse_element_component(parts: &[&str]) -> (usize, usize) {
1913 if parts.is_empty() {
1914 return (0, 0);
1915 }
1916 let element_idx = parts[0].parse::<usize>().unwrap_or(0);
1917 let component_idx = if parts.len() > 1 {
1918 parts[1].parse::<usize>().unwrap_or(0)
1919 } else {
1920 0
1921 };
1922 (element_idx, component_idx)
1923 }
1924
1925 pub fn populate_field(
1928 &self,
1929 bo4e_value: &serde_json::Value,
1930 target_field: &str,
1931 ) -> Option<String> {
1932 let mut current = bo4e_value;
1933 for part in target_field.split('.') {
1934 current = current.get(part)?;
1935 }
1936 if let Some(code) = current.get("code").and_then(|v| v.as_str()) {
1938 return Some(code.to_string());
1939 }
1940 current.as_str().map(|s| s.to_string())
1941 }
1942
1943 pub fn build_segment_from_bo4e(
1945 &self,
1946 bo4e_value: &serde_json::Value,
1947 segment_tag: &str,
1948 target_field: &str,
1949 ) -> AssembledSegment {
1950 let value = self.populate_field(bo4e_value, target_field);
1951 let elements = if let Some(val) = value {
1952 vec![vec![val]]
1953 } else {
1954 vec![]
1955 };
1956 AssembledSegment {
1957 tag: segment_tag.to_uppercase(),
1958 elements,
1959 mig_number: None,
1960 segment_number: None,
1961 }
1962 }
1963
1964 pub fn resolve_repetition(
1973 tree: &AssembledTree,
1974 group_path: &str,
1975 discriminator: &str,
1976 ) -> Option<usize> {
1977 let (spec, expected) = discriminator.split_once('=')?;
1978 let parts: Vec<&str> = spec.split('.').collect();
1979 if parts.len() != 3 {
1980 return None;
1981 }
1982 let tag = parts[0];
1983 let element_idx: usize = parts[1].parse().ok()?;
1984 let component_idx: usize = parts[2].parse().ok()?;
1985
1986 let path_parts: Vec<&str> = group_path.split('.').collect();
1988
1989 let leaf_group = if path_parts.len() == 1 {
1990 let (group_id, _) = parse_group_spec(path_parts[0]);
1991 tree.groups.iter().find(|g| g.group_id == group_id)?
1992 } else {
1993 let parent_parts = &path_parts[..path_parts.len() - 1];
1995 let mut current_instance = {
1996 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1997 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
1998 first_group.repetitions.get(first_rep.unwrap_or(0))?
1999 };
2000 for part in &parent_parts[1..] {
2001 let (group_id, explicit_rep) = parse_group_spec(part);
2002 let child_group = current_instance
2003 .child_groups
2004 .iter()
2005 .find(|g| g.group_id == group_id)?;
2006 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
2007 }
2008 let (leaf_id, _) = parse_group_spec(path_parts.last()?);
2009 current_instance
2010 .child_groups
2011 .iter()
2012 .find(|g| g.group_id == leaf_id)?
2013 };
2014
2015 let expected_values: Vec<&str> = expected.split('|').collect();
2017 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
2018 let matches = instance.segments.iter().any(|s| {
2019 s.tag.eq_ignore_ascii_case(tag)
2020 && s.elements
2021 .get(element_idx)
2022 .and_then(|e| e.get(component_idx))
2023 .map(|v| expected_values.iter().any(|ev| v == ev))
2024 .unwrap_or(false)
2025 });
2026 if matches {
2027 return Some(rep_idx);
2028 }
2029 }
2030
2031 None
2032 }
2033
2034 pub fn resolve_all_repetitions(
2039 tree: &AssembledTree,
2040 group_path: &str,
2041 discriminator: &str,
2042 ) -> Vec<usize> {
2043 let Some((spec, expected)) = discriminator.split_once('=') else {
2044 return Vec::new();
2045 };
2046 let parts: Vec<&str> = spec.split('.').collect();
2047 if parts.len() != 3 {
2048 return Vec::new();
2049 }
2050 let tag = parts[0];
2051 let element_idx: usize = match parts[1].parse() {
2052 Ok(v) => v,
2053 Err(_) => return Vec::new(),
2054 };
2055 let component_idx: usize = match parts[2].parse() {
2056 Ok(v) => v,
2057 Err(_) => return Vec::new(),
2058 };
2059
2060 let path_parts: Vec<&str> = group_path.split('.').collect();
2062
2063 let leaf_group = if path_parts.len() == 1 {
2064 let (group_id, _) = parse_group_spec(path_parts[0]);
2065 match tree.groups.iter().find(|g| g.group_id == group_id) {
2066 Some(g) => g,
2067 None => return Vec::new(),
2068 }
2069 } else {
2070 let parent_parts = &path_parts[..path_parts.len() - 1];
2071 let mut current_instance = {
2072 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
2073 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2074 Some(g) => g,
2075 None => return Vec::new(),
2076 };
2077 match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2078 Some(i) => i,
2079 None => return Vec::new(),
2080 }
2081 };
2082 for part in &parent_parts[1..] {
2083 let (group_id, explicit_rep) = parse_group_spec(part);
2084 let child_group = match current_instance
2085 .child_groups
2086 .iter()
2087 .find(|g| g.group_id == group_id)
2088 {
2089 Some(g) => g,
2090 None => return Vec::new(),
2091 };
2092 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2093 Some(i) => i,
2094 None => return Vec::new(),
2095 };
2096 }
2097 let (leaf_id, _) = match path_parts.last() {
2098 Some(p) => parse_group_spec(p),
2099 None => return Vec::new(),
2100 };
2101 match current_instance
2102 .child_groups
2103 .iter()
2104 .find(|g| g.group_id == leaf_id)
2105 {
2106 Some(g) => g,
2107 None => return Vec::new(),
2108 }
2109 };
2110
2111 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
2113
2114 let expected_values: Vec<&str> = expected_raw.split('|').collect();
2116 let mut result = Vec::new();
2117 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
2118 let matches = instance.segments.iter().any(|s| {
2119 s.tag.eq_ignore_ascii_case(tag)
2120 && s.elements
2121 .get(element_idx)
2122 .and_then(|e| e.get(component_idx))
2123 .map(|v| expected_values.iter().any(|ev| v == ev))
2124 .unwrap_or(false)
2125 });
2126 if matches {
2127 result.push(rep_idx);
2128 }
2129 }
2130
2131 if let Some(occ) = occurrence {
2133 result.into_iter().nth(occ).into_iter().collect()
2134 } else {
2135 result
2136 }
2137 }
2138
2139 pub fn map_all_forward(&self, tree: &AssembledTree) -> serde_json::Value {
2161 self.map_all_forward_inner(tree, true).0
2162 }
2163
2164 pub fn map_all_forward_enriched(
2168 &self,
2169 tree: &AssembledTree,
2170 enrich_codes: bool,
2171 ) -> serde_json::Value {
2172 self.map_all_forward_inner(tree, enrich_codes).0
2173 }
2174
2175 fn map_all_forward_inner(
2181 &self,
2182 tree: &AssembledTree,
2183 enrich_codes: bool,
2184 ) -> (
2185 serde_json::Value,
2186 std::collections::HashMap<String, Vec<usize>>,
2187 ) {
2188 self.map_all_forward_inner_with_tx(tree, enrich_codes, self.transaction_group.as_deref())
2189 }
2190
2191 fn map_all_forward_inner_with_tx(
2195 &self,
2196 tree: &AssembledTree,
2197 enrich_codes: bool,
2198 tx_group_override: Option<&str>,
2199 ) -> (
2200 serde_json::Value,
2201 std::collections::HashMap<String, Vec<usize>>,
2202 ) {
2203 let mut result = serde_json::Map::new();
2204 let mut nesting_info: std::collections::HashMap<String, Vec<usize>> =
2205 std::collections::HashMap::new();
2206 let mut contributors: std::collections::HashMap<String, Vec<String>> =
2208 std::collections::HashMap::new();
2209
2210 for def in &self.definitions {
2211 if def.meta.parent_field.is_some() {
2214 continue;
2215 }
2216 let entity = &def.meta.entity;
2217
2218 let bo4e = if let Some(ref disc) = def.meta.discriminator {
2219 let use_source_path = def
2224 .meta
2225 .source_path
2226 .as_ref()
2227 .is_some_and(|sp| has_source_path_qualifiers(sp));
2228 if use_source_path {
2229 let sp = def.meta.source_path.as_deref().unwrap();
2231 let all_instances = Self::resolve_all_by_source_path(tree, sp);
2232 let instances: Vec<_> = if let Some(matcher) = DiscriminatorMatcher::parse(disc)
2234 {
2235 matcher.filter_instances(all_instances)
2236 } else {
2237 all_instances
2238 };
2239 let extract = |instance: &AssembledGroupInstance| {
2240 let mut r = serde_json::Map::new();
2241 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
2242 serde_json::Value::Object(r)
2243 };
2244 match instances.len() {
2245 0 => None,
2246 1 => Some(extract(instances[0])),
2247 _ => Some(serde_json::Value::Array(
2248 instances.iter().map(|i| extract(i)).collect(),
2249 )),
2250 }
2251 } else {
2252 let reps = Self::resolve_all_repetitions(tree, &def.meta.source_group, disc);
2253 match reps.len() {
2254 0 => None,
2255 1 => Some(self.map_forward_inner(tree, def, reps[0], enrich_codes)),
2256 _ => Some(serde_json::Value::Array(
2257 reps.iter()
2258 .map(|&rep| self.map_forward_inner(tree, def, rep, enrich_codes))
2259 .collect(),
2260 )),
2261 }
2262 }
2263 } else if def.meta.source_group.is_empty() {
2264 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2266 } else if def.meta.source_path.as_ref().is_some_and(|sp| {
2267 has_source_path_qualifiers(sp) || def.meta.source_group.contains('.')
2268 }) {
2269 let sp = def.meta.source_path.as_deref().unwrap();
2274 let mut indexed = Self::resolve_all_with_parent_indices(tree, sp);
2275
2276 if let Some(last_part) = sp.rsplit('.').next() {
2281 if !last_part.contains('_') {
2282 let base_prefix = if let Some(parent) = sp.rsplit_once('.') {
2286 format!("{}.", parent.0)
2287 } else {
2288 String::new()
2289 };
2290 let sibling_qualifiers: Vec<String> = self
2291 .definitions
2292 .iter()
2293 .filter_map(|d| d.meta.source_path.as_deref())
2294 .filter(|other_sp| {
2295 *other_sp != sp
2296 && other_sp.starts_with(&base_prefix)
2297 && other_sp.split('.').count() == sp.split('.').count()
2298 })
2299 .filter_map(|other_sp| {
2300 let other_last = other_sp.rsplit('.').next()?;
2301 let (base, q) = other_last.split_once('_')?;
2304 if base == last_part {
2305 Some(q.to_string())
2306 } else {
2307 None
2308 }
2309 })
2310 .collect();
2311
2312 if !sibling_qualifiers.is_empty() {
2313 indexed.retain(|(_, inst)| {
2314 let entry_qual = inst
2315 .segments
2316 .first()
2317 .and_then(|seg| seg.elements.first())
2318 .and_then(|el| el.first())
2319 .map(|v| v.to_lowercase());
2320 !entry_qual.is_some_and(|q| {
2323 sibling_qualifiers.iter().any(|sq| {
2324 sq.split('_').any(|part| part.eq_ignore_ascii_case(&q))
2325 })
2326 })
2327 });
2328 }
2329 }
2330 }
2331 let extract = |instance: &AssembledGroupInstance| {
2332 let mut r = serde_json::Map::new();
2333 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
2334 serde_json::Value::Object(r)
2335 };
2336 if def.meta.source_group.contains('.') && !indexed.is_empty() {
2341 if let Some(sp) = &def.meta.source_path {
2342 let parent_indices: Vec<usize> =
2343 indexed.iter().map(|(idx, _)| *idx).collect();
2344 nesting_info.entry(sp.clone()).or_insert(parent_indices);
2345
2346 let child_key = format!("{sp}#child");
2349 if let std::collections::hash_map::Entry::Vacant(e) =
2350 nesting_info.entry(child_key)
2351 {
2352 let child_indices: Vec<usize> =
2353 Self::compute_child_indices(tree, sp, &indexed);
2354 if !child_indices.is_empty() {
2355 e.insert(child_indices);
2356 }
2357 }
2358 }
2359 }
2360 match indexed.len() {
2361 0 => None,
2362 1 => Some(extract(indexed[0].1)),
2363 _ => Some(serde_json::Value::Array(
2364 indexed.iter().map(|(_, i)| extract(i)).collect(),
2365 )),
2366 }
2367 } else {
2368 let num_reps = Self::count_repetitions(tree, &def.meta.source_group);
2369 if num_reps <= 1 {
2370 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2371 } else {
2372 let mut items = Vec::with_capacity(num_reps);
2374 for rep in 0..num_reps {
2375 items.push(self.map_forward_inner(tree, def, rep, enrich_codes));
2376 }
2377 Some(serde_json::Value::Array(items))
2378 }
2379 };
2380
2381 if let Some(bo4e) = bo4e {
2382 let key = to_camel_case(entity);
2383 match def.meta.target_list.as_deref() {
2384 Some(list_field) => append_to_list_field(&mut result, &key, list_field, bo4e),
2385 None => {
2386 let group = def
2390 .meta
2391 .source_path
2392 .clone()
2393 .unwrap_or_else(|| def.meta.source_group.to_lowercase());
2394 let seen = contributors.entry(key.clone()).or_default();
2395 let nested = seen.iter().any(|other: &String| {
2396 group.starts_with(&format!("{other}."))
2397 || other.starts_with(&format!("{group}."))
2398 });
2399 seen.push(group);
2400 merge_entity(&mut result, &key, bo4e, !nested);
2401 }
2402 }
2403 }
2404 }
2405
2406 nest_child_entities_in_result(
2411 &mut result,
2412 &self.definitions,
2413 &nesting_info,
2414 tx_group_override,
2415 );
2416
2417 (serde_json::Value::Object(result), nesting_info)
2418 }
2419
2420 pub fn map_all_reverse(
2429 &self,
2430 entities: &serde_json::Value,
2431 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2432 ) -> AssembledTree {
2433 self.map_all_reverse_with_mig(entities, nesting_info, None)
2434 }
2435
2436 pub fn map_all_reverse_with_mig(
2440 &self,
2441 entities: &serde_json::Value,
2442 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2443 mig: Option<&MigSchema>,
2444 ) -> AssembledTree {
2445 let mut root_segments: Vec<AssembledSegment> = Vec::new();
2446 let mut groups: Vec<AssembledGroup> = Vec::new();
2447 let mut inferred_nesting: std::collections::HashMap<String, Vec<usize>> =
2450 std::collections::HashMap::new();
2451
2452 for def in &self.definitions {
2453 if def.meta.parent_field.is_some() {
2456 continue;
2457 }
2458 let entity_key = to_camel_case(&def.meta.entity);
2459
2460 let _extracted: Option<serde_json::Value>;
2463 let entity_value = if let Some(list_field) = def.meta.target_list.as_deref() {
2464 match entities.get(&entity_key).and_then(|e| e.get(list_field)) {
2470 Some(v) if v.is_array() => {
2471 _extracted = None;
2472 v
2473 }
2474 _ => continue,
2475 }
2476 } else if let Some(v) = entities.get(&entity_key) {
2477 _extracted = None;
2478 v
2479 } else if def.meta.source_group.contains('.') {
2480 match extract_child_from_parent_with_indices(entities, &self.definitions, def) {
2482 Some((v, parent_indices)) => {
2483 if let Some(sp) = def.meta.source_path.as_deref() {
2485 inferred_nesting
2486 .entry(sp.to_string())
2487 .or_insert(parent_indices);
2488 }
2489 _extracted = Some(v);
2490 _extracted.as_ref().unwrap()
2491 }
2492 None => continue,
2493 }
2494 } else {
2495 continue;
2496 };
2497
2498 let unwrapped: Option<serde_json::Value>;
2506 let entity_value = if entity_value.is_object() && !entity_value.is_array() {
2507 if let Some(disc_value) = def
2508 .meta
2509 .discriminator
2510 .as_deref()
2511 .and_then(|d| d.split_once('='))
2512 .map(|(_, v)| v)
2513 {
2514 if let Some(inner) = entity_value.get(disc_value) {
2516 let mut injected = inner.clone();
2517 if let Some(qualifier_field) =
2520 find_qualifier_companion_field(&self.definitions, &def.meta.entity)
2521 {
2522 if let Some(obj) = injected.as_object_mut() {
2523 let entry = obj
2524 .entry(qualifier_field)
2525 .or_insert(serde_json::Value::Null);
2526 if entry.is_null() {
2527 *entry = serde_json::Value::String(disc_value.to_string());
2528 }
2529 }
2530 }
2531 unwrapped = Some(injected);
2532 unwrapped.as_ref().unwrap()
2533 } else {
2534 entity_value
2535 }
2536 } else if is_map_keyed_object(entity_value) {
2537 let map = entity_value.as_object().unwrap();
2542 let arr: Vec<serde_json::Value> = map
2543 .iter()
2544 .map(|(key, val)| {
2545 let mut item = val.clone();
2546 if let Some(obj) = item.as_object_mut() {
2549 if let Some(qualifier_field) = find_qualifier_companion_field(
2550 &self.definitions,
2551 &def.meta.entity,
2552 ) {
2553 let entry = obj
2554 .entry(qualifier_field)
2555 .or_insert(serde_json::Value::Null);
2556 if entry.is_null() {
2557 *entry = serde_json::Value::String(key.clone());
2558 }
2559 }
2560 }
2561 item
2562 })
2563 .collect();
2564 unwrapped = Some(serde_json::Value::Array(arr));
2565 unwrapped.as_ref().unwrap()
2566 } else {
2567 entity_value
2568 }
2569 } else {
2570 entity_value
2571 };
2572
2573 let leaf_group = def
2575 .meta
2576 .source_group
2577 .rsplit('.')
2578 .next()
2579 .unwrap_or(&def.meta.source_group);
2580
2581 if def.meta.source_group.is_empty() {
2582 let instance = self.map_reverse(entity_value, def);
2584 root_segments.extend(instance.segments);
2585 } else if entity_value.is_array() {
2586 let arr = entity_value.as_array().unwrap();
2588 let reps: Vec<_> = arr.iter().map(|item| self.map_reverse(item, def)).collect();
2589
2590 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2592 existing.repetitions.extend(reps);
2593 } else {
2594 groups.push(AssembledGroup {
2595 group_id: leaf_group.to_string(),
2596 repetitions: reps,
2597 });
2598 }
2599 } else {
2600 let instance = self.map_reverse(entity_value, def);
2602
2603 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2604 existing.repetitions.push(instance);
2605 } else {
2606 groups.push(AssembledGroup {
2607 group_id: leaf_group.to_string(),
2608 repetitions: vec![instance],
2609 });
2610 }
2611 }
2612 }
2613
2614 let nested_specs: Vec<(String, String)> = self
2620 .definitions
2621 .iter()
2622 .filter(|def| def.meta.parent_field.is_none())
2623 .filter_map(|def| {
2624 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
2625 if parts.len() > 1 {
2626 Some((parts[0].to_string(), parts[parts.len() - 1].to_string()))
2627 } else {
2628 None
2629 }
2630 })
2631 .collect();
2632 for (parent_id, child_id) in &nested_specs {
2633 let has_parent = groups.iter().any(|g| g.group_id == *parent_id);
2635 let has_child = groups.iter().any(|g| g.group_id == *child_id);
2636 if has_parent && has_child {
2637 let child_idx = groups.iter().position(|g| g.group_id == *child_id).unwrap();
2638 let child_group = groups.remove(child_idx);
2639 let parent = groups
2640 .iter_mut()
2641 .find(|g| g.group_id == *parent_id)
2642 .unwrap();
2643 let child_source_path = self
2647 .definitions
2648 .iter()
2649 .find(|d| {
2650 let parts: Vec<&str> = d.meta.source_group.split('.').collect();
2651 d.meta.parent_field.is_none()
2652 && parts.len() > 1
2653 && parts[parts.len() - 1] == *child_id
2654 })
2655 .and_then(|d| d.meta.source_path.as_deref());
2656 let distribution = child_source_path.and_then(|key| {
2657 nesting_info
2658 .and_then(|ni| ni.get(key))
2659 .or_else(|| inferred_nesting.get(key))
2660 });
2661 let unlinked_target = mig
2667 .and_then(|m| {
2668 mig_assembly::repetition_order::preferred_parent_repetition(
2669 parent,
2670 &m.segment_groups,
2671 child_id,
2672 )
2673 })
2674 .unwrap_or(0);
2675 for (i, child_rep) in child_group.repetitions.into_iter().enumerate() {
2676 let target_idx = distribution
2677 .and_then(|dist| dist.get(i))
2678 .copied()
2679 .unwrap_or(unlinked_target);
2680
2681 if let Some(target_rep) = parent.repetitions.get_mut(target_idx) {
2682 if let Some(existing) = target_rep
2683 .child_groups
2684 .iter_mut()
2685 .find(|g| g.group_id == *child_id)
2686 {
2687 existing.repetitions.push(child_rep);
2688 } else {
2689 target_rep.child_groups.push(AssembledGroup {
2690 group_id: child_id.clone(),
2691 repetitions: vec![child_rep],
2692 });
2693 }
2694 }
2695 }
2696 }
2697 }
2698
2699 let post_group_start = root_segments.len();
2700 AssembledTree {
2701 segments: root_segments,
2702 groups,
2703 post_group_start,
2704 inter_group_segments: std::collections::BTreeMap::new(),
2705 }
2706 }
2707
2708 fn count_repetitions(tree: &AssembledTree, group_path: &str) -> usize {
2710 let parts: Vec<&str> = group_path.split('.').collect();
2711
2712 let (first_id, first_rep) = parse_group_spec(parts[0]);
2713 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2714 Some(g) => g,
2715 None => return 0,
2716 };
2717
2718 if parts.len() == 1 {
2719 return first_group.repetitions.len();
2720 }
2721
2722 let mut current_instance = match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2724 Some(i) => i,
2725 None => return 0,
2726 };
2727
2728 for (i, part) in parts[1..].iter().enumerate() {
2729 let (group_id, explicit_rep) = parse_group_spec(part);
2730 let child_group = match current_instance
2731 .child_groups
2732 .iter()
2733 .find(|g| g.group_id == group_id)
2734 {
2735 Some(g) => g,
2736 None => return 0,
2737 };
2738
2739 if i == parts.len() - 2 {
2740 return child_group.repetitions.len();
2742 }
2743 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2744 Some(i) => i,
2745 None => return 0,
2746 };
2747 }
2748
2749 0
2750 }
2751
2752 pub fn translate_edifact_to_bo4e(
2760 msg_engine: &MappingEngine,
2761 tx_engine: &MappingEngine,
2762 tree: &AssembledTree,
2763 transaction_group: &str,
2764 ) -> crate::model::MappedMessage {
2765 Self::map_interchange_inner(msg_engine, tx_engine, tree, transaction_group, false)
2766 }
2767
2768 pub fn enrich_bo4e_types(
2782 msg_engine: &MappingEngine,
2783 tx_engine: &MappingEngine,
2784 mapped: &mut crate::model::MappedMessage,
2785 ) {
2786 msg_engine.enrich_entities(&mut mapped.stammdaten);
2787 for tx in &mut mapped.transaktionen {
2788 tx_engine.enrich_entities(&mut tx.stammdaten);
2789 }
2790
2791 msg_engine.enrich_named_entity(
2795 &mut mapped.nachricht_meta,
2796 crate::model::MSG_METADATA_ENTITY,
2797 );
2798 for tx in &mut mapped.transaktionen {
2799 tx_engine
2800 .enrich_named_entity(&mut tx.transaktionsdaten, crate::model::TX_METADATA_ENTITY);
2801 }
2802 }
2803
2804 fn enrich_named_entity(&self, value: &mut serde_json::Value, entity_key: &str) {
2809 if self.code_lookup.is_none() || value.is_null() {
2810 return;
2811 }
2812 let sites = self.code_sites();
2813 if let Some(entity_sites) = sites.get(entity_key) {
2814 Self::apply_sites(self, value, entity_sites);
2815 }
2816 }
2817
2818 fn enrich_entities(&self, value: &mut serde_json::Value) {
2824 if self.code_lookup.is_none() {
2825 return;
2826 }
2827 let sites: HashMap<String, Vec<CodeSite<'_>>> = self.code_sites();
2828 if sites.is_empty() {
2829 return;
2830 }
2831 Self::walk_and_enrich(self, value, &sites);
2832 }
2833
2834 fn code_sites(&self) -> HashMap<String, Vec<CodeSite<'_>>> {
2837 let Some(ref code_lookup) = self.code_lookup else {
2838 return HashMap::new();
2839 };
2840 let mut sites: HashMap<String, Vec<CodeSite<'_>>> = HashMap::new();
2841
2842 for def in &self.definitions {
2843 let Some(ref source_path) = def.meta.source_path else {
2844 continue;
2845 };
2846 let entity_key = to_camel_case(&def.meta.entity);
2847
2848 for (path, field_mapping) in &def.fields {
2849 let (target, enum_map, also_target, also_enum_map) = match field_mapping {
2850 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
2851 FieldMapping::Structured(s) => (
2852 s.target.as_str(),
2853 self.table(s.enum_map.as_ref(), s.code_list.as_deref()),
2854 s.also_target.as_deref(),
2855 self.table(s.also_enum_map.as_ref(), s.also_code_list.as_deref()),
2856 ),
2857 FieldMapping::Nested(_) => continue,
2858 };
2859 if target.is_empty() {
2860 continue;
2861 }
2862
2863 let parts: Vec<&str> = path.split('.').collect();
2864 let (seg_tag, path_qualifier, _occ) = parse_tag_qualifier(parts[0]);
2865 let (element_idx, component_idx) = Self::parse_element_component(&parts[1..]);
2866 let disc_qualifier = Self::discriminator_qualifier_for_tag(def, &seg_tag);
2873 if code_lookup
2874 .enrichment_codes(
2875 source_path,
2876 &seg_tag,
2877 path_qualifier,
2878 disc_qualifier.as_deref(),
2879 element_idx,
2880 component_idx,
2881 )
2882 .is_none()
2883 {
2884 continue;
2885 }
2886
2887 sites.entry(entity_key.clone()).or_default().push(CodeSite {
2888 target,
2889 parent_field: def.meta.parent_field.as_deref(),
2890 source_path,
2891 seg_tag,
2892 path_qualifier: path_qualifier.map(str::to_string),
2893 disc_qualifier,
2894 element_idx,
2895 component_idx,
2896 enum_map,
2897 also_target,
2898 also_enum_map,
2899 });
2900 }
2901 }
2902 sites
2903 }
2904
2905 fn walk_and_enrich(
2908 engine: &MappingEngine,
2909 value: &mut serde_json::Value,
2910 sites: &HashMap<String, Vec<CodeSite<'_>>>,
2911 ) {
2912 match value {
2913 serde_json::Value::Object(map) => {
2914 for (key, child) in map.iter_mut() {
2915 if let Some(entity_sites) = sites.get(key.as_str()) {
2916 Self::apply_sites(engine, child, entity_sites);
2917 }
2918 Self::walk_and_enrich(engine, child, sites);
2919 }
2920 }
2921 serde_json::Value::Array(items) => {
2922 for item in items.iter_mut() {
2923 Self::walk_and_enrich(engine, item, sites);
2924 }
2925 }
2926 _ => {}
2927 }
2928 }
2929
2930 fn apply_sites(engine: &MappingEngine, value: &mut serde_json::Value, sites: &[CodeSite<'_>]) {
2933 match value {
2934 serde_json::Value::Array(items) => {
2935 for item in items.iter_mut() {
2936 Self::apply_sites(engine, item, sites);
2937 }
2938 }
2939 serde_json::Value::Object(_) => {
2940 for site in sites {
2941 match site.parent_field {
2942 None => engine.enrich_one(value, site),
2943 Some(field) => {
2944 if let Some(nested) = value.get_mut(field) {
2945 Self::apply_nested_site(engine, nested, site);
2946 }
2947 }
2948 }
2949 }
2950 }
2951 _ => {}
2952 }
2953 }
2954
2955 fn apply_nested_site(
2957 engine: &MappingEngine,
2958 value: &mut serde_json::Value,
2959 site: &CodeSite<'_>,
2960 ) {
2961 match value {
2962 serde_json::Value::Array(items) => {
2963 for item in items.iter_mut() {
2964 Self::apply_nested_site(engine, item, site);
2965 }
2966 }
2967 serde_json::Value::Object(_) => engine.enrich_one(value, site),
2968 _ => {}
2969 }
2970 }
2971
2972 fn enrich_one(&self, entity: &mut serde_json::Value, site: &CodeSite<'_>) {
2974 if let Some((list, sub)) = list_target(site.target) {
2976 if let Some(items) = entity.get_mut(list).and_then(|v| v.as_array_mut()) {
2977 let element_site = CodeSite {
2978 target: sub,
2979 ..site.clone()
2980 };
2981 for item in items {
2982 self.enrich_one(item, &element_site);
2983 }
2984 }
2985 return;
2986 }
2987 let Some(ref code_lookup) = self.code_lookup else {
2988 return;
2989 };
2990 let Some(mapped_val) = Self::read_plain_string(entity, site.target) else {
2992 return;
2993 };
2994
2995 let raw = match site.enum_map {
2998 None => mapped_val.clone(),
2999 Some(map) => {
3000 let joint = match (site.also_target, site.also_enum_map) {
3001 (Some(also), Some(also_map)) => {
3002 Self::read_plain_string(entity, also).and_then(|also_v| {
3003 map.iter()
3004 .find(|(code, bo4e_v)| {
3005 *bo4e_v == &mapped_val && also_map.get(*code) == Some(&also_v)
3006 })
3007 .map(|(code, _)| code.clone())
3008 })
3009 }
3010 _ => None,
3011 };
3012 joint
3013 .or_else(|| {
3014 map.iter()
3015 .find(|(_, bo4e_v)| *bo4e_v == &mapped_val)
3016 .map(|(code, _)| code.clone())
3017 })
3018 .unwrap_or_else(|| mapped_val.clone())
3019 }
3020 };
3021
3022 let Some(codes) = code_lookup.enrichment_codes(
3023 site.source_path,
3024 &site.seg_tag,
3025 site.path_qualifier.as_deref(),
3026 site.disc_qualifier.as_deref(),
3027 site.element_idx,
3028 site.component_idx,
3029 ) else {
3030 return;
3031 };
3032
3033 if let Some(ref pid) = self.current_pid {
3035 if codes.len() == 1 && codes.contains_key(pid.as_str()) {
3036 return;
3037 }
3038 }
3039
3040 let enrichment = codes.get(&raw);
3041 let meaning = enrichment
3042 .map(|e| serde_json::Value::String(e.meaning.clone()))
3043 .unwrap_or(serde_json::Value::Null);
3044
3045 let mut obj = serde_json::Map::new();
3046 obj.insert("code".into(), serde_json::json!(mapped_val));
3047 obj.insert("meaning".into(), meaning);
3048 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
3049 obj.insert("enum".into(), serde_json::json!(enum_key));
3050 }
3051
3052 if let serde_json::Value::Object(map) = entity {
3053 set_nested_value_json(map, site.target, serde_json::Value::Object(obj));
3054 }
3055 }
3056
3057 fn read_plain_string(entity: &serde_json::Value, target: &str) -> Option<String> {
3060 let mut current = entity;
3061 for part in target.split('.') {
3062 current = current.get(part)?;
3063 }
3064 current.as_str().map(str::to_string)
3065 }
3066
3067 pub fn map_interchange(
3076 msg_engine: &MappingEngine,
3077 tx_engine: &MappingEngine,
3078 tree: &AssembledTree,
3079 transaction_group: &str,
3080 enrich_codes: bool,
3081 ) -> crate::model::MappedMessage {
3082 let mut mapped =
3083 Self::translate_edifact_to_bo4e(msg_engine, tx_engine, tree, transaction_group);
3084 if enrich_codes {
3085 Self::enrich_bo4e_types(msg_engine, tx_engine, &mut mapped);
3086 }
3087 mapped
3088 }
3089
3090 #[doc(hidden)]
3099 pub fn map_interchange_inner_for_test(
3100 msg_engine: &MappingEngine,
3101 tx_engine: &MappingEngine,
3102 tree: &AssembledTree,
3103 transaction_group: &str,
3104 enrich_codes: bool,
3105 ) -> crate::model::MappedMessage {
3106 Self::map_interchange_inner(msg_engine, tx_engine, tree, transaction_group, enrich_codes)
3107 }
3108
3109 pub(crate) fn map_interchange_inner(
3110 msg_engine: &MappingEngine,
3111 tx_engine: &MappingEngine,
3112 tree: &AssembledTree,
3113 transaction_group: &str,
3114 enrich_codes: bool,
3115 ) -> crate::model::MappedMessage {
3116 let (stammdaten, nesting_info) = msg_engine.map_all_forward_inner(tree, enrich_codes);
3118
3119 let transaktionen = tree
3121 .groups
3122 .iter()
3123 .find(|g| g.group_id == transaction_group)
3124 .map(|sg| {
3125 sg.repetitions
3126 .iter()
3127 .map(|instance| {
3128 let wrapped_tree = AssembledTree {
3131 segments: vec![],
3132 groups: vec![AssembledGroup {
3133 group_id: transaction_group.to_string(),
3134 repetitions: vec![instance.clone()],
3135 }],
3136 post_group_start: 0,
3137 inter_group_segments: std::collections::BTreeMap::new(),
3138 };
3139
3140 let (tx_result, tx_nesting) = tx_engine.map_all_forward_inner_with_tx(
3144 &wrapped_tree,
3145 enrich_codes,
3146 Some(transaction_group),
3147 );
3148
3149 let mut tx_result = tx_result;
3154 let transaktionsdaten = crate::model::take_entity(
3155 &mut tx_result,
3156 crate::model::TX_METADATA_ENTITY,
3157 );
3158
3159 crate::model::MappedTransaktion {
3160 stammdaten: tx_result,
3161 transaktionsdaten,
3162 nesting_info: tx_nesting,
3163 }
3164 })
3165 .collect()
3166 })
3167 .unwrap_or_default();
3168
3169 let mut stammdaten = stammdaten;
3171 let nachricht_meta =
3172 crate::model::take_entity(&mut stammdaten, crate::model::MSG_METADATA_ENTITY);
3173
3174 crate::model::MappedMessage {
3175 stammdaten,
3176 nachricht_meta,
3177 transaktionen,
3178 nesting_info,
3179 inter_group_segments: tree.inter_group_segments.clone(),
3180 }
3181 }
3182
3183 pub fn map_interchange_reverse(
3193 msg_engine: &MappingEngine,
3194 tx_engine: &MappingEngine,
3195 mapped: &crate::model::MappedMessage,
3196 transaction_group: &str,
3197 filtered_mig: Option<&MigSchema>,
3198 ) -> AssembledTree {
3199 let _owned_msg: Option<serde_json::Value>;
3207 let msg_stammdaten = if !mapped.nachricht_meta.is_null() {
3208 let mut merged = mapped.stammdaten.clone();
3209 crate::model::restore_entity(
3210 &mut merged,
3211 crate::model::MSG_METADATA_ENTITY,
3212 &mapped.nachricht_meta,
3213 );
3214 _owned_msg = Some(merged);
3215 _owned_msg.as_ref().unwrap()
3216 } else {
3217 _owned_msg = None;
3218 &mapped.stammdaten
3219 };
3220
3221 let msg_tree = msg_engine.map_all_reverse_with_mig(
3222 msg_stammdaten,
3223 if mapped.nesting_info.is_empty() {
3224 None
3225 } else {
3226 Some(&mapped.nesting_info)
3227 },
3228 filtered_mig,
3229 );
3230
3231 let mut sg4_reps: Vec<AssembledGroupInstance> = Vec::new();
3233
3234 struct DefWithMeta<'a> {
3238 def: &'a MappingDefinition,
3239 relative: String,
3240 depth: usize,
3241 }
3242
3243 let mut sorted_defs: Vec<DefWithMeta> = tx_engine
3244 .definitions
3245 .iter()
3246 .filter(|def| def.meta.parent_field.is_none())
3249 .map(|def| {
3250 let relative = strip_tx_group_prefix(&def.meta.source_group, transaction_group);
3251 let depth = if relative.is_empty() {
3252 0
3253 } else {
3254 relative.chars().filter(|c| *c == '.').count() + 1
3255 };
3256 DefWithMeta {
3257 def,
3258 relative,
3259 depth,
3260 }
3261 })
3262 .collect();
3263
3264 let mut parent_rep_map: std::collections::HashMap<String, usize> =
3268 std::collections::HashMap::new();
3269 for dm in &sorted_defs {
3270 if dm.depth >= 2 {
3271 let parts: Vec<&str> = dm.relative.split('.').collect();
3272 let (_, parent_rep) = parse_group_spec(parts[0]);
3273 if let Some(rep_idx) = parent_rep {
3274 if let Some(sp) = &dm.def.meta.source_path {
3275 if let Some((parent_path, _)) = sp.rsplit_once('.') {
3276 parent_rep_map
3277 .entry(parent_path.to_string())
3278 .or_insert(rep_idx);
3279 }
3280 }
3281 }
3282 }
3283 }
3284
3285 for dm in &mut sorted_defs {
3288 if dm.depth == 1 && !dm.relative.contains(':') {
3289 if let Some(sp) = &dm.def.meta.source_path {
3290 if let Some(rep_idx) = parent_rep_map.get(sp.as_str()) {
3291 dm.relative = format!("{}:{}", dm.relative, rep_idx);
3292 }
3293 }
3294 }
3295 }
3296
3297 if let Some(mig) = filtered_mig {
3304 let mig_order = build_reverse_mig_group_order(mig, transaction_group);
3305 sorted_defs.sort_by(|a, b| {
3306 a.depth.cmp(&b.depth).then_with(|| {
3307 let a_id = a.relative.split(':').next().unwrap_or(&a.relative);
3308 let b_id = b.relative.split(':').next().unwrap_or(&b.relative);
3309 let a_pos = variant_mig_position(a.def, a_id, &mig_order);
3311 let b_pos = variant_mig_position(b.def, b_id, &mig_order);
3312 a_pos.cmp(&b_pos).then(a.relative.cmp(&b.relative))
3313 })
3314 });
3315 } else {
3316 sorted_defs.sort_by(|a, b| a.depth.cmp(&b.depth).then(a.relative.cmp(&b.relative)));
3317 }
3318
3319 for tx in &mapped.transaktionen {
3320 let mut root_segs: Vec<AssembledSegment> = Vec::new();
3321 let mut child_groups: Vec<AssembledGroup> = Vec::new();
3322
3323 let _owned_tx: Option<serde_json::Value>;
3326 let tx_stammdaten: &serde_json::Value = if !tx.transaktionsdaten.is_null() {
3327 let mut merged = tx.stammdaten.clone();
3328 crate::model::restore_entity(
3329 &mut merged,
3330 crate::model::TX_METADATA_ENTITY,
3331 &tx.transaktionsdaten,
3332 );
3333 _owned_tx = Some(merged);
3334 _owned_tx.as_ref().unwrap()
3335 } else {
3336 _owned_tx = None;
3337 &tx.stammdaten
3338 };
3339
3340 let mut source_path_to_rep: std::collections::HashMap<String, Vec<usize>> =
3345 std::collections::HashMap::new();
3346
3347 for dm in &sorted_defs {
3348 let entity_key = to_camel_case(&dm.def.meta.entity);
3351 let _tx_extracted: Option<serde_json::Value>;
3352 let bo4e_value = if let Some(v) = tx_stammdaten.get(&entity_key) {
3353 _tx_extracted = None;
3354 v
3355 } else if dm.def.meta.source_group.contains('.') {
3356 match extract_child_from_parent(tx_stammdaten, &tx_engine.definitions, dm.def) {
3357 Some(v) => {
3358 _tx_extracted = Some(v);
3359 _tx_extracted.as_ref().unwrap()
3360 }
3361 None => continue,
3362 }
3363 } else {
3364 continue;
3365 };
3366
3367 let unwrapped_value: Option<serde_json::Value>;
3369 let bo4e_value = if bo4e_value.is_object() && !bo4e_value.is_array() {
3370 if let Some(disc_value) = dm
3371 .def
3372 .meta
3373 .discriminator
3374 .as_deref()
3375 .and_then(|d| d.split_once('='))
3376 .map(|(_, v)| v)
3377 {
3378 if let Some(inner) = bo4e_value.get(disc_value) {
3379 let mut injected = inner.clone();
3380 if let Some(qualifier_field) = find_qualifier_companion_field(
3381 &tx_engine.definitions,
3382 &dm.def.meta.entity,
3383 ) {
3384 if let Some(obj) = injected.as_object_mut() {
3385 obj.entry(qualifier_field).or_insert_with(|| {
3386 serde_json::Value::String(disc_value.to_string())
3387 });
3388 }
3389 }
3390 unwrapped_value = Some(injected);
3391 unwrapped_value.as_ref().unwrap()
3392 } else {
3393 bo4e_value
3394 }
3395 } else if is_map_keyed_object(bo4e_value) {
3396 let map = bo4e_value.as_object().unwrap();
3397 let arr: Vec<serde_json::Value> = map
3398 .iter()
3399 .map(|(key, val)| {
3400 let mut item = val.clone();
3401 if let Some(obj) = item.as_object_mut() {
3402 if let Some(qualifier_field) = find_qualifier_companion_field(
3403 &tx_engine.definitions,
3404 &dm.def.meta.entity,
3405 ) {
3406 let entry = obj
3407 .entry(qualifier_field)
3408 .or_insert(serde_json::Value::Null);
3409 if entry.is_null() {
3410 *entry = serde_json::Value::String(key.clone());
3411 }
3412 }
3413 }
3414 item
3415 })
3416 .collect();
3417 unwrapped_value = Some(serde_json::Value::Array(arr));
3418 unwrapped_value.as_ref().unwrap()
3419 } else {
3420 bo4e_value
3421 }
3422 } else {
3423 bo4e_value
3424 };
3425
3426 let items: Vec<&serde_json::Value> = if bo4e_value.is_array() {
3430 bo4e_value.as_array().unwrap().iter().collect()
3431 } else {
3432 vec![bo4e_value]
3433 };
3434
3435 for (item_idx, item) in items.iter().enumerate() {
3436 let instance = tx_engine.map_reverse(item, dm.def);
3437
3438 if instance.segments.is_empty() && instance.child_groups.is_empty() {
3440 continue;
3441 }
3442
3443 if dm.relative.is_empty() {
3444 root_segs.extend(instance.segments);
3451 for child in instance.child_groups {
3452 match child_groups
3453 .iter_mut()
3454 .find(|g| g.group_id == child.group_id)
3455 {
3456 Some(existing) => existing.repetitions.extend(child.repetitions),
3457 None => child_groups.push(child),
3458 }
3459 }
3460 } else {
3461 let effective_relative = if dm.depth >= 2 {
3465 let rel = if items.len() > 1 {
3468 strip_all_rep_indices(&dm.relative)
3469 } else {
3470 dm.relative.clone()
3471 };
3472 let skip_nesting = dm
3479 .def
3480 .meta
3481 .source_path
3482 .as_ref()
3483 .and_then(|sp| sp.rsplit_once('.'))
3484 .and_then(|(parent_path, _)| source_path_to_rep.get(parent_path))
3485 .is_some_and(|reps| reps.len() == 1);
3486 let nesting_idx = if items.len() > 1 && !skip_nesting {
3487 dm.def
3488 .meta
3489 .source_path
3490 .as_ref()
3491 .and_then(|sp| tx.nesting_info.get(sp))
3492 .and_then(|dist| dist.get(item_idx))
3493 .copied()
3494 } else {
3495 None
3496 };
3497 if let Some(parent_rep) = nesting_idx {
3498 let parts: Vec<&str> = rel.split('.').collect();
3500 let parent_id = parts[0].split(':').next().unwrap_or(parts[0]);
3501 let rest = parts[1..].join(".");
3502 format!("{}:{}.{}", parent_id, parent_rep, rest)
3503 } else {
3504 resolve_child_relative(
3505 &rel,
3506 dm.def.meta.source_path.as_deref(),
3507 &source_path_to_rep,
3508 item_idx,
3509 )
3510 }
3511 } else if dm.depth == 1 {
3512 let child_key = dm
3515 .def
3516 .meta
3517 .source_path
3518 .as_ref()
3519 .map(|sp| format!("{sp}#child"));
3520 if let Some(child_indices) =
3521 child_key.as_ref().and_then(|ck| tx.nesting_info.get(ck))
3522 {
3523 if let Some(&target) = child_indices.get(item_idx) {
3524 if target != usize::MAX {
3525 let base =
3526 dm.relative.split(':').next().unwrap_or(&dm.relative);
3527 format!("{}:{}", base, target)
3528 } else {
3529 dm.relative.clone()
3530 }
3531 } else if items.len() > 1 && item_idx > 0 {
3532 strip_rep_index(&dm.relative)
3533 } else {
3534 dm.relative.clone()
3535 }
3536 } else if items.len() > 1 && item_idx > 0 {
3537 strip_rep_index(&dm.relative)
3538 } else {
3539 dm.relative.clone()
3540 }
3541 } else if items.len() > 1 && item_idx > 0 {
3542 strip_rep_index(&dm.relative)
3545 } else {
3546 dm.relative.clone()
3547 };
3548
3549 let rep_used =
3550 place_in_groups(&mut child_groups, &effective_relative, instance);
3551
3552 if dm.depth == 1 {
3554 if let Some(sp) = &dm.def.meta.source_path {
3555 source_path_to_rep
3556 .entry(sp.clone())
3557 .or_default()
3558 .push(rep_used);
3559 }
3560 }
3561 }
3562 }
3563 }
3564
3565 sg4_reps.push(AssembledGroupInstance {
3566 segments: root_segs,
3567 child_groups,
3568 entry_mig_number: None,
3569 variant_mig_numbers: vec![],
3570 skipped_segments: Vec::new(),
3571 skipped_positions: Vec::new(),
3572 });
3573 }
3574
3575 let mut root_segments = Vec::new();
3582 let mut uns_segments = Vec::new();
3583 let mut uns_is_summary = false;
3584 let mut found_uns = false;
3585 for seg in msg_tree.segments {
3586 if seg.tag == "UNS" {
3587 uns_is_summary = seg
3589 .elements
3590 .first()
3591 .and_then(|el| el.first())
3592 .map(|v| v == "S")
3593 .unwrap_or(false);
3594 uns_segments.push(seg);
3595 found_uns = true;
3596 } else if found_uns {
3597 uns_segments.push(seg);
3599 } else {
3600 root_segments.push(seg);
3601 }
3602 }
3603
3604 let pre_group_count = root_segments.len();
3605 let mut all_groups = msg_tree.groups;
3606 let mut inter_group = msg_tree.inter_group_segments;
3607
3608 let sg_num = |id: &str| -> usize {
3610 id.strip_prefix("SG")
3611 .and_then(|n| n.parse::<usize>().ok())
3612 .unwrap_or(0)
3613 };
3614
3615 if !sg4_reps.is_empty() {
3616 if uns_is_summary {
3617 all_groups.push(AssembledGroup {
3619 group_id: transaction_group.to_string(),
3620 repetitions: sg4_reps,
3621 });
3622 if !uns_segments.is_empty() {
3623 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3628 let tx_num = sg_num(transaction_group);
3629 let uns_pos = all_groups
3630 .iter()
3631 .rposition(|g| sg_num(&g.group_id) <= tx_num)
3632 .map(|i| i + 1)
3633 .unwrap_or(all_groups.len());
3634 inter_group.insert(uns_pos, uns_segments);
3635 }
3636 } else {
3637 if !uns_segments.is_empty() {
3639 inter_group.insert(all_groups.len(), uns_segments);
3640 }
3641 all_groups.push(AssembledGroup {
3642 group_id: transaction_group.to_string(),
3643 repetitions: sg4_reps,
3644 });
3645 }
3646 } else if !uns_segments.is_empty() {
3647 if transaction_group.is_empty() {
3648 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3653 if uns_is_summary {
3654 inter_group.insert(all_groups.len(), uns_segments);
3655 } else {
3656 inter_group.insert(0, uns_segments);
3657 }
3658 } else {
3659 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3663 let tx_num = sg_num(transaction_group);
3664 let uns_pos = all_groups
3665 .iter()
3666 .rposition(|g| sg_num(&g.group_id) <= tx_num)
3667 .map(|i| i + 1)
3668 .unwrap_or(all_groups.len());
3669 inter_group.insert(uns_pos, uns_segments);
3670 }
3671 }
3672
3673 for (k, segs) in &mapped.inter_group_segments {
3681 if segs.is_empty() {
3682 continue;
3683 }
3684 let existing_tags: std::collections::HashSet<String> = inter_group
3685 .get(k)
3686 .map(|v| v.iter().map(|s| s.tag.clone()).collect())
3687 .unwrap_or_default();
3688 for seg in segs {
3689 if existing_tags.contains(&seg.tag) {
3690 continue;
3691 }
3692 inter_group.entry(*k).or_default().push(seg.clone());
3693 }
3694 }
3695
3696 let mut tree = AssembledTree {
3697 segments: root_segments,
3698 groups: all_groups,
3699 post_group_start: pre_group_count,
3700 inter_group_segments: inter_group,
3701 };
3702
3703 if let Some(mig) = filtered_mig {
3710 mig_assembly::repetition_order::sort_repetitions_by_mig_variant(
3711 &mut tree,
3712 mig,
3713 (!transaction_group.is_empty()).then_some(transaction_group),
3714 );
3715 }
3716 tree
3717 }
3718
3719 pub fn build_group_from_bo4e(
3721 &self,
3722 bo4e_value: &serde_json::Value,
3723 def: &MappingDefinition,
3724 ) -> AssembledGroup {
3725 let instance = self.map_reverse(bo4e_value, def);
3726 let leaf_group = def
3727 .meta
3728 .source_group
3729 .rsplit('.')
3730 .next()
3731 .unwrap_or(&def.meta.source_group);
3732
3733 AssembledGroup {
3734 group_id: leaf_group.to_string(),
3735 repetitions: vec![instance],
3736 }
3737 }
3738
3739 pub fn map_interchange_typed<M, T>(
3747 msg_engine: &MappingEngine,
3748 tx_engine: &MappingEngine,
3749 tree: &AssembledTree,
3750 tx_group: &str,
3751 enrich_codes: bool,
3752 nachrichtendaten: crate::model::Nachrichtendaten,
3753 interchangedaten: crate::model::Interchangedaten,
3754 ) -> Result<crate::model::Interchange<M, T>, serde_json::Error>
3755 where
3756 M: serde::de::DeserializeOwned,
3757 T: serde::de::DeserializeOwned,
3758 {
3759 let mapped = Self::map_interchange(msg_engine, tx_engine, tree, tx_group, enrich_codes);
3760 let nachricht = mapped.into_dynamic_nachricht(nachrichtendaten);
3761 let dynamic = crate::model::DynamicInterchange {
3762 interchangedaten,
3763 nachrichten: vec![nachricht],
3764 };
3765 let value = serde_json::to_value(&dynamic)?;
3766 serde_json::from_value(value)
3767 }
3768
3769 pub fn map_interchange_reverse_typed<M, T>(
3776 msg_engine: &MappingEngine,
3777 tx_engine: &MappingEngine,
3778 nachricht: &crate::model::Nachricht<M, T>,
3779 tx_group: &str,
3780 ) -> Result<AssembledTree, serde_json::Error>
3781 where
3782 M: serde::Serialize,
3783 T: serde::Serialize,
3784 {
3785 let mut stammdaten = serde_json::to_value(&nachricht.stammdaten)?;
3788 crate::model::restore_message_metadata(&mut stammdaten, &nachricht.nachrichtendaten);
3789 let transaktionen: Vec<crate::model::MappedTransaktion> = nachricht
3790 .transaktionen
3791 .iter()
3792 .map(|t| {
3793 Ok(crate::model::MappedTransaktion {
3794 stammdaten: serde_json::to_value(t)?,
3795 transaktionsdaten: serde_json::Value::Null,
3796 nesting_info: Default::default(),
3797 })
3798 })
3799 .collect::<Result<Vec<_>, serde_json::Error>>()?;
3800 let mapped = crate::model::MappedMessage {
3801 stammdaten,
3802 nachricht_meta: serde_json::Value::Null,
3803 transaktionen,
3804 nesting_info: Default::default(),
3805 inter_group_segments: Default::default(),
3806 };
3807 Ok(Self::map_interchange_reverse(
3808 msg_engine, tx_engine, &mapped, tx_group, None,
3809 ))
3810 }
3811}
3812
3813fn parse_source_path_part(part: &str) -> (&str, Option<&str>) {
3820 if let Some(pos) = part.find('_') {
3824 let group = &part[..pos];
3825 let qualifier = &part[pos + 1..];
3826 if !qualifier.is_empty() {
3827 return (group, Some(qualifier));
3828 }
3829 }
3830 (part, None)
3831}
3832
3833fn build_reverse_mig_group_order(mig: &MigSchema, tx_group_id: &str) -> HashMap<String, usize> {
3841 let mut order = HashMap::new();
3842 if let Some(tg) = mig.segment_groups.iter().find(|g| g.id == tx_group_id) {
3843 for (i, nested) in tg.nested_groups.iter().enumerate() {
3844 if let Some(ref vc) = nested.variant_code {
3846 let variant_key = format!("{}_{}", nested.id, vc.to_uppercase());
3847 order.insert(variant_key, i);
3848 }
3849 order.entry(nested.id.clone()).or_insert(i);
3851 }
3852 }
3853 order
3854}
3855
3856fn variant_mig_position(
3862 def: &MappingDefinition,
3863 base_group_id: &str,
3864 mig_order: &HashMap<String, usize>,
3865) -> usize {
3866 if let Some(ref sp) = def.meta.source_path {
3869 let base_lower = base_group_id.to_lowercase();
3871 for part in sp.split('.') {
3872 if part.starts_with(&base_lower)
3873 || part.starts_with(base_group_id.to_lowercase().as_str())
3874 {
3875 if let Some(underscore_pos) = part.find('_') {
3877 let qualifier = &part[underscore_pos + 1..];
3878 let variant_key = format!("{}_{}", base_group_id, qualifier.to_uppercase());
3879 if let Some(&pos) = mig_order.get(&variant_key) {
3880 return pos;
3881 }
3882 }
3883 }
3884 }
3885 }
3886 mig_order.get(base_group_id).copied().unwrap_or(usize::MAX)
3888}
3889
3890fn find_rep_by_entry_qualifier<'a>(
3895 reps: &'a [AssembledGroupInstance],
3896 qualifier: &str,
3897) -> Option<&'a AssembledGroupInstance> {
3898 let parts: Vec<&str> = qualifier.split('_').collect();
3900 reps.iter().find(|inst| {
3901 inst.segments.first().is_some_and(|seg| {
3902 seg.elements
3903 .first()
3904 .and_then(|e| e.first())
3905 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3906 })
3907 })
3908}
3909
3910fn find_all_reps_by_entry_qualifier<'a>(
3912 reps: &'a [AssembledGroupInstance],
3913 qualifier: &str,
3914) -> Vec<&'a AssembledGroupInstance> {
3915 let parts: Vec<&str> = qualifier.split('_').collect();
3917 reps.iter()
3918 .filter(|inst| {
3919 inst.segments.first().is_some_and(|seg| {
3920 seg.elements
3921 .first()
3922 .and_then(|e| e.first())
3923 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3924 })
3925 })
3926 .collect()
3927}
3928
3929fn has_source_path_qualifiers(source_path: &str) -> bool {
3931 source_path.split('.').any(|part| {
3932 if let Some(pos) = part.find('_') {
3933 pos < part.len() - 1
3934 } else {
3935 false
3936 }
3937 })
3938}
3939
3940fn parse_group_spec(part: &str) -> (&str, Option<usize>) {
3941 if let Some(colon_pos) = part.find(':') {
3942 let id = &part[..colon_pos];
3943 let rep = part[colon_pos + 1..].parse::<usize>().ok();
3944 (id, rep)
3945 } else {
3946 (part, None)
3947 }
3948}
3949
3950fn strip_tx_group_prefix(source_group: &str, tx_group: &str) -> String {
3956 if source_group == tx_group || source_group.is_empty() {
3957 String::new()
3958 } else if let Some(rest) = source_group.strip_prefix(tx_group) {
3959 rest.strip_prefix('.').unwrap_or(rest).to_string()
3960 } else {
3961 source_group.to_string()
3962 }
3963}
3964
3965fn place_in_groups(
3973 groups: &mut Vec<AssembledGroup>,
3974 relative_path: &str,
3975 instance: AssembledGroupInstance,
3976) -> usize {
3977 let parts: Vec<&str> = relative_path.split('.').collect();
3978
3979 if parts.len() == 1 {
3980 let (id, rep) = parse_group_spec(parts[0]);
3982
3983 let group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == id) {
3985 g
3986 } else {
3987 groups.push(AssembledGroup {
3988 group_id: id.to_string(),
3989 repetitions: vec![],
3990 });
3991 groups.last_mut().unwrap()
3992 };
3993
3994 if let Some(rep_idx) = rep {
3995 while group.repetitions.len() <= rep_idx {
3997 group.repetitions.push(AssembledGroupInstance {
3998 segments: vec![],
3999 child_groups: vec![],
4000 entry_mig_number: None,
4001 variant_mig_numbers: vec![],
4002 skipped_segments: Vec::new(),
4003 skipped_positions: Vec::new(),
4004 });
4005 }
4006 group.repetitions[rep_idx]
4007 .segments
4008 .extend(instance.segments);
4009 group.repetitions[rep_idx]
4010 .child_groups
4011 .extend(instance.child_groups);
4012 rep_idx
4013 } else {
4014 let pos = group.repetitions.len();
4016 group.repetitions.push(instance);
4017 pos
4018 }
4019 } else {
4020 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
4022 let rep_idx = parent_rep.unwrap_or(0);
4023
4024 let parent_group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == parent_id) {
4026 g
4027 } else {
4028 groups.push(AssembledGroup {
4029 group_id: parent_id.to_string(),
4030 repetitions: vec![],
4031 });
4032 groups.last_mut().unwrap()
4033 };
4034
4035 while parent_group.repetitions.len() <= rep_idx {
4037 parent_group.repetitions.push(AssembledGroupInstance {
4038 segments: vec![],
4039 child_groups: vec![],
4040 entry_mig_number: None,
4041 variant_mig_numbers: vec![],
4042 skipped_segments: Vec::new(),
4043 skipped_positions: Vec::new(),
4044 });
4045 }
4046
4047 let remaining = parts[1..].join(".");
4048 place_in_groups(
4049 &mut parent_group.repetitions[rep_idx].child_groups,
4050 &remaining,
4051 instance,
4052 );
4053 rep_idx
4054 }
4055}
4056
4057fn resolve_child_relative(
4069 relative: &str,
4070 source_path: Option<&str>,
4071 source_path_to_rep: &std::collections::HashMap<String, Vec<usize>>,
4072 item_idx: usize,
4073) -> String {
4074 let parts: Vec<&str> = relative.split('.').collect();
4075 if parts.is_empty() {
4076 return relative.to_string();
4077 }
4078
4079 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
4081 if parent_rep.is_some() {
4082 return relative.to_string();
4083 }
4084
4085 if let Some(sp) = source_path {
4087 if let Some((parent_path, _child)) = sp.rsplit_once('.') {
4088 if let Some(rep_indices) = source_path_to_rep.get(parent_path) {
4090 let rep_idx = rep_indices
4091 .get(item_idx)
4092 .or_else(|| rep_indices.last())
4093 .copied()
4094 .unwrap_or(0);
4095 let rest = parts[1..].join(".");
4096 return format!("{}:{}.{}", parent_id, rep_idx, rest);
4097 }
4098 let prefix = format!("{}_", parent_path);
4106 let mut unioned: Vec<usize> = source_path_to_rep
4107 .iter()
4108 .filter(|(k, _)| k.starts_with(&prefix))
4109 .flat_map(|(_, v)| v.iter().copied())
4110 .collect();
4111 if !unioned.is_empty() {
4112 unioned.sort_unstable();
4113 unioned.dedup();
4114 let rep_idx = unioned
4115 .get(item_idx)
4116 .or_else(|| unioned.last())
4117 .copied()
4118 .unwrap_or(0);
4119 let rest = parts[1..].join(".");
4120 return format!("{}:{}.{}", parent_id, rep_idx, rest);
4121 }
4122 }
4123 }
4124
4125 relative.to_string()
4127}
4128
4129struct DiscriminatorMatcher<'a> {
4136 tag: &'a str,
4137 element_idx: usize,
4138 component_idx: usize,
4139 expected_values: Vec<&'a str>,
4140 occurrence: Option<usize>,
4142}
4143
4144impl<'a> DiscriminatorMatcher<'a> {
4145 fn parse(disc: &'a str) -> Option<Self> {
4146 let (spec, expected) = disc.split_once('=')?;
4147 let parts: Vec<&str> = spec.split('.').collect();
4148 if parts.len() != 3 {
4149 return None;
4150 }
4151 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
4152 Some(Self {
4153 tag: parts[0],
4154 element_idx: parts[1].parse().ok()?,
4155 component_idx: parts[2].parse().ok()?,
4156 expected_values: expected_raw.split('|').collect(),
4157 occurrence,
4158 })
4159 }
4160
4161 fn matches(&self, instance: &AssembledGroupInstance) -> bool {
4162 instance.segments.iter().any(|s| {
4163 s.tag.eq_ignore_ascii_case(self.tag)
4164 && s.elements
4165 .get(self.element_idx)
4166 .and_then(|e| e.get(self.component_idx))
4167 .map(|v| self.expected_values.iter().any(|ev| v == ev))
4168 .unwrap_or(false)
4169 })
4170 }
4171
4172 fn filter_instances<'b>(
4174 &self,
4175 instances: Vec<&'b AssembledGroupInstance>,
4176 ) -> Vec<&'b AssembledGroupInstance> {
4177 let matching: Vec<_> = instances
4178 .into_iter()
4179 .filter(|inst| self.matches(inst))
4180 .collect();
4181 if let Some(occ) = self.occurrence {
4182 matching.into_iter().nth(occ).into_iter().collect()
4183 } else {
4184 matching
4185 }
4186 }
4187}
4188
4189fn parse_discriminator_occurrence(expected: &str) -> (&str, Option<usize>) {
4195 if let Some(hash_pos) = expected.rfind('#') {
4196 if let Ok(occ) = expected[hash_pos + 1..].parse::<usize>() {
4197 return (&expected[..hash_pos], Some(occ));
4198 }
4199 }
4200 (expected, None)
4201}
4202
4203fn strip_rep_index(relative: &str) -> String {
4207 let (id, _) = parse_group_spec(relative);
4208 id.to_string()
4209}
4210
4211pub(crate) fn strip_all_rep_indices(relative: &str) -> String {
4216 relative
4217 .split('.')
4218 .map(|part| {
4219 let (id, _) = parse_group_spec(part);
4220 id
4221 })
4222 .collect::<Vec<_>>()
4223 .join(".")
4224}
4225
4226pub fn is_nested_child_of(child: &MappingDefinition, parent: &MappingDefinition) -> bool {
4236 if child.meta.parent_field.is_none() || child.meta.entity != parent.meta.entity {
4237 return false;
4238 }
4239 let child_sg = strip_all_rep_indices(&child.meta.source_group);
4240 let parent_sg = strip_all_rep_indices(&parent.meta.source_group);
4241 match child_sg.rsplit_once('.') {
4242 Some((head, _)) if head.eq_ignore_ascii_case(&parent_sg) => {}
4243 _ => return false,
4244 }
4245 let (Some(child_sp), Some(parent_sp)) = (
4246 child.meta.source_path.as_deref(),
4247 parent.meta.source_path.as_deref(),
4248 ) else {
4249 return true;
4250 };
4251 let Some((child_parent_sp, _)) = child_sp.rsplit_once('.') else {
4252 return false;
4253 };
4254 let child_parts: Vec<&str> = child_parent_sp.split('.').collect();
4255 let parent_parts: Vec<&str> = parent_sp.split('.').collect();
4256 child_parts.len() == parent_parts.len()
4257 && child_parts.iter().zip(&parent_parts).all(|(c, p)| {
4258 let (c_id, c_q) = parse_source_path_part(c);
4259 let (p_id, p_q) = parse_source_path_part(p);
4260 c_id.eq_ignore_ascii_case(p_id)
4261 && match (c_q, p_q) {
4262 (Some(cq), Some(pq)) => cq.eq_ignore_ascii_case(pq),
4263 _ => true,
4264 }
4265 })
4266}
4267
4268fn nested_parent_qualifier(child: &MappingDefinition) -> Option<&str> {
4271 let (parent_path, _) = child.meta.source_path.as_deref()?.rsplit_once('.')?;
4272 let last = parent_path.rsplit('.').next()?;
4273 parse_source_path_part(last).1
4274}
4275
4276fn nested_child_leaf(child: &MappingDefinition) -> (String, Option<&str>) {
4279 let leaf_group = strip_all_rep_indices(
4280 child
4281 .meta
4282 .source_group
4283 .rsplit('.')
4284 .next()
4285 .unwrap_or(&child.meta.source_group),
4286 );
4287 let leaf_qualifier = child
4288 .meta
4289 .source_path
4290 .as_deref()
4291 .and_then(|sp| sp.rsplit('.').next())
4292 .and_then(|part| parse_source_path_part(part).1);
4293 (leaf_group, leaf_qualifier)
4294}
4295
4296fn entry_qualifier_matches(instance: &AssembledGroupInstance, qualifier: &str) -> bool {
4299 segment_qualifier_matches(instance.segments.first(), qualifier)
4300}
4301
4302fn rebuilt_entry_qualifier_matches(
4307 instance: &AssembledGroupInstance,
4308 def: &MappingDefinition,
4309 qualifier: &str,
4310) -> bool {
4311 let entry_tag = def
4312 .meta
4313 .discriminator
4314 .as_deref()
4315 .and_then(|d| d.split('.').next())
4316 .filter(|tag| !tag.is_empty());
4317 let entry = match entry_tag {
4318 Some(tag) => instance
4319 .segments
4320 .iter()
4321 .find(|s| s.tag.eq_ignore_ascii_case(tag)),
4322 None => instance.segments.first(),
4323 };
4324 segment_qualifier_matches(entry, qualifier)
4325}
4326
4327fn segment_qualifier_matches(segment: Option<&AssembledSegment>, qualifier: &str) -> bool {
4328 segment
4329 .and_then(|seg| seg.elements.first())
4330 .and_then(|e| e.first())
4331 .is_some_and(|v| qualifier.split('_').any(|q| v.eq_ignore_ascii_case(q)))
4332}
4333
4334fn field_is_filled(bo4e_value: &serde_json::Value, field: &str) -> bool {
4340 let mut current = bo4e_value;
4341 for part in field.split('.') {
4342 match current.get(part) {
4343 Some(v) => current = v,
4344 None => return false,
4345 }
4346 }
4347 match current {
4348 serde_json::Value::String(s) => !s.is_empty(),
4349 serde_json::Value::Array(a) => !a.is_empty(),
4350 serde_json::Value::Object(o) => !o.is_empty(),
4351 serde_json::Value::Number(_) | serde_json::Value::Bool(_) => true,
4352 serde_json::Value::Null => false,
4353 }
4354}
4355
4356pub(crate) fn list_target(target: &str) -> Option<(&str, &str)> {
4359 let (list, sub) = target.split_once("[].")?;
4360 (!list.is_empty() && !sub.is_empty()).then_some((list, sub))
4361}
4362
4363pub(crate) fn parse_tag_qualifier(tag_part: &str) -> (String, Option<&str>, usize) {
4370 if let Some(bracket_start) = tag_part.find('[') {
4371 let tag = tag_part[..bracket_start].to_uppercase();
4372 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
4373 if let Some(comma_pos) = inner.find(',') {
4374 let qualifier = &inner[..comma_pos];
4375 let index = inner[comma_pos + 1..].parse::<usize>().unwrap_or(0);
4376 if qualifier == "*" {
4378 (tag, None, index)
4379 } else {
4380 (tag, Some(qualifier), index)
4381 }
4382 } else {
4383 (tag, Some(inner), 0)
4384 }
4385 } else {
4386 (tag_part.to_uppercase(), None, 0)
4387 }
4388}
4389
4390pub fn deep_merge_insert(
4396 result: &mut serde_json::Map<String, serde_json::Value>,
4397 entity: &str,
4398 bo4e: serde_json::Value,
4399) {
4400 merge_entity(result, entity, bo4e, false);
4401}
4402
4403fn merge_entity(
4415 result: &mut serde_json::Map<String, serde_json::Value>,
4416 entity: &str,
4417 bo4e: serde_json::Value,
4418 keep_both: bool,
4419) {
4420 if let Some(existing) = result.get_mut(entity) {
4421 if let (Some(existing_arr), Some(new_arr)) =
4424 (existing.as_array().map(|a| a.len()), bo4e.as_array())
4425 {
4426 if existing_arr == new_arr.len() {
4427 let existing_arr = existing.as_array_mut().unwrap();
4428 for (existing_elem, new_elem) in existing_arr.iter_mut().zip(new_arr) {
4429 if let (Some(existing_map), Some(new_map)) =
4430 (existing_elem.as_object_mut(), new_elem.as_object())
4431 {
4432 for (k, v) in new_map {
4433 if let Some(existing_v) = existing_map.get_mut(k) {
4434 if let (Some(existing_inner), Some(new_inner)) =
4435 (existing_v.as_object_mut(), v.as_object())
4436 {
4437 for (ik, iv) in new_inner {
4438 existing_inner
4439 .entry(ik.clone())
4440 .or_insert_with(|| iv.clone());
4441 }
4442 }
4443 } else {
4444 existing_map.insert(k.clone(), v.clone());
4445 }
4446 }
4447 }
4448 }
4449 return;
4450 }
4451 }
4452 if let (Some(existing_map), serde_json::Value::Object(new_map)) =
4454 (existing.as_object_mut(), &bo4e)
4455 {
4456 for (k, v) in new_map {
4457 if let Some(existing_v) = existing_map.get_mut(k) {
4458 if let (Some(existing_inner), Some(new_inner)) =
4460 (existing_v.as_object_mut(), v.as_object())
4461 {
4462 for (ik, iv) in new_inner {
4463 existing_inner
4464 .entry(ik.clone())
4465 .or_insert_with(|| iv.clone());
4466 }
4467 }
4468 } else {
4470 existing_map.insert(k.clone(), v.clone());
4471 }
4472 }
4473 return;
4474 }
4475 if !keep_both {
4476 result.insert(entity.to_string(), bo4e);
4477 return;
4478 }
4479 let existing_items = match std::mem::take(existing) {
4481 serde_json::Value::Array(items) => items,
4482 other => vec![other],
4483 };
4484 let new_items = match bo4e {
4485 serde_json::Value::Array(items) => items,
4486 other => vec![other],
4487 };
4488 *existing = serde_json::Value::Array(existing_items.into_iter().chain(new_items).collect());
4489 return;
4490 }
4491 result.insert(entity.to_string(), bo4e);
4492}
4493
4494fn append_to_list_field(
4507 result: &mut serde_json::Map<String, serde_json::Value>,
4508 entity: &str,
4509 list_field: &str,
4510 bo4e: serde_json::Value,
4511) {
4512 let mut items = match bo4e {
4513 serde_json::Value::Array(a) => a,
4514 other => vec![other],
4515 };
4516 items.retain(|v| !v.as_object().is_some_and(|o| o.is_empty()));
4517 if items.is_empty() {
4518 return;
4519 }
4520 let entry = result
4521 .entry(entity.to_string())
4522 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
4523 let Some(obj) = entry.as_object_mut() else {
4527 return;
4528 };
4529 match obj.get_mut(list_field).and_then(|v| v.as_array_mut()) {
4530 Some(existing) => existing.extend(items),
4531 None => {
4532 obj.insert(list_field.to_string(), serde_json::Value::Array(items));
4533 }
4534 }
4535}
4536
4537fn is_map_keyed_object(value: &serde_json::Value) -> bool {
4548 let Some(obj) = value.as_object() else {
4549 return false;
4550 };
4551 if obj.is_empty() {
4552 return false;
4553 }
4554 obj.iter().all(|(k, v)| {
4556 k.len() <= 5
4557 && k.chars()
4558 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
4559 && v.is_object()
4560 })
4561}
4562
4563fn find_qualifier_companion_field(
4572 definitions: &[crate::definition::MappingDefinition],
4573 entity: &str,
4574) -> Option<String> {
4575 for def in definitions {
4576 if def.meta.entity != *entity || def.meta.parent_field.is_some() {
4577 continue;
4578 }
4579 let disc = def.meta.discriminator.as_deref()?;
4580 let (disc_path, _) = disc.split_once('=')?;
4581 let disc_path_lower = disc_path.to_lowercase();
4582
4583 for (path, mapping) in &def.fields {
4586 let cf_path = path.to_lowercase();
4587 let matches = cf_path == disc_path_lower || format!("{}.0", cf_path) == disc_path_lower;
4588 if matches {
4589 let target = match mapping {
4590 FieldMapping::Simple(t) => t.as_str(),
4591 FieldMapping::Structured(s) => s.target.as_str(),
4592 FieldMapping::Nested(_) => continue,
4593 };
4594 if !target.is_empty() {
4595 return Some(target.to_string());
4596 }
4597 }
4598 }
4599 }
4600 None
4601}
4602
4603fn extract_child_from_parent(
4612 entities: &serde_json::Value,
4613 definitions: &[MappingDefinition],
4614 child_def: &MappingDefinition,
4615) -> Option<serde_json::Value> {
4616 extract_child_from_parent_with_indices(entities, definitions, child_def).map(|(v, _)| v)
4617}
4618
4619fn extract_child_from_parent_with_indices(
4624 entities: &serde_json::Value,
4625 definitions: &[MappingDefinition],
4626 child_def: &MappingDefinition,
4627) -> Option<(serde_json::Value, Vec<usize>)> {
4628 let parts: Vec<&str> = child_def.meta.source_group.split('.').collect();
4629 if parts.len() < 2 {
4630 return None;
4631 }
4632 let parent_group = parts[0];
4633 let parent_def = definitions
4634 .iter()
4635 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_def.meta.entity)?;
4636 let parent_key = to_camel_case(&parent_def.meta.entity);
4637 let child_key = to_camel_case(&child_def.meta.entity);
4638 let parent_value = entities.get(&parent_key)?;
4639
4640 if let Some(parent_map) = parent_value.as_object() {
4642 if is_map_keyed_value(parent_map) {
4643 let mut children: Vec<serde_json::Value> = Vec::new();
4644 let mut indices: Vec<usize> = Vec::new();
4645 for (i, (_key, inner)) in parent_map.iter().enumerate() {
4646 if let Some(child) = inner.get(&child_key) {
4647 if !child.is_null() {
4648 children.push(child.clone());
4649 indices.push(i);
4650 }
4651 }
4652 }
4653 return match children.len() {
4654 0 => None,
4655 1 => Some((children.into_iter().next().unwrap(), indices)),
4656 _ => Some((serde_json::Value::Array(children), indices)),
4657 };
4658 }
4659 }
4660
4661 if let Some(parent_arr) = parent_value.as_array() {
4663 let mut children: Vec<serde_json::Value> = Vec::new();
4664 let mut indices: Vec<usize> = Vec::new();
4665 for (i, item) in parent_arr.iter().enumerate() {
4666 if let Some(child) = item.get(&child_key) {
4667 if !child.is_null() {
4668 children.push(child.clone());
4669 indices.push(i);
4670 }
4671 }
4672 }
4673 return match children.len() {
4674 0 => None,
4675 1 => Some((children.into_iter().next().unwrap(), indices)),
4676 _ => Some((serde_json::Value::Array(children), indices)),
4677 };
4678 }
4679
4680 let child = parent_value.get(&child_key)?;
4682 if child.is_null() {
4683 return None;
4684 }
4685 Some((child.clone(), vec![0]))
4686}
4687
4688fn nest_child_entities_in_result(
4694 result: &mut serde_json::Map<String, serde_json::Value>,
4695 definitions: &[MappingDefinition],
4696 nesting_info: &std::collections::HashMap<String, Vec<usize>>,
4697 transaction_group: Option<&str>,
4698) {
4699 let nesting_pairs = child_entity_nesting_pairs(definitions, transaction_group);
4700
4701 for (_parent_group, parent_entity, child_entity, child_source_path) in nesting_pairs {
4702 let parent_key = to_camel_case(&parent_entity);
4703 let child_key = to_camel_case(&child_entity);
4704
4705 let child_value = match result.remove(&child_key) {
4707 Some(v) => v,
4708 None => continue,
4709 };
4710
4711 let Some(parent_value) = result.get_mut(&parent_key) else {
4716 result.insert(child_key, child_value);
4718 continue;
4719 };
4720 if parent_value.is_array() {
4721 result.insert(child_key, child_value);
4722 continue;
4723 }
4724
4725 let distribution = child_source_path
4727 .as_deref()
4728 .and_then(|sp| nesting_info.get(sp));
4729
4730 let child_items: Vec<(usize, &serde_json::Value)> = match &child_value {
4732 serde_json::Value::Array(arr) => arr.iter().enumerate().collect(),
4733 other => vec![(0, other)],
4734 };
4735
4736 let insert_or_append = |obj: &mut serde_json::Map<String, serde_json::Value>,
4739 key: &str,
4740 val: &serde_json::Value| {
4741 match obj.get_mut(key) {
4742 Some(existing) => {
4743 if !existing.is_array() {
4745 let prev = existing.take();
4746 *existing = serde_json::Value::Array(vec![prev]);
4747 }
4748 if let Some(arr) = existing.as_array_mut() {
4749 arr.push(val.clone());
4750 }
4751 }
4752 None => {
4753 obj.insert(key.to_string(), val.clone());
4754 }
4755 }
4756 };
4757
4758 if let Some(parent_map) = parent_value.as_object_mut() {
4760 if is_map_keyed_value(parent_map) {
4761 let keys: Vec<String> = parent_map.keys().cloned().collect();
4763 for (i, child_item) in &child_items {
4764 let target_idx = distribution
4765 .and_then(|dist| dist.get(*i))
4766 .copied()
4767 .unwrap_or(0);
4768 if let Some(key) = keys.get(target_idx) {
4769 if let Some(inner) = parent_map.get_mut(key).and_then(|v| v.as_object_mut())
4770 {
4771 insert_or_append(inner, &child_key, child_item);
4772 }
4773 }
4774 }
4775 continue;
4776 }
4777 }
4778
4779 if let Some(parent_arr) = parent_value.as_array_mut() {
4781 for (i, child_item) in &child_items {
4782 let target_idx = distribution
4783 .and_then(|dist| dist.get(*i))
4784 .copied()
4785 .unwrap_or(0);
4786 if let Some(parent_obj) = parent_arr
4787 .get_mut(target_idx)
4788 .and_then(|v| v.as_object_mut())
4789 {
4790 insert_or_append(parent_obj, &child_key, child_item);
4791 }
4792 }
4793 continue;
4794 }
4795
4796 if let Some(parent_obj) = parent_value.as_object_mut() {
4798 for (_i, child_item) in &child_items {
4799 insert_or_append(parent_obj, &child_key, child_item);
4800 }
4801 continue;
4802 }
4803
4804 result.insert(child_key, child_value);
4806 }
4807}
4808
4809pub(crate) fn child_entity_nesting_pairs(
4819 definitions: &[MappingDefinition],
4820 transaction_group: Option<&str>,
4821) -> Vec<(String, String, String, Option<String>)> {
4822 let mut nesting_pairs: Vec<(String, String, String, Option<String>)> = Vec::new();
4825 for def in definitions {
4826 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
4827 if parts.len() < 2 || def.meta.parent_field.is_some() {
4828 continue;
4829 }
4830 let parent_group = parts[0];
4831 if transaction_group.is_some_and(|tx| tx == parent_group) {
4837 continue;
4838 }
4839 let child_entity = def.meta.entity.clone();
4840 let child_has_parent_level_def = definitions
4844 .iter()
4845 .any(|d| d.meta.source_group == parent_group && d.meta.entity == child_entity);
4846 if child_has_parent_level_def {
4847 continue;
4848 }
4849 let parent_entity = definitions
4851 .iter()
4852 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_entity)
4853 .map(|d| d.meta.entity.clone());
4854 if let Some(ref parent_entity) = parent_entity {
4855 let child_key_lc = to_camel_case(&child_entity);
4860 let parent_defs: Vec<_> = definitions
4861 .iter()
4862 .filter(|d| d.meta.entity == *parent_entity)
4863 .collect();
4864 let has_conflicting_field = parent_defs.iter().any(|pd| {
4865 pd.fields.values().any(|fm| {
4866 let target = match fm {
4867 crate::definition::FieldMapping::Simple(t) => t.as_str(),
4868 crate::definition::FieldMapping::Structured(s) => s.target.as_str(),
4869 crate::definition::FieldMapping::Nested(_) => "",
4870 };
4871 target.starts_with(&child_key_lc)
4872 && target.get(child_key_lc.len()..child_key_lc.len() + 1) == Some(".")
4873 })
4874 });
4875 if has_conflicting_field {
4876 continue;
4877 }
4878 if nesting_pairs
4880 .iter()
4881 .any(|(_, pe, ce, _)| *pe == *parent_entity && *ce == child_entity)
4882 {
4883 continue;
4884 }
4885 nesting_pairs.push((
4886 parent_group.to_string(),
4887 parent_entity.clone(),
4888 child_entity,
4889 def.meta.source_path.clone(),
4890 ));
4891 }
4892 }
4893
4894 nesting_pairs
4895}
4896
4897fn is_map_keyed_value(map: &serde_json::Map<String, serde_json::Value>) -> bool {
4899 if map.is_empty() {
4900 return false;
4901 }
4902 map.values().all(|v| v.is_object())
4903 && map.keys().all(|k| {
4904 k.len() <= 5
4905 || k.chars()
4906 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
4907 })
4908}
4909
4910#[derive(Clone)]
4913struct CodeSite<'a> {
4914 target: &'a str,
4915 parent_field: Option<&'a str>,
4921 source_path: &'a str,
4922 seg_tag: String,
4923 path_qualifier: Option<String>,
4925 disc_qualifier: Option<String>,
4927 element_idx: usize,
4928 component_idx: usize,
4929 enum_map: Option<&'a std::collections::BTreeMap<String, String>>,
4930 also_target: Option<&'a str>,
4931 also_enum_map: Option<&'a std::collections::BTreeMap<String, String>>,
4932}
4933
4934pub(crate) fn to_camel_case(name: &str) -> String {
4935 let mut chars = name.chars();
4936 match chars.next() {
4937 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
4938 None => String::new(),
4939 }
4940}
4941
4942fn set_nested_value(map: &mut serde_json::Map<String, serde_json::Value>, path: &str, val: String) {
4945 set_nested_value_json(map, path, serde_json::Value::String(val));
4946}
4947
4948fn set_nested_value_json(
4950 map: &mut serde_json::Map<String, serde_json::Value>,
4951 path: &str,
4952 val: serde_json::Value,
4953) {
4954 if let Some((prefix, leaf)) = path.rsplit_once('.') {
4955 let mut current = map;
4956 for part in prefix.split('.') {
4957 let entry = current
4958 .entry(part.to_string())
4959 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
4960 current = entry.as_object_mut().expect("expected object in path");
4961 }
4962 current.insert(leaf.to_string(), val);
4963 } else {
4964 map.insert(path.to_string(), val);
4965 }
4966}
4967
4968#[derive(serde::Serialize, serde::Deserialize)]
4973pub struct VariantCache {
4974 pub message_defs: Vec<MappingDefinition>,
4976 pub transaction_defs: BTreeMap<String, Vec<MappingDefinition>>,
4978 pub combined_defs: BTreeMap<String, Vec<MappingDefinition>>,
4980 #[serde(default)]
4982 pub code_lookups: BTreeMap<String, crate::code_lookup::CodeLookup>,
4983 #[serde(default)]
4985 pub mig_schema: Option<mig_types::schema::mig::MigSchema>,
4986 #[serde(default)]
4988 pub segment_structure: Option<crate::segment_structure::SegmentStructure>,
4989 #[serde(skip)]
4994 pub code_lists: std::sync::Arc<crate::code_lists::CodeLists>,
4995 #[serde(default)]
4998 pub pid_segment_numbers: BTreeMap<String, Vec<String>>,
4999 #[serde(default)]
5002 pub pid_requirements: BTreeMap<String, crate::pid_requirements::PidRequirements>,
5003 #[serde(default)]
5008 pub pid_ahb_workflows: BTreeMap<String, ahb_types::AhbWorkflow>,
5009 #[serde(default)]
5013 pub tx_groups: BTreeMap<String, String>,
5014}
5015
5016impl VariantCache {
5017 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
5019 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
5020 path: path.display().to_string(),
5021 message: e.to_string(),
5022 })?;
5023 if let Some(parent) = path.parent() {
5024 std::fs::create_dir_all(parent)?;
5025 }
5026 std::fs::write(path, encoded)?;
5027 Ok(())
5028 }
5029
5030 pub fn load(path: &Path) -> Result<Self, MappingError> {
5032 let bytes = std::fs::read(path)?;
5033 let mut cache: Self =
5034 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
5035 path: path.display().to_string(),
5036 message: e.to_string(),
5037 })?;
5038 cache.code_lists = crate::code_lists::CodeLists::discover(path);
5039 Ok(cache)
5040 }
5041
5042 pub fn tx_group(&self, pid: &str) -> Option<&str> {
5046 self.tx_groups
5047 .get(&format!("pid_{pid}"))
5048 .map(|s| s.as_str())
5049 }
5050
5051 pub fn msg_engine(&self, pid: &str) -> MappingEngine {
5055 let mut eng = MappingEngine::from_definitions_with_code_lists(
5056 std::sync::Arc::clone(&self.code_lists),
5057 self.message_defs.clone(),
5058 )
5059 .with_pid(pid);
5060 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
5061 eng = eng.with_code_lookup(cl.clone());
5062 }
5063 eng
5064 }
5065
5066 pub fn tx_engine(&self, pid: &str) -> Option<MappingEngine> {
5070 self.transaction_defs
5071 .get(&format!("pid_{pid}"))
5072 .map(|defs| {
5073 let mut eng = MappingEngine::from_definitions_with_code_lists(
5074 std::sync::Arc::clone(&self.code_lists),
5075 defs.clone(),
5076 )
5077 .with_pid(pid);
5078 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
5079 eng = eng.with_code_lookup(cl.clone());
5080 }
5081 eng
5082 })
5083 }
5084
5085 pub fn filtered_mig(&self, pid: &str) -> Option<mig_types::schema::mig::MigSchema> {
5093 let mig = self.mig_schema.as_ref()?;
5094 let numbers = self
5095 .pid_segment_numbers
5096 .get(&format!("pid_{pid}"))
5097 .or_else(|| self.pid_segment_numbers.get("pid_"))?;
5098 let number_set: std::collections::HashSet<String> = numbers.iter().cloned().collect();
5099 Some(mig_assembly::pid_filter::filter_mig_for_pid(
5100 mig,
5101 &number_set,
5102 ))
5103 }
5104}
5105
5106#[derive(serde::Serialize, serde::Deserialize)]
5111pub struct DataBundle {
5112 pub format_version: String,
5113 pub bundle_version: u32,
5114 pub variants: BTreeMap<String, VariantCache>,
5115 #[serde(default)]
5120 pub bo4e_catalog: crate::bo4e_catalog::Bo4eCatalog,
5121
5122 #[serde(default, skip_serializing_if = "Option::is_none")]
5134 pub built_by: Option<String>,
5135 #[serde(default)]
5147 pub code_lists: crate::code_lists::CodeLists,
5148}
5149
5150impl DataBundle {
5151 pub const CURRENT_VERSION: u32 = 2;
5152
5153 pub const PRODUCING_VERSION: &'static str = env!("CARGO_PKG_VERSION");
5163
5164 pub fn variant(&self, name: &str) -> Option<&VariantCache> {
5165 self.variants.get(name)
5166 }
5167
5168 pub fn write_to<W: std::io::Write>(&self, writer: &mut W) -> Result<(), MappingError> {
5169 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
5170 path: "<stream>".to_string(),
5171 message: e.to_string(),
5172 })?;
5173 writer.write_all(&encoded).map_err(MappingError::Io)
5174 }
5175
5176 pub fn read_from<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
5177 let mut bytes = Vec::new();
5178 reader.read_to_end(&mut bytes).map_err(MappingError::Io)?;
5179 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
5180 path: "<stream>".to_string(),
5181 message: e.to_string(),
5182 })
5183 }
5184
5185 pub fn read_from_checked<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
5186 let mut bundle = Self::read_from(reader)?;
5187 let shared = std::sync::Arc::new(std::mem::take(&mut bundle.code_lists));
5190 for variant in bundle.variants.values_mut() {
5191 variant.code_lists = std::sync::Arc::clone(&shared);
5192 }
5193 bundle.code_lists = (*shared).clone();
5194 if bundle.bundle_version != Self::CURRENT_VERSION {
5195 return Err(MappingError::CacheRead {
5196 path: "<stream>".to_string(),
5197 message: format!(
5198 "Incompatible bundle version {}, expected version {}. \
5199 Run `edifact-data update` to fetch compatible bundles.",
5200 bundle.bundle_version,
5201 Self::CURRENT_VERSION
5202 ),
5203 });
5204 }
5205 Ok(bundle)
5206 }
5207
5208 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
5209 if let Some(parent) = path.parent() {
5210 std::fs::create_dir_all(parent)?;
5211 }
5212 let mut file = std::fs::File::create(path).map_err(MappingError::Io)?;
5213 self.write_to(&mut file)
5214 }
5215
5216 pub fn load(path: &Path) -> Result<Self, MappingError> {
5217 let mut file = std::fs::File::open(path).map_err(MappingError::Io)?;
5218 Self::read_from_checked(&mut file)
5219 }
5220}
5221
5222#[cfg(test)]
5223mod variant_cache_helper_tests {
5224 use super::*;
5225
5226 fn make_test_cache() -> VariantCache {
5227 let mut tx_groups = BTreeMap::new();
5228 tx_groups.insert("pid_55001".to_string(), "SG4".to_string());
5229 tx_groups.insert("pid_21007".to_string(), "SG14".to_string());
5230
5231 let mut transaction_defs = BTreeMap::new();
5232 transaction_defs.insert("pid_55001".to_string(), vec![]);
5233 transaction_defs.insert("pid_21007".to_string(), vec![]);
5234
5235 VariantCache {
5236 code_lists: Default::default(),
5237 message_defs: vec![],
5238 transaction_defs,
5239 combined_defs: BTreeMap::new(),
5240 code_lookups: BTreeMap::new(),
5241 mig_schema: None,
5242 segment_structure: None,
5243 pid_segment_numbers: BTreeMap::new(),
5244 pid_requirements: BTreeMap::new(),
5245 pid_ahb_workflows: BTreeMap::new(),
5246 tx_groups,
5247 }
5248 }
5249
5250 #[test]
5251 fn test_tx_group_returns_correct_group() {
5252 let vc = make_test_cache();
5253 assert_eq!(vc.tx_group("55001").unwrap(), "SG4");
5254 assert_eq!(vc.tx_group("21007").unwrap(), "SG14");
5255 }
5256
5257 #[test]
5258 fn test_tx_group_unknown_pid_returns_none() {
5259 let vc = make_test_cache();
5260 assert!(vc.tx_group("99999").is_none());
5261 }
5262
5263 #[test]
5264 fn test_msg_engine_returns_engine() {
5265 let vc = make_test_cache();
5266 let engine = vc.msg_engine("55001");
5267 assert_eq!(engine.definitions().len(), 0);
5268 }
5269
5270 #[test]
5271 fn test_tx_engine_returns_engine_for_known_pid() {
5272 let vc = make_test_cache();
5273 assert!(vc.tx_engine("55001").is_some());
5274 }
5275
5276 #[test]
5277 fn test_tx_engine_returns_none_for_unknown_pid() {
5278 let vc = make_test_cache();
5279 assert!(vc.tx_engine("99999").is_none());
5280 }
5281
5282 fn make_populated_cache() -> VariantCache {
5286 let pids: Vec<String> = (0..40).map(|i| format!("pid_{}", 55000 + i * 7)).collect();
5287 let schema: serde_json::Value = serde_json::from_str(include_str!(
5288 "../../mig-types/src/generated/fv2504/utilmd/pids/pid_55001_schema.json"
5289 ))
5290 .unwrap();
5291 let code_lookup = crate::code_lookup::CodeLookup::from_schema_value(&schema);
5292 let element_counts: serde_json::Map<String, serde_json::Value> = (0..40)
5293 .map(|i| (format!("T{i:02}"), serde_json::json!(i)))
5294 .collect();
5295 let segment_structure: SegmentStructure =
5296 serde_json::from_value(serde_json::json!({ "element_counts": element_counts }))
5297 .unwrap();
5298 let ubs: serde_json::Map<String, serde_json::Value> = (0..40)
5299 .map(|i| (format!("UB{i}"), serde_json::json!({ "Ref": i })))
5300 .collect();
5301 let workflow: ahb_types::AhbWorkflow = serde_json::from_value(serde_json::json!({
5302 "pruefidentifikator": "55001",
5303 "description": "",
5304 "communication_direction": null,
5305 "fields": [],
5306 "ub_definitions": ubs,
5307 }))
5308 .unwrap();
5309
5310 let mut vc = make_test_cache();
5311 vc.segment_structure = Some(segment_structure);
5312 for pid in &pids {
5313 vc.transaction_defs.insert(pid.clone(), vec![]);
5314 vc.combined_defs.insert(pid.clone(), vec![]);
5315 vc.code_lookups.insert(pid.clone(), code_lookup.clone());
5316 vc.pid_segment_numbers
5317 .insert(pid.clone(), vec!["00001".to_string()]);
5318 vc.pid_ahb_workflows.insert(pid.clone(), workflow.clone());
5319 vc.tx_groups.insert(pid.clone(), "SG4".to_string());
5320 }
5321 vc
5322 }
5323
5324 #[test]
5327 fn test_enrichment_uses_codes_of_the_path_qualifier_variant() {
5328 let comp = |sub: u64, id: &str, codes: Option<serde_json::Value>| match codes {
5329 Some(c) => serde_json::json!({"sub_index": sub, "id": id, "type": "code", "codes": c}),
5330 None => serde_json::json!({"sub_index": sub, "id": id, "type": "data"}),
5331 };
5332 let code = |v: &str, n: &str| serde_json::json!([{"value": v, "name": n}]);
5333 let seg = |tag: &str, composite: &str, comps: Vec<serde_json::Value>| serde_json::json!({"id": tag, "elements": [{"index": 0, "composite": composite, "components": comps}]});
5334 let schema = serde_json::json!({"fields": {"sg15": {"segments": [
5335 seg("RFF", "C506", vec![comp(0, "1153", Some(code("Z13", "PID"))), comp(1, "1154", Some(code("21037", "RD / NB-Bewertung")))]),
5336 seg("RFF", "C506", vec![comp(0, "1153", Some(code("ACW", "Referenz"))), comp(1, "1154", None)]),
5337 seg("CAV", "C889", vec![comp(0, "7111", Some(code("Z91", "Z91"))), comp(1, "7110", Some(code("A", "Alpha")))]),
5338 seg("CAV", "C889", vec![comp(0, "7111", Some(code("ZF0", "ZF0"))), comp(1, "7110", Some(code("C", "Gamma")))]),
5339 ]}}});
5340 let engine = MappingEngine::new_empty()
5341 .with_code_lookup(crate::code_lookup::CodeLookup::from_schema_value(&schema));
5342 let def = MappingDefinition::from_toml_str(
5343 r#"
5344[meta]
5345entity = "Status"
5346bo4e_type = "Status"
5347source_group = "SG15"
5348source_path = "sg15"
5349discriminator = "RFF.0.0=Z13"
5350
5351[fields]
5352"rff.0.1" = "pruefidentifikator"
5353"rff[ACW].0.1" = "referenz"
5354"cav[Z91].0.1" = "z91Wert"
5355"cav[ZF0].0.1" = "zf0Wert"
5356"#,
5357 )
5358 .unwrap();
5359 let segment = |tag: &str, elements: &[&[&str]]| OwnedSegment {
5360 id: tag.to_string(),
5361 elements: elements
5362 .iter()
5363 .map(|e| e.iter().map(|c| c.to_string()).collect())
5364 .collect(),
5365 segment_number: 1,
5366 };
5367 let json = engine.map_forward_from_segments(
5368 &[
5369 segment("RFF", &[&["Z13", "21037"]]),
5370 segment("RFF", &[&["ACW", "REF-1"]]),
5371 segment("CAV", &[&["Z91", "C"]]),
5372 segment("CAV", &[&["ZF0", "C"]]),
5373 ],
5374 &def,
5375 );
5376 assert_eq!(
5377 json["referenz"],
5378 serde_json::json!("REF-1"),
5379 "RFF+ACW d1154 is data; RFF+Z13's codes must not apply: {json}"
5380 );
5381 assert_eq!(json["pruefidentifikator"]["meaning"], "RD / NB-Bewertung");
5382 assert_eq!(
5383 json["z91Wert"]["meaning"],
5384 serde_json::Value::Null,
5385 "'C' is a CAV+ZF0 code, unknown to CAV+Z91: {json}"
5386 );
5387 assert_eq!(json["zf0Wert"]["meaning"], "Gamma");
5388 }
5389
5390 #[test]
5394 fn list_target_reads_and_writes_every_repetition_in_order() {
5395 let engine = MappingEngine::new_empty();
5396 let def = MappingDefinition::from_toml_str(
5397 r#"
5398[meta]
5399entity = "Zuordnung"
5400bo4e_type = "Zuordnung"
5401source_group = "SG10"
5402source_path = "sg10"
5403
5404[fields]
5405"cci.2.0" = "merkmal.code"
5406"cav[*,*].0.0" = "werte[].code"
5407"cav[*,*].0.3" = "werte[].text"
5408"cav[Z30,*].0.3" = "geraetenummern[].nummer"
5409"#,
5410 )
5411 .unwrap();
5412 let segment = |tag: &str, elements: &[&[&str]]| OwnedSegment {
5413 id: tag.to_string(),
5414 elements: elements
5415 .iter()
5416 .map(|e| e.iter().map(|c| c.to_string()).collect())
5417 .collect(),
5418 segment_number: 1,
5419 };
5420 let json = engine.map_forward_from_segments(
5421 &[
5422 segment("CCI", &[&[""], &[""], &["ZB3"]]),
5423 segment("CAV", &[&["Z90", "", "", "UENB"]]),
5424 segment("CAV", &[&["Z91", "", "", "MSB"]]),
5425 segment("CAV", &[&["Z30", "", "", "W1"]]),
5426 segment("CAV", &[&["Z30", "", "", "W2"]]),
5427 ],
5428 &def,
5429 );
5430 assert_eq!(
5431 json["werte"],
5432 serde_json::json!([
5433 {"code": "Z90", "text": "UENB"},
5434 {"code": "Z91", "text": "MSB"},
5435 {"code": "Z30", "text": "W1"},
5436 {"code": "Z30", "text": "W2"},
5437 ]),
5438 "{json}"
5439 );
5440 assert_eq!(
5441 json["geraetenummern"],
5442 serde_json::json!([{"nummer": "W1"}, {"nummer": "W2"}])
5443 );
5444
5445 let only_werte = serde_json::json!({
5447 "merkmal": {"code": "ZB3"},
5448 "werte": [{"text": "UENB", "code": "Z90"}, {"code": "Z91", "text": "MSB"}],
5449 });
5450 let instance = engine.map_reverse(&only_werte, &def);
5451 let cavs: Vec<Vec<String>> = instance
5452 .segments
5453 .iter()
5454 .filter(|s| s.tag == "CAV")
5455 .map(|s| s.elements[0].clone())
5456 .collect();
5457 assert_eq!(
5458 cavs,
5459 vec![
5460 vec![
5461 "Z90".to_string(),
5462 String::new(),
5463 String::new(),
5464 "UENB".to_string()
5465 ],
5466 vec![
5467 "Z91".to_string(),
5468 String::new(),
5469 String::new(),
5470 "MSB".to_string()
5471 ],
5472 ]
5473 );
5474 }
5475
5476 #[test]
5477 fn test_variant_cache_serialization_is_deterministic() {
5478 let reference = serde_json::to_vec(&make_populated_cache()).unwrap();
5479 for _ in 0..5 {
5480 let again = serde_json::to_vec(&make_populated_cache()).unwrap();
5481 assert!(
5482 reference == again,
5483 "VariantCache serialization must not depend on HashMap iteration order"
5484 );
5485 }
5486 }
5487
5488 #[test]
5489 fn test_variant_cache_serializes_map_keys_sorted() {
5490 use indexmap::IndexMap;
5491 use serde::de::IgnoredAny;
5492
5493 #[derive(serde::Deserialize)]
5494 struct ProbeWorkflow {
5495 ub_definitions: IndexMap<String, IgnoredAny>,
5496 }
5497 #[derive(serde::Deserialize)]
5498 struct ProbeStructure {
5499 element_counts: IndexMap<String, usize>,
5500 }
5501 #[derive(serde::Deserialize)]
5502 struct Probe {
5503 transaction_defs: IndexMap<String, IgnoredAny>,
5504 combined_defs: IndexMap<String, IgnoredAny>,
5505 code_lookups: IndexMap<String, IndexMap<String, IgnoredAny>>,
5506 segment_structure: ProbeStructure,
5507 pid_segment_numbers: IndexMap<String, IgnoredAny>,
5508 pid_requirements: IndexMap<String, IgnoredAny>,
5509 pid_ahb_workflows: IndexMap<String, ProbeWorkflow>,
5510 tx_groups: IndexMap<String, String>,
5511 }
5512 fn assert_sorted<'a>(what: &str, keys: impl Iterator<Item = &'a String>) {
5513 let keys: Vec<&String> = keys.collect();
5514 let mut sorted = keys.clone();
5515 sorted.sort();
5516 assert_eq!(keys, sorted, "{what} keys must serialize in sorted order");
5517 }
5518
5519 let json = serde_json::to_string(&make_populated_cache()).unwrap();
5520 let probe: Probe = serde_json::from_str(&json).unwrap();
5521 assert_sorted("transaction_defs", probe.transaction_defs.keys());
5522 assert_sorted("combined_defs", probe.combined_defs.keys());
5523 assert_sorted("code_lookups", probe.code_lookups.keys());
5524 let lookup = probe.code_lookups.values().next().unwrap();
5525 assert!(lookup.len() > 10, "fixture lookup should have many entries");
5526 assert_sorted("code_lookup entries", lookup.keys());
5527 assert_sorted(
5528 "segment_structure",
5529 probe.segment_structure.element_counts.keys(),
5530 );
5531 assert_sorted("pid_segment_numbers", probe.pid_segment_numbers.keys());
5532 assert_sorted("pid_requirements", probe.pid_requirements.keys());
5533 assert_sorted("pid_ahb_workflows", probe.pid_ahb_workflows.keys());
5534 let wf = probe.pid_ahb_workflows.values().next().unwrap();
5535 assert_sorted("ub_definitions", wf.ub_definitions.keys());
5536 assert_sorted("tx_groups", probe.tx_groups.keys());
5537 }
5538
5539 #[test]
5540 fn test_data_bundle_serializes_variants_sorted() {
5541 use indexmap::IndexMap;
5542 use serde::de::IgnoredAny;
5543
5544 #[derive(serde::Deserialize)]
5545 struct Probe {
5546 variants: IndexMap<String, IgnoredAny>,
5547 }
5548 let variants: BTreeMap<String, VariantCache> = (0..20)
5549 .map(|i| (format!("VARIANT_{i:02}"), make_test_cache()))
5550 .collect();
5551 let bundle = DataBundle {
5552 format_version: "FV2504".to_string(),
5553 bundle_version: DataBundle::CURRENT_VERSION,
5554 built_by: Some(DataBundle::PRODUCING_VERSION.to_string()),
5555 variants,
5556 bo4e_catalog: Default::default(),
5557 code_lists: Default::default(),
5558 };
5559 let mut bytes = Vec::new();
5560 bundle.write_to(&mut bytes).unwrap();
5561 let probe: Probe = serde_json::from_slice(&bytes).unwrap();
5562 let keys: Vec<&String> = probe.variants.keys().collect();
5563 let mut sorted = keys.clone();
5564 sorted.sort();
5565 assert_eq!(keys, sorted);
5566 }
5567}
5568
5569#[cfg(test)]
5570mod tests {
5571 use super::*;
5572 use crate::definition::{MappingDefinition, MappingMeta, StructuredFieldMapping};
5573 use indexmap::IndexMap;
5574
5575 fn make_def(fields: IndexMap<String, FieldMapping>) -> MappingDefinition {
5576 MappingDefinition {
5577 meta: MappingMeta {
5578 entity: "Test".to_string(),
5579 bo4e_type: "Test".to_string(),
5580 source_group: "SG4".to_string(),
5581 source_path: None,
5582 discriminator: None,
5583 repeat_on_tag: None,
5584 parent_field: None,
5585 target_list: None,
5586 order: None,
5587 },
5588 fields,
5589 complex_handlers: None,
5590 }
5591 }
5592
5593 #[test]
5594 fn test_map_interchange_single_transaction_backward_compat() {
5595 use mig_assembly::assembler::*;
5596
5597 let tree = AssembledTree {
5599 segments: vec![
5600 AssembledSegment {
5601 tag: "UNH".to_string(),
5602 elements: vec![vec!["001".to_string()]],
5603 mig_number: None,
5604 segment_number: None,
5605 },
5606 AssembledSegment {
5607 tag: "BGM".to_string(),
5608 elements: vec![vec!["E01".to_string()], vec!["DOC001".to_string()]],
5609 mig_number: None,
5610 segment_number: None,
5611 },
5612 ],
5613 groups: vec![
5614 AssembledGroup {
5615 group_id: "SG2".to_string(),
5616 repetitions: vec![AssembledGroupInstance {
5617 segments: vec![AssembledSegment {
5618 tag: "NAD".to_string(),
5619 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
5620 mig_number: None,
5621 segment_number: None,
5622 }],
5623 child_groups: vec![],
5624 entry_mig_number: None,
5625 variant_mig_numbers: vec![],
5626 skipped_segments: vec![],
5627 skipped_positions: Vec::new(),
5628 }],
5629 },
5630 AssembledGroup {
5631 group_id: "SG4".to_string(),
5632 repetitions: vec![AssembledGroupInstance {
5633 segments: vec![AssembledSegment {
5634 tag: "IDE".to_string(),
5635 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5636 mig_number: None,
5637 segment_number: None,
5638 }],
5639 child_groups: vec![AssembledGroup {
5640 group_id: "SG5".to_string(),
5641 repetitions: vec![AssembledGroupInstance {
5642 segments: vec![AssembledSegment {
5643 tag: "LOC".to_string(),
5644 elements: vec![
5645 vec!["Z16".to_string()],
5646 vec!["DE000111222333".to_string()],
5647 ],
5648 mig_number: None,
5649 segment_number: None,
5650 }],
5651 child_groups: vec![],
5652 entry_mig_number: None,
5653 variant_mig_numbers: vec![],
5654 skipped_segments: vec![],
5655 skipped_positions: Vec::new(),
5656 }],
5657 }],
5658 entry_mig_number: None,
5659 variant_mig_numbers: vec![],
5660 skipped_segments: vec![],
5661 skipped_positions: Vec::new(),
5662 }],
5663 },
5664 ],
5665 post_group_start: 2,
5666 inter_group_segments: std::collections::BTreeMap::new(),
5667 };
5668
5669 let msg_engine = MappingEngine::from_definitions(vec![]);
5671
5672 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5674 tx_fields.insert(
5675 "ide.1".to_string(),
5676 FieldMapping::Simple("vorgangId".to_string()),
5677 );
5678 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5679 malo_fields.insert(
5680 "loc.1".to_string(),
5681 FieldMapping::Simple("marktlokationsId".to_string()),
5682 );
5683
5684 let tx_engine = MappingEngine::from_definitions(vec![
5685 MappingDefinition {
5686 meta: MappingMeta {
5687 entity: "Prozessdaten".to_string(),
5688 bo4e_type: "Prozessdaten".to_string(),
5689 source_group: "SG4".to_string(),
5690 source_path: None,
5691 discriminator: None,
5692 repeat_on_tag: None,
5693 parent_field: None,
5694 target_list: None,
5695 order: None,
5696 },
5697 fields: tx_fields,
5698 complex_handlers: None,
5699 },
5700 MappingDefinition {
5701 meta: MappingMeta {
5702 entity: "Marktlokation".to_string(),
5703 bo4e_type: "Marktlokation".to_string(),
5704 source_group: "SG4.SG5".to_string(),
5705 source_path: None,
5706 discriminator: None,
5707 repeat_on_tag: None,
5708 parent_field: None,
5709 target_list: None,
5710 order: None,
5711 },
5712 fields: malo_fields,
5713 complex_handlers: None,
5714 },
5715 ]);
5716
5717 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
5718
5719 assert_eq!(result.transaktionen.len(), 1);
5720 assert_eq!(
5721 result.transaktionen[0].transaktionsdaten["vorgangId"]
5722 .as_str()
5723 .unwrap(),
5724 "TX001"
5725 );
5726 assert_eq!(
5729 result.transaktionen[0].stammdaten["marktlokation"]["marktlokationsId"]
5730 .as_str()
5731 .unwrap(),
5732 "DE000111222333"
5733 );
5734 }
5735
5736 #[test]
5737 fn test_map_reverse_pads_intermediate_empty_elements() {
5738 let mut fields = IndexMap::new();
5740 fields.insert(
5741 "nad.0".to_string(),
5742 FieldMapping::Structured(StructuredFieldMapping {
5743 target: String::new(),
5744 transform: None,
5745 when: None,
5746 default: Some("Z09".to_string()),
5747 enum_map: None,
5748 code_list: None,
5749 also_code_list: None,
5750 when_filled: None,
5751 also_target: None,
5752 also_enum_map: None,
5753 }),
5754 );
5755 fields.insert(
5756 "nad.3.0".to_string(),
5757 FieldMapping::Simple("name".to_string()),
5758 );
5759 fields.insert(
5760 "nad.3.1".to_string(),
5761 FieldMapping::Simple("vorname".to_string()),
5762 );
5763
5764 let def = make_def(fields);
5765 let engine = MappingEngine::from_definitions(vec![]);
5766
5767 let bo4e = serde_json::json!({
5768 "name": "Muster",
5769 "vorname": "Max"
5770 });
5771
5772 let instance = engine.map_reverse(&bo4e, &def);
5773 assert_eq!(instance.segments.len(), 1);
5774
5775 let nad = &instance.segments[0];
5776 assert_eq!(nad.tag, "NAD");
5777 assert_eq!(nad.elements.len(), 4);
5778 assert_eq!(nad.elements[0], vec!["Z09"]);
5779 assert_eq!(nad.elements[1], vec![""]);
5781 assert_eq!(nad.elements[2], vec![""]);
5782 assert_eq!(nad.elements[3][0], "Muster");
5783 assert_eq!(nad.elements[3][1], "Max");
5784 }
5785
5786 #[test]
5787 fn test_map_reverse_no_padding_when_contiguous() {
5788 let mut fields = IndexMap::new();
5790 fields.insert(
5791 "dtm.0.0".to_string(),
5792 FieldMapping::Structured(StructuredFieldMapping {
5793 target: String::new(),
5794 transform: None,
5795 when: None,
5796 default: Some("92".to_string()),
5797 enum_map: None,
5798 code_list: None,
5799 also_code_list: None,
5800 when_filled: None,
5801 also_target: None,
5802 also_enum_map: None,
5803 }),
5804 );
5805 fields.insert(
5806 "dtm.0.1".to_string(),
5807 FieldMapping::Simple("value".to_string()),
5808 );
5809 fields.insert(
5810 "dtm.0.2".to_string(),
5811 FieldMapping::Structured(StructuredFieldMapping {
5812 target: String::new(),
5813 transform: None,
5814 when: None,
5815 default: Some("303".to_string()),
5816 enum_map: None,
5817 code_list: None,
5818 also_code_list: None,
5819 when_filled: None,
5820 also_target: None,
5821 also_enum_map: None,
5822 }),
5823 );
5824
5825 let def = make_def(fields);
5826 let engine = MappingEngine::from_definitions(vec![]);
5827
5828 let bo4e = serde_json::json!({ "value": "20250531" });
5829
5830 let instance = engine.map_reverse(&bo4e, &def);
5831 let dtm = &instance.segments[0];
5832 assert_eq!(dtm.elements.len(), 1);
5834 assert_eq!(dtm.elements[0], vec!["92", "20250531", "303"]);
5835 }
5836
5837 #[test]
5838 fn test_map_message_level_extracts_sg2_only() {
5839 use mig_assembly::assembler::*;
5840
5841 let tree = AssembledTree {
5843 segments: vec![
5844 AssembledSegment {
5845 tag: "UNH".to_string(),
5846 elements: vec![vec!["001".to_string()]],
5847 mig_number: None,
5848 segment_number: None,
5849 },
5850 AssembledSegment {
5851 tag: "BGM".to_string(),
5852 elements: vec![vec!["E01".to_string()]],
5853 mig_number: None,
5854 segment_number: None,
5855 },
5856 ],
5857 groups: vec![
5858 AssembledGroup {
5859 group_id: "SG2".to_string(),
5860 repetitions: vec![AssembledGroupInstance {
5861 segments: vec![AssembledSegment {
5862 tag: "NAD".to_string(),
5863 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
5864 mig_number: None,
5865 segment_number: None,
5866 }],
5867 child_groups: vec![],
5868 entry_mig_number: None,
5869 variant_mig_numbers: vec![],
5870 skipped_segments: vec![],
5871 skipped_positions: Vec::new(),
5872 }],
5873 },
5874 AssembledGroup {
5875 group_id: "SG4".to_string(),
5876 repetitions: vec![AssembledGroupInstance {
5877 segments: vec![AssembledSegment {
5878 tag: "IDE".to_string(),
5879 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5880 mig_number: None,
5881 segment_number: None,
5882 }],
5883 child_groups: vec![],
5884 entry_mig_number: None,
5885 variant_mig_numbers: vec![],
5886 skipped_segments: vec![],
5887 skipped_positions: Vec::new(),
5888 }],
5889 },
5890 ],
5891 post_group_start: 2,
5892 inter_group_segments: std::collections::BTreeMap::new(),
5893 };
5894
5895 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5897 msg_fields.insert(
5898 "nad.0".to_string(),
5899 FieldMapping::Simple("marktrolle".to_string()),
5900 );
5901 msg_fields.insert(
5902 "nad.1".to_string(),
5903 FieldMapping::Simple("rollencodenummer".to_string()),
5904 );
5905 let msg_def = MappingDefinition {
5906 meta: MappingMeta {
5907 entity: "Marktteilnehmer".to_string(),
5908 bo4e_type: "Marktteilnehmer".to_string(),
5909 source_group: "SG2".to_string(),
5910 source_path: None,
5911 discriminator: None,
5912 repeat_on_tag: None,
5913 parent_field: None,
5914 target_list: None,
5915 order: None,
5916 },
5917 fields: msg_fields,
5918 complex_handlers: None,
5919 };
5920
5921 let engine = MappingEngine::from_definitions(vec![msg_def.clone()]);
5922 let result = engine.map_all_forward(&tree);
5923
5924 assert!(result.get("marktteilnehmer").is_some());
5926 let mt = &result["marktteilnehmer"];
5927 assert_eq!(mt["marktrolle"].as_str().unwrap(), "MS");
5928 assert_eq!(mt["rollencodenummer"].as_str().unwrap(), "9900123");
5929 }
5930
5931 #[test]
5932 fn test_map_transaction_scoped_to_sg4_instance() {
5933 use mig_assembly::assembler::*;
5934
5935 let tree = AssembledTree {
5937 segments: vec![
5938 AssembledSegment {
5939 tag: "UNH".to_string(),
5940 elements: vec![vec!["001".to_string()]],
5941 mig_number: None,
5942 segment_number: None,
5943 },
5944 AssembledSegment {
5945 tag: "BGM".to_string(),
5946 elements: vec![vec!["E01".to_string()]],
5947 mig_number: None,
5948 segment_number: None,
5949 },
5950 ],
5951 groups: vec![AssembledGroup {
5952 group_id: "SG4".to_string(),
5953 repetitions: vec![AssembledGroupInstance {
5954 segments: vec![AssembledSegment {
5955 tag: "IDE".to_string(),
5956 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5957 mig_number: None,
5958 segment_number: None,
5959 }],
5960 child_groups: vec![AssembledGroup {
5961 group_id: "SG5".to_string(),
5962 repetitions: vec![AssembledGroupInstance {
5963 segments: vec![AssembledSegment {
5964 tag: "LOC".to_string(),
5965 elements: vec![
5966 vec!["Z16".to_string()],
5967 vec!["DE000111222333".to_string()],
5968 ],
5969 mig_number: None,
5970 segment_number: None,
5971 }],
5972 child_groups: vec![],
5973 entry_mig_number: None,
5974 variant_mig_numbers: vec![],
5975 skipped_segments: vec![],
5976 skipped_positions: Vec::new(),
5977 }],
5978 }],
5979 entry_mig_number: None,
5980 variant_mig_numbers: vec![],
5981 skipped_segments: vec![],
5982 skipped_positions: Vec::new(),
5983 }],
5984 }],
5985 post_group_start: 2,
5986 inter_group_segments: std::collections::BTreeMap::new(),
5987 };
5988
5989 let mut proz_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5991 proz_fields.insert(
5992 "ide.1".to_string(),
5993 FieldMapping::Simple("vorgangId".to_string()),
5994 );
5995 let proz_def = MappingDefinition {
5996 meta: MappingMeta {
5997 entity: "Prozessdaten".to_string(),
5998 bo4e_type: "Prozessdaten".to_string(),
5999 source_group: "".to_string(), source_path: None,
6001 discriminator: None,
6002 repeat_on_tag: None,
6003 parent_field: None,
6004 target_list: None,
6005 order: None,
6006 },
6007 fields: proz_fields,
6008 complex_handlers: None,
6009 };
6010
6011 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
6012 malo_fields.insert(
6013 "loc.1".to_string(),
6014 FieldMapping::Simple("marktlokationsId".to_string()),
6015 );
6016 let malo_def = MappingDefinition {
6017 meta: MappingMeta {
6018 entity: "Marktlokation".to_string(),
6019 bo4e_type: "Marktlokation".to_string(),
6020 source_group: "SG5".to_string(), source_path: None,
6022 discriminator: None,
6023 repeat_on_tag: None,
6024 parent_field: None,
6025 target_list: None,
6026 order: None,
6027 },
6028 fields: malo_fields,
6029 complex_handlers: None,
6030 };
6031
6032 let tx_engine = MappingEngine::from_definitions(vec![proz_def, malo_def]);
6033
6034 let sg4 = &tree.groups[0]; let sg4_instance = &sg4.repetitions[0];
6037 let sub_tree = sg4_instance.as_assembled_tree();
6038
6039 let result = tx_engine.map_all_forward(&sub_tree);
6040
6041 assert_eq!(
6043 result["prozessdaten"]["vorgangId"].as_str().unwrap(),
6044 "TX001"
6045 );
6046
6047 assert_eq!(
6049 result["marktlokation"]["marktlokationsId"]
6050 .as_str()
6051 .unwrap(),
6052 "DE000111222333"
6053 );
6054 }
6055
6056 #[test]
6057 fn test_map_interchange_produces_full_hierarchy() {
6058 use mig_assembly::assembler::*;
6059
6060 let tree = AssembledTree {
6062 segments: vec![
6063 AssembledSegment {
6064 tag: "UNH".to_string(),
6065 elements: vec![vec!["001".to_string()]],
6066 mig_number: None,
6067 segment_number: None,
6068 },
6069 AssembledSegment {
6070 tag: "BGM".to_string(),
6071 elements: vec![vec!["E01".to_string()]],
6072 mig_number: None,
6073 segment_number: None,
6074 },
6075 ],
6076 groups: vec![
6077 AssembledGroup {
6078 group_id: "SG2".to_string(),
6079 repetitions: vec![AssembledGroupInstance {
6080 segments: vec![AssembledSegment {
6081 tag: "NAD".to_string(),
6082 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
6083 mig_number: None,
6084 segment_number: None,
6085 }],
6086 child_groups: vec![],
6087 entry_mig_number: None,
6088 variant_mig_numbers: vec![],
6089 skipped_segments: vec![],
6090 skipped_positions: Vec::new(),
6091 }],
6092 },
6093 AssembledGroup {
6094 group_id: "SG4".to_string(),
6095 repetitions: vec![
6096 AssembledGroupInstance {
6097 segments: vec![AssembledSegment {
6098 tag: "IDE".to_string(),
6099 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
6100 mig_number: None,
6101 segment_number: None,
6102 }],
6103 child_groups: vec![],
6104 entry_mig_number: None,
6105 variant_mig_numbers: vec![],
6106 skipped_segments: vec![],
6107 skipped_positions: Vec::new(),
6108 },
6109 AssembledGroupInstance {
6110 segments: vec![AssembledSegment {
6111 tag: "IDE".to_string(),
6112 elements: vec![vec!["24".to_string()], vec!["TX002".to_string()]],
6113 mig_number: None,
6114 segment_number: None,
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 },
6124 ],
6125 post_group_start: 2,
6126 inter_group_segments: std::collections::BTreeMap::new(),
6127 };
6128
6129 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
6131 msg_fields.insert(
6132 "nad.0".to_string(),
6133 FieldMapping::Simple("marktrolle".to_string()),
6134 );
6135 let msg_defs = vec![MappingDefinition {
6136 meta: MappingMeta {
6137 entity: "Marktteilnehmer".to_string(),
6138 bo4e_type: "Marktteilnehmer".to_string(),
6139 source_group: "SG2".to_string(),
6140 source_path: None,
6141 discriminator: None,
6142 repeat_on_tag: None,
6143 parent_field: None,
6144 target_list: None,
6145 order: None,
6146 },
6147 fields: msg_fields,
6148 complex_handlers: None,
6149 }];
6150
6151 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
6153 tx_fields.insert(
6154 "ide.1".to_string(),
6155 FieldMapping::Simple("vorgangId".to_string()),
6156 );
6157 let tx_defs = vec![MappingDefinition {
6158 meta: MappingMeta {
6159 entity: "Prozessdaten".to_string(),
6160 bo4e_type: "Prozessdaten".to_string(),
6161 source_group: "SG4".to_string(),
6162 source_path: None,
6163 discriminator: None,
6164 repeat_on_tag: None,
6165 parent_field: None,
6166 target_list: None,
6167 order: None,
6168 },
6169 fields: tx_fields,
6170 complex_handlers: None,
6171 }];
6172
6173 let msg_engine = MappingEngine::from_definitions(msg_defs);
6174 let tx_engine = MappingEngine::from_definitions(tx_defs);
6175
6176 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
6177
6178 assert!(result.stammdaten["marktteilnehmer"].is_object());
6180 assert_eq!(
6181 result.stammdaten["marktteilnehmer"]["marktrolle"]
6182 .as_str()
6183 .unwrap(),
6184 "MS"
6185 );
6186
6187 assert_eq!(result.transaktionen.len(), 2);
6189 assert_eq!(
6190 result.transaktionen[0].transaktionsdaten["vorgangId"]
6191 .as_str()
6192 .unwrap(),
6193 "TX001"
6194 );
6195 assert_eq!(
6196 result.transaktionen[1].transaktionsdaten["vorgangId"]
6197 .as_str()
6198 .unwrap(),
6199 "TX002"
6200 );
6201 }
6202
6203 #[test]
6204 fn test_map_reverse_with_segment_structure_pads_trailing() {
6205 let mut fields = IndexMap::new();
6207 fields.insert(
6208 "sts.0".to_string(),
6209 FieldMapping::Structured(StructuredFieldMapping {
6210 target: String::new(),
6211 transform: None,
6212 when: None,
6213 default: Some("7".to_string()),
6214 enum_map: None,
6215 code_list: None,
6216 also_code_list: None,
6217 when_filled: None,
6218 also_target: None,
6219 also_enum_map: None,
6220 }),
6221 );
6222 fields.insert(
6223 "sts.2".to_string(),
6224 FieldMapping::Simple("grund".to_string()),
6225 );
6226
6227 let def = make_def(fields);
6228
6229 let mut counts = std::collections::BTreeMap::new();
6231 counts.insert("STS".to_string(), 5usize);
6232 let ss = SegmentStructure {
6233 element_counts: counts,
6234 };
6235
6236 let engine = MappingEngine::from_definitions(vec![]).with_segment_structure(ss);
6237
6238 let bo4e = serde_json::json!({ "grund": "E01" });
6239
6240 let instance = engine.map_reverse(&bo4e, &def);
6241 let sts = &instance.segments[0];
6242 assert_eq!(sts.elements.len(), 5);
6245 assert_eq!(sts.elements[0], vec!["7"]);
6246 assert_eq!(sts.elements[1], vec![""]);
6247 assert_eq!(sts.elements[2], vec!["E01"]);
6248 assert_eq!(sts.elements[3], vec![""]);
6249 assert_eq!(sts.elements[4], vec![""]);
6250 }
6251
6252 #[test]
6253 fn test_resolve_child_relative_with_source_path() {
6254 let mut map: std::collections::HashMap<String, Vec<usize>> =
6255 std::collections::HashMap::new();
6256 map.insert("sg4.sg8_ze1".to_string(), vec![6]);
6257 map.insert("sg4.sg8_z98".to_string(), vec![0]);
6258
6259 assert_eq!(
6261 resolve_child_relative("SG8.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
6262 "SG8:6.SG10"
6263 );
6264
6265 assert_eq!(
6267 resolve_child_relative("SG8:3.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
6268 "SG8:3.SG10"
6269 );
6270
6271 assert_eq!(
6273 resolve_child_relative("SG8.SG10", Some("sg4.sg8_unknown.sg10"), &map, 0),
6274 "SG8.SG10"
6275 );
6276
6277 assert_eq!(
6279 resolve_child_relative("SG8.SG10", None, &map, 0),
6280 "SG8.SG10"
6281 );
6282
6283 assert_eq!(
6285 resolve_child_relative("SG8.SG9", Some("sg4.sg8_z98.sg9"), &map, 0),
6286 "SG8:0.SG9"
6287 );
6288
6289 map.insert("sg4.sg8_zf3".to_string(), vec![3, 4]);
6291 assert_eq!(
6292 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 0),
6293 "SG8:3.SG10"
6294 );
6295 assert_eq!(
6296 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 1),
6297 "SG8:4.SG10"
6298 );
6299 }
6300
6301 #[test]
6302 fn test_place_in_groups_returns_rep_index() {
6303 let mut groups: Vec<AssembledGroup> = Vec::new();
6304
6305 let instance = AssembledGroupInstance {
6307 segments: vec![],
6308 child_groups: vec![],
6309 entry_mig_number: None,
6310 variant_mig_numbers: vec![],
6311 skipped_segments: vec![],
6312 skipped_positions: Vec::new(),
6313 };
6314 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 0);
6315
6316 let instance = AssembledGroupInstance {
6318 segments: vec![],
6319 child_groups: vec![],
6320 entry_mig_number: None,
6321 variant_mig_numbers: vec![],
6322 skipped_segments: vec![],
6323 skipped_positions: Vec::new(),
6324 };
6325 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 1);
6326
6327 let instance = AssembledGroupInstance {
6329 segments: vec![],
6330 child_groups: vec![],
6331 entry_mig_number: None,
6332 variant_mig_numbers: vec![],
6333 skipped_segments: vec![],
6334 skipped_positions: Vec::new(),
6335 };
6336 assert_eq!(place_in_groups(&mut groups, "SG8:5", instance), 5);
6337 }
6338
6339 #[test]
6340 fn test_resolve_by_source_path() {
6341 use mig_assembly::assembler::*;
6342
6343 let tree = AssembledTree {
6345 segments: vec![],
6346 groups: vec![AssembledGroup {
6347 group_id: "SG4".to_string(),
6348 repetitions: vec![AssembledGroupInstance {
6349 segments: vec![],
6350 child_groups: vec![AssembledGroup {
6351 group_id: "SG8".to_string(),
6352 repetitions: vec![
6353 AssembledGroupInstance {
6354 segments: vec![AssembledSegment {
6355 tag: "SEQ".to_string(),
6356 elements: vec![vec!["Z98".to_string()]],
6357 mig_number: None,
6358 segment_number: None,
6359 }],
6360 child_groups: vec![AssembledGroup {
6361 group_id: "SG10".to_string(),
6362 repetitions: vec![AssembledGroupInstance {
6363 segments: vec![AssembledSegment {
6364 tag: "CCI".to_string(),
6365 elements: vec![vec![], vec![], vec!["ZB3".to_string()]],
6366 mig_number: None,
6367 segment_number: None,
6368 }],
6369 child_groups: vec![],
6370 entry_mig_number: None,
6371 variant_mig_numbers: vec![],
6372 skipped_segments: vec![],
6373 skipped_positions: Vec::new(),
6374 }],
6375 }],
6376 entry_mig_number: None,
6377 variant_mig_numbers: vec![],
6378 skipped_segments: vec![],
6379 skipped_positions: Vec::new(),
6380 },
6381 AssembledGroupInstance {
6382 segments: vec![AssembledSegment {
6383 tag: "SEQ".to_string(),
6384 elements: vec![vec!["ZD7".to_string()]],
6385 mig_number: None,
6386 segment_number: None,
6387 }],
6388 child_groups: vec![AssembledGroup {
6389 group_id: "SG10".to_string(),
6390 repetitions: vec![AssembledGroupInstance {
6391 segments: vec![AssembledSegment {
6392 tag: "CCI".to_string(),
6393 elements: vec![vec![], vec![], vec!["ZE6".to_string()]],
6394 mig_number: None,
6395 segment_number: None,
6396 }],
6397 child_groups: vec![],
6398 entry_mig_number: None,
6399 variant_mig_numbers: vec![],
6400 skipped_segments: vec![],
6401 skipped_positions: Vec::new(),
6402 }],
6403 }],
6404 entry_mig_number: None,
6405 variant_mig_numbers: vec![],
6406 skipped_segments: vec![],
6407 skipped_positions: Vec::new(),
6408 },
6409 ],
6410 }],
6411 entry_mig_number: None,
6412 variant_mig_numbers: vec![],
6413 skipped_segments: vec![],
6414 skipped_positions: Vec::new(),
6415 }],
6416 }],
6417 post_group_start: 0,
6418 inter_group_segments: std::collections::BTreeMap::new(),
6419 };
6420
6421 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_z98.sg10");
6423 assert!(inst.is_some());
6424 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
6425
6426 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zd7.sg10");
6428 assert!(inst.is_some());
6429 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZE6");
6430
6431 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zzz.sg10");
6433 assert!(inst.is_none());
6434
6435 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8.sg10");
6437 assert!(inst.is_some());
6438 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
6439 }
6440
6441 #[test]
6442 fn test_parse_source_path_part() {
6443 assert_eq!(parse_source_path_part("sg4"), ("sg4", None));
6444 assert_eq!(parse_source_path_part("sg8_z98"), ("sg8", Some("z98")));
6445 assert_eq!(parse_source_path_part("sg10"), ("sg10", None));
6446 assert_eq!(parse_source_path_part("sg12_z04"), ("sg12", Some("z04")));
6447 }
6448
6449 #[test]
6450 fn test_has_source_path_qualifiers() {
6451 assert!(has_source_path_qualifiers("sg4.sg8_z98.sg10"));
6452 assert!(has_source_path_qualifiers("sg4.sg8_ze1.sg9"));
6453 assert!(!has_source_path_qualifiers("sg4.sg6"));
6454 assert!(!has_source_path_qualifiers("sg4.sg8.sg10"));
6455 }
6456
6457 #[test]
6458 fn test_extract_all_from_instance_collects_all_qualifier_matches() {
6459 use mig_assembly::assembler::*;
6460
6461 let instance = AssembledGroupInstance {
6463 segments: vec![
6464 AssembledSegment {
6465 tag: "SEQ".to_string(),
6466 elements: vec![vec!["ZD6".to_string()]],
6467 mig_number: None,
6468 segment_number: None,
6469 },
6470 AssembledSegment {
6471 tag: "RFF".to_string(),
6472 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
6473 mig_number: None,
6474 segment_number: None,
6475 },
6476 AssembledSegment {
6477 tag: "RFF".to_string(),
6478 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
6479 mig_number: None,
6480 segment_number: None,
6481 },
6482 AssembledSegment {
6483 tag: "RFF".to_string(),
6484 elements: vec![vec!["Z34".to_string(), "REF_C".to_string()]],
6485 mig_number: None,
6486 segment_number: None,
6487 },
6488 AssembledSegment {
6489 tag: "RFF".to_string(),
6490 elements: vec![vec!["Z35".to_string(), "OTHER".to_string()]],
6491 mig_number: None,
6492 segment_number: None,
6493 },
6494 ],
6495 child_groups: vec![],
6496 entry_mig_number: None,
6497 variant_mig_numbers: vec![],
6498 skipped_segments: vec![],
6499 skipped_positions: Vec::new(),
6500 };
6501
6502 let all = MappingEngine::extract_all_from_instance(&instance, "rff[Z34,*].0.1");
6504 assert_eq!(all, vec!["REF_A", "REF_B", "REF_C"]);
6505
6506 let single = MappingEngine::extract_from_instance(&instance, "rff[Z34].0.1");
6508 assert_eq!(single, Some("REF_A".to_string()));
6509
6510 let second = MappingEngine::extract_from_instance(&instance, "rff[Z34,1].0.1");
6511 assert_eq!(second, Some("REF_B".to_string()));
6512 }
6513}