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}
41
42impl MappingEngine {
43 pub fn new_empty() -> Self {
45 Self {
46 definitions: Vec::new(),
47 segment_structure: None,
48 code_lookup: None,
49 transaction_group: None,
50 current_pid: None,
51 }
52 }
53
54 pub fn load(dir: &Path) -> Result<Self, MappingError> {
56 let mut definitions = Vec::new();
57
58 let mut entries: Vec<_> = std::fs::read_dir(dir)?.filter_map(|e| e.ok()).collect();
59 entries.sort_by_key(|e| e.file_name());
60
61 for entry in entries {
62 let path = entry.path();
63 if path.extension().map(|e| e == "toml").unwrap_or(false) {
64 let content = std::fs::read_to_string(&path)?;
65 let def = MappingDefinition::from_toml_str(&content).map_err(|message| {
66 MappingError::TomlParse {
67 file: path.display().to_string(),
68 message,
69 }
70 })?;
71 definitions.push(def);
72 }
73 }
74
75 definitions.sort_by_key(|d| d.meta.order.unwrap_or(u32::MAX));
81
82 Ok(Self {
83 definitions,
84 segment_structure: None,
85 code_lookup: None,
86 transaction_group: None,
87 current_pid: None,
88 })
89 }
90
91 pub fn load_split(
97 message_dir: &Path,
98 transaction_dir: &Path,
99 ) -> Result<(Self, Self), MappingError> {
100 let msg_engine = Self::load(message_dir)?;
101 let tx_engine = Self::load(transaction_dir)?;
102 Ok((msg_engine, tx_engine))
103 }
104
105 pub fn load_merged(dirs: &[&Path]) -> Result<Self, MappingError> {
110 let mut definitions = Vec::new();
111 for dir in dirs {
112 let engine = Self::load(dir)?;
113 definitions.extend(engine.definitions);
114 }
115 Ok(Self {
116 definitions,
117 segment_structure: None,
118 code_lookup: None,
119 transaction_group: None,
120 current_pid: None,
121 })
122 }
123
124 pub fn load_with_common(
133 common_dir: &Path,
134 pid_dir: &Path,
135 schema_index: &crate::pid_schema_index::PidSchemaIndex,
136 ) -> Result<Self, MappingError> {
137 let mut common_defs = Self::load(common_dir)?.definitions;
138
139 common_defs.retain(|d| {
141 d.meta
142 .source_path
143 .as_deref()
144 .map(|sp| schema_index.has_group(sp))
145 .unwrap_or(true)
146 });
147
148 let pid_defs = Self::load(pid_dir)?.definitions;
149
150 let normalize_sg = |sg: &str| -> String {
155 sg.split('.')
156 .map(|part| part.split(':').next().unwrap_or(part))
157 .collect::<Vec<_>>()
158 .join(".")
159 };
160 let pid_keys: HashSet<(String, Option<String>)> = pid_defs
161 .iter()
162 .flat_map(|d| {
163 let sg = normalize_sg(&d.meta.source_group);
164 let disc = d.meta.discriminator.clone();
165 let mut keys = vec![(sg.clone(), disc.clone())];
166 if let Some(ref disc_str) = disc {
168 if let Some(base) = disc_str.rsplit_once('#') {
169 if base.1.chars().all(|c| c.is_ascii_digit()) {
170 keys.push((sg, Some(base.0.to_string())));
171 }
172 }
173 }
174 keys
175 })
176 .collect();
177
178 common_defs.retain(|d| {
180 let key = (
181 normalize_sg(&d.meta.source_group),
182 d.meta.discriminator.clone(),
183 );
184 !pid_keys.contains(&key)
185 });
186
187 let mut definitions = common_defs;
189 definitions.extend(pid_defs);
190
191 Ok(Self {
192 definitions,
193 segment_structure: None,
194 code_lookup: None,
195 transaction_group: None,
196 current_pid: None,
197 })
198 }
199
200 pub fn load_common_only(
204 common_dir: &Path,
205 schema_index: &crate::pid_schema_index::PidSchemaIndex,
206 ) -> Result<Self, MappingError> {
207 let mut common_defs = Self::load(common_dir)?.definitions;
208
209 common_defs.retain(|d| {
211 d.meta
212 .source_path
213 .as_deref()
214 .map(|sp| schema_index.has_group(sp))
215 .unwrap_or(true)
216 });
217
218 Ok(Self {
219 definitions: common_defs,
220 segment_structure: None,
221 code_lookup: None,
222 transaction_group: None,
223 current_pid: None,
224 })
225 }
226
227 pub fn load_split_with_common(
232 message_dir: &Path,
233 common_dir: &Path,
234 transaction_dir: &Path,
235 schema_index: &crate::pid_schema_index::PidSchemaIndex,
236 ) -> Result<(Self, Self), MappingError> {
237 let msg_engine = Self::load(message_dir)?;
238 let tx_engine = Self::load_with_common(common_dir, transaction_dir, schema_index)?;
239 Ok((msg_engine, tx_engine))
240 }
241
242 pub fn from_definitions(definitions: Vec<MappingDefinition>) -> Self {
244 Self {
245 definitions,
246 segment_structure: None,
247 code_lookup: None,
248 transaction_group: None,
249 current_pid: None,
250 }
251 }
252
253 pub fn save_cached(&self, path: &Path) -> Result<(), MappingError> {
259 let encoded =
260 serde_json::to_vec(&self.definitions).map_err(|e| MappingError::CacheWrite {
261 path: path.display().to_string(),
262 message: e.to_string(),
263 })?;
264 if let Some(parent) = path.parent() {
265 std::fs::create_dir_all(parent)?;
266 }
267 std::fs::write(path, encoded)?;
268 Ok(())
269 }
270
271 pub fn load_cached_or_toml(cache_path: &Path, toml_dir: &Path) -> Result<Self, MappingError> {
276 if cache_path.exists() {
277 Self::load_cached(cache_path)
278 } else {
279 Self::load(toml_dir)
280 }
281 }
282
283 pub fn load_cached(path: &Path) -> Result<Self, MappingError> {
288 let bytes = std::fs::read(path)?;
289 let definitions: Vec<MappingDefinition> =
290 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
291 path: path.display().to_string(),
292 message: e.to_string(),
293 })?;
294 Ok(Self {
295 definitions,
296 segment_structure: None,
297 code_lookup: None,
298 transaction_group: None,
299 current_pid: None,
300 })
301 }
302
303 pub fn with_segment_structure(mut self, ss: SegmentStructure) -> Self {
308 self.segment_structure = Some(ss);
309 self
310 }
311
312 pub fn with_code_lookup(mut self, cl: crate::code_lookup::CodeLookup) -> Self {
317 self.code_lookup = Some(cl);
318 self
319 }
320
321 pub fn with_pid(mut self, pid: impl Into<String>) -> Self {
329 self.current_pid = Some(pid.into());
330 self
331 }
332
333 pub fn with_path_resolver(mut self, resolver: crate::path_resolver::PathResolver) -> Self {
339 for def in &mut self.definitions {
340 def.normalize_paths(&resolver);
341 }
342 self
343 }
344
345 pub fn with_transaction_group(mut self, tx: impl Into<String>) -> Self {
352 self.transaction_group = Some(tx.into());
353 self
354 }
355
356 pub fn extend_definitions(mut self, defs: impl IntoIterator<Item = MappingDefinition>) -> Self {
370 fn key(d: &MappingDefinition) -> (String, String, String, String, String) {
371 (
372 d.meta.entity.clone(),
373 d.meta.source_group.clone(),
374 d.meta.source_path.clone().unwrap_or_default(),
375 d.meta.discriminator.clone().unwrap_or_default(),
376 d.meta.parent_field.clone().unwrap_or_default(),
377 )
378 }
379 let mut seen: std::collections::HashSet<_> = self.definitions.iter().map(key).collect();
380 for def in defs {
381 if seen.insert(key(&def)) {
382 self.definitions.push(def);
383 }
384 }
385 self
386 }
387
388 pub fn definitions(&self) -> &[MappingDefinition] {
390 &self.definitions
391 }
392
393 pub fn definition_for_entity(&self, entity: &str) -> Option<&MappingDefinition> {
398 self.definitions
399 .iter()
400 .find(|d| d.meta.entity == entity && d.meta.parent_field.is_none())
401 }
402
403 pub fn extract_field(
412 &self,
413 tree: &AssembledTree,
414 group_path: &str,
415 path: &str,
416 repetition: usize,
417 ) -> Option<String> {
418 let instance = Self::resolve_group_instance(tree, group_path, repetition)?;
419 Self::extract_from_instance(instance, path)
420 }
421
422 pub fn resolve_group_instance<'a>(
431 tree: &'a AssembledTree,
432 group_path: &str,
433 repetition: usize,
434 ) -> Option<&'a AssembledGroupInstance> {
435 let parts: Vec<&str> = group_path.split('.').collect();
436
437 let (first_id, first_rep) = parse_group_spec(parts[0]);
438 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
439
440 if parts.len() == 1 {
441 let rep = first_rep.unwrap_or(repetition);
443 return first_group.repetitions.get(rep);
444 }
445
446 let mut current_instance = first_group.repetitions.get(first_rep.unwrap_or(0))?;
449
450 for (i, part) in parts[1..].iter().enumerate() {
451 let (group_id, explicit_rep) = parse_group_spec(part);
452 let child_group = current_instance
453 .child_groups
454 .iter()
455 .find(|g| g.group_id == group_id)?;
456
457 if i == parts.len() - 2 {
458 let rep = explicit_rep.unwrap_or(repetition);
460 return child_group.repetitions.get(rep);
461 }
462 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
464 }
465
466 None
467 }
468
469 pub fn resolve_by_source_path<'a>(
477 tree: &'a AssembledTree,
478 source_path: &str,
479 ) -> Option<&'a AssembledGroupInstance> {
480 let parts: Vec<&str> = source_path.split('.').collect();
481 if parts.is_empty() {
482 return None;
483 }
484
485 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
486 let first_group = tree
487 .groups
488 .iter()
489 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))?;
490
491 let mut current_instance = if let Some(q) = first_qualifier {
492 find_rep_by_entry_qualifier(&first_group.repetitions, q)?
493 } else {
494 first_group.repetitions.first()?
495 };
496
497 if parts.len() == 1 {
498 return Some(current_instance);
499 }
500
501 for part in &parts[1..] {
502 let (group_id, qualifier) = parse_source_path_part(part);
503 let child_group = current_instance
504 .child_groups
505 .iter()
506 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))?;
507
508 current_instance = if let Some(q) = qualifier {
509 find_rep_by_entry_qualifier(&child_group.repetitions, q)?
510 } else {
511 child_group.repetitions.first()?
512 };
513 }
514
515 Some(current_instance)
516 }
517
518 pub fn resolve_all_by_source_path<'a>(
526 tree: &'a AssembledTree,
527 source_path: &str,
528 ) -> Vec<&'a AssembledGroupInstance> {
529 let parts: Vec<&str> = source_path.split('.').collect();
530 if parts.is_empty() {
531 return vec![];
532 }
533
534 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
536 let first_group = match tree
537 .groups
538 .iter()
539 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
540 {
541 Some(g) => g,
542 None => return vec![],
543 };
544
545 let mut current_instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
546 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
547 } else {
548 first_group.repetitions.iter().collect()
549 };
550
551 for part in &parts[1..] {
554 let (group_id, qualifier) = parse_source_path_part(part);
555 let mut next_instances = Vec::new();
556
557 for instance in ¤t_instances {
558 if let Some(child_group) = instance
559 .child_groups
560 .iter()
561 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
562 {
563 if let Some(q) = qualifier {
564 next_instances.extend(find_all_reps_by_entry_qualifier(
565 &child_group.repetitions,
566 q,
567 ));
568 } else {
569 next_instances.extend(child_group.repetitions.iter());
570 }
571 }
572 }
573
574 current_instances = next_instances;
575 }
576
577 current_instances
578 }
579
580 fn compute_child_indices(
593 tree: &AssembledTree,
594 source_path: &str,
595 indexed: &[(usize, &AssembledGroupInstance)],
596 ) -> Vec<usize> {
597 let parts: Vec<&str> = source_path.split('.').collect();
598 if parts.len() < 2 {
599 return vec![];
600 }
601 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
603 let first_group = match tree
604 .groups
605 .iter()
606 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
607 {
608 Some(g) => g,
609 None => return vec![],
610 };
611 let parent_reps: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
612 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
613 } else {
614 first_group.repetitions.iter().collect()
615 };
616 let (child_id, _child_qualifier) = parse_source_path_part(parts[parts.len() - 1]);
618 let mut result = Vec::new();
619 for (_, inst) in indexed {
620 let mut found = false;
622 for parent in &parent_reps {
623 if let Some(child_group) = parent
624 .child_groups
625 .iter()
626 .find(|g| g.group_id.eq_ignore_ascii_case(child_id))
627 {
628 if let Some(pos) = child_group
629 .repetitions
630 .iter()
631 .position(|r| std::ptr::eq(r, *inst))
632 {
633 result.push(pos);
634 found = true;
635 break;
636 }
637 }
638 }
639 if !found {
640 result.push(usize::MAX); }
642 }
643 result
644 }
645
646 pub fn resolve_all_with_parent_indices<'a>(
648 tree: &'a AssembledTree,
649 source_path: &str,
650 ) -> Vec<(usize, &'a AssembledGroupInstance)> {
651 let parts: Vec<&str> = source_path.split('.').collect();
652 if parts.is_empty() {
653 return vec![];
654 }
655
656 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
658 let first_group = match tree
659 .groups
660 .iter()
661 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
662 {
663 Some(g) => g,
664 None => return vec![],
665 };
666
667 if parts.len() == 1 {
669 let instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
670 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
671 } else {
672 first_group.repetitions.iter().collect()
673 };
674 return instances.into_iter().map(|i| (0, i)).collect();
675 }
676
677 let first_reps: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = first_qualifier {
682 let matching = find_all_reps_by_entry_qualifier(&first_group.repetitions, q);
683 let mut result = Vec::new();
684 for m in matching {
685 let idx = first_group
686 .repetitions
687 .iter()
688 .position(|r| std::ptr::eq(r, m))
689 .unwrap_or(0);
690 result.push((idx, m));
691 }
692 result
693 } else {
694 first_group.repetitions.iter().enumerate().collect()
695 };
696
697 let mut current: Vec<(usize, &AssembledGroupInstance)> = first_reps;
698 let remaining = &parts[1..];
699
700 for (level, part) in remaining.iter().enumerate() {
701 let is_leaf = level == remaining.len() - 1;
702 let (group_id, qualifier) = parse_source_path_part(part);
703 let mut next: Vec<(usize, &AssembledGroupInstance)> = Vec::new();
704
705 for (prev_parent_idx, instance) in ¤t {
706 if let Some(child_group) = instance
707 .child_groups
708 .iter()
709 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
710 {
711 let matching: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = qualifier
712 {
713 let filtered =
714 find_all_reps_by_entry_qualifier(&child_group.repetitions, q);
715 filtered
716 .into_iter()
717 .map(|m| {
718 let idx = child_group
719 .repetitions
720 .iter()
721 .position(|r| std::ptr::eq(r, m))
722 .unwrap_or(0);
723 (idx, m)
724 })
725 .collect()
726 } else {
727 child_group.repetitions.iter().enumerate().collect()
728 };
729
730 for (rep_idx, child_rep) in matching {
731 if is_leaf {
732 next.push((*prev_parent_idx, child_rep));
734 } else {
735 next.push((rep_idx, child_rep));
737 }
738 }
739 }
740 }
741
742 current = next;
743 }
744
745 current
746 }
747
748 pub fn extract_from_instance(instance: &AssembledGroupInstance, path: &str) -> Option<String> {
754 let parts: Vec<&str> = path.split('.').collect();
755 if parts.is_empty() {
756 return None;
757 }
758
759 let (segment_tag, qualifier, occurrence) = parse_tag_qualifier(parts[0]);
762
763 let segment = if let Some(q) = qualifier {
764 instance
765 .segments
766 .iter()
767 .filter(|s| {
768 s.tag.eq_ignore_ascii_case(&segment_tag)
769 && s.elements
770 .first()
771 .and_then(|e| e.first())
772 .map(|v| v.as_str())
773 == Some(q)
774 })
775 .nth(occurrence)?
776 } else {
777 instance
778 .segments
779 .iter()
780 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
781 .nth(occurrence)?
782 };
783
784 Self::resolve_field_path(segment, &parts[1..])
785 }
786
787 pub fn extract_all_from_instance(instance: &AssembledGroupInstance, path: &str) -> Vec<String> {
793 let parts: Vec<&str> = path.split('.').collect();
794 if parts.is_empty() {
795 return vec![];
796 }
797
798 let (segment_tag, qualifier, _) = parse_tag_qualifier(parts[0]);
799
800 let matching_segments: Vec<&AssembledSegment> = if let Some(q) = qualifier {
801 instance
802 .segments
803 .iter()
804 .filter(|s| {
805 s.tag.eq_ignore_ascii_case(&segment_tag)
806 && s.elements
807 .first()
808 .and_then(|e| e.first())
809 .map(|v| v.as_str())
810 == Some(q)
811 })
812 .collect()
813 } else {
814 instance
815 .segments
816 .iter()
817 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
818 .collect()
819 };
820
821 matching_segments
822 .into_iter()
823 .filter_map(|seg| Self::resolve_field_path(seg, &parts[1..]))
824 .collect()
825 }
826
827 pub fn map_forward(
833 &self,
834 tree: &AssembledTree,
835 def: &MappingDefinition,
836 repetition: usize,
837 ) -> serde_json::Value {
838 self.map_forward_inner(tree, def, repetition, true)
839 }
840
841 fn map_forward_inner(
843 &self,
844 tree: &AssembledTree,
845 def: &MappingDefinition,
846 repetition: usize,
847 enrich_codes: bool,
848 ) -> serde_json::Value {
849 let mut result = serde_json::Map::new();
850
851 if def.meta.source_group.is_empty() {
856 let mut all_root_segs = tree.segments.clone();
857 for segs in tree.inter_group_segments.values() {
858 all_root_segs.extend(segs.iter().cloned());
859 }
860 let root_instance = AssembledGroupInstance {
861 segments: all_root_segs,
862 child_groups: vec![],
863 entry_mig_number: None,
864 variant_mig_numbers: vec![],
865 skipped_segments: Vec::new(),
866 skipped_positions: Vec::new(),
867 };
868 self.extract_fields_from_instance(&root_instance, def, &mut result, enrich_codes);
869 return serde_json::Value::Object(result);
870 }
871
872 let instance = if let Some(ref sp) = def.meta.source_path {
878 if has_source_path_qualifiers(sp) && !def.meta.source_group.contains(':') {
879 Self::resolve_by_source_path(tree, sp).or_else(|| {
880 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
881 })
882 } else {
883 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
884 }
885 } else {
886 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
887 };
888
889 if let Some(instance) = instance {
890 if let Some(ref tag) = def.meta.repeat_on_tag {
892 let matching: Vec<_> = instance
893 .segments
894 .iter()
895 .filter(|s| s.tag.eq_ignore_ascii_case(tag))
896 .collect();
897
898 if matching.len() > 1 {
899 let mut arr = Vec::new();
900 for seg in &matching {
901 let sub_instance = AssembledGroupInstance {
902 segments: vec![(*seg).clone()],
903 child_groups: vec![],
904 entry_mig_number: None,
905 variant_mig_numbers: vec![],
906 skipped_segments: Vec::new(),
907 skipped_positions: Vec::new(),
908 };
909 let mut elem_result = serde_json::Map::new();
910 self.extract_fields_from_instance(
911 &sub_instance,
912 def,
913 &mut elem_result,
914 enrich_codes,
915 );
916 if !elem_result.is_empty() {
917 arr.push(serde_json::Value::Object(elem_result));
918 }
919 }
920 if !arr.is_empty() {
921 return serde_json::Value::Array(arr);
922 }
923 }
924 }
925
926 self.extract_fields_from_instance(instance, def, &mut result, enrich_codes);
927 }
928
929 serde_json::Value::Object(result)
930 }
931
932 fn extract_fields_from_instance(
937 &self,
938 instance: &AssembledGroupInstance,
939 def: &MappingDefinition,
940 result: &mut serde_json::Map<String, serde_json::Value>,
941 enrich_codes: bool,
942 ) {
943 for (path, field_mapping) in &def.fields {
944 let (target, enum_map) = match field_mapping {
945 FieldMapping::Simple(t) => (t.as_str(), None),
946 FieldMapping::Structured(s) => (s.target.as_str(), s.enum_map.as_ref()),
947 FieldMapping::Nested(_) => continue,
948 };
949 if target.is_empty() {
950 continue;
951 }
952 if let Some(val) = Self::extract_from_instance(instance, path) {
953 if let FieldMapping::Structured(s) = field_mapping {
958 if let (Some(also), Some(also_map)) =
959 (s.also_target.as_deref(), s.also_enum_map.as_ref())
960 {
961 if let Some(also_val) = also_map.get(&val) {
962 set_nested_value(result, also, also_val.clone());
963 }
964 }
965 }
966
967 let mapped_val = if let Some(map) = enum_map {
968 map.get(&val).cloned().unwrap_or_else(|| val.clone())
969 } else {
970 val.clone()
971 };
972
973 if enrich_codes {
975 if let (Some(ref code_lookup), Some(ref source_path)) =
976 (&self.code_lookup, &def.meta.source_path)
977 {
978 let parts: Vec<&str> = path.split('.').collect();
979 let (seg_tag, path_qualifier, _occ) = parse_tag_qualifier(parts[0]);
980 let (element_idx, component_idx) =
981 Self::parse_element_component(&parts[1..]);
982 let disc_qualifier = Self::discriminator_qualifier_for_tag(def, &seg_tag);
983 let q = disc_qualifier.as_deref();
984
985 if let Some(codes) = code_lookup.enrichment_codes(
986 source_path,
987 &seg_tag,
988 path_qualifier,
989 q,
990 element_idx,
991 component_idx,
992 ) {
993 if let Some(ref pid) = self.current_pid {
998 if codes.len() == 1 && codes.contains_key(pid.as_str()) {
999 set_nested_value(result, target, mapped_val);
1000 continue;
1001 }
1002 }
1003
1004 let enrichment = codes.get(&val);
1008 let meaning = enrichment
1009 .map(|e| serde_json::Value::String(e.meaning.clone()))
1010 .unwrap_or(serde_json::Value::Null);
1011
1012 let mut obj = serde_json::Map::new();
1013 obj.insert("code".into(), serde_json::json!(mapped_val));
1014 obj.insert("meaning".into(), meaning);
1015 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
1016 obj.insert("enum".into(), serde_json::json!(enum_key));
1017 }
1018 let enriched = serde_json::Value::Object(obj);
1019 set_nested_value_json(result, target, enriched);
1020 continue;
1021 }
1022 }
1023 }
1024
1025 set_nested_value(result, target, mapped_val);
1026 }
1027 }
1028
1029 if !instance.child_groups.is_empty() {
1032 self.extract_nested_children(instance, def, result, enrich_codes);
1033 }
1034 }
1035
1036 fn extract_nested_children(
1041 &self,
1042 instance: &AssembledGroupInstance,
1043 def: &MappingDefinition,
1044 result: &mut serde_json::Map<String, serde_json::Value>,
1045 enrich_codes: bool,
1046 ) {
1047 for child in self
1048 .definitions
1049 .iter()
1050 .filter(|c| is_nested_child_of(c, def))
1051 {
1052 if nested_parent_qualifier(child).is_some_and(|q| !entry_qualifier_matches(instance, q))
1053 {
1054 continue;
1055 }
1056 let (leaf_id, leaf_qualifier) = nested_child_leaf(child);
1057 let Some(group) = instance
1058 .child_groups
1059 .iter()
1060 .find(|g| g.group_id.eq_ignore_ascii_case(&leaf_id))
1061 else {
1062 continue;
1063 };
1064 let reps: Vec<&AssembledGroupInstance> = match leaf_qualifier {
1065 Some(q) => find_all_reps_by_entry_qualifier(&group.repetitions, q),
1066 None => group.repetitions.iter().collect(),
1067 };
1068
1069 let mut items: Vec<serde_json::Value> = Vec::new();
1070 let mut push_item = |sub: &AssembledGroupInstance| {
1071 let mut obj = serde_json::Map::new();
1072 self.extract_fields_from_instance(sub, child, &mut obj, enrich_codes);
1073 if !obj.is_empty() {
1074 items.push(serde_json::Value::Object(obj));
1075 }
1076 };
1077 for rep in reps {
1078 let repeat_tag = child
1079 .meta
1080 .repeat_on_tag
1081 .as_deref()
1082 .filter(|tag| rep.segments.iter().any(|s| s.tag.eq_ignore_ascii_case(tag)));
1083 let Some(tag) = repeat_tag else {
1084 push_item(rep);
1085 continue;
1086 };
1087 let shared: Vec<AssembledSegment> = rep
1090 .segments
1091 .iter()
1092 .filter(|s| !s.tag.eq_ignore_ascii_case(tag))
1093 .cloned()
1094 .collect();
1095 for seg in rep
1096 .segments
1097 .iter()
1098 .filter(|s| s.tag.eq_ignore_ascii_case(tag))
1099 {
1100 let mut segments = shared.clone();
1101 segments.push(seg.clone());
1102 push_item(&AssembledGroupInstance {
1103 segments,
1104 child_groups: vec![],
1105 entry_mig_number: None,
1106 variant_mig_numbers: vec![],
1107 skipped_segments: Vec::new(),
1108 skipped_positions: Vec::new(),
1109 });
1110 }
1111 }
1112 if items.is_empty() {
1113 continue;
1114 }
1115 let field = child.meta.parent_field.as_deref().unwrap_or_default();
1116 match result.get_mut(field) {
1117 Some(serde_json::Value::Array(existing)) => existing.extend(items),
1118 _ => {
1119 result.insert(field.to_string(), serde_json::Value::Array(items));
1120 }
1121 }
1122 }
1123 }
1124
1125 fn reverse_nested_children(
1129 &self,
1130 bo4e_value: &serde_json::Value,
1131 def: &MappingDefinition,
1132 instance: &mut AssembledGroupInstance,
1133 ) {
1134 let mut handled_fields: Vec<&str> = Vec::new();
1135 for child in self
1136 .definitions
1137 .iter()
1138 .filter(|c| is_nested_child_of(c, def))
1139 {
1140 let field = child.meta.parent_field.as_deref().unwrap_or_default();
1141 if handled_fields.contains(&field) {
1142 continue;
1143 }
1144 if nested_parent_qualifier(child)
1145 .is_some_and(|q| !rebuilt_entry_qualifier_matches(instance, def, q))
1146 {
1147 continue;
1148 }
1149 let elements: Vec<&serde_json::Value> = match bo4e_value.get(field) {
1150 Some(serde_json::Value::Array(arr)) => arr.iter().collect(),
1151 Some(serde_json::Value::Null) | None => continue,
1152 Some(other) => vec![other],
1153 };
1154
1155 let mut reps: Vec<AssembledGroupInstance> = Vec::new();
1156 if let Some(tag) = child.meta.repeat_on_tag.as_deref() {
1157 let mut merged: Option<AssembledGroupInstance> = None;
1160 for element in elements {
1161 let sub = self.map_reverse_single(element, child);
1162 if sub.segments.is_empty() {
1163 continue;
1164 }
1165 match merged.as_mut() {
1166 None => merged = Some(sub),
1167 Some(m) => m.segments.extend(
1168 sub.segments
1169 .into_iter()
1170 .filter(|s| s.tag.eq_ignore_ascii_case(tag)),
1171 ),
1172 }
1173 }
1174 reps.extend(merged);
1175 } else {
1176 for element in elements {
1177 let mut sub = self.map_reverse_single(element, child);
1178 if sub.segments.is_empty() {
1179 continue;
1180 }
1181 self.reverse_nested_children(element, child, &mut sub);
1183 reps.push(sub);
1184 }
1185 }
1186 if reps.is_empty() {
1187 continue;
1188 }
1189 handled_fields.push(field);
1190
1191 let (leaf_id, _) = nested_child_leaf(child);
1192 match instance
1193 .child_groups
1194 .iter_mut()
1195 .find(|g| g.group_id.eq_ignore_ascii_case(&leaf_id))
1196 {
1197 Some(group) => group.repetitions.extend(reps),
1198 None => instance.child_groups.push(AssembledGroup {
1199 group_id: leaf_id,
1200 repetitions: reps,
1201 }),
1202 }
1203 }
1204 }
1205
1206 pub(crate) fn discriminator_qualifier(def: &MappingDefinition) -> Option<String> {
1213 def.meta
1214 .discriminator
1215 .as_deref()
1216 .and_then(|d| d.split_once('=').map(|(_, v)| v.to_string()))
1217 }
1218
1219 pub(crate) fn discriminator_qualifier_for_tag(
1224 def: &MappingDefinition,
1225 segment_tag: &str,
1226 ) -> Option<String> {
1227 let (lhs, value) = def.meta.discriminator.as_deref()?.split_once('=')?;
1228 let disc_tag = lhs.split('.').next().unwrap_or(lhs);
1229 disc_tag
1230 .eq_ignore_ascii_case(segment_tag)
1231 .then(|| value.to_string())
1232 }
1233
1234 pub fn map_forward_from_segments(
1240 &self,
1241 segments: &[OwnedSegment],
1242 def: &MappingDefinition,
1243 ) -> serde_json::Value {
1244 let assembled_segments: Vec<AssembledSegment> = segments
1245 .iter()
1246 .map(|s| AssembledSegment {
1247 tag: s.id.clone(),
1248 elements: s.elements.clone(),
1249 mig_number: None,
1250 segment_number: Some(s.segment_number),
1251 })
1252 .collect();
1253
1254 let instance = AssembledGroupInstance {
1255 segments: assembled_segments,
1256 child_groups: vec![],
1257 entry_mig_number: None,
1258 variant_mig_numbers: vec![],
1259 skipped_segments: Vec::new(),
1260 skipped_positions: Vec::new(),
1261 };
1262
1263 let mut result = serde_json::Map::new();
1264 self.extract_fields_from_instance(&instance, def, &mut result, true);
1265 serde_json::Value::Object(result)
1266 }
1267
1268 pub fn map_reverse(
1281 &self,
1282 bo4e_value: &serde_json::Value,
1283 def: &MappingDefinition,
1284 ) -> AssembledGroupInstance {
1285 if def.meta.repeat_on_tag.is_some() {
1287 if let Some(arr) = bo4e_value.as_array() {
1288 let mut all_segments = Vec::new();
1289 for elem in arr {
1290 let sub = self.map_reverse_single(elem, def);
1291 all_segments.extend(sub.segments);
1292 }
1293 return AssembledGroupInstance {
1294 segments: all_segments,
1295 child_groups: vec![],
1296 entry_mig_number: None,
1297 variant_mig_numbers: vec![],
1298 skipped_segments: Vec::new(),
1299 skipped_positions: Vec::new(),
1300 };
1301 }
1302 }
1303 let mut instance = self.map_reverse_single(bo4e_value, def);
1304 if def.meta.parent_field.is_none() && !instance.segments.is_empty() {
1305 self.reverse_nested_children(bo4e_value, def, &mut instance);
1306 }
1307 instance
1308 }
1309
1310 fn map_reverse_single(
1311 &self,
1312 bo4e_value: &serde_json::Value,
1313 def: &MappingDefinition,
1314 ) -> AssembledGroupInstance {
1315 let mut field_values: Vec<(String, String, usize, usize, String)> =
1318 Vec::with_capacity(def.fields.len());
1319
1320 let mut has_real_data = false;
1327 let mut has_data_fields = false;
1328 let mut seg_has_data_field: HashSet<String> = HashSet::new();
1331 let mut seg_has_real_data: HashSet<String> = HashSet::new();
1332 let mut injected_qualifiers: HashSet<String> = HashSet::new();
1333
1334 for (path, field_mapping) in &def.fields {
1335 let (target, default, enum_map, when_filled, also_target, also_enum_map) =
1336 match field_mapping {
1337 FieldMapping::Simple(t) => (t.as_str(), None, None, None, None, None),
1338 FieldMapping::Structured(s) => (
1339 s.target.as_str(),
1340 s.default.as_ref(),
1341 s.enum_map.as_ref(),
1342 s.when_filled.as_ref(),
1343 s.also_target.as_deref(),
1344 s.also_enum_map.as_ref(),
1345 ),
1346 FieldMapping::Nested(_) => continue,
1347 };
1348
1349 let parts: Vec<&str> = path.split('.').collect();
1350 if parts.len() < 2 {
1351 continue;
1352 }
1353
1354 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1355 let seg_key = parts[0].to_uppercase();
1358 let sub_path = &parts[1..];
1359
1360 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1362 let ci = if sub_path.len() > 1 {
1363 sub_path[1].parse::<usize>().unwrap_or(0)
1364 } else {
1365 0
1366 };
1367 (ei, ci)
1368 } else {
1369 match sub_path.len() {
1370 1 => (0, 0),
1371 2 => (1, 0),
1372 _ => continue,
1373 }
1374 };
1375
1376 let val = if target.is_empty() {
1378 match (default, when_filled) {
1379 (Some(d), Some(fields)) => {
1381 let any_filled = fields
1382 .iter()
1383 .any(|f| self.populate_field(bo4e_value, f).is_some());
1384 if any_filled {
1385 has_real_data = true;
1388 Some(d.clone())
1389 } else {
1390 None
1391 }
1392 }
1393 (Some(d), None) => Some(d.clone()),
1395 (None, _) => None,
1396 }
1397 } else {
1398 has_data_fields = true;
1399 seg_has_data_field.insert(seg_key.clone());
1400 let bo4e_val = self.populate_field(bo4e_value, target);
1401 if bo4e_val.is_some() {
1402 has_real_data = true;
1403 seg_has_real_data.insert(seg_key.clone());
1404 }
1405 let mapped_val = match (bo4e_val, enum_map) {
1407 (Some(v), Some(map)) => {
1408 let joint = match (also_target, also_enum_map) {
1415 (Some(also), Some(also_map)) => {
1416 self.populate_field(bo4e_value, also).and_then(|also_v| {
1417 map.iter()
1418 .find(|(code, bo4e_v)| {
1419 *bo4e_v == &v && also_map.get(*code) == Some(&also_v)
1420 })
1421 .map(|(code, _)| code.clone())
1422 })
1423 }
1424 _ => None,
1425 };
1426 joint
1427 .or_else(|| {
1428 map.iter()
1430 .find(|(_, bo4e_v)| *bo4e_v == &v)
1431 .map(|(edifact_k, _)| edifact_k.clone())
1432 })
1433 .or(Some(v))
1434 }
1435 (v, _) => v,
1436 };
1437 mapped_val.or_else(|| default.cloned())
1438 };
1439
1440 if let Some(val) = val {
1441 field_values.push((
1442 seg_key.clone(),
1443 seg_tag.clone(),
1444 element_idx,
1445 component_idx,
1446 val,
1447 ));
1448 }
1449
1450 if let Some(q) = qualifier {
1452 if injected_qualifiers.insert(seg_key.clone()) {
1453 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1454 }
1455 }
1456 }
1457
1458 field_values.retain(|(seg_key, _, _, _, _)| {
1466 if !seg_key.contains('[') {
1467 return true; }
1469 !seg_has_data_field.contains(seg_key) || seg_has_real_data.contains(seg_key)
1470 });
1471
1472 if has_data_fields && !has_real_data {
1477 return AssembledGroupInstance {
1478 segments: vec![],
1479 child_groups: vec![],
1480 entry_mig_number: None,
1481 variant_mig_numbers: vec![],
1482 skipped_segments: Vec::new(),
1483 skipped_positions: Vec::new(),
1484 };
1485 }
1486
1487 let mut segments: Vec<AssembledSegment> = Vec::with_capacity(field_values.len());
1490 let mut seen_keys: HashMap<String, usize> = HashMap::new();
1491
1492 for (seg_key, seg_tag, element_idx, component_idx, val) in &field_values {
1493 let seg = if let Some(&pos) = seen_keys.get(seg_key) {
1494 &mut segments[pos]
1495 } else {
1496 let pos = segments.len();
1497 seen_keys.insert(seg_key.clone(), pos);
1498 segments.push(AssembledSegment {
1499 tag: seg_tag.clone(),
1500 elements: vec![],
1501 mig_number: None,
1502 segment_number: None,
1503 });
1504 &mut segments[pos]
1505 };
1506
1507 while seg.elements.len() <= *element_idx {
1508 seg.elements.push(vec![]);
1509 }
1510 while seg.elements[*element_idx].len() <= *component_idx {
1511 seg.elements[*element_idx].push(String::new());
1512 }
1513 seg.elements[*element_idx][*component_idx] = val.clone();
1514 }
1515
1516 for seg in &mut segments {
1519 let last_populated = seg.elements.iter().rposition(|e| !e.is_empty());
1520 if let Some(last_idx) = last_populated {
1521 for i in 0..last_idx {
1522 if seg.elements[i].is_empty() {
1523 seg.elements[i] = vec![String::new()];
1524 }
1525 }
1526 }
1527 }
1528
1529 if let Some(ref ss) = self.segment_structure {
1531 for seg in &mut segments {
1532 if let Some(expected) = ss.element_count(&seg.tag) {
1533 while seg.elements.len() < expected {
1534 seg.elements.push(vec![String::new()]);
1535 }
1536 }
1537 }
1538 }
1539
1540 AssembledGroupInstance {
1541 segments,
1542 child_groups: vec![],
1543 entry_mig_number: None,
1544 variant_mig_numbers: vec![],
1545 skipped_segments: Vec::new(),
1546 skipped_positions: Vec::new(),
1547 }
1548 }
1549
1550 fn resolve_field_path(segment: &AssembledSegment, path: &[&str]) -> Option<String> {
1563 if path.is_empty() {
1564 return None;
1565 }
1566
1567 if let Ok(element_idx) = path[0].parse::<usize>() {
1569 let component_idx = if path.len() > 1 {
1570 path[1].parse::<usize>().unwrap_or(0)
1571 } else {
1572 0
1573 };
1574 return segment
1575 .elements
1576 .get(element_idx)?
1577 .get(component_idx)
1578 .filter(|v| !v.is_empty())
1579 .cloned();
1580 }
1581
1582 None
1588 }
1589
1590 pub(crate) fn parse_element_component(parts: &[&str]) -> (usize, usize) {
1593 if parts.is_empty() {
1594 return (0, 0);
1595 }
1596 let element_idx = parts[0].parse::<usize>().unwrap_or(0);
1597 let component_idx = if parts.len() > 1 {
1598 parts[1].parse::<usize>().unwrap_or(0)
1599 } else {
1600 0
1601 };
1602 (element_idx, component_idx)
1603 }
1604
1605 pub fn populate_field(
1608 &self,
1609 bo4e_value: &serde_json::Value,
1610 target_field: &str,
1611 ) -> Option<String> {
1612 let mut current = bo4e_value;
1613 for part in target_field.split('.') {
1614 current = current.get(part)?;
1615 }
1616 if let Some(code) = current.get("code").and_then(|v| v.as_str()) {
1618 return Some(code.to_string());
1619 }
1620 current.as_str().map(|s| s.to_string())
1621 }
1622
1623 pub fn build_segment_from_bo4e(
1625 &self,
1626 bo4e_value: &serde_json::Value,
1627 segment_tag: &str,
1628 target_field: &str,
1629 ) -> AssembledSegment {
1630 let value = self.populate_field(bo4e_value, target_field);
1631 let elements = if let Some(val) = value {
1632 vec![vec![val]]
1633 } else {
1634 vec![]
1635 };
1636 AssembledSegment {
1637 tag: segment_tag.to_uppercase(),
1638 elements,
1639 mig_number: None,
1640 segment_number: None,
1641 }
1642 }
1643
1644 pub fn resolve_repetition(
1653 tree: &AssembledTree,
1654 group_path: &str,
1655 discriminator: &str,
1656 ) -> Option<usize> {
1657 let (spec, expected) = discriminator.split_once('=')?;
1658 let parts: Vec<&str> = spec.split('.').collect();
1659 if parts.len() != 3 {
1660 return None;
1661 }
1662 let tag = parts[0];
1663 let element_idx: usize = parts[1].parse().ok()?;
1664 let component_idx: usize = parts[2].parse().ok()?;
1665
1666 let path_parts: Vec<&str> = group_path.split('.').collect();
1668
1669 let leaf_group = if path_parts.len() == 1 {
1670 let (group_id, _) = parse_group_spec(path_parts[0]);
1671 tree.groups.iter().find(|g| g.group_id == group_id)?
1672 } else {
1673 let parent_parts = &path_parts[..path_parts.len() - 1];
1675 let mut current_instance = {
1676 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1677 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
1678 first_group.repetitions.get(first_rep.unwrap_or(0))?
1679 };
1680 for part in &parent_parts[1..] {
1681 let (group_id, explicit_rep) = parse_group_spec(part);
1682 let child_group = current_instance
1683 .child_groups
1684 .iter()
1685 .find(|g| g.group_id == group_id)?;
1686 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
1687 }
1688 let (leaf_id, _) = parse_group_spec(path_parts.last()?);
1689 current_instance
1690 .child_groups
1691 .iter()
1692 .find(|g| g.group_id == leaf_id)?
1693 };
1694
1695 let expected_values: Vec<&str> = expected.split('|').collect();
1697 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1698 let matches = instance.segments.iter().any(|s| {
1699 s.tag.eq_ignore_ascii_case(tag)
1700 && s.elements
1701 .get(element_idx)
1702 .and_then(|e| e.get(component_idx))
1703 .map(|v| expected_values.iter().any(|ev| v == ev))
1704 .unwrap_or(false)
1705 });
1706 if matches {
1707 return Some(rep_idx);
1708 }
1709 }
1710
1711 None
1712 }
1713
1714 pub fn resolve_all_repetitions(
1719 tree: &AssembledTree,
1720 group_path: &str,
1721 discriminator: &str,
1722 ) -> Vec<usize> {
1723 let Some((spec, expected)) = discriminator.split_once('=') else {
1724 return Vec::new();
1725 };
1726 let parts: Vec<&str> = spec.split('.').collect();
1727 if parts.len() != 3 {
1728 return Vec::new();
1729 }
1730 let tag = parts[0];
1731 let element_idx: usize = match parts[1].parse() {
1732 Ok(v) => v,
1733 Err(_) => return Vec::new(),
1734 };
1735 let component_idx: usize = match parts[2].parse() {
1736 Ok(v) => v,
1737 Err(_) => return Vec::new(),
1738 };
1739
1740 let path_parts: Vec<&str> = group_path.split('.').collect();
1742
1743 let leaf_group = if path_parts.len() == 1 {
1744 let (group_id, _) = parse_group_spec(path_parts[0]);
1745 match tree.groups.iter().find(|g| g.group_id == group_id) {
1746 Some(g) => g,
1747 None => return Vec::new(),
1748 }
1749 } else {
1750 let parent_parts = &path_parts[..path_parts.len() - 1];
1751 let mut current_instance = {
1752 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1753 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
1754 Some(g) => g,
1755 None => return Vec::new(),
1756 };
1757 match first_group.repetitions.get(first_rep.unwrap_or(0)) {
1758 Some(i) => i,
1759 None => return Vec::new(),
1760 }
1761 };
1762 for part in &parent_parts[1..] {
1763 let (group_id, explicit_rep) = parse_group_spec(part);
1764 let child_group = match current_instance
1765 .child_groups
1766 .iter()
1767 .find(|g| g.group_id == group_id)
1768 {
1769 Some(g) => g,
1770 None => return Vec::new(),
1771 };
1772 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
1773 Some(i) => i,
1774 None => return Vec::new(),
1775 };
1776 }
1777 let (leaf_id, _) = match path_parts.last() {
1778 Some(p) => parse_group_spec(p),
1779 None => return Vec::new(),
1780 };
1781 match current_instance
1782 .child_groups
1783 .iter()
1784 .find(|g| g.group_id == leaf_id)
1785 {
1786 Some(g) => g,
1787 None => return Vec::new(),
1788 }
1789 };
1790
1791 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
1793
1794 let expected_values: Vec<&str> = expected_raw.split('|').collect();
1796 let mut result = Vec::new();
1797 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1798 let matches = instance.segments.iter().any(|s| {
1799 s.tag.eq_ignore_ascii_case(tag)
1800 && s.elements
1801 .get(element_idx)
1802 .and_then(|e| e.get(component_idx))
1803 .map(|v| expected_values.iter().any(|ev| v == ev))
1804 .unwrap_or(false)
1805 });
1806 if matches {
1807 result.push(rep_idx);
1808 }
1809 }
1810
1811 if let Some(occ) = occurrence {
1813 result.into_iter().nth(occ).into_iter().collect()
1814 } else {
1815 result
1816 }
1817 }
1818
1819 pub fn map_all_forward(&self, tree: &AssembledTree) -> serde_json::Value {
1841 self.map_all_forward_inner(tree, true).0
1842 }
1843
1844 pub fn map_all_forward_enriched(
1848 &self,
1849 tree: &AssembledTree,
1850 enrich_codes: bool,
1851 ) -> serde_json::Value {
1852 self.map_all_forward_inner(tree, enrich_codes).0
1853 }
1854
1855 fn map_all_forward_inner(
1861 &self,
1862 tree: &AssembledTree,
1863 enrich_codes: bool,
1864 ) -> (
1865 serde_json::Value,
1866 std::collections::HashMap<String, Vec<usize>>,
1867 ) {
1868 self.map_all_forward_inner_with_tx(tree, enrich_codes, self.transaction_group.as_deref())
1869 }
1870
1871 fn map_all_forward_inner_with_tx(
1875 &self,
1876 tree: &AssembledTree,
1877 enrich_codes: bool,
1878 tx_group_override: Option<&str>,
1879 ) -> (
1880 serde_json::Value,
1881 std::collections::HashMap<String, Vec<usize>>,
1882 ) {
1883 let mut result = serde_json::Map::new();
1884 let mut nesting_info: std::collections::HashMap<String, Vec<usize>> =
1885 std::collections::HashMap::new();
1886
1887 for def in &self.definitions {
1888 if def.meta.parent_field.is_some() {
1891 continue;
1892 }
1893 let entity = &def.meta.entity;
1894
1895 let bo4e = if let Some(ref disc) = def.meta.discriminator {
1896 let use_source_path = def
1901 .meta
1902 .source_path
1903 .as_ref()
1904 .is_some_and(|sp| has_source_path_qualifiers(sp));
1905 if use_source_path {
1906 let sp = def.meta.source_path.as_deref().unwrap();
1908 let all_instances = Self::resolve_all_by_source_path(tree, sp);
1909 let instances: Vec<_> = if let Some(matcher) = DiscriminatorMatcher::parse(disc)
1911 {
1912 matcher.filter_instances(all_instances)
1913 } else {
1914 all_instances
1915 };
1916 let extract = |instance: &AssembledGroupInstance| {
1917 let mut r = serde_json::Map::new();
1918 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
1919 serde_json::Value::Object(r)
1920 };
1921 match instances.len() {
1922 0 => None,
1923 1 => Some(extract(instances[0])),
1924 _ => Some(serde_json::Value::Array(
1925 instances.iter().map(|i| extract(i)).collect(),
1926 )),
1927 }
1928 } else {
1929 let reps = Self::resolve_all_repetitions(tree, &def.meta.source_group, disc);
1930 match reps.len() {
1931 0 => None,
1932 1 => Some(self.map_forward_inner(tree, def, reps[0], enrich_codes)),
1933 _ => Some(serde_json::Value::Array(
1934 reps.iter()
1935 .map(|&rep| self.map_forward_inner(tree, def, rep, enrich_codes))
1936 .collect(),
1937 )),
1938 }
1939 }
1940 } else if def.meta.source_group.is_empty() {
1941 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
1943 } else if def.meta.source_path.as_ref().is_some_and(|sp| {
1944 has_source_path_qualifiers(sp) || def.meta.source_group.contains('.')
1945 }) {
1946 let sp = def.meta.source_path.as_deref().unwrap();
1951 let mut indexed = Self::resolve_all_with_parent_indices(tree, sp);
1952
1953 if let Some(last_part) = sp.rsplit('.').next() {
1958 if !last_part.contains('_') {
1959 let base_prefix = if let Some(parent) = sp.rsplit_once('.') {
1963 format!("{}.", parent.0)
1964 } else {
1965 String::new()
1966 };
1967 let sibling_qualifiers: Vec<String> = self
1968 .definitions
1969 .iter()
1970 .filter_map(|d| d.meta.source_path.as_deref())
1971 .filter(|other_sp| {
1972 *other_sp != sp
1973 && other_sp.starts_with(&base_prefix)
1974 && other_sp.split('.').count() == sp.split('.').count()
1975 })
1976 .filter_map(|other_sp| {
1977 let other_last = other_sp.rsplit('.').next()?;
1978 let (base, q) = other_last.split_once('_')?;
1981 if base == last_part {
1982 Some(q.to_string())
1983 } else {
1984 None
1985 }
1986 })
1987 .collect();
1988
1989 if !sibling_qualifiers.is_empty() {
1990 indexed.retain(|(_, inst)| {
1991 let entry_qual = inst
1992 .segments
1993 .first()
1994 .and_then(|seg| seg.elements.first())
1995 .and_then(|el| el.first())
1996 .map(|v| v.to_lowercase());
1997 !entry_qual.is_some_and(|q| {
2000 sibling_qualifiers.iter().any(|sq| {
2001 sq.split('_').any(|part| part.eq_ignore_ascii_case(&q))
2002 })
2003 })
2004 });
2005 }
2006 }
2007 }
2008 let extract = |instance: &AssembledGroupInstance| {
2009 let mut r = serde_json::Map::new();
2010 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
2011 serde_json::Value::Object(r)
2012 };
2013 if def.meta.source_group.contains('.') && !indexed.is_empty() {
2018 if let Some(sp) = &def.meta.source_path {
2019 let parent_indices: Vec<usize> =
2020 indexed.iter().map(|(idx, _)| *idx).collect();
2021 nesting_info.entry(sp.clone()).or_insert(parent_indices);
2022
2023 let child_key = format!("{sp}#child");
2026 if let std::collections::hash_map::Entry::Vacant(e) =
2027 nesting_info.entry(child_key)
2028 {
2029 let child_indices: Vec<usize> =
2030 Self::compute_child_indices(tree, sp, &indexed);
2031 if !child_indices.is_empty() {
2032 e.insert(child_indices);
2033 }
2034 }
2035 }
2036 }
2037 match indexed.len() {
2038 0 => None,
2039 1 => Some(extract(indexed[0].1)),
2040 _ => Some(serde_json::Value::Array(
2041 indexed.iter().map(|(_, i)| extract(i)).collect(),
2042 )),
2043 }
2044 } else {
2045 let num_reps = Self::count_repetitions(tree, &def.meta.source_group);
2046 if num_reps <= 1 {
2047 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2048 } else {
2049 let mut items = Vec::with_capacity(num_reps);
2051 for rep in 0..num_reps {
2052 items.push(self.map_forward_inner(tree, def, rep, enrich_codes));
2053 }
2054 Some(serde_json::Value::Array(items))
2055 }
2056 };
2057
2058 if let Some(bo4e) = bo4e {
2059 let key = to_camel_case(entity);
2060 match def.meta.target_list.as_deref() {
2061 Some(list_field) => append_to_list_field(&mut result, &key, list_field, bo4e),
2062 None => deep_merge_insert(&mut result, &key, bo4e),
2063 }
2064 }
2065 }
2066
2067 nest_child_entities_in_result(
2072 &mut result,
2073 &self.definitions,
2074 &nesting_info,
2075 tx_group_override,
2076 );
2077
2078 (serde_json::Value::Object(result), nesting_info)
2079 }
2080
2081 pub fn map_all_reverse(
2090 &self,
2091 entities: &serde_json::Value,
2092 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2093 ) -> AssembledTree {
2094 self.map_all_reverse_with_mig(entities, nesting_info, None)
2095 }
2096
2097 pub fn map_all_reverse_with_mig(
2101 &self,
2102 entities: &serde_json::Value,
2103 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2104 mig: Option<&MigSchema>,
2105 ) -> AssembledTree {
2106 let mut root_segments: Vec<AssembledSegment> = Vec::new();
2107 let mut groups: Vec<AssembledGroup> = Vec::new();
2108 let mut inferred_nesting: std::collections::HashMap<String, Vec<usize>> =
2111 std::collections::HashMap::new();
2112
2113 for def in &self.definitions {
2114 if def.meta.parent_field.is_some() {
2117 continue;
2118 }
2119 let entity_key = to_camel_case(&def.meta.entity);
2120
2121 let _extracted: Option<serde_json::Value>;
2124 let entity_value = if let Some(list_field) = def.meta.target_list.as_deref() {
2125 match entities.get(&entity_key).and_then(|e| e.get(list_field)) {
2131 Some(v) if v.is_array() => {
2132 _extracted = None;
2133 v
2134 }
2135 _ => continue,
2136 }
2137 } else if let Some(v) = entities.get(&entity_key) {
2138 _extracted = None;
2139 v
2140 } else if def.meta.source_group.contains('.') {
2141 match extract_child_from_parent_with_indices(entities, &self.definitions, def) {
2143 Some((v, parent_indices)) => {
2144 if let Some(sp) = def.meta.source_path.as_deref() {
2146 inferred_nesting
2147 .entry(sp.to_string())
2148 .or_insert(parent_indices);
2149 }
2150 _extracted = Some(v);
2151 _extracted.as_ref().unwrap()
2152 }
2153 None => continue,
2154 }
2155 } else {
2156 continue;
2157 };
2158
2159 let unwrapped: Option<serde_json::Value>;
2167 let entity_value = if entity_value.is_object() && !entity_value.is_array() {
2168 if let Some(disc_value) = def
2169 .meta
2170 .discriminator
2171 .as_deref()
2172 .and_then(|d| d.split_once('='))
2173 .map(|(_, v)| v)
2174 {
2175 if let Some(inner) = entity_value.get(disc_value) {
2177 let mut injected = inner.clone();
2178 if let Some(qualifier_field) =
2181 find_qualifier_companion_field(&self.definitions, &def.meta.entity)
2182 {
2183 if let Some(obj) = injected.as_object_mut() {
2184 let entry = obj
2185 .entry(qualifier_field)
2186 .or_insert(serde_json::Value::Null);
2187 if entry.is_null() {
2188 *entry = serde_json::Value::String(disc_value.to_string());
2189 }
2190 }
2191 }
2192 unwrapped = Some(injected);
2193 unwrapped.as_ref().unwrap()
2194 } else {
2195 entity_value
2196 }
2197 } else if is_map_keyed_object(entity_value) {
2198 let map = entity_value.as_object().unwrap();
2203 let arr: Vec<serde_json::Value> = map
2204 .iter()
2205 .map(|(key, val)| {
2206 let mut item = val.clone();
2207 if let Some(obj) = item.as_object_mut() {
2210 if let Some(qualifier_field) = find_qualifier_companion_field(
2211 &self.definitions,
2212 &def.meta.entity,
2213 ) {
2214 let entry = obj
2215 .entry(qualifier_field)
2216 .or_insert(serde_json::Value::Null);
2217 if entry.is_null() {
2218 *entry = serde_json::Value::String(key.clone());
2219 }
2220 }
2221 }
2222 item
2223 })
2224 .collect();
2225 unwrapped = Some(serde_json::Value::Array(arr));
2226 unwrapped.as_ref().unwrap()
2227 } else {
2228 entity_value
2229 }
2230 } else {
2231 entity_value
2232 };
2233
2234 let leaf_group = def
2236 .meta
2237 .source_group
2238 .rsplit('.')
2239 .next()
2240 .unwrap_or(&def.meta.source_group);
2241
2242 if def.meta.source_group.is_empty() {
2243 let instance = self.map_reverse(entity_value, def);
2245 root_segments.extend(instance.segments);
2246 } else if entity_value.is_array() {
2247 let arr = entity_value.as_array().unwrap();
2249 let reps: Vec<_> = arr.iter().map(|item| self.map_reverse(item, def)).collect();
2250
2251 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2253 existing.repetitions.extend(reps);
2254 } else {
2255 groups.push(AssembledGroup {
2256 group_id: leaf_group.to_string(),
2257 repetitions: reps,
2258 });
2259 }
2260 } else {
2261 let instance = self.map_reverse(entity_value, def);
2263
2264 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2265 existing.repetitions.push(instance);
2266 } else {
2267 groups.push(AssembledGroup {
2268 group_id: leaf_group.to_string(),
2269 repetitions: vec![instance],
2270 });
2271 }
2272 }
2273 }
2274
2275 let nested_specs: Vec<(String, String)> = self
2281 .definitions
2282 .iter()
2283 .filter(|def| def.meta.parent_field.is_none())
2284 .filter_map(|def| {
2285 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
2286 if parts.len() > 1 {
2287 Some((parts[0].to_string(), parts[parts.len() - 1].to_string()))
2288 } else {
2289 None
2290 }
2291 })
2292 .collect();
2293 for (parent_id, child_id) in &nested_specs {
2294 let has_parent = groups.iter().any(|g| g.group_id == *parent_id);
2296 let has_child = groups.iter().any(|g| g.group_id == *child_id);
2297 if has_parent && has_child {
2298 let child_idx = groups.iter().position(|g| g.group_id == *child_id).unwrap();
2299 let child_group = groups.remove(child_idx);
2300 let parent = groups
2301 .iter_mut()
2302 .find(|g| g.group_id == *parent_id)
2303 .unwrap();
2304 let child_source_path = self
2308 .definitions
2309 .iter()
2310 .find(|d| {
2311 let parts: Vec<&str> = d.meta.source_group.split('.').collect();
2312 d.meta.parent_field.is_none()
2313 && parts.len() > 1
2314 && parts[parts.len() - 1] == *child_id
2315 })
2316 .and_then(|d| d.meta.source_path.as_deref());
2317 let distribution = child_source_path.and_then(|key| {
2318 nesting_info
2319 .and_then(|ni| ni.get(key))
2320 .or_else(|| inferred_nesting.get(key))
2321 });
2322 let unlinked_target = mig
2328 .and_then(|m| {
2329 mig_assembly::repetition_order::preferred_parent_repetition(
2330 parent,
2331 &m.segment_groups,
2332 child_id,
2333 )
2334 })
2335 .unwrap_or(0);
2336 for (i, child_rep) in child_group.repetitions.into_iter().enumerate() {
2337 let target_idx = distribution
2338 .and_then(|dist| dist.get(i))
2339 .copied()
2340 .unwrap_or(unlinked_target);
2341
2342 if let Some(target_rep) = parent.repetitions.get_mut(target_idx) {
2343 if let Some(existing) = target_rep
2344 .child_groups
2345 .iter_mut()
2346 .find(|g| g.group_id == *child_id)
2347 {
2348 existing.repetitions.push(child_rep);
2349 } else {
2350 target_rep.child_groups.push(AssembledGroup {
2351 group_id: child_id.clone(),
2352 repetitions: vec![child_rep],
2353 });
2354 }
2355 }
2356 }
2357 }
2358 }
2359
2360 let post_group_start = root_segments.len();
2361 AssembledTree {
2362 segments: root_segments,
2363 groups,
2364 post_group_start,
2365 inter_group_segments: std::collections::BTreeMap::new(),
2366 }
2367 }
2368
2369 fn count_repetitions(tree: &AssembledTree, group_path: &str) -> usize {
2371 let parts: Vec<&str> = group_path.split('.').collect();
2372
2373 let (first_id, first_rep) = parse_group_spec(parts[0]);
2374 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2375 Some(g) => g,
2376 None => return 0,
2377 };
2378
2379 if parts.len() == 1 {
2380 return first_group.repetitions.len();
2381 }
2382
2383 let mut current_instance = match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2385 Some(i) => i,
2386 None => return 0,
2387 };
2388
2389 for (i, part) in parts[1..].iter().enumerate() {
2390 let (group_id, explicit_rep) = parse_group_spec(part);
2391 let child_group = match current_instance
2392 .child_groups
2393 .iter()
2394 .find(|g| g.group_id == group_id)
2395 {
2396 Some(g) => g,
2397 None => return 0,
2398 };
2399
2400 if i == parts.len() - 2 {
2401 return child_group.repetitions.len();
2403 }
2404 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2405 Some(i) => i,
2406 None => return 0,
2407 };
2408 }
2409
2410 0
2411 }
2412
2413 pub fn translate_edifact_to_bo4e(
2421 msg_engine: &MappingEngine,
2422 tx_engine: &MappingEngine,
2423 tree: &AssembledTree,
2424 transaction_group: &str,
2425 ) -> crate::model::MappedMessage {
2426 Self::map_interchange_inner(msg_engine, tx_engine, tree, transaction_group, false)
2427 }
2428
2429 pub fn enrich_bo4e_types(
2443 msg_engine: &MappingEngine,
2444 tx_engine: &MappingEngine,
2445 mapped: &mut crate::model::MappedMessage,
2446 ) {
2447 msg_engine.enrich_entities(&mut mapped.stammdaten);
2448 for tx in &mut mapped.transaktionen {
2449 tx_engine.enrich_entities(&mut tx.stammdaten);
2450 }
2451
2452 msg_engine.enrich_named_entity(
2456 &mut mapped.nachricht_meta,
2457 crate::model::MSG_METADATA_ENTITY,
2458 );
2459 for tx in &mut mapped.transaktionen {
2460 tx_engine
2461 .enrich_named_entity(&mut tx.transaktionsdaten, crate::model::TX_METADATA_ENTITY);
2462 }
2463 }
2464
2465 fn enrich_named_entity(&self, value: &mut serde_json::Value, entity_key: &str) {
2470 if self.code_lookup.is_none() || value.is_null() {
2471 return;
2472 }
2473 let sites = self.code_sites();
2474 if let Some(entity_sites) = sites.get(entity_key) {
2475 Self::apply_sites(self, value, entity_sites);
2476 }
2477 }
2478
2479 fn enrich_entities(&self, value: &mut serde_json::Value) {
2485 if self.code_lookup.is_none() {
2486 return;
2487 }
2488 let sites: HashMap<String, Vec<CodeSite<'_>>> = self.code_sites();
2489 if sites.is_empty() {
2490 return;
2491 }
2492 Self::walk_and_enrich(self, value, &sites);
2493 }
2494
2495 fn code_sites(&self) -> HashMap<String, Vec<CodeSite<'_>>> {
2498 let Some(ref code_lookup) = self.code_lookup else {
2499 return HashMap::new();
2500 };
2501 let mut sites: HashMap<String, Vec<CodeSite<'_>>> = HashMap::new();
2502
2503 for def in &self.definitions {
2504 let Some(ref source_path) = def.meta.source_path else {
2505 continue;
2506 };
2507 let entity_key = to_camel_case(&def.meta.entity);
2508
2509 for (path, field_mapping) in &def.fields {
2510 let (target, enum_map, also_target, also_enum_map) = match field_mapping {
2511 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
2512 FieldMapping::Structured(s) => (
2513 s.target.as_str(),
2514 s.enum_map.as_ref(),
2515 s.also_target.as_deref(),
2516 s.also_enum_map.as_ref(),
2517 ),
2518 FieldMapping::Nested(_) => continue,
2519 };
2520 if target.is_empty() {
2521 continue;
2522 }
2523
2524 let parts: Vec<&str> = path.split('.').collect();
2525 let (seg_tag, path_qualifier, _occ) = parse_tag_qualifier(parts[0]);
2526 let (element_idx, component_idx) = Self::parse_element_component(&parts[1..]);
2527 let disc_qualifier = Self::discriminator_qualifier_for_tag(def, &seg_tag);
2534 if code_lookup
2535 .enrichment_codes(
2536 source_path,
2537 &seg_tag,
2538 path_qualifier,
2539 disc_qualifier.as_deref(),
2540 element_idx,
2541 component_idx,
2542 )
2543 .is_none()
2544 {
2545 continue;
2546 }
2547
2548 sites.entry(entity_key.clone()).or_default().push(CodeSite {
2549 target,
2550 parent_field: def.meta.parent_field.as_deref(),
2551 source_path,
2552 seg_tag,
2553 path_qualifier: path_qualifier.map(str::to_string),
2554 disc_qualifier,
2555 element_idx,
2556 component_idx,
2557 enum_map,
2558 also_target,
2559 also_enum_map,
2560 });
2561 }
2562 }
2563 sites
2564 }
2565
2566 fn walk_and_enrich(
2569 engine: &MappingEngine,
2570 value: &mut serde_json::Value,
2571 sites: &HashMap<String, Vec<CodeSite<'_>>>,
2572 ) {
2573 match value {
2574 serde_json::Value::Object(map) => {
2575 for (key, child) in map.iter_mut() {
2576 if let Some(entity_sites) = sites.get(key.as_str()) {
2577 Self::apply_sites(engine, child, entity_sites);
2578 }
2579 Self::walk_and_enrich(engine, child, sites);
2580 }
2581 }
2582 serde_json::Value::Array(items) => {
2583 for item in items.iter_mut() {
2584 Self::walk_and_enrich(engine, item, sites);
2585 }
2586 }
2587 _ => {}
2588 }
2589 }
2590
2591 fn apply_sites(engine: &MappingEngine, value: &mut serde_json::Value, sites: &[CodeSite<'_>]) {
2594 match value {
2595 serde_json::Value::Array(items) => {
2596 for item in items.iter_mut() {
2597 Self::apply_sites(engine, item, sites);
2598 }
2599 }
2600 serde_json::Value::Object(_) => {
2601 for site in sites {
2602 match site.parent_field {
2603 None => engine.enrich_one(value, site),
2604 Some(field) => {
2605 if let Some(nested) = value.get_mut(field) {
2606 Self::apply_nested_site(engine, nested, site);
2607 }
2608 }
2609 }
2610 }
2611 }
2612 _ => {}
2613 }
2614 }
2615
2616 fn apply_nested_site(
2618 engine: &MappingEngine,
2619 value: &mut serde_json::Value,
2620 site: &CodeSite<'_>,
2621 ) {
2622 match value {
2623 serde_json::Value::Array(items) => {
2624 for item in items.iter_mut() {
2625 Self::apply_nested_site(engine, item, site);
2626 }
2627 }
2628 serde_json::Value::Object(_) => engine.enrich_one(value, site),
2629 _ => {}
2630 }
2631 }
2632
2633 fn enrich_one(&self, entity: &mut serde_json::Value, site: &CodeSite<'_>) {
2635 let Some(ref code_lookup) = self.code_lookup else {
2636 return;
2637 };
2638 let Some(mapped_val) = Self::read_plain_string(entity, site.target) else {
2640 return;
2641 };
2642
2643 let raw = match site.enum_map {
2646 None => mapped_val.clone(),
2647 Some(map) => {
2648 let joint = match (site.also_target, site.also_enum_map) {
2649 (Some(also), Some(also_map)) => {
2650 Self::read_plain_string(entity, also).and_then(|also_v| {
2651 map.iter()
2652 .find(|(code, bo4e_v)| {
2653 *bo4e_v == &mapped_val && also_map.get(*code) == Some(&also_v)
2654 })
2655 .map(|(code, _)| code.clone())
2656 })
2657 }
2658 _ => None,
2659 };
2660 joint
2661 .or_else(|| {
2662 map.iter()
2663 .find(|(_, bo4e_v)| *bo4e_v == &mapped_val)
2664 .map(|(code, _)| code.clone())
2665 })
2666 .unwrap_or_else(|| mapped_val.clone())
2667 }
2668 };
2669
2670 let Some(codes) = code_lookup.enrichment_codes(
2671 site.source_path,
2672 &site.seg_tag,
2673 site.path_qualifier.as_deref(),
2674 site.disc_qualifier.as_deref(),
2675 site.element_idx,
2676 site.component_idx,
2677 ) else {
2678 return;
2679 };
2680
2681 if let Some(ref pid) = self.current_pid {
2683 if codes.len() == 1 && codes.contains_key(pid.as_str()) {
2684 return;
2685 }
2686 }
2687
2688 let enrichment = codes.get(&raw);
2689 let meaning = enrichment
2690 .map(|e| serde_json::Value::String(e.meaning.clone()))
2691 .unwrap_or(serde_json::Value::Null);
2692
2693 let mut obj = serde_json::Map::new();
2694 obj.insert("code".into(), serde_json::json!(mapped_val));
2695 obj.insert("meaning".into(), meaning);
2696 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
2697 obj.insert("enum".into(), serde_json::json!(enum_key));
2698 }
2699
2700 if let serde_json::Value::Object(map) = entity {
2701 set_nested_value_json(map, site.target, serde_json::Value::Object(obj));
2702 }
2703 }
2704
2705 fn read_plain_string(entity: &serde_json::Value, target: &str) -> Option<String> {
2708 let mut current = entity;
2709 for part in target.split('.') {
2710 current = current.get(part)?;
2711 }
2712 current.as_str().map(str::to_string)
2713 }
2714
2715 pub fn map_interchange(
2724 msg_engine: &MappingEngine,
2725 tx_engine: &MappingEngine,
2726 tree: &AssembledTree,
2727 transaction_group: &str,
2728 enrich_codes: bool,
2729 ) -> crate::model::MappedMessage {
2730 let mut mapped =
2731 Self::translate_edifact_to_bo4e(msg_engine, tx_engine, tree, transaction_group);
2732 if enrich_codes {
2733 Self::enrich_bo4e_types(msg_engine, tx_engine, &mut mapped);
2734 }
2735 mapped
2736 }
2737
2738 #[doc(hidden)]
2747 pub fn map_interchange_inner_for_test(
2748 msg_engine: &MappingEngine,
2749 tx_engine: &MappingEngine,
2750 tree: &AssembledTree,
2751 transaction_group: &str,
2752 enrich_codes: bool,
2753 ) -> crate::model::MappedMessage {
2754 Self::map_interchange_inner(msg_engine, tx_engine, tree, transaction_group, enrich_codes)
2755 }
2756
2757 pub(crate) fn map_interchange_inner(
2758 msg_engine: &MappingEngine,
2759 tx_engine: &MappingEngine,
2760 tree: &AssembledTree,
2761 transaction_group: &str,
2762 enrich_codes: bool,
2763 ) -> crate::model::MappedMessage {
2764 let (stammdaten, nesting_info) = msg_engine.map_all_forward_inner(tree, enrich_codes);
2766
2767 let transaktionen = tree
2769 .groups
2770 .iter()
2771 .find(|g| g.group_id == transaction_group)
2772 .map(|sg| {
2773 sg.repetitions
2774 .iter()
2775 .map(|instance| {
2776 let wrapped_tree = AssembledTree {
2779 segments: vec![],
2780 groups: vec![AssembledGroup {
2781 group_id: transaction_group.to_string(),
2782 repetitions: vec![instance.clone()],
2783 }],
2784 post_group_start: 0,
2785 inter_group_segments: std::collections::BTreeMap::new(),
2786 };
2787
2788 let (tx_result, tx_nesting) = tx_engine.map_all_forward_inner_with_tx(
2792 &wrapped_tree,
2793 enrich_codes,
2794 Some(transaction_group),
2795 );
2796
2797 let mut tx_result = tx_result;
2802 let transaktionsdaten = crate::model::take_entity(
2803 &mut tx_result,
2804 crate::model::TX_METADATA_ENTITY,
2805 );
2806
2807 crate::model::MappedTransaktion {
2808 stammdaten: tx_result,
2809 transaktionsdaten,
2810 nesting_info: tx_nesting,
2811 }
2812 })
2813 .collect()
2814 })
2815 .unwrap_or_default();
2816
2817 let mut stammdaten = stammdaten;
2819 let nachricht_meta =
2820 crate::model::take_entity(&mut stammdaten, crate::model::MSG_METADATA_ENTITY);
2821
2822 crate::model::MappedMessage {
2823 stammdaten,
2824 nachricht_meta,
2825 transaktionen,
2826 nesting_info,
2827 inter_group_segments: tree.inter_group_segments.clone(),
2828 }
2829 }
2830
2831 pub fn map_interchange_reverse(
2841 msg_engine: &MappingEngine,
2842 tx_engine: &MappingEngine,
2843 mapped: &crate::model::MappedMessage,
2844 transaction_group: &str,
2845 filtered_mig: Option<&MigSchema>,
2846 ) -> AssembledTree {
2847 let _owned_msg: Option<serde_json::Value>;
2855 let msg_stammdaten = if !mapped.nachricht_meta.is_null() {
2856 let mut merged = mapped.stammdaten.clone();
2857 crate::model::restore_entity(
2858 &mut merged,
2859 crate::model::MSG_METADATA_ENTITY,
2860 &mapped.nachricht_meta,
2861 );
2862 _owned_msg = Some(merged);
2863 _owned_msg.as_ref().unwrap()
2864 } else {
2865 _owned_msg = None;
2866 &mapped.stammdaten
2867 };
2868
2869 let msg_tree = msg_engine.map_all_reverse_with_mig(
2870 msg_stammdaten,
2871 if mapped.nesting_info.is_empty() {
2872 None
2873 } else {
2874 Some(&mapped.nesting_info)
2875 },
2876 filtered_mig,
2877 );
2878
2879 let mut sg4_reps: Vec<AssembledGroupInstance> = Vec::new();
2881
2882 struct DefWithMeta<'a> {
2886 def: &'a MappingDefinition,
2887 relative: String,
2888 depth: usize,
2889 }
2890
2891 let mut sorted_defs: Vec<DefWithMeta> = tx_engine
2892 .definitions
2893 .iter()
2894 .filter(|def| def.meta.parent_field.is_none())
2897 .map(|def| {
2898 let relative = strip_tx_group_prefix(&def.meta.source_group, transaction_group);
2899 let depth = if relative.is_empty() {
2900 0
2901 } else {
2902 relative.chars().filter(|c| *c == '.').count() + 1
2903 };
2904 DefWithMeta {
2905 def,
2906 relative,
2907 depth,
2908 }
2909 })
2910 .collect();
2911
2912 let mut parent_rep_map: std::collections::HashMap<String, usize> =
2916 std::collections::HashMap::new();
2917 for dm in &sorted_defs {
2918 if dm.depth >= 2 {
2919 let parts: Vec<&str> = dm.relative.split('.').collect();
2920 let (_, parent_rep) = parse_group_spec(parts[0]);
2921 if let Some(rep_idx) = parent_rep {
2922 if let Some(sp) = &dm.def.meta.source_path {
2923 if let Some((parent_path, _)) = sp.rsplit_once('.') {
2924 parent_rep_map
2925 .entry(parent_path.to_string())
2926 .or_insert(rep_idx);
2927 }
2928 }
2929 }
2930 }
2931 }
2932
2933 for dm in &mut sorted_defs {
2936 if dm.depth == 1 && !dm.relative.contains(':') {
2937 if let Some(sp) = &dm.def.meta.source_path {
2938 if let Some(rep_idx) = parent_rep_map.get(sp.as_str()) {
2939 dm.relative = format!("{}:{}", dm.relative, rep_idx);
2940 }
2941 }
2942 }
2943 }
2944
2945 if let Some(mig) = filtered_mig {
2952 let mig_order = build_reverse_mig_group_order(mig, transaction_group);
2953 sorted_defs.sort_by(|a, b| {
2954 a.depth.cmp(&b.depth).then_with(|| {
2955 let a_id = a.relative.split(':').next().unwrap_or(&a.relative);
2956 let b_id = b.relative.split(':').next().unwrap_or(&b.relative);
2957 let a_pos = variant_mig_position(a.def, a_id, &mig_order);
2959 let b_pos = variant_mig_position(b.def, b_id, &mig_order);
2960 a_pos.cmp(&b_pos).then(a.relative.cmp(&b.relative))
2961 })
2962 });
2963 } else {
2964 sorted_defs.sort_by(|a, b| a.depth.cmp(&b.depth).then(a.relative.cmp(&b.relative)));
2965 }
2966
2967 for tx in &mapped.transaktionen {
2968 let mut root_segs: Vec<AssembledSegment> = Vec::new();
2969 let mut child_groups: Vec<AssembledGroup> = Vec::new();
2970
2971 let _owned_tx: Option<serde_json::Value>;
2974 let tx_stammdaten: &serde_json::Value = if !tx.transaktionsdaten.is_null() {
2975 let mut merged = tx.stammdaten.clone();
2976 crate::model::restore_entity(
2977 &mut merged,
2978 crate::model::TX_METADATA_ENTITY,
2979 &tx.transaktionsdaten,
2980 );
2981 _owned_tx = Some(merged);
2982 _owned_tx.as_ref().unwrap()
2983 } else {
2984 _owned_tx = None;
2985 &tx.stammdaten
2986 };
2987
2988 let mut source_path_to_rep: std::collections::HashMap<String, Vec<usize>> =
2993 std::collections::HashMap::new();
2994
2995 for dm in &sorted_defs {
2996 let entity_key = to_camel_case(&dm.def.meta.entity);
2999 let _tx_extracted: Option<serde_json::Value>;
3000 let bo4e_value = if let Some(v) = tx_stammdaten.get(&entity_key) {
3001 _tx_extracted = None;
3002 v
3003 } else if dm.def.meta.source_group.contains('.') {
3004 match extract_child_from_parent(tx_stammdaten, &tx_engine.definitions, dm.def) {
3005 Some(v) => {
3006 _tx_extracted = Some(v);
3007 _tx_extracted.as_ref().unwrap()
3008 }
3009 None => continue,
3010 }
3011 } else {
3012 continue;
3013 };
3014
3015 let unwrapped_value: Option<serde_json::Value>;
3017 let bo4e_value = if bo4e_value.is_object() && !bo4e_value.is_array() {
3018 if let Some(disc_value) = dm
3019 .def
3020 .meta
3021 .discriminator
3022 .as_deref()
3023 .and_then(|d| d.split_once('='))
3024 .map(|(_, v)| v)
3025 {
3026 if let Some(inner) = bo4e_value.get(disc_value) {
3027 let mut injected = inner.clone();
3028 if let Some(qualifier_field) = find_qualifier_companion_field(
3029 &tx_engine.definitions,
3030 &dm.def.meta.entity,
3031 ) {
3032 if let Some(obj) = injected.as_object_mut() {
3033 obj.entry(qualifier_field).or_insert_with(|| {
3034 serde_json::Value::String(disc_value.to_string())
3035 });
3036 }
3037 }
3038 unwrapped_value = Some(injected);
3039 unwrapped_value.as_ref().unwrap()
3040 } else {
3041 bo4e_value
3042 }
3043 } else if is_map_keyed_object(bo4e_value) {
3044 let map = bo4e_value.as_object().unwrap();
3045 let arr: Vec<serde_json::Value> = map
3046 .iter()
3047 .map(|(key, val)| {
3048 let mut item = val.clone();
3049 if let Some(obj) = item.as_object_mut() {
3050 if let Some(qualifier_field) = find_qualifier_companion_field(
3051 &tx_engine.definitions,
3052 &dm.def.meta.entity,
3053 ) {
3054 let entry = obj
3055 .entry(qualifier_field)
3056 .or_insert(serde_json::Value::Null);
3057 if entry.is_null() {
3058 *entry = serde_json::Value::String(key.clone());
3059 }
3060 }
3061 }
3062 item
3063 })
3064 .collect();
3065 unwrapped_value = Some(serde_json::Value::Array(arr));
3066 unwrapped_value.as_ref().unwrap()
3067 } else {
3068 bo4e_value
3069 }
3070 } else {
3071 bo4e_value
3072 };
3073
3074 let items: Vec<&serde_json::Value> = if bo4e_value.is_array() {
3078 bo4e_value.as_array().unwrap().iter().collect()
3079 } else {
3080 vec![bo4e_value]
3081 };
3082
3083 for (item_idx, item) in items.iter().enumerate() {
3084 let instance = tx_engine.map_reverse(item, dm.def);
3085
3086 if instance.segments.is_empty() && instance.child_groups.is_empty() {
3088 continue;
3089 }
3090
3091 if dm.relative.is_empty() {
3092 root_segs.extend(instance.segments);
3099 for child in instance.child_groups {
3100 match child_groups
3101 .iter_mut()
3102 .find(|g| g.group_id == child.group_id)
3103 {
3104 Some(existing) => existing.repetitions.extend(child.repetitions),
3105 None => child_groups.push(child),
3106 }
3107 }
3108 } else {
3109 let effective_relative = if dm.depth >= 2 {
3113 let rel = if items.len() > 1 {
3116 strip_all_rep_indices(&dm.relative)
3117 } else {
3118 dm.relative.clone()
3119 };
3120 let skip_nesting = dm
3127 .def
3128 .meta
3129 .source_path
3130 .as_ref()
3131 .and_then(|sp| sp.rsplit_once('.'))
3132 .and_then(|(parent_path, _)| source_path_to_rep.get(parent_path))
3133 .is_some_and(|reps| reps.len() == 1);
3134 let nesting_idx = if items.len() > 1 && !skip_nesting {
3135 dm.def
3136 .meta
3137 .source_path
3138 .as_ref()
3139 .and_then(|sp| tx.nesting_info.get(sp))
3140 .and_then(|dist| dist.get(item_idx))
3141 .copied()
3142 } else {
3143 None
3144 };
3145 if let Some(parent_rep) = nesting_idx {
3146 let parts: Vec<&str> = rel.split('.').collect();
3148 let parent_id = parts[0].split(':').next().unwrap_or(parts[0]);
3149 let rest = parts[1..].join(".");
3150 format!("{}:{}.{}", parent_id, parent_rep, rest)
3151 } else {
3152 resolve_child_relative(
3153 &rel,
3154 dm.def.meta.source_path.as_deref(),
3155 &source_path_to_rep,
3156 item_idx,
3157 )
3158 }
3159 } else if dm.depth == 1 {
3160 let child_key = dm
3163 .def
3164 .meta
3165 .source_path
3166 .as_ref()
3167 .map(|sp| format!("{sp}#child"));
3168 if let Some(child_indices) =
3169 child_key.as_ref().and_then(|ck| tx.nesting_info.get(ck))
3170 {
3171 if let Some(&target) = child_indices.get(item_idx) {
3172 if target != usize::MAX {
3173 let base =
3174 dm.relative.split(':').next().unwrap_or(&dm.relative);
3175 format!("{}:{}", base, target)
3176 } else {
3177 dm.relative.clone()
3178 }
3179 } else if items.len() > 1 && item_idx > 0 {
3180 strip_rep_index(&dm.relative)
3181 } else {
3182 dm.relative.clone()
3183 }
3184 } else if items.len() > 1 && item_idx > 0 {
3185 strip_rep_index(&dm.relative)
3186 } else {
3187 dm.relative.clone()
3188 }
3189 } else if items.len() > 1 && item_idx > 0 {
3190 strip_rep_index(&dm.relative)
3193 } else {
3194 dm.relative.clone()
3195 };
3196
3197 let rep_used =
3198 place_in_groups(&mut child_groups, &effective_relative, instance);
3199
3200 if dm.depth == 1 {
3202 if let Some(sp) = &dm.def.meta.source_path {
3203 source_path_to_rep
3204 .entry(sp.clone())
3205 .or_default()
3206 .push(rep_used);
3207 }
3208 }
3209 }
3210 }
3211 }
3212
3213 sg4_reps.push(AssembledGroupInstance {
3214 segments: root_segs,
3215 child_groups,
3216 entry_mig_number: None,
3217 variant_mig_numbers: vec![],
3218 skipped_segments: Vec::new(),
3219 skipped_positions: Vec::new(),
3220 });
3221 }
3222
3223 let mut root_segments = Vec::new();
3230 let mut uns_segments = Vec::new();
3231 let mut uns_is_summary = false;
3232 let mut found_uns = false;
3233 for seg in msg_tree.segments {
3234 if seg.tag == "UNS" {
3235 uns_is_summary = seg
3237 .elements
3238 .first()
3239 .and_then(|el| el.first())
3240 .map(|v| v == "S")
3241 .unwrap_or(false);
3242 uns_segments.push(seg);
3243 found_uns = true;
3244 } else if found_uns {
3245 uns_segments.push(seg);
3247 } else {
3248 root_segments.push(seg);
3249 }
3250 }
3251
3252 let pre_group_count = root_segments.len();
3253 let mut all_groups = msg_tree.groups;
3254 let mut inter_group = msg_tree.inter_group_segments;
3255
3256 let sg_num = |id: &str| -> usize {
3258 id.strip_prefix("SG")
3259 .and_then(|n| n.parse::<usize>().ok())
3260 .unwrap_or(0)
3261 };
3262
3263 if !sg4_reps.is_empty() {
3264 if uns_is_summary {
3265 all_groups.push(AssembledGroup {
3267 group_id: transaction_group.to_string(),
3268 repetitions: sg4_reps,
3269 });
3270 if !uns_segments.is_empty() {
3271 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3276 let tx_num = sg_num(transaction_group);
3277 let uns_pos = all_groups
3278 .iter()
3279 .rposition(|g| sg_num(&g.group_id) <= tx_num)
3280 .map(|i| i + 1)
3281 .unwrap_or(all_groups.len());
3282 inter_group.insert(uns_pos, uns_segments);
3283 }
3284 } else {
3285 if !uns_segments.is_empty() {
3287 inter_group.insert(all_groups.len(), uns_segments);
3288 }
3289 all_groups.push(AssembledGroup {
3290 group_id: transaction_group.to_string(),
3291 repetitions: sg4_reps,
3292 });
3293 }
3294 } else if !uns_segments.is_empty() {
3295 if transaction_group.is_empty() {
3296 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3301 if uns_is_summary {
3302 inter_group.insert(all_groups.len(), uns_segments);
3303 } else {
3304 inter_group.insert(0, uns_segments);
3305 }
3306 } else {
3307 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3311 let tx_num = sg_num(transaction_group);
3312 let uns_pos = all_groups
3313 .iter()
3314 .rposition(|g| sg_num(&g.group_id) <= tx_num)
3315 .map(|i| i + 1)
3316 .unwrap_or(all_groups.len());
3317 inter_group.insert(uns_pos, uns_segments);
3318 }
3319 }
3320
3321 for (k, segs) in &mapped.inter_group_segments {
3329 if segs.is_empty() {
3330 continue;
3331 }
3332 let existing_tags: std::collections::HashSet<String> = inter_group
3333 .get(k)
3334 .map(|v| v.iter().map(|s| s.tag.clone()).collect())
3335 .unwrap_or_default();
3336 for seg in segs {
3337 if existing_tags.contains(&seg.tag) {
3338 continue;
3339 }
3340 inter_group.entry(*k).or_default().push(seg.clone());
3341 }
3342 }
3343
3344 let mut tree = AssembledTree {
3345 segments: root_segments,
3346 groups: all_groups,
3347 post_group_start: pre_group_count,
3348 inter_group_segments: inter_group,
3349 };
3350
3351 if let Some(mig) = filtered_mig {
3358 mig_assembly::repetition_order::sort_repetitions_by_mig_variant(
3359 &mut tree,
3360 mig,
3361 (!transaction_group.is_empty()).then_some(transaction_group),
3362 );
3363 }
3364 tree
3365 }
3366
3367 pub fn build_group_from_bo4e(
3369 &self,
3370 bo4e_value: &serde_json::Value,
3371 def: &MappingDefinition,
3372 ) -> AssembledGroup {
3373 let instance = self.map_reverse(bo4e_value, def);
3374 let leaf_group = def
3375 .meta
3376 .source_group
3377 .rsplit('.')
3378 .next()
3379 .unwrap_or(&def.meta.source_group);
3380
3381 AssembledGroup {
3382 group_id: leaf_group.to_string(),
3383 repetitions: vec![instance],
3384 }
3385 }
3386
3387 pub fn map_interchange_typed<M, T>(
3395 msg_engine: &MappingEngine,
3396 tx_engine: &MappingEngine,
3397 tree: &AssembledTree,
3398 tx_group: &str,
3399 enrich_codes: bool,
3400 nachrichtendaten: crate::model::Nachrichtendaten,
3401 interchangedaten: crate::model::Interchangedaten,
3402 ) -> Result<crate::model::Interchange<M, T>, serde_json::Error>
3403 where
3404 M: serde::de::DeserializeOwned,
3405 T: serde::de::DeserializeOwned,
3406 {
3407 let mapped = Self::map_interchange(msg_engine, tx_engine, tree, tx_group, enrich_codes);
3408 let nachricht = mapped.into_dynamic_nachricht(nachrichtendaten);
3409 let dynamic = crate::model::DynamicInterchange {
3410 interchangedaten,
3411 nachrichten: vec![nachricht],
3412 };
3413 let value = serde_json::to_value(&dynamic)?;
3414 serde_json::from_value(value)
3415 }
3416
3417 pub fn map_interchange_reverse_typed<M, T>(
3424 msg_engine: &MappingEngine,
3425 tx_engine: &MappingEngine,
3426 nachricht: &crate::model::Nachricht<M, T>,
3427 tx_group: &str,
3428 ) -> Result<AssembledTree, serde_json::Error>
3429 where
3430 M: serde::Serialize,
3431 T: serde::Serialize,
3432 {
3433 let mut stammdaten = serde_json::to_value(&nachricht.stammdaten)?;
3436 crate::model::restore_message_metadata(&mut stammdaten, &nachricht.nachrichtendaten);
3437 let transaktionen: Vec<crate::model::MappedTransaktion> = nachricht
3438 .transaktionen
3439 .iter()
3440 .map(|t| {
3441 Ok(crate::model::MappedTransaktion {
3442 stammdaten: serde_json::to_value(t)?,
3443 transaktionsdaten: serde_json::Value::Null,
3444 nesting_info: Default::default(),
3445 })
3446 })
3447 .collect::<Result<Vec<_>, serde_json::Error>>()?;
3448 let mapped = crate::model::MappedMessage {
3449 stammdaten,
3450 nachricht_meta: serde_json::Value::Null,
3451 transaktionen,
3452 nesting_info: Default::default(),
3453 inter_group_segments: Default::default(),
3454 };
3455 Ok(Self::map_interchange_reverse(
3456 msg_engine, tx_engine, &mapped, tx_group, None,
3457 ))
3458 }
3459}
3460
3461fn parse_source_path_part(part: &str) -> (&str, Option<&str>) {
3468 if let Some(pos) = part.find('_') {
3472 let group = &part[..pos];
3473 let qualifier = &part[pos + 1..];
3474 if !qualifier.is_empty() {
3475 return (group, Some(qualifier));
3476 }
3477 }
3478 (part, None)
3479}
3480
3481fn build_reverse_mig_group_order(mig: &MigSchema, tx_group_id: &str) -> HashMap<String, usize> {
3489 let mut order = HashMap::new();
3490 if let Some(tg) = mig.segment_groups.iter().find(|g| g.id == tx_group_id) {
3491 for (i, nested) in tg.nested_groups.iter().enumerate() {
3492 if let Some(ref vc) = nested.variant_code {
3494 let variant_key = format!("{}_{}", nested.id, vc.to_uppercase());
3495 order.insert(variant_key, i);
3496 }
3497 order.entry(nested.id.clone()).or_insert(i);
3499 }
3500 }
3501 order
3502}
3503
3504fn variant_mig_position(
3510 def: &MappingDefinition,
3511 base_group_id: &str,
3512 mig_order: &HashMap<String, usize>,
3513) -> usize {
3514 if let Some(ref sp) = def.meta.source_path {
3517 let base_lower = base_group_id.to_lowercase();
3519 for part in sp.split('.') {
3520 if part.starts_with(&base_lower)
3521 || part.starts_with(base_group_id.to_lowercase().as_str())
3522 {
3523 if let Some(underscore_pos) = part.find('_') {
3525 let qualifier = &part[underscore_pos + 1..];
3526 let variant_key = format!("{}_{}", base_group_id, qualifier.to_uppercase());
3527 if let Some(&pos) = mig_order.get(&variant_key) {
3528 return pos;
3529 }
3530 }
3531 }
3532 }
3533 }
3534 mig_order.get(base_group_id).copied().unwrap_or(usize::MAX)
3536}
3537
3538fn find_rep_by_entry_qualifier<'a>(
3543 reps: &'a [AssembledGroupInstance],
3544 qualifier: &str,
3545) -> Option<&'a AssembledGroupInstance> {
3546 let parts: Vec<&str> = qualifier.split('_').collect();
3548 reps.iter().find(|inst| {
3549 inst.segments.first().is_some_and(|seg| {
3550 seg.elements
3551 .first()
3552 .and_then(|e| e.first())
3553 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3554 })
3555 })
3556}
3557
3558fn find_all_reps_by_entry_qualifier<'a>(
3560 reps: &'a [AssembledGroupInstance],
3561 qualifier: &str,
3562) -> Vec<&'a AssembledGroupInstance> {
3563 let parts: Vec<&str> = qualifier.split('_').collect();
3565 reps.iter()
3566 .filter(|inst| {
3567 inst.segments.first().is_some_and(|seg| {
3568 seg.elements
3569 .first()
3570 .and_then(|e| e.first())
3571 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3572 })
3573 })
3574 .collect()
3575}
3576
3577fn has_source_path_qualifiers(source_path: &str) -> bool {
3579 source_path.split('.').any(|part| {
3580 if let Some(pos) = part.find('_') {
3581 pos < part.len() - 1
3582 } else {
3583 false
3584 }
3585 })
3586}
3587
3588fn parse_group_spec(part: &str) -> (&str, Option<usize>) {
3589 if let Some(colon_pos) = part.find(':') {
3590 let id = &part[..colon_pos];
3591 let rep = part[colon_pos + 1..].parse::<usize>().ok();
3592 (id, rep)
3593 } else {
3594 (part, None)
3595 }
3596}
3597
3598fn strip_tx_group_prefix(source_group: &str, tx_group: &str) -> String {
3604 if source_group == tx_group || source_group.is_empty() {
3605 String::new()
3606 } else if let Some(rest) = source_group.strip_prefix(tx_group) {
3607 rest.strip_prefix('.').unwrap_or(rest).to_string()
3608 } else {
3609 source_group.to_string()
3610 }
3611}
3612
3613fn place_in_groups(
3621 groups: &mut Vec<AssembledGroup>,
3622 relative_path: &str,
3623 instance: AssembledGroupInstance,
3624) -> usize {
3625 let parts: Vec<&str> = relative_path.split('.').collect();
3626
3627 if parts.len() == 1 {
3628 let (id, rep) = parse_group_spec(parts[0]);
3630
3631 let group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == id) {
3633 g
3634 } else {
3635 groups.push(AssembledGroup {
3636 group_id: id.to_string(),
3637 repetitions: vec![],
3638 });
3639 groups.last_mut().unwrap()
3640 };
3641
3642 if let Some(rep_idx) = rep {
3643 while group.repetitions.len() <= rep_idx {
3645 group.repetitions.push(AssembledGroupInstance {
3646 segments: vec![],
3647 child_groups: vec![],
3648 entry_mig_number: None,
3649 variant_mig_numbers: vec![],
3650 skipped_segments: Vec::new(),
3651 skipped_positions: Vec::new(),
3652 });
3653 }
3654 group.repetitions[rep_idx]
3655 .segments
3656 .extend(instance.segments);
3657 group.repetitions[rep_idx]
3658 .child_groups
3659 .extend(instance.child_groups);
3660 rep_idx
3661 } else {
3662 let pos = group.repetitions.len();
3664 group.repetitions.push(instance);
3665 pos
3666 }
3667 } else {
3668 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3670 let rep_idx = parent_rep.unwrap_or(0);
3671
3672 let parent_group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == parent_id) {
3674 g
3675 } else {
3676 groups.push(AssembledGroup {
3677 group_id: parent_id.to_string(),
3678 repetitions: vec![],
3679 });
3680 groups.last_mut().unwrap()
3681 };
3682
3683 while parent_group.repetitions.len() <= rep_idx {
3685 parent_group.repetitions.push(AssembledGroupInstance {
3686 segments: vec![],
3687 child_groups: vec![],
3688 entry_mig_number: None,
3689 variant_mig_numbers: vec![],
3690 skipped_segments: Vec::new(),
3691 skipped_positions: Vec::new(),
3692 });
3693 }
3694
3695 let remaining = parts[1..].join(".");
3696 place_in_groups(
3697 &mut parent_group.repetitions[rep_idx].child_groups,
3698 &remaining,
3699 instance,
3700 );
3701 rep_idx
3702 }
3703}
3704
3705fn resolve_child_relative(
3717 relative: &str,
3718 source_path: Option<&str>,
3719 source_path_to_rep: &std::collections::HashMap<String, Vec<usize>>,
3720 item_idx: usize,
3721) -> String {
3722 let parts: Vec<&str> = relative.split('.').collect();
3723 if parts.is_empty() {
3724 return relative.to_string();
3725 }
3726
3727 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3729 if parent_rep.is_some() {
3730 return relative.to_string();
3731 }
3732
3733 if let Some(sp) = source_path {
3735 if let Some((parent_path, _child)) = sp.rsplit_once('.') {
3736 if let Some(rep_indices) = source_path_to_rep.get(parent_path) {
3738 let rep_idx = rep_indices
3739 .get(item_idx)
3740 .or_else(|| rep_indices.last())
3741 .copied()
3742 .unwrap_or(0);
3743 let rest = parts[1..].join(".");
3744 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3745 }
3746 let prefix = format!("{}_", parent_path);
3754 let mut unioned: Vec<usize> = source_path_to_rep
3755 .iter()
3756 .filter(|(k, _)| k.starts_with(&prefix))
3757 .flat_map(|(_, v)| v.iter().copied())
3758 .collect();
3759 if !unioned.is_empty() {
3760 unioned.sort_unstable();
3761 unioned.dedup();
3762 let rep_idx = unioned
3763 .get(item_idx)
3764 .or_else(|| unioned.last())
3765 .copied()
3766 .unwrap_or(0);
3767 let rest = parts[1..].join(".");
3768 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3769 }
3770 }
3771 }
3772
3773 relative.to_string()
3775}
3776
3777struct DiscriminatorMatcher<'a> {
3784 tag: &'a str,
3785 element_idx: usize,
3786 component_idx: usize,
3787 expected_values: Vec<&'a str>,
3788 occurrence: Option<usize>,
3790}
3791
3792impl<'a> DiscriminatorMatcher<'a> {
3793 fn parse(disc: &'a str) -> Option<Self> {
3794 let (spec, expected) = disc.split_once('=')?;
3795 let parts: Vec<&str> = spec.split('.').collect();
3796 if parts.len() != 3 {
3797 return None;
3798 }
3799 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
3800 Some(Self {
3801 tag: parts[0],
3802 element_idx: parts[1].parse().ok()?,
3803 component_idx: parts[2].parse().ok()?,
3804 expected_values: expected_raw.split('|').collect(),
3805 occurrence,
3806 })
3807 }
3808
3809 fn matches(&self, instance: &AssembledGroupInstance) -> bool {
3810 instance.segments.iter().any(|s| {
3811 s.tag.eq_ignore_ascii_case(self.tag)
3812 && s.elements
3813 .get(self.element_idx)
3814 .and_then(|e| e.get(self.component_idx))
3815 .map(|v| self.expected_values.iter().any(|ev| v == ev))
3816 .unwrap_or(false)
3817 })
3818 }
3819
3820 fn filter_instances<'b>(
3822 &self,
3823 instances: Vec<&'b AssembledGroupInstance>,
3824 ) -> Vec<&'b AssembledGroupInstance> {
3825 let matching: Vec<_> = instances
3826 .into_iter()
3827 .filter(|inst| self.matches(inst))
3828 .collect();
3829 if let Some(occ) = self.occurrence {
3830 matching.into_iter().nth(occ).into_iter().collect()
3831 } else {
3832 matching
3833 }
3834 }
3835}
3836
3837fn parse_discriminator_occurrence(expected: &str) -> (&str, Option<usize>) {
3843 if let Some(hash_pos) = expected.rfind('#') {
3844 if let Ok(occ) = expected[hash_pos + 1..].parse::<usize>() {
3845 return (&expected[..hash_pos], Some(occ));
3846 }
3847 }
3848 (expected, None)
3849}
3850
3851fn strip_rep_index(relative: &str) -> String {
3855 let (id, _) = parse_group_spec(relative);
3856 id.to_string()
3857}
3858
3859pub(crate) fn strip_all_rep_indices(relative: &str) -> String {
3864 relative
3865 .split('.')
3866 .map(|part| {
3867 let (id, _) = parse_group_spec(part);
3868 id
3869 })
3870 .collect::<Vec<_>>()
3871 .join(".")
3872}
3873
3874pub fn is_nested_child_of(child: &MappingDefinition, parent: &MappingDefinition) -> bool {
3884 if child.meta.parent_field.is_none() || child.meta.entity != parent.meta.entity {
3885 return false;
3886 }
3887 let child_sg = strip_all_rep_indices(&child.meta.source_group);
3888 let parent_sg = strip_all_rep_indices(&parent.meta.source_group);
3889 match child_sg.rsplit_once('.') {
3890 Some((head, _)) if head.eq_ignore_ascii_case(&parent_sg) => {}
3891 _ => return false,
3892 }
3893 let (Some(child_sp), Some(parent_sp)) = (
3894 child.meta.source_path.as_deref(),
3895 parent.meta.source_path.as_deref(),
3896 ) else {
3897 return true;
3898 };
3899 let Some((child_parent_sp, _)) = child_sp.rsplit_once('.') else {
3900 return false;
3901 };
3902 let child_parts: Vec<&str> = child_parent_sp.split('.').collect();
3903 let parent_parts: Vec<&str> = parent_sp.split('.').collect();
3904 child_parts.len() == parent_parts.len()
3905 && child_parts.iter().zip(&parent_parts).all(|(c, p)| {
3906 let (c_id, c_q) = parse_source_path_part(c);
3907 let (p_id, p_q) = parse_source_path_part(p);
3908 c_id.eq_ignore_ascii_case(p_id)
3909 && match (c_q, p_q) {
3910 (Some(cq), Some(pq)) => cq.eq_ignore_ascii_case(pq),
3911 _ => true,
3912 }
3913 })
3914}
3915
3916fn nested_parent_qualifier(child: &MappingDefinition) -> Option<&str> {
3919 let (parent_path, _) = child.meta.source_path.as_deref()?.rsplit_once('.')?;
3920 let last = parent_path.rsplit('.').next()?;
3921 parse_source_path_part(last).1
3922}
3923
3924fn nested_child_leaf(child: &MappingDefinition) -> (String, Option<&str>) {
3927 let leaf_group = strip_all_rep_indices(
3928 child
3929 .meta
3930 .source_group
3931 .rsplit('.')
3932 .next()
3933 .unwrap_or(&child.meta.source_group),
3934 );
3935 let leaf_qualifier = child
3936 .meta
3937 .source_path
3938 .as_deref()
3939 .and_then(|sp| sp.rsplit('.').next())
3940 .and_then(|part| parse_source_path_part(part).1);
3941 (leaf_group, leaf_qualifier)
3942}
3943
3944fn entry_qualifier_matches(instance: &AssembledGroupInstance, qualifier: &str) -> bool {
3947 segment_qualifier_matches(instance.segments.first(), qualifier)
3948}
3949
3950fn rebuilt_entry_qualifier_matches(
3955 instance: &AssembledGroupInstance,
3956 def: &MappingDefinition,
3957 qualifier: &str,
3958) -> bool {
3959 let entry_tag = def
3960 .meta
3961 .discriminator
3962 .as_deref()
3963 .and_then(|d| d.split('.').next())
3964 .filter(|tag| !tag.is_empty());
3965 let entry = match entry_tag {
3966 Some(tag) => instance
3967 .segments
3968 .iter()
3969 .find(|s| s.tag.eq_ignore_ascii_case(tag)),
3970 None => instance.segments.first(),
3971 };
3972 segment_qualifier_matches(entry, qualifier)
3973}
3974
3975fn segment_qualifier_matches(segment: Option<&AssembledSegment>, qualifier: &str) -> bool {
3976 segment
3977 .and_then(|seg| seg.elements.first())
3978 .and_then(|e| e.first())
3979 .is_some_and(|v| qualifier.split('_').any(|q| v.eq_ignore_ascii_case(q)))
3980}
3981
3982pub(crate) fn parse_tag_qualifier(tag_part: &str) -> (String, Option<&str>, usize) {
3989 if let Some(bracket_start) = tag_part.find('[') {
3990 let tag = tag_part[..bracket_start].to_uppercase();
3991 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3992 if let Some(comma_pos) = inner.find(',') {
3993 let qualifier = &inner[..comma_pos];
3994 let index = inner[comma_pos + 1..].parse::<usize>().unwrap_or(0);
3995 if qualifier == "*" {
3997 (tag, None, index)
3998 } else {
3999 (tag, Some(qualifier), index)
4000 }
4001 } else {
4002 (tag, Some(inner), 0)
4003 }
4004 } else {
4005 (tag_part.to_uppercase(), None, 0)
4006 }
4007}
4008
4009pub fn deep_merge_insert(
4015 result: &mut serde_json::Map<String, serde_json::Value>,
4016 entity: &str,
4017 bo4e: serde_json::Value,
4018) {
4019 if let Some(existing) = result.get_mut(entity) {
4020 if let (Some(existing_arr), Some(new_arr)) =
4023 (existing.as_array().map(|a| a.len()), bo4e.as_array())
4024 {
4025 if existing_arr == new_arr.len() {
4026 let existing_arr = existing.as_array_mut().unwrap();
4027 for (existing_elem, new_elem) in existing_arr.iter_mut().zip(new_arr) {
4028 if let (Some(existing_map), Some(new_map)) =
4029 (existing_elem.as_object_mut(), new_elem.as_object())
4030 {
4031 for (k, v) in new_map {
4032 if let Some(existing_v) = existing_map.get_mut(k) {
4033 if let (Some(existing_inner), Some(new_inner)) =
4034 (existing_v.as_object_mut(), v.as_object())
4035 {
4036 for (ik, iv) in new_inner {
4037 existing_inner
4038 .entry(ik.clone())
4039 .or_insert_with(|| iv.clone());
4040 }
4041 }
4042 } else {
4043 existing_map.insert(k.clone(), v.clone());
4044 }
4045 }
4046 }
4047 }
4048 return;
4049 }
4050 }
4051 if let (Some(existing_map), serde_json::Value::Object(new_map)) =
4053 (existing.as_object_mut(), &bo4e)
4054 {
4055 for (k, v) in new_map {
4056 if let Some(existing_v) = existing_map.get_mut(k) {
4057 if let (Some(existing_inner), Some(new_inner)) =
4059 (existing_v.as_object_mut(), v.as_object())
4060 {
4061 for (ik, iv) in new_inner {
4062 existing_inner
4063 .entry(ik.clone())
4064 .or_insert_with(|| iv.clone());
4065 }
4066 }
4067 } else {
4069 existing_map.insert(k.clone(), v.clone());
4070 }
4071 }
4072 return;
4073 }
4074 }
4075 result.insert(entity.to_string(), bo4e);
4076}
4077
4078fn append_to_list_field(
4091 result: &mut serde_json::Map<String, serde_json::Value>,
4092 entity: &str,
4093 list_field: &str,
4094 bo4e: serde_json::Value,
4095) {
4096 let mut items = match bo4e {
4097 serde_json::Value::Array(a) => a,
4098 other => vec![other],
4099 };
4100 items.retain(|v| !v.as_object().is_some_and(|o| o.is_empty()));
4101 if items.is_empty() {
4102 return;
4103 }
4104 let entry = result
4105 .entry(entity.to_string())
4106 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
4107 let Some(obj) = entry.as_object_mut() else {
4111 return;
4112 };
4113 match obj.get_mut(list_field).and_then(|v| v.as_array_mut()) {
4114 Some(existing) => existing.extend(items),
4115 None => {
4116 obj.insert(list_field.to_string(), serde_json::Value::Array(items));
4117 }
4118 }
4119}
4120
4121fn is_map_keyed_object(value: &serde_json::Value) -> bool {
4132 let Some(obj) = value.as_object() else {
4133 return false;
4134 };
4135 if obj.is_empty() {
4136 return false;
4137 }
4138 obj.iter().all(|(k, v)| {
4140 k.len() <= 5
4141 && k.chars()
4142 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
4143 && v.is_object()
4144 })
4145}
4146
4147fn find_qualifier_companion_field(
4156 definitions: &[crate::definition::MappingDefinition],
4157 entity: &str,
4158) -> Option<String> {
4159 for def in definitions {
4160 if def.meta.entity != *entity || def.meta.parent_field.is_some() {
4161 continue;
4162 }
4163 let disc = def.meta.discriminator.as_deref()?;
4164 let (disc_path, _) = disc.split_once('=')?;
4165 let disc_path_lower = disc_path.to_lowercase();
4166
4167 for (path, mapping) in &def.fields {
4170 let cf_path = path.to_lowercase();
4171 let matches = cf_path == disc_path_lower || format!("{}.0", cf_path) == disc_path_lower;
4172 if matches {
4173 let target = match mapping {
4174 FieldMapping::Simple(t) => t.as_str(),
4175 FieldMapping::Structured(s) => s.target.as_str(),
4176 FieldMapping::Nested(_) => continue,
4177 };
4178 if !target.is_empty() {
4179 return Some(target.to_string());
4180 }
4181 }
4182 }
4183 }
4184 None
4185}
4186
4187fn extract_child_from_parent(
4196 entities: &serde_json::Value,
4197 definitions: &[MappingDefinition],
4198 child_def: &MappingDefinition,
4199) -> Option<serde_json::Value> {
4200 extract_child_from_parent_with_indices(entities, definitions, child_def).map(|(v, _)| v)
4201}
4202
4203fn extract_child_from_parent_with_indices(
4208 entities: &serde_json::Value,
4209 definitions: &[MappingDefinition],
4210 child_def: &MappingDefinition,
4211) -> Option<(serde_json::Value, Vec<usize>)> {
4212 let parts: Vec<&str> = child_def.meta.source_group.split('.').collect();
4213 if parts.len() < 2 {
4214 return None;
4215 }
4216 let parent_group = parts[0];
4217 let parent_def = definitions
4218 .iter()
4219 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_def.meta.entity)?;
4220 let parent_key = to_camel_case(&parent_def.meta.entity);
4221 let child_key = to_camel_case(&child_def.meta.entity);
4222 let parent_value = entities.get(&parent_key)?;
4223
4224 if let Some(parent_map) = parent_value.as_object() {
4226 if is_map_keyed_value(parent_map) {
4227 let mut children: Vec<serde_json::Value> = Vec::new();
4228 let mut indices: Vec<usize> = Vec::new();
4229 for (i, (_key, inner)) in parent_map.iter().enumerate() {
4230 if let Some(child) = inner.get(&child_key) {
4231 if !child.is_null() {
4232 children.push(child.clone());
4233 indices.push(i);
4234 }
4235 }
4236 }
4237 return match children.len() {
4238 0 => None,
4239 1 => Some((children.into_iter().next().unwrap(), indices)),
4240 _ => Some((serde_json::Value::Array(children), indices)),
4241 };
4242 }
4243 }
4244
4245 if let Some(parent_arr) = parent_value.as_array() {
4247 let mut children: Vec<serde_json::Value> = Vec::new();
4248 let mut indices: Vec<usize> = Vec::new();
4249 for (i, item) in parent_arr.iter().enumerate() {
4250 if let Some(child) = item.get(&child_key) {
4251 if !child.is_null() {
4252 children.push(child.clone());
4253 indices.push(i);
4254 }
4255 }
4256 }
4257 return match children.len() {
4258 0 => None,
4259 1 => Some((children.into_iter().next().unwrap(), indices)),
4260 _ => Some((serde_json::Value::Array(children), indices)),
4261 };
4262 }
4263
4264 let child = parent_value.get(&child_key)?;
4266 if child.is_null() {
4267 return None;
4268 }
4269 Some((child.clone(), vec![0]))
4270}
4271
4272fn nest_child_entities_in_result(
4278 result: &mut serde_json::Map<String, serde_json::Value>,
4279 definitions: &[MappingDefinition],
4280 nesting_info: &std::collections::HashMap<String, Vec<usize>>,
4281 transaction_group: Option<&str>,
4282) {
4283 let nesting_pairs = child_entity_nesting_pairs(definitions, transaction_group);
4284
4285 for (_parent_group, parent_entity, child_entity, child_source_path) in nesting_pairs {
4286 let parent_key = to_camel_case(&parent_entity);
4287 let child_key = to_camel_case(&child_entity);
4288
4289 let child_value = match result.remove(&child_key) {
4291 Some(v) => v,
4292 None => continue,
4293 };
4294
4295 let Some(parent_value) = result.get_mut(&parent_key) else {
4300 result.insert(child_key, child_value);
4302 continue;
4303 };
4304 if parent_value.is_array() {
4305 result.insert(child_key, child_value);
4306 continue;
4307 }
4308
4309 let distribution = child_source_path
4311 .as_deref()
4312 .and_then(|sp| nesting_info.get(sp));
4313
4314 let child_items: Vec<(usize, &serde_json::Value)> = match &child_value {
4316 serde_json::Value::Array(arr) => arr.iter().enumerate().collect(),
4317 other => vec![(0, other)],
4318 };
4319
4320 let insert_or_append = |obj: &mut serde_json::Map<String, serde_json::Value>,
4323 key: &str,
4324 val: &serde_json::Value| {
4325 match obj.get_mut(key) {
4326 Some(existing) => {
4327 if !existing.is_array() {
4329 let prev = existing.take();
4330 *existing = serde_json::Value::Array(vec![prev]);
4331 }
4332 if let Some(arr) = existing.as_array_mut() {
4333 arr.push(val.clone());
4334 }
4335 }
4336 None => {
4337 obj.insert(key.to_string(), val.clone());
4338 }
4339 }
4340 };
4341
4342 if let Some(parent_map) = parent_value.as_object_mut() {
4344 if is_map_keyed_value(parent_map) {
4345 let keys: Vec<String> = parent_map.keys().cloned().collect();
4347 for (i, child_item) in &child_items {
4348 let target_idx = distribution
4349 .and_then(|dist| dist.get(*i))
4350 .copied()
4351 .unwrap_or(0);
4352 if let Some(key) = keys.get(target_idx) {
4353 if let Some(inner) = parent_map.get_mut(key).and_then(|v| v.as_object_mut())
4354 {
4355 insert_or_append(inner, &child_key, child_item);
4356 }
4357 }
4358 }
4359 continue;
4360 }
4361 }
4362
4363 if let Some(parent_arr) = parent_value.as_array_mut() {
4365 for (i, child_item) in &child_items {
4366 let target_idx = distribution
4367 .and_then(|dist| dist.get(*i))
4368 .copied()
4369 .unwrap_or(0);
4370 if let Some(parent_obj) = parent_arr
4371 .get_mut(target_idx)
4372 .and_then(|v| v.as_object_mut())
4373 {
4374 insert_or_append(parent_obj, &child_key, child_item);
4375 }
4376 }
4377 continue;
4378 }
4379
4380 if let Some(parent_obj) = parent_value.as_object_mut() {
4382 for (_i, child_item) in &child_items {
4383 insert_or_append(parent_obj, &child_key, child_item);
4384 }
4385 continue;
4386 }
4387
4388 result.insert(child_key, child_value);
4390 }
4391}
4392
4393pub(crate) fn child_entity_nesting_pairs(
4403 definitions: &[MappingDefinition],
4404 transaction_group: Option<&str>,
4405) -> Vec<(String, String, String, Option<String>)> {
4406 let mut nesting_pairs: Vec<(String, String, String, Option<String>)> = Vec::new();
4409 for def in definitions {
4410 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
4411 if parts.len() < 2 || def.meta.parent_field.is_some() {
4412 continue;
4413 }
4414 let parent_group = parts[0];
4415 if transaction_group.is_some_and(|tx| tx == parent_group) {
4421 continue;
4422 }
4423 let child_entity = def.meta.entity.clone();
4424 let child_has_parent_level_def = definitions
4428 .iter()
4429 .any(|d| d.meta.source_group == parent_group && d.meta.entity == child_entity);
4430 if child_has_parent_level_def {
4431 continue;
4432 }
4433 let parent_entity = definitions
4435 .iter()
4436 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_entity)
4437 .map(|d| d.meta.entity.clone());
4438 if let Some(ref parent_entity) = parent_entity {
4439 let child_key_lc = to_camel_case(&child_entity);
4444 let parent_defs: Vec<_> = definitions
4445 .iter()
4446 .filter(|d| d.meta.entity == *parent_entity)
4447 .collect();
4448 let has_conflicting_field = parent_defs.iter().any(|pd| {
4449 pd.fields.values().any(|fm| {
4450 let target = match fm {
4451 crate::definition::FieldMapping::Simple(t) => t.as_str(),
4452 crate::definition::FieldMapping::Structured(s) => s.target.as_str(),
4453 crate::definition::FieldMapping::Nested(_) => "",
4454 };
4455 target.starts_with(&child_key_lc)
4456 && target.get(child_key_lc.len()..child_key_lc.len() + 1) == Some(".")
4457 })
4458 });
4459 if has_conflicting_field {
4460 continue;
4461 }
4462 if nesting_pairs
4464 .iter()
4465 .any(|(_, pe, ce, _)| *pe == *parent_entity && *ce == child_entity)
4466 {
4467 continue;
4468 }
4469 nesting_pairs.push((
4470 parent_group.to_string(),
4471 parent_entity.clone(),
4472 child_entity,
4473 def.meta.source_path.clone(),
4474 ));
4475 }
4476 }
4477
4478 nesting_pairs
4479}
4480
4481fn is_map_keyed_value(map: &serde_json::Map<String, serde_json::Value>) -> bool {
4483 if map.is_empty() {
4484 return false;
4485 }
4486 map.values().all(|v| v.is_object())
4487 && map.keys().all(|k| {
4488 k.len() <= 5
4489 || k.chars()
4490 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
4491 })
4492}
4493
4494struct CodeSite<'a> {
4497 target: &'a str,
4498 parent_field: Option<&'a str>,
4504 source_path: &'a str,
4505 seg_tag: String,
4506 path_qualifier: Option<String>,
4508 disc_qualifier: Option<String>,
4510 element_idx: usize,
4511 component_idx: usize,
4512 enum_map: Option<&'a std::collections::BTreeMap<String, String>>,
4513 also_target: Option<&'a str>,
4514 also_enum_map: Option<&'a std::collections::BTreeMap<String, String>>,
4515}
4516
4517pub(crate) fn to_camel_case(name: &str) -> String {
4518 let mut chars = name.chars();
4519 match chars.next() {
4520 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
4521 None => String::new(),
4522 }
4523}
4524
4525fn set_nested_value(map: &mut serde_json::Map<String, serde_json::Value>, path: &str, val: String) {
4528 set_nested_value_json(map, path, serde_json::Value::String(val));
4529}
4530
4531fn set_nested_value_json(
4533 map: &mut serde_json::Map<String, serde_json::Value>,
4534 path: &str,
4535 val: serde_json::Value,
4536) {
4537 if let Some((prefix, leaf)) = path.rsplit_once('.') {
4538 let mut current = map;
4539 for part in prefix.split('.') {
4540 let entry = current
4541 .entry(part.to_string())
4542 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
4543 current = entry.as_object_mut().expect("expected object in path");
4544 }
4545 current.insert(leaf.to_string(), val);
4546 } else {
4547 map.insert(path.to_string(), val);
4548 }
4549}
4550
4551#[derive(serde::Serialize, serde::Deserialize)]
4556pub struct VariantCache {
4557 pub message_defs: Vec<MappingDefinition>,
4559 pub transaction_defs: BTreeMap<String, Vec<MappingDefinition>>,
4561 pub combined_defs: BTreeMap<String, Vec<MappingDefinition>>,
4563 #[serde(default)]
4565 pub code_lookups: BTreeMap<String, crate::code_lookup::CodeLookup>,
4566 #[serde(default)]
4568 pub mig_schema: Option<mig_types::schema::mig::MigSchema>,
4569 #[serde(default)]
4571 pub segment_structure: Option<crate::segment_structure::SegmentStructure>,
4572 #[serde(default)]
4575 pub pid_segment_numbers: BTreeMap<String, Vec<String>>,
4576 #[serde(default)]
4579 pub pid_requirements: BTreeMap<String, crate::pid_requirements::PidRequirements>,
4580 #[serde(default)]
4585 pub pid_ahb_workflows: BTreeMap<String, ahb_types::AhbWorkflow>,
4586 #[serde(default)]
4590 pub tx_groups: BTreeMap<String, String>,
4591}
4592
4593impl VariantCache {
4594 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
4596 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
4597 path: path.display().to_string(),
4598 message: e.to_string(),
4599 })?;
4600 if let Some(parent) = path.parent() {
4601 std::fs::create_dir_all(parent)?;
4602 }
4603 std::fs::write(path, encoded)?;
4604 Ok(())
4605 }
4606
4607 pub fn load(path: &Path) -> Result<Self, MappingError> {
4609 let bytes = std::fs::read(path)?;
4610 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
4611 path: path.display().to_string(),
4612 message: e.to_string(),
4613 })
4614 }
4615
4616 pub fn tx_group(&self, pid: &str) -> Option<&str> {
4620 self.tx_groups
4621 .get(&format!("pid_{pid}"))
4622 .map(|s| s.as_str())
4623 }
4624
4625 pub fn msg_engine(&self, pid: &str) -> MappingEngine {
4629 let mut eng = MappingEngine::from_definitions(self.message_defs.clone()).with_pid(pid);
4630 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
4631 eng = eng.with_code_lookup(cl.clone());
4632 }
4633 eng
4634 }
4635
4636 pub fn tx_engine(&self, pid: &str) -> Option<MappingEngine> {
4640 self.transaction_defs
4641 .get(&format!("pid_{pid}"))
4642 .map(|defs| {
4643 let mut eng = MappingEngine::from_definitions(defs.clone()).with_pid(pid);
4644 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
4645 eng = eng.with_code_lookup(cl.clone());
4646 }
4647 eng
4648 })
4649 }
4650
4651 pub fn filtered_mig(&self, pid: &str) -> Option<mig_types::schema::mig::MigSchema> {
4659 let mig = self.mig_schema.as_ref()?;
4660 let numbers = self
4661 .pid_segment_numbers
4662 .get(&format!("pid_{pid}"))
4663 .or_else(|| self.pid_segment_numbers.get("pid_"))?;
4664 let number_set: std::collections::HashSet<String> = numbers.iter().cloned().collect();
4665 Some(mig_assembly::pid_filter::filter_mig_for_pid(
4666 mig,
4667 &number_set,
4668 ))
4669 }
4670}
4671
4672#[derive(serde::Serialize, serde::Deserialize)]
4677pub struct DataBundle {
4678 pub format_version: String,
4679 pub bundle_version: u32,
4680 pub variants: BTreeMap<String, VariantCache>,
4681 #[serde(default)]
4686 pub bo4e_catalog: crate::bo4e_catalog::Bo4eCatalog,
4687}
4688
4689impl DataBundle {
4690 pub const CURRENT_VERSION: u32 = 2;
4691
4692 pub fn variant(&self, name: &str) -> Option<&VariantCache> {
4693 self.variants.get(name)
4694 }
4695
4696 pub fn write_to<W: std::io::Write>(&self, writer: &mut W) -> Result<(), MappingError> {
4697 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
4698 path: "<stream>".to_string(),
4699 message: e.to_string(),
4700 })?;
4701 writer.write_all(&encoded).map_err(MappingError::Io)
4702 }
4703
4704 pub fn read_from<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
4705 let mut bytes = Vec::new();
4706 reader.read_to_end(&mut bytes).map_err(MappingError::Io)?;
4707 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
4708 path: "<stream>".to_string(),
4709 message: e.to_string(),
4710 })
4711 }
4712
4713 pub fn read_from_checked<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
4714 let bundle = Self::read_from(reader)?;
4715 if bundle.bundle_version != Self::CURRENT_VERSION {
4716 return Err(MappingError::CacheRead {
4717 path: "<stream>".to_string(),
4718 message: format!(
4719 "Incompatible bundle version {}, expected version {}. \
4720 Run `edifact-data update` to fetch compatible bundles.",
4721 bundle.bundle_version,
4722 Self::CURRENT_VERSION
4723 ),
4724 });
4725 }
4726 Ok(bundle)
4727 }
4728
4729 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
4730 if let Some(parent) = path.parent() {
4731 std::fs::create_dir_all(parent)?;
4732 }
4733 let mut file = std::fs::File::create(path).map_err(MappingError::Io)?;
4734 self.write_to(&mut file)
4735 }
4736
4737 pub fn load(path: &Path) -> Result<Self, MappingError> {
4738 let mut file = std::fs::File::open(path).map_err(MappingError::Io)?;
4739 Self::read_from_checked(&mut file)
4740 }
4741}
4742
4743#[cfg(test)]
4744mod variant_cache_helper_tests {
4745 use super::*;
4746
4747 fn make_test_cache() -> VariantCache {
4748 let mut tx_groups = BTreeMap::new();
4749 tx_groups.insert("pid_55001".to_string(), "SG4".to_string());
4750 tx_groups.insert("pid_21007".to_string(), "SG14".to_string());
4751
4752 let mut transaction_defs = BTreeMap::new();
4753 transaction_defs.insert("pid_55001".to_string(), vec![]);
4754 transaction_defs.insert("pid_21007".to_string(), vec![]);
4755
4756 VariantCache {
4757 message_defs: vec![],
4758 transaction_defs,
4759 combined_defs: BTreeMap::new(),
4760 code_lookups: BTreeMap::new(),
4761 mig_schema: None,
4762 segment_structure: None,
4763 pid_segment_numbers: BTreeMap::new(),
4764 pid_requirements: BTreeMap::new(),
4765 pid_ahb_workflows: BTreeMap::new(),
4766 tx_groups,
4767 }
4768 }
4769
4770 #[test]
4771 fn test_tx_group_returns_correct_group() {
4772 let vc = make_test_cache();
4773 assert_eq!(vc.tx_group("55001").unwrap(), "SG4");
4774 assert_eq!(vc.tx_group("21007").unwrap(), "SG14");
4775 }
4776
4777 #[test]
4778 fn test_tx_group_unknown_pid_returns_none() {
4779 let vc = make_test_cache();
4780 assert!(vc.tx_group("99999").is_none());
4781 }
4782
4783 #[test]
4784 fn test_msg_engine_returns_engine() {
4785 let vc = make_test_cache();
4786 let engine = vc.msg_engine("55001");
4787 assert_eq!(engine.definitions().len(), 0);
4788 }
4789
4790 #[test]
4791 fn test_tx_engine_returns_engine_for_known_pid() {
4792 let vc = make_test_cache();
4793 assert!(vc.tx_engine("55001").is_some());
4794 }
4795
4796 #[test]
4797 fn test_tx_engine_returns_none_for_unknown_pid() {
4798 let vc = make_test_cache();
4799 assert!(vc.tx_engine("99999").is_none());
4800 }
4801
4802 fn make_populated_cache() -> VariantCache {
4806 let pids: Vec<String> = (0..40).map(|i| format!("pid_{}", 55000 + i * 7)).collect();
4807 let schema: serde_json::Value = serde_json::from_str(include_str!(
4808 "../../mig-types/src/generated/fv2504/utilmd/pids/pid_55001_schema.json"
4809 ))
4810 .unwrap();
4811 let code_lookup = crate::code_lookup::CodeLookup::from_schema_value(&schema);
4812 let element_counts: serde_json::Map<String, serde_json::Value> = (0..40)
4813 .map(|i| (format!("T{i:02}"), serde_json::json!(i)))
4814 .collect();
4815 let segment_structure: SegmentStructure =
4816 serde_json::from_value(serde_json::json!({ "element_counts": element_counts }))
4817 .unwrap();
4818 let ubs: serde_json::Map<String, serde_json::Value> = (0..40)
4819 .map(|i| (format!("UB{i}"), serde_json::json!({ "Ref": i })))
4820 .collect();
4821 let workflow: ahb_types::AhbWorkflow = serde_json::from_value(serde_json::json!({
4822 "pruefidentifikator": "55001",
4823 "description": "",
4824 "communication_direction": null,
4825 "fields": [],
4826 "ub_definitions": ubs,
4827 }))
4828 .unwrap();
4829
4830 let mut vc = make_test_cache();
4831 vc.segment_structure = Some(segment_structure);
4832 for pid in &pids {
4833 vc.transaction_defs.insert(pid.clone(), vec![]);
4834 vc.combined_defs.insert(pid.clone(), vec![]);
4835 vc.code_lookups.insert(pid.clone(), code_lookup.clone());
4836 vc.pid_segment_numbers
4837 .insert(pid.clone(), vec!["00001".to_string()]);
4838 vc.pid_ahb_workflows.insert(pid.clone(), workflow.clone());
4839 vc.tx_groups.insert(pid.clone(), "SG4".to_string());
4840 }
4841 vc
4842 }
4843
4844 #[test]
4847 fn test_enrichment_uses_codes_of_the_path_qualifier_variant() {
4848 let comp = |sub: u64, id: &str, codes: Option<serde_json::Value>| match codes {
4849 Some(c) => serde_json::json!({"sub_index": sub, "id": id, "type": "code", "codes": c}),
4850 None => serde_json::json!({"sub_index": sub, "id": id, "type": "data"}),
4851 };
4852 let code = |v: &str, n: &str| serde_json::json!([{"value": v, "name": n}]);
4853 let seg = |tag: &str, composite: &str, comps: Vec<serde_json::Value>| serde_json::json!({"id": tag, "elements": [{"index": 0, "composite": composite, "components": comps}]});
4854 let schema = serde_json::json!({"fields": {"sg15": {"segments": [
4855 seg("RFF", "C506", vec![comp(0, "1153", Some(code("Z13", "PID"))), comp(1, "1154", Some(code("21037", "RD / NB-Bewertung")))]),
4856 seg("RFF", "C506", vec![comp(0, "1153", Some(code("ACW", "Referenz"))), comp(1, "1154", None)]),
4857 seg("CAV", "C889", vec![comp(0, "7111", Some(code("Z91", "Z91"))), comp(1, "7110", Some(code("A", "Alpha")))]),
4858 seg("CAV", "C889", vec![comp(0, "7111", Some(code("ZF0", "ZF0"))), comp(1, "7110", Some(code("C", "Gamma")))]),
4859 ]}}});
4860 let engine = MappingEngine::new_empty()
4861 .with_code_lookup(crate::code_lookup::CodeLookup::from_schema_value(&schema));
4862 let def = MappingDefinition::from_toml_str(
4863 r#"
4864[meta]
4865entity = "Status"
4866bo4e_type = "Status"
4867source_group = "SG15"
4868source_path = "sg15"
4869discriminator = "RFF.0.0=Z13"
4870
4871[fields]
4872"rff.0.1" = "pruefidentifikator"
4873"rff[ACW].0.1" = "referenz"
4874"cav[Z91].0.1" = "z91Wert"
4875"cav[ZF0].0.1" = "zf0Wert"
4876"#,
4877 )
4878 .unwrap();
4879 let segment = |tag: &str, elements: &[&[&str]]| OwnedSegment {
4880 id: tag.to_string(),
4881 elements: elements
4882 .iter()
4883 .map(|e| e.iter().map(|c| c.to_string()).collect())
4884 .collect(),
4885 segment_number: 1,
4886 };
4887 let json = engine.map_forward_from_segments(
4888 &[
4889 segment("RFF", &[&["Z13", "21037"]]),
4890 segment("RFF", &[&["ACW", "REF-1"]]),
4891 segment("CAV", &[&["Z91", "C"]]),
4892 segment("CAV", &[&["ZF0", "C"]]),
4893 ],
4894 &def,
4895 );
4896 assert_eq!(
4897 json["referenz"],
4898 serde_json::json!("REF-1"),
4899 "RFF+ACW d1154 is data; RFF+Z13's codes must not apply: {json}"
4900 );
4901 assert_eq!(json["pruefidentifikator"]["meaning"], "RD / NB-Bewertung");
4902 assert_eq!(
4903 json["z91Wert"]["meaning"],
4904 serde_json::Value::Null,
4905 "'C' is a CAV+ZF0 code, unknown to CAV+Z91: {json}"
4906 );
4907 assert_eq!(json["zf0Wert"]["meaning"], "Gamma");
4908 }
4909
4910 #[test]
4911 fn test_variant_cache_serialization_is_deterministic() {
4912 let reference = serde_json::to_vec(&make_populated_cache()).unwrap();
4913 for _ in 0..5 {
4914 let again = serde_json::to_vec(&make_populated_cache()).unwrap();
4915 assert!(
4916 reference == again,
4917 "VariantCache serialization must not depend on HashMap iteration order"
4918 );
4919 }
4920 }
4921
4922 #[test]
4923 fn test_variant_cache_serializes_map_keys_sorted() {
4924 use indexmap::IndexMap;
4925 use serde::de::IgnoredAny;
4926
4927 #[derive(serde::Deserialize)]
4928 struct ProbeWorkflow {
4929 ub_definitions: IndexMap<String, IgnoredAny>,
4930 }
4931 #[derive(serde::Deserialize)]
4932 struct ProbeStructure {
4933 element_counts: IndexMap<String, usize>,
4934 }
4935 #[derive(serde::Deserialize)]
4936 struct Probe {
4937 transaction_defs: IndexMap<String, IgnoredAny>,
4938 combined_defs: IndexMap<String, IgnoredAny>,
4939 code_lookups: IndexMap<String, IndexMap<String, IgnoredAny>>,
4940 segment_structure: ProbeStructure,
4941 pid_segment_numbers: IndexMap<String, IgnoredAny>,
4942 pid_requirements: IndexMap<String, IgnoredAny>,
4943 pid_ahb_workflows: IndexMap<String, ProbeWorkflow>,
4944 tx_groups: IndexMap<String, String>,
4945 }
4946 fn assert_sorted<'a>(what: &str, keys: impl Iterator<Item = &'a String>) {
4947 let keys: Vec<&String> = keys.collect();
4948 let mut sorted = keys.clone();
4949 sorted.sort();
4950 assert_eq!(keys, sorted, "{what} keys must serialize in sorted order");
4951 }
4952
4953 let json = serde_json::to_string(&make_populated_cache()).unwrap();
4954 let probe: Probe = serde_json::from_str(&json).unwrap();
4955 assert_sorted("transaction_defs", probe.transaction_defs.keys());
4956 assert_sorted("combined_defs", probe.combined_defs.keys());
4957 assert_sorted("code_lookups", probe.code_lookups.keys());
4958 let lookup = probe.code_lookups.values().next().unwrap();
4959 assert!(lookup.len() > 10, "fixture lookup should have many entries");
4960 assert_sorted("code_lookup entries", lookup.keys());
4961 assert_sorted(
4962 "segment_structure",
4963 probe.segment_structure.element_counts.keys(),
4964 );
4965 assert_sorted("pid_segment_numbers", probe.pid_segment_numbers.keys());
4966 assert_sorted("pid_requirements", probe.pid_requirements.keys());
4967 assert_sorted("pid_ahb_workflows", probe.pid_ahb_workflows.keys());
4968 let wf = probe.pid_ahb_workflows.values().next().unwrap();
4969 assert_sorted("ub_definitions", wf.ub_definitions.keys());
4970 assert_sorted("tx_groups", probe.tx_groups.keys());
4971 }
4972
4973 #[test]
4974 fn test_data_bundle_serializes_variants_sorted() {
4975 use indexmap::IndexMap;
4976 use serde::de::IgnoredAny;
4977
4978 #[derive(serde::Deserialize)]
4979 struct Probe {
4980 variants: IndexMap<String, IgnoredAny>,
4981 }
4982 let variants: BTreeMap<String, VariantCache> = (0..20)
4983 .map(|i| (format!("VARIANT_{i:02}"), make_test_cache()))
4984 .collect();
4985 let bundle = DataBundle {
4986 format_version: "FV2504".to_string(),
4987 bundle_version: DataBundle::CURRENT_VERSION,
4988 variants,
4989 bo4e_catalog: Default::default(),
4990 };
4991 let mut bytes = Vec::new();
4992 bundle.write_to(&mut bytes).unwrap();
4993 let probe: Probe = serde_json::from_slice(&bytes).unwrap();
4994 let keys: Vec<&String> = probe.variants.keys().collect();
4995 let mut sorted = keys.clone();
4996 sorted.sort();
4997 assert_eq!(keys, sorted);
4998 }
4999}
5000
5001#[cfg(test)]
5002mod tests {
5003 use super::*;
5004 use crate::definition::{MappingDefinition, MappingMeta, StructuredFieldMapping};
5005 use indexmap::IndexMap;
5006
5007 fn make_def(fields: IndexMap<String, FieldMapping>) -> MappingDefinition {
5008 MappingDefinition {
5009 meta: MappingMeta {
5010 entity: "Test".to_string(),
5011 bo4e_type: "Test".to_string(),
5012 source_group: "SG4".to_string(),
5013 source_path: None,
5014 discriminator: None,
5015 repeat_on_tag: None,
5016 parent_field: None,
5017 target_list: None,
5018 order: None,
5019 },
5020 fields,
5021 complex_handlers: None,
5022 }
5023 }
5024
5025 #[test]
5026 fn test_map_interchange_single_transaction_backward_compat() {
5027 use mig_assembly::assembler::*;
5028
5029 let tree = AssembledTree {
5031 segments: vec![
5032 AssembledSegment {
5033 tag: "UNH".to_string(),
5034 elements: vec![vec!["001".to_string()]],
5035 mig_number: None,
5036 segment_number: None,
5037 },
5038 AssembledSegment {
5039 tag: "BGM".to_string(),
5040 elements: vec![vec!["E01".to_string()], vec!["DOC001".to_string()]],
5041 mig_number: None,
5042 segment_number: None,
5043 },
5044 ],
5045 groups: vec![
5046 AssembledGroup {
5047 group_id: "SG2".to_string(),
5048 repetitions: vec![AssembledGroupInstance {
5049 segments: vec![AssembledSegment {
5050 tag: "NAD".to_string(),
5051 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
5052 mig_number: None,
5053 segment_number: None,
5054 }],
5055 child_groups: vec![],
5056 entry_mig_number: None,
5057 variant_mig_numbers: vec![],
5058 skipped_segments: vec![],
5059 skipped_positions: Vec::new(),
5060 }],
5061 },
5062 AssembledGroup {
5063 group_id: "SG4".to_string(),
5064 repetitions: vec![AssembledGroupInstance {
5065 segments: vec![AssembledSegment {
5066 tag: "IDE".to_string(),
5067 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5068 mig_number: None,
5069 segment_number: None,
5070 }],
5071 child_groups: vec![AssembledGroup {
5072 group_id: "SG5".to_string(),
5073 repetitions: vec![AssembledGroupInstance {
5074 segments: vec![AssembledSegment {
5075 tag: "LOC".to_string(),
5076 elements: vec![
5077 vec!["Z16".to_string()],
5078 vec!["DE000111222333".to_string()],
5079 ],
5080 mig_number: None,
5081 segment_number: None,
5082 }],
5083 child_groups: vec![],
5084 entry_mig_number: None,
5085 variant_mig_numbers: vec![],
5086 skipped_segments: vec![],
5087 skipped_positions: Vec::new(),
5088 }],
5089 }],
5090 entry_mig_number: None,
5091 variant_mig_numbers: vec![],
5092 skipped_segments: vec![],
5093 skipped_positions: Vec::new(),
5094 }],
5095 },
5096 ],
5097 post_group_start: 2,
5098 inter_group_segments: std::collections::BTreeMap::new(),
5099 };
5100
5101 let msg_engine = MappingEngine::from_definitions(vec![]);
5103
5104 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5106 tx_fields.insert(
5107 "ide.1".to_string(),
5108 FieldMapping::Simple("vorgangId".to_string()),
5109 );
5110 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5111 malo_fields.insert(
5112 "loc.1".to_string(),
5113 FieldMapping::Simple("marktlokationsId".to_string()),
5114 );
5115
5116 let tx_engine = MappingEngine::from_definitions(vec![
5117 MappingDefinition {
5118 meta: MappingMeta {
5119 entity: "Prozessdaten".to_string(),
5120 bo4e_type: "Prozessdaten".to_string(),
5121 source_group: "SG4".to_string(),
5122 source_path: None,
5123 discriminator: None,
5124 repeat_on_tag: None,
5125 parent_field: None,
5126 target_list: None,
5127 order: None,
5128 },
5129 fields: tx_fields,
5130 complex_handlers: None,
5131 },
5132 MappingDefinition {
5133 meta: MappingMeta {
5134 entity: "Marktlokation".to_string(),
5135 bo4e_type: "Marktlokation".to_string(),
5136 source_group: "SG4.SG5".to_string(),
5137 source_path: None,
5138 discriminator: None,
5139 repeat_on_tag: None,
5140 parent_field: None,
5141 target_list: None,
5142 order: None,
5143 },
5144 fields: malo_fields,
5145 complex_handlers: None,
5146 },
5147 ]);
5148
5149 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
5150
5151 assert_eq!(result.transaktionen.len(), 1);
5152 assert_eq!(
5153 result.transaktionen[0].transaktionsdaten["vorgangId"]
5154 .as_str()
5155 .unwrap(),
5156 "TX001"
5157 );
5158 assert_eq!(
5161 result.transaktionen[0].stammdaten["marktlokation"]["marktlokationsId"]
5162 .as_str()
5163 .unwrap(),
5164 "DE000111222333"
5165 );
5166 }
5167
5168 #[test]
5169 fn test_map_reverse_pads_intermediate_empty_elements() {
5170 let mut fields = IndexMap::new();
5172 fields.insert(
5173 "nad.0".to_string(),
5174 FieldMapping::Structured(StructuredFieldMapping {
5175 target: String::new(),
5176 transform: None,
5177 when: None,
5178 default: Some("Z09".to_string()),
5179 enum_map: None,
5180 when_filled: None,
5181 also_target: None,
5182 also_enum_map: None,
5183 }),
5184 );
5185 fields.insert(
5186 "nad.3.0".to_string(),
5187 FieldMapping::Simple("name".to_string()),
5188 );
5189 fields.insert(
5190 "nad.3.1".to_string(),
5191 FieldMapping::Simple("vorname".to_string()),
5192 );
5193
5194 let def = make_def(fields);
5195 let engine = MappingEngine::from_definitions(vec![]);
5196
5197 let bo4e = serde_json::json!({
5198 "name": "Muster",
5199 "vorname": "Max"
5200 });
5201
5202 let instance = engine.map_reverse(&bo4e, &def);
5203 assert_eq!(instance.segments.len(), 1);
5204
5205 let nad = &instance.segments[0];
5206 assert_eq!(nad.tag, "NAD");
5207 assert_eq!(nad.elements.len(), 4);
5208 assert_eq!(nad.elements[0], vec!["Z09"]);
5209 assert_eq!(nad.elements[1], vec![""]);
5211 assert_eq!(nad.elements[2], vec![""]);
5212 assert_eq!(nad.elements[3][0], "Muster");
5213 assert_eq!(nad.elements[3][1], "Max");
5214 }
5215
5216 #[test]
5217 fn test_map_reverse_no_padding_when_contiguous() {
5218 let mut fields = IndexMap::new();
5220 fields.insert(
5221 "dtm.0.0".to_string(),
5222 FieldMapping::Structured(StructuredFieldMapping {
5223 target: String::new(),
5224 transform: None,
5225 when: None,
5226 default: Some("92".to_string()),
5227 enum_map: None,
5228 when_filled: None,
5229 also_target: None,
5230 also_enum_map: None,
5231 }),
5232 );
5233 fields.insert(
5234 "dtm.0.1".to_string(),
5235 FieldMapping::Simple("value".to_string()),
5236 );
5237 fields.insert(
5238 "dtm.0.2".to_string(),
5239 FieldMapping::Structured(StructuredFieldMapping {
5240 target: String::new(),
5241 transform: None,
5242 when: None,
5243 default: Some("303".to_string()),
5244 enum_map: None,
5245 when_filled: None,
5246 also_target: None,
5247 also_enum_map: None,
5248 }),
5249 );
5250
5251 let def = make_def(fields);
5252 let engine = MappingEngine::from_definitions(vec![]);
5253
5254 let bo4e = serde_json::json!({ "value": "20250531" });
5255
5256 let instance = engine.map_reverse(&bo4e, &def);
5257 let dtm = &instance.segments[0];
5258 assert_eq!(dtm.elements.len(), 1);
5260 assert_eq!(dtm.elements[0], vec!["92", "20250531", "303"]);
5261 }
5262
5263 #[test]
5264 fn test_map_message_level_extracts_sg2_only() {
5265 use mig_assembly::assembler::*;
5266
5267 let tree = AssembledTree {
5269 segments: vec![
5270 AssembledSegment {
5271 tag: "UNH".to_string(),
5272 elements: vec![vec!["001".to_string()]],
5273 mig_number: None,
5274 segment_number: None,
5275 },
5276 AssembledSegment {
5277 tag: "BGM".to_string(),
5278 elements: vec![vec!["E01".to_string()]],
5279 mig_number: None,
5280 segment_number: None,
5281 },
5282 ],
5283 groups: vec![
5284 AssembledGroup {
5285 group_id: "SG2".to_string(),
5286 repetitions: vec![AssembledGroupInstance {
5287 segments: vec![AssembledSegment {
5288 tag: "NAD".to_string(),
5289 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
5290 mig_number: None,
5291 segment_number: None,
5292 }],
5293 child_groups: vec![],
5294 entry_mig_number: None,
5295 variant_mig_numbers: vec![],
5296 skipped_segments: vec![],
5297 skipped_positions: Vec::new(),
5298 }],
5299 },
5300 AssembledGroup {
5301 group_id: "SG4".to_string(),
5302 repetitions: vec![AssembledGroupInstance {
5303 segments: vec![AssembledSegment {
5304 tag: "IDE".to_string(),
5305 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5306 mig_number: None,
5307 segment_number: None,
5308 }],
5309 child_groups: vec![],
5310 entry_mig_number: None,
5311 variant_mig_numbers: vec![],
5312 skipped_segments: vec![],
5313 skipped_positions: Vec::new(),
5314 }],
5315 },
5316 ],
5317 post_group_start: 2,
5318 inter_group_segments: std::collections::BTreeMap::new(),
5319 };
5320
5321 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5323 msg_fields.insert(
5324 "nad.0".to_string(),
5325 FieldMapping::Simple("marktrolle".to_string()),
5326 );
5327 msg_fields.insert(
5328 "nad.1".to_string(),
5329 FieldMapping::Simple("rollencodenummer".to_string()),
5330 );
5331 let msg_def = MappingDefinition {
5332 meta: MappingMeta {
5333 entity: "Marktteilnehmer".to_string(),
5334 bo4e_type: "Marktteilnehmer".to_string(),
5335 source_group: "SG2".to_string(),
5336 source_path: None,
5337 discriminator: None,
5338 repeat_on_tag: None,
5339 parent_field: None,
5340 target_list: None,
5341 order: None,
5342 },
5343 fields: msg_fields,
5344 complex_handlers: None,
5345 };
5346
5347 let engine = MappingEngine::from_definitions(vec![msg_def.clone()]);
5348 let result = engine.map_all_forward(&tree);
5349
5350 assert!(result.get("marktteilnehmer").is_some());
5352 let mt = &result["marktteilnehmer"];
5353 assert_eq!(mt["marktrolle"].as_str().unwrap(), "MS");
5354 assert_eq!(mt["rollencodenummer"].as_str().unwrap(), "9900123");
5355 }
5356
5357 #[test]
5358 fn test_map_transaction_scoped_to_sg4_instance() {
5359 use mig_assembly::assembler::*;
5360
5361 let tree = AssembledTree {
5363 segments: vec![
5364 AssembledSegment {
5365 tag: "UNH".to_string(),
5366 elements: vec![vec!["001".to_string()]],
5367 mig_number: None,
5368 segment_number: None,
5369 },
5370 AssembledSegment {
5371 tag: "BGM".to_string(),
5372 elements: vec![vec!["E01".to_string()]],
5373 mig_number: None,
5374 segment_number: None,
5375 },
5376 ],
5377 groups: vec![AssembledGroup {
5378 group_id: "SG4".to_string(),
5379 repetitions: vec![AssembledGroupInstance {
5380 segments: vec![AssembledSegment {
5381 tag: "IDE".to_string(),
5382 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5383 mig_number: None,
5384 segment_number: None,
5385 }],
5386 child_groups: vec![AssembledGroup {
5387 group_id: "SG5".to_string(),
5388 repetitions: vec![AssembledGroupInstance {
5389 segments: vec![AssembledSegment {
5390 tag: "LOC".to_string(),
5391 elements: vec![
5392 vec!["Z16".to_string()],
5393 vec!["DE000111222333".to_string()],
5394 ],
5395 mig_number: None,
5396 segment_number: None,
5397 }],
5398 child_groups: vec![],
5399 entry_mig_number: None,
5400 variant_mig_numbers: vec![],
5401 skipped_segments: vec![],
5402 skipped_positions: Vec::new(),
5403 }],
5404 }],
5405 entry_mig_number: None,
5406 variant_mig_numbers: vec![],
5407 skipped_segments: vec![],
5408 skipped_positions: Vec::new(),
5409 }],
5410 }],
5411 post_group_start: 2,
5412 inter_group_segments: std::collections::BTreeMap::new(),
5413 };
5414
5415 let mut proz_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5417 proz_fields.insert(
5418 "ide.1".to_string(),
5419 FieldMapping::Simple("vorgangId".to_string()),
5420 );
5421 let proz_def = MappingDefinition {
5422 meta: MappingMeta {
5423 entity: "Prozessdaten".to_string(),
5424 bo4e_type: "Prozessdaten".to_string(),
5425 source_group: "".to_string(), source_path: None,
5427 discriminator: None,
5428 repeat_on_tag: None,
5429 parent_field: None,
5430 target_list: None,
5431 order: None,
5432 },
5433 fields: proz_fields,
5434 complex_handlers: None,
5435 };
5436
5437 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5438 malo_fields.insert(
5439 "loc.1".to_string(),
5440 FieldMapping::Simple("marktlokationsId".to_string()),
5441 );
5442 let malo_def = MappingDefinition {
5443 meta: MappingMeta {
5444 entity: "Marktlokation".to_string(),
5445 bo4e_type: "Marktlokation".to_string(),
5446 source_group: "SG5".to_string(), source_path: None,
5448 discriminator: None,
5449 repeat_on_tag: None,
5450 parent_field: None,
5451 target_list: None,
5452 order: None,
5453 },
5454 fields: malo_fields,
5455 complex_handlers: None,
5456 };
5457
5458 let tx_engine = MappingEngine::from_definitions(vec![proz_def, malo_def]);
5459
5460 let sg4 = &tree.groups[0]; let sg4_instance = &sg4.repetitions[0];
5463 let sub_tree = sg4_instance.as_assembled_tree();
5464
5465 let result = tx_engine.map_all_forward(&sub_tree);
5466
5467 assert_eq!(
5469 result["prozessdaten"]["vorgangId"].as_str().unwrap(),
5470 "TX001"
5471 );
5472
5473 assert_eq!(
5475 result["marktlokation"]["marktlokationsId"]
5476 .as_str()
5477 .unwrap(),
5478 "DE000111222333"
5479 );
5480 }
5481
5482 #[test]
5483 fn test_map_interchange_produces_full_hierarchy() {
5484 use mig_assembly::assembler::*;
5485
5486 let tree = AssembledTree {
5488 segments: vec![
5489 AssembledSegment {
5490 tag: "UNH".to_string(),
5491 elements: vec![vec!["001".to_string()]],
5492 mig_number: None,
5493 segment_number: None,
5494 },
5495 AssembledSegment {
5496 tag: "BGM".to_string(),
5497 elements: vec![vec!["E01".to_string()]],
5498 mig_number: None,
5499 segment_number: None,
5500 },
5501 ],
5502 groups: vec![
5503 AssembledGroup {
5504 group_id: "SG2".to_string(),
5505 repetitions: vec![AssembledGroupInstance {
5506 segments: vec![AssembledSegment {
5507 tag: "NAD".to_string(),
5508 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
5509 mig_number: None,
5510 segment_number: None,
5511 }],
5512 child_groups: vec![],
5513 entry_mig_number: None,
5514 variant_mig_numbers: vec![],
5515 skipped_segments: vec![],
5516 skipped_positions: Vec::new(),
5517 }],
5518 },
5519 AssembledGroup {
5520 group_id: "SG4".to_string(),
5521 repetitions: vec![
5522 AssembledGroupInstance {
5523 segments: vec![AssembledSegment {
5524 tag: "IDE".to_string(),
5525 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5526 mig_number: None,
5527 segment_number: None,
5528 }],
5529 child_groups: vec![],
5530 entry_mig_number: None,
5531 variant_mig_numbers: vec![],
5532 skipped_segments: vec![],
5533 skipped_positions: Vec::new(),
5534 },
5535 AssembledGroupInstance {
5536 segments: vec![AssembledSegment {
5537 tag: "IDE".to_string(),
5538 elements: vec![vec!["24".to_string()], vec!["TX002".to_string()]],
5539 mig_number: None,
5540 segment_number: None,
5541 }],
5542 child_groups: vec![],
5543 entry_mig_number: None,
5544 variant_mig_numbers: vec![],
5545 skipped_segments: vec![],
5546 skipped_positions: Vec::new(),
5547 },
5548 ],
5549 },
5550 ],
5551 post_group_start: 2,
5552 inter_group_segments: std::collections::BTreeMap::new(),
5553 };
5554
5555 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5557 msg_fields.insert(
5558 "nad.0".to_string(),
5559 FieldMapping::Simple("marktrolle".to_string()),
5560 );
5561 let msg_defs = vec![MappingDefinition {
5562 meta: MappingMeta {
5563 entity: "Marktteilnehmer".to_string(),
5564 bo4e_type: "Marktteilnehmer".to_string(),
5565 source_group: "SG2".to_string(),
5566 source_path: None,
5567 discriminator: None,
5568 repeat_on_tag: None,
5569 parent_field: None,
5570 target_list: None,
5571 order: None,
5572 },
5573 fields: msg_fields,
5574 complex_handlers: None,
5575 }];
5576
5577 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5579 tx_fields.insert(
5580 "ide.1".to_string(),
5581 FieldMapping::Simple("vorgangId".to_string()),
5582 );
5583 let tx_defs = vec![MappingDefinition {
5584 meta: MappingMeta {
5585 entity: "Prozessdaten".to_string(),
5586 bo4e_type: "Prozessdaten".to_string(),
5587 source_group: "SG4".to_string(),
5588 source_path: None,
5589 discriminator: None,
5590 repeat_on_tag: None,
5591 parent_field: None,
5592 target_list: None,
5593 order: None,
5594 },
5595 fields: tx_fields,
5596 complex_handlers: None,
5597 }];
5598
5599 let msg_engine = MappingEngine::from_definitions(msg_defs);
5600 let tx_engine = MappingEngine::from_definitions(tx_defs);
5601
5602 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
5603
5604 assert!(result.stammdaten["marktteilnehmer"].is_object());
5606 assert_eq!(
5607 result.stammdaten["marktteilnehmer"]["marktrolle"]
5608 .as_str()
5609 .unwrap(),
5610 "MS"
5611 );
5612
5613 assert_eq!(result.transaktionen.len(), 2);
5615 assert_eq!(
5616 result.transaktionen[0].transaktionsdaten["vorgangId"]
5617 .as_str()
5618 .unwrap(),
5619 "TX001"
5620 );
5621 assert_eq!(
5622 result.transaktionen[1].transaktionsdaten["vorgangId"]
5623 .as_str()
5624 .unwrap(),
5625 "TX002"
5626 );
5627 }
5628
5629 #[test]
5630 fn test_map_reverse_with_segment_structure_pads_trailing() {
5631 let mut fields = IndexMap::new();
5633 fields.insert(
5634 "sts.0".to_string(),
5635 FieldMapping::Structured(StructuredFieldMapping {
5636 target: String::new(),
5637 transform: None,
5638 when: None,
5639 default: Some("7".to_string()),
5640 enum_map: None,
5641 when_filled: None,
5642 also_target: None,
5643 also_enum_map: None,
5644 }),
5645 );
5646 fields.insert(
5647 "sts.2".to_string(),
5648 FieldMapping::Simple("grund".to_string()),
5649 );
5650
5651 let def = make_def(fields);
5652
5653 let mut counts = std::collections::BTreeMap::new();
5655 counts.insert("STS".to_string(), 5usize);
5656 let ss = SegmentStructure {
5657 element_counts: counts,
5658 };
5659
5660 let engine = MappingEngine::from_definitions(vec![]).with_segment_structure(ss);
5661
5662 let bo4e = serde_json::json!({ "grund": "E01" });
5663
5664 let instance = engine.map_reverse(&bo4e, &def);
5665 let sts = &instance.segments[0];
5666 assert_eq!(sts.elements.len(), 5);
5669 assert_eq!(sts.elements[0], vec!["7"]);
5670 assert_eq!(sts.elements[1], vec![""]);
5671 assert_eq!(sts.elements[2], vec!["E01"]);
5672 assert_eq!(sts.elements[3], vec![""]);
5673 assert_eq!(sts.elements[4], vec![""]);
5674 }
5675
5676 #[test]
5677 fn test_resolve_child_relative_with_source_path() {
5678 let mut map: std::collections::HashMap<String, Vec<usize>> =
5679 std::collections::HashMap::new();
5680 map.insert("sg4.sg8_ze1".to_string(), vec![6]);
5681 map.insert("sg4.sg8_z98".to_string(), vec![0]);
5682
5683 assert_eq!(
5685 resolve_child_relative("SG8.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5686 "SG8:6.SG10"
5687 );
5688
5689 assert_eq!(
5691 resolve_child_relative("SG8:3.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5692 "SG8:3.SG10"
5693 );
5694
5695 assert_eq!(
5697 resolve_child_relative("SG8.SG10", Some("sg4.sg8_unknown.sg10"), &map, 0),
5698 "SG8.SG10"
5699 );
5700
5701 assert_eq!(
5703 resolve_child_relative("SG8.SG10", None, &map, 0),
5704 "SG8.SG10"
5705 );
5706
5707 assert_eq!(
5709 resolve_child_relative("SG8.SG9", Some("sg4.sg8_z98.sg9"), &map, 0),
5710 "SG8:0.SG9"
5711 );
5712
5713 map.insert("sg4.sg8_zf3".to_string(), vec![3, 4]);
5715 assert_eq!(
5716 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 0),
5717 "SG8:3.SG10"
5718 );
5719 assert_eq!(
5720 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 1),
5721 "SG8:4.SG10"
5722 );
5723 }
5724
5725 #[test]
5726 fn test_place_in_groups_returns_rep_index() {
5727 let mut groups: Vec<AssembledGroup> = Vec::new();
5728
5729 let instance = AssembledGroupInstance {
5731 segments: vec![],
5732 child_groups: vec![],
5733 entry_mig_number: None,
5734 variant_mig_numbers: vec![],
5735 skipped_segments: vec![],
5736 skipped_positions: Vec::new(),
5737 };
5738 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 0);
5739
5740 let instance = AssembledGroupInstance {
5742 segments: vec![],
5743 child_groups: vec![],
5744 entry_mig_number: None,
5745 variant_mig_numbers: vec![],
5746 skipped_segments: vec![],
5747 skipped_positions: Vec::new(),
5748 };
5749 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 1);
5750
5751 let instance = AssembledGroupInstance {
5753 segments: vec![],
5754 child_groups: vec![],
5755 entry_mig_number: None,
5756 variant_mig_numbers: vec![],
5757 skipped_segments: vec![],
5758 skipped_positions: Vec::new(),
5759 };
5760 assert_eq!(place_in_groups(&mut groups, "SG8:5", instance), 5);
5761 }
5762
5763 #[test]
5764 fn test_resolve_by_source_path() {
5765 use mig_assembly::assembler::*;
5766
5767 let tree = AssembledTree {
5769 segments: vec![],
5770 groups: vec![AssembledGroup {
5771 group_id: "SG4".to_string(),
5772 repetitions: vec![AssembledGroupInstance {
5773 segments: vec![],
5774 child_groups: vec![AssembledGroup {
5775 group_id: "SG8".to_string(),
5776 repetitions: vec![
5777 AssembledGroupInstance {
5778 segments: vec![AssembledSegment {
5779 tag: "SEQ".to_string(),
5780 elements: vec![vec!["Z98".to_string()]],
5781 mig_number: None,
5782 segment_number: None,
5783 }],
5784 child_groups: vec![AssembledGroup {
5785 group_id: "SG10".to_string(),
5786 repetitions: vec![AssembledGroupInstance {
5787 segments: vec![AssembledSegment {
5788 tag: "CCI".to_string(),
5789 elements: vec![vec![], vec![], vec!["ZB3".to_string()]],
5790 mig_number: None,
5791 segment_number: None,
5792 }],
5793 child_groups: vec![],
5794 entry_mig_number: None,
5795 variant_mig_numbers: vec![],
5796 skipped_segments: vec![],
5797 skipped_positions: Vec::new(),
5798 }],
5799 }],
5800 entry_mig_number: None,
5801 variant_mig_numbers: vec![],
5802 skipped_segments: vec![],
5803 skipped_positions: Vec::new(),
5804 },
5805 AssembledGroupInstance {
5806 segments: vec![AssembledSegment {
5807 tag: "SEQ".to_string(),
5808 elements: vec![vec!["ZD7".to_string()]],
5809 mig_number: None,
5810 segment_number: None,
5811 }],
5812 child_groups: vec![AssembledGroup {
5813 group_id: "SG10".to_string(),
5814 repetitions: vec![AssembledGroupInstance {
5815 segments: vec![AssembledSegment {
5816 tag: "CCI".to_string(),
5817 elements: vec![vec![], vec![], vec!["ZE6".to_string()]],
5818 mig_number: None,
5819 segment_number: None,
5820 }],
5821 child_groups: vec![],
5822 entry_mig_number: None,
5823 variant_mig_numbers: vec![],
5824 skipped_segments: vec![],
5825 skipped_positions: Vec::new(),
5826 }],
5827 }],
5828 entry_mig_number: None,
5829 variant_mig_numbers: vec![],
5830 skipped_segments: vec![],
5831 skipped_positions: Vec::new(),
5832 },
5833 ],
5834 }],
5835 entry_mig_number: None,
5836 variant_mig_numbers: vec![],
5837 skipped_segments: vec![],
5838 skipped_positions: Vec::new(),
5839 }],
5840 }],
5841 post_group_start: 0,
5842 inter_group_segments: std::collections::BTreeMap::new(),
5843 };
5844
5845 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_z98.sg10");
5847 assert!(inst.is_some());
5848 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
5849
5850 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zd7.sg10");
5852 assert!(inst.is_some());
5853 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZE6");
5854
5855 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zzz.sg10");
5857 assert!(inst.is_none());
5858
5859 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8.sg10");
5861 assert!(inst.is_some());
5862 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
5863 }
5864
5865 #[test]
5866 fn test_parse_source_path_part() {
5867 assert_eq!(parse_source_path_part("sg4"), ("sg4", None));
5868 assert_eq!(parse_source_path_part("sg8_z98"), ("sg8", Some("z98")));
5869 assert_eq!(parse_source_path_part("sg10"), ("sg10", None));
5870 assert_eq!(parse_source_path_part("sg12_z04"), ("sg12", Some("z04")));
5871 }
5872
5873 #[test]
5874 fn test_has_source_path_qualifiers() {
5875 assert!(has_source_path_qualifiers("sg4.sg8_z98.sg10"));
5876 assert!(has_source_path_qualifiers("sg4.sg8_ze1.sg9"));
5877 assert!(!has_source_path_qualifiers("sg4.sg6"));
5878 assert!(!has_source_path_qualifiers("sg4.sg8.sg10"));
5879 }
5880
5881 #[test]
5882 fn test_extract_all_from_instance_collects_all_qualifier_matches() {
5883 use mig_assembly::assembler::*;
5884
5885 let instance = AssembledGroupInstance {
5887 segments: vec![
5888 AssembledSegment {
5889 tag: "SEQ".to_string(),
5890 elements: vec![vec!["ZD6".to_string()]],
5891 mig_number: None,
5892 segment_number: None,
5893 },
5894 AssembledSegment {
5895 tag: "RFF".to_string(),
5896 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
5897 mig_number: None,
5898 segment_number: None,
5899 },
5900 AssembledSegment {
5901 tag: "RFF".to_string(),
5902 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
5903 mig_number: None,
5904 segment_number: None,
5905 },
5906 AssembledSegment {
5907 tag: "RFF".to_string(),
5908 elements: vec![vec!["Z34".to_string(), "REF_C".to_string()]],
5909 mig_number: None,
5910 segment_number: None,
5911 },
5912 AssembledSegment {
5913 tag: "RFF".to_string(),
5914 elements: vec![vec!["Z35".to_string(), "OTHER".to_string()]],
5915 mig_number: None,
5916 segment_number: None,
5917 },
5918 ],
5919 child_groups: vec![],
5920 entry_mig_number: None,
5921 variant_mig_numbers: vec![],
5922 skipped_segments: vec![],
5923 skipped_positions: Vec::new(),
5924 };
5925
5926 let all = MappingEngine::extract_all_from_instance(&instance, "rff[Z34,*].0.1");
5928 assert_eq!(all, vec!["REF_A", "REF_B", "REF_C"]);
5929
5930 let single = MappingEngine::extract_from_instance(&instance, "rff[Z34].0.1");
5932 assert_eq!(single, Some("REF_A".to_string()));
5933
5934 let second = MappingEngine::extract_from_instance(&instance, "rff[Z34,1].0.1");
5935 assert_eq!(second, Some("REF_B".to_string()));
5936 }
5937}