1use std::collections::{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 =
66 toml::from_str(&content).map_err(|e| MappingError::TomlParse {
67 file: path.display().to_string(),
68 message: e.to_string(),
69 })?;
70 definitions.push(def);
71 }
72 }
73
74 Ok(Self {
75 definitions,
76 segment_structure: None,
77 code_lookup: None,
78 transaction_group: None,
79 current_pid: None,
80 })
81 }
82
83 pub fn load_split(
89 message_dir: &Path,
90 transaction_dir: &Path,
91 ) -> Result<(Self, Self), MappingError> {
92 let msg_engine = Self::load(message_dir)?;
93 let tx_engine = Self::load(transaction_dir)?;
94 Ok((msg_engine, tx_engine))
95 }
96
97 pub fn load_merged(dirs: &[&Path]) -> Result<Self, MappingError> {
102 let mut definitions = Vec::new();
103 for dir in dirs {
104 let engine = Self::load(dir)?;
105 definitions.extend(engine.definitions);
106 }
107 Ok(Self {
108 definitions,
109 segment_structure: None,
110 code_lookup: None,
111 transaction_group: None,
112 current_pid: None,
113 })
114 }
115
116 pub fn load_with_common(
125 common_dir: &Path,
126 pid_dir: &Path,
127 schema_index: &crate::pid_schema_index::PidSchemaIndex,
128 ) -> Result<Self, MappingError> {
129 let mut common_defs = Self::load(common_dir)?.definitions;
130
131 common_defs.retain(|d| {
133 d.meta
134 .source_path
135 .as_deref()
136 .map(|sp| schema_index.has_group(sp))
137 .unwrap_or(true)
138 });
139
140 let pid_defs = Self::load(pid_dir)?.definitions;
141
142 let normalize_sg = |sg: &str| -> String {
147 sg.split('.')
148 .map(|part| part.split(':').next().unwrap_or(part))
149 .collect::<Vec<_>>()
150 .join(".")
151 };
152 let pid_keys: HashSet<(String, Option<String>)> = pid_defs
153 .iter()
154 .flat_map(|d| {
155 let sg = normalize_sg(&d.meta.source_group);
156 let disc = d.meta.discriminator.clone();
157 let mut keys = vec![(sg.clone(), disc.clone())];
158 if let Some(ref disc_str) = disc {
160 if let Some(base) = disc_str.rsplit_once('#') {
161 if base.1.chars().all(|c| c.is_ascii_digit()) {
162 keys.push((sg, Some(base.0.to_string())));
163 }
164 }
165 }
166 keys
167 })
168 .collect();
169
170 common_defs.retain(|d| {
172 let key = (
173 normalize_sg(&d.meta.source_group),
174 d.meta.discriminator.clone(),
175 );
176 !pid_keys.contains(&key)
177 });
178
179 let mut definitions = common_defs;
181 definitions.extend(pid_defs);
182
183 Ok(Self {
184 definitions,
185 segment_structure: None,
186 code_lookup: None,
187 transaction_group: None,
188 current_pid: None,
189 })
190 }
191
192 pub fn load_common_only(
196 common_dir: &Path,
197 schema_index: &crate::pid_schema_index::PidSchemaIndex,
198 ) -> Result<Self, MappingError> {
199 let mut common_defs = Self::load(common_dir)?.definitions;
200
201 common_defs.retain(|d| {
203 d.meta
204 .source_path
205 .as_deref()
206 .map(|sp| schema_index.has_group(sp))
207 .unwrap_or(true)
208 });
209
210 Ok(Self {
211 definitions: common_defs,
212 segment_structure: None,
213 code_lookup: None,
214 transaction_group: None,
215 current_pid: None,
216 })
217 }
218
219 pub fn load_split_with_common(
224 message_dir: &Path,
225 common_dir: &Path,
226 transaction_dir: &Path,
227 schema_index: &crate::pid_schema_index::PidSchemaIndex,
228 ) -> Result<(Self, Self), MappingError> {
229 let msg_engine = Self::load(message_dir)?;
230 let tx_engine = Self::load_with_common(common_dir, transaction_dir, schema_index)?;
231 Ok((msg_engine, tx_engine))
232 }
233
234 pub fn from_definitions(definitions: Vec<MappingDefinition>) -> Self {
236 Self {
237 definitions,
238 segment_structure: None,
239 code_lookup: None,
240 transaction_group: None,
241 current_pid: None,
242 }
243 }
244
245 pub fn save_cached(&self, path: &Path) -> Result<(), MappingError> {
251 let encoded =
252 serde_json::to_vec(&self.definitions).map_err(|e| MappingError::CacheWrite {
253 path: path.display().to_string(),
254 message: e.to_string(),
255 })?;
256 if let Some(parent) = path.parent() {
257 std::fs::create_dir_all(parent)?;
258 }
259 std::fs::write(path, encoded)?;
260 Ok(())
261 }
262
263 pub fn load_cached_or_toml(cache_path: &Path, toml_dir: &Path) -> Result<Self, MappingError> {
268 if cache_path.exists() {
269 Self::load_cached(cache_path)
270 } else {
271 Self::load(toml_dir)
272 }
273 }
274
275 pub fn load_cached(path: &Path) -> Result<Self, MappingError> {
280 let bytes = std::fs::read(path)?;
281 let definitions: Vec<MappingDefinition> =
282 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
283 path: path.display().to_string(),
284 message: e.to_string(),
285 })?;
286 Ok(Self {
287 definitions,
288 segment_structure: None,
289 code_lookup: None,
290 transaction_group: None,
291 current_pid: None,
292 })
293 }
294
295 pub fn with_segment_structure(mut self, ss: SegmentStructure) -> Self {
300 self.segment_structure = Some(ss);
301 self
302 }
303
304 pub fn with_code_lookup(mut self, cl: crate::code_lookup::CodeLookup) -> Self {
309 self.code_lookup = Some(cl);
310 self
311 }
312
313 pub fn with_pid(mut self, pid: impl Into<String>) -> Self {
321 self.current_pid = Some(pid.into());
322 self
323 }
324
325 pub fn with_path_resolver(mut self, resolver: crate::path_resolver::PathResolver) -> Self {
331 for def in &mut self.definitions {
332 def.normalize_paths(&resolver);
333 }
334 self
335 }
336
337 pub fn with_transaction_group(mut self, tx: impl Into<String>) -> Self {
344 self.transaction_group = Some(tx.into());
345 self
346 }
347
348 pub fn definitions(&self) -> &[MappingDefinition] {
350 &self.definitions
351 }
352
353 pub fn definition_for_entity(&self, entity: &str) -> Option<&MappingDefinition> {
355 self.definitions.iter().find(|d| d.meta.entity == entity)
356 }
357
358 pub fn extract_field(
367 &self,
368 tree: &AssembledTree,
369 group_path: &str,
370 path: &str,
371 repetition: usize,
372 ) -> Option<String> {
373 let instance = Self::resolve_group_instance(tree, group_path, repetition)?;
374 Self::extract_from_instance(instance, path)
375 }
376
377 pub fn resolve_group_instance<'a>(
386 tree: &'a AssembledTree,
387 group_path: &str,
388 repetition: usize,
389 ) -> Option<&'a AssembledGroupInstance> {
390 let parts: Vec<&str> = group_path.split('.').collect();
391
392 let (first_id, first_rep) = parse_group_spec(parts[0]);
393 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
394
395 if parts.len() == 1 {
396 let rep = first_rep.unwrap_or(repetition);
398 return first_group.repetitions.get(rep);
399 }
400
401 let mut current_instance = first_group.repetitions.get(first_rep.unwrap_or(0))?;
404
405 for (i, part) in parts[1..].iter().enumerate() {
406 let (group_id, explicit_rep) = parse_group_spec(part);
407 let child_group = current_instance
408 .child_groups
409 .iter()
410 .find(|g| g.group_id == group_id)?;
411
412 if i == parts.len() - 2 {
413 let rep = explicit_rep.unwrap_or(repetition);
415 return child_group.repetitions.get(rep);
416 }
417 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
419 }
420
421 None
422 }
423
424 pub fn resolve_by_source_path<'a>(
432 tree: &'a AssembledTree,
433 source_path: &str,
434 ) -> Option<&'a AssembledGroupInstance> {
435 let parts: Vec<&str> = source_path.split('.').collect();
436 if parts.is_empty() {
437 return None;
438 }
439
440 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
441 let first_group = tree
442 .groups
443 .iter()
444 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))?;
445
446 let mut current_instance = if let Some(q) = first_qualifier {
447 find_rep_by_entry_qualifier(&first_group.repetitions, q)?
448 } else {
449 first_group.repetitions.first()?
450 };
451
452 if parts.len() == 1 {
453 return Some(current_instance);
454 }
455
456 for part in &parts[1..] {
457 let (group_id, qualifier) = parse_source_path_part(part);
458 let child_group = current_instance
459 .child_groups
460 .iter()
461 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))?;
462
463 current_instance = if let Some(q) = qualifier {
464 find_rep_by_entry_qualifier(&child_group.repetitions, q)?
465 } else {
466 child_group.repetitions.first()?
467 };
468 }
469
470 Some(current_instance)
471 }
472
473 pub fn resolve_all_by_source_path<'a>(
481 tree: &'a AssembledTree,
482 source_path: &str,
483 ) -> Vec<&'a AssembledGroupInstance> {
484 let parts: Vec<&str> = source_path.split('.').collect();
485 if parts.is_empty() {
486 return vec![];
487 }
488
489 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
491 let first_group = match tree
492 .groups
493 .iter()
494 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
495 {
496 Some(g) => g,
497 None => return vec![],
498 };
499
500 let mut current_instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
501 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
502 } else {
503 first_group.repetitions.iter().collect()
504 };
505
506 for part in &parts[1..] {
509 let (group_id, qualifier) = parse_source_path_part(part);
510 let mut next_instances = Vec::new();
511
512 for instance in ¤t_instances {
513 if let Some(child_group) = instance
514 .child_groups
515 .iter()
516 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
517 {
518 if let Some(q) = qualifier {
519 next_instances.extend(find_all_reps_by_entry_qualifier(
520 &child_group.repetitions,
521 q,
522 ));
523 } else {
524 next_instances.extend(child_group.repetitions.iter());
525 }
526 }
527 }
528
529 current_instances = next_instances;
530 }
531
532 current_instances
533 }
534
535 fn compute_child_indices(
548 tree: &AssembledTree,
549 source_path: &str,
550 indexed: &[(usize, &AssembledGroupInstance)],
551 ) -> Vec<usize> {
552 let parts: Vec<&str> = source_path.split('.').collect();
553 if parts.len() < 2 {
554 return vec![];
555 }
556 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
558 let first_group = match tree
559 .groups
560 .iter()
561 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
562 {
563 Some(g) => g,
564 None => return vec![],
565 };
566 let parent_reps: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
567 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
568 } else {
569 first_group.repetitions.iter().collect()
570 };
571 let (child_id, _child_qualifier) = parse_source_path_part(parts[parts.len() - 1]);
573 let mut result = Vec::new();
574 for (_, inst) in indexed {
575 let mut found = false;
577 for parent in &parent_reps {
578 if let Some(child_group) = parent
579 .child_groups
580 .iter()
581 .find(|g| g.group_id.eq_ignore_ascii_case(child_id))
582 {
583 if let Some(pos) = child_group
584 .repetitions
585 .iter()
586 .position(|r| std::ptr::eq(r, *inst))
587 {
588 result.push(pos);
589 found = true;
590 break;
591 }
592 }
593 }
594 if !found {
595 result.push(usize::MAX); }
597 }
598 result
599 }
600
601 pub fn resolve_all_with_parent_indices<'a>(
603 tree: &'a AssembledTree,
604 source_path: &str,
605 ) -> Vec<(usize, &'a AssembledGroupInstance)> {
606 let parts: Vec<&str> = source_path.split('.').collect();
607 if parts.is_empty() {
608 return vec![];
609 }
610
611 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
613 let first_group = match tree
614 .groups
615 .iter()
616 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
617 {
618 Some(g) => g,
619 None => return vec![],
620 };
621
622 if parts.len() == 1 {
624 let instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
625 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
626 } else {
627 first_group.repetitions.iter().collect()
628 };
629 return instances.into_iter().map(|i| (0, i)).collect();
630 }
631
632 let first_reps: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = first_qualifier {
637 let matching = find_all_reps_by_entry_qualifier(&first_group.repetitions, q);
638 let mut result = Vec::new();
639 for m in matching {
640 let idx = first_group
641 .repetitions
642 .iter()
643 .position(|r| std::ptr::eq(r, m))
644 .unwrap_or(0);
645 result.push((idx, m));
646 }
647 result
648 } else {
649 first_group.repetitions.iter().enumerate().collect()
650 };
651
652 let mut current: Vec<(usize, &AssembledGroupInstance)> = first_reps;
653 let remaining = &parts[1..];
654
655 for (level, part) in remaining.iter().enumerate() {
656 let is_leaf = level == remaining.len() - 1;
657 let (group_id, qualifier) = parse_source_path_part(part);
658 let mut next: Vec<(usize, &AssembledGroupInstance)> = Vec::new();
659
660 for (prev_parent_idx, instance) in ¤t {
661 if let Some(child_group) = instance
662 .child_groups
663 .iter()
664 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
665 {
666 let matching: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = qualifier
667 {
668 let filtered =
669 find_all_reps_by_entry_qualifier(&child_group.repetitions, q);
670 filtered
671 .into_iter()
672 .map(|m| {
673 let idx = child_group
674 .repetitions
675 .iter()
676 .position(|r| std::ptr::eq(r, m))
677 .unwrap_or(0);
678 (idx, m)
679 })
680 .collect()
681 } else {
682 child_group.repetitions.iter().enumerate().collect()
683 };
684
685 for (rep_idx, child_rep) in matching {
686 if is_leaf {
687 next.push((*prev_parent_idx, child_rep));
689 } else {
690 next.push((rep_idx, child_rep));
692 }
693 }
694 }
695 }
696
697 current = next;
698 }
699
700 current
701 }
702
703 pub fn extract_from_instance(instance: &AssembledGroupInstance, path: &str) -> Option<String> {
709 let parts: Vec<&str> = path.split('.').collect();
710 if parts.is_empty() {
711 return None;
712 }
713
714 let (segment_tag, qualifier, occurrence) = parse_tag_qualifier(parts[0]);
717
718 let segment = if let Some(q) = qualifier {
719 instance
720 .segments
721 .iter()
722 .filter(|s| {
723 s.tag.eq_ignore_ascii_case(&segment_tag)
724 && s.elements
725 .first()
726 .and_then(|e| e.first())
727 .map(|v| v.as_str())
728 == Some(q)
729 })
730 .nth(occurrence)?
731 } else {
732 instance
733 .segments
734 .iter()
735 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
736 .nth(occurrence)?
737 };
738
739 Self::resolve_field_path(segment, &parts[1..])
740 }
741
742 pub fn extract_all_from_instance(instance: &AssembledGroupInstance, path: &str) -> Vec<String> {
748 let parts: Vec<&str> = path.split('.').collect();
749 if parts.is_empty() {
750 return vec![];
751 }
752
753 let (segment_tag, qualifier, _) = parse_tag_qualifier(parts[0]);
754
755 let matching_segments: Vec<&AssembledSegment> = if let Some(q) = qualifier {
756 instance
757 .segments
758 .iter()
759 .filter(|s| {
760 s.tag.eq_ignore_ascii_case(&segment_tag)
761 && s.elements
762 .first()
763 .and_then(|e| e.first())
764 .map(|v| v.as_str())
765 == Some(q)
766 })
767 .collect()
768 } else {
769 instance
770 .segments
771 .iter()
772 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
773 .collect()
774 };
775
776 matching_segments
777 .into_iter()
778 .filter_map(|seg| Self::resolve_field_path(seg, &parts[1..]))
779 .collect()
780 }
781
782 pub fn map_forward(
792 &self,
793 tree: &AssembledTree,
794 def: &MappingDefinition,
795 repetition: usize,
796 ) -> serde_json::Value {
797 self.map_forward_inner(tree, def, repetition, true)
798 }
799
800 fn map_forward_inner(
802 &self,
803 tree: &AssembledTree,
804 def: &MappingDefinition,
805 repetition: usize,
806 enrich_codes: bool,
807 ) -> serde_json::Value {
808 let mut result = serde_json::Map::new();
809
810 if def.meta.source_group.is_empty() {
815 let mut all_root_segs = tree.segments.clone();
816 for segs in tree.inter_group_segments.values() {
817 all_root_segs.extend(segs.iter().cloned());
818 }
819 let root_instance = AssembledGroupInstance {
820 segments: all_root_segs,
821 child_groups: vec![],
822 entry_mig_number: None,
823 variant_mig_numbers: vec![],
824 skipped_segments: Vec::new(),
825 skipped_positions: Vec::new(),
826 };
827 self.extract_fields_from_instance(&root_instance, def, &mut result, enrich_codes);
828 self.extract_companion_fields(&root_instance, def, &mut result, enrich_codes);
829 return serde_json::Value::Object(result);
830 }
831
832 let instance = if let Some(ref sp) = def.meta.source_path {
838 if has_source_path_qualifiers(sp) && !def.meta.source_group.contains(':') {
839 Self::resolve_by_source_path(tree, sp).or_else(|| {
840 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
841 })
842 } else {
843 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
844 }
845 } else {
846 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
847 };
848
849 if let Some(instance) = instance {
850 if let Some(ref tag) = def.meta.repeat_on_tag {
852 let matching: Vec<_> = instance
853 .segments
854 .iter()
855 .filter(|s| s.tag.eq_ignore_ascii_case(tag))
856 .collect();
857
858 if matching.len() > 1 {
859 let mut arr = Vec::new();
860 for seg in &matching {
861 let sub_instance = AssembledGroupInstance {
862 segments: vec![(*seg).clone()],
863 child_groups: vec![],
864 entry_mig_number: None,
865 variant_mig_numbers: vec![],
866 skipped_segments: Vec::new(),
867 skipped_positions: Vec::new(),
868 };
869 let mut elem_result = serde_json::Map::new();
870 self.extract_fields_from_instance(
871 &sub_instance,
872 def,
873 &mut elem_result,
874 enrich_codes,
875 );
876 self.extract_companion_fields(
877 &sub_instance,
878 def,
879 &mut elem_result,
880 enrich_codes,
881 );
882 if !elem_result.is_empty() {
883 arr.push(serde_json::Value::Object(elem_result));
884 }
885 }
886 if !arr.is_empty() {
887 return serde_json::Value::Array(arr);
888 }
889 }
890 }
891
892 self.extract_fields_from_instance(instance, def, &mut result, enrich_codes);
893 self.extract_companion_fields(instance, def, &mut result, enrich_codes);
894 }
895
896 serde_json::Value::Object(result)
897 }
898
899 fn extract_companion_fields(
910 &self,
911 instance: &AssembledGroupInstance,
912 def: &MappingDefinition,
913 result: &mut serde_json::Map<String, serde_json::Value>,
914 enrich_codes: bool,
915 ) {
916 if let Some(ref companion_fields) = def.companion_fields {
917 let companion_key = def.meta.companion_type.as_deref().map(to_camel_case);
918 let mut companion_result = serde_json::Map::new();
919
920 for (path, field_mapping) in companion_fields {
921 let (target, enum_map, also_target, also_enum_map) = match field_mapping {
922 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
923 FieldMapping::Structured(s) => (
924 s.target.as_str(),
925 s.enum_map.as_ref(),
926 s.also_target.as_deref(),
927 s.also_enum_map.as_ref(),
928 ),
929 FieldMapping::Nested(_) => continue,
930 };
931 if target.is_empty() {
932 continue;
933 }
934
935 if is_collect_all_path(path) {
937 let all = Self::extract_all_from_instance(instance, path);
938 if !all.is_empty() {
939 let arr: Vec<serde_json::Value> = all
940 .into_iter()
941 .map(|v| {
942 let mapped = if let Some(map) = enum_map {
943 map.get(&v).cloned().unwrap_or_else(|| v.clone())
944 } else {
945 v
946 };
947 serde_json::Value::String(mapped)
948 })
949 .collect();
950 set_nested_value_json(
951 &mut companion_result,
952 target,
953 serde_json::Value::Array(arr),
954 );
955 }
956 continue;
957 }
958
959 if let Some(val) = Self::extract_from_instance(instance, path) {
960 let mapped_val = if let Some(map) = enum_map {
961 map.get(&val).cloned().unwrap_or_else(|| val.clone())
962 } else {
963 val.clone()
964 };
965
966 if enrich_codes {
968 if let (Some(ref code_lookup), Some(ref source_path)) =
969 (&self.code_lookup, &def.meta.source_path)
970 {
971 let parts: Vec<&str> = path.split('.').collect();
972 let (seg_tag, _qualifier, _occ) = parse_tag_qualifier(parts[0]);
973 let (element_idx, component_idx) =
974 Self::parse_element_component(&parts[1..]);
975 let disc_qualifier = Self::discriminator_qualifier(def);
976 let q = disc_qualifier.as_deref();
977
978 if code_lookup.is_code_field_q(
979 source_path,
980 &seg_tag,
981 q,
982 element_idx,
983 component_idx,
984 ) {
985 if let Some(ref pid) = self.current_pid {
991 if code_lookup.is_pid_self_reference(
992 source_path,
993 &seg_tag,
994 q,
995 element_idx,
996 component_idx,
997 pid,
998 ) {
999 set_nested_value(&mut companion_result, target, mapped_val);
1000 if let (Some(at), Some(am)) = (also_target, also_enum_map) {
1001 if let Some(also_mapped) = am.get(&val) {
1002 set_nested_value(
1003 &mut companion_result,
1004 at,
1005 also_mapped.clone(),
1006 );
1007 }
1008 }
1009 continue;
1010 }
1011 }
1012
1013 let enrichment = code_lookup.enrichment_for_q(
1017 source_path,
1018 &seg_tag,
1019 q,
1020 element_idx,
1021 component_idx,
1022 &val,
1023 );
1024 let meaning = enrichment
1025 .map(|e| serde_json::Value::String(e.meaning.clone()))
1026 .unwrap_or(serde_json::Value::Null);
1027
1028 let mut obj = serde_json::Map::new();
1029 obj.insert("code".into(), serde_json::json!(mapped_val));
1030 obj.insert("meaning".into(), meaning);
1031 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref())
1032 {
1033 obj.insert("enum".into(), serde_json::json!(enum_key));
1034 }
1035 let enriched = serde_json::Value::Object(obj);
1036 set_nested_value_json(&mut companion_result, target, enriched);
1037 continue;
1038 }
1039 }
1040 }
1041
1042 set_nested_value(&mut companion_result, target, mapped_val);
1043
1044 if let (Some(at), Some(am)) = (also_target, also_enum_map) {
1048 if let Some(also_mapped) = am.get(&val) {
1049 set_nested_value(&mut companion_result, at, also_mapped.clone());
1050 }
1051 }
1052 }
1053 }
1054
1055 if !companion_result.is_empty() {
1056 match companion_key {
1057 Some(key) => {
1058 result.insert(key, serde_json::Value::Object(companion_result));
1059 }
1060 None => {
1061 for (k, v) in companion_result {
1066 result.entry(k).or_insert(v);
1067 }
1068 }
1069 }
1070 }
1071 }
1072 }
1073
1074 fn extract_fields_from_instance(
1079 &self,
1080 instance: &AssembledGroupInstance,
1081 def: &MappingDefinition,
1082 result: &mut serde_json::Map<String, serde_json::Value>,
1083 enrich_codes: bool,
1084 ) {
1085 for (path, field_mapping) in &def.fields {
1086 let (target, enum_map) = match field_mapping {
1087 FieldMapping::Simple(t) => (t.as_str(), None),
1088 FieldMapping::Structured(s) => (s.target.as_str(), s.enum_map.as_ref()),
1089 FieldMapping::Nested(_) => continue,
1090 };
1091 if target.is_empty() {
1092 continue;
1093 }
1094 if let Some(val) = Self::extract_from_instance(instance, path) {
1095 let mapped_val = if let Some(map) = enum_map {
1096 map.get(&val).cloned().unwrap_or_else(|| val.clone())
1097 } else {
1098 val.clone()
1099 };
1100
1101 if enrich_codes {
1103 if let (Some(ref code_lookup), Some(ref source_path)) =
1104 (&self.code_lookup, &def.meta.source_path)
1105 {
1106 let parts: Vec<&str> = path.split('.').collect();
1107 let (seg_tag, _qualifier, _occ) = parse_tag_qualifier(parts[0]);
1108 let (element_idx, component_idx) =
1109 Self::parse_element_component(&parts[1..]);
1110 let disc_qualifier = Self::discriminator_qualifier(def);
1111 let q = disc_qualifier.as_deref();
1112
1113 if code_lookup.is_code_field_q(
1114 source_path,
1115 &seg_tag,
1116 q,
1117 element_idx,
1118 component_idx,
1119 ) {
1120 if let Some(ref pid) = self.current_pid {
1125 if code_lookup.is_pid_self_reference(
1126 source_path,
1127 &seg_tag,
1128 q,
1129 element_idx,
1130 component_idx,
1131 pid,
1132 ) {
1133 set_nested_value(result, target, mapped_val);
1134 continue;
1135 }
1136 }
1137
1138 let enrichment = code_lookup.enrichment_for_q(
1142 source_path,
1143 &seg_tag,
1144 q,
1145 element_idx,
1146 component_idx,
1147 &val,
1148 );
1149 let meaning = enrichment
1150 .map(|e| serde_json::Value::String(e.meaning.clone()))
1151 .unwrap_or(serde_json::Value::Null);
1152
1153 let mut obj = serde_json::Map::new();
1154 obj.insert("code".into(), serde_json::json!(mapped_val));
1155 obj.insert("meaning".into(), meaning);
1156 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
1157 obj.insert("enum".into(), serde_json::json!(enum_key));
1158 }
1159 let enriched = serde_json::Value::Object(obj);
1160 set_nested_value_json(result, target, enriched);
1161 continue;
1162 }
1163 }
1164 }
1165
1166 set_nested_value(result, target, mapped_val);
1167 }
1168 }
1169 }
1170
1171 fn discriminator_qualifier(def: &MappingDefinition) -> Option<String> {
1178 def.meta
1179 .discriminator
1180 .as_deref()
1181 .and_then(|d| d.split_once('=').map(|(_, v)| v.to_string()))
1182 }
1183
1184 pub fn map_forward_from_segments(
1190 &self,
1191 segments: &[OwnedSegment],
1192 def: &MappingDefinition,
1193 ) -> serde_json::Value {
1194 let assembled_segments: Vec<AssembledSegment> = segments
1195 .iter()
1196 .map(|s| AssembledSegment {
1197 tag: s.id.clone(),
1198 elements: s.elements.clone(),
1199 mig_number: None,
1200 segment_number: Some(s.segment_number),
1201 })
1202 .collect();
1203
1204 let instance = AssembledGroupInstance {
1205 segments: assembled_segments,
1206 child_groups: vec![],
1207 entry_mig_number: None,
1208 variant_mig_numbers: vec![],
1209 skipped_segments: Vec::new(),
1210 skipped_positions: Vec::new(),
1211 };
1212
1213 let mut result = serde_json::Map::new();
1214 self.extract_fields_from_instance(&instance, def, &mut result, true);
1215 serde_json::Value::Object(result)
1216 }
1217
1218 pub fn map_reverse(
1231 &self,
1232 bo4e_value: &serde_json::Value,
1233 def: &MappingDefinition,
1234 ) -> AssembledGroupInstance {
1235 if def.meta.repeat_on_tag.is_some() {
1237 if let Some(arr) = bo4e_value.as_array() {
1238 let mut all_segments = Vec::new();
1239 for elem in arr {
1240 let sub = self.map_reverse_single(elem, def);
1241 all_segments.extend(sub.segments);
1242 }
1243 return AssembledGroupInstance {
1244 segments: all_segments,
1245 child_groups: vec![],
1246 entry_mig_number: None,
1247 variant_mig_numbers: vec![],
1248 skipped_segments: Vec::new(),
1249 skipped_positions: Vec::new(),
1250 };
1251 }
1252 }
1253 self.map_reverse_single(bo4e_value, def)
1254 }
1255
1256 fn map_reverse_single(
1257 &self,
1258 bo4e_value: &serde_json::Value,
1259 def: &MappingDefinition,
1260 ) -> AssembledGroupInstance {
1261 let mut field_values: Vec<(String, String, usize, usize, String)> =
1264 Vec::with_capacity(def.fields.len());
1265
1266 let mut has_real_data = false;
1273 let mut has_data_fields = false;
1274 let mut seg_has_data_field: HashSet<String> = HashSet::new();
1277 let mut seg_has_real_data: HashSet<String> = HashSet::new();
1278 let mut injected_qualifiers: HashSet<String> = HashSet::new();
1279
1280 for (path, field_mapping) in &def.fields {
1281 let (target, default, enum_map, when_filled) = match field_mapping {
1282 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
1283 FieldMapping::Structured(s) => (
1284 s.target.as_str(),
1285 s.default.as_ref(),
1286 s.enum_map.as_ref(),
1287 s.when_filled.as_ref(),
1288 ),
1289 FieldMapping::Nested(_) => continue,
1290 };
1291
1292 let parts: Vec<&str> = path.split('.').collect();
1293 if parts.len() < 2 {
1294 continue;
1295 }
1296
1297 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1298 let seg_key = parts[0].to_uppercase();
1301 let sub_path = &parts[1..];
1302
1303 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1305 let ci = if sub_path.len() > 1 {
1306 sub_path[1].parse::<usize>().unwrap_or(0)
1307 } else {
1308 0
1309 };
1310 (ei, ci)
1311 } else {
1312 match sub_path.len() {
1313 1 => (0, 0),
1314 2 => (1, 0),
1315 _ => continue,
1316 }
1317 };
1318
1319 let val = if target.is_empty() {
1321 match (default, when_filled) {
1322 (Some(d), Some(fields)) => {
1325 let companion_key_for_check =
1326 def.meta.companion_type.as_deref().map(to_camel_case);
1327 let companion_for_check = companion_key_for_check
1328 .as_ref()
1329 .and_then(|k| bo4e_value.get(k))
1330 .unwrap_or(&serde_json::Value::Null);
1331 let any_filled = fields.iter().any(|f| {
1332 self.populate_field(bo4e_value, f).is_some()
1333 || self.populate_field(companion_for_check, f).is_some()
1334 });
1335 if any_filled {
1336 has_real_data = true;
1340 Some(d.clone())
1341 } else {
1342 None
1343 }
1344 }
1345 (Some(d), None) => Some(d.clone()),
1347 (None, _) => None,
1348 }
1349 } else {
1350 has_data_fields = true;
1351 seg_has_data_field.insert(seg_key.clone());
1352 let bo4e_val = self.populate_field(bo4e_value, target);
1353 if bo4e_val.is_some() {
1354 has_real_data = true;
1355 seg_has_real_data.insert(seg_key.clone());
1356 }
1357 let mapped_val = match (bo4e_val, enum_map) {
1359 (Some(v), Some(map)) => {
1360 map.iter()
1362 .find(|(_, bo4e_v)| *bo4e_v == &v)
1363 .map(|(edifact_k, _)| edifact_k.clone())
1364 .or(Some(v))
1365 }
1366 (v, _) => v,
1367 };
1368 mapped_val.or_else(|| default.cloned())
1369 };
1370
1371 if let Some(val) = val {
1372 field_values.push((
1373 seg_key.clone(),
1374 seg_tag.clone(),
1375 element_idx,
1376 component_idx,
1377 val,
1378 ));
1379 }
1380
1381 if let Some(q) = qualifier {
1383 if injected_qualifiers.insert(seg_key.clone()) {
1384 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1385 }
1386 }
1387 }
1388
1389 if let Some(ref companion_fields) = def.companion_fields {
1394 let companion_value = match def.meta.companion_type.as_deref() {
1395 Some(raw_key) => {
1396 let key = to_camel_case(raw_key);
1397 bo4e_value.get(&key).unwrap_or(bo4e_value)
1398 }
1399 None => bo4e_value,
1400 };
1401
1402 for (path, field_mapping) in companion_fields {
1403 let (target, default, enum_map, when_filled, also_target, also_enum_map) =
1404 match field_mapping {
1405 FieldMapping::Simple(t) => (t.as_str(), None, None, None, None, None),
1406 FieldMapping::Structured(s) => (
1407 s.target.as_str(),
1408 s.default.as_ref(),
1409 s.enum_map.as_ref(),
1410 s.when_filled.as_ref(),
1411 s.also_target.as_deref(),
1412 s.also_enum_map.as_ref(),
1413 ),
1414 FieldMapping::Nested(_) => continue,
1415 };
1416
1417 let parts: Vec<&str> = path.split('.').collect();
1418 if parts.len() < 2 {
1419 continue;
1420 }
1421
1422 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1423 let seg_key = parts[0].to_uppercase();
1424 let sub_path = &parts[1..];
1425
1426 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1427 let ci = if sub_path.len() > 1 {
1428 sub_path[1].parse::<usize>().unwrap_or(0)
1429 } else {
1430 0
1431 };
1432 (ei, ci)
1433 } else {
1434 match sub_path.len() {
1435 1 => (0, 0),
1436 2 => (1, 0),
1437 _ => continue,
1438 }
1439 };
1440
1441 if is_collect_all_path(path) && !target.is_empty() {
1443 if let Some(arr) = self
1444 .populate_field_json(companion_value, target)
1445 .and_then(|v| v.as_array().cloned())
1446 {
1447 has_data_fields = true;
1448 if !arr.is_empty() {
1449 has_real_data = true;
1450 }
1451 for (idx, item) in arr.iter().enumerate() {
1452 if let Some(val_str) = item.as_str() {
1453 let mapped = if let Some(map) = enum_map {
1454 map.iter()
1455 .find(|(_, bo4e_v)| *bo4e_v == val_str)
1456 .map(|(edifact_k, _)| edifact_k.clone())
1457 .unwrap_or_else(|| val_str.to_string())
1458 } else {
1459 val_str.to_string()
1460 };
1461 let occ_key = if let Some(q) = qualifier {
1462 format!("{}[{},{}]", seg_tag, q, idx)
1463 } else {
1464 format!("{}[*,{}]", seg_tag, idx)
1465 };
1466 field_values.push((
1467 occ_key.clone(),
1468 seg_tag.clone(),
1469 element_idx,
1470 component_idx,
1471 mapped,
1472 ));
1473 if let Some(q) = qualifier {
1475 if injected_qualifiers.insert(occ_key.clone()) {
1476 field_values.push((
1477 occ_key,
1478 seg_tag.clone(),
1479 0,
1480 0,
1481 q.to_string(),
1482 ));
1483 }
1484 }
1485 }
1486 }
1487 }
1488 continue;
1489 }
1490
1491 let val = if target.is_empty() {
1492 match (default, when_filled) {
1493 (Some(d), Some(fields)) => {
1494 let any_filled = fields.iter().any(|f| {
1495 self.populate_field(bo4e_value, f).is_some()
1496 || self.populate_field(companion_value, f).is_some()
1497 });
1498 if any_filled {
1499 has_real_data = true;
1500 Some(d.clone())
1501 } else {
1502 None
1503 }
1504 }
1505 (Some(d), None) => Some(d.clone()),
1506 (None, _) => None,
1507 }
1508 } else {
1509 has_data_fields = true;
1510 seg_has_data_field.insert(seg_key.clone());
1511 let bo4e_val = self.populate_field(companion_value, target);
1512 if bo4e_val.is_some() {
1513 has_real_data = true;
1514 seg_has_real_data.insert(seg_key.clone());
1515 }
1516 let mapped_val = match (bo4e_val, enum_map) {
1517 (Some(v), Some(map)) => {
1518 if let (Some(at), Some(am)) = (also_target, also_enum_map) {
1519 let also_val = self.populate_field(companion_value, at);
1520 if let Some(av) = also_val.as_deref() {
1521 map.iter()
1523 .find(|(edifact_k, bo4e_v)| {
1524 *bo4e_v == &v
1525 && am.get(*edifact_k).is_some_and(|am_v| am_v == av)
1526 })
1527 .map(|(edifact_k, _)| edifact_k.clone())
1528 .or(Some(v))
1529 } else {
1530 map.iter()
1533 .find(|(edifact_k, bo4e_v)| {
1534 *bo4e_v == &v && !am.contains_key(*edifact_k)
1535 })
1536 .or_else(|| {
1537 map.iter().find(|(_, bo4e_v)| *bo4e_v == &v)
1539 })
1540 .map(|(edifact_k, _)| edifact_k.clone())
1541 .or(Some(v))
1542 }
1543 } else {
1544 map.iter()
1545 .find(|(_, bo4e_v)| *bo4e_v == &v)
1546 .map(|(edifact_k, _)| edifact_k.clone())
1547 .or(Some(v))
1548 }
1549 }
1550 (v, _) => v,
1551 };
1552 mapped_val.or_else(|| default.cloned())
1553 };
1554
1555 if let Some(val) = val {
1556 field_values.push((
1557 seg_key.clone(),
1558 seg_tag.clone(),
1559 element_idx,
1560 component_idx,
1561 val,
1562 ));
1563 }
1564
1565 if let Some(q) = qualifier {
1566 if injected_qualifiers.insert(seg_key.clone()) {
1567 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1568 }
1569 }
1570 }
1571 }
1572
1573 field_values.retain(|(seg_key, _, _, _, _)| {
1581 if !seg_key.contains('[') {
1582 return true; }
1584 !seg_has_data_field.contains(seg_key) || seg_has_real_data.contains(seg_key)
1585 });
1586
1587 if has_data_fields && !has_real_data {
1592 return AssembledGroupInstance {
1593 segments: vec![],
1594 child_groups: vec![],
1595 entry_mig_number: None,
1596 variant_mig_numbers: vec![],
1597 skipped_segments: Vec::new(),
1598 skipped_positions: Vec::new(),
1599 };
1600 }
1601
1602 let mut segments: Vec<AssembledSegment> = Vec::with_capacity(field_values.len());
1605 let mut seen_keys: HashMap<String, usize> = HashMap::new();
1606
1607 for (seg_key, seg_tag, element_idx, component_idx, val) in &field_values {
1608 let seg = if let Some(&pos) = seen_keys.get(seg_key) {
1609 &mut segments[pos]
1610 } else {
1611 let pos = segments.len();
1612 seen_keys.insert(seg_key.clone(), pos);
1613 segments.push(AssembledSegment {
1614 tag: seg_tag.clone(),
1615 elements: vec![],
1616 mig_number: None,
1617 segment_number: None,
1618 });
1619 &mut segments[pos]
1620 };
1621
1622 while seg.elements.len() <= *element_idx {
1623 seg.elements.push(vec![]);
1624 }
1625 while seg.elements[*element_idx].len() <= *component_idx {
1626 seg.elements[*element_idx].push(String::new());
1627 }
1628 seg.elements[*element_idx][*component_idx] = val.clone();
1629 }
1630
1631 for seg in &mut segments {
1634 let last_populated = seg.elements.iter().rposition(|e| !e.is_empty());
1635 if let Some(last_idx) = last_populated {
1636 for i in 0..last_idx {
1637 if seg.elements[i].is_empty() {
1638 seg.elements[i] = vec![String::new()];
1639 }
1640 }
1641 }
1642 }
1643
1644 if let Some(ref ss) = self.segment_structure {
1646 for seg in &mut segments {
1647 if let Some(expected) = ss.element_count(&seg.tag) {
1648 while seg.elements.len() < expected {
1649 seg.elements.push(vec![String::new()]);
1650 }
1651 }
1652 }
1653 }
1654
1655 AssembledGroupInstance {
1656 segments,
1657 child_groups: vec![],
1658 entry_mig_number: None,
1659 variant_mig_numbers: vec![],
1660 skipped_segments: Vec::new(),
1661 skipped_positions: Vec::new(),
1662 }
1663 }
1664
1665 fn resolve_field_path(segment: &AssembledSegment, path: &[&str]) -> Option<String> {
1678 if path.is_empty() {
1679 return None;
1680 }
1681
1682 if let Ok(element_idx) = path[0].parse::<usize>() {
1684 let component_idx = if path.len() > 1 {
1685 path[1].parse::<usize>().unwrap_or(0)
1686 } else {
1687 0
1688 };
1689 return segment
1690 .elements
1691 .get(element_idx)?
1692 .get(component_idx)
1693 .filter(|v| !v.is_empty())
1694 .cloned();
1695 }
1696
1697 None
1703 }
1704
1705 fn parse_element_component(parts: &[&str]) -> (usize, usize) {
1708 if parts.is_empty() {
1709 return (0, 0);
1710 }
1711 let element_idx = parts[0].parse::<usize>().unwrap_or(0);
1712 let component_idx = if parts.len() > 1 {
1713 parts[1].parse::<usize>().unwrap_or(0)
1714 } else {
1715 0
1716 };
1717 (element_idx, component_idx)
1718 }
1719
1720 pub fn populate_field(
1723 &self,
1724 bo4e_value: &serde_json::Value,
1725 target_field: &str,
1726 ) -> Option<String> {
1727 let mut current = bo4e_value;
1728 for part in target_field.split('.') {
1729 current = current.get(part)?;
1730 }
1731 if let Some(code) = current.get("code").and_then(|v| v.as_str()) {
1733 return Some(code.to_string());
1734 }
1735 current.as_str().map(|s| s.to_string())
1736 }
1737
1738 fn populate_field_json<'a>(
1741 &self,
1742 bo4e_value: &'a serde_json::Value,
1743 target_field: &str,
1744 ) -> Option<&'a serde_json::Value> {
1745 let mut current = bo4e_value;
1746 for part in target_field.split('.') {
1747 current = current.get(part)?;
1748 }
1749 Some(current)
1750 }
1751
1752 pub fn build_segment_from_bo4e(
1754 &self,
1755 bo4e_value: &serde_json::Value,
1756 segment_tag: &str,
1757 target_field: &str,
1758 ) -> AssembledSegment {
1759 let value = self.populate_field(bo4e_value, target_field);
1760 let elements = if let Some(val) = value {
1761 vec![vec![val]]
1762 } else {
1763 vec![]
1764 };
1765 AssembledSegment {
1766 tag: segment_tag.to_uppercase(),
1767 elements,
1768 mig_number: None,
1769 segment_number: None,
1770 }
1771 }
1772
1773 pub fn resolve_repetition(
1782 tree: &AssembledTree,
1783 group_path: &str,
1784 discriminator: &str,
1785 ) -> Option<usize> {
1786 let (spec, expected) = discriminator.split_once('=')?;
1787 let parts: Vec<&str> = spec.split('.').collect();
1788 if parts.len() != 3 {
1789 return None;
1790 }
1791 let tag = parts[0];
1792 let element_idx: usize = parts[1].parse().ok()?;
1793 let component_idx: usize = parts[2].parse().ok()?;
1794
1795 let path_parts: Vec<&str> = group_path.split('.').collect();
1797
1798 let leaf_group = if path_parts.len() == 1 {
1799 let (group_id, _) = parse_group_spec(path_parts[0]);
1800 tree.groups.iter().find(|g| g.group_id == group_id)?
1801 } else {
1802 let parent_parts = &path_parts[..path_parts.len() - 1];
1804 let mut current_instance = {
1805 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1806 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
1807 first_group.repetitions.get(first_rep.unwrap_or(0))?
1808 };
1809 for part in &parent_parts[1..] {
1810 let (group_id, explicit_rep) = parse_group_spec(part);
1811 let child_group = current_instance
1812 .child_groups
1813 .iter()
1814 .find(|g| g.group_id == group_id)?;
1815 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
1816 }
1817 let (leaf_id, _) = parse_group_spec(path_parts.last()?);
1818 current_instance
1819 .child_groups
1820 .iter()
1821 .find(|g| g.group_id == leaf_id)?
1822 };
1823
1824 let expected_values: Vec<&str> = expected.split('|').collect();
1826 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1827 let matches = instance.segments.iter().any(|s| {
1828 s.tag.eq_ignore_ascii_case(tag)
1829 && s.elements
1830 .get(element_idx)
1831 .and_then(|e| e.get(component_idx))
1832 .map(|v| expected_values.iter().any(|ev| v == ev))
1833 .unwrap_or(false)
1834 });
1835 if matches {
1836 return Some(rep_idx);
1837 }
1838 }
1839
1840 None
1841 }
1842
1843 pub fn resolve_all_repetitions(
1848 tree: &AssembledTree,
1849 group_path: &str,
1850 discriminator: &str,
1851 ) -> Vec<usize> {
1852 let Some((spec, expected)) = discriminator.split_once('=') else {
1853 return Vec::new();
1854 };
1855 let parts: Vec<&str> = spec.split('.').collect();
1856 if parts.len() != 3 {
1857 return Vec::new();
1858 }
1859 let tag = parts[0];
1860 let element_idx: usize = match parts[1].parse() {
1861 Ok(v) => v,
1862 Err(_) => return Vec::new(),
1863 };
1864 let component_idx: usize = match parts[2].parse() {
1865 Ok(v) => v,
1866 Err(_) => return Vec::new(),
1867 };
1868
1869 let path_parts: Vec<&str> = group_path.split('.').collect();
1871
1872 let leaf_group = if path_parts.len() == 1 {
1873 let (group_id, _) = parse_group_spec(path_parts[0]);
1874 match tree.groups.iter().find(|g| g.group_id == group_id) {
1875 Some(g) => g,
1876 None => return Vec::new(),
1877 }
1878 } else {
1879 let parent_parts = &path_parts[..path_parts.len() - 1];
1880 let mut current_instance = {
1881 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1882 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
1883 Some(g) => g,
1884 None => return Vec::new(),
1885 };
1886 match first_group.repetitions.get(first_rep.unwrap_or(0)) {
1887 Some(i) => i,
1888 None => return Vec::new(),
1889 }
1890 };
1891 for part in &parent_parts[1..] {
1892 let (group_id, explicit_rep) = parse_group_spec(part);
1893 let child_group = match current_instance
1894 .child_groups
1895 .iter()
1896 .find(|g| g.group_id == group_id)
1897 {
1898 Some(g) => g,
1899 None => return Vec::new(),
1900 };
1901 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
1902 Some(i) => i,
1903 None => return Vec::new(),
1904 };
1905 }
1906 let (leaf_id, _) = match path_parts.last() {
1907 Some(p) => parse_group_spec(p),
1908 None => return Vec::new(),
1909 };
1910 match current_instance
1911 .child_groups
1912 .iter()
1913 .find(|g| g.group_id == leaf_id)
1914 {
1915 Some(g) => g,
1916 None => return Vec::new(),
1917 }
1918 };
1919
1920 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
1922
1923 let expected_values: Vec<&str> = expected_raw.split('|').collect();
1925 let mut result = Vec::new();
1926 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1927 let matches = instance.segments.iter().any(|s| {
1928 s.tag.eq_ignore_ascii_case(tag)
1929 && s.elements
1930 .get(element_idx)
1931 .and_then(|e| e.get(component_idx))
1932 .map(|v| expected_values.iter().any(|ev| v == ev))
1933 .unwrap_or(false)
1934 });
1935 if matches {
1936 result.push(rep_idx);
1937 }
1938 }
1939
1940 if let Some(occ) = occurrence {
1942 result.into_iter().nth(occ).into_iter().collect()
1943 } else {
1944 result
1945 }
1946 }
1947
1948 pub fn map_all_forward(&self, tree: &AssembledTree) -> serde_json::Value {
1970 self.map_all_forward_inner(tree, true).0
1971 }
1972
1973 pub fn map_all_forward_enriched(
1977 &self,
1978 tree: &AssembledTree,
1979 enrich_codes: bool,
1980 ) -> serde_json::Value {
1981 self.map_all_forward_inner(tree, enrich_codes).0
1982 }
1983
1984 fn map_all_forward_inner(
1994 &self,
1995 tree: &AssembledTree,
1996 enrich_codes: bool,
1997 ) -> (
1998 serde_json::Value,
1999 std::collections::HashMap<String, Vec<usize>>,
2000 DpRouting,
2001 ) {
2002 self.map_all_forward_inner_with_tx(tree, enrich_codes, self.transaction_group.as_deref())
2003 }
2004
2005 fn map_all_forward_inner_with_tx(
2009 &self,
2010 tree: &AssembledTree,
2011 enrich_codes: bool,
2012 tx_group_override: Option<&str>,
2013 ) -> (
2014 serde_json::Value,
2015 std::collections::HashMap<String, Vec<usize>>,
2016 DpRouting,
2017 ) {
2018 let mut result = serde_json::Map::new();
2019 let mut nesting_info: std::collections::HashMap<String, Vec<usize>> =
2020 std::collections::HashMap::new();
2021
2022 for def in &self.definitions {
2023 let entity = &def.meta.entity;
2024
2025 let bo4e = if let Some(ref disc) = def.meta.discriminator {
2026 let use_source_path = def
2031 .meta
2032 .source_path
2033 .as_ref()
2034 .is_some_and(|sp| has_source_path_qualifiers(sp));
2035 if use_source_path {
2036 let sp = def.meta.source_path.as_deref().unwrap();
2038 let all_instances = Self::resolve_all_by_source_path(tree, sp);
2039 let instances: Vec<_> = if let Some(matcher) = DiscriminatorMatcher::parse(disc)
2041 {
2042 matcher.filter_instances(all_instances)
2043 } else {
2044 all_instances
2045 };
2046 let extract = |instance: &AssembledGroupInstance| {
2047 let mut r = serde_json::Map::new();
2048 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
2049 self.extract_companion_fields(instance, def, &mut r, enrich_codes);
2050 serde_json::Value::Object(r)
2051 };
2052 match instances.len() {
2053 0 => None,
2054 1 => Some(extract(instances[0])),
2055 _ => Some(serde_json::Value::Array(
2056 instances.iter().map(|i| extract(i)).collect(),
2057 )),
2058 }
2059 } else {
2060 let reps = Self::resolve_all_repetitions(tree, &def.meta.source_group, disc);
2061 match reps.len() {
2062 0 => None,
2063 1 => Some(self.map_forward_inner(tree, def, reps[0], enrich_codes)),
2064 _ => Some(serde_json::Value::Array(
2065 reps.iter()
2066 .map(|&rep| self.map_forward_inner(tree, def, rep, enrich_codes))
2067 .collect(),
2068 )),
2069 }
2070 }
2071 } else if def.meta.source_group.is_empty() {
2072 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2074 } else if def.meta.source_path.as_ref().is_some_and(|sp| {
2075 has_source_path_qualifiers(sp) || def.meta.source_group.contains('.')
2076 }) {
2077 let sp = def.meta.source_path.as_deref().unwrap();
2082 let mut indexed = Self::resolve_all_with_parent_indices(tree, sp);
2083
2084 if let Some(last_part) = sp.rsplit('.').next() {
2089 if !last_part.contains('_') {
2090 let base_prefix = if let Some(parent) = sp.rsplit_once('.') {
2094 format!("{}.", parent.0)
2095 } else {
2096 String::new()
2097 };
2098 let sibling_qualifiers: Vec<String> = self
2099 .definitions
2100 .iter()
2101 .filter_map(|d| d.meta.source_path.as_deref())
2102 .filter(|other_sp| {
2103 *other_sp != sp
2104 && other_sp.starts_with(&base_prefix)
2105 && other_sp.split('.').count() == sp.split('.').count()
2106 })
2107 .filter_map(|other_sp| {
2108 let other_last = other_sp.rsplit('.').next()?;
2109 let (base, q) = other_last.split_once('_')?;
2112 if base == last_part {
2113 Some(q.to_string())
2114 } else {
2115 None
2116 }
2117 })
2118 .collect();
2119
2120 if !sibling_qualifiers.is_empty() {
2121 indexed.retain(|(_, inst)| {
2122 let entry_qual = inst
2123 .segments
2124 .first()
2125 .and_then(|seg| seg.elements.first())
2126 .and_then(|el| el.first())
2127 .map(|v| v.to_lowercase());
2128 !entry_qual.is_some_and(|q| {
2131 sibling_qualifiers.iter().any(|sq| {
2132 sq.split('_').any(|part| part.eq_ignore_ascii_case(&q))
2133 })
2134 })
2135 });
2136 }
2137 }
2138 }
2139 let extract = |instance: &AssembledGroupInstance| {
2140 let mut r = serde_json::Map::new();
2141 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
2142 self.extract_companion_fields(instance, def, &mut r, enrich_codes);
2143 serde_json::Value::Object(r)
2144 };
2145 if def.meta.source_group.contains('.') && !indexed.is_empty() {
2150 if let Some(sp) = &def.meta.source_path {
2151 let parent_indices: Vec<usize> =
2152 indexed.iter().map(|(idx, _)| *idx).collect();
2153 nesting_info.entry(sp.clone()).or_insert(parent_indices);
2154
2155 let child_key = format!("{sp}#child");
2158 if let std::collections::hash_map::Entry::Vacant(e) =
2159 nesting_info.entry(child_key)
2160 {
2161 let child_indices: Vec<usize> =
2162 Self::compute_child_indices(tree, sp, &indexed);
2163 if !child_indices.is_empty() {
2164 e.insert(child_indices);
2165 }
2166 }
2167 }
2168 }
2169 match indexed.len() {
2170 0 => None,
2171 1 => Some(extract(indexed[0].1)),
2172 _ => Some(serde_json::Value::Array(
2173 indexed.iter().map(|(_, i)| extract(i)).collect(),
2174 )),
2175 }
2176 } else {
2177 let num_reps = Self::count_repetitions(tree, &def.meta.source_group);
2178 if num_reps <= 1 {
2179 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2180 } else {
2181 let mut items = Vec::with_capacity(num_reps);
2183 for rep in 0..num_reps {
2184 items.push(self.map_forward_inner(tree, def, rep, enrich_codes));
2185 }
2186 Some(serde_json::Value::Array(items))
2187 }
2188 };
2189
2190 if let Some(bo4e) = bo4e {
2191 let bo4e = inject_bo4e_metadata(bo4e, &def.meta.bo4e_type);
2192 let key = to_camel_case(entity);
2193 deep_merge_insert(&mut result, &key, bo4e);
2194 }
2195 }
2196
2197 nest_child_entities_in_result(
2202 &mut result,
2203 &self.definitions,
2204 &nesting_info,
2205 tx_group_override,
2206 );
2207
2208 let dp_routing = route_nad_dp_to_lokation(&mut result);
2214
2215 (serde_json::Value::Object(result), nesting_info, dp_routing)
2216 }
2217
2218 pub fn map_all_reverse(
2227 &self,
2228 entities: &serde_json::Value,
2229 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2230 ) -> AssembledTree {
2231 let mut root_segments: Vec<AssembledSegment> = Vec::new();
2232 let mut groups: Vec<AssembledGroup> = Vec::new();
2233 let mut inferred_nesting: std::collections::HashMap<String, Vec<usize>> =
2236 std::collections::HashMap::new();
2237
2238 for def in &self.definitions {
2239 let entity_key = to_camel_case(&def.meta.entity);
2240
2241 let _extracted: Option<serde_json::Value>;
2244 let entity_value = if let Some(v) = entities.get(&entity_key) {
2245 _extracted = None;
2246 v
2247 } else if def.meta.source_group.contains('.') {
2248 match extract_child_from_parent_with_indices(entities, &self.definitions, def) {
2250 Some((v, parent_indices)) => {
2251 if let Some(sp) = def.meta.source_path.as_deref() {
2253 inferred_nesting
2254 .entry(sp.to_string())
2255 .or_insert(parent_indices);
2256 }
2257 _extracted = Some(v);
2258 _extracted.as_ref().unwrap()
2259 }
2260 None => continue,
2261 }
2262 } else {
2263 continue;
2264 };
2265
2266 let unwrapped: Option<serde_json::Value>;
2274 let entity_value = if entity_value.is_object() && !entity_value.is_array() {
2275 if let Some(disc_value) = def
2276 .meta
2277 .discriminator
2278 .as_deref()
2279 .and_then(|d| d.split_once('='))
2280 .map(|(_, v)| v)
2281 {
2282 if let Some(inner) = entity_value.get(disc_value) {
2284 let mut injected = inner.clone();
2285 if let Some(ref cf) = def.companion_fields {
2288 let disc_path = def
2289 .meta
2290 .discriminator
2291 .as_deref()
2292 .unwrap()
2293 .split_once('=')
2294 .unwrap()
2295 .0
2296 .to_lowercase();
2297 for (path, mapping) in cf {
2298 let cf_path = path.to_lowercase();
2302 let matches =
2303 cf_path == disc_path || format!("{}.0", cf_path) == disc_path;
2304 if matches {
2305 let target = match mapping {
2306 FieldMapping::Simple(t) => t.as_str(),
2307 FieldMapping::Structured(s) => s.target.as_str(),
2308 FieldMapping::Nested(_) => continue,
2309 };
2310 if !target.is_empty() {
2311 if let Some(obj) = injected.as_object_mut() {
2312 let entry = obj
2313 .entry(target.to_string())
2314 .or_insert(serde_json::Value::Null);
2315 if entry.is_null() {
2316 *entry = serde_json::Value::String(
2317 disc_value.to_string(),
2318 );
2319 }
2320 }
2321 }
2322 break;
2323 }
2324 }
2325 }
2326 unwrapped = Some(injected);
2327 unwrapped.as_ref().unwrap()
2328 } else {
2329 entity_value
2330 }
2331 } else if is_map_keyed_object(entity_value) {
2332 let map = entity_value.as_object().unwrap();
2337 let arr: Vec<serde_json::Value> = map
2338 .iter()
2339 .map(|(key, val)| {
2340 let mut item = val.clone();
2341 if let Some(obj) = item.as_object_mut() {
2344 if let Some(qualifier_field) = find_qualifier_companion_field(
2345 &self.definitions,
2346 &def.meta.entity,
2347 ) {
2348 let entry = obj
2349 .entry(qualifier_field)
2350 .or_insert(serde_json::Value::Null);
2351 if entry.is_null() {
2352 *entry = serde_json::Value::String(key.clone());
2353 }
2354 }
2355 }
2356 item
2357 })
2358 .collect();
2359 unwrapped = Some(serde_json::Value::Array(arr));
2360 unwrapped.as_ref().unwrap()
2361 } else {
2362 entity_value
2363 }
2364 } else {
2365 entity_value
2366 };
2367
2368 let leaf_group = def
2370 .meta
2371 .source_group
2372 .rsplit('.')
2373 .next()
2374 .unwrap_or(&def.meta.source_group);
2375
2376 if def.meta.source_group.is_empty() {
2377 let instance = self.map_reverse(entity_value, def);
2379 root_segments.extend(instance.segments);
2380 } else if entity_value.is_array() {
2381 let arr = entity_value.as_array().unwrap();
2383 let reps: Vec<_> = arr.iter().map(|item| self.map_reverse(item, def)).collect();
2384
2385 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2387 existing.repetitions.extend(reps);
2388 } else {
2389 groups.push(AssembledGroup {
2390 group_id: leaf_group.to_string(),
2391 repetitions: reps,
2392 });
2393 }
2394 } else {
2395 let instance = self.map_reverse(entity_value, def);
2397
2398 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2399 existing.repetitions.push(instance);
2400 } else {
2401 groups.push(AssembledGroup {
2402 group_id: leaf_group.to_string(),
2403 repetitions: vec![instance],
2404 });
2405 }
2406 }
2407 }
2408
2409 let nested_specs: Vec<(String, String)> = self
2415 .definitions
2416 .iter()
2417 .filter_map(|def| {
2418 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
2419 if parts.len() > 1 {
2420 Some((parts[0].to_string(), parts[parts.len() - 1].to_string()))
2421 } else {
2422 None
2423 }
2424 })
2425 .collect();
2426 for (parent_id, child_id) in &nested_specs {
2427 let has_parent = groups.iter().any(|g| g.group_id == *parent_id);
2429 let has_child = groups.iter().any(|g| g.group_id == *child_id);
2430 if has_parent && has_child {
2431 let child_idx = groups.iter().position(|g| g.group_id == *child_id).unwrap();
2432 let child_group = groups.remove(child_idx);
2433 let parent = groups
2434 .iter_mut()
2435 .find(|g| g.group_id == *parent_id)
2436 .unwrap();
2437 let child_source_path = self
2441 .definitions
2442 .iter()
2443 .find(|d| {
2444 let parts: Vec<&str> = d.meta.source_group.split('.').collect();
2445 parts.len() > 1 && parts[parts.len() - 1] == *child_id
2446 })
2447 .and_then(|d| d.meta.source_path.as_deref());
2448 let distribution = child_source_path.and_then(|key| {
2449 nesting_info
2450 .and_then(|ni| ni.get(key))
2451 .or_else(|| inferred_nesting.get(key))
2452 });
2453 for (i, child_rep) in child_group.repetitions.into_iter().enumerate() {
2454 let target_idx = distribution
2455 .and_then(|dist| dist.get(i))
2456 .copied()
2457 .unwrap_or(0);
2458
2459 if let Some(target_rep) = parent.repetitions.get_mut(target_idx) {
2460 if let Some(existing) = target_rep
2461 .child_groups
2462 .iter_mut()
2463 .find(|g| g.group_id == *child_id)
2464 {
2465 existing.repetitions.push(child_rep);
2466 } else {
2467 target_rep.child_groups.push(AssembledGroup {
2468 group_id: child_id.clone(),
2469 repetitions: vec![child_rep],
2470 });
2471 }
2472 }
2473 }
2474 }
2475 }
2476
2477 let post_group_start = root_segments.len();
2478 AssembledTree {
2479 segments: root_segments,
2480 groups,
2481 post_group_start,
2482 inter_group_segments: std::collections::BTreeMap::new(),
2483 }
2484 }
2485
2486 fn count_repetitions(tree: &AssembledTree, group_path: &str) -> usize {
2488 let parts: Vec<&str> = group_path.split('.').collect();
2489
2490 let (first_id, first_rep) = parse_group_spec(parts[0]);
2491 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2492 Some(g) => g,
2493 None => return 0,
2494 };
2495
2496 if parts.len() == 1 {
2497 return first_group.repetitions.len();
2498 }
2499
2500 let mut current_instance = match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2502 Some(i) => i,
2503 None => return 0,
2504 };
2505
2506 for (i, part) in parts[1..].iter().enumerate() {
2507 let (group_id, explicit_rep) = parse_group_spec(part);
2508 let child_group = match current_instance
2509 .child_groups
2510 .iter()
2511 .find(|g| g.group_id == group_id)
2512 {
2513 Some(g) => g,
2514 None => return 0,
2515 };
2516
2517 if i == parts.len() - 2 {
2518 return child_group.repetitions.len();
2520 }
2521 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2522 Some(i) => i,
2523 None => return 0,
2524 };
2525 }
2526
2527 0
2528 }
2529
2530 pub fn map_interchange(
2539 msg_engine: &MappingEngine,
2540 tx_engine: &MappingEngine,
2541 tree: &AssembledTree,
2542 transaction_group: &str,
2543 enrich_codes: bool,
2544 ) -> crate::model::MappedMessage {
2545 let (stammdaten, nesting_info, dp_routing) =
2547 msg_engine.map_all_forward_inner(tree, enrich_codes);
2548
2549 let transaktionen = tree
2551 .groups
2552 .iter()
2553 .find(|g| g.group_id == transaction_group)
2554 .map(|sg| {
2555 sg.repetitions
2556 .iter()
2557 .map(|instance| {
2558 let wrapped_tree = AssembledTree {
2561 segments: vec![],
2562 groups: vec![AssembledGroup {
2563 group_id: transaction_group.to_string(),
2564 repetitions: vec![instance.clone()],
2565 }],
2566 post_group_start: 0,
2567 inter_group_segments: std::collections::BTreeMap::new(),
2568 };
2569
2570 let (tx_result, tx_nesting, tx_dp_routing) = tx_engine
2574 .map_all_forward_inner_with_tx(
2575 &wrapped_tree,
2576 enrich_codes,
2577 Some(transaction_group),
2578 );
2579
2580 crate::model::MappedTransaktion {
2581 stammdaten: tx_result,
2582 nesting_info: tx_nesting,
2583 dp_routing: tx_dp_routing,
2584 }
2585 })
2586 .collect()
2587 })
2588 .unwrap_or_default();
2589
2590 crate::model::MappedMessage {
2591 stammdaten,
2592 transaktionen,
2593 nesting_info,
2594 dp_routing,
2595 inter_group_segments: tree.inter_group_segments.clone(),
2596 }
2597 }
2598
2599 pub fn map_interchange_reverse(
2609 msg_engine: &MappingEngine,
2610 tx_engine: &MappingEngine,
2611 mapped: &crate::model::MappedMessage,
2612 transaction_group: &str,
2613 filtered_mig: Option<&MigSchema>,
2614 ) -> AssembledTree {
2615 let _owned_msg: Option<serde_json::Value>;
2621 let msg_stammdaten = if !mapped.dp_routing.is_empty() {
2622 if let serde_json::Value::Object(map) = &mapped.stammdaten {
2623 let mut cloned = map.clone();
2624 unroute_lokation_to_nad_dp(&mut cloned, &mapped.dp_routing);
2625 _owned_msg = Some(serde_json::Value::Object(cloned));
2626 _owned_msg.as_ref().unwrap()
2627 } else {
2628 _owned_msg = None;
2629 &mapped.stammdaten
2630 }
2631 } else {
2632 _owned_msg = None;
2633 &mapped.stammdaten
2634 };
2635
2636 let msg_tree = msg_engine.map_all_reverse(
2637 msg_stammdaten,
2638 if mapped.nesting_info.is_empty() {
2639 None
2640 } else {
2641 Some(&mapped.nesting_info)
2642 },
2643 );
2644
2645 let mut sg4_reps: Vec<AssembledGroupInstance> = Vec::new();
2647
2648 struct DefWithMeta<'a> {
2652 def: &'a MappingDefinition,
2653 relative: String,
2654 depth: usize,
2655 }
2656
2657 let mut sorted_defs: Vec<DefWithMeta> = tx_engine
2658 .definitions
2659 .iter()
2660 .map(|def| {
2661 let relative = strip_tx_group_prefix(&def.meta.source_group, transaction_group);
2662 let depth = if relative.is_empty() {
2663 0
2664 } else {
2665 relative.chars().filter(|c| *c == '.').count() + 1
2666 };
2667 DefWithMeta {
2668 def,
2669 relative,
2670 depth,
2671 }
2672 })
2673 .collect();
2674
2675 let mut parent_rep_map: std::collections::HashMap<String, usize> =
2679 std::collections::HashMap::new();
2680 for dm in &sorted_defs {
2681 if dm.depth >= 2 {
2682 let parts: Vec<&str> = dm.relative.split('.').collect();
2683 let (_, parent_rep) = parse_group_spec(parts[0]);
2684 if let Some(rep_idx) = parent_rep {
2685 if let Some(sp) = &dm.def.meta.source_path {
2686 if let Some((parent_path, _)) = sp.rsplit_once('.') {
2687 parent_rep_map
2688 .entry(parent_path.to_string())
2689 .or_insert(rep_idx);
2690 }
2691 }
2692 }
2693 }
2694 }
2695
2696 for dm in &mut sorted_defs {
2699 if dm.depth == 1 && !dm.relative.contains(':') {
2700 if let Some(sp) = &dm.def.meta.source_path {
2701 if let Some(rep_idx) = parent_rep_map.get(sp.as_str()) {
2702 dm.relative = format!("{}:{}", dm.relative, rep_idx);
2703 }
2704 }
2705 }
2706 }
2707
2708 if let Some(mig) = filtered_mig {
2715 let mig_order = build_reverse_mig_group_order(mig, transaction_group);
2716 sorted_defs.sort_by(|a, b| {
2717 a.depth.cmp(&b.depth).then_with(|| {
2718 let a_id = a.relative.split(':').next().unwrap_or(&a.relative);
2719 let b_id = b.relative.split(':').next().unwrap_or(&b.relative);
2720 let a_pos = variant_mig_position(a.def, a_id, &mig_order);
2722 let b_pos = variant_mig_position(b.def, b_id, &mig_order);
2723 a_pos.cmp(&b_pos).then(a.relative.cmp(&b.relative))
2724 })
2725 });
2726 } else {
2727 sorted_defs.sort_by(|a, b| a.depth.cmp(&b.depth).then(a.relative.cmp(&b.relative)));
2728 }
2729
2730 for tx in &mapped.transaktionen {
2731 let mut root_segs: Vec<AssembledSegment> = Vec::new();
2732 let mut child_groups: Vec<AssembledGroup> = Vec::new();
2733
2734 let _owned_tx: Option<serde_json::Value>;
2739 let tx_stammdaten: &serde_json::Value = if !tx.dp_routing.is_empty() {
2740 if let serde_json::Value::Object(map) = &tx.stammdaten {
2741 let mut cloned = map.clone();
2742 unroute_lokation_to_nad_dp(&mut cloned, &tx.dp_routing);
2743 _owned_tx = Some(serde_json::Value::Object(cloned));
2744 _owned_tx.as_ref().unwrap()
2745 } else {
2746 _owned_tx = None;
2747 &tx.stammdaten
2748 }
2749 } else {
2750 _owned_tx = None;
2751 &tx.stammdaten
2752 };
2753
2754 let mut source_path_to_rep: std::collections::HashMap<String, Vec<usize>> =
2759 std::collections::HashMap::new();
2760
2761 for dm in &sorted_defs {
2762 let entity_key = to_camel_case(&dm.def.meta.entity);
2765 let _tx_extracted: Option<serde_json::Value>;
2766 let bo4e_value = if let Some(v) = tx_stammdaten.get(&entity_key) {
2767 _tx_extracted = None;
2768 v
2769 } else if dm.def.meta.source_group.contains('.') {
2770 match extract_child_from_parent(tx_stammdaten, &tx_engine.definitions, dm.def)
2771 {
2772 Some(v) => {
2773 _tx_extracted = Some(v);
2774 _tx_extracted.as_ref().unwrap()
2775 }
2776 None => continue,
2777 }
2778 } else {
2779 continue;
2780 };
2781
2782 let unwrapped_value: Option<serde_json::Value>;
2784 let bo4e_value = if bo4e_value.is_object() && !bo4e_value.is_array() {
2785 if let Some(disc_value) = dm
2786 .def
2787 .meta
2788 .discriminator
2789 .as_deref()
2790 .and_then(|d| d.split_once('='))
2791 .map(|(_, v)| v)
2792 {
2793 if let Some(inner) = bo4e_value.get(disc_value) {
2794 let mut injected = inner.clone();
2795 if let Some(ref cf) = dm.def.companion_fields {
2796 let disc_path = dm
2797 .def
2798 .meta
2799 .discriminator
2800 .as_deref()
2801 .unwrap()
2802 .split_once('=')
2803 .unwrap()
2804 .0
2805 .to_lowercase();
2806 for (path, mapping) in cf {
2807 let cf_path = path.to_lowercase();
2808 let matches = cf_path == disc_path
2809 || format!("{}.0", cf_path) == disc_path;
2810 if matches {
2811 let target = match mapping {
2812 FieldMapping::Simple(t) => t.as_str(),
2813 FieldMapping::Structured(s) => s.target.as_str(),
2814 FieldMapping::Nested(_) => continue,
2815 };
2816 if !target.is_empty() {
2817 if let Some(obj) = injected.as_object_mut() {
2818 obj.entry(target.to_string()).or_insert_with(
2819 || {
2820 serde_json::Value::String(
2821 disc_value.to_string(),
2822 )
2823 },
2824 );
2825 }
2826 }
2827 break;
2828 }
2829 }
2830 }
2831 unwrapped_value = Some(injected);
2832 unwrapped_value.as_ref().unwrap()
2833 } else {
2834 bo4e_value
2835 }
2836 } else if is_map_keyed_object(bo4e_value) {
2837 let map = bo4e_value.as_object().unwrap();
2838 let arr: Vec<serde_json::Value> = map
2839 .iter()
2840 .map(|(key, val)| {
2841 let mut item = val.clone();
2842 if let Some(obj) = item.as_object_mut() {
2843 if let Some(qualifier_field) = find_qualifier_companion_field(
2844 &tx_engine.definitions,
2845 &dm.def.meta.entity,
2846 ) {
2847 let entry = obj
2848 .entry(qualifier_field)
2849 .or_insert(serde_json::Value::Null);
2850 if entry.is_null() {
2851 *entry = serde_json::Value::String(key.clone());
2852 }
2853 }
2854 }
2855 item
2856 })
2857 .collect();
2858 unwrapped_value = Some(serde_json::Value::Array(arr));
2859 unwrapped_value.as_ref().unwrap()
2860 } else {
2861 bo4e_value
2862 }
2863 } else {
2864 bo4e_value
2865 };
2866
2867 let items: Vec<&serde_json::Value> = if bo4e_value.is_array() {
2871 bo4e_value.as_array().unwrap().iter().collect()
2872 } else {
2873 vec![bo4e_value]
2874 };
2875
2876 for (item_idx, item) in items.iter().enumerate() {
2877 let instance = tx_engine.map_reverse(item, dm.def);
2878
2879 if instance.segments.is_empty() && instance.child_groups.is_empty() {
2881 continue;
2882 }
2883
2884 if dm.relative.is_empty() {
2885 root_segs.extend(instance.segments);
2886 } else {
2887 let effective_relative = if dm.depth >= 2 {
2891 let rel = if items.len() > 1 {
2894 strip_all_rep_indices(&dm.relative)
2895 } else {
2896 dm.relative.clone()
2897 };
2898 let skip_nesting = dm
2905 .def
2906 .meta
2907 .source_path
2908 .as_ref()
2909 .and_then(|sp| sp.rsplit_once('.'))
2910 .and_then(|(parent_path, _)| source_path_to_rep.get(parent_path))
2911 .is_some_and(|reps| reps.len() == 1);
2912 let nesting_idx = if items.len() > 1 && !skip_nesting {
2913 dm.def
2914 .meta
2915 .source_path
2916 .as_ref()
2917 .and_then(|sp| tx.nesting_info.get(sp))
2918 .and_then(|dist| dist.get(item_idx))
2919 .copied()
2920 } else {
2921 None
2922 };
2923 if let Some(parent_rep) = nesting_idx {
2924 let parts: Vec<&str> = rel.split('.').collect();
2926 let parent_id = parts[0].split(':').next().unwrap_or(parts[0]);
2927 let rest = parts[1..].join(".");
2928 format!("{}:{}.{}", parent_id, parent_rep, rest)
2929 } else {
2930 resolve_child_relative(
2931 &rel,
2932 dm.def.meta.source_path.as_deref(),
2933 &source_path_to_rep,
2934 item_idx,
2935 )
2936 }
2937 } else if dm.depth == 1 {
2938 let child_key = dm
2941 .def
2942 .meta
2943 .source_path
2944 .as_ref()
2945 .map(|sp| format!("{sp}#child"));
2946 if let Some(child_indices) =
2947 child_key.as_ref().and_then(|ck| tx.nesting_info.get(ck))
2948 {
2949 if let Some(&target) = child_indices.get(item_idx) {
2950 if target != usize::MAX {
2951 let base =
2952 dm.relative.split(':').next().unwrap_or(&dm.relative);
2953 format!("{}:{}", base, target)
2954 } else {
2955 dm.relative.clone()
2956 }
2957 } else if items.len() > 1 && item_idx > 0 {
2958 strip_rep_index(&dm.relative)
2959 } else {
2960 dm.relative.clone()
2961 }
2962 } else if items.len() > 1 && item_idx > 0 {
2963 strip_rep_index(&dm.relative)
2964 } else {
2965 dm.relative.clone()
2966 }
2967 } else if items.len() > 1 && item_idx > 0 {
2968 strip_rep_index(&dm.relative)
2971 } else {
2972 dm.relative.clone()
2973 };
2974
2975 let rep_used =
2976 place_in_groups(&mut child_groups, &effective_relative, instance);
2977
2978 if dm.depth == 1 {
2980 if let Some(sp) = &dm.def.meta.source_path {
2981 source_path_to_rep
2982 .entry(sp.clone())
2983 .or_default()
2984 .push(rep_used);
2985 }
2986 }
2987 }
2988 }
2989 }
2990
2991 if let Some(mig) = filtered_mig {
2996 sort_variant_reps_by_mig(&mut child_groups, mig, transaction_group);
2997 }
2998
2999 sg4_reps.push(AssembledGroupInstance {
3000 segments: root_segs,
3001 child_groups,
3002 entry_mig_number: None,
3003 variant_mig_numbers: vec![],
3004 skipped_segments: Vec::new(),
3005 skipped_positions: Vec::new(),
3006 });
3007 }
3008
3009 let mut root_segments = Vec::new();
3016 let mut uns_segments = Vec::new();
3017 let mut uns_is_summary = false;
3018 let mut found_uns = false;
3019 for seg in msg_tree.segments {
3020 if seg.tag == "UNS" {
3021 uns_is_summary = seg
3023 .elements
3024 .first()
3025 .and_then(|el| el.first())
3026 .map(|v| v == "S")
3027 .unwrap_or(false);
3028 uns_segments.push(seg);
3029 found_uns = true;
3030 } else if found_uns {
3031 uns_segments.push(seg);
3033 } else {
3034 root_segments.push(seg);
3035 }
3036 }
3037
3038 let pre_group_count = root_segments.len();
3039 let mut all_groups = msg_tree.groups;
3040 let mut inter_group = msg_tree.inter_group_segments;
3041
3042 let sg_num = |id: &str| -> usize {
3044 id.strip_prefix("SG")
3045 .and_then(|n| n.parse::<usize>().ok())
3046 .unwrap_or(0)
3047 };
3048
3049 if !sg4_reps.is_empty() {
3050 if uns_is_summary {
3051 all_groups.push(AssembledGroup {
3053 group_id: transaction_group.to_string(),
3054 repetitions: sg4_reps,
3055 });
3056 if !uns_segments.is_empty() {
3057 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3062 let tx_num = sg_num(transaction_group);
3063 let uns_pos = all_groups
3064 .iter()
3065 .rposition(|g| sg_num(&g.group_id) <= tx_num)
3066 .map(|i| i + 1)
3067 .unwrap_or(all_groups.len());
3068 inter_group.insert(uns_pos, uns_segments);
3069 }
3070 } else {
3071 if !uns_segments.is_empty() {
3073 inter_group.insert(all_groups.len(), uns_segments);
3074 }
3075 all_groups.push(AssembledGroup {
3076 group_id: transaction_group.to_string(),
3077 repetitions: sg4_reps,
3078 });
3079 }
3080 } else if !uns_segments.is_empty() {
3081 if transaction_group.is_empty() {
3082 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3087 if uns_is_summary {
3088 inter_group.insert(all_groups.len(), uns_segments);
3089 } else {
3090 inter_group.insert(0, uns_segments);
3091 }
3092 } else {
3093 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3097 let tx_num = sg_num(transaction_group);
3098 let uns_pos = all_groups
3099 .iter()
3100 .rposition(|g| sg_num(&g.group_id) <= tx_num)
3101 .map(|i| i + 1)
3102 .unwrap_or(all_groups.len());
3103 inter_group.insert(uns_pos, uns_segments);
3104 }
3105 }
3106
3107 for (k, segs) in &mapped.inter_group_segments {
3115 if segs.is_empty() {
3116 continue;
3117 }
3118 let existing_tags: std::collections::HashSet<String> = inter_group
3119 .get(k)
3120 .map(|v| v.iter().map(|s| s.tag.clone()).collect())
3121 .unwrap_or_default();
3122 for seg in segs {
3123 if existing_tags.contains(&seg.tag) {
3124 continue;
3125 }
3126 inter_group.entry(*k).or_default().push(seg.clone());
3127 }
3128 }
3129
3130 AssembledTree {
3131 segments: root_segments,
3132 groups: all_groups,
3133 post_group_start: pre_group_count,
3134 inter_group_segments: inter_group,
3135 }
3136 }
3137
3138 pub fn build_group_from_bo4e(
3140 &self,
3141 bo4e_value: &serde_json::Value,
3142 def: &MappingDefinition,
3143 ) -> AssembledGroup {
3144 let instance = self.map_reverse(bo4e_value, def);
3145 let leaf_group = def
3146 .meta
3147 .source_group
3148 .rsplit('.')
3149 .next()
3150 .unwrap_or(&def.meta.source_group);
3151
3152 AssembledGroup {
3153 group_id: leaf_group.to_string(),
3154 repetitions: vec![instance],
3155 }
3156 }
3157
3158 pub fn map_interchange_typed<M, T>(
3166 msg_engine: &MappingEngine,
3167 tx_engine: &MappingEngine,
3168 tree: &AssembledTree,
3169 tx_group: &str,
3170 enrich_codes: bool,
3171 nachrichtendaten: crate::model::Nachrichtendaten,
3172 interchangedaten: crate::model::Interchangedaten,
3173 ) -> Result<crate::model::Interchange<M, T>, serde_json::Error>
3174 where
3175 M: serde::de::DeserializeOwned,
3176 T: serde::de::DeserializeOwned,
3177 {
3178 let mapped = Self::map_interchange(msg_engine, tx_engine, tree, tx_group, enrich_codes);
3179 let nachricht = mapped.into_dynamic_nachricht(nachrichtendaten);
3180 let dynamic = crate::model::DynamicInterchange {
3181 interchangedaten,
3182 nachrichten: vec![nachricht],
3183 };
3184 let value = serde_json::to_value(&dynamic)?;
3185 serde_json::from_value(value)
3186 }
3187
3188 pub fn map_interchange_reverse_typed<M, T>(
3195 msg_engine: &MappingEngine,
3196 tx_engine: &MappingEngine,
3197 nachricht: &crate::model::Nachricht<M, T>,
3198 tx_group: &str,
3199 ) -> Result<AssembledTree, serde_json::Error>
3200 where
3201 M: serde::Serialize,
3202 T: serde::Serialize,
3203 {
3204 let stammdaten = serde_json::to_value(&nachricht.stammdaten)?;
3205 let transaktionen: Vec<crate::model::MappedTransaktion> = nachricht
3206 .transaktionen
3207 .iter()
3208 .map(|t| {
3209 Ok(crate::model::MappedTransaktion {
3210 stammdaten: serde_json::to_value(t)?,
3211 nesting_info: Default::default(),
3212 dp_routing: Default::default(),
3213 })
3214 })
3215 .collect::<Result<Vec<_>, serde_json::Error>>()?;
3216 let mapped = crate::model::MappedMessage {
3217 stammdaten,
3218 transaktionen,
3219 nesting_info: Default::default(),
3220 dp_routing: Default::default(),
3221 inter_group_segments: Default::default(),
3222 };
3223 Ok(Self::map_interchange_reverse(
3224 msg_engine, tx_engine, &mapped, tx_group, None,
3225 ))
3226 }
3227}
3228
3229fn parse_source_path_part(part: &str) -> (&str, Option<&str>) {
3236 if let Some(pos) = part.find('_') {
3240 let group = &part[..pos];
3241 let qualifier = &part[pos + 1..];
3242 if !qualifier.is_empty() {
3243 return (group, Some(qualifier));
3244 }
3245 }
3246 (part, None)
3247}
3248
3249fn build_reverse_mig_group_order(mig: &MigSchema, tx_group_id: &str) -> HashMap<String, usize> {
3257 let mut order = HashMap::new();
3258 if let Some(tg) = mig.segment_groups.iter().find(|g| g.id == tx_group_id) {
3259 for (i, nested) in tg.nested_groups.iter().enumerate() {
3260 if let Some(ref vc) = nested.variant_code {
3262 let variant_key = format!("{}_{}", nested.id, vc.to_uppercase());
3263 order.insert(variant_key, i);
3264 }
3265 order.entry(nested.id.clone()).or_insert(i);
3267 }
3268 }
3269 order
3270}
3271
3272fn variant_mig_position(
3278 def: &MappingDefinition,
3279 base_group_id: &str,
3280 mig_order: &HashMap<String, usize>,
3281) -> usize {
3282 if let Some(ref sp) = def.meta.source_path {
3285 let base_lower = base_group_id.to_lowercase();
3287 for part in sp.split('.') {
3288 if part.starts_with(&base_lower)
3289 || part.starts_with(base_group_id.to_lowercase().as_str())
3290 {
3291 if let Some(underscore_pos) = part.find('_') {
3293 let qualifier = &part[underscore_pos + 1..];
3294 let variant_key = format!("{}_{}", base_group_id, qualifier.to_uppercase());
3295 if let Some(&pos) = mig_order.get(&variant_key) {
3296 return pos;
3297 }
3298 }
3299 }
3300 }
3301 }
3302 mig_order.get(base_group_id).copied().unwrap_or(usize::MAX)
3304}
3305
3306fn find_rep_by_entry_qualifier<'a>(
3311 reps: &'a [AssembledGroupInstance],
3312 qualifier: &str,
3313) -> Option<&'a AssembledGroupInstance> {
3314 let parts: Vec<&str> = qualifier.split('_').collect();
3316 reps.iter().find(|inst| {
3317 inst.segments.first().is_some_and(|seg| {
3318 seg.elements
3319 .first()
3320 .and_then(|e| e.first())
3321 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3322 })
3323 })
3324}
3325
3326fn find_all_reps_by_entry_qualifier<'a>(
3328 reps: &'a [AssembledGroupInstance],
3329 qualifier: &str,
3330) -> Vec<&'a AssembledGroupInstance> {
3331 let parts: Vec<&str> = qualifier.split('_').collect();
3333 reps.iter()
3334 .filter(|inst| {
3335 inst.segments.first().is_some_and(|seg| {
3336 seg.elements
3337 .first()
3338 .and_then(|e| e.first())
3339 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3340 })
3341 })
3342 .collect()
3343}
3344
3345fn has_source_path_qualifiers(source_path: &str) -> bool {
3347 source_path.split('.').any(|part| {
3348 if let Some(pos) = part.find('_') {
3349 pos < part.len() - 1
3350 } else {
3351 false
3352 }
3353 })
3354}
3355
3356fn parse_group_spec(part: &str) -> (&str, Option<usize>) {
3357 if let Some(colon_pos) = part.find(':') {
3358 let id = &part[..colon_pos];
3359 let rep = part[colon_pos + 1..].parse::<usize>().ok();
3360 (id, rep)
3361 } else {
3362 (part, None)
3363 }
3364}
3365
3366fn strip_tx_group_prefix(source_group: &str, tx_group: &str) -> String {
3372 if source_group == tx_group || source_group.is_empty() {
3373 String::new()
3374 } else if let Some(rest) = source_group.strip_prefix(tx_group) {
3375 rest.strip_prefix('.').unwrap_or(rest).to_string()
3376 } else {
3377 source_group.to_string()
3378 }
3379}
3380
3381fn place_in_groups(
3389 groups: &mut Vec<AssembledGroup>,
3390 relative_path: &str,
3391 instance: AssembledGroupInstance,
3392) -> usize {
3393 let parts: Vec<&str> = relative_path.split('.').collect();
3394
3395 if parts.len() == 1 {
3396 let (id, rep) = parse_group_spec(parts[0]);
3398
3399 let group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == id) {
3401 g
3402 } else {
3403 groups.push(AssembledGroup {
3404 group_id: id.to_string(),
3405 repetitions: vec![],
3406 });
3407 groups.last_mut().unwrap()
3408 };
3409
3410 if let Some(rep_idx) = rep {
3411 while group.repetitions.len() <= rep_idx {
3413 group.repetitions.push(AssembledGroupInstance {
3414 segments: vec![],
3415 child_groups: vec![],
3416 entry_mig_number: None,
3417 variant_mig_numbers: vec![],
3418 skipped_segments: Vec::new(),
3419 skipped_positions: Vec::new(),
3420 });
3421 }
3422 group.repetitions[rep_idx]
3423 .segments
3424 .extend(instance.segments);
3425 group.repetitions[rep_idx]
3426 .child_groups
3427 .extend(instance.child_groups);
3428 rep_idx
3429 } else {
3430 let pos = group.repetitions.len();
3432 group.repetitions.push(instance);
3433 pos
3434 }
3435 } else {
3436 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3438 let rep_idx = parent_rep.unwrap_or(0);
3439
3440 let parent_group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == parent_id) {
3442 g
3443 } else {
3444 groups.push(AssembledGroup {
3445 group_id: parent_id.to_string(),
3446 repetitions: vec![],
3447 });
3448 groups.last_mut().unwrap()
3449 };
3450
3451 while parent_group.repetitions.len() <= rep_idx {
3453 parent_group.repetitions.push(AssembledGroupInstance {
3454 segments: vec![],
3455 child_groups: vec![],
3456 entry_mig_number: None,
3457 variant_mig_numbers: vec![],
3458 skipped_segments: Vec::new(),
3459 skipped_positions: Vec::new(),
3460 });
3461 }
3462
3463 let remaining = parts[1..].join(".");
3464 place_in_groups(
3465 &mut parent_group.repetitions[rep_idx].child_groups,
3466 &remaining,
3467 instance,
3468 );
3469 rep_idx
3470 }
3471}
3472
3473fn resolve_child_relative(
3485 relative: &str,
3486 source_path: Option<&str>,
3487 source_path_to_rep: &std::collections::HashMap<String, Vec<usize>>,
3488 item_idx: usize,
3489) -> String {
3490 let parts: Vec<&str> = relative.split('.').collect();
3491 if parts.is_empty() {
3492 return relative.to_string();
3493 }
3494
3495 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3497 if parent_rep.is_some() {
3498 return relative.to_string();
3499 }
3500
3501 if let Some(sp) = source_path {
3503 if let Some((parent_path, _child)) = sp.rsplit_once('.') {
3504 if let Some(rep_indices) = source_path_to_rep.get(parent_path) {
3506 let rep_idx = rep_indices
3507 .get(item_idx)
3508 .or_else(|| rep_indices.last())
3509 .copied()
3510 .unwrap_or(0);
3511 let rest = parts[1..].join(".");
3512 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3513 }
3514 let prefix = format!("{}_", parent_path);
3522 let mut unioned: Vec<usize> = source_path_to_rep
3523 .iter()
3524 .filter(|(k, _)| k.starts_with(&prefix))
3525 .flat_map(|(_, v)| v.iter().copied())
3526 .collect();
3527 if !unioned.is_empty() {
3528 unioned.sort_unstable();
3529 unioned.dedup();
3530 let rep_idx = unioned
3531 .get(item_idx)
3532 .or_else(|| unioned.last())
3533 .copied()
3534 .unwrap_or(0);
3535 let rest = parts[1..].join(".");
3536 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3537 }
3538 }
3539 }
3540
3541 relative.to_string()
3543}
3544
3545struct DiscriminatorMatcher<'a> {
3552 tag: &'a str,
3553 element_idx: usize,
3554 component_idx: usize,
3555 expected_values: Vec<&'a str>,
3556 occurrence: Option<usize>,
3558}
3559
3560impl<'a> DiscriminatorMatcher<'a> {
3561 fn parse(disc: &'a str) -> Option<Self> {
3562 let (spec, expected) = disc.split_once('=')?;
3563 let parts: Vec<&str> = spec.split('.').collect();
3564 if parts.len() != 3 {
3565 return None;
3566 }
3567 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
3568 Some(Self {
3569 tag: parts[0],
3570 element_idx: parts[1].parse().ok()?,
3571 component_idx: parts[2].parse().ok()?,
3572 expected_values: expected_raw.split('|').collect(),
3573 occurrence,
3574 })
3575 }
3576
3577 fn matches(&self, instance: &AssembledGroupInstance) -> bool {
3578 instance.segments.iter().any(|s| {
3579 s.tag.eq_ignore_ascii_case(self.tag)
3580 && s.elements
3581 .get(self.element_idx)
3582 .and_then(|e| e.get(self.component_idx))
3583 .map(|v| self.expected_values.iter().any(|ev| v == ev))
3584 .unwrap_or(false)
3585 })
3586 }
3587
3588 fn filter_instances<'b>(
3590 &self,
3591 instances: Vec<&'b AssembledGroupInstance>,
3592 ) -> Vec<&'b AssembledGroupInstance> {
3593 let matching: Vec<_> = instances
3594 .into_iter()
3595 .filter(|inst| self.matches(inst))
3596 .collect();
3597 if let Some(occ) = self.occurrence {
3598 matching.into_iter().nth(occ).into_iter().collect()
3599 } else {
3600 matching
3601 }
3602 }
3603}
3604
3605fn parse_discriminator_occurrence(expected: &str) -> (&str, Option<usize>) {
3611 if let Some(hash_pos) = expected.rfind('#') {
3612 if let Ok(occ) = expected[hash_pos + 1..].parse::<usize>() {
3613 return (&expected[..hash_pos], Some(occ));
3614 }
3615 }
3616 (expected, None)
3617}
3618
3619fn strip_rep_index(relative: &str) -> String {
3623 let (id, _) = parse_group_spec(relative);
3624 id.to_string()
3625}
3626
3627fn strip_all_rep_indices(relative: &str) -> String {
3632 relative
3633 .split('.')
3634 .map(|part| {
3635 let (id, _) = parse_group_spec(part);
3636 id
3637 })
3638 .collect::<Vec<_>>()
3639 .join(".")
3640}
3641
3642fn is_collect_all_path(path: &str) -> bool {
3647 let tag_part = path.split('.').next().unwrap_or("");
3648 if let Some(bracket_start) = tag_part.find('[') {
3649 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3650 if let Some(comma_pos) = inner.find(',') {
3651 let qualifier = &inner[..comma_pos];
3652 let occ = &inner[comma_pos + 1..];
3653 qualifier != "*" && occ == "*"
3655 } else {
3656 false
3657 }
3658 } else {
3659 false
3660 }
3661}
3662
3663fn parse_tag_qualifier(tag_part: &str) -> (String, Option<&str>, usize) {
3670 if let Some(bracket_start) = tag_part.find('[') {
3671 let tag = tag_part[..bracket_start].to_uppercase();
3672 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3673 if let Some(comma_pos) = inner.find(',') {
3674 let qualifier = &inner[..comma_pos];
3675 let index = inner[comma_pos + 1..].parse::<usize>().unwrap_or(0);
3676 if qualifier == "*" {
3678 (tag, None, index)
3679 } else {
3680 (tag, Some(qualifier), index)
3681 }
3682 } else {
3683 (tag, Some(inner), 0)
3684 }
3685 } else {
3686 (tag_part.to_uppercase(), None, 0)
3687 }
3688}
3689
3690fn inject_bo4e_metadata(mut value: serde_json::Value, bo4e_type: &str) -> serde_json::Value {
3695 match &mut value {
3696 serde_json::Value::Object(map) => {
3697 map.entry("boTyp")
3698 .or_insert_with(|| serde_json::Value::String(bo4e_type.to_uppercase()));
3699 map.entry("versionStruktur")
3700 .or_insert_with(|| serde_json::Value::String("1".to_string()));
3701 }
3702 serde_json::Value::Array(items) => {
3703 for item in items {
3704 if let serde_json::Value::Object(map) = item {
3705 map.entry("boTyp")
3706 .or_insert_with(|| serde_json::Value::String(bo4e_type.to_uppercase()));
3707 map.entry("versionStruktur")
3708 .or_insert_with(|| serde_json::Value::String("1".to_string()));
3709 }
3710 }
3711 }
3712 _ => {}
3713 }
3714 value
3715}
3716
3717pub fn deep_merge_insert(
3723 result: &mut serde_json::Map<String, serde_json::Value>,
3724 entity: &str,
3725 bo4e: serde_json::Value,
3726) {
3727 if let Some(existing) = result.get_mut(entity) {
3728 if let (Some(existing_arr), Some(new_arr)) =
3731 (existing.as_array().map(|a| a.len()), bo4e.as_array())
3732 {
3733 if existing_arr == new_arr.len() {
3734 let existing_arr = existing.as_array_mut().unwrap();
3735 for (existing_elem, new_elem) in existing_arr.iter_mut().zip(new_arr) {
3736 if let (Some(existing_map), Some(new_map)) =
3737 (existing_elem.as_object_mut(), new_elem.as_object())
3738 {
3739 for (k, v) in new_map {
3740 if let Some(existing_v) = existing_map.get_mut(k) {
3741 if let (Some(existing_inner), Some(new_inner)) =
3742 (existing_v.as_object_mut(), v.as_object())
3743 {
3744 for (ik, iv) in new_inner {
3745 existing_inner
3746 .entry(ik.clone())
3747 .or_insert_with(|| iv.clone());
3748 }
3749 }
3750 } else {
3751 existing_map.insert(k.clone(), v.clone());
3752 }
3753 }
3754 }
3755 }
3756 return;
3757 }
3758 }
3759 if let (Some(existing_map), serde_json::Value::Object(new_map)) =
3761 (existing.as_object_mut(), &bo4e)
3762 {
3763 for (k, v) in new_map {
3764 if let Some(existing_v) = existing_map.get_mut(k) {
3765 if let (Some(existing_inner), Some(new_inner)) =
3767 (existing_v.as_object_mut(), v.as_object())
3768 {
3769 for (ik, iv) in new_inner {
3770 existing_inner
3771 .entry(ik.clone())
3772 .or_insert_with(|| iv.clone());
3773 }
3774 }
3775 } else {
3777 existing_map.insert(k.clone(), v.clone());
3778 }
3779 }
3780 return;
3781 }
3782 }
3783 result.insert(entity.to_string(), bo4e);
3784}
3785
3786fn is_map_keyed_object(value: &serde_json::Value) -> bool {
3797 let Some(obj) = value.as_object() else {
3798 return false;
3799 };
3800 if obj.is_empty() {
3801 return false;
3802 }
3803 obj.iter().all(|(k, v)| {
3805 k.len() <= 5
3806 && k.chars()
3807 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
3808 && v.is_object()
3809 })
3810}
3811
3812fn find_qualifier_companion_field(
3821 definitions: &[crate::definition::MappingDefinition],
3822 entity: &str,
3823) -> Option<String> {
3824 for def in definitions {
3825 if def.meta.entity != *entity {
3826 continue;
3827 }
3828 let disc = def.meta.discriminator.as_deref()?;
3829 let (disc_path, _) = disc.split_once('=')?;
3830 let disc_path_lower = disc_path.to_lowercase();
3831
3832 let sections: Vec<&indexmap::IndexMap<String, FieldMapping>> =
3835 [def.companion_fields.as_ref(), Some(&def.fields)]
3836 .into_iter()
3837 .flatten()
3838 .collect();
3839
3840 for section in sections {
3841 for (path, mapping) in section {
3842 let cf_path = path.to_lowercase();
3843 let matches =
3844 cf_path == disc_path_lower || format!("{}.0", cf_path) == disc_path_lower;
3845 if matches {
3846 let target = match mapping {
3847 FieldMapping::Simple(t) => t.as_str(),
3848 FieldMapping::Structured(s) => s.target.as_str(),
3849 FieldMapping::Nested(_) => continue,
3850 };
3851 if !target.is_empty() {
3852 return Some(target.to_string());
3853 }
3854 }
3855 }
3856 }
3857 }
3858 None
3859}
3860
3861fn extract_child_from_parent(
3870 entities: &serde_json::Value,
3871 definitions: &[MappingDefinition],
3872 child_def: &MappingDefinition,
3873) -> Option<serde_json::Value> {
3874 extract_child_from_parent_with_indices(entities, definitions, child_def).map(|(v, _)| v)
3875}
3876
3877fn extract_child_from_parent_with_indices(
3882 entities: &serde_json::Value,
3883 definitions: &[MappingDefinition],
3884 child_def: &MappingDefinition,
3885) -> Option<(serde_json::Value, Vec<usize>)> {
3886 let parts: Vec<&str> = child_def.meta.source_group.split('.').collect();
3887 if parts.len() < 2 {
3888 return None;
3889 }
3890 let parent_group = parts[0];
3891 let parent_def = definitions
3892 .iter()
3893 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_def.meta.entity)?;
3894 let parent_key = to_camel_case(&parent_def.meta.entity);
3895 let child_key = to_camel_case(&child_def.meta.entity);
3896 let parent_value = entities.get(&parent_key)?;
3897
3898 if let Some(parent_map) = parent_value.as_object() {
3900 if is_map_keyed_value(parent_map) {
3901 let mut children: Vec<serde_json::Value> = Vec::new();
3902 let mut indices: Vec<usize> = Vec::new();
3903 for (i, (_key, inner)) in parent_map.iter().enumerate() {
3904 if let Some(child) = inner.get(&child_key) {
3905 if !child.is_null() {
3906 children.push(child.clone());
3907 indices.push(i);
3908 }
3909 }
3910 }
3911 return match children.len() {
3912 0 => None,
3913 1 => Some((children.into_iter().next().unwrap(), indices)),
3914 _ => Some((serde_json::Value::Array(children), indices)),
3915 };
3916 }
3917 }
3918
3919 if let Some(parent_arr) = parent_value.as_array() {
3921 let mut children: Vec<serde_json::Value> = Vec::new();
3922 let mut indices: Vec<usize> = Vec::new();
3923 for (i, item) in parent_arr.iter().enumerate() {
3924 if let Some(child) = item.get(&child_key) {
3925 if !child.is_null() {
3926 children.push(child.clone());
3927 indices.push(i);
3928 }
3929 }
3930 }
3931 return match children.len() {
3932 0 => None,
3933 1 => Some((children.into_iter().next().unwrap(), indices)),
3934 _ => Some((serde_json::Value::Array(children), indices)),
3935 };
3936 }
3937
3938 let child = parent_value.get(&child_key)?;
3940 if child.is_null() {
3941 return None;
3942 }
3943 Some((child.clone(), vec![0]))
3944}
3945
3946fn nest_child_entities_in_result(
3952 result: &mut serde_json::Map<String, serde_json::Value>,
3953 definitions: &[MappingDefinition],
3954 nesting_info: &std::collections::HashMap<String, Vec<usize>>,
3955 transaction_group: Option<&str>,
3956) {
3957 let mut nesting_pairs: Vec<(String, String, String, Option<String>)> = Vec::new();
3960 for def in definitions {
3961 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
3962 if parts.len() < 2 {
3963 continue;
3964 }
3965 let parent_group = parts[0];
3966 if transaction_group.is_some_and(|tx| tx == parent_group) {
3972 continue;
3973 }
3974 let child_entity = def.meta.entity.clone();
3975 let child_has_parent_level_def = definitions
3979 .iter()
3980 .any(|d| d.meta.source_group == parent_group && d.meta.entity == child_entity);
3981 if child_has_parent_level_def {
3982 continue;
3983 }
3984 let parent_entity = definitions
3986 .iter()
3987 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_entity)
3988 .map(|d| d.meta.entity.clone());
3989 if let Some(ref parent_entity) = parent_entity {
3990 let child_key_lc = to_camel_case(&child_entity);
3995 let parent_defs: Vec<_> = definitions
3996 .iter()
3997 .filter(|d| d.meta.entity == *parent_entity)
3998 .collect();
3999 let has_conflicting_field = parent_defs.iter().any(|pd| {
4000 pd.fields.values().any(|fm| {
4001 let target = match fm {
4002 crate::definition::FieldMapping::Simple(t) => t.as_str(),
4003 crate::definition::FieldMapping::Structured(s) => s.target.as_str(),
4004 crate::definition::FieldMapping::Nested(_) => "",
4005 };
4006 target.starts_with(&child_key_lc)
4007 && target.get(child_key_lc.len()..child_key_lc.len() + 1) == Some(".")
4008 })
4009 });
4010 if has_conflicting_field {
4011 continue;
4012 }
4013 if nesting_pairs
4015 .iter()
4016 .any(|(_, pe, ce, _)| *pe == *parent_entity && *ce == child_entity)
4017 {
4018 continue;
4019 }
4020 nesting_pairs.push((
4021 parent_group.to_string(),
4022 parent_entity.clone(),
4023 child_entity,
4024 def.meta.source_path.clone(),
4025 ));
4026 }
4027 }
4028
4029 for (_parent_group, parent_entity, child_entity, child_source_path) in nesting_pairs {
4030 let parent_key = to_camel_case(&parent_entity);
4031 let child_key = to_camel_case(&child_entity);
4032
4033 let child_value = match result.remove(&child_key) {
4035 Some(v) => v,
4036 None => continue,
4037 };
4038
4039 let Some(parent_value) = result.get_mut(&parent_key) else {
4044 result.insert(child_key, child_value);
4046 continue;
4047 };
4048 if parent_value.is_array() {
4049 result.insert(child_key, child_value);
4050 continue;
4051 }
4052
4053 let distribution = child_source_path
4055 .as_deref()
4056 .and_then(|sp| nesting_info.get(sp));
4057
4058 let child_items: Vec<(usize, &serde_json::Value)> = match &child_value {
4060 serde_json::Value::Array(arr) => arr.iter().enumerate().collect(),
4061 other => vec![(0, other)],
4062 };
4063
4064 let insert_or_append = |obj: &mut serde_json::Map<String, serde_json::Value>,
4067 key: &str,
4068 val: &serde_json::Value| {
4069 match obj.get_mut(key) {
4070 Some(existing) => {
4071 if !existing.is_array() {
4073 let prev = existing.take();
4074 *existing = serde_json::Value::Array(vec![prev]);
4075 }
4076 if let Some(arr) = existing.as_array_mut() {
4077 arr.push(val.clone());
4078 }
4079 }
4080 None => {
4081 obj.insert(key.to_string(), val.clone());
4082 }
4083 }
4084 };
4085
4086 if let Some(parent_map) = parent_value.as_object_mut() {
4088 if is_map_keyed_value(parent_map) {
4089 let keys: Vec<String> = parent_map.keys().cloned().collect();
4091 for (i, child_item) in &child_items {
4092 let target_idx = distribution
4093 .and_then(|dist| dist.get(*i))
4094 .copied()
4095 .unwrap_or(0);
4096 if let Some(key) = keys.get(target_idx) {
4097 if let Some(inner) = parent_map.get_mut(key).and_then(|v| v.as_object_mut())
4098 {
4099 insert_or_append(inner, &child_key, child_item);
4100 }
4101 }
4102 }
4103 continue;
4104 }
4105 }
4106
4107 if let Some(parent_arr) = parent_value.as_array_mut() {
4109 for (i, child_item) in &child_items {
4110 let target_idx = distribution
4111 .and_then(|dist| dist.get(*i))
4112 .copied()
4113 .unwrap_or(0);
4114 if let Some(parent_obj) = parent_arr
4115 .get_mut(target_idx)
4116 .and_then(|v| v.as_object_mut())
4117 {
4118 insert_or_append(parent_obj, &child_key, child_item);
4119 }
4120 }
4121 continue;
4122 }
4123
4124 if let Some(parent_obj) = parent_value.as_object_mut() {
4126 for (_i, child_item) in &child_items {
4127 insert_or_append(parent_obj, &child_key, child_item);
4128 }
4129 continue;
4130 }
4131
4132 result.insert(child_key, child_value);
4134 }
4135}
4136
4137pub use crate::dp_routing::{route_nad_dp_to_lokation, unroute_lokation_to_nad_dp, DpRouting};
4139
4140fn is_map_keyed_value(map: &serde_json::Map<String, serde_json::Value>) -> bool {
4142 if map.is_empty() {
4143 return false;
4144 }
4145 map.values().all(|v| v.is_object())
4146 && map.keys().all(|k| {
4147 k.len() <= 5
4148 || k.chars()
4149 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
4150 })
4151}
4152
4153fn to_camel_case(name: &str) -> String {
4154 let mut chars = name.chars();
4155 match chars.next() {
4156 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
4157 None => String::new(),
4158 }
4159}
4160
4161fn set_nested_value(map: &mut serde_json::Map<String, serde_json::Value>, path: &str, val: String) {
4164 set_nested_value_json(map, path, serde_json::Value::String(val));
4165}
4166
4167fn set_nested_value_json(
4169 map: &mut serde_json::Map<String, serde_json::Value>,
4170 path: &str,
4171 val: serde_json::Value,
4172) {
4173 if let Some((prefix, leaf)) = path.rsplit_once('.') {
4174 let mut current = map;
4175 for part in prefix.split('.') {
4176 let entry = current
4177 .entry(part.to_string())
4178 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
4179 current = entry.as_object_mut().expect("expected object in path");
4180 }
4181 current.insert(leaf.to_string(), val);
4182 } else {
4183 map.insert(path.to_string(), val);
4184 }
4185}
4186
4187#[derive(serde::Serialize, serde::Deserialize)]
4192pub struct VariantCache {
4193 pub message_defs: Vec<MappingDefinition>,
4195 pub transaction_defs: HashMap<String, Vec<MappingDefinition>>,
4197 pub combined_defs: HashMap<String, Vec<MappingDefinition>>,
4199 #[serde(default)]
4201 pub code_lookups: HashMap<String, crate::code_lookup::CodeLookup>,
4202 #[serde(default)]
4204 pub mig_schema: Option<mig_types::schema::mig::MigSchema>,
4205 #[serde(default)]
4207 pub segment_structure: Option<crate::segment_structure::SegmentStructure>,
4208 #[serde(default)]
4211 pub pid_segment_numbers: HashMap<String, Vec<String>>,
4212 #[serde(default)]
4215 pub pid_requirements: HashMap<String, crate::pid_requirements::PidRequirements>,
4216 #[serde(default)]
4220 pub tx_groups: HashMap<String, String>,
4221}
4222
4223impl VariantCache {
4224 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
4226 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
4227 path: path.display().to_string(),
4228 message: e.to_string(),
4229 })?;
4230 if let Some(parent) = path.parent() {
4231 std::fs::create_dir_all(parent)?;
4232 }
4233 std::fs::write(path, encoded)?;
4234 Ok(())
4235 }
4236
4237 pub fn load(path: &Path) -> Result<Self, MappingError> {
4239 let bytes = std::fs::read(path)?;
4240 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
4241 path: path.display().to_string(),
4242 message: e.to_string(),
4243 })
4244 }
4245
4246 pub fn tx_group(&self, pid: &str) -> Option<&str> {
4250 self.tx_groups
4251 .get(&format!("pid_{pid}"))
4252 .map(|s| s.as_str())
4253 }
4254
4255 pub fn msg_engine(&self, pid: &str) -> MappingEngine {
4259 let mut eng = MappingEngine::from_definitions(self.message_defs.clone()).with_pid(pid);
4260 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
4261 eng = eng.with_code_lookup(cl.clone());
4262 }
4263 eng
4264 }
4265
4266 pub fn tx_engine(&self, pid: &str) -> Option<MappingEngine> {
4270 self.transaction_defs
4271 .get(&format!("pid_{pid}"))
4272 .map(|defs| {
4273 let mut eng = MappingEngine::from_definitions(defs.clone()).with_pid(pid);
4274 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
4275 eng = eng.with_code_lookup(cl.clone());
4276 }
4277 eng
4278 })
4279 }
4280
4281 pub fn filtered_mig(&self, pid: &str) -> Option<mig_types::schema::mig::MigSchema> {
4289 let mig = self.mig_schema.as_ref()?;
4290 let numbers = self
4291 .pid_segment_numbers
4292 .get(&format!("pid_{pid}"))
4293 .or_else(|| self.pid_segment_numbers.get("pid_"))?;
4294 let number_set: std::collections::HashSet<String> = numbers.iter().cloned().collect();
4295 Some(mig_assembly::pid_filter::filter_mig_for_pid(
4296 mig,
4297 &number_set,
4298 ))
4299 }
4300}
4301
4302#[derive(serde::Serialize, serde::Deserialize)]
4307pub struct DataBundle {
4308 pub format_version: String,
4309 pub bundle_version: u32,
4310 pub variants: HashMap<String, VariantCache>,
4311}
4312
4313impl DataBundle {
4314 pub const CURRENT_VERSION: u32 = 2;
4315
4316 pub fn variant(&self, name: &str) -> Option<&VariantCache> {
4317 self.variants.get(name)
4318 }
4319
4320 pub fn write_to<W: std::io::Write>(&self, writer: &mut W) -> Result<(), MappingError> {
4321 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
4322 path: "<stream>".to_string(),
4323 message: e.to_string(),
4324 })?;
4325 writer.write_all(&encoded).map_err(MappingError::Io)
4326 }
4327
4328 pub fn read_from<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
4329 let mut bytes = Vec::new();
4330 reader.read_to_end(&mut bytes).map_err(MappingError::Io)?;
4331 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
4332 path: "<stream>".to_string(),
4333 message: e.to_string(),
4334 })
4335 }
4336
4337 pub fn read_from_checked<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
4338 let bundle = Self::read_from(reader)?;
4339 if bundle.bundle_version != Self::CURRENT_VERSION {
4340 return Err(MappingError::CacheRead {
4341 path: "<stream>".to_string(),
4342 message: format!(
4343 "Incompatible bundle version {}, expected version {}. \
4344 Run `edifact-data update` to fetch compatible bundles.",
4345 bundle.bundle_version,
4346 Self::CURRENT_VERSION
4347 ),
4348 });
4349 }
4350 Ok(bundle)
4351 }
4352
4353 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
4354 if let Some(parent) = path.parent() {
4355 std::fs::create_dir_all(parent)?;
4356 }
4357 let mut file = std::fs::File::create(path).map_err(MappingError::Io)?;
4358 self.write_to(&mut file)
4359 }
4360
4361 pub fn load(path: &Path) -> Result<Self, MappingError> {
4362 let mut file = std::fs::File::open(path).map_err(MappingError::Io)?;
4363 Self::read_from_checked(&mut file)
4364 }
4365}
4366
4367fn sort_variant_reps_by_mig(
4380 child_groups: &mut [AssembledGroup],
4381 mig: &MigSchema,
4382 transaction_group: &str,
4383) {
4384 let tx_def = match mig
4385 .segment_groups
4386 .iter()
4387 .find(|sg| sg.id == transaction_group)
4388 {
4389 Some(d) => d,
4390 None => return,
4391 };
4392
4393 for cg in child_groups.iter_mut() {
4394 if cg.repetitions.len() <= 1 {
4395 continue;
4396 }
4397
4398 let variant_defs: Vec<(usize, &mig_types::schema::mig::MigSegmentGroup)> = tx_def
4400 .nested_groups
4401 .iter()
4402 .enumerate()
4403 .filter(|(_, ng)| ng.id == cg.group_id && ng.variant_code.is_some())
4404 .collect();
4405
4406 if variant_defs.is_empty() {
4407 continue;
4408 }
4409
4410 cg.repetitions.sort_by_key(|rep| {
4413 let entry_seg = rep.segments.first();
4414 for &(mig_pos, variant_def) in &variant_defs {
4415 let (ei, ci) = variant_def.variant_qualifier_position.unwrap_or((0, 0));
4416 let actual_qual = entry_seg
4417 .and_then(|s| s.elements.get(ei))
4418 .and_then(|e| e.get(ci))
4419 .map(|s| s.as_str())
4420 .unwrap_or("");
4421 let matches = if !variant_def.variant_codes.is_empty() {
4422 variant_def
4423 .variant_codes
4424 .iter()
4425 .any(|c| actual_qual.eq_ignore_ascii_case(c))
4426 } else if let Some(ref expected_code) = variant_def.variant_code {
4427 actual_qual.eq_ignore_ascii_case(expected_code)
4428 } else {
4429 false
4430 };
4431 if matches {
4432 return mig_pos;
4433 }
4434 }
4435 usize::MAX });
4437 }
4438}
4439
4440#[cfg(test)]
4441mod variant_cache_helper_tests {
4442 use super::*;
4443
4444 fn make_test_cache() -> VariantCache {
4445 let mut tx_groups = HashMap::new();
4446 tx_groups.insert("pid_55001".to_string(), "SG4".to_string());
4447 tx_groups.insert("pid_21007".to_string(), "SG14".to_string());
4448
4449 let mut transaction_defs = HashMap::new();
4450 transaction_defs.insert("pid_55001".to_string(), vec![]);
4451 transaction_defs.insert("pid_21007".to_string(), vec![]);
4452
4453 VariantCache {
4454 message_defs: vec![],
4455 transaction_defs,
4456 combined_defs: HashMap::new(),
4457 code_lookups: HashMap::new(),
4458 mig_schema: None,
4459 segment_structure: None,
4460 pid_segment_numbers: HashMap::new(),
4461 pid_requirements: HashMap::new(),
4462 tx_groups,
4463 }
4464 }
4465
4466 #[test]
4467 fn test_tx_group_returns_correct_group() {
4468 let vc = make_test_cache();
4469 assert_eq!(vc.tx_group("55001").unwrap(), "SG4");
4470 assert_eq!(vc.tx_group("21007").unwrap(), "SG14");
4471 }
4472
4473 #[test]
4474 fn test_tx_group_unknown_pid_returns_none() {
4475 let vc = make_test_cache();
4476 assert!(vc.tx_group("99999").is_none());
4477 }
4478
4479 #[test]
4480 fn test_msg_engine_returns_engine() {
4481 let vc = make_test_cache();
4482 let engine = vc.msg_engine("55001");
4483 assert_eq!(engine.definitions().len(), 0);
4484 }
4485
4486 #[test]
4487 fn test_tx_engine_returns_engine_for_known_pid() {
4488 let vc = make_test_cache();
4489 assert!(vc.tx_engine("55001").is_some());
4490 }
4491
4492 #[test]
4493 fn test_tx_engine_returns_none_for_unknown_pid() {
4494 let vc = make_test_cache();
4495 assert!(vc.tx_engine("99999").is_none());
4496 }
4497}
4498
4499#[cfg(test)]
4500mod tests {
4501 use super::*;
4502 use crate::definition::{MappingDefinition, MappingMeta, StructuredFieldMapping};
4503 use indexmap::IndexMap;
4504
4505 fn make_def(fields: IndexMap<String, FieldMapping>) -> MappingDefinition {
4506 MappingDefinition {
4507 meta: MappingMeta {
4508 entity: "Test".to_string(),
4509 bo4e_type: "Test".to_string(),
4510 companion_type: None,
4511 source_group: "SG4".to_string(),
4512 source_path: None,
4513 discriminator: None,
4514 repeat_on_tag: None,
4515 },
4516 fields,
4517 companion_fields: None,
4518 complex_handlers: None,
4519 }
4520 }
4521
4522 #[test]
4523 fn test_map_interchange_single_transaction_backward_compat() {
4524 use mig_assembly::assembler::*;
4525
4526 let tree = AssembledTree {
4528 segments: vec![
4529 AssembledSegment {
4530 tag: "UNH".to_string(),
4531 elements: vec![vec!["001".to_string()]],
4532 mig_number: None,
4533 segment_number: None,
4534 },
4535 AssembledSegment {
4536 tag: "BGM".to_string(),
4537 elements: vec![vec!["E01".to_string()], vec!["DOC001".to_string()]],
4538 mig_number: None,
4539 segment_number: None,
4540 },
4541 ],
4542 groups: vec![
4543 AssembledGroup {
4544 group_id: "SG2".to_string(),
4545 repetitions: vec![AssembledGroupInstance {
4546 segments: vec![AssembledSegment {
4547 tag: "NAD".to_string(),
4548 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4549 mig_number: None,
4550 segment_number: None,
4551 }],
4552 child_groups: vec![],
4553 entry_mig_number: None,
4554 variant_mig_numbers: vec![],
4555 skipped_segments: vec![],
4556 skipped_positions: Vec::new(),
4557 }],
4558 },
4559 AssembledGroup {
4560 group_id: "SG4".to_string(),
4561 repetitions: vec![AssembledGroupInstance {
4562 segments: vec![AssembledSegment {
4563 tag: "IDE".to_string(),
4564 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4565 mig_number: None,
4566 segment_number: None,
4567 }],
4568 child_groups: vec![AssembledGroup {
4569 group_id: "SG5".to_string(),
4570 repetitions: vec![AssembledGroupInstance {
4571 segments: vec![AssembledSegment {
4572 tag: "LOC".to_string(),
4573 elements: vec![
4574 vec!["Z16".to_string()],
4575 vec!["DE000111222333".to_string()],
4576 ],
4577 mig_number: None,
4578 segment_number: None,
4579 }],
4580 child_groups: vec![],
4581 entry_mig_number: None,
4582 variant_mig_numbers: vec![],
4583 skipped_segments: vec![],
4584 skipped_positions: Vec::new(),
4585 }],
4586 }],
4587 entry_mig_number: None,
4588 variant_mig_numbers: vec![],
4589 skipped_segments: vec![],
4590 skipped_positions: Vec::new(),
4591 }],
4592 },
4593 ],
4594 post_group_start: 2,
4595 inter_group_segments: std::collections::BTreeMap::new(),
4596 };
4597
4598 let msg_engine = MappingEngine::from_definitions(vec![]);
4600
4601 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4603 tx_fields.insert(
4604 "ide.1".to_string(),
4605 FieldMapping::Simple("vorgangId".to_string()),
4606 );
4607 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4608 malo_fields.insert(
4609 "loc.1".to_string(),
4610 FieldMapping::Simple("marktlokationsId".to_string()),
4611 );
4612
4613 let tx_engine = MappingEngine::from_definitions(vec![
4614 MappingDefinition {
4615 meta: MappingMeta {
4616 entity: "Prozessdaten".to_string(),
4617 bo4e_type: "Prozessdaten".to_string(),
4618 companion_type: None,
4619 source_group: "SG4".to_string(),
4620 source_path: None,
4621 discriminator: None,
4622 repeat_on_tag: None,
4623 },
4624 fields: tx_fields,
4625 companion_fields: None,
4626 complex_handlers: None,
4627 },
4628 MappingDefinition {
4629 meta: MappingMeta {
4630 entity: "Marktlokation".to_string(),
4631 bo4e_type: "Marktlokation".to_string(),
4632 companion_type: None,
4633 source_group: "SG4.SG5".to_string(),
4634 source_path: None,
4635 discriminator: None,
4636 repeat_on_tag: None,
4637 },
4638 fields: malo_fields,
4639 companion_fields: None,
4640 complex_handlers: None,
4641 },
4642 ]);
4643
4644 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
4645
4646 assert_eq!(result.transaktionen.len(), 1);
4647 assert_eq!(
4648 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
4649 .as_str()
4650 .unwrap(),
4651 "TX001"
4652 );
4653 assert_eq!(
4656 result.transaktionen[0].stammdaten["marktlokation"]["marktlokationsId"]
4657 .as_str()
4658 .unwrap(),
4659 "DE000111222333"
4660 );
4661 }
4662
4663 #[test]
4664 fn test_map_reverse_pads_intermediate_empty_elements() {
4665 let mut fields = IndexMap::new();
4667 fields.insert(
4668 "nad.0".to_string(),
4669 FieldMapping::Structured(StructuredFieldMapping {
4670 target: String::new(),
4671 transform: None,
4672 when: None,
4673 default: Some("Z09".to_string()),
4674 enum_map: None,
4675 when_filled: None,
4676 also_target: None,
4677 also_enum_map: None,
4678 }),
4679 );
4680 fields.insert(
4681 "nad.3.0".to_string(),
4682 FieldMapping::Simple("name".to_string()),
4683 );
4684 fields.insert(
4685 "nad.3.1".to_string(),
4686 FieldMapping::Simple("vorname".to_string()),
4687 );
4688
4689 let def = make_def(fields);
4690 let engine = MappingEngine::from_definitions(vec![]);
4691
4692 let bo4e = serde_json::json!({
4693 "name": "Muster",
4694 "vorname": "Max"
4695 });
4696
4697 let instance = engine.map_reverse(&bo4e, &def);
4698 assert_eq!(instance.segments.len(), 1);
4699
4700 let nad = &instance.segments[0];
4701 assert_eq!(nad.tag, "NAD");
4702 assert_eq!(nad.elements.len(), 4);
4703 assert_eq!(nad.elements[0], vec!["Z09"]);
4704 assert_eq!(nad.elements[1], vec![""]);
4706 assert_eq!(nad.elements[2], vec![""]);
4707 assert_eq!(nad.elements[3][0], "Muster");
4708 assert_eq!(nad.elements[3][1], "Max");
4709 }
4710
4711 #[test]
4712 fn test_map_reverse_no_padding_when_contiguous() {
4713 let mut fields = IndexMap::new();
4715 fields.insert(
4716 "dtm.0.0".to_string(),
4717 FieldMapping::Structured(StructuredFieldMapping {
4718 target: String::new(),
4719 transform: None,
4720 when: None,
4721 default: Some("92".to_string()),
4722 enum_map: None,
4723 when_filled: None,
4724 also_target: None,
4725 also_enum_map: None,
4726 }),
4727 );
4728 fields.insert(
4729 "dtm.0.1".to_string(),
4730 FieldMapping::Simple("value".to_string()),
4731 );
4732 fields.insert(
4733 "dtm.0.2".to_string(),
4734 FieldMapping::Structured(StructuredFieldMapping {
4735 target: String::new(),
4736 transform: None,
4737 when: None,
4738 default: Some("303".to_string()),
4739 enum_map: None,
4740 when_filled: None,
4741 also_target: None,
4742 also_enum_map: None,
4743 }),
4744 );
4745
4746 let def = make_def(fields);
4747 let engine = MappingEngine::from_definitions(vec![]);
4748
4749 let bo4e = serde_json::json!({ "value": "20250531" });
4750
4751 let instance = engine.map_reverse(&bo4e, &def);
4752 let dtm = &instance.segments[0];
4753 assert_eq!(dtm.elements.len(), 1);
4755 assert_eq!(dtm.elements[0], vec!["92", "20250531", "303"]);
4756 }
4757
4758 #[test]
4759 fn test_map_message_level_extracts_sg2_only() {
4760 use mig_assembly::assembler::*;
4761
4762 let tree = AssembledTree {
4764 segments: vec![
4765 AssembledSegment {
4766 tag: "UNH".to_string(),
4767 elements: vec![vec!["001".to_string()]],
4768 mig_number: None,
4769 segment_number: None,
4770 },
4771 AssembledSegment {
4772 tag: "BGM".to_string(),
4773 elements: vec![vec!["E01".to_string()]],
4774 mig_number: None,
4775 segment_number: None,
4776 },
4777 ],
4778 groups: vec![
4779 AssembledGroup {
4780 group_id: "SG2".to_string(),
4781 repetitions: vec![AssembledGroupInstance {
4782 segments: vec![AssembledSegment {
4783 tag: "NAD".to_string(),
4784 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4785 mig_number: None,
4786 segment_number: None,
4787 }],
4788 child_groups: vec![],
4789 entry_mig_number: None,
4790 variant_mig_numbers: vec![],
4791 skipped_segments: vec![],
4792 skipped_positions: Vec::new(),
4793 }],
4794 },
4795 AssembledGroup {
4796 group_id: "SG4".to_string(),
4797 repetitions: vec![AssembledGroupInstance {
4798 segments: vec![AssembledSegment {
4799 tag: "IDE".to_string(),
4800 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4801 mig_number: None,
4802 segment_number: None,
4803 }],
4804 child_groups: vec![],
4805 entry_mig_number: None,
4806 variant_mig_numbers: vec![],
4807 skipped_segments: vec![],
4808 skipped_positions: Vec::new(),
4809 }],
4810 },
4811 ],
4812 post_group_start: 2,
4813 inter_group_segments: std::collections::BTreeMap::new(),
4814 };
4815
4816 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4818 msg_fields.insert(
4819 "nad.0".to_string(),
4820 FieldMapping::Simple("marktrolle".to_string()),
4821 );
4822 msg_fields.insert(
4823 "nad.1".to_string(),
4824 FieldMapping::Simple("rollencodenummer".to_string()),
4825 );
4826 let msg_def = MappingDefinition {
4827 meta: MappingMeta {
4828 entity: "Marktteilnehmer".to_string(),
4829 bo4e_type: "Marktteilnehmer".to_string(),
4830 companion_type: None,
4831 source_group: "SG2".to_string(),
4832 source_path: None,
4833 discriminator: None,
4834 repeat_on_tag: None,
4835 },
4836 fields: msg_fields,
4837 companion_fields: None,
4838 complex_handlers: None,
4839 };
4840
4841 let engine = MappingEngine::from_definitions(vec![msg_def.clone()]);
4842 let result = engine.map_all_forward(&tree);
4843
4844 assert!(result.get("marktteilnehmer").is_some());
4846 let mt = &result["marktteilnehmer"];
4847 assert_eq!(mt["marktrolle"].as_str().unwrap(), "MS");
4848 assert_eq!(mt["rollencodenummer"].as_str().unwrap(), "9900123");
4849 }
4850
4851 #[test]
4852 fn test_map_transaction_scoped_to_sg4_instance() {
4853 use mig_assembly::assembler::*;
4854
4855 let tree = AssembledTree {
4857 segments: vec![
4858 AssembledSegment {
4859 tag: "UNH".to_string(),
4860 elements: vec![vec!["001".to_string()]],
4861 mig_number: None,
4862 segment_number: None,
4863 },
4864 AssembledSegment {
4865 tag: "BGM".to_string(),
4866 elements: vec![vec!["E01".to_string()]],
4867 mig_number: None,
4868 segment_number: None,
4869 },
4870 ],
4871 groups: vec![AssembledGroup {
4872 group_id: "SG4".to_string(),
4873 repetitions: vec![AssembledGroupInstance {
4874 segments: vec![AssembledSegment {
4875 tag: "IDE".to_string(),
4876 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4877 mig_number: None,
4878 segment_number: None,
4879 }],
4880 child_groups: vec![AssembledGroup {
4881 group_id: "SG5".to_string(),
4882 repetitions: vec![AssembledGroupInstance {
4883 segments: vec![AssembledSegment {
4884 tag: "LOC".to_string(),
4885 elements: vec![
4886 vec!["Z16".to_string()],
4887 vec!["DE000111222333".to_string()],
4888 ],
4889 mig_number: None,
4890 segment_number: None,
4891 }],
4892 child_groups: vec![],
4893 entry_mig_number: None,
4894 variant_mig_numbers: vec![],
4895 skipped_segments: vec![],
4896 skipped_positions: Vec::new(),
4897 }],
4898 }],
4899 entry_mig_number: None,
4900 variant_mig_numbers: vec![],
4901 skipped_segments: vec![],
4902 skipped_positions: Vec::new(),
4903 }],
4904 }],
4905 post_group_start: 2,
4906 inter_group_segments: std::collections::BTreeMap::new(),
4907 };
4908
4909 let mut proz_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4911 proz_fields.insert(
4912 "ide.1".to_string(),
4913 FieldMapping::Simple("vorgangId".to_string()),
4914 );
4915 let proz_def = MappingDefinition {
4916 meta: MappingMeta {
4917 entity: "Prozessdaten".to_string(),
4918 bo4e_type: "Prozessdaten".to_string(),
4919 companion_type: None,
4920 source_group: "".to_string(), source_path: None,
4922 discriminator: None,
4923 repeat_on_tag: None,
4924 },
4925 fields: proz_fields,
4926 companion_fields: None,
4927 complex_handlers: None,
4928 };
4929
4930 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4931 malo_fields.insert(
4932 "loc.1".to_string(),
4933 FieldMapping::Simple("marktlokationsId".to_string()),
4934 );
4935 let malo_def = MappingDefinition {
4936 meta: MappingMeta {
4937 entity: "Marktlokation".to_string(),
4938 bo4e_type: "Marktlokation".to_string(),
4939 companion_type: None,
4940 source_group: "SG5".to_string(), source_path: None,
4942 discriminator: None,
4943 repeat_on_tag: None,
4944 },
4945 fields: malo_fields,
4946 companion_fields: None,
4947 complex_handlers: None,
4948 };
4949
4950 let tx_engine = MappingEngine::from_definitions(vec![proz_def, malo_def]);
4951
4952 let sg4 = &tree.groups[0]; let sg4_instance = &sg4.repetitions[0];
4955 let sub_tree = sg4_instance.as_assembled_tree();
4956
4957 let result = tx_engine.map_all_forward(&sub_tree);
4958
4959 assert_eq!(
4961 result["prozessdaten"]["vorgangId"].as_str().unwrap(),
4962 "TX001"
4963 );
4964
4965 assert_eq!(
4967 result["marktlokation"]["marktlokationsId"]
4968 .as_str()
4969 .unwrap(),
4970 "DE000111222333"
4971 );
4972 }
4973
4974 #[test]
4975 fn test_map_interchange_produces_full_hierarchy() {
4976 use mig_assembly::assembler::*;
4977
4978 let tree = AssembledTree {
4980 segments: vec![
4981 AssembledSegment {
4982 tag: "UNH".to_string(),
4983 elements: vec![vec!["001".to_string()]],
4984 mig_number: None,
4985 segment_number: None,
4986 },
4987 AssembledSegment {
4988 tag: "BGM".to_string(),
4989 elements: vec![vec!["E01".to_string()]],
4990 mig_number: None,
4991 segment_number: None,
4992 },
4993 ],
4994 groups: vec![
4995 AssembledGroup {
4996 group_id: "SG2".to_string(),
4997 repetitions: vec![AssembledGroupInstance {
4998 segments: vec![AssembledSegment {
4999 tag: "NAD".to_string(),
5000 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
5001 mig_number: None,
5002 segment_number: None,
5003 }],
5004 child_groups: vec![],
5005 entry_mig_number: None,
5006 variant_mig_numbers: vec![],
5007 skipped_segments: vec![],
5008 skipped_positions: Vec::new(),
5009 }],
5010 },
5011 AssembledGroup {
5012 group_id: "SG4".to_string(),
5013 repetitions: vec![
5014 AssembledGroupInstance {
5015 segments: vec![AssembledSegment {
5016 tag: "IDE".to_string(),
5017 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5018 mig_number: None,
5019 segment_number: None,
5020 }],
5021 child_groups: vec![],
5022 entry_mig_number: None,
5023 variant_mig_numbers: vec![],
5024 skipped_segments: vec![],
5025 skipped_positions: Vec::new(),
5026 },
5027 AssembledGroupInstance {
5028 segments: vec![AssembledSegment {
5029 tag: "IDE".to_string(),
5030 elements: vec![vec!["24".to_string()], vec!["TX002".to_string()]],
5031 mig_number: None,
5032 segment_number: None,
5033 }],
5034 child_groups: vec![],
5035 entry_mig_number: None,
5036 variant_mig_numbers: vec![],
5037 skipped_segments: vec![],
5038 skipped_positions: Vec::new(),
5039 },
5040 ],
5041 },
5042 ],
5043 post_group_start: 2,
5044 inter_group_segments: std::collections::BTreeMap::new(),
5045 };
5046
5047 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5049 msg_fields.insert(
5050 "nad.0".to_string(),
5051 FieldMapping::Simple("marktrolle".to_string()),
5052 );
5053 let msg_defs = vec![MappingDefinition {
5054 meta: MappingMeta {
5055 entity: "Marktteilnehmer".to_string(),
5056 bo4e_type: "Marktteilnehmer".to_string(),
5057 companion_type: None,
5058 source_group: "SG2".to_string(),
5059 source_path: None,
5060 discriminator: None,
5061 repeat_on_tag: None,
5062 },
5063 fields: msg_fields,
5064 companion_fields: None,
5065 complex_handlers: None,
5066 }];
5067
5068 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5070 tx_fields.insert(
5071 "ide.1".to_string(),
5072 FieldMapping::Simple("vorgangId".to_string()),
5073 );
5074 let tx_defs = vec![MappingDefinition {
5075 meta: MappingMeta {
5076 entity: "Prozessdaten".to_string(),
5077 bo4e_type: "Prozessdaten".to_string(),
5078 companion_type: None,
5079 source_group: "SG4".to_string(),
5080 source_path: None,
5081 discriminator: None,
5082 repeat_on_tag: None,
5083 },
5084 fields: tx_fields,
5085 companion_fields: None,
5086 complex_handlers: None,
5087 }];
5088
5089 let msg_engine = MappingEngine::from_definitions(msg_defs);
5090 let tx_engine = MappingEngine::from_definitions(tx_defs);
5091
5092 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
5093
5094 assert!(result.stammdaten["marktteilnehmer"].is_object());
5096 assert_eq!(
5097 result.stammdaten["marktteilnehmer"]["marktrolle"]
5098 .as_str()
5099 .unwrap(),
5100 "MS"
5101 );
5102
5103 assert_eq!(result.transaktionen.len(), 2);
5105 assert_eq!(
5106 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
5107 .as_str()
5108 .unwrap(),
5109 "TX001"
5110 );
5111 assert_eq!(
5112 result.transaktionen[1].stammdaten["prozessdaten"]["vorgangId"]
5113 .as_str()
5114 .unwrap(),
5115 "TX002"
5116 );
5117 }
5118
5119 #[test]
5120 fn test_map_reverse_with_segment_structure_pads_trailing() {
5121 let mut fields = IndexMap::new();
5123 fields.insert(
5124 "sts.0".to_string(),
5125 FieldMapping::Structured(StructuredFieldMapping {
5126 target: String::new(),
5127 transform: None,
5128 when: None,
5129 default: Some("7".to_string()),
5130 enum_map: None,
5131 when_filled: None,
5132 also_target: None,
5133 also_enum_map: None,
5134 }),
5135 );
5136 fields.insert(
5137 "sts.2".to_string(),
5138 FieldMapping::Simple("grund".to_string()),
5139 );
5140
5141 let def = make_def(fields);
5142
5143 let mut counts = std::collections::HashMap::new();
5145 counts.insert("STS".to_string(), 5usize);
5146 let ss = SegmentStructure {
5147 element_counts: counts,
5148 };
5149
5150 let engine = MappingEngine::from_definitions(vec![]).with_segment_structure(ss);
5151
5152 let bo4e = serde_json::json!({ "grund": "E01" });
5153
5154 let instance = engine.map_reverse(&bo4e, &def);
5155 let sts = &instance.segments[0];
5156 assert_eq!(sts.elements.len(), 5);
5159 assert_eq!(sts.elements[0], vec!["7"]);
5160 assert_eq!(sts.elements[1], vec![""]);
5161 assert_eq!(sts.elements[2], vec!["E01"]);
5162 assert_eq!(sts.elements[3], vec![""]);
5163 assert_eq!(sts.elements[4], vec![""]);
5164 }
5165
5166 #[test]
5167 fn test_extract_companion_fields_with_code_enrichment() {
5168 use crate::code_lookup::CodeLookup;
5169 use mig_assembly::assembler::*;
5170
5171 let schema = serde_json::json!({
5172 "fields": {
5173 "sg4": {
5174 "children": {
5175 "sg8_z01": {
5176 "children": {
5177 "sg10": {
5178 "segments": [{
5179 "id": "CCI",
5180 "elements": [{
5181 "index": 2,
5182 "components": [{
5183 "sub_index": 0,
5184 "type": "code",
5185 "codes": [
5186 {"value": "Z15", "name": "Haushaltskunde"},
5187 {"value": "Z18", "name": "Kein Haushaltskunde"}
5188 ]
5189 }]
5190 }]
5191 }],
5192 "source_group": "SG10"
5193 }
5194 },
5195 "segments": [],
5196 "source_group": "SG8"
5197 }
5198 },
5199 "segments": [],
5200 "source_group": "SG4"
5201 }
5202 }
5203 });
5204
5205 let code_lookup = CodeLookup::from_schema_value(&schema);
5206
5207 let tree = AssembledTree {
5208 segments: vec![],
5209 groups: vec![AssembledGroup {
5210 group_id: "SG4".to_string(),
5211 repetitions: vec![AssembledGroupInstance {
5212 segments: vec![],
5213 child_groups: vec![AssembledGroup {
5214 group_id: "SG8".to_string(),
5215 repetitions: vec![AssembledGroupInstance {
5216 segments: vec![],
5217 child_groups: vec![AssembledGroup {
5218 group_id: "SG10".to_string(),
5219 repetitions: vec![AssembledGroupInstance {
5220 segments: vec![AssembledSegment {
5221 tag: "CCI".to_string(),
5222 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
5223 mig_number: None,
5224 segment_number: None,
5225 }],
5226 child_groups: vec![],
5227 entry_mig_number: None,
5228 variant_mig_numbers: vec![],
5229 skipped_segments: vec![],
5230 skipped_positions: Vec::new(),
5231 }],
5232 }],
5233 entry_mig_number: None,
5234 variant_mig_numbers: vec![],
5235 skipped_segments: vec![],
5236 skipped_positions: Vec::new(),
5237 }],
5238 }],
5239 entry_mig_number: None,
5240 variant_mig_numbers: vec![],
5241 skipped_segments: vec![],
5242 skipped_positions: Vec::new(),
5243 }],
5244 }],
5245 post_group_start: 0,
5246 inter_group_segments: std::collections::BTreeMap::new(),
5247 };
5248
5249 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5250 companion_fields.insert(
5251 "cci.2".to_string(),
5252 FieldMapping::Simple("haushaltskunde".to_string()),
5253 );
5254
5255 let def = MappingDefinition {
5256 meta: MappingMeta {
5257 entity: "Marktlokation".to_string(),
5258 bo4e_type: "Marktlokation".to_string(),
5259 companion_type: Some("MarktlokationEdifact".to_string()),
5260 source_group: "SG4.SG8.SG10".to_string(),
5261 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5262 discriminator: None,
5263 repeat_on_tag: None,
5264 },
5265 fields: IndexMap::new(),
5266 companion_fields: Some(companion_fields),
5267 complex_handlers: None,
5268 };
5269
5270 let engine_plain = MappingEngine::from_definitions(vec![]);
5272 let bo4e_plain = engine_plain.map_forward(&tree, &def, 0);
5273 assert_eq!(
5274 bo4e_plain["marktlokationEdifact"]["haushaltskunde"].as_str(),
5275 Some("Z15"),
5276 "Without code lookup, should be plain string"
5277 );
5278
5279 let engine_enriched = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
5281 let bo4e_enriched = engine_enriched.map_forward(&tree, &def, 0);
5282 let hk = &bo4e_enriched["marktlokationEdifact"]["haushaltskunde"];
5283 assert_eq!(hk["code"].as_str(), Some("Z15"));
5284 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
5285 assert!(hk.get("enum").is_none());
5287 }
5288
5289 #[test]
5290 fn test_extract_companion_fields_with_enum_enrichment() {
5291 use crate::code_lookup::CodeLookup;
5292 use mig_assembly::assembler::*;
5293
5294 let schema = serde_json::json!({
5296 "fields": {
5297 "sg4": {
5298 "children": {
5299 "sg8_z01": {
5300 "children": {
5301 "sg10": {
5302 "segments": [{
5303 "id": "CCI",
5304 "elements": [{
5305 "index": 2,
5306 "components": [{
5307 "sub_index": 0,
5308 "type": "code",
5309 "codes": [
5310 {"value": "Z15", "name": "Haushaltskunde", "enum": "HAUSHALTSKUNDE"},
5311 {"value": "Z18", "name": "Kein Haushaltskunde", "enum": "KEIN_HAUSHALTSKUNDE"}
5312 ]
5313 }]
5314 }]
5315 }],
5316 "source_group": "SG10"
5317 }
5318 },
5319 "segments": [],
5320 "source_group": "SG8"
5321 }
5322 },
5323 "segments": [],
5324 "source_group": "SG4"
5325 }
5326 }
5327 });
5328
5329 let code_lookup = CodeLookup::from_schema_value(&schema);
5330
5331 let tree = AssembledTree {
5332 segments: vec![],
5333 groups: vec![AssembledGroup {
5334 group_id: "SG4".to_string(),
5335 repetitions: vec![AssembledGroupInstance {
5336 segments: vec![],
5337 child_groups: vec![AssembledGroup {
5338 group_id: "SG8".to_string(),
5339 repetitions: vec![AssembledGroupInstance {
5340 segments: vec![],
5341 child_groups: vec![AssembledGroup {
5342 group_id: "SG10".to_string(),
5343 repetitions: vec![AssembledGroupInstance {
5344 segments: vec![AssembledSegment {
5345 tag: "CCI".to_string(),
5346 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
5347 mig_number: None,
5348 segment_number: None,
5349 }],
5350 child_groups: vec![],
5351 entry_mig_number: None,
5352 variant_mig_numbers: vec![],
5353 skipped_segments: vec![],
5354 skipped_positions: Vec::new(),
5355 }],
5356 }],
5357 entry_mig_number: None,
5358 variant_mig_numbers: vec![],
5359 skipped_segments: vec![],
5360 skipped_positions: Vec::new(),
5361 }],
5362 }],
5363 entry_mig_number: None,
5364 variant_mig_numbers: vec![],
5365 skipped_segments: vec![],
5366 skipped_positions: Vec::new(),
5367 }],
5368 }],
5369 post_group_start: 0,
5370 inter_group_segments: std::collections::BTreeMap::new(),
5371 };
5372
5373 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5374 companion_fields.insert(
5375 "cci.2".to_string(),
5376 FieldMapping::Simple("haushaltskunde".to_string()),
5377 );
5378
5379 let def = MappingDefinition {
5380 meta: MappingMeta {
5381 entity: "Marktlokation".to_string(),
5382 bo4e_type: "Marktlokation".to_string(),
5383 companion_type: Some("MarktlokationEdifact".to_string()),
5384 source_group: "SG4.SG8.SG10".to_string(),
5385 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5386 discriminator: None,
5387 repeat_on_tag: None,
5388 },
5389 fields: IndexMap::new(),
5390 companion_fields: Some(companion_fields),
5391 complex_handlers: None,
5392 };
5393
5394 let engine = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
5395 let bo4e = engine.map_forward(&tree, &def, 0);
5396 let hk = &bo4e["marktlokationEdifact"]["haushaltskunde"];
5397 assert_eq!(hk["code"].as_str(), Some("Z15"));
5398 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
5399 assert_eq!(
5400 hk["enum"].as_str(),
5401 Some("HAUSHALTSKUNDE"),
5402 "enum field should be present"
5403 );
5404 }
5405
5406 #[test]
5407 fn test_reverse_mapping_accepts_enriched_with_enum() {
5408 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5410 companion_fields.insert(
5411 "cci.2".to_string(),
5412 FieldMapping::Simple("haushaltskunde".to_string()),
5413 );
5414
5415 let def = MappingDefinition {
5416 meta: MappingMeta {
5417 entity: "Test".to_string(),
5418 bo4e_type: "Test".to_string(),
5419 companion_type: Some("TestEdifact".to_string()),
5420 source_group: "SG4".to_string(),
5421 source_path: None,
5422 discriminator: None,
5423 repeat_on_tag: None,
5424 },
5425 fields: IndexMap::new(),
5426 companion_fields: Some(companion_fields),
5427 complex_handlers: None,
5428 };
5429
5430 let engine = MappingEngine::from_definitions(vec![]);
5431
5432 let bo4e = serde_json::json!({
5433 "testEdifact": {
5434 "haushaltskunde": {
5435 "code": "Z15",
5436 "meaning": "Haushaltskunde",
5437 "enum": "HAUSHALTSKUNDE"
5438 }
5439 }
5440 });
5441 let instance = engine.map_reverse(&bo4e, &def);
5442 assert_eq!(instance.segments[0].elements[2], vec!["Z15"]);
5443 }
5444
5445 #[test]
5446 fn test_reverse_mapping_accepts_enriched_companion() {
5447 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5449 companion_fields.insert(
5450 "cci.2".to_string(),
5451 FieldMapping::Simple("haushaltskunde".to_string()),
5452 );
5453
5454 let def = MappingDefinition {
5455 meta: MappingMeta {
5456 entity: "Test".to_string(),
5457 bo4e_type: "Test".to_string(),
5458 companion_type: Some("TestEdifact".to_string()),
5459 source_group: "SG4".to_string(),
5460 source_path: None,
5461 discriminator: None,
5462 repeat_on_tag: None,
5463 },
5464 fields: IndexMap::new(),
5465 companion_fields: Some(companion_fields),
5466 complex_handlers: None,
5467 };
5468
5469 let engine = MappingEngine::from_definitions(vec![]);
5470
5471 let bo4e_plain = serde_json::json!({
5473 "testEdifact": {
5474 "haushaltskunde": "Z15"
5475 }
5476 });
5477 let instance_plain = engine.map_reverse(&bo4e_plain, &def);
5478 assert_eq!(instance_plain.segments[0].elements[2], vec!["Z15"]);
5479
5480 let bo4e_enriched = serde_json::json!({
5482 "testEdifact": {
5483 "haushaltskunde": {
5484 "code": "Z15",
5485 "meaning": "Haushaltskunde gem. EnWG"
5486 }
5487 }
5488 });
5489 let instance_enriched = engine.map_reverse(&bo4e_enriched, &def);
5490 assert_eq!(instance_enriched.segments[0].elements[2], vec!["Z15"]);
5491 }
5492
5493 #[test]
5494 fn test_resolve_child_relative_with_source_path() {
5495 let mut map: std::collections::HashMap<String, Vec<usize>> =
5496 std::collections::HashMap::new();
5497 map.insert("sg4.sg8_ze1".to_string(), vec![6]);
5498 map.insert("sg4.sg8_z98".to_string(), vec![0]);
5499
5500 assert_eq!(
5502 resolve_child_relative("SG8.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5503 "SG8:6.SG10"
5504 );
5505
5506 assert_eq!(
5508 resolve_child_relative("SG8:3.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5509 "SG8:3.SG10"
5510 );
5511
5512 assert_eq!(
5514 resolve_child_relative("SG8.SG10", Some("sg4.sg8_unknown.sg10"), &map, 0),
5515 "SG8.SG10"
5516 );
5517
5518 assert_eq!(
5520 resolve_child_relative("SG8.SG10", None, &map, 0),
5521 "SG8.SG10"
5522 );
5523
5524 assert_eq!(
5526 resolve_child_relative("SG8.SG9", Some("sg4.sg8_z98.sg9"), &map, 0),
5527 "SG8:0.SG9"
5528 );
5529
5530 map.insert("sg4.sg8_zf3".to_string(), vec![3, 4]);
5532 assert_eq!(
5533 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 0),
5534 "SG8:3.SG10"
5535 );
5536 assert_eq!(
5537 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 1),
5538 "SG8:4.SG10"
5539 );
5540 }
5541
5542 #[test]
5543 fn test_place_in_groups_returns_rep_index() {
5544 let mut groups: Vec<AssembledGroup> = Vec::new();
5545
5546 let instance = AssembledGroupInstance {
5548 segments: vec![],
5549 child_groups: vec![],
5550 entry_mig_number: None,
5551 variant_mig_numbers: vec![],
5552 skipped_segments: vec![],
5553 skipped_positions: Vec::new(),
5554 };
5555 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 0);
5556
5557 let instance = AssembledGroupInstance {
5559 segments: vec![],
5560 child_groups: vec![],
5561 entry_mig_number: None,
5562 variant_mig_numbers: vec![],
5563 skipped_segments: vec![],
5564 skipped_positions: Vec::new(),
5565 };
5566 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 1);
5567
5568 let instance = AssembledGroupInstance {
5570 segments: vec![],
5571 child_groups: vec![],
5572 entry_mig_number: None,
5573 variant_mig_numbers: vec![],
5574 skipped_segments: vec![],
5575 skipped_positions: Vec::new(),
5576 };
5577 assert_eq!(place_in_groups(&mut groups, "SG8:5", instance), 5);
5578 }
5579
5580 #[test]
5581 fn test_resolve_by_source_path() {
5582 use mig_assembly::assembler::*;
5583
5584 let tree = AssembledTree {
5586 segments: vec![],
5587 groups: vec![AssembledGroup {
5588 group_id: "SG4".to_string(),
5589 repetitions: vec![AssembledGroupInstance {
5590 segments: vec![],
5591 child_groups: vec![AssembledGroup {
5592 group_id: "SG8".to_string(),
5593 repetitions: vec![
5594 AssembledGroupInstance {
5595 segments: vec![AssembledSegment {
5596 tag: "SEQ".to_string(),
5597 elements: vec![vec!["Z98".to_string()]],
5598 mig_number: None,
5599 segment_number: None,
5600 }],
5601 child_groups: vec![AssembledGroup {
5602 group_id: "SG10".to_string(),
5603 repetitions: vec![AssembledGroupInstance {
5604 segments: vec![AssembledSegment {
5605 tag: "CCI".to_string(),
5606 elements: vec![vec![], vec![], vec!["ZB3".to_string()]],
5607 mig_number: None,
5608 segment_number: None,
5609 }],
5610 child_groups: vec![],
5611 entry_mig_number: None,
5612 variant_mig_numbers: vec![],
5613 skipped_segments: vec![],
5614 skipped_positions: Vec::new(),
5615 }],
5616 }],
5617 entry_mig_number: None,
5618 variant_mig_numbers: vec![],
5619 skipped_segments: vec![],
5620 skipped_positions: Vec::new(),
5621 },
5622 AssembledGroupInstance {
5623 segments: vec![AssembledSegment {
5624 tag: "SEQ".to_string(),
5625 elements: vec![vec!["ZD7".to_string()]],
5626 mig_number: None,
5627 segment_number: None,
5628 }],
5629 child_groups: vec![AssembledGroup {
5630 group_id: "SG10".to_string(),
5631 repetitions: vec![AssembledGroupInstance {
5632 segments: vec![AssembledSegment {
5633 tag: "CCI".to_string(),
5634 elements: vec![vec![], vec![], vec!["ZE6".to_string()]],
5635 mig_number: None,
5636 segment_number: None,
5637 }],
5638 child_groups: vec![],
5639 entry_mig_number: None,
5640 variant_mig_numbers: vec![],
5641 skipped_segments: vec![],
5642 skipped_positions: Vec::new(),
5643 }],
5644 }],
5645 entry_mig_number: None,
5646 variant_mig_numbers: vec![],
5647 skipped_segments: vec![],
5648 skipped_positions: Vec::new(),
5649 },
5650 ],
5651 }],
5652 entry_mig_number: None,
5653 variant_mig_numbers: vec![],
5654 skipped_segments: vec![],
5655 skipped_positions: Vec::new(),
5656 }],
5657 }],
5658 post_group_start: 0,
5659 inter_group_segments: std::collections::BTreeMap::new(),
5660 };
5661
5662 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_z98.sg10");
5664 assert!(inst.is_some());
5665 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
5666
5667 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zd7.sg10");
5669 assert!(inst.is_some());
5670 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZE6");
5671
5672 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zzz.sg10");
5674 assert!(inst.is_none());
5675
5676 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8.sg10");
5678 assert!(inst.is_some());
5679 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
5680 }
5681
5682 #[test]
5683 fn test_parse_source_path_part() {
5684 assert_eq!(parse_source_path_part("sg4"), ("sg4", None));
5685 assert_eq!(parse_source_path_part("sg8_z98"), ("sg8", Some("z98")));
5686 assert_eq!(parse_source_path_part("sg10"), ("sg10", None));
5687 assert_eq!(parse_source_path_part("sg12_z04"), ("sg12", Some("z04")));
5688 }
5689
5690 #[test]
5691 fn test_has_source_path_qualifiers() {
5692 assert!(has_source_path_qualifiers("sg4.sg8_z98.sg10"));
5693 assert!(has_source_path_qualifiers("sg4.sg8_ze1.sg9"));
5694 assert!(!has_source_path_qualifiers("sg4.sg6"));
5695 assert!(!has_source_path_qualifiers("sg4.sg8.sg10"));
5696 }
5697
5698 #[test]
5699 fn test_companion_dotted_path_forward() {
5700 use mig_assembly::assembler::*;
5701
5702 let tree = AssembledTree {
5704 segments: vec![],
5705 groups: vec![AssembledGroup {
5706 group_id: "SG4".to_string(),
5707 repetitions: vec![AssembledGroupInstance {
5708 segments: vec![],
5709 child_groups: vec![AssembledGroup {
5710 group_id: "SG8".to_string(),
5711 repetitions: vec![AssembledGroupInstance {
5712 segments: vec![],
5713 child_groups: vec![AssembledGroup {
5714 group_id: "SG10".to_string(),
5715 repetitions: vec![AssembledGroupInstance {
5716 segments: vec![AssembledSegment {
5717 tag: "CCI".to_string(),
5718 elements: vec![
5719 vec!["11XAB-1234".to_string()],
5720 vec!["305".to_string()],
5721 ],
5722 mig_number: None,
5723 segment_number: None,
5724 }],
5725 child_groups: vec![],
5726 entry_mig_number: None,
5727 variant_mig_numbers: vec![],
5728 skipped_segments: vec![],
5729 skipped_positions: Vec::new(),
5730 }],
5731 }],
5732 entry_mig_number: None,
5733 variant_mig_numbers: vec![],
5734 skipped_segments: vec![],
5735 skipped_positions: Vec::new(),
5736 }],
5737 }],
5738 entry_mig_number: None,
5739 variant_mig_numbers: vec![],
5740 skipped_segments: vec![],
5741 skipped_positions: Vec::new(),
5742 }],
5743 }],
5744 post_group_start: 0,
5745 inter_group_segments: std::collections::BTreeMap::new(),
5746 };
5747
5748 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5750 companion_fields.insert(
5751 "cci.0".to_string(),
5752 FieldMapping::Simple("bilanzkreis.id".to_string()),
5753 );
5754 companion_fields.insert(
5755 "cci.1".to_string(),
5756 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
5757 );
5758
5759 let def = MappingDefinition {
5760 meta: MappingMeta {
5761 entity: "Test".to_string(),
5762 bo4e_type: "Test".to_string(),
5763 companion_type: Some("TestEdifact".to_string()),
5764 source_group: "SG4.SG8.SG10".to_string(),
5765 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5766 discriminator: None,
5767 repeat_on_tag: None,
5768 },
5769 fields: IndexMap::new(),
5770 companion_fields: Some(companion_fields),
5771 complex_handlers: None,
5772 };
5773
5774 let engine = MappingEngine::from_definitions(vec![]);
5775 let bo4e = engine.map_forward(&tree, &def, 0);
5776
5777 let companion = &bo4e["testEdifact"];
5779 assert!(
5780 companion.is_object(),
5781 "testEdifact should be an object, got: {companion}"
5782 );
5783 let bilanzkreis = &companion["bilanzkreis"];
5784 assert!(
5785 bilanzkreis.is_object(),
5786 "bilanzkreis should be a nested object, got: {bilanzkreis}"
5787 );
5788 assert_eq!(
5789 bilanzkreis["id"].as_str(),
5790 Some("11XAB-1234"),
5791 "bilanzkreis.id should be 11XAB-1234"
5792 );
5793 assert_eq!(
5794 bilanzkreis["codelist"].as_str(),
5795 Some("305"),
5796 "bilanzkreis.codelist should be 305"
5797 );
5798 }
5799
5800 #[test]
5801 fn test_companion_dotted_path_reverse() {
5802 let engine = MappingEngine::from_definitions(vec![]);
5804
5805 let companion_value = serde_json::json!({
5806 "bilanzkreis": {
5807 "id": "11XAB-1234",
5808 "codelist": "305"
5809 }
5810 });
5811
5812 assert_eq!(
5813 engine.populate_field(&companion_value, "bilanzkreis.id"),
5814 Some("11XAB-1234".to_string()),
5815 "dotted path bilanzkreis.id should resolve"
5816 );
5817 assert_eq!(
5818 engine.populate_field(&companion_value, "bilanzkreis.codelist"),
5819 Some("305".to_string()),
5820 "dotted path bilanzkreis.codelist should resolve"
5821 );
5822
5823 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5825 companion_fields.insert(
5826 "cci.0".to_string(),
5827 FieldMapping::Simple("bilanzkreis.id".to_string()),
5828 );
5829 companion_fields.insert(
5830 "cci.1".to_string(),
5831 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
5832 );
5833
5834 let def = MappingDefinition {
5835 meta: MappingMeta {
5836 entity: "Test".to_string(),
5837 bo4e_type: "Test".to_string(),
5838 companion_type: Some("TestEdifact".to_string()),
5839 source_group: "SG4.SG8.SG10".to_string(),
5840 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5841 discriminator: None,
5842 repeat_on_tag: None,
5843 },
5844 fields: IndexMap::new(),
5845 companion_fields: Some(companion_fields),
5846 complex_handlers: None,
5847 };
5848
5849 let bo4e = serde_json::json!({
5850 "testEdifact": {
5851 "bilanzkreis": {
5852 "id": "11XAB-1234",
5853 "codelist": "305"
5854 }
5855 }
5856 });
5857
5858 let instance = engine.map_reverse(&bo4e, &def);
5859 assert_eq!(instance.segments.len(), 1, "should produce one CCI segment");
5860 let cci = &instance.segments[0];
5861 assert_eq!(cci.tag, "CCI");
5862 assert_eq!(
5863 cci.elements[0],
5864 vec!["11XAB-1234"],
5865 "element 0 should contain bilanzkreis.id"
5866 );
5867 assert_eq!(
5868 cci.elements[1],
5869 vec!["305"],
5870 "element 1 should contain bilanzkreis.codelist"
5871 );
5872 }
5873
5874 #[test]
5875 fn test_when_filled_injects_when_field_present() {
5876 let toml_str = r#"
5877[meta]
5878entity = "Test"
5879bo4e_type = "Test"
5880companion_type = "TestEdifact"
5881source_group = "SG4.SG8.SG10"
5882
5883[fields]
5884
5885[companion_fields]
5886"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmalCode"] }
5887"cav.0.0" = "merkmalCode"
5888"#;
5889 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5890
5891 let bo4e_with = serde_json::json!({
5893 "testEdifact": { "merkmalCode": "ZA7" }
5894 });
5895 let engine = MappingEngine::new_empty();
5896 let instance = engine.map_reverse(&bo4e_with, &def);
5897 let cci = instance
5898 .segments
5899 .iter()
5900 .find(|s| s.tag == "CCI")
5901 .expect("CCI should exist");
5902 assert_eq!(cci.elements[0][0], "Z83");
5903
5904 let bo4e_without = serde_json::json!({
5906 "testEdifact": {}
5907 });
5908 let instance2 = engine.map_reverse(&bo4e_without, &def);
5909 let cci2 = instance2.segments.iter().find(|s| s.tag == "CCI");
5910 assert!(
5911 cci2.is_none(),
5912 "CCI should not be emitted when merkmalCode is absent"
5913 );
5914 }
5915
5916 #[test]
5917 fn test_when_filled_checks_core_and_companion() {
5918 let toml_str = r#"
5919[meta]
5920entity = "Test"
5921bo4e_type = "Test"
5922companion_type = "TestEdifact"
5923source_group = "SG4.SG5"
5924
5925[fields]
5926"loc.1.0" = "marktlokationsId"
5927
5928[companion_fields]
5929"loc.0.0" = { target = "", default = "Z16", when_filled = ["marktlokationsId"] }
5930"#;
5931 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5932
5933 let bo4e_with = serde_json::json!({
5935 "marktlokationsId": "51234567890"
5936 });
5937 let engine = MappingEngine::new_empty();
5938 let instance = engine.map_reverse(&bo4e_with, &def);
5939 let loc = instance
5940 .segments
5941 .iter()
5942 .find(|s| s.tag == "LOC")
5943 .expect("LOC should exist");
5944 assert_eq!(loc.elements[0][0], "Z16");
5945 assert_eq!(loc.elements[1][0], "51234567890");
5946
5947 let bo4e_without = serde_json::json!({});
5949 let instance2 = engine.map_reverse(&bo4e_without, &def);
5950 let loc2 = instance2.segments.iter().find(|s| s.tag == "LOC");
5951 assert!(loc2.is_none());
5952 }
5953
5954 #[test]
5955 fn test_extract_all_from_instance_collects_all_qualifier_matches() {
5956 use mig_assembly::assembler::*;
5957
5958 let instance = AssembledGroupInstance {
5960 segments: vec![
5961 AssembledSegment {
5962 tag: "SEQ".to_string(),
5963 elements: vec![vec!["ZD6".to_string()]],
5964 mig_number: None,
5965 segment_number: None,
5966 },
5967 AssembledSegment {
5968 tag: "RFF".to_string(),
5969 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
5970 mig_number: None,
5971 segment_number: None,
5972 },
5973 AssembledSegment {
5974 tag: "RFF".to_string(),
5975 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
5976 mig_number: None,
5977 segment_number: None,
5978 },
5979 AssembledSegment {
5980 tag: "RFF".to_string(),
5981 elements: vec![vec!["Z34".to_string(), "REF_C".to_string()]],
5982 mig_number: None,
5983 segment_number: None,
5984 },
5985 AssembledSegment {
5986 tag: "RFF".to_string(),
5987 elements: vec![vec!["Z35".to_string(), "OTHER".to_string()]],
5988 mig_number: None,
5989 segment_number: None,
5990 },
5991 ],
5992 child_groups: vec![],
5993 entry_mig_number: None,
5994 variant_mig_numbers: vec![],
5995 skipped_segments: vec![],
5996 skipped_positions: Vec::new(),
5997 };
5998
5999 let all = MappingEngine::extract_all_from_instance(&instance, "rff[Z34,*].0.1");
6001 assert_eq!(all, vec!["REF_A", "REF_B", "REF_C"]);
6002
6003 let single = MappingEngine::extract_from_instance(&instance, "rff[Z34].0.1");
6005 assert_eq!(single, Some("REF_A".to_string()));
6006
6007 let second = MappingEngine::extract_from_instance(&instance, "rff[Z34,1].0.1");
6008 assert_eq!(second, Some("REF_B".to_string()));
6009 }
6010
6011 #[test]
6012 fn test_forward_wildcard_collect_produces_json_array() {
6013 use mig_assembly::assembler::*;
6014
6015 let instance = AssembledGroupInstance {
6016 segments: vec![
6017 AssembledSegment {
6018 tag: "SEQ".to_string(),
6019 elements: vec![vec!["ZD6".to_string()]],
6020 mig_number: None,
6021 segment_number: None,
6022 },
6023 AssembledSegment {
6024 tag: "RFF".to_string(),
6025 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
6026 mig_number: None,
6027 segment_number: None,
6028 },
6029 AssembledSegment {
6030 tag: "RFF".to_string(),
6031 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
6032 mig_number: None,
6033 segment_number: None,
6034 },
6035 ],
6036 child_groups: vec![],
6037 entry_mig_number: None,
6038 variant_mig_numbers: vec![],
6039 skipped_segments: vec![],
6040 skipped_positions: Vec::new(),
6041 };
6042
6043 let toml_str = r#"
6044[meta]
6045entity = "Test"
6046bo4e_type = "Test"
6047companion_type = "TestEdifact"
6048source_group = "SG4.SG8"
6049
6050[fields]
6051
6052[companion_fields]
6053"rff[Z34,*].0.1" = "messlokationsIdRefs"
6054"#;
6055 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
6056 let engine = MappingEngine::new_empty();
6057
6058 let mut result = serde_json::Map::new();
6059 engine.extract_companion_fields(&instance, &def, &mut result, false);
6060
6061 let companion = result.get("testEdifact").unwrap().as_object().unwrap();
6062 let refs = companion
6063 .get("messlokationsIdRefs")
6064 .unwrap()
6065 .as_array()
6066 .unwrap();
6067 assert_eq!(refs.len(), 2);
6068 assert_eq!(refs[0].as_str().unwrap(), "REF_A");
6069 assert_eq!(refs[1].as_str().unwrap(), "REF_B");
6070 }
6071
6072 #[test]
6073 fn test_reverse_json_array_produces_multiple_segments() {
6074 let toml_str = r#"
6075[meta]
6076entity = "Test"
6077bo4e_type = "Test"
6078companion_type = "TestEdifact"
6079source_group = "SG4.SG8"
6080
6081[fields]
6082
6083[companion_fields]
6084"seq.0.0" = { target = "", default = "ZD6" }
6085"rff[Z34,*].0.1" = "messlokationsIdRefs"
6086"#;
6087 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
6088 let engine = MappingEngine::new_empty();
6089
6090 let bo4e = serde_json::json!({
6091 "testEdifact": {
6092 "messlokationsIdRefs": ["REF_A", "REF_B", "REF_C"]
6093 }
6094 });
6095
6096 let instance = engine.map_reverse(&bo4e, &def);
6097
6098 let rff_segs: Vec<_> = instance
6100 .segments
6101 .iter()
6102 .filter(|s| s.tag == "RFF")
6103 .collect();
6104 assert_eq!(rff_segs.len(), 3);
6105 assert_eq!(rff_segs[0].elements[0][0], "Z34");
6106 assert_eq!(rff_segs[0].elements[0][1], "REF_A");
6107 assert_eq!(rff_segs[1].elements[0][0], "Z34");
6108 assert_eq!(rff_segs[1].elements[0][1], "REF_B");
6109 assert_eq!(rff_segs[2].elements[0][0], "Z34");
6110 assert_eq!(rff_segs[2].elements[0][1], "REF_C");
6111 }
6112
6113 #[test]
6114 fn test_when_filled_dotted_path() {
6115 let toml_str = r#"
6116[meta]
6117entity = "Test"
6118bo4e_type = "Test"
6119companion_type = "TestEdifact"
6120source_group = "SG4.SG8.SG10"
6121
6122[fields]
6123
6124[companion_fields]
6125"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmal.code"] }
6126"cav.0.0" = "merkmal.code"
6127"#;
6128 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
6129
6130 let bo4e = serde_json::json!({
6131 "testEdifact": { "merkmal": { "code": "ZA7" } }
6132 });
6133 let engine = MappingEngine::new_empty();
6134 let instance = engine.map_reverse(&bo4e, &def);
6135 let cci = instance
6136 .segments
6137 .iter()
6138 .find(|s| s.tag == "CCI")
6139 .expect("CCI should exist");
6140 assert_eq!(cci.elements[0][0], "Z83");
6141 }
6142
6143 #[test]
6144 fn test_also_target_forward_extracts_both_fields() {
6145 use mig_assembly::assembler::*;
6146
6147 let instance = AssembledGroupInstance {
6148 segments: vec![AssembledSegment {
6149 tag: "NAD".to_string(),
6150 elements: vec![vec!["Z47".to_string()], vec!["12345".to_string()]],
6151 mig_number: None,
6152 segment_number: None,
6153 }],
6154 child_groups: vec![],
6155 entry_mig_number: None,
6156 variant_mig_numbers: vec![],
6157 skipped_segments: vec![],
6158 skipped_positions: Vec::new(),
6159 };
6160
6161 let toml_str = r#"
6162[meta]
6163entity = "Geschaeftspartner"
6164bo4e_type = "Geschaeftspartner"
6165companion_type = "GeschaeftspartnerEdifact"
6166source_group = "SG4.SG12"
6167
6168[fields]
6169"nad.1.0" = "identifikation"
6170
6171[companion_fields."nad.0.0"]
6172target = "partnerrolle"
6173enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
6174also_target = "datenqualitaet"
6175also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
6176"#;
6177 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
6178 let engine = MappingEngine::new_empty();
6179
6180 let mut result = serde_json::Map::new();
6181 engine.extract_companion_fields(&instance, &def, &mut result, false);
6182
6183 let companion = result
6184 .get("geschaeftspartnerEdifact")
6185 .unwrap()
6186 .as_object()
6187 .unwrap();
6188 assert_eq!(
6189 companion.get("partnerrolle").unwrap().as_str().unwrap(),
6190 "kundeDesLf"
6191 );
6192 assert_eq!(
6193 companion.get("datenqualitaet").unwrap().as_str().unwrap(),
6194 "erwartet"
6195 );
6196 }
6197
6198 #[test]
6199 fn test_also_target_reverse_joint_lookup() {
6200 let toml_str = r#"
6201[meta]
6202entity = "Geschaeftspartner"
6203bo4e_type = "Geschaeftspartner"
6204companion_type = "GeschaeftspartnerEdifact"
6205source_group = "SG4.SG12"
6206
6207[fields]
6208
6209[companion_fields."nad.0.0"]
6210target = "partnerrolle"
6211enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
6212also_target = "datenqualitaet"
6213also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
6214"#;
6215 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
6216 let engine = MappingEngine::new_empty();
6217
6218 let bo4e = serde_json::json!({
6220 "geschaeftspartnerEdifact": {
6221 "partnerrolle": "kundeDesLf",
6222 "datenqualitaet": "erwartet"
6223 }
6224 });
6225 let instance = engine.map_reverse(&bo4e, &def);
6226 let nad = instance
6227 .segments
6228 .iter()
6229 .find(|s| s.tag == "NAD")
6230 .expect("NAD");
6231 assert_eq!(nad.elements[0][0], "Z47");
6232
6233 let bo4e2 = serde_json::json!({
6235 "geschaeftspartnerEdifact": {
6236 "partnerrolle": "kundeDesNb",
6237 "datenqualitaet": "imSystemVorhanden"
6238 }
6239 });
6240 let instance2 = engine.map_reverse(&bo4e2, &def);
6241 let nad2 = instance2
6242 .segments
6243 .iter()
6244 .find(|s| s.tag == "NAD")
6245 .expect("NAD");
6246 assert_eq!(nad2.elements[0][0], "Z52");
6247 }
6248
6249 #[test]
6250 fn test_also_target_mixed_codes_unpaired_skips_datenqualitaet() {
6251 use mig_assembly::assembler::*;
6252
6253 let toml_str = r#"
6255[meta]
6256entity = "Geschaeftspartner"
6257bo4e_type = "Geschaeftspartner"
6258companion_type = "GeschaeftspartnerEdifact"
6259source_group = "SG4.SG12"
6260
6261[fields]
6262
6263[companion_fields."nad.0.0"]
6264target = "partnerrolle"
6265enum_map = { "Z09" = "kundeDesLf", "Z47" = "kundeDesLf", "Z48" = "kundeDesLf" }
6266also_target = "datenqualitaet"
6267also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden" }
6268"#;
6269 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
6270 let engine = MappingEngine::new_empty();
6271
6272 let instance_z09 = AssembledGroupInstance {
6274 segments: vec![AssembledSegment {
6275 tag: "NAD".to_string(),
6276 elements: vec![vec!["Z09".to_string()]],
6277 mig_number: None,
6278 segment_number: None,
6279 }],
6280 child_groups: vec![],
6281 entry_mig_number: None,
6282 variant_mig_numbers: vec![],
6283 skipped_segments: vec![],
6284 skipped_positions: Vec::new(),
6285 };
6286 let mut result = serde_json::Map::new();
6287 engine.extract_companion_fields(&instance_z09, &def, &mut result, false);
6288 let comp = result
6289 .get("geschaeftspartnerEdifact")
6290 .unwrap()
6291 .as_object()
6292 .unwrap();
6293 assert_eq!(
6294 comp.get("partnerrolle").unwrap().as_str().unwrap(),
6295 "kundeDesLf"
6296 );
6297 assert!(
6298 comp.get("datenqualitaet").is_none(),
6299 "Z09 should not set datenqualitaet"
6300 );
6301
6302 let bo4e = serde_json::json!({
6304 "geschaeftspartnerEdifact": { "partnerrolle": "kundeDesLf" }
6305 });
6306 let instance = engine.map_reverse(&bo4e, &def);
6307 let nad = instance
6308 .segments
6309 .iter()
6310 .find(|s| s.tag == "NAD")
6311 .expect("NAD");
6312 assert_eq!(nad.elements[0][0], "Z09");
6313
6314 let bo4e2 = serde_json::json!({
6316 "geschaeftspartnerEdifact": {
6317 "partnerrolle": "kundeDesLf",
6318 "datenqualitaet": "erwartet"
6319 }
6320 });
6321 let instance2 = engine.map_reverse(&bo4e2, &def);
6322 let nad2 = instance2
6323 .segments
6324 .iter()
6325 .find(|s| s.tag == "NAD")
6326 .expect("NAD");
6327 assert_eq!(nad2.elements[0][0], "Z47");
6328 }
6329
6330 #[test]
6336 fn test_unroute_uses_side_channel_metadata() {
6337 let mut entities = serde_json::json!({
6338 "marktlokation": {
6339 "marktlokationsId": "12345678901",
6340 "boTyp": "MARKTLOKATION",
6341 "adresse": {
6342 "ort": "Testhausen",
6343 "postleitzahl": "99011",
6344 "land": "DE"
6345 }
6346 }
6347 })
6348 .as_object()
6349 .unwrap()
6350 .clone();
6351
6352 let mut metadata = serde_json::Map::new();
6353 metadata.insert(
6354 "originalIdx".to_string(),
6355 serde_json::Value::Number(serde_json::Number::from(2usize)),
6356 );
6357 metadata.insert(
6358 "marktrolle".to_string(),
6359 serde_json::json!({"code": "DP"}),
6360 );
6361 metadata.insert(
6362 "ortQualifier".to_string(),
6363 serde_json::json!({"code": "172"}),
6364 );
6365
6366 let mut dp_routing: DpRouting = std::collections::HashMap::new();
6367 dp_routing.insert("marktlokation".to_string(), vec![metadata]);
6368
6369 unroute_lokation_to_nad_dp(&mut entities, &dp_routing);
6370
6371 assert!(
6373 !entities.contains_key("marktlokation"),
6374 "marktlokation should be consumed by unroute"
6375 );
6376 let mts = entities
6377 .get("marktteilnehmer")
6378 .and_then(|v| v.as_array())
6379 .expect("marktteilnehmer array reconstructed");
6380 assert_eq!(mts.len(), 1);
6381 let dp = &mts[0];
6382 assert_eq!(
6383 dp.get("meldepunktId").and_then(|v| v.as_str()),
6384 Some("12345678901")
6385 );
6386 assert_eq!(
6387 dp.get("ort").and_then(|v| v.as_str()),
6388 Some("Testhausen")
6389 );
6390 assert!(
6392 dp.as_object().is_some_and(|m| !m.contains_key("_dpSource")),
6393 "rebuilt DP entry must not carry _dpSource: {dp}"
6394 );
6395 assert!(dp.get("marktrolle").is_some());
6397 assert!(dp.get("ortQualifier").is_some());
6398 }
6399}