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 })
1201 .collect();
1202
1203 let instance = AssembledGroupInstance {
1204 segments: assembled_segments,
1205 child_groups: vec![],
1206 entry_mig_number: None,
1207 variant_mig_numbers: vec![],
1208 skipped_segments: Vec::new(),
1209 skipped_positions: Vec::new(),
1210 };
1211
1212 let mut result = serde_json::Map::new();
1213 self.extract_fields_from_instance(&instance, def, &mut result, true);
1214 serde_json::Value::Object(result)
1215 }
1216
1217 pub fn map_reverse(
1230 &self,
1231 bo4e_value: &serde_json::Value,
1232 def: &MappingDefinition,
1233 ) -> AssembledGroupInstance {
1234 if def.meta.repeat_on_tag.is_some() {
1236 if let Some(arr) = bo4e_value.as_array() {
1237 let mut all_segments = Vec::new();
1238 for elem in arr {
1239 let sub = self.map_reverse_single(elem, def);
1240 all_segments.extend(sub.segments);
1241 }
1242 return AssembledGroupInstance {
1243 segments: all_segments,
1244 child_groups: vec![],
1245 entry_mig_number: None,
1246 variant_mig_numbers: vec![],
1247 skipped_segments: Vec::new(),
1248 skipped_positions: Vec::new(),
1249 };
1250 }
1251 }
1252 self.map_reverse_single(bo4e_value, def)
1253 }
1254
1255 fn map_reverse_single(
1256 &self,
1257 bo4e_value: &serde_json::Value,
1258 def: &MappingDefinition,
1259 ) -> AssembledGroupInstance {
1260 let mut field_values: Vec<(String, String, usize, usize, String)> =
1263 Vec::with_capacity(def.fields.len());
1264
1265 let mut has_real_data = false;
1272 let mut has_data_fields = false;
1273 let mut seg_has_data_field: HashSet<String> = HashSet::new();
1276 let mut seg_has_real_data: HashSet<String> = HashSet::new();
1277 let mut injected_qualifiers: HashSet<String> = HashSet::new();
1278
1279 for (path, field_mapping) in &def.fields {
1280 let (target, default, enum_map, when_filled) = match field_mapping {
1281 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
1282 FieldMapping::Structured(s) => (
1283 s.target.as_str(),
1284 s.default.as_ref(),
1285 s.enum_map.as_ref(),
1286 s.when_filled.as_ref(),
1287 ),
1288 FieldMapping::Nested(_) => continue,
1289 };
1290
1291 let parts: Vec<&str> = path.split('.').collect();
1292 if parts.len() < 2 {
1293 continue;
1294 }
1295
1296 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1297 let seg_key = parts[0].to_uppercase();
1300 let sub_path = &parts[1..];
1301
1302 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1304 let ci = if sub_path.len() > 1 {
1305 sub_path[1].parse::<usize>().unwrap_or(0)
1306 } else {
1307 0
1308 };
1309 (ei, ci)
1310 } else {
1311 match sub_path.len() {
1312 1 => (0, 0),
1313 2 => (1, 0),
1314 _ => continue,
1315 }
1316 };
1317
1318 let val = if target.is_empty() {
1320 match (default, when_filled) {
1321 (Some(d), Some(fields)) => {
1324 let companion_key_for_check =
1325 def.meta.companion_type.as_deref().map(to_camel_case);
1326 let companion_for_check = companion_key_for_check
1327 .as_ref()
1328 .and_then(|k| bo4e_value.get(k))
1329 .unwrap_or(&serde_json::Value::Null);
1330 let any_filled = fields.iter().any(|f| {
1331 self.populate_field(bo4e_value, f).is_some()
1332 || self.populate_field(companion_for_check, f).is_some()
1333 });
1334 if any_filled {
1335 has_real_data = true;
1339 Some(d.clone())
1340 } else {
1341 None
1342 }
1343 }
1344 (Some(d), None) => Some(d.clone()),
1346 (None, _) => None,
1347 }
1348 } else {
1349 has_data_fields = true;
1350 seg_has_data_field.insert(seg_key.clone());
1351 let bo4e_val = self.populate_field(bo4e_value, target);
1352 if bo4e_val.is_some() {
1353 has_real_data = true;
1354 seg_has_real_data.insert(seg_key.clone());
1355 }
1356 let mapped_val = match (bo4e_val, enum_map) {
1358 (Some(v), Some(map)) => {
1359 map.iter()
1361 .find(|(_, bo4e_v)| *bo4e_v == &v)
1362 .map(|(edifact_k, _)| edifact_k.clone())
1363 .or(Some(v))
1364 }
1365 (v, _) => v,
1366 };
1367 mapped_val.or_else(|| default.cloned())
1368 };
1369
1370 if let Some(val) = val {
1371 field_values.push((
1372 seg_key.clone(),
1373 seg_tag.clone(),
1374 element_idx,
1375 component_idx,
1376 val,
1377 ));
1378 }
1379
1380 if let Some(q) = qualifier {
1382 if injected_qualifiers.insert(seg_key.clone()) {
1383 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1384 }
1385 }
1386 }
1387
1388 if let Some(ref companion_fields) = def.companion_fields {
1393 let companion_value = match def.meta.companion_type.as_deref() {
1394 Some(raw_key) => {
1395 let key = to_camel_case(raw_key);
1396 bo4e_value.get(&key).unwrap_or(bo4e_value)
1397 }
1398 None => bo4e_value,
1399 };
1400
1401 for (path, field_mapping) in companion_fields {
1402 let (target, default, enum_map, when_filled, also_target, also_enum_map) =
1403 match field_mapping {
1404 FieldMapping::Simple(t) => (t.as_str(), None, None, None, None, None),
1405 FieldMapping::Structured(s) => (
1406 s.target.as_str(),
1407 s.default.as_ref(),
1408 s.enum_map.as_ref(),
1409 s.when_filled.as_ref(),
1410 s.also_target.as_deref(),
1411 s.also_enum_map.as_ref(),
1412 ),
1413 FieldMapping::Nested(_) => continue,
1414 };
1415
1416 let parts: Vec<&str> = path.split('.').collect();
1417 if parts.len() < 2 {
1418 continue;
1419 }
1420
1421 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1422 let seg_key = parts[0].to_uppercase();
1423 let sub_path = &parts[1..];
1424
1425 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1426 let ci = if sub_path.len() > 1 {
1427 sub_path[1].parse::<usize>().unwrap_or(0)
1428 } else {
1429 0
1430 };
1431 (ei, ci)
1432 } else {
1433 match sub_path.len() {
1434 1 => (0, 0),
1435 2 => (1, 0),
1436 _ => continue,
1437 }
1438 };
1439
1440 if is_collect_all_path(path) && !target.is_empty() {
1442 if let Some(arr) = self
1443 .populate_field_json(companion_value, target)
1444 .and_then(|v| v.as_array().cloned())
1445 {
1446 has_data_fields = true;
1447 if !arr.is_empty() {
1448 has_real_data = true;
1449 }
1450 for (idx, item) in arr.iter().enumerate() {
1451 if let Some(val_str) = item.as_str() {
1452 let mapped = if let Some(map) = enum_map {
1453 map.iter()
1454 .find(|(_, bo4e_v)| *bo4e_v == val_str)
1455 .map(|(edifact_k, _)| edifact_k.clone())
1456 .unwrap_or_else(|| val_str.to_string())
1457 } else {
1458 val_str.to_string()
1459 };
1460 let occ_key = if let Some(q) = qualifier {
1461 format!("{}[{},{}]", seg_tag, q, idx)
1462 } else {
1463 format!("{}[*,{}]", seg_tag, idx)
1464 };
1465 field_values.push((
1466 occ_key.clone(),
1467 seg_tag.clone(),
1468 element_idx,
1469 component_idx,
1470 mapped,
1471 ));
1472 if let Some(q) = qualifier {
1474 if injected_qualifiers.insert(occ_key.clone()) {
1475 field_values.push((
1476 occ_key,
1477 seg_tag.clone(),
1478 0,
1479 0,
1480 q.to_string(),
1481 ));
1482 }
1483 }
1484 }
1485 }
1486 }
1487 continue;
1488 }
1489
1490 let val = if target.is_empty() {
1491 match (default, when_filled) {
1492 (Some(d), Some(fields)) => {
1493 let any_filled = fields.iter().any(|f| {
1494 self.populate_field(bo4e_value, f).is_some()
1495 || self.populate_field(companion_value, f).is_some()
1496 });
1497 if any_filled {
1498 has_real_data = true;
1499 Some(d.clone())
1500 } else {
1501 None
1502 }
1503 }
1504 (Some(d), None) => Some(d.clone()),
1505 (None, _) => None,
1506 }
1507 } else {
1508 has_data_fields = true;
1509 seg_has_data_field.insert(seg_key.clone());
1510 let bo4e_val = self.populate_field(companion_value, target);
1511 if bo4e_val.is_some() {
1512 has_real_data = true;
1513 seg_has_real_data.insert(seg_key.clone());
1514 }
1515 let mapped_val = match (bo4e_val, enum_map) {
1516 (Some(v), Some(map)) => {
1517 if let (Some(at), Some(am)) = (also_target, also_enum_map) {
1518 let also_val = self.populate_field(companion_value, at);
1519 if let Some(av) = also_val.as_deref() {
1520 map.iter()
1522 .find(|(edifact_k, bo4e_v)| {
1523 *bo4e_v == &v
1524 && am.get(*edifact_k).is_some_and(|am_v| am_v == av)
1525 })
1526 .map(|(edifact_k, _)| edifact_k.clone())
1527 .or(Some(v))
1528 } else {
1529 map.iter()
1532 .find(|(edifact_k, bo4e_v)| {
1533 *bo4e_v == &v && !am.contains_key(*edifact_k)
1534 })
1535 .or_else(|| {
1536 map.iter().find(|(_, bo4e_v)| *bo4e_v == &v)
1538 })
1539 .map(|(edifact_k, _)| edifact_k.clone())
1540 .or(Some(v))
1541 }
1542 } else {
1543 map.iter()
1544 .find(|(_, bo4e_v)| *bo4e_v == &v)
1545 .map(|(edifact_k, _)| edifact_k.clone())
1546 .or(Some(v))
1547 }
1548 }
1549 (v, _) => v,
1550 };
1551 mapped_val.or_else(|| default.cloned())
1552 };
1553
1554 if let Some(val) = val {
1555 field_values.push((
1556 seg_key.clone(),
1557 seg_tag.clone(),
1558 element_idx,
1559 component_idx,
1560 val,
1561 ));
1562 }
1563
1564 if let Some(q) = qualifier {
1565 if injected_qualifiers.insert(seg_key.clone()) {
1566 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1567 }
1568 }
1569 }
1570 }
1571
1572 field_values.retain(|(seg_key, _, _, _, _)| {
1580 if !seg_key.contains('[') {
1581 return true; }
1583 !seg_has_data_field.contains(seg_key) || seg_has_real_data.contains(seg_key)
1584 });
1585
1586 if has_data_fields && !has_real_data {
1591 return AssembledGroupInstance {
1592 segments: vec![],
1593 child_groups: vec![],
1594 entry_mig_number: None,
1595 variant_mig_numbers: vec![],
1596 skipped_segments: Vec::new(),
1597 skipped_positions: Vec::new(),
1598 };
1599 }
1600
1601 let mut segments: Vec<AssembledSegment> = Vec::with_capacity(field_values.len());
1604 let mut seen_keys: HashMap<String, usize> = HashMap::new();
1605
1606 for (seg_key, seg_tag, element_idx, component_idx, val) in &field_values {
1607 let seg = if let Some(&pos) = seen_keys.get(seg_key) {
1608 &mut segments[pos]
1609 } else {
1610 let pos = segments.len();
1611 seen_keys.insert(seg_key.clone(), pos);
1612 segments.push(AssembledSegment {
1613 tag: seg_tag.clone(),
1614 elements: vec![],
1615 mig_number: None,
1616 });
1617 &mut segments[pos]
1618 };
1619
1620 while seg.elements.len() <= *element_idx {
1621 seg.elements.push(vec![]);
1622 }
1623 while seg.elements[*element_idx].len() <= *component_idx {
1624 seg.elements[*element_idx].push(String::new());
1625 }
1626 seg.elements[*element_idx][*component_idx] = val.clone();
1627 }
1628
1629 for seg in &mut segments {
1632 let last_populated = seg.elements.iter().rposition(|e| !e.is_empty());
1633 if let Some(last_idx) = last_populated {
1634 for i in 0..last_idx {
1635 if seg.elements[i].is_empty() {
1636 seg.elements[i] = vec![String::new()];
1637 }
1638 }
1639 }
1640 }
1641
1642 if let Some(ref ss) = self.segment_structure {
1644 for seg in &mut segments {
1645 if let Some(expected) = ss.element_count(&seg.tag) {
1646 while seg.elements.len() < expected {
1647 seg.elements.push(vec![String::new()]);
1648 }
1649 }
1650 }
1651 }
1652
1653 AssembledGroupInstance {
1654 segments,
1655 child_groups: vec![],
1656 entry_mig_number: None,
1657 variant_mig_numbers: vec![],
1658 skipped_segments: Vec::new(),
1659 skipped_positions: Vec::new(),
1660 }
1661 }
1662
1663 fn resolve_field_path(segment: &AssembledSegment, path: &[&str]) -> Option<String> {
1676 if path.is_empty() {
1677 return None;
1678 }
1679
1680 if let Ok(element_idx) = path[0].parse::<usize>() {
1682 let component_idx = if path.len() > 1 {
1683 path[1].parse::<usize>().unwrap_or(0)
1684 } else {
1685 0
1686 };
1687 return segment
1688 .elements
1689 .get(element_idx)?
1690 .get(component_idx)
1691 .filter(|v| !v.is_empty())
1692 .cloned();
1693 }
1694
1695 None
1701 }
1702
1703 fn parse_element_component(parts: &[&str]) -> (usize, usize) {
1706 if parts.is_empty() {
1707 return (0, 0);
1708 }
1709 let element_idx = parts[0].parse::<usize>().unwrap_or(0);
1710 let component_idx = if parts.len() > 1 {
1711 parts[1].parse::<usize>().unwrap_or(0)
1712 } else {
1713 0
1714 };
1715 (element_idx, component_idx)
1716 }
1717
1718 pub fn populate_field(
1721 &self,
1722 bo4e_value: &serde_json::Value,
1723 target_field: &str,
1724 ) -> Option<String> {
1725 let mut current = bo4e_value;
1726 for part in target_field.split('.') {
1727 current = current.get(part)?;
1728 }
1729 if let Some(code) = current.get("code").and_then(|v| v.as_str()) {
1731 return Some(code.to_string());
1732 }
1733 current.as_str().map(|s| s.to_string())
1734 }
1735
1736 fn populate_field_json<'a>(
1739 &self,
1740 bo4e_value: &'a serde_json::Value,
1741 target_field: &str,
1742 ) -> Option<&'a serde_json::Value> {
1743 let mut current = bo4e_value;
1744 for part in target_field.split('.') {
1745 current = current.get(part)?;
1746 }
1747 Some(current)
1748 }
1749
1750 pub fn build_segment_from_bo4e(
1752 &self,
1753 bo4e_value: &serde_json::Value,
1754 segment_tag: &str,
1755 target_field: &str,
1756 ) -> AssembledSegment {
1757 let value = self.populate_field(bo4e_value, target_field);
1758 let elements = if let Some(val) = value {
1759 vec![vec![val]]
1760 } else {
1761 vec![]
1762 };
1763 AssembledSegment {
1764 tag: segment_tag.to_uppercase(),
1765 elements,
1766 mig_number: None,
1767 }
1768 }
1769
1770 pub fn resolve_repetition(
1779 tree: &AssembledTree,
1780 group_path: &str,
1781 discriminator: &str,
1782 ) -> Option<usize> {
1783 let (spec, expected) = discriminator.split_once('=')?;
1784 let parts: Vec<&str> = spec.split('.').collect();
1785 if parts.len() != 3 {
1786 return None;
1787 }
1788 let tag = parts[0];
1789 let element_idx: usize = parts[1].parse().ok()?;
1790 let component_idx: usize = parts[2].parse().ok()?;
1791
1792 let path_parts: Vec<&str> = group_path.split('.').collect();
1794
1795 let leaf_group = if path_parts.len() == 1 {
1796 let (group_id, _) = parse_group_spec(path_parts[0]);
1797 tree.groups.iter().find(|g| g.group_id == group_id)?
1798 } else {
1799 let parent_parts = &path_parts[..path_parts.len() - 1];
1801 let mut current_instance = {
1802 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1803 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
1804 first_group.repetitions.get(first_rep.unwrap_or(0))?
1805 };
1806 for part in &parent_parts[1..] {
1807 let (group_id, explicit_rep) = parse_group_spec(part);
1808 let child_group = current_instance
1809 .child_groups
1810 .iter()
1811 .find(|g| g.group_id == group_id)?;
1812 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
1813 }
1814 let (leaf_id, _) = parse_group_spec(path_parts.last()?);
1815 current_instance
1816 .child_groups
1817 .iter()
1818 .find(|g| g.group_id == leaf_id)?
1819 };
1820
1821 let expected_values: Vec<&str> = expected.split('|').collect();
1823 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1824 let matches = instance.segments.iter().any(|s| {
1825 s.tag.eq_ignore_ascii_case(tag)
1826 && s.elements
1827 .get(element_idx)
1828 .and_then(|e| e.get(component_idx))
1829 .map(|v| expected_values.iter().any(|ev| v == ev))
1830 .unwrap_or(false)
1831 });
1832 if matches {
1833 return Some(rep_idx);
1834 }
1835 }
1836
1837 None
1838 }
1839
1840 pub fn resolve_all_repetitions(
1845 tree: &AssembledTree,
1846 group_path: &str,
1847 discriminator: &str,
1848 ) -> Vec<usize> {
1849 let Some((spec, expected)) = discriminator.split_once('=') else {
1850 return Vec::new();
1851 };
1852 let parts: Vec<&str> = spec.split('.').collect();
1853 if parts.len() != 3 {
1854 return Vec::new();
1855 }
1856 let tag = parts[0];
1857 let element_idx: usize = match parts[1].parse() {
1858 Ok(v) => v,
1859 Err(_) => return Vec::new(),
1860 };
1861 let component_idx: usize = match parts[2].parse() {
1862 Ok(v) => v,
1863 Err(_) => return Vec::new(),
1864 };
1865
1866 let path_parts: Vec<&str> = group_path.split('.').collect();
1868
1869 let leaf_group = if path_parts.len() == 1 {
1870 let (group_id, _) = parse_group_spec(path_parts[0]);
1871 match tree.groups.iter().find(|g| g.group_id == group_id) {
1872 Some(g) => g,
1873 None => return Vec::new(),
1874 }
1875 } else {
1876 let parent_parts = &path_parts[..path_parts.len() - 1];
1877 let mut current_instance = {
1878 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1879 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
1880 Some(g) => g,
1881 None => return Vec::new(),
1882 };
1883 match first_group.repetitions.get(first_rep.unwrap_or(0)) {
1884 Some(i) => i,
1885 None => return Vec::new(),
1886 }
1887 };
1888 for part in &parent_parts[1..] {
1889 let (group_id, explicit_rep) = parse_group_spec(part);
1890 let child_group = match current_instance
1891 .child_groups
1892 .iter()
1893 .find(|g| g.group_id == group_id)
1894 {
1895 Some(g) => g,
1896 None => return Vec::new(),
1897 };
1898 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
1899 Some(i) => i,
1900 None => return Vec::new(),
1901 };
1902 }
1903 let (leaf_id, _) = match path_parts.last() {
1904 Some(p) => parse_group_spec(p),
1905 None => return Vec::new(),
1906 };
1907 match current_instance
1908 .child_groups
1909 .iter()
1910 .find(|g| g.group_id == leaf_id)
1911 {
1912 Some(g) => g,
1913 None => return Vec::new(),
1914 }
1915 };
1916
1917 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
1919
1920 let expected_values: Vec<&str> = expected_raw.split('|').collect();
1922 let mut result = Vec::new();
1923 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1924 let matches = instance.segments.iter().any(|s| {
1925 s.tag.eq_ignore_ascii_case(tag)
1926 && s.elements
1927 .get(element_idx)
1928 .and_then(|e| e.get(component_idx))
1929 .map(|v| expected_values.iter().any(|ev| v == ev))
1930 .unwrap_or(false)
1931 });
1932 if matches {
1933 result.push(rep_idx);
1934 }
1935 }
1936
1937 if let Some(occ) = occurrence {
1939 result.into_iter().nth(occ).into_iter().collect()
1940 } else {
1941 result
1942 }
1943 }
1944
1945 pub fn map_all_forward(&self, tree: &AssembledTree) -> serde_json::Value {
1967 self.map_all_forward_inner(tree, true).0
1968 }
1969
1970 pub fn map_all_forward_enriched(
1974 &self,
1975 tree: &AssembledTree,
1976 enrich_codes: bool,
1977 ) -> serde_json::Value {
1978 self.map_all_forward_inner(tree, enrich_codes).0
1979 }
1980
1981 fn map_all_forward_inner(
1991 &self,
1992 tree: &AssembledTree,
1993 enrich_codes: bool,
1994 ) -> (
1995 serde_json::Value,
1996 std::collections::HashMap<String, Vec<usize>>,
1997 DpRouting,
1998 ) {
1999 self.map_all_forward_inner_with_tx(tree, enrich_codes, self.transaction_group.as_deref())
2000 }
2001
2002 fn map_all_forward_inner_with_tx(
2006 &self,
2007 tree: &AssembledTree,
2008 enrich_codes: bool,
2009 tx_group_override: Option<&str>,
2010 ) -> (
2011 serde_json::Value,
2012 std::collections::HashMap<String, Vec<usize>>,
2013 DpRouting,
2014 ) {
2015 let mut result = serde_json::Map::new();
2016 let mut nesting_info: std::collections::HashMap<String, Vec<usize>> =
2017 std::collections::HashMap::new();
2018
2019 for def in &self.definitions {
2020 let entity = &def.meta.entity;
2021
2022 let bo4e = if let Some(ref disc) = def.meta.discriminator {
2023 let use_source_path = def
2028 .meta
2029 .source_path
2030 .as_ref()
2031 .is_some_and(|sp| has_source_path_qualifiers(sp));
2032 if use_source_path {
2033 let sp = def.meta.source_path.as_deref().unwrap();
2035 let all_instances = Self::resolve_all_by_source_path(tree, sp);
2036 let instances: Vec<_> = if let Some(matcher) = DiscriminatorMatcher::parse(disc)
2038 {
2039 matcher.filter_instances(all_instances)
2040 } else {
2041 all_instances
2042 };
2043 let extract = |instance: &AssembledGroupInstance| {
2044 let mut r = serde_json::Map::new();
2045 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
2046 self.extract_companion_fields(instance, def, &mut r, enrich_codes);
2047 serde_json::Value::Object(r)
2048 };
2049 match instances.len() {
2050 0 => None,
2051 1 => Some(extract(instances[0])),
2052 _ => Some(serde_json::Value::Array(
2053 instances.iter().map(|i| extract(i)).collect(),
2054 )),
2055 }
2056 } else {
2057 let reps = Self::resolve_all_repetitions(tree, &def.meta.source_group, disc);
2058 match reps.len() {
2059 0 => None,
2060 1 => Some(self.map_forward_inner(tree, def, reps[0], enrich_codes)),
2061 _ => Some(serde_json::Value::Array(
2062 reps.iter()
2063 .map(|&rep| self.map_forward_inner(tree, def, rep, enrich_codes))
2064 .collect(),
2065 )),
2066 }
2067 }
2068 } else if def.meta.source_group.is_empty() {
2069 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2071 } else if def.meta.source_path.as_ref().is_some_and(|sp| {
2072 has_source_path_qualifiers(sp) || def.meta.source_group.contains('.')
2073 }) {
2074 let sp = def.meta.source_path.as_deref().unwrap();
2079 let mut indexed = Self::resolve_all_with_parent_indices(tree, sp);
2080
2081 if let Some(last_part) = sp.rsplit('.').next() {
2086 if !last_part.contains('_') {
2087 let base_prefix = if let Some(parent) = sp.rsplit_once('.') {
2091 format!("{}.", parent.0)
2092 } else {
2093 String::new()
2094 };
2095 let sibling_qualifiers: Vec<String> = self
2096 .definitions
2097 .iter()
2098 .filter_map(|d| d.meta.source_path.as_deref())
2099 .filter(|other_sp| {
2100 *other_sp != sp
2101 && other_sp.starts_with(&base_prefix)
2102 && other_sp.split('.').count() == sp.split('.').count()
2103 })
2104 .filter_map(|other_sp| {
2105 let other_last = other_sp.rsplit('.').next()?;
2106 let (base, q) = other_last.split_once('_')?;
2109 if base == last_part {
2110 Some(q.to_string())
2111 } else {
2112 None
2113 }
2114 })
2115 .collect();
2116
2117 if !sibling_qualifiers.is_empty() {
2118 indexed.retain(|(_, inst)| {
2119 let entry_qual = inst
2120 .segments
2121 .first()
2122 .and_then(|seg| seg.elements.first())
2123 .and_then(|el| el.first())
2124 .map(|v| v.to_lowercase());
2125 !entry_qual.is_some_and(|q| {
2128 sibling_qualifiers.iter().any(|sq| {
2129 sq.split('_').any(|part| part.eq_ignore_ascii_case(&q))
2130 })
2131 })
2132 });
2133 }
2134 }
2135 }
2136 let extract = |instance: &AssembledGroupInstance| {
2137 let mut r = serde_json::Map::new();
2138 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
2139 self.extract_companion_fields(instance, def, &mut r, enrich_codes);
2140 serde_json::Value::Object(r)
2141 };
2142 if def.meta.source_group.contains('.') && !indexed.is_empty() {
2147 if let Some(sp) = &def.meta.source_path {
2148 let parent_indices: Vec<usize> =
2149 indexed.iter().map(|(idx, _)| *idx).collect();
2150 nesting_info.entry(sp.clone()).or_insert(parent_indices);
2151
2152 let child_key = format!("{sp}#child");
2155 if let std::collections::hash_map::Entry::Vacant(e) =
2156 nesting_info.entry(child_key)
2157 {
2158 let child_indices: Vec<usize> =
2159 Self::compute_child_indices(tree, sp, &indexed);
2160 if !child_indices.is_empty() {
2161 e.insert(child_indices);
2162 }
2163 }
2164 }
2165 }
2166 match indexed.len() {
2167 0 => None,
2168 1 => Some(extract(indexed[0].1)),
2169 _ => Some(serde_json::Value::Array(
2170 indexed.iter().map(|(_, i)| extract(i)).collect(),
2171 )),
2172 }
2173 } else {
2174 let num_reps = Self::count_repetitions(tree, &def.meta.source_group);
2175 if num_reps <= 1 {
2176 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2177 } else {
2178 let mut items = Vec::with_capacity(num_reps);
2180 for rep in 0..num_reps {
2181 items.push(self.map_forward_inner(tree, def, rep, enrich_codes));
2182 }
2183 Some(serde_json::Value::Array(items))
2184 }
2185 };
2186
2187 if let Some(bo4e) = bo4e {
2188 let bo4e = inject_bo4e_metadata(bo4e, &def.meta.bo4e_type);
2189 let key = to_camel_case(entity);
2190 deep_merge_insert(&mut result, &key, bo4e);
2191 }
2192 }
2193
2194 nest_child_entities_in_result(
2199 &mut result,
2200 &self.definitions,
2201 &nesting_info,
2202 tx_group_override,
2203 );
2204
2205 let dp_routing = route_nad_dp_to_lokation(&mut result);
2211
2212 (serde_json::Value::Object(result), nesting_info, dp_routing)
2213 }
2214
2215 pub fn map_all_reverse(
2224 &self,
2225 entities: &serde_json::Value,
2226 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2227 ) -> AssembledTree {
2228 let mut root_segments: Vec<AssembledSegment> = Vec::new();
2229 let mut groups: Vec<AssembledGroup> = Vec::new();
2230 let mut inferred_nesting: std::collections::HashMap<String, Vec<usize>> =
2233 std::collections::HashMap::new();
2234
2235 for def in &self.definitions {
2236 let entity_key = to_camel_case(&def.meta.entity);
2237
2238 let _extracted: Option<serde_json::Value>;
2241 let entity_value = if let Some(v) = entities.get(&entity_key) {
2242 _extracted = None;
2243 v
2244 } else if def.meta.source_group.contains('.') {
2245 match extract_child_from_parent_with_indices(entities, &self.definitions, def) {
2247 Some((v, parent_indices)) => {
2248 if let Some(sp) = def.meta.source_path.as_deref() {
2250 inferred_nesting
2251 .entry(sp.to_string())
2252 .or_insert(parent_indices);
2253 }
2254 _extracted = Some(v);
2255 _extracted.as_ref().unwrap()
2256 }
2257 None => continue,
2258 }
2259 } else {
2260 continue;
2261 };
2262
2263 let unwrapped: Option<serde_json::Value>;
2271 let entity_value = if entity_value.is_object() && !entity_value.is_array() {
2272 if let Some(disc_value) = def
2273 .meta
2274 .discriminator
2275 .as_deref()
2276 .and_then(|d| d.split_once('='))
2277 .map(|(_, v)| v)
2278 {
2279 if let Some(inner) = entity_value.get(disc_value) {
2281 let mut injected = inner.clone();
2282 if let Some(ref cf) = def.companion_fields {
2285 let disc_path = def
2286 .meta
2287 .discriminator
2288 .as_deref()
2289 .unwrap()
2290 .split_once('=')
2291 .unwrap()
2292 .0
2293 .to_lowercase();
2294 for (path, mapping) in cf {
2295 let cf_path = path.to_lowercase();
2299 let matches =
2300 cf_path == disc_path || format!("{}.0", cf_path) == disc_path;
2301 if matches {
2302 let target = match mapping {
2303 FieldMapping::Simple(t) => t.as_str(),
2304 FieldMapping::Structured(s) => s.target.as_str(),
2305 FieldMapping::Nested(_) => continue,
2306 };
2307 if !target.is_empty() {
2308 if let Some(obj) = injected.as_object_mut() {
2309 let entry = obj
2310 .entry(target.to_string())
2311 .or_insert(serde_json::Value::Null);
2312 if entry.is_null() {
2313 *entry = serde_json::Value::String(
2314 disc_value.to_string(),
2315 );
2316 }
2317 }
2318 }
2319 break;
2320 }
2321 }
2322 }
2323 unwrapped = Some(injected);
2324 unwrapped.as_ref().unwrap()
2325 } else {
2326 entity_value
2327 }
2328 } else if is_map_keyed_object(entity_value) {
2329 let map = entity_value.as_object().unwrap();
2334 let arr: Vec<serde_json::Value> = map
2335 .iter()
2336 .map(|(key, val)| {
2337 let mut item = val.clone();
2338 if let Some(obj) = item.as_object_mut() {
2341 if let Some(qualifier_field) = find_qualifier_companion_field(
2342 &self.definitions,
2343 &def.meta.entity,
2344 ) {
2345 let entry = obj
2346 .entry(qualifier_field)
2347 .or_insert(serde_json::Value::Null);
2348 if entry.is_null() {
2349 *entry = serde_json::Value::String(key.clone());
2350 }
2351 }
2352 }
2353 item
2354 })
2355 .collect();
2356 unwrapped = Some(serde_json::Value::Array(arr));
2357 unwrapped.as_ref().unwrap()
2358 } else {
2359 entity_value
2360 }
2361 } else {
2362 entity_value
2363 };
2364
2365 let leaf_group = def
2367 .meta
2368 .source_group
2369 .rsplit('.')
2370 .next()
2371 .unwrap_or(&def.meta.source_group);
2372
2373 if def.meta.source_group.is_empty() {
2374 let instance = self.map_reverse(entity_value, def);
2376 root_segments.extend(instance.segments);
2377 } else if entity_value.is_array() {
2378 let arr = entity_value.as_array().unwrap();
2380 let reps: Vec<_> = arr.iter().map(|item| self.map_reverse(item, def)).collect();
2381
2382 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2384 existing.repetitions.extend(reps);
2385 } else {
2386 groups.push(AssembledGroup {
2387 group_id: leaf_group.to_string(),
2388 repetitions: reps,
2389 });
2390 }
2391 } else {
2392 let instance = self.map_reverse(entity_value, def);
2394
2395 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2396 existing.repetitions.push(instance);
2397 } else {
2398 groups.push(AssembledGroup {
2399 group_id: leaf_group.to_string(),
2400 repetitions: vec![instance],
2401 });
2402 }
2403 }
2404 }
2405
2406 let nested_specs: Vec<(String, String)> = self
2412 .definitions
2413 .iter()
2414 .filter_map(|def| {
2415 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
2416 if parts.len() > 1 {
2417 Some((parts[0].to_string(), parts[parts.len() - 1].to_string()))
2418 } else {
2419 None
2420 }
2421 })
2422 .collect();
2423 for (parent_id, child_id) in &nested_specs {
2424 let has_parent = groups.iter().any(|g| g.group_id == *parent_id);
2426 let has_child = groups.iter().any(|g| g.group_id == *child_id);
2427 if has_parent && has_child {
2428 let child_idx = groups.iter().position(|g| g.group_id == *child_id).unwrap();
2429 let child_group = groups.remove(child_idx);
2430 let parent = groups
2431 .iter_mut()
2432 .find(|g| g.group_id == *parent_id)
2433 .unwrap();
2434 let child_source_path = self
2438 .definitions
2439 .iter()
2440 .find(|d| {
2441 let parts: Vec<&str> = d.meta.source_group.split('.').collect();
2442 parts.len() > 1 && parts[parts.len() - 1] == *child_id
2443 })
2444 .and_then(|d| d.meta.source_path.as_deref());
2445 let distribution = child_source_path.and_then(|key| {
2446 nesting_info
2447 .and_then(|ni| ni.get(key))
2448 .or_else(|| inferred_nesting.get(key))
2449 });
2450 for (i, child_rep) in child_group.repetitions.into_iter().enumerate() {
2451 let target_idx = distribution
2452 .and_then(|dist| dist.get(i))
2453 .copied()
2454 .unwrap_or(0);
2455
2456 if let Some(target_rep) = parent.repetitions.get_mut(target_idx) {
2457 if let Some(existing) = target_rep
2458 .child_groups
2459 .iter_mut()
2460 .find(|g| g.group_id == *child_id)
2461 {
2462 existing.repetitions.push(child_rep);
2463 } else {
2464 target_rep.child_groups.push(AssembledGroup {
2465 group_id: child_id.clone(),
2466 repetitions: vec![child_rep],
2467 });
2468 }
2469 }
2470 }
2471 }
2472 }
2473
2474 let post_group_start = root_segments.len();
2475 AssembledTree {
2476 segments: root_segments,
2477 groups,
2478 post_group_start,
2479 inter_group_segments: std::collections::BTreeMap::new(),
2480 }
2481 }
2482
2483 fn count_repetitions(tree: &AssembledTree, group_path: &str) -> usize {
2485 let parts: Vec<&str> = group_path.split('.').collect();
2486
2487 let (first_id, first_rep) = parse_group_spec(parts[0]);
2488 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2489 Some(g) => g,
2490 None => return 0,
2491 };
2492
2493 if parts.len() == 1 {
2494 return first_group.repetitions.len();
2495 }
2496
2497 let mut current_instance = match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2499 Some(i) => i,
2500 None => return 0,
2501 };
2502
2503 for (i, part) in parts[1..].iter().enumerate() {
2504 let (group_id, explicit_rep) = parse_group_spec(part);
2505 let child_group = match current_instance
2506 .child_groups
2507 .iter()
2508 .find(|g| g.group_id == group_id)
2509 {
2510 Some(g) => g,
2511 None => return 0,
2512 };
2513
2514 if i == parts.len() - 2 {
2515 return child_group.repetitions.len();
2517 }
2518 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2519 Some(i) => i,
2520 None => return 0,
2521 };
2522 }
2523
2524 0
2525 }
2526
2527 pub fn map_interchange(
2536 msg_engine: &MappingEngine,
2537 tx_engine: &MappingEngine,
2538 tree: &AssembledTree,
2539 transaction_group: &str,
2540 enrich_codes: bool,
2541 ) -> crate::model::MappedMessage {
2542 let (stammdaten, nesting_info, dp_routing) =
2544 msg_engine.map_all_forward_inner(tree, enrich_codes);
2545
2546 let transaktionen = tree
2548 .groups
2549 .iter()
2550 .find(|g| g.group_id == transaction_group)
2551 .map(|sg| {
2552 sg.repetitions
2553 .iter()
2554 .map(|instance| {
2555 let wrapped_tree = AssembledTree {
2558 segments: vec![],
2559 groups: vec![AssembledGroup {
2560 group_id: transaction_group.to_string(),
2561 repetitions: vec![instance.clone()],
2562 }],
2563 post_group_start: 0,
2564 inter_group_segments: std::collections::BTreeMap::new(),
2565 };
2566
2567 let (tx_result, tx_nesting, tx_dp_routing) = tx_engine
2571 .map_all_forward_inner_with_tx(
2572 &wrapped_tree,
2573 enrich_codes,
2574 Some(transaction_group),
2575 );
2576
2577 crate::model::MappedTransaktion {
2578 stammdaten: tx_result,
2579 nesting_info: tx_nesting,
2580 dp_routing: tx_dp_routing,
2581 }
2582 })
2583 .collect()
2584 })
2585 .unwrap_or_default();
2586
2587 crate::model::MappedMessage {
2588 stammdaten,
2589 transaktionen,
2590 nesting_info,
2591 dp_routing,
2592 inter_group_segments: tree.inter_group_segments.clone(),
2593 }
2594 }
2595
2596 pub fn map_interchange_reverse(
2606 msg_engine: &MappingEngine,
2607 tx_engine: &MappingEngine,
2608 mapped: &crate::model::MappedMessage,
2609 transaction_group: &str,
2610 filtered_mig: Option<&MigSchema>,
2611 ) -> AssembledTree {
2612 let _owned_msg: Option<serde_json::Value>;
2618 let msg_stammdaten = if !mapped.dp_routing.is_empty() {
2619 if let serde_json::Value::Object(map) = &mapped.stammdaten {
2620 let mut cloned = map.clone();
2621 unroute_lokation_to_nad_dp(&mut cloned, &mapped.dp_routing);
2622 _owned_msg = Some(serde_json::Value::Object(cloned));
2623 _owned_msg.as_ref().unwrap()
2624 } else {
2625 _owned_msg = None;
2626 &mapped.stammdaten
2627 }
2628 } else {
2629 _owned_msg = None;
2630 &mapped.stammdaten
2631 };
2632
2633 let msg_tree = msg_engine.map_all_reverse(
2634 msg_stammdaten,
2635 if mapped.nesting_info.is_empty() {
2636 None
2637 } else {
2638 Some(&mapped.nesting_info)
2639 },
2640 );
2641
2642 let mut sg4_reps: Vec<AssembledGroupInstance> = Vec::new();
2644
2645 struct DefWithMeta<'a> {
2649 def: &'a MappingDefinition,
2650 relative: String,
2651 depth: usize,
2652 }
2653
2654 let mut sorted_defs: Vec<DefWithMeta> = tx_engine
2655 .definitions
2656 .iter()
2657 .map(|def| {
2658 let relative = strip_tx_group_prefix(&def.meta.source_group, transaction_group);
2659 let depth = if relative.is_empty() {
2660 0
2661 } else {
2662 relative.chars().filter(|c| *c == '.').count() + 1
2663 };
2664 DefWithMeta {
2665 def,
2666 relative,
2667 depth,
2668 }
2669 })
2670 .collect();
2671
2672 let mut parent_rep_map: std::collections::HashMap<String, usize> =
2676 std::collections::HashMap::new();
2677 for dm in &sorted_defs {
2678 if dm.depth >= 2 {
2679 let parts: Vec<&str> = dm.relative.split('.').collect();
2680 let (_, parent_rep) = parse_group_spec(parts[0]);
2681 if let Some(rep_idx) = parent_rep {
2682 if let Some(sp) = &dm.def.meta.source_path {
2683 if let Some((parent_path, _)) = sp.rsplit_once('.') {
2684 parent_rep_map
2685 .entry(parent_path.to_string())
2686 .or_insert(rep_idx);
2687 }
2688 }
2689 }
2690 }
2691 }
2692
2693 for dm in &mut sorted_defs {
2696 if dm.depth == 1 && !dm.relative.contains(':') {
2697 if let Some(sp) = &dm.def.meta.source_path {
2698 if let Some(rep_idx) = parent_rep_map.get(sp.as_str()) {
2699 dm.relative = format!("{}:{}", dm.relative, rep_idx);
2700 }
2701 }
2702 }
2703 }
2704
2705 if let Some(mig) = filtered_mig {
2712 let mig_order = build_reverse_mig_group_order(mig, transaction_group);
2713 sorted_defs.sort_by(|a, b| {
2714 a.depth.cmp(&b.depth).then_with(|| {
2715 let a_id = a.relative.split(':').next().unwrap_or(&a.relative);
2716 let b_id = b.relative.split(':').next().unwrap_or(&b.relative);
2717 let a_pos = variant_mig_position(a.def, a_id, &mig_order);
2719 let b_pos = variant_mig_position(b.def, b_id, &mig_order);
2720 a_pos.cmp(&b_pos).then(a.relative.cmp(&b.relative))
2721 })
2722 });
2723 } else {
2724 sorted_defs.sort_by(|a, b| a.depth.cmp(&b.depth).then(a.relative.cmp(&b.relative)));
2725 }
2726
2727 for tx in &mapped.transaktionen {
2728 let mut root_segs: Vec<AssembledSegment> = Vec::new();
2729 let mut child_groups: Vec<AssembledGroup> = Vec::new();
2730
2731 let _owned_tx: Option<serde_json::Value>;
2736 let tx_stammdaten: &serde_json::Value = if !tx.dp_routing.is_empty() {
2737 if let serde_json::Value::Object(map) = &tx.stammdaten {
2738 let mut cloned = map.clone();
2739 unroute_lokation_to_nad_dp(&mut cloned, &tx.dp_routing);
2740 _owned_tx = Some(serde_json::Value::Object(cloned));
2741 _owned_tx.as_ref().unwrap()
2742 } else {
2743 _owned_tx = None;
2744 &tx.stammdaten
2745 }
2746 } else {
2747 _owned_tx = None;
2748 &tx.stammdaten
2749 };
2750
2751 let mut source_path_to_rep: std::collections::HashMap<String, Vec<usize>> =
2756 std::collections::HashMap::new();
2757
2758 for dm in &sorted_defs {
2759 let entity_key = to_camel_case(&dm.def.meta.entity);
2762 let _tx_extracted: Option<serde_json::Value>;
2763 let bo4e_value = if let Some(v) = tx_stammdaten.get(&entity_key) {
2764 _tx_extracted = None;
2765 v
2766 } else if dm.def.meta.source_group.contains('.') {
2767 match extract_child_from_parent(tx_stammdaten, &tx_engine.definitions, dm.def)
2768 {
2769 Some(v) => {
2770 _tx_extracted = Some(v);
2771 _tx_extracted.as_ref().unwrap()
2772 }
2773 None => continue,
2774 }
2775 } else {
2776 continue;
2777 };
2778
2779 let unwrapped_value: Option<serde_json::Value>;
2781 let bo4e_value = if bo4e_value.is_object() && !bo4e_value.is_array() {
2782 if let Some(disc_value) = dm
2783 .def
2784 .meta
2785 .discriminator
2786 .as_deref()
2787 .and_then(|d| d.split_once('='))
2788 .map(|(_, v)| v)
2789 {
2790 if let Some(inner) = bo4e_value.get(disc_value) {
2791 let mut injected = inner.clone();
2792 if let Some(ref cf) = dm.def.companion_fields {
2793 let disc_path = dm
2794 .def
2795 .meta
2796 .discriminator
2797 .as_deref()
2798 .unwrap()
2799 .split_once('=')
2800 .unwrap()
2801 .0
2802 .to_lowercase();
2803 for (path, mapping) in cf {
2804 let cf_path = path.to_lowercase();
2805 let matches = cf_path == disc_path
2806 || format!("{}.0", cf_path) == disc_path;
2807 if matches {
2808 let target = match mapping {
2809 FieldMapping::Simple(t) => t.as_str(),
2810 FieldMapping::Structured(s) => s.target.as_str(),
2811 FieldMapping::Nested(_) => continue,
2812 };
2813 if !target.is_empty() {
2814 if let Some(obj) = injected.as_object_mut() {
2815 obj.entry(target.to_string()).or_insert_with(
2816 || {
2817 serde_json::Value::String(
2818 disc_value.to_string(),
2819 )
2820 },
2821 );
2822 }
2823 }
2824 break;
2825 }
2826 }
2827 }
2828 unwrapped_value = Some(injected);
2829 unwrapped_value.as_ref().unwrap()
2830 } else {
2831 bo4e_value
2832 }
2833 } else if is_map_keyed_object(bo4e_value) {
2834 let map = bo4e_value.as_object().unwrap();
2835 let arr: Vec<serde_json::Value> = map
2836 .iter()
2837 .map(|(key, val)| {
2838 let mut item = val.clone();
2839 if let Some(obj) = item.as_object_mut() {
2840 if let Some(qualifier_field) = find_qualifier_companion_field(
2841 &tx_engine.definitions,
2842 &dm.def.meta.entity,
2843 ) {
2844 let entry = obj
2845 .entry(qualifier_field)
2846 .or_insert(serde_json::Value::Null);
2847 if entry.is_null() {
2848 *entry = serde_json::Value::String(key.clone());
2849 }
2850 }
2851 }
2852 item
2853 })
2854 .collect();
2855 unwrapped_value = Some(serde_json::Value::Array(arr));
2856 unwrapped_value.as_ref().unwrap()
2857 } else {
2858 bo4e_value
2859 }
2860 } else {
2861 bo4e_value
2862 };
2863
2864 let items: Vec<&serde_json::Value> = if bo4e_value.is_array() {
2868 bo4e_value.as_array().unwrap().iter().collect()
2869 } else {
2870 vec![bo4e_value]
2871 };
2872
2873 for (item_idx, item) in items.iter().enumerate() {
2874 let instance = tx_engine.map_reverse(item, dm.def);
2875
2876 if instance.segments.is_empty() && instance.child_groups.is_empty() {
2878 continue;
2879 }
2880
2881 if dm.relative.is_empty() {
2882 root_segs.extend(instance.segments);
2883 } else {
2884 let effective_relative = if dm.depth >= 2 {
2888 let rel = if items.len() > 1 {
2891 strip_all_rep_indices(&dm.relative)
2892 } else {
2893 dm.relative.clone()
2894 };
2895 let skip_nesting = dm
2902 .def
2903 .meta
2904 .source_path
2905 .as_ref()
2906 .and_then(|sp| sp.rsplit_once('.'))
2907 .and_then(|(parent_path, _)| source_path_to_rep.get(parent_path))
2908 .is_some_and(|reps| reps.len() == 1);
2909 let nesting_idx = if items.len() > 1 && !skip_nesting {
2910 dm.def
2911 .meta
2912 .source_path
2913 .as_ref()
2914 .and_then(|sp| tx.nesting_info.get(sp))
2915 .and_then(|dist| dist.get(item_idx))
2916 .copied()
2917 } else {
2918 None
2919 };
2920 if let Some(parent_rep) = nesting_idx {
2921 let parts: Vec<&str> = rel.split('.').collect();
2923 let parent_id = parts[0].split(':').next().unwrap_or(parts[0]);
2924 let rest = parts[1..].join(".");
2925 format!("{}:{}.{}", parent_id, parent_rep, rest)
2926 } else {
2927 resolve_child_relative(
2928 &rel,
2929 dm.def.meta.source_path.as_deref(),
2930 &source_path_to_rep,
2931 item_idx,
2932 )
2933 }
2934 } else if dm.depth == 1 {
2935 let child_key = dm
2938 .def
2939 .meta
2940 .source_path
2941 .as_ref()
2942 .map(|sp| format!("{sp}#child"));
2943 if let Some(child_indices) =
2944 child_key.as_ref().and_then(|ck| tx.nesting_info.get(ck))
2945 {
2946 if let Some(&target) = child_indices.get(item_idx) {
2947 if target != usize::MAX {
2948 let base =
2949 dm.relative.split(':').next().unwrap_or(&dm.relative);
2950 format!("{}:{}", base, target)
2951 } else {
2952 dm.relative.clone()
2953 }
2954 } else if items.len() > 1 && item_idx > 0 {
2955 strip_rep_index(&dm.relative)
2956 } else {
2957 dm.relative.clone()
2958 }
2959 } else if items.len() > 1 && item_idx > 0 {
2960 strip_rep_index(&dm.relative)
2961 } else {
2962 dm.relative.clone()
2963 }
2964 } else if items.len() > 1 && item_idx > 0 {
2965 strip_rep_index(&dm.relative)
2968 } else {
2969 dm.relative.clone()
2970 };
2971
2972 let rep_used =
2973 place_in_groups(&mut child_groups, &effective_relative, instance);
2974
2975 if dm.depth == 1 {
2977 if let Some(sp) = &dm.def.meta.source_path {
2978 source_path_to_rep
2979 .entry(sp.clone())
2980 .or_default()
2981 .push(rep_used);
2982 }
2983 }
2984 }
2985 }
2986 }
2987
2988 if let Some(mig) = filtered_mig {
2993 sort_variant_reps_by_mig(&mut child_groups, mig, transaction_group);
2994 }
2995
2996 sg4_reps.push(AssembledGroupInstance {
2997 segments: root_segs,
2998 child_groups,
2999 entry_mig_number: None,
3000 variant_mig_numbers: vec![],
3001 skipped_segments: Vec::new(),
3002 skipped_positions: Vec::new(),
3003 });
3004 }
3005
3006 let mut root_segments = Vec::new();
3013 let mut uns_segments = Vec::new();
3014 let mut uns_is_summary = false;
3015 let mut found_uns = false;
3016 for seg in msg_tree.segments {
3017 if seg.tag == "UNS" {
3018 uns_is_summary = seg
3020 .elements
3021 .first()
3022 .and_then(|el| el.first())
3023 .map(|v| v == "S")
3024 .unwrap_or(false);
3025 uns_segments.push(seg);
3026 found_uns = true;
3027 } else if found_uns {
3028 uns_segments.push(seg);
3030 } else {
3031 root_segments.push(seg);
3032 }
3033 }
3034
3035 let pre_group_count = root_segments.len();
3036 let mut all_groups = msg_tree.groups;
3037 let mut inter_group = msg_tree.inter_group_segments;
3038
3039 let sg_num = |id: &str| -> usize {
3041 id.strip_prefix("SG")
3042 .and_then(|n| n.parse::<usize>().ok())
3043 .unwrap_or(0)
3044 };
3045
3046 if !sg4_reps.is_empty() {
3047 if uns_is_summary {
3048 all_groups.push(AssembledGroup {
3050 group_id: transaction_group.to_string(),
3051 repetitions: sg4_reps,
3052 });
3053 if !uns_segments.is_empty() {
3054 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3059 let tx_num = sg_num(transaction_group);
3060 let uns_pos = all_groups
3061 .iter()
3062 .rposition(|g| sg_num(&g.group_id) <= tx_num)
3063 .map(|i| i + 1)
3064 .unwrap_or(all_groups.len());
3065 inter_group.insert(uns_pos, uns_segments);
3066 }
3067 } else {
3068 if !uns_segments.is_empty() {
3070 inter_group.insert(all_groups.len(), uns_segments);
3071 }
3072 all_groups.push(AssembledGroup {
3073 group_id: transaction_group.to_string(),
3074 repetitions: sg4_reps,
3075 });
3076 }
3077 } else if !uns_segments.is_empty() {
3078 if transaction_group.is_empty() {
3079 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3084 if uns_is_summary {
3085 inter_group.insert(all_groups.len(), uns_segments);
3086 } else {
3087 inter_group.insert(0, uns_segments);
3088 }
3089 } else {
3090 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3094 let tx_num = sg_num(transaction_group);
3095 let uns_pos = all_groups
3096 .iter()
3097 .rposition(|g| sg_num(&g.group_id) <= tx_num)
3098 .map(|i| i + 1)
3099 .unwrap_or(all_groups.len());
3100 inter_group.insert(uns_pos, uns_segments);
3101 }
3102 }
3103
3104 for (k, segs) in &mapped.inter_group_segments {
3112 if segs.is_empty() {
3113 continue;
3114 }
3115 let existing_tags: std::collections::HashSet<String> = inter_group
3116 .get(k)
3117 .map(|v| v.iter().map(|s| s.tag.clone()).collect())
3118 .unwrap_or_default();
3119 for seg in segs {
3120 if existing_tags.contains(&seg.tag) {
3121 continue;
3122 }
3123 inter_group.entry(*k).or_default().push(seg.clone());
3124 }
3125 }
3126
3127 AssembledTree {
3128 segments: root_segments,
3129 groups: all_groups,
3130 post_group_start: pre_group_count,
3131 inter_group_segments: inter_group,
3132 }
3133 }
3134
3135 pub fn build_group_from_bo4e(
3137 &self,
3138 bo4e_value: &serde_json::Value,
3139 def: &MappingDefinition,
3140 ) -> AssembledGroup {
3141 let instance = self.map_reverse(bo4e_value, def);
3142 let leaf_group = def
3143 .meta
3144 .source_group
3145 .rsplit('.')
3146 .next()
3147 .unwrap_or(&def.meta.source_group);
3148
3149 AssembledGroup {
3150 group_id: leaf_group.to_string(),
3151 repetitions: vec![instance],
3152 }
3153 }
3154
3155 pub fn map_interchange_typed<M, T>(
3163 msg_engine: &MappingEngine,
3164 tx_engine: &MappingEngine,
3165 tree: &AssembledTree,
3166 tx_group: &str,
3167 enrich_codes: bool,
3168 nachrichtendaten: crate::model::Nachrichtendaten,
3169 interchangedaten: crate::model::Interchangedaten,
3170 ) -> Result<crate::model::Interchange<M, T>, serde_json::Error>
3171 where
3172 M: serde::de::DeserializeOwned,
3173 T: serde::de::DeserializeOwned,
3174 {
3175 let mapped = Self::map_interchange(msg_engine, tx_engine, tree, tx_group, enrich_codes);
3176 let nachricht = mapped.into_dynamic_nachricht(nachrichtendaten);
3177 let dynamic = crate::model::DynamicInterchange {
3178 interchangedaten,
3179 nachrichten: vec![nachricht],
3180 };
3181 let value = serde_json::to_value(&dynamic)?;
3182 serde_json::from_value(value)
3183 }
3184
3185 pub fn map_interchange_reverse_typed<M, T>(
3192 msg_engine: &MappingEngine,
3193 tx_engine: &MappingEngine,
3194 nachricht: &crate::model::Nachricht<M, T>,
3195 tx_group: &str,
3196 ) -> Result<AssembledTree, serde_json::Error>
3197 where
3198 M: serde::Serialize,
3199 T: serde::Serialize,
3200 {
3201 let stammdaten = serde_json::to_value(&nachricht.stammdaten)?;
3202 let transaktionen: Vec<crate::model::MappedTransaktion> = nachricht
3203 .transaktionen
3204 .iter()
3205 .map(|t| {
3206 Ok(crate::model::MappedTransaktion {
3207 stammdaten: serde_json::to_value(t)?,
3208 nesting_info: Default::default(),
3209 dp_routing: Default::default(),
3210 })
3211 })
3212 .collect::<Result<Vec<_>, serde_json::Error>>()?;
3213 let mapped = crate::model::MappedMessage {
3214 stammdaten,
3215 transaktionen,
3216 nesting_info: Default::default(),
3217 dp_routing: Default::default(),
3218 inter_group_segments: Default::default(),
3219 };
3220 Ok(Self::map_interchange_reverse(
3221 msg_engine, tx_engine, &mapped, tx_group, None,
3222 ))
3223 }
3224}
3225
3226fn parse_source_path_part(part: &str) -> (&str, Option<&str>) {
3233 if let Some(pos) = part.find('_') {
3237 let group = &part[..pos];
3238 let qualifier = &part[pos + 1..];
3239 if !qualifier.is_empty() {
3240 return (group, Some(qualifier));
3241 }
3242 }
3243 (part, None)
3244}
3245
3246fn build_reverse_mig_group_order(mig: &MigSchema, tx_group_id: &str) -> HashMap<String, usize> {
3254 let mut order = HashMap::new();
3255 if let Some(tg) = mig.segment_groups.iter().find(|g| g.id == tx_group_id) {
3256 for (i, nested) in tg.nested_groups.iter().enumerate() {
3257 if let Some(ref vc) = nested.variant_code {
3259 let variant_key = format!("{}_{}", nested.id, vc.to_uppercase());
3260 order.insert(variant_key, i);
3261 }
3262 order.entry(nested.id.clone()).or_insert(i);
3264 }
3265 }
3266 order
3267}
3268
3269fn variant_mig_position(
3275 def: &MappingDefinition,
3276 base_group_id: &str,
3277 mig_order: &HashMap<String, usize>,
3278) -> usize {
3279 if let Some(ref sp) = def.meta.source_path {
3282 let base_lower = base_group_id.to_lowercase();
3284 for part in sp.split('.') {
3285 if part.starts_with(&base_lower)
3286 || part.starts_with(base_group_id.to_lowercase().as_str())
3287 {
3288 if let Some(underscore_pos) = part.find('_') {
3290 let qualifier = &part[underscore_pos + 1..];
3291 let variant_key = format!("{}_{}", base_group_id, qualifier.to_uppercase());
3292 if let Some(&pos) = mig_order.get(&variant_key) {
3293 return pos;
3294 }
3295 }
3296 }
3297 }
3298 }
3299 mig_order.get(base_group_id).copied().unwrap_or(usize::MAX)
3301}
3302
3303fn find_rep_by_entry_qualifier<'a>(
3308 reps: &'a [AssembledGroupInstance],
3309 qualifier: &str,
3310) -> Option<&'a AssembledGroupInstance> {
3311 let parts: Vec<&str> = qualifier.split('_').collect();
3313 reps.iter().find(|inst| {
3314 inst.segments.first().is_some_and(|seg| {
3315 seg.elements
3316 .first()
3317 .and_then(|e| e.first())
3318 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3319 })
3320 })
3321}
3322
3323fn find_all_reps_by_entry_qualifier<'a>(
3325 reps: &'a [AssembledGroupInstance],
3326 qualifier: &str,
3327) -> Vec<&'a AssembledGroupInstance> {
3328 let parts: Vec<&str> = qualifier.split('_').collect();
3330 reps.iter()
3331 .filter(|inst| {
3332 inst.segments.first().is_some_and(|seg| {
3333 seg.elements
3334 .first()
3335 .and_then(|e| e.first())
3336 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3337 })
3338 })
3339 .collect()
3340}
3341
3342fn has_source_path_qualifiers(source_path: &str) -> bool {
3344 source_path.split('.').any(|part| {
3345 if let Some(pos) = part.find('_') {
3346 pos < part.len() - 1
3347 } else {
3348 false
3349 }
3350 })
3351}
3352
3353fn parse_group_spec(part: &str) -> (&str, Option<usize>) {
3354 if let Some(colon_pos) = part.find(':') {
3355 let id = &part[..colon_pos];
3356 let rep = part[colon_pos + 1..].parse::<usize>().ok();
3357 (id, rep)
3358 } else {
3359 (part, None)
3360 }
3361}
3362
3363fn strip_tx_group_prefix(source_group: &str, tx_group: &str) -> String {
3369 if source_group == tx_group || source_group.is_empty() {
3370 String::new()
3371 } else if let Some(rest) = source_group.strip_prefix(tx_group) {
3372 rest.strip_prefix('.').unwrap_or(rest).to_string()
3373 } else {
3374 source_group.to_string()
3375 }
3376}
3377
3378fn place_in_groups(
3386 groups: &mut Vec<AssembledGroup>,
3387 relative_path: &str,
3388 instance: AssembledGroupInstance,
3389) -> usize {
3390 let parts: Vec<&str> = relative_path.split('.').collect();
3391
3392 if parts.len() == 1 {
3393 let (id, rep) = parse_group_spec(parts[0]);
3395
3396 let group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == id) {
3398 g
3399 } else {
3400 groups.push(AssembledGroup {
3401 group_id: id.to_string(),
3402 repetitions: vec![],
3403 });
3404 groups.last_mut().unwrap()
3405 };
3406
3407 if let Some(rep_idx) = rep {
3408 while group.repetitions.len() <= rep_idx {
3410 group.repetitions.push(AssembledGroupInstance {
3411 segments: vec![],
3412 child_groups: vec![],
3413 entry_mig_number: None,
3414 variant_mig_numbers: vec![],
3415 skipped_segments: Vec::new(),
3416 skipped_positions: Vec::new(),
3417 });
3418 }
3419 group.repetitions[rep_idx]
3420 .segments
3421 .extend(instance.segments);
3422 group.repetitions[rep_idx]
3423 .child_groups
3424 .extend(instance.child_groups);
3425 rep_idx
3426 } else {
3427 let pos = group.repetitions.len();
3429 group.repetitions.push(instance);
3430 pos
3431 }
3432 } else {
3433 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3435 let rep_idx = parent_rep.unwrap_or(0);
3436
3437 let parent_group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == parent_id) {
3439 g
3440 } else {
3441 groups.push(AssembledGroup {
3442 group_id: parent_id.to_string(),
3443 repetitions: vec![],
3444 });
3445 groups.last_mut().unwrap()
3446 };
3447
3448 while parent_group.repetitions.len() <= rep_idx {
3450 parent_group.repetitions.push(AssembledGroupInstance {
3451 segments: vec![],
3452 child_groups: vec![],
3453 entry_mig_number: None,
3454 variant_mig_numbers: vec![],
3455 skipped_segments: Vec::new(),
3456 skipped_positions: Vec::new(),
3457 });
3458 }
3459
3460 let remaining = parts[1..].join(".");
3461 place_in_groups(
3462 &mut parent_group.repetitions[rep_idx].child_groups,
3463 &remaining,
3464 instance,
3465 );
3466 rep_idx
3467 }
3468}
3469
3470fn resolve_child_relative(
3482 relative: &str,
3483 source_path: Option<&str>,
3484 source_path_to_rep: &std::collections::HashMap<String, Vec<usize>>,
3485 item_idx: usize,
3486) -> String {
3487 let parts: Vec<&str> = relative.split('.').collect();
3488 if parts.is_empty() {
3489 return relative.to_string();
3490 }
3491
3492 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3494 if parent_rep.is_some() {
3495 return relative.to_string();
3496 }
3497
3498 if let Some(sp) = source_path {
3500 if let Some((parent_path, _child)) = sp.rsplit_once('.') {
3501 if let Some(rep_indices) = source_path_to_rep.get(parent_path) {
3503 let rep_idx = rep_indices
3504 .get(item_idx)
3505 .or_else(|| rep_indices.last())
3506 .copied()
3507 .unwrap_or(0);
3508 let rest = parts[1..].join(".");
3509 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3510 }
3511 let prefix = format!("{}_", parent_path);
3519 let mut unioned: Vec<usize> = source_path_to_rep
3520 .iter()
3521 .filter(|(k, _)| k.starts_with(&prefix))
3522 .flat_map(|(_, v)| v.iter().copied())
3523 .collect();
3524 if !unioned.is_empty() {
3525 unioned.sort_unstable();
3526 unioned.dedup();
3527 let rep_idx = unioned
3528 .get(item_idx)
3529 .or_else(|| unioned.last())
3530 .copied()
3531 .unwrap_or(0);
3532 let rest = parts[1..].join(".");
3533 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3534 }
3535 }
3536 }
3537
3538 relative.to_string()
3540}
3541
3542struct DiscriminatorMatcher<'a> {
3549 tag: &'a str,
3550 element_idx: usize,
3551 component_idx: usize,
3552 expected_values: Vec<&'a str>,
3553 occurrence: Option<usize>,
3555}
3556
3557impl<'a> DiscriminatorMatcher<'a> {
3558 fn parse(disc: &'a str) -> Option<Self> {
3559 let (spec, expected) = disc.split_once('=')?;
3560 let parts: Vec<&str> = spec.split('.').collect();
3561 if parts.len() != 3 {
3562 return None;
3563 }
3564 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
3565 Some(Self {
3566 tag: parts[0],
3567 element_idx: parts[1].parse().ok()?,
3568 component_idx: parts[2].parse().ok()?,
3569 expected_values: expected_raw.split('|').collect(),
3570 occurrence,
3571 })
3572 }
3573
3574 fn matches(&self, instance: &AssembledGroupInstance) -> bool {
3575 instance.segments.iter().any(|s| {
3576 s.tag.eq_ignore_ascii_case(self.tag)
3577 && s.elements
3578 .get(self.element_idx)
3579 .and_then(|e| e.get(self.component_idx))
3580 .map(|v| self.expected_values.iter().any(|ev| v == ev))
3581 .unwrap_or(false)
3582 })
3583 }
3584
3585 fn filter_instances<'b>(
3587 &self,
3588 instances: Vec<&'b AssembledGroupInstance>,
3589 ) -> Vec<&'b AssembledGroupInstance> {
3590 let matching: Vec<_> = instances
3591 .into_iter()
3592 .filter(|inst| self.matches(inst))
3593 .collect();
3594 if let Some(occ) = self.occurrence {
3595 matching.into_iter().nth(occ).into_iter().collect()
3596 } else {
3597 matching
3598 }
3599 }
3600}
3601
3602fn parse_discriminator_occurrence(expected: &str) -> (&str, Option<usize>) {
3608 if let Some(hash_pos) = expected.rfind('#') {
3609 if let Ok(occ) = expected[hash_pos + 1..].parse::<usize>() {
3610 return (&expected[..hash_pos], Some(occ));
3611 }
3612 }
3613 (expected, None)
3614}
3615
3616fn strip_rep_index(relative: &str) -> String {
3620 let (id, _) = parse_group_spec(relative);
3621 id.to_string()
3622}
3623
3624fn strip_all_rep_indices(relative: &str) -> String {
3629 relative
3630 .split('.')
3631 .map(|part| {
3632 let (id, _) = parse_group_spec(part);
3633 id
3634 })
3635 .collect::<Vec<_>>()
3636 .join(".")
3637}
3638
3639fn is_collect_all_path(path: &str) -> bool {
3644 let tag_part = path.split('.').next().unwrap_or("");
3645 if let Some(bracket_start) = tag_part.find('[') {
3646 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3647 if let Some(comma_pos) = inner.find(',') {
3648 let qualifier = &inner[..comma_pos];
3649 let occ = &inner[comma_pos + 1..];
3650 qualifier != "*" && occ == "*"
3652 } else {
3653 false
3654 }
3655 } else {
3656 false
3657 }
3658}
3659
3660fn parse_tag_qualifier(tag_part: &str) -> (String, Option<&str>, usize) {
3667 if let Some(bracket_start) = tag_part.find('[') {
3668 let tag = tag_part[..bracket_start].to_uppercase();
3669 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3670 if let Some(comma_pos) = inner.find(',') {
3671 let qualifier = &inner[..comma_pos];
3672 let index = inner[comma_pos + 1..].parse::<usize>().unwrap_or(0);
3673 if qualifier == "*" {
3675 (tag, None, index)
3676 } else {
3677 (tag, Some(qualifier), index)
3678 }
3679 } else {
3680 (tag, Some(inner), 0)
3681 }
3682 } else {
3683 (tag_part.to_uppercase(), None, 0)
3684 }
3685}
3686
3687fn inject_bo4e_metadata(mut value: serde_json::Value, bo4e_type: &str) -> serde_json::Value {
3692 match &mut value {
3693 serde_json::Value::Object(map) => {
3694 map.entry("boTyp")
3695 .or_insert_with(|| serde_json::Value::String(bo4e_type.to_uppercase()));
3696 map.entry("versionStruktur")
3697 .or_insert_with(|| serde_json::Value::String("1".to_string()));
3698 }
3699 serde_json::Value::Array(items) => {
3700 for item in items {
3701 if let serde_json::Value::Object(map) = item {
3702 map.entry("boTyp")
3703 .or_insert_with(|| serde_json::Value::String(bo4e_type.to_uppercase()));
3704 map.entry("versionStruktur")
3705 .or_insert_with(|| serde_json::Value::String("1".to_string()));
3706 }
3707 }
3708 }
3709 _ => {}
3710 }
3711 value
3712}
3713
3714pub fn deep_merge_insert(
3720 result: &mut serde_json::Map<String, serde_json::Value>,
3721 entity: &str,
3722 bo4e: serde_json::Value,
3723) {
3724 if let Some(existing) = result.get_mut(entity) {
3725 if let (Some(existing_arr), Some(new_arr)) =
3728 (existing.as_array().map(|a| a.len()), bo4e.as_array())
3729 {
3730 if existing_arr == new_arr.len() {
3731 let existing_arr = existing.as_array_mut().unwrap();
3732 for (existing_elem, new_elem) in existing_arr.iter_mut().zip(new_arr) {
3733 if let (Some(existing_map), Some(new_map)) =
3734 (existing_elem.as_object_mut(), new_elem.as_object())
3735 {
3736 for (k, v) in new_map {
3737 if let Some(existing_v) = existing_map.get_mut(k) {
3738 if let (Some(existing_inner), Some(new_inner)) =
3739 (existing_v.as_object_mut(), v.as_object())
3740 {
3741 for (ik, iv) in new_inner {
3742 existing_inner
3743 .entry(ik.clone())
3744 .or_insert_with(|| iv.clone());
3745 }
3746 }
3747 } else {
3748 existing_map.insert(k.clone(), v.clone());
3749 }
3750 }
3751 }
3752 }
3753 return;
3754 }
3755 }
3756 if let (Some(existing_map), serde_json::Value::Object(new_map)) =
3758 (existing.as_object_mut(), &bo4e)
3759 {
3760 for (k, v) in new_map {
3761 if let Some(existing_v) = existing_map.get_mut(k) {
3762 if let (Some(existing_inner), Some(new_inner)) =
3764 (existing_v.as_object_mut(), v.as_object())
3765 {
3766 for (ik, iv) in new_inner {
3767 existing_inner
3768 .entry(ik.clone())
3769 .or_insert_with(|| iv.clone());
3770 }
3771 }
3772 } else {
3774 existing_map.insert(k.clone(), v.clone());
3775 }
3776 }
3777 return;
3778 }
3779 }
3780 result.insert(entity.to_string(), bo4e);
3781}
3782
3783fn is_map_keyed_object(value: &serde_json::Value) -> bool {
3794 let Some(obj) = value.as_object() else {
3795 return false;
3796 };
3797 if obj.is_empty() {
3798 return false;
3799 }
3800 obj.iter().all(|(k, v)| {
3802 k.len() <= 5
3803 && k.chars()
3804 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
3805 && v.is_object()
3806 })
3807}
3808
3809fn find_qualifier_companion_field(
3818 definitions: &[crate::definition::MappingDefinition],
3819 entity: &str,
3820) -> Option<String> {
3821 for def in definitions {
3822 if def.meta.entity != *entity {
3823 continue;
3824 }
3825 let disc = def.meta.discriminator.as_deref()?;
3826 let (disc_path, _) = disc.split_once('=')?;
3827 let disc_path_lower = disc_path.to_lowercase();
3828
3829 let sections: Vec<&indexmap::IndexMap<String, FieldMapping>> =
3832 [def.companion_fields.as_ref(), Some(&def.fields)]
3833 .into_iter()
3834 .flatten()
3835 .collect();
3836
3837 for section in sections {
3838 for (path, mapping) in section {
3839 let cf_path = path.to_lowercase();
3840 let matches =
3841 cf_path == disc_path_lower || format!("{}.0", cf_path) == disc_path_lower;
3842 if matches {
3843 let target = match mapping {
3844 FieldMapping::Simple(t) => t.as_str(),
3845 FieldMapping::Structured(s) => s.target.as_str(),
3846 FieldMapping::Nested(_) => continue,
3847 };
3848 if !target.is_empty() {
3849 return Some(target.to_string());
3850 }
3851 }
3852 }
3853 }
3854 }
3855 None
3856}
3857
3858fn extract_child_from_parent(
3867 entities: &serde_json::Value,
3868 definitions: &[MappingDefinition],
3869 child_def: &MappingDefinition,
3870) -> Option<serde_json::Value> {
3871 extract_child_from_parent_with_indices(entities, definitions, child_def).map(|(v, _)| v)
3872}
3873
3874fn extract_child_from_parent_with_indices(
3879 entities: &serde_json::Value,
3880 definitions: &[MappingDefinition],
3881 child_def: &MappingDefinition,
3882) -> Option<(serde_json::Value, Vec<usize>)> {
3883 let parts: Vec<&str> = child_def.meta.source_group.split('.').collect();
3884 if parts.len() < 2 {
3885 return None;
3886 }
3887 let parent_group = parts[0];
3888 let parent_def = definitions
3889 .iter()
3890 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_def.meta.entity)?;
3891 let parent_key = to_camel_case(&parent_def.meta.entity);
3892 let child_key = to_camel_case(&child_def.meta.entity);
3893 let parent_value = entities.get(&parent_key)?;
3894
3895 if let Some(parent_map) = parent_value.as_object() {
3897 if is_map_keyed_value(parent_map) {
3898 let mut children: Vec<serde_json::Value> = Vec::new();
3899 let mut indices: Vec<usize> = Vec::new();
3900 for (i, (_key, inner)) in parent_map.iter().enumerate() {
3901 if let Some(child) = inner.get(&child_key) {
3902 if !child.is_null() {
3903 children.push(child.clone());
3904 indices.push(i);
3905 }
3906 }
3907 }
3908 return match children.len() {
3909 0 => None,
3910 1 => Some((children.into_iter().next().unwrap(), indices)),
3911 _ => Some((serde_json::Value::Array(children), indices)),
3912 };
3913 }
3914 }
3915
3916 if let Some(parent_arr) = parent_value.as_array() {
3918 let mut children: Vec<serde_json::Value> = Vec::new();
3919 let mut indices: Vec<usize> = Vec::new();
3920 for (i, item) in parent_arr.iter().enumerate() {
3921 if let Some(child) = item.get(&child_key) {
3922 if !child.is_null() {
3923 children.push(child.clone());
3924 indices.push(i);
3925 }
3926 }
3927 }
3928 return match children.len() {
3929 0 => None,
3930 1 => Some((children.into_iter().next().unwrap(), indices)),
3931 _ => Some((serde_json::Value::Array(children), indices)),
3932 };
3933 }
3934
3935 let child = parent_value.get(&child_key)?;
3937 if child.is_null() {
3938 return None;
3939 }
3940 Some((child.clone(), vec![0]))
3941}
3942
3943fn nest_child_entities_in_result(
3949 result: &mut serde_json::Map<String, serde_json::Value>,
3950 definitions: &[MappingDefinition],
3951 nesting_info: &std::collections::HashMap<String, Vec<usize>>,
3952 transaction_group: Option<&str>,
3953) {
3954 let mut nesting_pairs: Vec<(String, String, String, Option<String>)> = Vec::new();
3957 for def in definitions {
3958 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
3959 if parts.len() < 2 {
3960 continue;
3961 }
3962 let parent_group = parts[0];
3963 if transaction_group.is_some_and(|tx| tx == parent_group) {
3969 continue;
3970 }
3971 let child_entity = def.meta.entity.clone();
3972 let child_has_parent_level_def = definitions
3976 .iter()
3977 .any(|d| d.meta.source_group == parent_group && d.meta.entity == child_entity);
3978 if child_has_parent_level_def {
3979 continue;
3980 }
3981 let parent_entity = definitions
3983 .iter()
3984 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_entity)
3985 .map(|d| d.meta.entity.clone());
3986 if let Some(ref parent_entity) = parent_entity {
3987 let child_key_lc = to_camel_case(&child_entity);
3992 let parent_defs: Vec<_> = definitions
3993 .iter()
3994 .filter(|d| d.meta.entity == *parent_entity)
3995 .collect();
3996 let has_conflicting_field = parent_defs.iter().any(|pd| {
3997 pd.fields.values().any(|fm| {
3998 let target = match fm {
3999 crate::definition::FieldMapping::Simple(t) => t.as_str(),
4000 crate::definition::FieldMapping::Structured(s) => s.target.as_str(),
4001 crate::definition::FieldMapping::Nested(_) => "",
4002 };
4003 target.starts_with(&child_key_lc)
4004 && target.get(child_key_lc.len()..child_key_lc.len() + 1) == Some(".")
4005 })
4006 });
4007 if has_conflicting_field {
4008 continue;
4009 }
4010 if nesting_pairs
4012 .iter()
4013 .any(|(_, pe, ce, _)| *pe == *parent_entity && *ce == child_entity)
4014 {
4015 continue;
4016 }
4017 nesting_pairs.push((
4018 parent_group.to_string(),
4019 parent_entity.clone(),
4020 child_entity,
4021 def.meta.source_path.clone(),
4022 ));
4023 }
4024 }
4025
4026 for (_parent_group, parent_entity, child_entity, child_source_path) in nesting_pairs {
4027 let parent_key = to_camel_case(&parent_entity);
4028 let child_key = to_camel_case(&child_entity);
4029
4030 let child_value = match result.remove(&child_key) {
4032 Some(v) => v,
4033 None => continue,
4034 };
4035
4036 let Some(parent_value) = result.get_mut(&parent_key) else {
4041 result.insert(child_key, child_value);
4043 continue;
4044 };
4045 if parent_value.is_array() {
4046 result.insert(child_key, child_value);
4047 continue;
4048 }
4049
4050 let distribution = child_source_path
4052 .as_deref()
4053 .and_then(|sp| nesting_info.get(sp));
4054
4055 let child_items: Vec<(usize, &serde_json::Value)> = match &child_value {
4057 serde_json::Value::Array(arr) => arr.iter().enumerate().collect(),
4058 other => vec![(0, other)],
4059 };
4060
4061 let insert_or_append = |obj: &mut serde_json::Map<String, serde_json::Value>,
4064 key: &str,
4065 val: &serde_json::Value| {
4066 match obj.get_mut(key) {
4067 Some(existing) => {
4068 if !existing.is_array() {
4070 let prev = existing.take();
4071 *existing = serde_json::Value::Array(vec![prev]);
4072 }
4073 if let Some(arr) = existing.as_array_mut() {
4074 arr.push(val.clone());
4075 }
4076 }
4077 None => {
4078 obj.insert(key.to_string(), val.clone());
4079 }
4080 }
4081 };
4082
4083 if let Some(parent_map) = parent_value.as_object_mut() {
4085 if is_map_keyed_value(parent_map) {
4086 let keys: Vec<String> = parent_map.keys().cloned().collect();
4088 for (i, child_item) in &child_items {
4089 let target_idx = distribution
4090 .and_then(|dist| dist.get(*i))
4091 .copied()
4092 .unwrap_or(0);
4093 if let Some(key) = keys.get(target_idx) {
4094 if let Some(inner) = parent_map.get_mut(key).and_then(|v| v.as_object_mut())
4095 {
4096 insert_or_append(inner, &child_key, child_item);
4097 }
4098 }
4099 }
4100 continue;
4101 }
4102 }
4103
4104 if let Some(parent_arr) = parent_value.as_array_mut() {
4106 for (i, child_item) in &child_items {
4107 let target_idx = distribution
4108 .and_then(|dist| dist.get(*i))
4109 .copied()
4110 .unwrap_or(0);
4111 if let Some(parent_obj) = parent_arr
4112 .get_mut(target_idx)
4113 .and_then(|v| v.as_object_mut())
4114 {
4115 insert_or_append(parent_obj, &child_key, child_item);
4116 }
4117 }
4118 continue;
4119 }
4120
4121 if let Some(parent_obj) = parent_value.as_object_mut() {
4123 for (_i, child_item) in &child_items {
4124 insert_or_append(parent_obj, &child_key, child_item);
4125 }
4126 continue;
4127 }
4128
4129 result.insert(child_key, child_value);
4131 }
4132}
4133
4134pub use crate::dp_routing::{route_nad_dp_to_lokation, unroute_lokation_to_nad_dp, DpRouting};
4136
4137fn is_map_keyed_value(map: &serde_json::Map<String, serde_json::Value>) -> bool {
4139 if map.is_empty() {
4140 return false;
4141 }
4142 map.values().all(|v| v.is_object())
4143 && map.keys().all(|k| {
4144 k.len() <= 5
4145 || k.chars()
4146 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
4147 })
4148}
4149
4150fn to_camel_case(name: &str) -> String {
4151 let mut chars = name.chars();
4152 match chars.next() {
4153 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
4154 None => String::new(),
4155 }
4156}
4157
4158fn set_nested_value(map: &mut serde_json::Map<String, serde_json::Value>, path: &str, val: String) {
4161 set_nested_value_json(map, path, serde_json::Value::String(val));
4162}
4163
4164fn set_nested_value_json(
4166 map: &mut serde_json::Map<String, serde_json::Value>,
4167 path: &str,
4168 val: serde_json::Value,
4169) {
4170 if let Some((prefix, leaf)) = path.rsplit_once('.') {
4171 let mut current = map;
4172 for part in prefix.split('.') {
4173 let entry = current
4174 .entry(part.to_string())
4175 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
4176 current = entry.as_object_mut().expect("expected object in path");
4177 }
4178 current.insert(leaf.to_string(), val);
4179 } else {
4180 map.insert(path.to_string(), val);
4181 }
4182}
4183
4184#[derive(serde::Serialize, serde::Deserialize)]
4189pub struct VariantCache {
4190 pub message_defs: Vec<MappingDefinition>,
4192 pub transaction_defs: HashMap<String, Vec<MappingDefinition>>,
4194 pub combined_defs: HashMap<String, Vec<MappingDefinition>>,
4196 #[serde(default)]
4198 pub code_lookups: HashMap<String, crate::code_lookup::CodeLookup>,
4199 #[serde(default)]
4201 pub mig_schema: Option<mig_types::schema::mig::MigSchema>,
4202 #[serde(default)]
4204 pub segment_structure: Option<crate::segment_structure::SegmentStructure>,
4205 #[serde(default)]
4208 pub pid_segment_numbers: HashMap<String, Vec<String>>,
4209 #[serde(default)]
4212 pub pid_requirements: HashMap<String, crate::pid_requirements::PidRequirements>,
4213 #[serde(default)]
4217 pub tx_groups: HashMap<String, String>,
4218}
4219
4220impl VariantCache {
4221 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
4223 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
4224 path: path.display().to_string(),
4225 message: e.to_string(),
4226 })?;
4227 if let Some(parent) = path.parent() {
4228 std::fs::create_dir_all(parent)?;
4229 }
4230 std::fs::write(path, encoded)?;
4231 Ok(())
4232 }
4233
4234 pub fn load(path: &Path) -> Result<Self, MappingError> {
4236 let bytes = std::fs::read(path)?;
4237 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
4238 path: path.display().to_string(),
4239 message: e.to_string(),
4240 })
4241 }
4242
4243 pub fn tx_group(&self, pid: &str) -> Option<&str> {
4247 self.tx_groups
4248 .get(&format!("pid_{pid}"))
4249 .map(|s| s.as_str())
4250 }
4251
4252 pub fn msg_engine(&self, pid: &str) -> MappingEngine {
4256 let mut eng = MappingEngine::from_definitions(self.message_defs.clone()).with_pid(pid);
4257 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
4258 eng = eng.with_code_lookup(cl.clone());
4259 }
4260 eng
4261 }
4262
4263 pub fn tx_engine(&self, pid: &str) -> Option<MappingEngine> {
4267 self.transaction_defs
4268 .get(&format!("pid_{pid}"))
4269 .map(|defs| {
4270 let mut eng = MappingEngine::from_definitions(defs.clone()).with_pid(pid);
4271 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
4272 eng = eng.with_code_lookup(cl.clone());
4273 }
4274 eng
4275 })
4276 }
4277
4278 pub fn filtered_mig(&self, pid: &str) -> Option<mig_types::schema::mig::MigSchema> {
4286 let mig = self.mig_schema.as_ref()?;
4287 let numbers = self
4288 .pid_segment_numbers
4289 .get(&format!("pid_{pid}"))
4290 .or_else(|| self.pid_segment_numbers.get("pid_"))?;
4291 let number_set: std::collections::HashSet<String> = numbers.iter().cloned().collect();
4292 Some(mig_assembly::pid_filter::filter_mig_for_pid(
4293 mig,
4294 &number_set,
4295 ))
4296 }
4297}
4298
4299#[derive(serde::Serialize, serde::Deserialize)]
4304pub struct DataBundle {
4305 pub format_version: String,
4306 pub bundle_version: u32,
4307 pub variants: HashMap<String, VariantCache>,
4308}
4309
4310impl DataBundle {
4311 pub const CURRENT_VERSION: u32 = 2;
4312
4313 pub fn variant(&self, name: &str) -> Option<&VariantCache> {
4314 self.variants.get(name)
4315 }
4316
4317 pub fn write_to<W: std::io::Write>(&self, writer: &mut W) -> Result<(), MappingError> {
4318 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
4319 path: "<stream>".to_string(),
4320 message: e.to_string(),
4321 })?;
4322 writer.write_all(&encoded).map_err(MappingError::Io)
4323 }
4324
4325 pub fn read_from<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
4326 let mut bytes = Vec::new();
4327 reader.read_to_end(&mut bytes).map_err(MappingError::Io)?;
4328 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
4329 path: "<stream>".to_string(),
4330 message: e.to_string(),
4331 })
4332 }
4333
4334 pub fn read_from_checked<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
4335 let bundle = Self::read_from(reader)?;
4336 if bundle.bundle_version != Self::CURRENT_VERSION {
4337 return Err(MappingError::CacheRead {
4338 path: "<stream>".to_string(),
4339 message: format!(
4340 "Incompatible bundle version {}, expected version {}. \
4341 Run `edifact-data update` to fetch compatible bundles.",
4342 bundle.bundle_version,
4343 Self::CURRENT_VERSION
4344 ),
4345 });
4346 }
4347 Ok(bundle)
4348 }
4349
4350 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
4351 if let Some(parent) = path.parent() {
4352 std::fs::create_dir_all(parent)?;
4353 }
4354 let mut file = std::fs::File::create(path).map_err(MappingError::Io)?;
4355 self.write_to(&mut file)
4356 }
4357
4358 pub fn load(path: &Path) -> Result<Self, MappingError> {
4359 let mut file = std::fs::File::open(path).map_err(MappingError::Io)?;
4360 Self::read_from_checked(&mut file)
4361 }
4362}
4363
4364fn sort_variant_reps_by_mig(
4377 child_groups: &mut [AssembledGroup],
4378 mig: &MigSchema,
4379 transaction_group: &str,
4380) {
4381 let tx_def = match mig
4382 .segment_groups
4383 .iter()
4384 .find(|sg| sg.id == transaction_group)
4385 {
4386 Some(d) => d,
4387 None => return,
4388 };
4389
4390 for cg in child_groups.iter_mut() {
4391 if cg.repetitions.len() <= 1 {
4392 continue;
4393 }
4394
4395 let variant_defs: Vec<(usize, &mig_types::schema::mig::MigSegmentGroup)> = tx_def
4397 .nested_groups
4398 .iter()
4399 .enumerate()
4400 .filter(|(_, ng)| ng.id == cg.group_id && ng.variant_code.is_some())
4401 .collect();
4402
4403 if variant_defs.is_empty() {
4404 continue;
4405 }
4406
4407 cg.repetitions.sort_by_key(|rep| {
4410 let entry_seg = rep.segments.first();
4411 for &(mig_pos, variant_def) in &variant_defs {
4412 let (ei, ci) = variant_def.variant_qualifier_position.unwrap_or((0, 0));
4413 let actual_qual = entry_seg
4414 .and_then(|s| s.elements.get(ei))
4415 .and_then(|e| e.get(ci))
4416 .map(|s| s.as_str())
4417 .unwrap_or("");
4418 let matches = if !variant_def.variant_codes.is_empty() {
4419 variant_def
4420 .variant_codes
4421 .iter()
4422 .any(|c| actual_qual.eq_ignore_ascii_case(c))
4423 } else if let Some(ref expected_code) = variant_def.variant_code {
4424 actual_qual.eq_ignore_ascii_case(expected_code)
4425 } else {
4426 false
4427 };
4428 if matches {
4429 return mig_pos;
4430 }
4431 }
4432 usize::MAX });
4434 }
4435}
4436
4437#[cfg(test)]
4438mod variant_cache_helper_tests {
4439 use super::*;
4440
4441 fn make_test_cache() -> VariantCache {
4442 let mut tx_groups = HashMap::new();
4443 tx_groups.insert("pid_55001".to_string(), "SG4".to_string());
4444 tx_groups.insert("pid_21007".to_string(), "SG14".to_string());
4445
4446 let mut transaction_defs = HashMap::new();
4447 transaction_defs.insert("pid_55001".to_string(), vec![]);
4448 transaction_defs.insert("pid_21007".to_string(), vec![]);
4449
4450 VariantCache {
4451 message_defs: vec![],
4452 transaction_defs,
4453 combined_defs: HashMap::new(),
4454 code_lookups: HashMap::new(),
4455 mig_schema: None,
4456 segment_structure: None,
4457 pid_segment_numbers: HashMap::new(),
4458 pid_requirements: HashMap::new(),
4459 tx_groups,
4460 }
4461 }
4462
4463 #[test]
4464 fn test_tx_group_returns_correct_group() {
4465 let vc = make_test_cache();
4466 assert_eq!(vc.tx_group("55001").unwrap(), "SG4");
4467 assert_eq!(vc.tx_group("21007").unwrap(), "SG14");
4468 }
4469
4470 #[test]
4471 fn test_tx_group_unknown_pid_returns_none() {
4472 let vc = make_test_cache();
4473 assert!(vc.tx_group("99999").is_none());
4474 }
4475
4476 #[test]
4477 fn test_msg_engine_returns_engine() {
4478 let vc = make_test_cache();
4479 let engine = vc.msg_engine("55001");
4480 assert_eq!(engine.definitions().len(), 0);
4481 }
4482
4483 #[test]
4484 fn test_tx_engine_returns_engine_for_known_pid() {
4485 let vc = make_test_cache();
4486 assert!(vc.tx_engine("55001").is_some());
4487 }
4488
4489 #[test]
4490 fn test_tx_engine_returns_none_for_unknown_pid() {
4491 let vc = make_test_cache();
4492 assert!(vc.tx_engine("99999").is_none());
4493 }
4494}
4495
4496#[cfg(test)]
4497mod tests {
4498 use super::*;
4499 use crate::definition::{MappingDefinition, MappingMeta, StructuredFieldMapping};
4500 use indexmap::IndexMap;
4501
4502 fn make_def(fields: IndexMap<String, FieldMapping>) -> MappingDefinition {
4503 MappingDefinition {
4504 meta: MappingMeta {
4505 entity: "Test".to_string(),
4506 bo4e_type: "Test".to_string(),
4507 companion_type: None,
4508 source_group: "SG4".to_string(),
4509 source_path: None,
4510 discriminator: None,
4511 repeat_on_tag: None,
4512 },
4513 fields,
4514 companion_fields: None,
4515 complex_handlers: None,
4516 }
4517 }
4518
4519 #[test]
4520 fn test_map_interchange_single_transaction_backward_compat() {
4521 use mig_assembly::assembler::*;
4522
4523 let tree = AssembledTree {
4525 segments: vec![
4526 AssembledSegment {
4527 tag: "UNH".to_string(),
4528 elements: vec![vec!["001".to_string()]],
4529 mig_number: None,
4530 },
4531 AssembledSegment {
4532 tag: "BGM".to_string(),
4533 elements: vec![vec!["E01".to_string()], vec!["DOC001".to_string()]],
4534 mig_number: None,
4535 },
4536 ],
4537 groups: vec![
4538 AssembledGroup {
4539 group_id: "SG2".to_string(),
4540 repetitions: vec![AssembledGroupInstance {
4541 segments: vec![AssembledSegment {
4542 tag: "NAD".to_string(),
4543 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4544 mig_number: None,
4545 }],
4546 child_groups: vec![],
4547 entry_mig_number: None,
4548 variant_mig_numbers: vec![],
4549 skipped_segments: vec![],
4550 skipped_positions: Vec::new(),
4551 }],
4552 },
4553 AssembledGroup {
4554 group_id: "SG4".to_string(),
4555 repetitions: vec![AssembledGroupInstance {
4556 segments: vec![AssembledSegment {
4557 tag: "IDE".to_string(),
4558 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4559 mig_number: None,
4560 }],
4561 child_groups: vec![AssembledGroup {
4562 group_id: "SG5".to_string(),
4563 repetitions: vec![AssembledGroupInstance {
4564 segments: vec![AssembledSegment {
4565 tag: "LOC".to_string(),
4566 elements: vec![
4567 vec!["Z16".to_string()],
4568 vec!["DE000111222333".to_string()],
4569 ],
4570 mig_number: None,
4571 }],
4572 child_groups: vec![],
4573 entry_mig_number: None,
4574 variant_mig_numbers: vec![],
4575 skipped_segments: vec![],
4576 skipped_positions: Vec::new(),
4577 }],
4578 }],
4579 entry_mig_number: None,
4580 variant_mig_numbers: vec![],
4581 skipped_segments: vec![],
4582 skipped_positions: Vec::new(),
4583 }],
4584 },
4585 ],
4586 post_group_start: 2,
4587 inter_group_segments: std::collections::BTreeMap::new(),
4588 };
4589
4590 let msg_engine = MappingEngine::from_definitions(vec![]);
4592
4593 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4595 tx_fields.insert(
4596 "ide.1".to_string(),
4597 FieldMapping::Simple("vorgangId".to_string()),
4598 );
4599 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4600 malo_fields.insert(
4601 "loc.1".to_string(),
4602 FieldMapping::Simple("marktlokationsId".to_string()),
4603 );
4604
4605 let tx_engine = MappingEngine::from_definitions(vec![
4606 MappingDefinition {
4607 meta: MappingMeta {
4608 entity: "Prozessdaten".to_string(),
4609 bo4e_type: "Prozessdaten".to_string(),
4610 companion_type: None,
4611 source_group: "SG4".to_string(),
4612 source_path: None,
4613 discriminator: None,
4614 repeat_on_tag: None,
4615 },
4616 fields: tx_fields,
4617 companion_fields: None,
4618 complex_handlers: None,
4619 },
4620 MappingDefinition {
4621 meta: MappingMeta {
4622 entity: "Marktlokation".to_string(),
4623 bo4e_type: "Marktlokation".to_string(),
4624 companion_type: None,
4625 source_group: "SG4.SG5".to_string(),
4626 source_path: None,
4627 discriminator: None,
4628 repeat_on_tag: None,
4629 },
4630 fields: malo_fields,
4631 companion_fields: None,
4632 complex_handlers: None,
4633 },
4634 ]);
4635
4636 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
4637
4638 assert_eq!(result.transaktionen.len(), 1);
4639 assert_eq!(
4640 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
4641 .as_str()
4642 .unwrap(),
4643 "TX001"
4644 );
4645 assert_eq!(
4648 result.transaktionen[0].stammdaten["marktlokation"]["marktlokationsId"]
4649 .as_str()
4650 .unwrap(),
4651 "DE000111222333"
4652 );
4653 }
4654
4655 #[test]
4656 fn test_map_reverse_pads_intermediate_empty_elements() {
4657 let mut fields = IndexMap::new();
4659 fields.insert(
4660 "nad.0".to_string(),
4661 FieldMapping::Structured(StructuredFieldMapping {
4662 target: String::new(),
4663 transform: None,
4664 when: None,
4665 default: Some("Z09".to_string()),
4666 enum_map: None,
4667 when_filled: None,
4668 also_target: None,
4669 also_enum_map: None,
4670 }),
4671 );
4672 fields.insert(
4673 "nad.3.0".to_string(),
4674 FieldMapping::Simple("name".to_string()),
4675 );
4676 fields.insert(
4677 "nad.3.1".to_string(),
4678 FieldMapping::Simple("vorname".to_string()),
4679 );
4680
4681 let def = make_def(fields);
4682 let engine = MappingEngine::from_definitions(vec![]);
4683
4684 let bo4e = serde_json::json!({
4685 "name": "Muster",
4686 "vorname": "Max"
4687 });
4688
4689 let instance = engine.map_reverse(&bo4e, &def);
4690 assert_eq!(instance.segments.len(), 1);
4691
4692 let nad = &instance.segments[0];
4693 assert_eq!(nad.tag, "NAD");
4694 assert_eq!(nad.elements.len(), 4);
4695 assert_eq!(nad.elements[0], vec!["Z09"]);
4696 assert_eq!(nad.elements[1], vec![""]);
4698 assert_eq!(nad.elements[2], vec![""]);
4699 assert_eq!(nad.elements[3][0], "Muster");
4700 assert_eq!(nad.elements[3][1], "Max");
4701 }
4702
4703 #[test]
4704 fn test_map_reverse_no_padding_when_contiguous() {
4705 let mut fields = IndexMap::new();
4707 fields.insert(
4708 "dtm.0.0".to_string(),
4709 FieldMapping::Structured(StructuredFieldMapping {
4710 target: String::new(),
4711 transform: None,
4712 when: None,
4713 default: Some("92".to_string()),
4714 enum_map: None,
4715 when_filled: None,
4716 also_target: None,
4717 also_enum_map: None,
4718 }),
4719 );
4720 fields.insert(
4721 "dtm.0.1".to_string(),
4722 FieldMapping::Simple("value".to_string()),
4723 );
4724 fields.insert(
4725 "dtm.0.2".to_string(),
4726 FieldMapping::Structured(StructuredFieldMapping {
4727 target: String::new(),
4728 transform: None,
4729 when: None,
4730 default: Some("303".to_string()),
4731 enum_map: None,
4732 when_filled: None,
4733 also_target: None,
4734 also_enum_map: None,
4735 }),
4736 );
4737
4738 let def = make_def(fields);
4739 let engine = MappingEngine::from_definitions(vec![]);
4740
4741 let bo4e = serde_json::json!({ "value": "20250531" });
4742
4743 let instance = engine.map_reverse(&bo4e, &def);
4744 let dtm = &instance.segments[0];
4745 assert_eq!(dtm.elements.len(), 1);
4747 assert_eq!(dtm.elements[0], vec!["92", "20250531", "303"]);
4748 }
4749
4750 #[test]
4751 fn test_map_message_level_extracts_sg2_only() {
4752 use mig_assembly::assembler::*;
4753
4754 let tree = AssembledTree {
4756 segments: vec![
4757 AssembledSegment {
4758 tag: "UNH".to_string(),
4759 elements: vec![vec!["001".to_string()]],
4760 mig_number: None,
4761 },
4762 AssembledSegment {
4763 tag: "BGM".to_string(),
4764 elements: vec![vec!["E01".to_string()]],
4765 mig_number: None,
4766 },
4767 ],
4768 groups: vec![
4769 AssembledGroup {
4770 group_id: "SG2".to_string(),
4771 repetitions: vec![AssembledGroupInstance {
4772 segments: vec![AssembledSegment {
4773 tag: "NAD".to_string(),
4774 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4775 mig_number: None,
4776 }],
4777 child_groups: vec![],
4778 entry_mig_number: None,
4779 variant_mig_numbers: vec![],
4780 skipped_segments: vec![],
4781 skipped_positions: Vec::new(),
4782 }],
4783 },
4784 AssembledGroup {
4785 group_id: "SG4".to_string(),
4786 repetitions: vec![AssembledGroupInstance {
4787 segments: vec![AssembledSegment {
4788 tag: "IDE".to_string(),
4789 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4790 mig_number: None,
4791 }],
4792 child_groups: vec![],
4793 entry_mig_number: None,
4794 variant_mig_numbers: vec![],
4795 skipped_segments: vec![],
4796 skipped_positions: Vec::new(),
4797 }],
4798 },
4799 ],
4800 post_group_start: 2,
4801 inter_group_segments: std::collections::BTreeMap::new(),
4802 };
4803
4804 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4806 msg_fields.insert(
4807 "nad.0".to_string(),
4808 FieldMapping::Simple("marktrolle".to_string()),
4809 );
4810 msg_fields.insert(
4811 "nad.1".to_string(),
4812 FieldMapping::Simple("rollencodenummer".to_string()),
4813 );
4814 let msg_def = MappingDefinition {
4815 meta: MappingMeta {
4816 entity: "Marktteilnehmer".to_string(),
4817 bo4e_type: "Marktteilnehmer".to_string(),
4818 companion_type: None,
4819 source_group: "SG2".to_string(),
4820 source_path: None,
4821 discriminator: None,
4822 repeat_on_tag: None,
4823 },
4824 fields: msg_fields,
4825 companion_fields: None,
4826 complex_handlers: None,
4827 };
4828
4829 let engine = MappingEngine::from_definitions(vec![msg_def.clone()]);
4830 let result = engine.map_all_forward(&tree);
4831
4832 assert!(result.get("marktteilnehmer").is_some());
4834 let mt = &result["marktteilnehmer"];
4835 assert_eq!(mt["marktrolle"].as_str().unwrap(), "MS");
4836 assert_eq!(mt["rollencodenummer"].as_str().unwrap(), "9900123");
4837 }
4838
4839 #[test]
4840 fn test_map_transaction_scoped_to_sg4_instance() {
4841 use mig_assembly::assembler::*;
4842
4843 let tree = AssembledTree {
4845 segments: vec![
4846 AssembledSegment {
4847 tag: "UNH".to_string(),
4848 elements: vec![vec!["001".to_string()]],
4849 mig_number: None,
4850 },
4851 AssembledSegment {
4852 tag: "BGM".to_string(),
4853 elements: vec![vec!["E01".to_string()]],
4854 mig_number: None,
4855 },
4856 ],
4857 groups: vec![AssembledGroup {
4858 group_id: "SG4".to_string(),
4859 repetitions: vec![AssembledGroupInstance {
4860 segments: vec![AssembledSegment {
4861 tag: "IDE".to_string(),
4862 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4863 mig_number: None,
4864 }],
4865 child_groups: vec![AssembledGroup {
4866 group_id: "SG5".to_string(),
4867 repetitions: vec![AssembledGroupInstance {
4868 segments: vec![AssembledSegment {
4869 tag: "LOC".to_string(),
4870 elements: vec![
4871 vec!["Z16".to_string()],
4872 vec!["DE000111222333".to_string()],
4873 ],
4874 mig_number: None,
4875 }],
4876 child_groups: vec![],
4877 entry_mig_number: None,
4878 variant_mig_numbers: vec![],
4879 skipped_segments: vec![],
4880 skipped_positions: Vec::new(),
4881 }],
4882 }],
4883 entry_mig_number: None,
4884 variant_mig_numbers: vec![],
4885 skipped_segments: vec![],
4886 skipped_positions: Vec::new(),
4887 }],
4888 }],
4889 post_group_start: 2,
4890 inter_group_segments: std::collections::BTreeMap::new(),
4891 };
4892
4893 let mut proz_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4895 proz_fields.insert(
4896 "ide.1".to_string(),
4897 FieldMapping::Simple("vorgangId".to_string()),
4898 );
4899 let proz_def = MappingDefinition {
4900 meta: MappingMeta {
4901 entity: "Prozessdaten".to_string(),
4902 bo4e_type: "Prozessdaten".to_string(),
4903 companion_type: None,
4904 source_group: "".to_string(), source_path: None,
4906 discriminator: None,
4907 repeat_on_tag: None,
4908 },
4909 fields: proz_fields,
4910 companion_fields: None,
4911 complex_handlers: None,
4912 };
4913
4914 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4915 malo_fields.insert(
4916 "loc.1".to_string(),
4917 FieldMapping::Simple("marktlokationsId".to_string()),
4918 );
4919 let malo_def = MappingDefinition {
4920 meta: MappingMeta {
4921 entity: "Marktlokation".to_string(),
4922 bo4e_type: "Marktlokation".to_string(),
4923 companion_type: None,
4924 source_group: "SG5".to_string(), source_path: None,
4926 discriminator: None,
4927 repeat_on_tag: None,
4928 },
4929 fields: malo_fields,
4930 companion_fields: None,
4931 complex_handlers: None,
4932 };
4933
4934 let tx_engine = MappingEngine::from_definitions(vec![proz_def, malo_def]);
4935
4936 let sg4 = &tree.groups[0]; let sg4_instance = &sg4.repetitions[0];
4939 let sub_tree = sg4_instance.as_assembled_tree();
4940
4941 let result = tx_engine.map_all_forward(&sub_tree);
4942
4943 assert_eq!(
4945 result["prozessdaten"]["vorgangId"].as_str().unwrap(),
4946 "TX001"
4947 );
4948
4949 assert_eq!(
4951 result["marktlokation"]["marktlokationsId"]
4952 .as_str()
4953 .unwrap(),
4954 "DE000111222333"
4955 );
4956 }
4957
4958 #[test]
4959 fn test_map_interchange_produces_full_hierarchy() {
4960 use mig_assembly::assembler::*;
4961
4962 let tree = AssembledTree {
4964 segments: vec![
4965 AssembledSegment {
4966 tag: "UNH".to_string(),
4967 elements: vec![vec!["001".to_string()]],
4968 mig_number: None,
4969 },
4970 AssembledSegment {
4971 tag: "BGM".to_string(),
4972 elements: vec![vec!["E01".to_string()]],
4973 mig_number: None,
4974 },
4975 ],
4976 groups: vec![
4977 AssembledGroup {
4978 group_id: "SG2".to_string(),
4979 repetitions: vec![AssembledGroupInstance {
4980 segments: vec![AssembledSegment {
4981 tag: "NAD".to_string(),
4982 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4983 mig_number: None,
4984 }],
4985 child_groups: vec![],
4986 entry_mig_number: None,
4987 variant_mig_numbers: vec![],
4988 skipped_segments: vec![],
4989 skipped_positions: Vec::new(),
4990 }],
4991 },
4992 AssembledGroup {
4993 group_id: "SG4".to_string(),
4994 repetitions: vec![
4995 AssembledGroupInstance {
4996 segments: vec![AssembledSegment {
4997 tag: "IDE".to_string(),
4998 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4999 mig_number: None,
5000 }],
5001 child_groups: vec![],
5002 entry_mig_number: None,
5003 variant_mig_numbers: vec![],
5004 skipped_segments: vec![],
5005 skipped_positions: Vec::new(),
5006 },
5007 AssembledGroupInstance {
5008 segments: vec![AssembledSegment {
5009 tag: "IDE".to_string(),
5010 elements: vec![vec!["24".to_string()], vec!["TX002".to_string()]],
5011 mig_number: None,
5012 }],
5013 child_groups: vec![],
5014 entry_mig_number: None,
5015 variant_mig_numbers: vec![],
5016 skipped_segments: vec![],
5017 skipped_positions: Vec::new(),
5018 },
5019 ],
5020 },
5021 ],
5022 post_group_start: 2,
5023 inter_group_segments: std::collections::BTreeMap::new(),
5024 };
5025
5026 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5028 msg_fields.insert(
5029 "nad.0".to_string(),
5030 FieldMapping::Simple("marktrolle".to_string()),
5031 );
5032 let msg_defs = vec![MappingDefinition {
5033 meta: MappingMeta {
5034 entity: "Marktteilnehmer".to_string(),
5035 bo4e_type: "Marktteilnehmer".to_string(),
5036 companion_type: None,
5037 source_group: "SG2".to_string(),
5038 source_path: None,
5039 discriminator: None,
5040 repeat_on_tag: None,
5041 },
5042 fields: msg_fields,
5043 companion_fields: None,
5044 complex_handlers: None,
5045 }];
5046
5047 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5049 tx_fields.insert(
5050 "ide.1".to_string(),
5051 FieldMapping::Simple("vorgangId".to_string()),
5052 );
5053 let tx_defs = vec![MappingDefinition {
5054 meta: MappingMeta {
5055 entity: "Prozessdaten".to_string(),
5056 bo4e_type: "Prozessdaten".to_string(),
5057 companion_type: None,
5058 source_group: "SG4".to_string(),
5059 source_path: None,
5060 discriminator: None,
5061 repeat_on_tag: None,
5062 },
5063 fields: tx_fields,
5064 companion_fields: None,
5065 complex_handlers: None,
5066 }];
5067
5068 let msg_engine = MappingEngine::from_definitions(msg_defs);
5069 let tx_engine = MappingEngine::from_definitions(tx_defs);
5070
5071 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
5072
5073 assert!(result.stammdaten["marktteilnehmer"].is_object());
5075 assert_eq!(
5076 result.stammdaten["marktteilnehmer"]["marktrolle"]
5077 .as_str()
5078 .unwrap(),
5079 "MS"
5080 );
5081
5082 assert_eq!(result.transaktionen.len(), 2);
5084 assert_eq!(
5085 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
5086 .as_str()
5087 .unwrap(),
5088 "TX001"
5089 );
5090 assert_eq!(
5091 result.transaktionen[1].stammdaten["prozessdaten"]["vorgangId"]
5092 .as_str()
5093 .unwrap(),
5094 "TX002"
5095 );
5096 }
5097
5098 #[test]
5099 fn test_map_reverse_with_segment_structure_pads_trailing() {
5100 let mut fields = IndexMap::new();
5102 fields.insert(
5103 "sts.0".to_string(),
5104 FieldMapping::Structured(StructuredFieldMapping {
5105 target: String::new(),
5106 transform: None,
5107 when: None,
5108 default: Some("7".to_string()),
5109 enum_map: None,
5110 when_filled: None,
5111 also_target: None,
5112 also_enum_map: None,
5113 }),
5114 );
5115 fields.insert(
5116 "sts.2".to_string(),
5117 FieldMapping::Simple("grund".to_string()),
5118 );
5119
5120 let def = make_def(fields);
5121
5122 let mut counts = std::collections::HashMap::new();
5124 counts.insert("STS".to_string(), 5usize);
5125 let ss = SegmentStructure {
5126 element_counts: counts,
5127 };
5128
5129 let engine = MappingEngine::from_definitions(vec![]).with_segment_structure(ss);
5130
5131 let bo4e = serde_json::json!({ "grund": "E01" });
5132
5133 let instance = engine.map_reverse(&bo4e, &def);
5134 let sts = &instance.segments[0];
5135 assert_eq!(sts.elements.len(), 5);
5138 assert_eq!(sts.elements[0], vec!["7"]);
5139 assert_eq!(sts.elements[1], vec![""]);
5140 assert_eq!(sts.elements[2], vec!["E01"]);
5141 assert_eq!(sts.elements[3], vec![""]);
5142 assert_eq!(sts.elements[4], vec![""]);
5143 }
5144
5145 #[test]
5146 fn test_extract_companion_fields_with_code_enrichment() {
5147 use crate::code_lookup::CodeLookup;
5148 use mig_assembly::assembler::*;
5149
5150 let schema = serde_json::json!({
5151 "fields": {
5152 "sg4": {
5153 "children": {
5154 "sg8_z01": {
5155 "children": {
5156 "sg10": {
5157 "segments": [{
5158 "id": "CCI",
5159 "elements": [{
5160 "index": 2,
5161 "components": [{
5162 "sub_index": 0,
5163 "type": "code",
5164 "codes": [
5165 {"value": "Z15", "name": "Haushaltskunde"},
5166 {"value": "Z18", "name": "Kein Haushaltskunde"}
5167 ]
5168 }]
5169 }]
5170 }],
5171 "source_group": "SG10"
5172 }
5173 },
5174 "segments": [],
5175 "source_group": "SG8"
5176 }
5177 },
5178 "segments": [],
5179 "source_group": "SG4"
5180 }
5181 }
5182 });
5183
5184 let code_lookup = CodeLookup::from_schema_value(&schema);
5185
5186 let tree = AssembledTree {
5187 segments: vec![],
5188 groups: vec![AssembledGroup {
5189 group_id: "SG4".to_string(),
5190 repetitions: vec![AssembledGroupInstance {
5191 segments: vec![],
5192 child_groups: vec![AssembledGroup {
5193 group_id: "SG8".to_string(),
5194 repetitions: vec![AssembledGroupInstance {
5195 segments: vec![],
5196 child_groups: vec![AssembledGroup {
5197 group_id: "SG10".to_string(),
5198 repetitions: vec![AssembledGroupInstance {
5199 segments: vec![AssembledSegment {
5200 tag: "CCI".to_string(),
5201 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
5202 mig_number: None,
5203 }],
5204 child_groups: vec![],
5205 entry_mig_number: None,
5206 variant_mig_numbers: vec![],
5207 skipped_segments: vec![],
5208 skipped_positions: Vec::new(),
5209 }],
5210 }],
5211 entry_mig_number: None,
5212 variant_mig_numbers: vec![],
5213 skipped_segments: vec![],
5214 skipped_positions: Vec::new(),
5215 }],
5216 }],
5217 entry_mig_number: None,
5218 variant_mig_numbers: vec![],
5219 skipped_segments: vec![],
5220 skipped_positions: Vec::new(),
5221 }],
5222 }],
5223 post_group_start: 0,
5224 inter_group_segments: std::collections::BTreeMap::new(),
5225 };
5226
5227 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5228 companion_fields.insert(
5229 "cci.2".to_string(),
5230 FieldMapping::Simple("haushaltskunde".to_string()),
5231 );
5232
5233 let def = MappingDefinition {
5234 meta: MappingMeta {
5235 entity: "Marktlokation".to_string(),
5236 bo4e_type: "Marktlokation".to_string(),
5237 companion_type: Some("MarktlokationEdifact".to_string()),
5238 source_group: "SG4.SG8.SG10".to_string(),
5239 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5240 discriminator: None,
5241 repeat_on_tag: None,
5242 },
5243 fields: IndexMap::new(),
5244 companion_fields: Some(companion_fields),
5245 complex_handlers: None,
5246 };
5247
5248 let engine_plain = MappingEngine::from_definitions(vec![]);
5250 let bo4e_plain = engine_plain.map_forward(&tree, &def, 0);
5251 assert_eq!(
5252 bo4e_plain["marktlokationEdifact"]["haushaltskunde"].as_str(),
5253 Some("Z15"),
5254 "Without code lookup, should be plain string"
5255 );
5256
5257 let engine_enriched = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
5259 let bo4e_enriched = engine_enriched.map_forward(&tree, &def, 0);
5260 let hk = &bo4e_enriched["marktlokationEdifact"]["haushaltskunde"];
5261 assert_eq!(hk["code"].as_str(), Some("Z15"));
5262 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
5263 assert!(hk.get("enum").is_none());
5265 }
5266
5267 #[test]
5268 fn test_extract_companion_fields_with_enum_enrichment() {
5269 use crate::code_lookup::CodeLookup;
5270 use mig_assembly::assembler::*;
5271
5272 let schema = serde_json::json!({
5274 "fields": {
5275 "sg4": {
5276 "children": {
5277 "sg8_z01": {
5278 "children": {
5279 "sg10": {
5280 "segments": [{
5281 "id": "CCI",
5282 "elements": [{
5283 "index": 2,
5284 "components": [{
5285 "sub_index": 0,
5286 "type": "code",
5287 "codes": [
5288 {"value": "Z15", "name": "Haushaltskunde", "enum": "HAUSHALTSKUNDE"},
5289 {"value": "Z18", "name": "Kein Haushaltskunde", "enum": "KEIN_HAUSHALTSKUNDE"}
5290 ]
5291 }]
5292 }]
5293 }],
5294 "source_group": "SG10"
5295 }
5296 },
5297 "segments": [],
5298 "source_group": "SG8"
5299 }
5300 },
5301 "segments": [],
5302 "source_group": "SG4"
5303 }
5304 }
5305 });
5306
5307 let code_lookup = CodeLookup::from_schema_value(&schema);
5308
5309 let tree = AssembledTree {
5310 segments: vec![],
5311 groups: vec![AssembledGroup {
5312 group_id: "SG4".to_string(),
5313 repetitions: vec![AssembledGroupInstance {
5314 segments: vec![],
5315 child_groups: vec![AssembledGroup {
5316 group_id: "SG8".to_string(),
5317 repetitions: vec![AssembledGroupInstance {
5318 segments: vec![],
5319 child_groups: vec![AssembledGroup {
5320 group_id: "SG10".to_string(),
5321 repetitions: vec![AssembledGroupInstance {
5322 segments: vec![AssembledSegment {
5323 tag: "CCI".to_string(),
5324 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
5325 mig_number: None,
5326 }],
5327 child_groups: vec![],
5328 entry_mig_number: None,
5329 variant_mig_numbers: vec![],
5330 skipped_segments: vec![],
5331 skipped_positions: Vec::new(),
5332 }],
5333 }],
5334 entry_mig_number: None,
5335 variant_mig_numbers: vec![],
5336 skipped_segments: vec![],
5337 skipped_positions: Vec::new(),
5338 }],
5339 }],
5340 entry_mig_number: None,
5341 variant_mig_numbers: vec![],
5342 skipped_segments: vec![],
5343 skipped_positions: Vec::new(),
5344 }],
5345 }],
5346 post_group_start: 0,
5347 inter_group_segments: std::collections::BTreeMap::new(),
5348 };
5349
5350 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5351 companion_fields.insert(
5352 "cci.2".to_string(),
5353 FieldMapping::Simple("haushaltskunde".to_string()),
5354 );
5355
5356 let def = MappingDefinition {
5357 meta: MappingMeta {
5358 entity: "Marktlokation".to_string(),
5359 bo4e_type: "Marktlokation".to_string(),
5360 companion_type: Some("MarktlokationEdifact".to_string()),
5361 source_group: "SG4.SG8.SG10".to_string(),
5362 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5363 discriminator: None,
5364 repeat_on_tag: None,
5365 },
5366 fields: IndexMap::new(),
5367 companion_fields: Some(companion_fields),
5368 complex_handlers: None,
5369 };
5370
5371 let engine = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
5372 let bo4e = engine.map_forward(&tree, &def, 0);
5373 let hk = &bo4e["marktlokationEdifact"]["haushaltskunde"];
5374 assert_eq!(hk["code"].as_str(), Some("Z15"));
5375 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
5376 assert_eq!(
5377 hk["enum"].as_str(),
5378 Some("HAUSHALTSKUNDE"),
5379 "enum field should be present"
5380 );
5381 }
5382
5383 #[test]
5384 fn test_reverse_mapping_accepts_enriched_with_enum() {
5385 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5387 companion_fields.insert(
5388 "cci.2".to_string(),
5389 FieldMapping::Simple("haushaltskunde".to_string()),
5390 );
5391
5392 let def = MappingDefinition {
5393 meta: MappingMeta {
5394 entity: "Test".to_string(),
5395 bo4e_type: "Test".to_string(),
5396 companion_type: Some("TestEdifact".to_string()),
5397 source_group: "SG4".to_string(),
5398 source_path: None,
5399 discriminator: None,
5400 repeat_on_tag: None,
5401 },
5402 fields: IndexMap::new(),
5403 companion_fields: Some(companion_fields),
5404 complex_handlers: None,
5405 };
5406
5407 let engine = MappingEngine::from_definitions(vec![]);
5408
5409 let bo4e = serde_json::json!({
5410 "testEdifact": {
5411 "haushaltskunde": {
5412 "code": "Z15",
5413 "meaning": "Haushaltskunde",
5414 "enum": "HAUSHALTSKUNDE"
5415 }
5416 }
5417 });
5418 let instance = engine.map_reverse(&bo4e, &def);
5419 assert_eq!(instance.segments[0].elements[2], vec!["Z15"]);
5420 }
5421
5422 #[test]
5423 fn test_reverse_mapping_accepts_enriched_companion() {
5424 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5426 companion_fields.insert(
5427 "cci.2".to_string(),
5428 FieldMapping::Simple("haushaltskunde".to_string()),
5429 );
5430
5431 let def = MappingDefinition {
5432 meta: MappingMeta {
5433 entity: "Test".to_string(),
5434 bo4e_type: "Test".to_string(),
5435 companion_type: Some("TestEdifact".to_string()),
5436 source_group: "SG4".to_string(),
5437 source_path: None,
5438 discriminator: None,
5439 repeat_on_tag: None,
5440 },
5441 fields: IndexMap::new(),
5442 companion_fields: Some(companion_fields),
5443 complex_handlers: None,
5444 };
5445
5446 let engine = MappingEngine::from_definitions(vec![]);
5447
5448 let bo4e_plain = serde_json::json!({
5450 "testEdifact": {
5451 "haushaltskunde": "Z15"
5452 }
5453 });
5454 let instance_plain = engine.map_reverse(&bo4e_plain, &def);
5455 assert_eq!(instance_plain.segments[0].elements[2], vec!["Z15"]);
5456
5457 let bo4e_enriched = serde_json::json!({
5459 "testEdifact": {
5460 "haushaltskunde": {
5461 "code": "Z15",
5462 "meaning": "Haushaltskunde gem. EnWG"
5463 }
5464 }
5465 });
5466 let instance_enriched = engine.map_reverse(&bo4e_enriched, &def);
5467 assert_eq!(instance_enriched.segments[0].elements[2], vec!["Z15"]);
5468 }
5469
5470 #[test]
5471 fn test_resolve_child_relative_with_source_path() {
5472 let mut map: std::collections::HashMap<String, Vec<usize>> =
5473 std::collections::HashMap::new();
5474 map.insert("sg4.sg8_ze1".to_string(), vec![6]);
5475 map.insert("sg4.sg8_z98".to_string(), vec![0]);
5476
5477 assert_eq!(
5479 resolve_child_relative("SG8.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5480 "SG8:6.SG10"
5481 );
5482
5483 assert_eq!(
5485 resolve_child_relative("SG8:3.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5486 "SG8:3.SG10"
5487 );
5488
5489 assert_eq!(
5491 resolve_child_relative("SG8.SG10", Some("sg4.sg8_unknown.sg10"), &map, 0),
5492 "SG8.SG10"
5493 );
5494
5495 assert_eq!(
5497 resolve_child_relative("SG8.SG10", None, &map, 0),
5498 "SG8.SG10"
5499 );
5500
5501 assert_eq!(
5503 resolve_child_relative("SG8.SG9", Some("sg4.sg8_z98.sg9"), &map, 0),
5504 "SG8:0.SG9"
5505 );
5506
5507 map.insert("sg4.sg8_zf3".to_string(), vec![3, 4]);
5509 assert_eq!(
5510 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 0),
5511 "SG8:3.SG10"
5512 );
5513 assert_eq!(
5514 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 1),
5515 "SG8:4.SG10"
5516 );
5517 }
5518
5519 #[test]
5520 fn test_place_in_groups_returns_rep_index() {
5521 let mut groups: Vec<AssembledGroup> = Vec::new();
5522
5523 let instance = AssembledGroupInstance {
5525 segments: vec![],
5526 child_groups: vec![],
5527 entry_mig_number: None,
5528 variant_mig_numbers: vec![],
5529 skipped_segments: vec![],
5530 skipped_positions: Vec::new(),
5531 };
5532 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 0);
5533
5534 let instance = AssembledGroupInstance {
5536 segments: vec![],
5537 child_groups: vec![],
5538 entry_mig_number: None,
5539 variant_mig_numbers: vec![],
5540 skipped_segments: vec![],
5541 skipped_positions: Vec::new(),
5542 };
5543 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 1);
5544
5545 let instance = AssembledGroupInstance {
5547 segments: vec![],
5548 child_groups: vec![],
5549 entry_mig_number: None,
5550 variant_mig_numbers: vec![],
5551 skipped_segments: vec![],
5552 skipped_positions: Vec::new(),
5553 };
5554 assert_eq!(place_in_groups(&mut groups, "SG8:5", instance), 5);
5555 }
5556
5557 #[test]
5558 fn test_resolve_by_source_path() {
5559 use mig_assembly::assembler::*;
5560
5561 let tree = AssembledTree {
5563 segments: vec![],
5564 groups: vec![AssembledGroup {
5565 group_id: "SG4".to_string(),
5566 repetitions: vec![AssembledGroupInstance {
5567 segments: vec![],
5568 child_groups: vec![AssembledGroup {
5569 group_id: "SG8".to_string(),
5570 repetitions: vec![
5571 AssembledGroupInstance {
5572 segments: vec![AssembledSegment {
5573 tag: "SEQ".to_string(),
5574 elements: vec![vec!["Z98".to_string()]],
5575 mig_number: None,
5576 }],
5577 child_groups: vec![AssembledGroup {
5578 group_id: "SG10".to_string(),
5579 repetitions: vec![AssembledGroupInstance {
5580 segments: vec![AssembledSegment {
5581 tag: "CCI".to_string(),
5582 elements: vec![vec![], vec![], vec!["ZB3".to_string()]],
5583 mig_number: None,
5584 }],
5585 child_groups: vec![],
5586 entry_mig_number: None,
5587 variant_mig_numbers: vec![],
5588 skipped_segments: vec![],
5589 skipped_positions: Vec::new(),
5590 }],
5591 }],
5592 entry_mig_number: None,
5593 variant_mig_numbers: vec![],
5594 skipped_segments: vec![],
5595 skipped_positions: Vec::new(),
5596 },
5597 AssembledGroupInstance {
5598 segments: vec![AssembledSegment {
5599 tag: "SEQ".to_string(),
5600 elements: vec![vec!["ZD7".to_string()]],
5601 mig_number: None,
5602 }],
5603 child_groups: vec![AssembledGroup {
5604 group_id: "SG10".to_string(),
5605 repetitions: vec![AssembledGroupInstance {
5606 segments: vec![AssembledSegment {
5607 tag: "CCI".to_string(),
5608 elements: vec![vec![], vec![], vec!["ZE6".to_string()]],
5609 mig_number: None,
5610 }],
5611 child_groups: vec![],
5612 entry_mig_number: None,
5613 variant_mig_numbers: vec![],
5614 skipped_segments: vec![],
5615 skipped_positions: Vec::new(),
5616 }],
5617 }],
5618 entry_mig_number: None,
5619 variant_mig_numbers: vec![],
5620 skipped_segments: vec![],
5621 skipped_positions: Vec::new(),
5622 },
5623 ],
5624 }],
5625 entry_mig_number: None,
5626 variant_mig_numbers: vec![],
5627 skipped_segments: vec![],
5628 skipped_positions: Vec::new(),
5629 }],
5630 }],
5631 post_group_start: 0,
5632 inter_group_segments: std::collections::BTreeMap::new(),
5633 };
5634
5635 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_z98.sg10");
5637 assert!(inst.is_some());
5638 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
5639
5640 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zd7.sg10");
5642 assert!(inst.is_some());
5643 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZE6");
5644
5645 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zzz.sg10");
5647 assert!(inst.is_none());
5648
5649 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8.sg10");
5651 assert!(inst.is_some());
5652 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
5653 }
5654
5655 #[test]
5656 fn test_parse_source_path_part() {
5657 assert_eq!(parse_source_path_part("sg4"), ("sg4", None));
5658 assert_eq!(parse_source_path_part("sg8_z98"), ("sg8", Some("z98")));
5659 assert_eq!(parse_source_path_part("sg10"), ("sg10", None));
5660 assert_eq!(parse_source_path_part("sg12_z04"), ("sg12", Some("z04")));
5661 }
5662
5663 #[test]
5664 fn test_has_source_path_qualifiers() {
5665 assert!(has_source_path_qualifiers("sg4.sg8_z98.sg10"));
5666 assert!(has_source_path_qualifiers("sg4.sg8_ze1.sg9"));
5667 assert!(!has_source_path_qualifiers("sg4.sg6"));
5668 assert!(!has_source_path_qualifiers("sg4.sg8.sg10"));
5669 }
5670
5671 #[test]
5672 fn test_companion_dotted_path_forward() {
5673 use mig_assembly::assembler::*;
5674
5675 let tree = AssembledTree {
5677 segments: vec![],
5678 groups: vec![AssembledGroup {
5679 group_id: "SG4".to_string(),
5680 repetitions: vec![AssembledGroupInstance {
5681 segments: vec![],
5682 child_groups: vec![AssembledGroup {
5683 group_id: "SG8".to_string(),
5684 repetitions: vec![AssembledGroupInstance {
5685 segments: vec![],
5686 child_groups: vec![AssembledGroup {
5687 group_id: "SG10".to_string(),
5688 repetitions: vec![AssembledGroupInstance {
5689 segments: vec![AssembledSegment {
5690 tag: "CCI".to_string(),
5691 elements: vec![
5692 vec!["11XAB-1234".to_string()],
5693 vec!["305".to_string()],
5694 ],
5695 mig_number: None,
5696 }],
5697 child_groups: vec![],
5698 entry_mig_number: None,
5699 variant_mig_numbers: vec![],
5700 skipped_segments: vec![],
5701 skipped_positions: Vec::new(),
5702 }],
5703 }],
5704 entry_mig_number: None,
5705 variant_mig_numbers: vec![],
5706 skipped_segments: vec![],
5707 skipped_positions: Vec::new(),
5708 }],
5709 }],
5710 entry_mig_number: None,
5711 variant_mig_numbers: vec![],
5712 skipped_segments: vec![],
5713 skipped_positions: Vec::new(),
5714 }],
5715 }],
5716 post_group_start: 0,
5717 inter_group_segments: std::collections::BTreeMap::new(),
5718 };
5719
5720 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5722 companion_fields.insert(
5723 "cci.0".to_string(),
5724 FieldMapping::Simple("bilanzkreis.id".to_string()),
5725 );
5726 companion_fields.insert(
5727 "cci.1".to_string(),
5728 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
5729 );
5730
5731 let def = MappingDefinition {
5732 meta: MappingMeta {
5733 entity: "Test".to_string(),
5734 bo4e_type: "Test".to_string(),
5735 companion_type: Some("TestEdifact".to_string()),
5736 source_group: "SG4.SG8.SG10".to_string(),
5737 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5738 discriminator: None,
5739 repeat_on_tag: None,
5740 },
5741 fields: IndexMap::new(),
5742 companion_fields: Some(companion_fields),
5743 complex_handlers: None,
5744 };
5745
5746 let engine = MappingEngine::from_definitions(vec![]);
5747 let bo4e = engine.map_forward(&tree, &def, 0);
5748
5749 let companion = &bo4e["testEdifact"];
5751 assert!(
5752 companion.is_object(),
5753 "testEdifact should be an object, got: {companion}"
5754 );
5755 let bilanzkreis = &companion["bilanzkreis"];
5756 assert!(
5757 bilanzkreis.is_object(),
5758 "bilanzkreis should be a nested object, got: {bilanzkreis}"
5759 );
5760 assert_eq!(
5761 bilanzkreis["id"].as_str(),
5762 Some("11XAB-1234"),
5763 "bilanzkreis.id should be 11XAB-1234"
5764 );
5765 assert_eq!(
5766 bilanzkreis["codelist"].as_str(),
5767 Some("305"),
5768 "bilanzkreis.codelist should be 305"
5769 );
5770 }
5771
5772 #[test]
5773 fn test_companion_dotted_path_reverse() {
5774 let engine = MappingEngine::from_definitions(vec![]);
5776
5777 let companion_value = serde_json::json!({
5778 "bilanzkreis": {
5779 "id": "11XAB-1234",
5780 "codelist": "305"
5781 }
5782 });
5783
5784 assert_eq!(
5785 engine.populate_field(&companion_value, "bilanzkreis.id"),
5786 Some("11XAB-1234".to_string()),
5787 "dotted path bilanzkreis.id should resolve"
5788 );
5789 assert_eq!(
5790 engine.populate_field(&companion_value, "bilanzkreis.codelist"),
5791 Some("305".to_string()),
5792 "dotted path bilanzkreis.codelist should resolve"
5793 );
5794
5795 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5797 companion_fields.insert(
5798 "cci.0".to_string(),
5799 FieldMapping::Simple("bilanzkreis.id".to_string()),
5800 );
5801 companion_fields.insert(
5802 "cci.1".to_string(),
5803 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
5804 );
5805
5806 let def = MappingDefinition {
5807 meta: MappingMeta {
5808 entity: "Test".to_string(),
5809 bo4e_type: "Test".to_string(),
5810 companion_type: Some("TestEdifact".to_string()),
5811 source_group: "SG4.SG8.SG10".to_string(),
5812 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5813 discriminator: None,
5814 repeat_on_tag: None,
5815 },
5816 fields: IndexMap::new(),
5817 companion_fields: Some(companion_fields),
5818 complex_handlers: None,
5819 };
5820
5821 let bo4e = serde_json::json!({
5822 "testEdifact": {
5823 "bilanzkreis": {
5824 "id": "11XAB-1234",
5825 "codelist": "305"
5826 }
5827 }
5828 });
5829
5830 let instance = engine.map_reverse(&bo4e, &def);
5831 assert_eq!(instance.segments.len(), 1, "should produce one CCI segment");
5832 let cci = &instance.segments[0];
5833 assert_eq!(cci.tag, "CCI");
5834 assert_eq!(
5835 cci.elements[0],
5836 vec!["11XAB-1234"],
5837 "element 0 should contain bilanzkreis.id"
5838 );
5839 assert_eq!(
5840 cci.elements[1],
5841 vec!["305"],
5842 "element 1 should contain bilanzkreis.codelist"
5843 );
5844 }
5845
5846 #[test]
5847 fn test_when_filled_injects_when_field_present() {
5848 let toml_str = r#"
5849[meta]
5850entity = "Test"
5851bo4e_type = "Test"
5852companion_type = "TestEdifact"
5853source_group = "SG4.SG8.SG10"
5854
5855[fields]
5856
5857[companion_fields]
5858"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmalCode"] }
5859"cav.0.0" = "merkmalCode"
5860"#;
5861 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5862
5863 let bo4e_with = serde_json::json!({
5865 "testEdifact": { "merkmalCode": "ZA7" }
5866 });
5867 let engine = MappingEngine::new_empty();
5868 let instance = engine.map_reverse(&bo4e_with, &def);
5869 let cci = instance
5870 .segments
5871 .iter()
5872 .find(|s| s.tag == "CCI")
5873 .expect("CCI should exist");
5874 assert_eq!(cci.elements[0][0], "Z83");
5875
5876 let bo4e_without = serde_json::json!({
5878 "testEdifact": {}
5879 });
5880 let instance2 = engine.map_reverse(&bo4e_without, &def);
5881 let cci2 = instance2.segments.iter().find(|s| s.tag == "CCI");
5882 assert!(
5883 cci2.is_none(),
5884 "CCI should not be emitted when merkmalCode is absent"
5885 );
5886 }
5887
5888 #[test]
5889 fn test_when_filled_checks_core_and_companion() {
5890 let toml_str = r#"
5891[meta]
5892entity = "Test"
5893bo4e_type = "Test"
5894companion_type = "TestEdifact"
5895source_group = "SG4.SG5"
5896
5897[fields]
5898"loc.1.0" = "marktlokationsId"
5899
5900[companion_fields]
5901"loc.0.0" = { target = "", default = "Z16", when_filled = ["marktlokationsId"] }
5902"#;
5903 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5904
5905 let bo4e_with = serde_json::json!({
5907 "marktlokationsId": "51234567890"
5908 });
5909 let engine = MappingEngine::new_empty();
5910 let instance = engine.map_reverse(&bo4e_with, &def);
5911 let loc = instance
5912 .segments
5913 .iter()
5914 .find(|s| s.tag == "LOC")
5915 .expect("LOC should exist");
5916 assert_eq!(loc.elements[0][0], "Z16");
5917 assert_eq!(loc.elements[1][0], "51234567890");
5918
5919 let bo4e_without = serde_json::json!({});
5921 let instance2 = engine.map_reverse(&bo4e_without, &def);
5922 let loc2 = instance2.segments.iter().find(|s| s.tag == "LOC");
5923 assert!(loc2.is_none());
5924 }
5925
5926 #[test]
5927 fn test_extract_all_from_instance_collects_all_qualifier_matches() {
5928 use mig_assembly::assembler::*;
5929
5930 let instance = AssembledGroupInstance {
5932 segments: vec![
5933 AssembledSegment {
5934 tag: "SEQ".to_string(),
5935 elements: vec![vec!["ZD6".to_string()]],
5936 mig_number: None,
5937 },
5938 AssembledSegment {
5939 tag: "RFF".to_string(),
5940 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
5941 mig_number: None,
5942 },
5943 AssembledSegment {
5944 tag: "RFF".to_string(),
5945 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
5946 mig_number: None,
5947 },
5948 AssembledSegment {
5949 tag: "RFF".to_string(),
5950 elements: vec![vec!["Z34".to_string(), "REF_C".to_string()]],
5951 mig_number: None,
5952 },
5953 AssembledSegment {
5954 tag: "RFF".to_string(),
5955 elements: vec![vec!["Z35".to_string(), "OTHER".to_string()]],
5956 mig_number: None,
5957 },
5958 ],
5959 child_groups: vec![],
5960 entry_mig_number: None,
5961 variant_mig_numbers: vec![],
5962 skipped_segments: vec![],
5963 skipped_positions: Vec::new(),
5964 };
5965
5966 let all = MappingEngine::extract_all_from_instance(&instance, "rff[Z34,*].0.1");
5968 assert_eq!(all, vec!["REF_A", "REF_B", "REF_C"]);
5969
5970 let single = MappingEngine::extract_from_instance(&instance, "rff[Z34].0.1");
5972 assert_eq!(single, Some("REF_A".to_string()));
5973
5974 let second = MappingEngine::extract_from_instance(&instance, "rff[Z34,1].0.1");
5975 assert_eq!(second, Some("REF_B".to_string()));
5976 }
5977
5978 #[test]
5979 fn test_forward_wildcard_collect_produces_json_array() {
5980 use mig_assembly::assembler::*;
5981
5982 let instance = AssembledGroupInstance {
5983 segments: vec![
5984 AssembledSegment {
5985 tag: "SEQ".to_string(),
5986 elements: vec![vec!["ZD6".to_string()]],
5987 mig_number: None,
5988 },
5989 AssembledSegment {
5990 tag: "RFF".to_string(),
5991 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
5992 mig_number: None,
5993 },
5994 AssembledSegment {
5995 tag: "RFF".to_string(),
5996 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
5997 mig_number: None,
5998 },
5999 ],
6000 child_groups: vec![],
6001 entry_mig_number: None,
6002 variant_mig_numbers: vec![],
6003 skipped_segments: vec![],
6004 skipped_positions: Vec::new(),
6005 };
6006
6007 let toml_str = r#"
6008[meta]
6009entity = "Test"
6010bo4e_type = "Test"
6011companion_type = "TestEdifact"
6012source_group = "SG4.SG8"
6013
6014[fields]
6015
6016[companion_fields]
6017"rff[Z34,*].0.1" = "messlokationsIdRefs"
6018"#;
6019 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
6020 let engine = MappingEngine::new_empty();
6021
6022 let mut result = serde_json::Map::new();
6023 engine.extract_companion_fields(&instance, &def, &mut result, false);
6024
6025 let companion = result.get("testEdifact").unwrap().as_object().unwrap();
6026 let refs = companion
6027 .get("messlokationsIdRefs")
6028 .unwrap()
6029 .as_array()
6030 .unwrap();
6031 assert_eq!(refs.len(), 2);
6032 assert_eq!(refs[0].as_str().unwrap(), "REF_A");
6033 assert_eq!(refs[1].as_str().unwrap(), "REF_B");
6034 }
6035
6036 #[test]
6037 fn test_reverse_json_array_produces_multiple_segments() {
6038 let toml_str = r#"
6039[meta]
6040entity = "Test"
6041bo4e_type = "Test"
6042companion_type = "TestEdifact"
6043source_group = "SG4.SG8"
6044
6045[fields]
6046
6047[companion_fields]
6048"seq.0.0" = { target = "", default = "ZD6" }
6049"rff[Z34,*].0.1" = "messlokationsIdRefs"
6050"#;
6051 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
6052 let engine = MappingEngine::new_empty();
6053
6054 let bo4e = serde_json::json!({
6055 "testEdifact": {
6056 "messlokationsIdRefs": ["REF_A", "REF_B", "REF_C"]
6057 }
6058 });
6059
6060 let instance = engine.map_reverse(&bo4e, &def);
6061
6062 let rff_segs: Vec<_> = instance
6064 .segments
6065 .iter()
6066 .filter(|s| s.tag == "RFF")
6067 .collect();
6068 assert_eq!(rff_segs.len(), 3);
6069 assert_eq!(rff_segs[0].elements[0][0], "Z34");
6070 assert_eq!(rff_segs[0].elements[0][1], "REF_A");
6071 assert_eq!(rff_segs[1].elements[0][0], "Z34");
6072 assert_eq!(rff_segs[1].elements[0][1], "REF_B");
6073 assert_eq!(rff_segs[2].elements[0][0], "Z34");
6074 assert_eq!(rff_segs[2].elements[0][1], "REF_C");
6075 }
6076
6077 #[test]
6078 fn test_when_filled_dotted_path() {
6079 let toml_str = r#"
6080[meta]
6081entity = "Test"
6082bo4e_type = "Test"
6083companion_type = "TestEdifact"
6084source_group = "SG4.SG8.SG10"
6085
6086[fields]
6087
6088[companion_fields]
6089"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmal.code"] }
6090"cav.0.0" = "merkmal.code"
6091"#;
6092 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
6093
6094 let bo4e = serde_json::json!({
6095 "testEdifact": { "merkmal": { "code": "ZA7" } }
6096 });
6097 let engine = MappingEngine::new_empty();
6098 let instance = engine.map_reverse(&bo4e, &def);
6099 let cci = instance
6100 .segments
6101 .iter()
6102 .find(|s| s.tag == "CCI")
6103 .expect("CCI should exist");
6104 assert_eq!(cci.elements[0][0], "Z83");
6105 }
6106
6107 #[test]
6108 fn test_also_target_forward_extracts_both_fields() {
6109 use mig_assembly::assembler::*;
6110
6111 let instance = AssembledGroupInstance {
6112 segments: vec![AssembledSegment {
6113 tag: "NAD".to_string(),
6114 elements: vec![vec!["Z47".to_string()], vec!["12345".to_string()]],
6115 mig_number: None,
6116 }],
6117 child_groups: vec![],
6118 entry_mig_number: None,
6119 variant_mig_numbers: vec![],
6120 skipped_segments: vec![],
6121 skipped_positions: Vec::new(),
6122 };
6123
6124 let toml_str = r#"
6125[meta]
6126entity = "Geschaeftspartner"
6127bo4e_type = "Geschaeftspartner"
6128companion_type = "GeschaeftspartnerEdifact"
6129source_group = "SG4.SG12"
6130
6131[fields]
6132"nad.1.0" = "identifikation"
6133
6134[companion_fields."nad.0.0"]
6135target = "partnerrolle"
6136enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
6137also_target = "datenqualitaet"
6138also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
6139"#;
6140 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
6141 let engine = MappingEngine::new_empty();
6142
6143 let mut result = serde_json::Map::new();
6144 engine.extract_companion_fields(&instance, &def, &mut result, false);
6145
6146 let companion = result
6147 .get("geschaeftspartnerEdifact")
6148 .unwrap()
6149 .as_object()
6150 .unwrap();
6151 assert_eq!(
6152 companion.get("partnerrolle").unwrap().as_str().unwrap(),
6153 "kundeDesLf"
6154 );
6155 assert_eq!(
6156 companion.get("datenqualitaet").unwrap().as_str().unwrap(),
6157 "erwartet"
6158 );
6159 }
6160
6161 #[test]
6162 fn test_also_target_reverse_joint_lookup() {
6163 let toml_str = r#"
6164[meta]
6165entity = "Geschaeftspartner"
6166bo4e_type = "Geschaeftspartner"
6167companion_type = "GeschaeftspartnerEdifact"
6168source_group = "SG4.SG12"
6169
6170[fields]
6171
6172[companion_fields."nad.0.0"]
6173target = "partnerrolle"
6174enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
6175also_target = "datenqualitaet"
6176also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
6177"#;
6178 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
6179 let engine = MappingEngine::new_empty();
6180
6181 let bo4e = serde_json::json!({
6183 "geschaeftspartnerEdifact": {
6184 "partnerrolle": "kundeDesLf",
6185 "datenqualitaet": "erwartet"
6186 }
6187 });
6188 let instance = engine.map_reverse(&bo4e, &def);
6189 let nad = instance
6190 .segments
6191 .iter()
6192 .find(|s| s.tag == "NAD")
6193 .expect("NAD");
6194 assert_eq!(nad.elements[0][0], "Z47");
6195
6196 let bo4e2 = serde_json::json!({
6198 "geschaeftspartnerEdifact": {
6199 "partnerrolle": "kundeDesNb",
6200 "datenqualitaet": "imSystemVorhanden"
6201 }
6202 });
6203 let instance2 = engine.map_reverse(&bo4e2, &def);
6204 let nad2 = instance2
6205 .segments
6206 .iter()
6207 .find(|s| s.tag == "NAD")
6208 .expect("NAD");
6209 assert_eq!(nad2.elements[0][0], "Z52");
6210 }
6211
6212 #[test]
6213 fn test_also_target_mixed_codes_unpaired_skips_datenqualitaet() {
6214 use mig_assembly::assembler::*;
6215
6216 let toml_str = r#"
6218[meta]
6219entity = "Geschaeftspartner"
6220bo4e_type = "Geschaeftspartner"
6221companion_type = "GeschaeftspartnerEdifact"
6222source_group = "SG4.SG12"
6223
6224[fields]
6225
6226[companion_fields."nad.0.0"]
6227target = "partnerrolle"
6228enum_map = { "Z09" = "kundeDesLf", "Z47" = "kundeDesLf", "Z48" = "kundeDesLf" }
6229also_target = "datenqualitaet"
6230also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden" }
6231"#;
6232 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
6233 let engine = MappingEngine::new_empty();
6234
6235 let instance_z09 = AssembledGroupInstance {
6237 segments: vec![AssembledSegment {
6238 tag: "NAD".to_string(),
6239 elements: vec![vec!["Z09".to_string()]],
6240 mig_number: None,
6241 }],
6242 child_groups: vec![],
6243 entry_mig_number: None,
6244 variant_mig_numbers: vec![],
6245 skipped_segments: vec![],
6246 skipped_positions: Vec::new(),
6247 };
6248 let mut result = serde_json::Map::new();
6249 engine.extract_companion_fields(&instance_z09, &def, &mut result, false);
6250 let comp = result
6251 .get("geschaeftspartnerEdifact")
6252 .unwrap()
6253 .as_object()
6254 .unwrap();
6255 assert_eq!(
6256 comp.get("partnerrolle").unwrap().as_str().unwrap(),
6257 "kundeDesLf"
6258 );
6259 assert!(
6260 comp.get("datenqualitaet").is_none(),
6261 "Z09 should not set datenqualitaet"
6262 );
6263
6264 let bo4e = serde_json::json!({
6266 "geschaeftspartnerEdifact": { "partnerrolle": "kundeDesLf" }
6267 });
6268 let instance = engine.map_reverse(&bo4e, &def);
6269 let nad = instance
6270 .segments
6271 .iter()
6272 .find(|s| s.tag == "NAD")
6273 .expect("NAD");
6274 assert_eq!(nad.elements[0][0], "Z09");
6275
6276 let bo4e2 = serde_json::json!({
6278 "geschaeftspartnerEdifact": {
6279 "partnerrolle": "kundeDesLf",
6280 "datenqualitaet": "erwartet"
6281 }
6282 });
6283 let instance2 = engine.map_reverse(&bo4e2, &def);
6284 let nad2 = instance2
6285 .segments
6286 .iter()
6287 .find(|s| s.tag == "NAD")
6288 .expect("NAD");
6289 assert_eq!(nad2.elements[0][0], "Z47");
6290 }
6291
6292 #[test]
6298 fn test_unroute_uses_side_channel_metadata() {
6299 let mut entities = serde_json::json!({
6300 "marktlokation": {
6301 "marktlokationsId": "12345678901",
6302 "boTyp": "MARKTLOKATION",
6303 "adresse": {
6304 "ort": "Testhausen",
6305 "postleitzahl": "99011",
6306 "land": "DE"
6307 }
6308 }
6309 })
6310 .as_object()
6311 .unwrap()
6312 .clone();
6313
6314 let mut metadata = serde_json::Map::new();
6315 metadata.insert(
6316 "originalIdx".to_string(),
6317 serde_json::Value::Number(serde_json::Number::from(2usize)),
6318 );
6319 metadata.insert(
6320 "marktrolle".to_string(),
6321 serde_json::json!({"code": "DP"}),
6322 );
6323 metadata.insert(
6324 "ortQualifier".to_string(),
6325 serde_json::json!({"code": "172"}),
6326 );
6327
6328 let mut dp_routing: DpRouting = std::collections::HashMap::new();
6329 dp_routing.insert("marktlokation".to_string(), vec![metadata]);
6330
6331 unroute_lokation_to_nad_dp(&mut entities, &dp_routing);
6332
6333 assert!(
6335 !entities.contains_key("marktlokation"),
6336 "marktlokation should be consumed by unroute"
6337 );
6338 let mts = entities
6339 .get("marktteilnehmer")
6340 .and_then(|v| v.as_array())
6341 .expect("marktteilnehmer array reconstructed");
6342 assert_eq!(mts.len(), 1);
6343 let dp = &mts[0];
6344 assert_eq!(
6345 dp.get("meldepunktId").and_then(|v| v.as_str()),
6346 Some("12345678901")
6347 );
6348 assert_eq!(
6349 dp.get("ort").and_then(|v| v.as_str()),
6350 Some("Testhausen")
6351 );
6352 assert!(
6354 dp.as_object().is_some_and(|m| !m.contains_key("_dpSource")),
6355 "rebuilt DP entry must not carry _dpSource: {dp}"
6356 );
6357 assert!(dp.get("marktrolle").is_some());
6359 assert!(dp.get("ortQualifier").is_some());
6360 }
6361}