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}
26
27impl MappingEngine {
28 pub fn new_empty() -> Self {
30 Self {
31 definitions: Vec::new(),
32 segment_structure: None,
33 code_lookup: None,
34 }
35 }
36
37 pub fn load(dir: &Path) -> Result<Self, MappingError> {
39 let mut definitions = Vec::new();
40
41 let mut entries: Vec<_> = std::fs::read_dir(dir)?.filter_map(|e| e.ok()).collect();
42 entries.sort_by_key(|e| e.file_name());
43
44 for entry in entries {
45 let path = entry.path();
46 if path.extension().map(|e| e == "toml").unwrap_or(false) {
47 let content = std::fs::read_to_string(&path)?;
48 let def: MappingDefinition =
49 toml::from_str(&content).map_err(|e| MappingError::TomlParse {
50 file: path.display().to_string(),
51 message: e.to_string(),
52 })?;
53 definitions.push(def);
54 }
55 }
56
57 Ok(Self {
58 definitions,
59 segment_structure: None,
60 code_lookup: None,
61 })
62 }
63
64 pub fn load_split(
70 message_dir: &Path,
71 transaction_dir: &Path,
72 ) -> Result<(Self, Self), MappingError> {
73 let msg_engine = Self::load(message_dir)?;
74 let tx_engine = Self::load(transaction_dir)?;
75 Ok((msg_engine, tx_engine))
76 }
77
78 pub fn load_merged(dirs: &[&Path]) -> Result<Self, MappingError> {
83 let mut definitions = Vec::new();
84 for dir in dirs {
85 let engine = Self::load(dir)?;
86 definitions.extend(engine.definitions);
87 }
88 Ok(Self {
89 definitions,
90 segment_structure: None,
91 code_lookup: None,
92 })
93 }
94
95 pub fn load_with_common(
104 common_dir: &Path,
105 pid_dir: &Path,
106 schema_index: &crate::pid_schema_index::PidSchemaIndex,
107 ) -> Result<Self, MappingError> {
108 let mut common_defs = Self::load(common_dir)?.definitions;
109
110 common_defs.retain(|d| {
112 d.meta
113 .source_path
114 .as_deref()
115 .map(|sp| schema_index.has_group(sp))
116 .unwrap_or(true)
117 });
118
119 let pid_defs = Self::load(pid_dir)?.definitions;
120
121 let normalize_sg = |sg: &str| -> String {
126 sg.split('.')
127 .map(|part| part.split(':').next().unwrap_or(part))
128 .collect::<Vec<_>>()
129 .join(".")
130 };
131 let pid_keys: HashSet<(String, Option<String>)> = pid_defs
132 .iter()
133 .flat_map(|d| {
134 let sg = normalize_sg(&d.meta.source_group);
135 let disc = d.meta.discriminator.clone();
136 let mut keys = vec![(sg.clone(), disc.clone())];
137 if let Some(ref disc_str) = disc {
139 if let Some(base) = disc_str.rsplit_once('#') {
140 if base.1.chars().all(|c| c.is_ascii_digit()) {
141 keys.push((sg, Some(base.0.to_string())));
142 }
143 }
144 }
145 keys
146 })
147 .collect();
148
149 common_defs.retain(|d| {
151 let key = (
152 normalize_sg(&d.meta.source_group),
153 d.meta.discriminator.clone(),
154 );
155 !pid_keys.contains(&key)
156 });
157
158 let mut definitions = common_defs;
160 definitions.extend(pid_defs);
161
162 Ok(Self {
163 definitions,
164 segment_structure: None,
165 code_lookup: None,
166 })
167 }
168
169 pub fn load_common_only(
173 common_dir: &Path,
174 schema_index: &crate::pid_schema_index::PidSchemaIndex,
175 ) -> Result<Self, MappingError> {
176 let mut common_defs = Self::load(common_dir)?.definitions;
177
178 common_defs.retain(|d| {
180 d.meta
181 .source_path
182 .as_deref()
183 .map(|sp| schema_index.has_group(sp))
184 .unwrap_or(true)
185 });
186
187 Ok(Self {
188 definitions: common_defs,
189 segment_structure: None,
190 code_lookup: None,
191 })
192 }
193
194 pub fn load_split_with_common(
199 message_dir: &Path,
200 common_dir: &Path,
201 transaction_dir: &Path,
202 schema_index: &crate::pid_schema_index::PidSchemaIndex,
203 ) -> Result<(Self, Self), MappingError> {
204 let msg_engine = Self::load(message_dir)?;
205 let tx_engine = Self::load_with_common(common_dir, transaction_dir, schema_index)?;
206 Ok((msg_engine, tx_engine))
207 }
208
209 pub fn from_definitions(definitions: Vec<MappingDefinition>) -> Self {
211 Self {
212 definitions,
213 segment_structure: None,
214 code_lookup: None,
215 }
216 }
217
218 pub fn save_cached(&self, path: &Path) -> Result<(), MappingError> {
224 let encoded =
225 serde_json::to_vec(&self.definitions).map_err(|e| MappingError::CacheWrite {
226 path: path.display().to_string(),
227 message: e.to_string(),
228 })?;
229 if let Some(parent) = path.parent() {
230 std::fs::create_dir_all(parent)?;
231 }
232 std::fs::write(path, encoded)?;
233 Ok(())
234 }
235
236 pub fn load_cached_or_toml(cache_path: &Path, toml_dir: &Path) -> Result<Self, MappingError> {
241 if cache_path.exists() {
242 Self::load_cached(cache_path)
243 } else {
244 Self::load(toml_dir)
245 }
246 }
247
248 pub fn load_cached(path: &Path) -> Result<Self, MappingError> {
253 let bytes = std::fs::read(path)?;
254 let definitions: Vec<MappingDefinition> =
255 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
256 path: path.display().to_string(),
257 message: e.to_string(),
258 })?;
259 Ok(Self {
260 definitions,
261 segment_structure: None,
262 code_lookup: None,
263 })
264 }
265
266 pub fn with_segment_structure(mut self, ss: SegmentStructure) -> Self {
271 self.segment_structure = Some(ss);
272 self
273 }
274
275 pub fn with_code_lookup(mut self, cl: crate::code_lookup::CodeLookup) -> Self {
280 self.code_lookup = Some(cl);
281 self
282 }
283
284 pub fn with_path_resolver(mut self, resolver: crate::path_resolver::PathResolver) -> Self {
290 for def in &mut self.definitions {
291 def.normalize_paths(&resolver);
292 }
293 self
294 }
295
296 pub fn definitions(&self) -> &[MappingDefinition] {
298 &self.definitions
299 }
300
301 pub fn definition_for_entity(&self, entity: &str) -> Option<&MappingDefinition> {
303 self.definitions.iter().find(|d| d.meta.entity == entity)
304 }
305
306 pub fn extract_field(
315 &self,
316 tree: &AssembledTree,
317 group_path: &str,
318 path: &str,
319 repetition: usize,
320 ) -> Option<String> {
321 let instance = Self::resolve_group_instance(tree, group_path, repetition)?;
322 Self::extract_from_instance(instance, path)
323 }
324
325 pub fn resolve_group_instance<'a>(
334 tree: &'a AssembledTree,
335 group_path: &str,
336 repetition: usize,
337 ) -> Option<&'a AssembledGroupInstance> {
338 let parts: Vec<&str> = group_path.split('.').collect();
339
340 let (first_id, first_rep) = parse_group_spec(parts[0]);
341 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
342
343 if parts.len() == 1 {
344 let rep = first_rep.unwrap_or(repetition);
346 return first_group.repetitions.get(rep);
347 }
348
349 let mut current_instance = first_group.repetitions.get(first_rep.unwrap_or(0))?;
352
353 for (i, part) in parts[1..].iter().enumerate() {
354 let (group_id, explicit_rep) = parse_group_spec(part);
355 let child_group = current_instance
356 .child_groups
357 .iter()
358 .find(|g| g.group_id == group_id)?;
359
360 if i == parts.len() - 2 {
361 let rep = explicit_rep.unwrap_or(repetition);
363 return child_group.repetitions.get(rep);
364 }
365 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
367 }
368
369 None
370 }
371
372 pub fn resolve_by_source_path<'a>(
380 tree: &'a AssembledTree,
381 source_path: &str,
382 ) -> Option<&'a AssembledGroupInstance> {
383 let parts: Vec<&str> = source_path.split('.').collect();
384 if parts.is_empty() {
385 return None;
386 }
387
388 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
389 let first_group = tree
390 .groups
391 .iter()
392 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))?;
393
394 let mut current_instance = if let Some(q) = first_qualifier {
395 find_rep_by_entry_qualifier(&first_group.repetitions, q)?
396 } else {
397 first_group.repetitions.first()?
398 };
399
400 if parts.len() == 1 {
401 return Some(current_instance);
402 }
403
404 for part in &parts[1..] {
405 let (group_id, qualifier) = parse_source_path_part(part);
406 let child_group = current_instance
407 .child_groups
408 .iter()
409 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))?;
410
411 current_instance = if let Some(q) = qualifier {
412 find_rep_by_entry_qualifier(&child_group.repetitions, q)?
413 } else {
414 child_group.repetitions.first()?
415 };
416 }
417
418 Some(current_instance)
419 }
420
421 pub fn resolve_all_by_source_path<'a>(
429 tree: &'a AssembledTree,
430 source_path: &str,
431 ) -> Vec<&'a AssembledGroupInstance> {
432 let parts: Vec<&str> = source_path.split('.').collect();
433 if parts.is_empty() {
434 return vec![];
435 }
436
437 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
439 let first_group = match tree
440 .groups
441 .iter()
442 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
443 {
444 Some(g) => g,
445 None => return vec![],
446 };
447
448 let mut current_instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
449 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
450 } else {
451 first_group.repetitions.iter().collect()
452 };
453
454 for part in &parts[1..] {
457 let (group_id, qualifier) = parse_source_path_part(part);
458 let mut next_instances = Vec::new();
459
460 for instance in ¤t_instances {
461 if let Some(child_group) = instance
462 .child_groups
463 .iter()
464 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
465 {
466 if let Some(q) = qualifier {
467 next_instances.extend(find_all_reps_by_entry_qualifier(
468 &child_group.repetitions,
469 q,
470 ));
471 } else {
472 next_instances.extend(child_group.repetitions.iter());
473 }
474 }
475 }
476
477 current_instances = next_instances;
478 }
479
480 current_instances
481 }
482
483 fn compute_child_indices(
496 tree: &AssembledTree,
497 source_path: &str,
498 indexed: &[(usize, &AssembledGroupInstance)],
499 ) -> Vec<usize> {
500 let parts: Vec<&str> = source_path.split('.').collect();
501 if parts.len() < 2 {
502 return vec![];
503 }
504 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
506 let first_group = match tree
507 .groups
508 .iter()
509 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
510 {
511 Some(g) => g,
512 None => return vec![],
513 };
514 let parent_reps: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
515 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
516 } else {
517 first_group.repetitions.iter().collect()
518 };
519 let (child_id, _child_qualifier) = parse_source_path_part(parts[parts.len() - 1]);
521 let mut result = Vec::new();
522 for (_, inst) in indexed {
523 let mut found = false;
525 for parent in &parent_reps {
526 if let Some(child_group) = parent
527 .child_groups
528 .iter()
529 .find(|g| g.group_id.eq_ignore_ascii_case(child_id))
530 {
531 if let Some(pos) = child_group
532 .repetitions
533 .iter()
534 .position(|r| std::ptr::eq(r, *inst))
535 {
536 result.push(pos);
537 found = true;
538 break;
539 }
540 }
541 }
542 if !found {
543 result.push(usize::MAX); }
545 }
546 result
547 }
548
549 pub fn resolve_all_with_parent_indices<'a>(
551 tree: &'a AssembledTree,
552 source_path: &str,
553 ) -> Vec<(usize, &'a AssembledGroupInstance)> {
554 let parts: Vec<&str> = source_path.split('.').collect();
555 if parts.is_empty() {
556 return vec![];
557 }
558
559 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
561 let first_group = match tree
562 .groups
563 .iter()
564 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
565 {
566 Some(g) => g,
567 None => return vec![],
568 };
569
570 if parts.len() == 1 {
572 let instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
573 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
574 } else {
575 first_group.repetitions.iter().collect()
576 };
577 return instances.into_iter().map(|i| (0, i)).collect();
578 }
579
580 let first_reps: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = first_qualifier {
585 let matching = find_all_reps_by_entry_qualifier(&first_group.repetitions, q);
586 let mut result = Vec::new();
587 for m in matching {
588 let idx = first_group
589 .repetitions
590 .iter()
591 .position(|r| std::ptr::eq(r, m))
592 .unwrap_or(0);
593 result.push((idx, m));
594 }
595 result
596 } else {
597 first_group.repetitions.iter().enumerate().collect()
598 };
599
600 let mut current: Vec<(usize, &AssembledGroupInstance)> = first_reps;
601 let remaining = &parts[1..];
602
603 for (level, part) in remaining.iter().enumerate() {
604 let is_leaf = level == remaining.len() - 1;
605 let (group_id, qualifier) = parse_source_path_part(part);
606 let mut next: Vec<(usize, &AssembledGroupInstance)> = Vec::new();
607
608 for (prev_parent_idx, instance) in ¤t {
609 if let Some(child_group) = instance
610 .child_groups
611 .iter()
612 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
613 {
614 let matching: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = qualifier
615 {
616 let filtered =
617 find_all_reps_by_entry_qualifier(&child_group.repetitions, q);
618 filtered
619 .into_iter()
620 .map(|m| {
621 let idx = child_group
622 .repetitions
623 .iter()
624 .position(|r| std::ptr::eq(r, m))
625 .unwrap_or(0);
626 (idx, m)
627 })
628 .collect()
629 } else {
630 child_group.repetitions.iter().enumerate().collect()
631 };
632
633 for (rep_idx, child_rep) in matching {
634 if is_leaf {
635 next.push((*prev_parent_idx, child_rep));
637 } else {
638 next.push((rep_idx, child_rep));
640 }
641 }
642 }
643 }
644
645 current = next;
646 }
647
648 current
649 }
650
651 pub fn extract_from_instance(instance: &AssembledGroupInstance, path: &str) -> Option<String> {
657 let parts: Vec<&str> = path.split('.').collect();
658 if parts.is_empty() {
659 return None;
660 }
661
662 let (segment_tag, qualifier, occurrence) = parse_tag_qualifier(parts[0]);
665
666 let segment = if let Some(q) = qualifier {
667 instance
668 .segments
669 .iter()
670 .filter(|s| {
671 s.tag.eq_ignore_ascii_case(&segment_tag)
672 && s.elements
673 .first()
674 .and_then(|e| e.first())
675 .map(|v| v.as_str())
676 == Some(q)
677 })
678 .nth(occurrence)?
679 } else {
680 instance
681 .segments
682 .iter()
683 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
684 .nth(occurrence)?
685 };
686
687 Self::resolve_field_path(segment, &parts[1..])
688 }
689
690 pub fn extract_all_from_instance(instance: &AssembledGroupInstance, path: &str) -> Vec<String> {
696 let parts: Vec<&str> = path.split('.').collect();
697 if parts.is_empty() {
698 return vec![];
699 }
700
701 let (segment_tag, qualifier, _) = parse_tag_qualifier(parts[0]);
702
703 let matching_segments: Vec<&AssembledSegment> = if let Some(q) = qualifier {
704 instance
705 .segments
706 .iter()
707 .filter(|s| {
708 s.tag.eq_ignore_ascii_case(&segment_tag)
709 && s.elements
710 .first()
711 .and_then(|e| e.first())
712 .map(|v| v.as_str())
713 == Some(q)
714 })
715 .collect()
716 } else {
717 instance
718 .segments
719 .iter()
720 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
721 .collect()
722 };
723
724 matching_segments
725 .into_iter()
726 .filter_map(|seg| Self::resolve_field_path(seg, &parts[1..]))
727 .collect()
728 }
729
730 pub fn map_forward(
739 &self,
740 tree: &AssembledTree,
741 def: &MappingDefinition,
742 repetition: usize,
743 ) -> serde_json::Value {
744 self.map_forward_inner(tree, def, repetition, true)
745 }
746
747 fn map_forward_inner(
749 &self,
750 tree: &AssembledTree,
751 def: &MappingDefinition,
752 repetition: usize,
753 enrich_codes: bool,
754 ) -> serde_json::Value {
755 let mut result = serde_json::Map::new();
756
757 if def.meta.source_group.is_empty() {
762 let mut all_root_segs = tree.segments.clone();
763 for segs in tree.inter_group_segments.values() {
764 all_root_segs.extend(segs.iter().cloned());
765 }
766 let root_instance = AssembledGroupInstance {
767 segments: all_root_segs,
768 child_groups: vec![],
769 entry_mig_number: None,
770 variant_mig_numbers: vec![],
771 skipped_segments: Vec::new(),
772 };
773 self.extract_fields_from_instance(&root_instance, def, &mut result, enrich_codes);
774 self.extract_companion_fields(&root_instance, def, &mut result, enrich_codes);
775 return serde_json::Value::Object(result);
776 }
777
778 let instance = if let Some(ref sp) = def.meta.source_path {
784 if has_source_path_qualifiers(sp) && !def.meta.source_group.contains(':') {
785 Self::resolve_by_source_path(tree, sp).or_else(|| {
786 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
787 })
788 } else {
789 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
790 }
791 } else {
792 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
793 };
794
795 if let Some(instance) = instance {
796 if let Some(ref tag) = def.meta.repeat_on_tag {
798 let matching: Vec<_> = instance
799 .segments
800 .iter()
801 .filter(|s| s.tag.eq_ignore_ascii_case(tag))
802 .collect();
803
804 if matching.len() > 1 {
805 let mut arr = Vec::new();
806 for seg in &matching {
807 let sub_instance = AssembledGroupInstance {
808 segments: vec![(*seg).clone()],
809 child_groups: vec![],
810 entry_mig_number: None,
811 variant_mig_numbers: vec![],
812 skipped_segments: Vec::new(),
813 };
814 let mut elem_result = serde_json::Map::new();
815 self.extract_fields_from_instance(
816 &sub_instance,
817 def,
818 &mut elem_result,
819 enrich_codes,
820 );
821 self.extract_companion_fields(
822 &sub_instance,
823 def,
824 &mut elem_result,
825 enrich_codes,
826 );
827 if !elem_result.is_empty() {
828 arr.push(serde_json::Value::Object(elem_result));
829 }
830 }
831 if !arr.is_empty() {
832 return serde_json::Value::Array(arr);
833 }
834 }
835 }
836
837 self.extract_fields_from_instance(instance, def, &mut result, enrich_codes);
838 self.extract_companion_fields(instance, def, &mut result, enrich_codes);
839 }
840
841 serde_json::Value::Object(result)
842 }
843
844 fn extract_companion_fields(
849 &self,
850 instance: &AssembledGroupInstance,
851 def: &MappingDefinition,
852 result: &mut serde_json::Map<String, serde_json::Value>,
853 enrich_codes: bool,
854 ) {
855 if let Some(ref companion_fields) = def.companion_fields {
856 let raw_key = def.meta.companion_type.as_deref().unwrap_or("_companion");
857 let companion_key = to_camel_case(raw_key);
858 let mut companion_result = serde_json::Map::new();
859
860 for (path, field_mapping) in companion_fields {
861 let (target, enum_map, also_target, also_enum_map) = match field_mapping {
862 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
863 FieldMapping::Structured(s) => (
864 s.target.as_str(),
865 s.enum_map.as_ref(),
866 s.also_target.as_deref(),
867 s.also_enum_map.as_ref(),
868 ),
869 FieldMapping::Nested(_) => continue,
870 };
871 if target.is_empty() {
872 continue;
873 }
874
875 if is_collect_all_path(path) {
877 let all = Self::extract_all_from_instance(instance, path);
878 if !all.is_empty() {
879 let arr: Vec<serde_json::Value> = all
880 .into_iter()
881 .map(|v| {
882 let mapped = if let Some(map) = enum_map {
883 map.get(&v).cloned().unwrap_or_else(|| v.clone())
884 } else {
885 v
886 };
887 serde_json::Value::String(mapped)
888 })
889 .collect();
890 set_nested_value_json(
891 &mut companion_result,
892 target,
893 serde_json::Value::Array(arr),
894 );
895 }
896 continue;
897 }
898
899 if let Some(val) = Self::extract_from_instance(instance, path) {
900 let mapped_val = if let Some(map) = enum_map {
901 map.get(&val).cloned().unwrap_or_else(|| val.clone())
902 } else {
903 val.clone()
904 };
905
906 if enrich_codes {
908 if let (Some(ref code_lookup), Some(ref source_path)) =
909 (&self.code_lookup, &def.meta.source_path)
910 {
911 let parts: Vec<&str> = path.split('.').collect();
912 let (seg_tag, _qualifier, _occ) = parse_tag_qualifier(parts[0]);
913 let (element_idx, component_idx) =
914 Self::parse_element_component(&parts[1..]);
915
916 if code_lookup.is_code_field(
917 source_path,
918 &seg_tag,
919 element_idx,
920 component_idx,
921 ) {
922 let enrichment = code_lookup.enrichment_for(
926 source_path,
927 &seg_tag,
928 element_idx,
929 component_idx,
930 &val,
931 );
932 let meaning = enrichment
933 .map(|e| serde_json::Value::String(e.meaning.clone()))
934 .unwrap_or(serde_json::Value::Null);
935
936 let mut obj = serde_json::Map::new();
937 obj.insert("code".into(), serde_json::json!(mapped_val));
938 obj.insert("meaning".into(), meaning);
939 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref())
940 {
941 obj.insert("enum".into(), serde_json::json!(enum_key));
942 }
943 let enriched = serde_json::Value::Object(obj);
944 set_nested_value_json(&mut companion_result, target, enriched);
945 continue;
946 }
947 }
948 }
949
950 set_nested_value(&mut companion_result, target, mapped_val);
951
952 if let (Some(at), Some(am)) = (also_target, also_enum_map) {
956 if let Some(also_mapped) = am.get(&val) {
957 set_nested_value(&mut companion_result, at, also_mapped.clone());
958 }
959 }
960 }
961 }
962
963 if !companion_result.is_empty() {
964 result.insert(
965 companion_key.to_string(),
966 serde_json::Value::Object(companion_result),
967 );
968 }
969 }
970 }
971
972 fn extract_fields_from_instance(
977 &self,
978 instance: &AssembledGroupInstance,
979 def: &MappingDefinition,
980 result: &mut serde_json::Map<String, serde_json::Value>,
981 enrich_codes: bool,
982 ) {
983 for (path, field_mapping) in &def.fields {
984 let (target, enum_map) = match field_mapping {
985 FieldMapping::Simple(t) => (t.as_str(), None),
986 FieldMapping::Structured(s) => (s.target.as_str(), s.enum_map.as_ref()),
987 FieldMapping::Nested(_) => continue,
988 };
989 if target.is_empty() {
990 continue;
991 }
992 if let Some(val) = Self::extract_from_instance(instance, path) {
993 let mapped_val = if let Some(map) = enum_map {
994 map.get(&val).cloned().unwrap_or_else(|| val.clone())
995 } else {
996 val.clone()
997 };
998
999 if enrich_codes {
1001 if let (Some(ref code_lookup), Some(ref source_path)) =
1002 (&self.code_lookup, &def.meta.source_path)
1003 {
1004 let parts: Vec<&str> = path.split('.').collect();
1005 let (seg_tag, _qualifier, _occ) = parse_tag_qualifier(parts[0]);
1006 let (element_idx, component_idx) =
1007 Self::parse_element_component(&parts[1..]);
1008
1009 if code_lookup.is_code_field(
1010 source_path,
1011 &seg_tag,
1012 element_idx,
1013 component_idx,
1014 ) {
1015 let enrichment = code_lookup.enrichment_for(
1019 source_path,
1020 &seg_tag,
1021 element_idx,
1022 component_idx,
1023 &val,
1024 );
1025 let meaning = enrichment
1026 .map(|e| serde_json::Value::String(e.meaning.clone()))
1027 .unwrap_or(serde_json::Value::Null);
1028
1029 let mut obj = serde_json::Map::new();
1030 obj.insert("code".into(), serde_json::json!(mapped_val));
1031 obj.insert("meaning".into(), meaning);
1032 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
1033 obj.insert("enum".into(), serde_json::json!(enum_key));
1034 }
1035 let enriched = serde_json::Value::Object(obj);
1036 set_nested_value_json(result, target, enriched);
1037 continue;
1038 }
1039 }
1040 }
1041
1042 set_nested_value(result, target, mapped_val);
1043 }
1044 }
1045 }
1046
1047 pub fn map_forward_from_segments(
1053 &self,
1054 segments: &[OwnedSegment],
1055 def: &MappingDefinition,
1056 ) -> serde_json::Value {
1057 let assembled_segments: Vec<AssembledSegment> = segments
1058 .iter()
1059 .map(|s| AssembledSegment {
1060 tag: s.id.clone(),
1061 elements: s.elements.clone(),
1062 mig_number: None,
1063 })
1064 .collect();
1065
1066 let instance = AssembledGroupInstance {
1067 segments: assembled_segments,
1068 child_groups: vec![],
1069 entry_mig_number: None,
1070 variant_mig_numbers: vec![],
1071 skipped_segments: Vec::new(),
1072 };
1073
1074 let mut result = serde_json::Map::new();
1075 self.extract_fields_from_instance(&instance, def, &mut result, true);
1076 serde_json::Value::Object(result)
1077 }
1078
1079 pub fn map_reverse(
1092 &self,
1093 bo4e_value: &serde_json::Value,
1094 def: &MappingDefinition,
1095 ) -> AssembledGroupInstance {
1096 if def.meta.repeat_on_tag.is_some() {
1098 if let Some(arr) = bo4e_value.as_array() {
1099 let mut all_segments = Vec::new();
1100 for elem in arr {
1101 let sub = self.map_reverse_single(elem, def);
1102 all_segments.extend(sub.segments);
1103 }
1104 return AssembledGroupInstance {
1105 segments: all_segments,
1106 child_groups: vec![],
1107 entry_mig_number: None,
1108 variant_mig_numbers: vec![],
1109 skipped_segments: Vec::new(),
1110 };
1111 }
1112 }
1113 self.map_reverse_single(bo4e_value, def)
1114 }
1115
1116 fn map_reverse_single(
1117 &self,
1118 bo4e_value: &serde_json::Value,
1119 def: &MappingDefinition,
1120 ) -> AssembledGroupInstance {
1121 let mut field_values: Vec<(String, String, usize, usize, String)> =
1124 Vec::with_capacity(def.fields.len());
1125
1126 let mut has_real_data = false;
1133 let mut has_data_fields = false;
1134 let mut seg_has_data_field: HashSet<String> = HashSet::new();
1137 let mut seg_has_real_data: HashSet<String> = HashSet::new();
1138 let mut injected_qualifiers: HashSet<String> = HashSet::new();
1139
1140 for (path, field_mapping) in &def.fields {
1141 let (target, default, enum_map, when_filled) = match field_mapping {
1142 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
1143 FieldMapping::Structured(s) => (
1144 s.target.as_str(),
1145 s.default.as_ref(),
1146 s.enum_map.as_ref(),
1147 s.when_filled.as_ref(),
1148 ),
1149 FieldMapping::Nested(_) => continue,
1150 };
1151
1152 let parts: Vec<&str> = path.split('.').collect();
1153 if parts.len() < 2 {
1154 continue;
1155 }
1156
1157 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1158 let seg_key = parts[0].to_uppercase();
1161 let sub_path = &parts[1..];
1162
1163 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1165 let ci = if sub_path.len() > 1 {
1166 sub_path[1].parse::<usize>().unwrap_or(0)
1167 } else {
1168 0
1169 };
1170 (ei, ci)
1171 } else {
1172 match sub_path.len() {
1173 1 => (0, 0),
1174 2 => (1, 0),
1175 _ => continue,
1176 }
1177 };
1178
1179 let val = if target.is_empty() {
1181 match (default, when_filled) {
1182 (Some(d), Some(fields)) => {
1185 let companion_key_for_check =
1186 def.meta.companion_type.as_deref().map(to_camel_case);
1187 let companion_for_check = companion_key_for_check
1188 .as_ref()
1189 .and_then(|k| bo4e_value.get(k))
1190 .unwrap_or(&serde_json::Value::Null);
1191 let any_filled = fields.iter().any(|f| {
1192 self.populate_field(bo4e_value, f).is_some()
1193 || self.populate_field(companion_for_check, f).is_some()
1194 });
1195 if any_filled {
1196 has_real_data = true;
1200 Some(d.clone())
1201 } else {
1202 None
1203 }
1204 }
1205 (Some(d), None) => Some(d.clone()),
1207 (None, _) => None,
1208 }
1209 } else {
1210 has_data_fields = true;
1211 seg_has_data_field.insert(seg_key.clone());
1212 let bo4e_val = self.populate_field(bo4e_value, target);
1213 if bo4e_val.is_some() {
1214 has_real_data = true;
1215 seg_has_real_data.insert(seg_key.clone());
1216 }
1217 let mapped_val = match (bo4e_val, enum_map) {
1219 (Some(v), Some(map)) => {
1220 map.iter()
1222 .find(|(_, bo4e_v)| *bo4e_v == &v)
1223 .map(|(edifact_k, _)| edifact_k.clone())
1224 .or(Some(v))
1225 }
1226 (v, _) => v,
1227 };
1228 mapped_val.or_else(|| default.cloned())
1229 };
1230
1231 if let Some(val) = val {
1232 field_values.push((
1233 seg_key.clone(),
1234 seg_tag.clone(),
1235 element_idx,
1236 component_idx,
1237 val,
1238 ));
1239 }
1240
1241 if let Some(q) = qualifier {
1243 if injected_qualifiers.insert(seg_key.clone()) {
1244 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1245 }
1246 }
1247 }
1248
1249 if let Some(ref companion_fields) = def.companion_fields {
1253 let raw_key = def.meta.companion_type.as_deref().unwrap_or("_companion");
1254 let companion_key = to_camel_case(raw_key);
1255 let companion_value = bo4e_value
1256 .get(&companion_key)
1257 .unwrap_or(bo4e_value);
1258
1259 for (path, field_mapping) in companion_fields {
1260 let (target, default, enum_map, when_filled, also_target, also_enum_map) =
1261 match field_mapping {
1262 FieldMapping::Simple(t) => (t.as_str(), None, None, None, None, None),
1263 FieldMapping::Structured(s) => (
1264 s.target.as_str(),
1265 s.default.as_ref(),
1266 s.enum_map.as_ref(),
1267 s.when_filled.as_ref(),
1268 s.also_target.as_deref(),
1269 s.also_enum_map.as_ref(),
1270 ),
1271 FieldMapping::Nested(_) => continue,
1272 };
1273
1274 let parts: Vec<&str> = path.split('.').collect();
1275 if parts.len() < 2 {
1276 continue;
1277 }
1278
1279 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1280 let seg_key = parts[0].to_uppercase();
1281 let sub_path = &parts[1..];
1282
1283 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1284 let ci = if sub_path.len() > 1 {
1285 sub_path[1].parse::<usize>().unwrap_or(0)
1286 } else {
1287 0
1288 };
1289 (ei, ci)
1290 } else {
1291 match sub_path.len() {
1292 1 => (0, 0),
1293 2 => (1, 0),
1294 _ => continue,
1295 }
1296 };
1297
1298 if is_collect_all_path(path) && !target.is_empty() {
1300 if let Some(arr) = self
1301 .populate_field_json(companion_value, target)
1302 .and_then(|v| v.as_array().cloned())
1303 {
1304 has_data_fields = true;
1305 if !arr.is_empty() {
1306 has_real_data = true;
1307 }
1308 for (idx, item) in arr.iter().enumerate() {
1309 if let Some(val_str) = item.as_str() {
1310 let mapped = if let Some(map) = enum_map {
1311 map.iter()
1312 .find(|(_, bo4e_v)| *bo4e_v == val_str)
1313 .map(|(edifact_k, _)| edifact_k.clone())
1314 .unwrap_or_else(|| val_str.to_string())
1315 } else {
1316 val_str.to_string()
1317 };
1318 let occ_key = if let Some(q) = qualifier {
1319 format!("{}[{},{}]", seg_tag, q, idx)
1320 } else {
1321 format!("{}[*,{}]", seg_tag, idx)
1322 };
1323 field_values.push((
1324 occ_key.clone(),
1325 seg_tag.clone(),
1326 element_idx,
1327 component_idx,
1328 mapped,
1329 ));
1330 if let Some(q) = qualifier {
1332 if injected_qualifiers.insert(occ_key.clone()) {
1333 field_values.push((
1334 occ_key,
1335 seg_tag.clone(),
1336 0,
1337 0,
1338 q.to_string(),
1339 ));
1340 }
1341 }
1342 }
1343 }
1344 }
1345 continue;
1346 }
1347
1348 let val = if target.is_empty() {
1349 match (default, when_filled) {
1350 (Some(d), Some(fields)) => {
1351 let any_filled = fields.iter().any(|f| {
1352 self.populate_field(bo4e_value, f).is_some()
1353 || self.populate_field(companion_value, f).is_some()
1354 });
1355 if any_filled {
1356 has_real_data = true;
1357 Some(d.clone())
1358 } else {
1359 None
1360 }
1361 }
1362 (Some(d), None) => Some(d.clone()),
1363 (None, _) => None,
1364 }
1365 } else {
1366 has_data_fields = true;
1367 seg_has_data_field.insert(seg_key.clone());
1368 let bo4e_val = self.populate_field(companion_value, target);
1369 if bo4e_val.is_some() {
1370 has_real_data = true;
1371 seg_has_real_data.insert(seg_key.clone());
1372 }
1373 let mapped_val = match (bo4e_val, enum_map) {
1374 (Some(v), Some(map)) => {
1375 if let (Some(at), Some(am)) = (also_target, also_enum_map) {
1376 let also_val = self.populate_field(companion_value, at);
1377 if let Some(av) = also_val.as_deref() {
1378 map.iter()
1380 .find(|(edifact_k, bo4e_v)| {
1381 *bo4e_v == &v
1382 && am.get(*edifact_k).is_some_and(|am_v| am_v == av)
1383 })
1384 .map(|(edifact_k, _)| edifact_k.clone())
1385 .or(Some(v))
1386 } else {
1387 map.iter()
1390 .find(|(edifact_k, bo4e_v)| {
1391 *bo4e_v == &v && !am.contains_key(*edifact_k)
1392 })
1393 .or_else(|| {
1394 map.iter().find(|(_, bo4e_v)| *bo4e_v == &v)
1396 })
1397 .map(|(edifact_k, _)| edifact_k.clone())
1398 .or(Some(v))
1399 }
1400 } else {
1401 map.iter()
1402 .find(|(_, bo4e_v)| *bo4e_v == &v)
1403 .map(|(edifact_k, _)| edifact_k.clone())
1404 .or(Some(v))
1405 }
1406 }
1407 (v, _) => v,
1408 };
1409 mapped_val.or_else(|| default.cloned())
1410 };
1411
1412 if let Some(val) = val {
1413 field_values.push((
1414 seg_key.clone(),
1415 seg_tag.clone(),
1416 element_idx,
1417 component_idx,
1418 val,
1419 ));
1420 }
1421
1422 if let Some(q) = qualifier {
1423 if injected_qualifiers.insert(seg_key.clone()) {
1424 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1425 }
1426 }
1427 }
1428 }
1429
1430 field_values.retain(|(seg_key, _, _, _, _)| {
1438 if !seg_key.contains('[') {
1439 return true; }
1441 !seg_has_data_field.contains(seg_key) || seg_has_real_data.contains(seg_key)
1442 });
1443
1444 if has_data_fields && !has_real_data {
1449 return AssembledGroupInstance {
1450 segments: vec![],
1451 child_groups: vec![],
1452 entry_mig_number: None,
1453 variant_mig_numbers: vec![],
1454 skipped_segments: Vec::new(),
1455 };
1456 }
1457
1458 let mut segments: Vec<AssembledSegment> = Vec::with_capacity(field_values.len());
1461 let mut seen_keys: HashMap<String, usize> = HashMap::new();
1462
1463 for (seg_key, seg_tag, element_idx, component_idx, val) in &field_values {
1464 let seg = if let Some(&pos) = seen_keys.get(seg_key) {
1465 &mut segments[pos]
1466 } else {
1467 let pos = segments.len();
1468 seen_keys.insert(seg_key.clone(), pos);
1469 segments.push(AssembledSegment {
1470 tag: seg_tag.clone(),
1471 elements: vec![],
1472 mig_number: None,
1473 });
1474 &mut segments[pos]
1475 };
1476
1477 while seg.elements.len() <= *element_idx {
1478 seg.elements.push(vec![]);
1479 }
1480 while seg.elements[*element_idx].len() <= *component_idx {
1481 seg.elements[*element_idx].push(String::new());
1482 }
1483 seg.elements[*element_idx][*component_idx] = val.clone();
1484 }
1485
1486 for seg in &mut segments {
1489 let last_populated = seg.elements.iter().rposition(|e| !e.is_empty());
1490 if let Some(last_idx) = last_populated {
1491 for i in 0..last_idx {
1492 if seg.elements[i].is_empty() {
1493 seg.elements[i] = vec![String::new()];
1494 }
1495 }
1496 }
1497 }
1498
1499 if let Some(ref ss) = self.segment_structure {
1501 for seg in &mut segments {
1502 if let Some(expected) = ss.element_count(&seg.tag) {
1503 while seg.elements.len() < expected {
1504 seg.elements.push(vec![String::new()]);
1505 }
1506 }
1507 }
1508 }
1509
1510 AssembledGroupInstance {
1511 segments,
1512 child_groups: vec![],
1513 entry_mig_number: None,
1514 variant_mig_numbers: vec![],
1515 skipped_segments: Vec::new(),
1516 }
1517 }
1518
1519 fn resolve_field_path(segment: &AssembledSegment, path: &[&str]) -> Option<String> {
1532 if path.is_empty() {
1533 return None;
1534 }
1535
1536 if let Ok(element_idx) = path[0].parse::<usize>() {
1538 let component_idx = if path.len() > 1 {
1539 path[1].parse::<usize>().unwrap_or(0)
1540 } else {
1541 0
1542 };
1543 return segment
1544 .elements
1545 .get(element_idx)?
1546 .get(component_idx)
1547 .filter(|v| !v.is_empty())
1548 .cloned();
1549 }
1550
1551 match path.len() {
1553 1 => segment
1554 .elements
1555 .first()?
1556 .first()
1557 .filter(|v| !v.is_empty())
1558 .cloned(),
1559 2 => segment
1560 .elements
1561 .get(1)?
1562 .first()
1563 .filter(|v| !v.is_empty())
1564 .cloned(),
1565 _ => None,
1566 }
1567 }
1568
1569 fn parse_element_component(parts: &[&str]) -> (usize, usize) {
1572 if parts.is_empty() {
1573 return (0, 0);
1574 }
1575 let element_idx = parts[0].parse::<usize>().unwrap_or(0);
1576 let component_idx = if parts.len() > 1 {
1577 parts[1].parse::<usize>().unwrap_or(0)
1578 } else {
1579 0
1580 };
1581 (element_idx, component_idx)
1582 }
1583
1584 pub fn populate_field(
1587 &self,
1588 bo4e_value: &serde_json::Value,
1589 target_field: &str,
1590 ) -> Option<String> {
1591 let mut current = bo4e_value;
1592 for part in target_field.split('.') {
1593 current = current.get(part)?;
1594 }
1595 if let Some(code) = current.get("code").and_then(|v| v.as_str()) {
1597 return Some(code.to_string());
1598 }
1599 current.as_str().map(|s| s.to_string())
1600 }
1601
1602 fn populate_field_json<'a>(
1605 &self,
1606 bo4e_value: &'a serde_json::Value,
1607 target_field: &str,
1608 ) -> Option<&'a serde_json::Value> {
1609 let mut current = bo4e_value;
1610 for part in target_field.split('.') {
1611 current = current.get(part)?;
1612 }
1613 Some(current)
1614 }
1615
1616 pub fn build_segment_from_bo4e(
1618 &self,
1619 bo4e_value: &serde_json::Value,
1620 segment_tag: &str,
1621 target_field: &str,
1622 ) -> AssembledSegment {
1623 let value = self.populate_field(bo4e_value, target_field);
1624 let elements = if let Some(val) = value {
1625 vec![vec![val]]
1626 } else {
1627 vec![]
1628 };
1629 AssembledSegment {
1630 tag: segment_tag.to_uppercase(),
1631 elements,
1632 mig_number: None,
1633 }
1634 }
1635
1636 pub fn resolve_repetition(
1645 tree: &AssembledTree,
1646 group_path: &str,
1647 discriminator: &str,
1648 ) -> Option<usize> {
1649 let (spec, expected) = discriminator.split_once('=')?;
1650 let parts: Vec<&str> = spec.split('.').collect();
1651 if parts.len() != 3 {
1652 return None;
1653 }
1654 let tag = parts[0];
1655 let element_idx: usize = parts[1].parse().ok()?;
1656 let component_idx: usize = parts[2].parse().ok()?;
1657
1658 let path_parts: Vec<&str> = group_path.split('.').collect();
1660
1661 let leaf_group = if path_parts.len() == 1 {
1662 let (group_id, _) = parse_group_spec(path_parts[0]);
1663 tree.groups.iter().find(|g| g.group_id == group_id)?
1664 } else {
1665 let parent_parts = &path_parts[..path_parts.len() - 1];
1667 let mut current_instance = {
1668 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1669 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
1670 first_group.repetitions.get(first_rep.unwrap_or(0))?
1671 };
1672 for part in &parent_parts[1..] {
1673 let (group_id, explicit_rep) = parse_group_spec(part);
1674 let child_group = current_instance
1675 .child_groups
1676 .iter()
1677 .find(|g| g.group_id == group_id)?;
1678 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
1679 }
1680 let (leaf_id, _) = parse_group_spec(path_parts.last()?);
1681 current_instance
1682 .child_groups
1683 .iter()
1684 .find(|g| g.group_id == leaf_id)?
1685 };
1686
1687 let expected_values: Vec<&str> = expected.split('|').collect();
1689 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1690 let matches = instance.segments.iter().any(|s| {
1691 s.tag.eq_ignore_ascii_case(tag)
1692 && s.elements
1693 .get(element_idx)
1694 .and_then(|e| e.get(component_idx))
1695 .map(|v| expected_values.iter().any(|ev| v == ev))
1696 .unwrap_or(false)
1697 });
1698 if matches {
1699 return Some(rep_idx);
1700 }
1701 }
1702
1703 None
1704 }
1705
1706 pub fn resolve_all_repetitions(
1711 tree: &AssembledTree,
1712 group_path: &str,
1713 discriminator: &str,
1714 ) -> Vec<usize> {
1715 let Some((spec, expected)) = discriminator.split_once('=') else {
1716 return Vec::new();
1717 };
1718 let parts: Vec<&str> = spec.split('.').collect();
1719 if parts.len() != 3 {
1720 return Vec::new();
1721 }
1722 let tag = parts[0];
1723 let element_idx: usize = match parts[1].parse() {
1724 Ok(v) => v,
1725 Err(_) => return Vec::new(),
1726 };
1727 let component_idx: usize = match parts[2].parse() {
1728 Ok(v) => v,
1729 Err(_) => return Vec::new(),
1730 };
1731
1732 let path_parts: Vec<&str> = group_path.split('.').collect();
1734
1735 let leaf_group = if path_parts.len() == 1 {
1736 let (group_id, _) = parse_group_spec(path_parts[0]);
1737 match tree.groups.iter().find(|g| g.group_id == group_id) {
1738 Some(g) => g,
1739 None => return Vec::new(),
1740 }
1741 } else {
1742 let parent_parts = &path_parts[..path_parts.len() - 1];
1743 let mut current_instance = {
1744 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1745 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
1746 Some(g) => g,
1747 None => return Vec::new(),
1748 };
1749 match first_group.repetitions.get(first_rep.unwrap_or(0)) {
1750 Some(i) => i,
1751 None => return Vec::new(),
1752 }
1753 };
1754 for part in &parent_parts[1..] {
1755 let (group_id, explicit_rep) = parse_group_spec(part);
1756 let child_group = match current_instance
1757 .child_groups
1758 .iter()
1759 .find(|g| g.group_id == group_id)
1760 {
1761 Some(g) => g,
1762 None => return Vec::new(),
1763 };
1764 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
1765 Some(i) => i,
1766 None => return Vec::new(),
1767 };
1768 }
1769 let (leaf_id, _) = match path_parts.last() {
1770 Some(p) => parse_group_spec(p),
1771 None => return Vec::new(),
1772 };
1773 match current_instance
1774 .child_groups
1775 .iter()
1776 .find(|g| g.group_id == leaf_id)
1777 {
1778 Some(g) => g,
1779 None => return Vec::new(),
1780 }
1781 };
1782
1783 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
1785
1786 let expected_values: Vec<&str> = expected_raw.split('|').collect();
1788 let mut result = Vec::new();
1789 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1790 let matches = instance.segments.iter().any(|s| {
1791 s.tag.eq_ignore_ascii_case(tag)
1792 && s.elements
1793 .get(element_idx)
1794 .and_then(|e| e.get(component_idx))
1795 .map(|v| expected_values.iter().any(|ev| v == ev))
1796 .unwrap_or(false)
1797 });
1798 if matches {
1799 result.push(rep_idx);
1800 }
1801 }
1802
1803 if let Some(occ) = occurrence {
1805 result.into_iter().nth(occ).into_iter().collect()
1806 } else {
1807 result
1808 }
1809 }
1810
1811 pub fn map_all_forward(&self, tree: &AssembledTree) -> serde_json::Value {
1833 self.map_all_forward_inner(tree, true).0
1834 }
1835
1836 pub fn map_all_forward_enriched(
1840 &self,
1841 tree: &AssembledTree,
1842 enrich_codes: bool,
1843 ) -> serde_json::Value {
1844 self.map_all_forward_inner(tree, enrich_codes).0
1845 }
1846
1847 fn map_all_forward_inner(
1854 &self,
1855 tree: &AssembledTree,
1856 enrich_codes: bool,
1857 ) -> (
1858 serde_json::Value,
1859 std::collections::HashMap<String, Vec<usize>>,
1860 ) {
1861 let mut result = serde_json::Map::new();
1862 let mut nesting_info: std::collections::HashMap<String, Vec<usize>> =
1863 std::collections::HashMap::new();
1864
1865 for def in &self.definitions {
1866 let entity = &def.meta.entity;
1867
1868 let bo4e = if let Some(ref disc) = def.meta.discriminator {
1869 let use_source_path = def
1874 .meta
1875 .source_path
1876 .as_ref()
1877 .is_some_and(|sp| has_source_path_qualifiers(sp));
1878 if use_source_path {
1879 let sp = def.meta.source_path.as_deref().unwrap();
1881 let all_instances = Self::resolve_all_by_source_path(tree, sp);
1882 let instances: Vec<_> = if let Some(matcher) = DiscriminatorMatcher::parse(disc)
1884 {
1885 matcher.filter_instances(all_instances)
1886 } else {
1887 all_instances
1888 };
1889 let extract = |instance: &AssembledGroupInstance| {
1890 let mut r = serde_json::Map::new();
1891 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
1892 self.extract_companion_fields(instance, def, &mut r, enrich_codes);
1893 serde_json::Value::Object(r)
1894 };
1895 match instances.len() {
1896 0 => None,
1897 1 => Some(extract(instances[0])),
1898 _ => Some(serde_json::Value::Array(
1899 instances.iter().map(|i| extract(i)).collect(),
1900 )),
1901 }
1902 } else {
1903 let reps = Self::resolve_all_repetitions(tree, &def.meta.source_group, disc);
1904 match reps.len() {
1905 0 => None,
1906 1 => Some(self.map_forward_inner(tree, def, reps[0], enrich_codes)),
1907 _ => Some(serde_json::Value::Array(
1908 reps.iter()
1909 .map(|&rep| self.map_forward_inner(tree, def, rep, enrich_codes))
1910 .collect(),
1911 )),
1912 }
1913 }
1914 } else if def.meta.source_group.is_empty() {
1915 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
1917 } else if def.meta.source_path.as_ref().is_some_and(|sp| {
1918 has_source_path_qualifiers(sp) || def.meta.source_group.contains('.')
1919 }) {
1920 let sp = def.meta.source_path.as_deref().unwrap();
1925 let mut indexed = Self::resolve_all_with_parent_indices(tree, sp);
1926
1927 if let Some(last_part) = sp.rsplit('.').next() {
1932 if !last_part.contains('_') {
1933 let base_prefix = if let Some(parent) = sp.rsplit_once('.') {
1937 format!("{}.", parent.0)
1938 } else {
1939 String::new()
1940 };
1941 let sibling_qualifiers: Vec<String> = self
1942 .definitions
1943 .iter()
1944 .filter_map(|d| d.meta.source_path.as_deref())
1945 .filter(|other_sp| {
1946 *other_sp != sp
1947 && other_sp.starts_with(&base_prefix)
1948 && other_sp.split('.').count() == sp.split('.').count()
1949 })
1950 .filter_map(|other_sp| {
1951 let other_last = other_sp.rsplit('.').next()?;
1952 let (base, q) = other_last.split_once('_')?;
1955 if base == last_part {
1956 Some(q.to_string())
1957 } else {
1958 None
1959 }
1960 })
1961 .collect();
1962
1963 if !sibling_qualifiers.is_empty() {
1964 indexed.retain(|(_, inst)| {
1965 let entry_qual = inst
1966 .segments
1967 .first()
1968 .and_then(|seg| seg.elements.first())
1969 .and_then(|el| el.first())
1970 .map(|v| v.to_lowercase());
1971 !entry_qual.is_some_and(|q| {
1974 sibling_qualifiers.iter().any(|sq| {
1975 sq.split('_').any(|part| part.eq_ignore_ascii_case(&q))
1976 })
1977 })
1978 });
1979 }
1980 }
1981 }
1982 let extract = |instance: &AssembledGroupInstance| {
1983 let mut r = serde_json::Map::new();
1984 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
1985 self.extract_companion_fields(instance, def, &mut r, enrich_codes);
1986 serde_json::Value::Object(r)
1987 };
1988 if def.meta.source_group.contains('.') && !indexed.is_empty() {
1993 if let Some(sp) = &def.meta.source_path {
1994 let parent_indices: Vec<usize> =
1995 indexed.iter().map(|(idx, _)| *idx).collect();
1996 nesting_info.entry(sp.clone()).or_insert(parent_indices);
1997
1998 let child_key = format!("{sp}#child");
2001 if let std::collections::hash_map::Entry::Vacant(e) =
2002 nesting_info.entry(child_key)
2003 {
2004 let child_indices: Vec<usize> =
2005 Self::compute_child_indices(tree, sp, &indexed);
2006 if !child_indices.is_empty() {
2007 e.insert(child_indices);
2008 }
2009 }
2010 }
2011 }
2012 match indexed.len() {
2013 0 => None,
2014 1 => Some(extract(indexed[0].1)),
2015 _ => Some(serde_json::Value::Array(
2016 indexed.iter().map(|(_, i)| extract(i)).collect(),
2017 )),
2018 }
2019 } else {
2020 let num_reps = Self::count_repetitions(tree, &def.meta.source_group);
2021 if num_reps <= 1 {
2022 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2023 } else {
2024 let mut items = Vec::with_capacity(num_reps);
2026 for rep in 0..num_reps {
2027 items.push(self.map_forward_inner(tree, def, rep, enrich_codes));
2028 }
2029 Some(serde_json::Value::Array(items))
2030 }
2031 };
2032
2033 if let Some(bo4e) = bo4e {
2034 let bo4e = inject_bo4e_metadata(bo4e, &def.meta.bo4e_type);
2035 let key = to_camel_case(entity);
2036 deep_merge_insert(&mut result, &key, bo4e);
2037 }
2038 }
2039
2040 nest_child_entities_in_result(&mut result, &self.definitions, &nesting_info);
2043
2044 (serde_json::Value::Object(result), nesting_info)
2045 }
2046
2047 pub fn map_all_reverse(
2056 &self,
2057 entities: &serde_json::Value,
2058 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2059 ) -> AssembledTree {
2060 let mut root_segments: Vec<AssembledSegment> = Vec::new();
2061 let mut groups: Vec<AssembledGroup> = Vec::new();
2062 let mut inferred_nesting: std::collections::HashMap<String, Vec<usize>> =
2065 std::collections::HashMap::new();
2066
2067 for def in &self.definitions {
2068 let entity_key = to_camel_case(&def.meta.entity);
2069
2070 let _extracted: Option<serde_json::Value>;
2073 let entity_value = if let Some(v) = entities.get(&entity_key) {
2074 _extracted = None;
2075 v
2076 } else if def.meta.source_group.contains('.') {
2077 match extract_child_from_parent_with_indices(
2079 entities,
2080 &self.definitions,
2081 def,
2082 ) {
2083 Some((v, parent_indices)) => {
2084 if let Some(sp) = def.meta.source_path.as_deref() {
2086 inferred_nesting
2087 .entry(sp.to_string())
2088 .or_insert(parent_indices);
2089 }
2090 _extracted = Some(v);
2091 _extracted.as_ref().unwrap()
2092 }
2093 None => continue,
2094 }
2095 } else {
2096 continue;
2097 };
2098
2099 let unwrapped: Option<serde_json::Value>;
2107 let entity_value = if entity_value.is_object() && !entity_value.is_array() {
2108 if let Some(disc_value) = def
2109 .meta
2110 .discriminator
2111 .as_deref()
2112 .and_then(|d| d.split_once('='))
2113 .map(|(_, v)| v)
2114 {
2115 if let Some(inner) = entity_value.get(disc_value) {
2117 let mut injected = inner.clone();
2118 if let Some(ref cf) = def.companion_fields {
2121 let disc_path = def
2122 .meta
2123 .discriminator
2124 .as_deref()
2125 .unwrap()
2126 .split_once('=')
2127 .unwrap()
2128 .0
2129 .to_lowercase();
2130 for (path, mapping) in cf {
2131 let cf_path = path.to_lowercase();
2135 let matches = cf_path == disc_path
2136 || format!("{}.0", cf_path) == disc_path;
2137 if matches {
2138 let target = match mapping {
2139 FieldMapping::Simple(t) => t.as_str(),
2140 FieldMapping::Structured(s) => s.target.as_str(),
2141 FieldMapping::Nested(_) => continue,
2142 };
2143 if !target.is_empty() {
2144 if let Some(obj) = injected.as_object_mut() {
2145 let entry = obj.entry(target.to_string())
2146 .or_insert(serde_json::Value::Null);
2147 if entry.is_null() {
2148 *entry = serde_json::Value::String(
2149 disc_value.to_string(),
2150 );
2151 }
2152 }
2153 }
2154 break;
2155 }
2156 }
2157 }
2158 unwrapped = Some(injected);
2159 unwrapped.as_ref().unwrap()
2160 } else {
2161 entity_value
2162 }
2163 } else if is_map_keyed_object(entity_value) {
2164 let map = entity_value.as_object().unwrap();
2169 let arr: Vec<serde_json::Value> = map
2170 .iter()
2171 .map(|(key, val)| {
2172 let mut item = val.clone();
2173 if let Some(obj) = item.as_object_mut() {
2176 if let Some(qualifier_field) =
2177 find_qualifier_companion_field(&self.definitions, &def.meta.entity)
2178 {
2179 let entry = obj.entry(qualifier_field).or_insert(serde_json::Value::Null);
2180 if entry.is_null() {
2181 *entry = serde_json::Value::String(key.clone());
2182 }
2183 }
2184 }
2185 item
2186 })
2187 .collect();
2188 unwrapped = Some(serde_json::Value::Array(arr));
2189 unwrapped.as_ref().unwrap()
2190 } else {
2191 entity_value
2192 }
2193 } else {
2194 entity_value
2195 };
2196
2197 let leaf_group = def
2199 .meta
2200 .source_group
2201 .rsplit('.')
2202 .next()
2203 .unwrap_or(&def.meta.source_group);
2204
2205 if def.meta.source_group.is_empty() {
2206 let instance = self.map_reverse(entity_value, def);
2208 root_segments.extend(instance.segments);
2209 } else if entity_value.is_array() {
2210 let arr = entity_value.as_array().unwrap();
2212 let reps: Vec<_> = arr.iter().map(|item| self.map_reverse(item, def)).collect();
2213
2214 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2216 existing.repetitions.extend(reps);
2217 } else {
2218 groups.push(AssembledGroup {
2219 group_id: leaf_group.to_string(),
2220 repetitions: reps,
2221 });
2222 }
2223 } else {
2224 let instance = self.map_reverse(entity_value, def);
2226
2227 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2228 existing.repetitions.push(instance);
2229 } else {
2230 groups.push(AssembledGroup {
2231 group_id: leaf_group.to_string(),
2232 repetitions: vec![instance],
2233 });
2234 }
2235 }
2236 }
2237
2238 let nested_specs: Vec<(String, String)> = self
2244 .definitions
2245 .iter()
2246 .filter_map(|def| {
2247 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
2248 if parts.len() > 1 {
2249 Some((parts[0].to_string(), parts[parts.len() - 1].to_string()))
2250 } else {
2251 None
2252 }
2253 })
2254 .collect();
2255 for (parent_id, child_id) in &nested_specs {
2256 let has_parent = groups.iter().any(|g| g.group_id == *parent_id);
2258 let has_child = groups.iter().any(|g| g.group_id == *child_id);
2259 if has_parent && has_child {
2260 let child_idx = groups.iter().position(|g| g.group_id == *child_id).unwrap();
2261 let child_group = groups.remove(child_idx);
2262 let parent = groups
2263 .iter_mut()
2264 .find(|g| g.group_id == *parent_id)
2265 .unwrap();
2266 let child_source_path = self
2270 .definitions
2271 .iter()
2272 .find(|d| {
2273 let parts: Vec<&str> = d.meta.source_group.split('.').collect();
2274 parts.len() > 1 && parts[parts.len() - 1] == *child_id
2275 })
2276 .and_then(|d| d.meta.source_path.as_deref());
2277 let distribution = child_source_path
2278 .and_then(|key| {
2279 nesting_info
2280 .and_then(|ni| ni.get(key))
2281 .or_else(|| inferred_nesting.get(key))
2282 });
2283 for (i, child_rep) in child_group.repetitions.into_iter().enumerate() {
2284 let target_idx = distribution
2285 .and_then(|dist| dist.get(i))
2286 .copied()
2287 .unwrap_or(0);
2288
2289 if let Some(target_rep) = parent.repetitions.get_mut(target_idx) {
2290 if let Some(existing) = target_rep
2291 .child_groups
2292 .iter_mut()
2293 .find(|g| g.group_id == *child_id)
2294 {
2295 existing.repetitions.push(child_rep);
2296 } else {
2297 target_rep.child_groups.push(AssembledGroup {
2298 group_id: child_id.clone(),
2299 repetitions: vec![child_rep],
2300 });
2301 }
2302 }
2303 }
2304 }
2305 }
2306
2307 let post_group_start = root_segments.len();
2308 AssembledTree {
2309 segments: root_segments,
2310 groups,
2311 post_group_start,
2312 inter_group_segments: std::collections::BTreeMap::new(),
2313 }
2314 }
2315
2316 fn count_repetitions(tree: &AssembledTree, group_path: &str) -> usize {
2318 let parts: Vec<&str> = group_path.split('.').collect();
2319
2320 let (first_id, first_rep) = parse_group_spec(parts[0]);
2321 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2322 Some(g) => g,
2323 None => return 0,
2324 };
2325
2326 if parts.len() == 1 {
2327 return first_group.repetitions.len();
2328 }
2329
2330 let mut current_instance = match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2332 Some(i) => i,
2333 None => return 0,
2334 };
2335
2336 for (i, part) in parts[1..].iter().enumerate() {
2337 let (group_id, explicit_rep) = parse_group_spec(part);
2338 let child_group = match current_instance
2339 .child_groups
2340 .iter()
2341 .find(|g| g.group_id == group_id)
2342 {
2343 Some(g) => g,
2344 None => return 0,
2345 };
2346
2347 if i == parts.len() - 2 {
2348 return child_group.repetitions.len();
2350 }
2351 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2352 Some(i) => i,
2353 None => return 0,
2354 };
2355 }
2356
2357 0
2358 }
2359
2360 pub fn map_interchange(
2369 msg_engine: &MappingEngine,
2370 tx_engine: &MappingEngine,
2371 tree: &AssembledTree,
2372 transaction_group: &str,
2373 enrich_codes: bool,
2374 ) -> crate::model::MappedMessage {
2375 let (stammdaten, nesting_info) = msg_engine.map_all_forward_inner(tree, enrich_codes);
2377
2378 let transaktionen = tree
2380 .groups
2381 .iter()
2382 .find(|g| g.group_id == transaction_group)
2383 .map(|sg| {
2384 sg.repetitions
2385 .iter()
2386 .map(|instance| {
2387 let wrapped_tree = AssembledTree {
2390 segments: vec![],
2391 groups: vec![AssembledGroup {
2392 group_id: transaction_group.to_string(),
2393 repetitions: vec![instance.clone()],
2394 }],
2395 post_group_start: 0,
2396 inter_group_segments: std::collections::BTreeMap::new(),
2397 };
2398
2399 let (tx_result, tx_nesting) =
2400 tx_engine.map_all_forward_inner(&wrapped_tree, enrich_codes);
2401
2402 crate::model::MappedTransaktion {
2403 stammdaten: tx_result,
2404 nesting_info: tx_nesting,
2405 }
2406 })
2407 .collect()
2408 })
2409 .unwrap_or_default();
2410
2411 crate::model::MappedMessage {
2412 stammdaten,
2413 transaktionen,
2414 nesting_info,
2415 }
2416 }
2417
2418 pub fn map_interchange_reverse(
2428 msg_engine: &MappingEngine,
2429 tx_engine: &MappingEngine,
2430 mapped: &crate::model::MappedMessage,
2431 transaction_group: &str,
2432 filtered_mig: Option<&MigSchema>,
2433 ) -> AssembledTree {
2434 let msg_tree = msg_engine.map_all_reverse(
2436 &mapped.stammdaten,
2437 if mapped.nesting_info.is_empty() {
2438 None
2439 } else {
2440 Some(&mapped.nesting_info)
2441 },
2442 );
2443
2444 let mut sg4_reps: Vec<AssembledGroupInstance> = Vec::new();
2446
2447 struct DefWithMeta<'a> {
2451 def: &'a MappingDefinition,
2452 relative: String,
2453 depth: usize,
2454 }
2455
2456 let mut sorted_defs: Vec<DefWithMeta> = tx_engine
2457 .definitions
2458 .iter()
2459 .map(|def| {
2460 let relative = strip_tx_group_prefix(&def.meta.source_group, transaction_group);
2461 let depth = if relative.is_empty() {
2462 0
2463 } else {
2464 relative.chars().filter(|c| *c == '.').count() + 1
2465 };
2466 DefWithMeta {
2467 def,
2468 relative,
2469 depth,
2470 }
2471 })
2472 .collect();
2473
2474 let mut parent_rep_map: std::collections::HashMap<String, usize> =
2478 std::collections::HashMap::new();
2479 for dm in &sorted_defs {
2480 if dm.depth >= 2 {
2481 let parts: Vec<&str> = dm.relative.split('.').collect();
2482 let (_, parent_rep) = parse_group_spec(parts[0]);
2483 if let Some(rep_idx) = parent_rep {
2484 if let Some(sp) = &dm.def.meta.source_path {
2485 if let Some((parent_path, _)) = sp.rsplit_once('.') {
2486 parent_rep_map
2487 .entry(parent_path.to_string())
2488 .or_insert(rep_idx);
2489 }
2490 }
2491 }
2492 }
2493 }
2494
2495 for dm in &mut sorted_defs {
2498 if dm.depth == 1 && !dm.relative.contains(':') {
2499 if let Some(sp) = &dm.def.meta.source_path {
2500 if let Some(rep_idx) = parent_rep_map.get(sp.as_str()) {
2501 dm.relative = format!("{}:{}", dm.relative, rep_idx);
2502 }
2503 }
2504 }
2505 }
2506
2507 if let Some(mig) = filtered_mig {
2514 let mig_order = build_reverse_mig_group_order(mig, transaction_group);
2515 sorted_defs.sort_by(|a, b| {
2516 a.depth.cmp(&b.depth).then_with(|| {
2517 let a_id = a.relative.split(':').next().unwrap_or(&a.relative);
2518 let b_id = b.relative.split(':').next().unwrap_or(&b.relative);
2519 let a_pos = variant_mig_position(a.def, a_id, &mig_order);
2521 let b_pos = variant_mig_position(b.def, b_id, &mig_order);
2522 a_pos.cmp(&b_pos).then(a.relative.cmp(&b.relative))
2523 })
2524 });
2525 } else {
2526 sorted_defs.sort_by(|a, b| a.depth.cmp(&b.depth).then(a.relative.cmp(&b.relative)));
2527 }
2528
2529 for tx in &mapped.transaktionen {
2530 let mut root_segs: Vec<AssembledSegment> = Vec::new();
2531 let mut child_groups: Vec<AssembledGroup> = Vec::new();
2532
2533 let mut source_path_to_rep: std::collections::HashMap<String, Vec<usize>> =
2538 std::collections::HashMap::new();
2539
2540 for dm in &sorted_defs {
2541 let entity_key = to_camel_case(&dm.def.meta.entity);
2544 let _tx_extracted: Option<serde_json::Value>;
2545 let bo4e_value = if let Some(v) = tx.stammdaten.get(&entity_key) {
2546 _tx_extracted = None;
2547 v
2548 } else if dm.def.meta.source_group.contains('.') {
2549 match extract_child_from_parent(
2550 &tx.stammdaten,
2551 &tx_engine.definitions,
2552 dm.def,
2553 ) {
2554 Some(v) => {
2555 _tx_extracted = Some(v);
2556 _tx_extracted.as_ref().unwrap()
2557 }
2558 None => continue,
2559 }
2560 } else {
2561 continue;
2562 };
2563
2564 let unwrapped_value: Option<serde_json::Value>;
2566 let bo4e_value = if bo4e_value.is_object() && !bo4e_value.is_array() {
2567 if let Some(disc_value) = dm
2568 .def
2569 .meta
2570 .discriminator
2571 .as_deref()
2572 .and_then(|d| d.split_once('='))
2573 .map(|(_, v)| v)
2574 {
2575 if let Some(inner) = bo4e_value.get(disc_value) {
2576 let mut injected = inner.clone();
2577 if let Some(ref cf) = dm.def.companion_fields {
2578 let disc_path = dm
2579 .def
2580 .meta
2581 .discriminator
2582 .as_deref()
2583 .unwrap()
2584 .split_once('=')
2585 .unwrap()
2586 .0
2587 .to_lowercase();
2588 for (path, mapping) in cf {
2589 let cf_path = path.to_lowercase();
2590 let matches = cf_path == disc_path
2591 || format!("{}.0", cf_path) == disc_path;
2592 if matches {
2593 let target = match mapping {
2594 FieldMapping::Simple(t) => t.as_str(),
2595 FieldMapping::Structured(s) => s.target.as_str(),
2596 FieldMapping::Nested(_) => continue,
2597 };
2598 if !target.is_empty() {
2599 if let Some(obj) = injected.as_object_mut() {
2600 obj.entry(target.to_string()).or_insert_with(
2601 || {
2602 serde_json::Value::String(
2603 disc_value.to_string(),
2604 )
2605 },
2606 );
2607 }
2608 }
2609 break;
2610 }
2611 }
2612 }
2613 unwrapped_value = Some(injected);
2614 unwrapped_value.as_ref().unwrap()
2615 } else {
2616 bo4e_value
2617 }
2618 } else if is_map_keyed_object(bo4e_value) {
2619 let map = bo4e_value.as_object().unwrap();
2620 let arr: Vec<serde_json::Value> = map
2621 .iter()
2622 .map(|(key, val)| {
2623 let mut item = val.clone();
2624 if let Some(obj) = item.as_object_mut() {
2625 if let Some(qualifier_field) =
2626 find_qualifier_companion_field(
2627 &tx_engine.definitions,
2628 &dm.def.meta.entity,
2629 )
2630 {
2631 let entry = obj.entry(qualifier_field).or_insert(serde_json::Value::Null);
2632 if entry.is_null() {
2633 *entry = serde_json::Value::String(key.clone());
2634 }
2635 }
2636 }
2637 item
2638 })
2639 .collect();
2640 unwrapped_value = Some(serde_json::Value::Array(arr));
2641 unwrapped_value.as_ref().unwrap()
2642 } else {
2643 bo4e_value
2644 }
2645 } else {
2646 bo4e_value
2647 };
2648
2649 let items: Vec<&serde_json::Value> = if bo4e_value.is_array() {
2653 bo4e_value.as_array().unwrap().iter().collect()
2654 } else {
2655 vec![bo4e_value]
2656 };
2657
2658 for (item_idx, item) in items.iter().enumerate() {
2659 let instance = tx_engine.map_reverse(item, dm.def);
2660
2661 if instance.segments.is_empty() && instance.child_groups.is_empty() {
2663 continue;
2664 }
2665
2666 if dm.relative.is_empty() {
2667 root_segs.extend(instance.segments);
2668 } else {
2669 let effective_relative = if dm.depth >= 2 {
2673 let rel = if items.len() > 1 {
2676 strip_all_rep_indices(&dm.relative)
2677 } else {
2678 dm.relative.clone()
2679 };
2680 let skip_nesting = dm
2687 .def
2688 .meta
2689 .source_path
2690 .as_ref()
2691 .and_then(|sp| sp.rsplit_once('.'))
2692 .and_then(|(parent_path, _)| {
2693 source_path_to_rep.get(parent_path)
2694 })
2695 .is_some_and(|reps| reps.len() == 1);
2696 let nesting_idx = if items.len() > 1 && !skip_nesting {
2697 dm.def
2698 .meta
2699 .source_path
2700 .as_ref()
2701 .and_then(|sp| tx.nesting_info.get(sp))
2702 .and_then(|dist| dist.get(item_idx))
2703 .copied()
2704 } else {
2705 None
2706 };
2707 if let Some(parent_rep) = nesting_idx {
2708 let parts: Vec<&str> = rel.split('.').collect();
2710 let parent_id = parts[0].split(':').next().unwrap_or(parts[0]);
2711 let rest = parts[1..].join(".");
2712 format!("{}:{}.{}", parent_id, parent_rep, rest)
2713 } else {
2714 resolve_child_relative(
2715 &rel,
2716 dm.def.meta.source_path.as_deref(),
2717 &source_path_to_rep,
2718 item_idx,
2719 )
2720 }
2721 } else if dm.depth == 1 {
2722 let child_key = dm
2725 .def
2726 .meta
2727 .source_path
2728 .as_ref()
2729 .map(|sp| format!("{sp}#child"));
2730 if let Some(child_indices) =
2731 child_key.as_ref().and_then(|ck| tx.nesting_info.get(ck))
2732 {
2733 if let Some(&target) = child_indices.get(item_idx) {
2734 if target != usize::MAX {
2735 let base =
2736 dm.relative.split(':').next().unwrap_or(&dm.relative);
2737 format!("{}:{}", base, target)
2738 } else {
2739 dm.relative.clone()
2740 }
2741 } else if items.len() > 1 && item_idx > 0 {
2742 strip_rep_index(&dm.relative)
2743 } else {
2744 dm.relative.clone()
2745 }
2746 } else if items.len() > 1 && item_idx > 0 {
2747 strip_rep_index(&dm.relative)
2748 } else {
2749 dm.relative.clone()
2750 }
2751 } else if items.len() > 1 && item_idx > 0 {
2752 strip_rep_index(&dm.relative)
2755 } else {
2756 dm.relative.clone()
2757 };
2758
2759 let rep_used =
2760 place_in_groups(&mut child_groups, &effective_relative, instance);
2761
2762 if dm.depth == 1 {
2764 if let Some(sp) = &dm.def.meta.source_path {
2765 source_path_to_rep
2766 .entry(sp.clone())
2767 .or_default()
2768 .push(rep_used);
2769 }
2770 }
2771 }
2772 }
2773 }
2774
2775 if let Some(mig) = filtered_mig {
2780 sort_variant_reps_by_mig(&mut child_groups, mig, transaction_group);
2781 }
2782
2783 sg4_reps.push(AssembledGroupInstance {
2784 segments: root_segs,
2785 child_groups,
2786 entry_mig_number: None,
2787 variant_mig_numbers: vec![],
2788 skipped_segments: Vec::new(),
2789 });
2790 }
2791
2792 let mut root_segments = Vec::new();
2799 let mut uns_segments = Vec::new();
2800 let mut uns_is_summary = false;
2801 let mut found_uns = false;
2802 for seg in msg_tree.segments {
2803 if seg.tag == "UNS" {
2804 uns_is_summary = seg
2806 .elements
2807 .first()
2808 .and_then(|el| el.first())
2809 .map(|v| v == "S")
2810 .unwrap_or(false);
2811 uns_segments.push(seg);
2812 found_uns = true;
2813 } else if found_uns {
2814 uns_segments.push(seg);
2816 } else {
2817 root_segments.push(seg);
2818 }
2819 }
2820
2821 let pre_group_count = root_segments.len();
2822 let mut all_groups = msg_tree.groups;
2823 let mut inter_group = msg_tree.inter_group_segments;
2824
2825 let sg_num = |id: &str| -> usize {
2827 id.strip_prefix("SG")
2828 .and_then(|n| n.parse::<usize>().ok())
2829 .unwrap_or(0)
2830 };
2831
2832 if !sg4_reps.is_empty() {
2833 if uns_is_summary {
2834 all_groups.push(AssembledGroup {
2836 group_id: transaction_group.to_string(),
2837 repetitions: sg4_reps,
2838 });
2839 if !uns_segments.is_empty() {
2840 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2845 let tx_num = sg_num(transaction_group);
2846 let uns_pos = all_groups
2847 .iter()
2848 .rposition(|g| sg_num(&g.group_id) <= tx_num)
2849 .map(|i| i + 1)
2850 .unwrap_or(all_groups.len());
2851 inter_group.insert(uns_pos, uns_segments);
2852 }
2853 } else {
2854 if !uns_segments.is_empty() {
2856 inter_group.insert(all_groups.len(), uns_segments);
2857 }
2858 all_groups.push(AssembledGroup {
2859 group_id: transaction_group.to_string(),
2860 repetitions: sg4_reps,
2861 });
2862 }
2863 } else if !uns_segments.is_empty() {
2864 if transaction_group.is_empty() {
2865 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2870 if uns_is_summary {
2871 inter_group.insert(all_groups.len(), uns_segments);
2872 } else {
2873 inter_group.insert(0, uns_segments);
2874 }
2875 } else {
2876 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2880 let tx_num = sg_num(transaction_group);
2881 let uns_pos = all_groups
2882 .iter()
2883 .rposition(|g| sg_num(&g.group_id) <= tx_num)
2884 .map(|i| i + 1)
2885 .unwrap_or(all_groups.len());
2886 inter_group.insert(uns_pos, uns_segments);
2887 }
2888 }
2889
2890 AssembledTree {
2891 segments: root_segments,
2892 groups: all_groups,
2893 post_group_start: pre_group_count,
2894 inter_group_segments: inter_group,
2895 }
2896 }
2897
2898 pub fn build_group_from_bo4e(
2900 &self,
2901 bo4e_value: &serde_json::Value,
2902 def: &MappingDefinition,
2903 ) -> AssembledGroup {
2904 let instance = self.map_reverse(bo4e_value, def);
2905 let leaf_group = def
2906 .meta
2907 .source_group
2908 .rsplit('.')
2909 .next()
2910 .unwrap_or(&def.meta.source_group);
2911
2912 AssembledGroup {
2913 group_id: leaf_group.to_string(),
2914 repetitions: vec![instance],
2915 }
2916 }
2917
2918 pub fn map_interchange_typed<M, T>(
2926 msg_engine: &MappingEngine,
2927 tx_engine: &MappingEngine,
2928 tree: &AssembledTree,
2929 tx_group: &str,
2930 enrich_codes: bool,
2931 nachrichtendaten: crate::model::Nachrichtendaten,
2932 interchangedaten: crate::model::Interchangedaten,
2933 ) -> Result<crate::model::Interchange<M, T>, serde_json::Error>
2934 where
2935 M: serde::de::DeserializeOwned,
2936 T: serde::de::DeserializeOwned,
2937 {
2938 let mapped = Self::map_interchange(msg_engine, tx_engine, tree, tx_group, enrich_codes);
2939 let nachricht = mapped.into_dynamic_nachricht(nachrichtendaten);
2940 let dynamic = crate::model::DynamicInterchange {
2941 interchangedaten,
2942 nachrichten: vec![nachricht],
2943 };
2944 let value = serde_json::to_value(&dynamic)?;
2945 serde_json::from_value(value)
2946 }
2947
2948 pub fn map_interchange_reverse_typed<M, T>(
2955 msg_engine: &MappingEngine,
2956 tx_engine: &MappingEngine,
2957 nachricht: &crate::model::Nachricht<M, T>,
2958 tx_group: &str,
2959 ) -> Result<AssembledTree, serde_json::Error>
2960 where
2961 M: serde::Serialize,
2962 T: serde::Serialize,
2963 {
2964 let stammdaten = serde_json::to_value(&nachricht.stammdaten)?;
2965 let transaktionen: Vec<crate::model::MappedTransaktion> = nachricht
2966 .transaktionen
2967 .iter()
2968 .map(|t| {
2969 Ok(crate::model::MappedTransaktion {
2970 stammdaten: serde_json::to_value(t)?,
2971 nesting_info: Default::default(),
2972 })
2973 })
2974 .collect::<Result<Vec<_>, serde_json::Error>>()?;
2975 let mapped = crate::model::MappedMessage {
2976 stammdaten,
2977 transaktionen,
2978 nesting_info: Default::default(),
2979 };
2980 Ok(Self::map_interchange_reverse(
2981 msg_engine, tx_engine, &mapped, tx_group, None,
2982 ))
2983 }
2984}
2985
2986fn parse_source_path_part(part: &str) -> (&str, Option<&str>) {
2993 if let Some(pos) = part.find('_') {
2997 let group = &part[..pos];
2998 let qualifier = &part[pos + 1..];
2999 if !qualifier.is_empty() {
3000 return (group, Some(qualifier));
3001 }
3002 }
3003 (part, None)
3004}
3005
3006fn build_reverse_mig_group_order(mig: &MigSchema, tx_group_id: &str) -> HashMap<String, usize> {
3014 let mut order = HashMap::new();
3015 if let Some(tg) = mig.segment_groups.iter().find(|g| g.id == tx_group_id) {
3016 for (i, nested) in tg.nested_groups.iter().enumerate() {
3017 if let Some(ref vc) = nested.variant_code {
3019 let variant_key = format!("{}_{}", nested.id, vc.to_uppercase());
3020 order.insert(variant_key, i);
3021 }
3022 order.entry(nested.id.clone()).or_insert(i);
3024 }
3025 }
3026 order
3027}
3028
3029fn variant_mig_position(
3035 def: &MappingDefinition,
3036 base_group_id: &str,
3037 mig_order: &HashMap<String, usize>,
3038) -> usize {
3039 if let Some(ref sp) = def.meta.source_path {
3042 let base_lower = base_group_id.to_lowercase();
3044 for part in sp.split('.') {
3045 if part.starts_with(&base_lower)
3046 || part.starts_with(base_group_id.to_lowercase().as_str())
3047 {
3048 if let Some(underscore_pos) = part.find('_') {
3050 let qualifier = &part[underscore_pos + 1..];
3051 let variant_key = format!("{}_{}", base_group_id, qualifier.to_uppercase());
3052 if let Some(&pos) = mig_order.get(&variant_key) {
3053 return pos;
3054 }
3055 }
3056 }
3057 }
3058 }
3059 mig_order.get(base_group_id).copied().unwrap_or(usize::MAX)
3061}
3062
3063fn find_rep_by_entry_qualifier<'a>(
3068 reps: &'a [AssembledGroupInstance],
3069 qualifier: &str,
3070) -> Option<&'a AssembledGroupInstance> {
3071 let parts: Vec<&str> = qualifier.split('_').collect();
3073 reps.iter().find(|inst| {
3074 inst.segments.first().is_some_and(|seg| {
3075 seg.elements
3076 .first()
3077 .and_then(|e| e.first())
3078 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3079 })
3080 })
3081}
3082
3083fn find_all_reps_by_entry_qualifier<'a>(
3085 reps: &'a [AssembledGroupInstance],
3086 qualifier: &str,
3087) -> Vec<&'a AssembledGroupInstance> {
3088 let parts: Vec<&str> = qualifier.split('_').collect();
3090 reps.iter()
3091 .filter(|inst| {
3092 inst.segments.first().is_some_and(|seg| {
3093 seg.elements
3094 .first()
3095 .and_then(|e| e.first())
3096 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3097 })
3098 })
3099 .collect()
3100}
3101
3102fn has_source_path_qualifiers(source_path: &str) -> bool {
3104 source_path.split('.').any(|part| {
3105 if let Some(pos) = part.find('_') {
3106 pos < part.len() - 1
3107 } else {
3108 false
3109 }
3110 })
3111}
3112
3113fn parse_group_spec(part: &str) -> (&str, Option<usize>) {
3114 if let Some(colon_pos) = part.find(':') {
3115 let id = &part[..colon_pos];
3116 let rep = part[colon_pos + 1..].parse::<usize>().ok();
3117 (id, rep)
3118 } else {
3119 (part, None)
3120 }
3121}
3122
3123fn strip_tx_group_prefix(source_group: &str, tx_group: &str) -> String {
3129 if source_group == tx_group || source_group.is_empty() {
3130 String::new()
3131 } else if let Some(rest) = source_group.strip_prefix(tx_group) {
3132 rest.strip_prefix('.').unwrap_or(rest).to_string()
3133 } else {
3134 source_group.to_string()
3135 }
3136}
3137
3138fn place_in_groups(
3146 groups: &mut Vec<AssembledGroup>,
3147 relative_path: &str,
3148 instance: AssembledGroupInstance,
3149) -> usize {
3150 let parts: Vec<&str> = relative_path.split('.').collect();
3151
3152 if parts.len() == 1 {
3153 let (id, rep) = parse_group_spec(parts[0]);
3155
3156 let group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == id) {
3158 g
3159 } else {
3160 groups.push(AssembledGroup {
3161 group_id: id.to_string(),
3162 repetitions: vec![],
3163 });
3164 groups.last_mut().unwrap()
3165 };
3166
3167 if let Some(rep_idx) = rep {
3168 while group.repetitions.len() <= rep_idx {
3170 group.repetitions.push(AssembledGroupInstance {
3171 segments: vec![],
3172 child_groups: vec![],
3173 entry_mig_number: None,
3174 variant_mig_numbers: vec![],
3175 skipped_segments: Vec::new(),
3176 });
3177 }
3178 group.repetitions[rep_idx]
3179 .segments
3180 .extend(instance.segments);
3181 group.repetitions[rep_idx]
3182 .child_groups
3183 .extend(instance.child_groups);
3184 rep_idx
3185 } else {
3186 let pos = group.repetitions.len();
3188 group.repetitions.push(instance);
3189 pos
3190 }
3191 } else {
3192 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3194 let rep_idx = parent_rep.unwrap_or(0);
3195
3196 let parent_group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == parent_id) {
3198 g
3199 } else {
3200 groups.push(AssembledGroup {
3201 group_id: parent_id.to_string(),
3202 repetitions: vec![],
3203 });
3204 groups.last_mut().unwrap()
3205 };
3206
3207 while parent_group.repetitions.len() <= rep_idx {
3209 parent_group.repetitions.push(AssembledGroupInstance {
3210 segments: vec![],
3211 child_groups: vec![],
3212 entry_mig_number: None,
3213 variant_mig_numbers: vec![],
3214 skipped_segments: Vec::new(),
3215 });
3216 }
3217
3218 let remaining = parts[1..].join(".");
3219 place_in_groups(
3220 &mut parent_group.repetitions[rep_idx].child_groups,
3221 &remaining,
3222 instance,
3223 );
3224 rep_idx
3225 }
3226}
3227
3228fn resolve_child_relative(
3240 relative: &str,
3241 source_path: Option<&str>,
3242 source_path_to_rep: &std::collections::HashMap<String, Vec<usize>>,
3243 item_idx: usize,
3244) -> String {
3245 let parts: Vec<&str> = relative.split('.').collect();
3246 if parts.is_empty() {
3247 return relative.to_string();
3248 }
3249
3250 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3252 if parent_rep.is_some() {
3253 return relative.to_string();
3254 }
3255
3256 if let Some(sp) = source_path {
3258 if let Some((parent_path, _child)) = sp.rsplit_once('.') {
3259 if let Some(rep_indices) = source_path_to_rep.get(parent_path) {
3260 let rep_idx = rep_indices
3262 .get(item_idx)
3263 .or_else(|| rep_indices.last())
3264 .copied()
3265 .unwrap_or(0);
3266 let rest = parts[1..].join(".");
3267 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3268 }
3269 }
3270 }
3271
3272 relative.to_string()
3274}
3275
3276struct DiscriminatorMatcher<'a> {
3283 tag: &'a str,
3284 element_idx: usize,
3285 component_idx: usize,
3286 expected_values: Vec<&'a str>,
3287 occurrence: Option<usize>,
3289}
3290
3291impl<'a> DiscriminatorMatcher<'a> {
3292 fn parse(disc: &'a str) -> Option<Self> {
3293 let (spec, expected) = disc.split_once('=')?;
3294 let parts: Vec<&str> = spec.split('.').collect();
3295 if parts.len() != 3 {
3296 return None;
3297 }
3298 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
3299 Some(Self {
3300 tag: parts[0],
3301 element_idx: parts[1].parse().ok()?,
3302 component_idx: parts[2].parse().ok()?,
3303 expected_values: expected_raw.split('|').collect(),
3304 occurrence,
3305 })
3306 }
3307
3308 fn matches(&self, instance: &AssembledGroupInstance) -> bool {
3309 instance.segments.iter().any(|s| {
3310 s.tag.eq_ignore_ascii_case(self.tag)
3311 && s.elements
3312 .get(self.element_idx)
3313 .and_then(|e| e.get(self.component_idx))
3314 .map(|v| self.expected_values.iter().any(|ev| v == ev))
3315 .unwrap_or(false)
3316 })
3317 }
3318
3319 fn filter_instances<'b>(
3321 &self,
3322 instances: Vec<&'b AssembledGroupInstance>,
3323 ) -> Vec<&'b AssembledGroupInstance> {
3324 let matching: Vec<_> = instances
3325 .into_iter()
3326 .filter(|inst| self.matches(inst))
3327 .collect();
3328 if let Some(occ) = self.occurrence {
3329 matching.into_iter().nth(occ).into_iter().collect()
3330 } else {
3331 matching
3332 }
3333 }
3334}
3335
3336fn parse_discriminator_occurrence(expected: &str) -> (&str, Option<usize>) {
3342 if let Some(hash_pos) = expected.rfind('#') {
3343 if let Ok(occ) = expected[hash_pos + 1..].parse::<usize>() {
3344 return (&expected[..hash_pos], Some(occ));
3345 }
3346 }
3347 (expected, None)
3348}
3349
3350fn strip_rep_index(relative: &str) -> String {
3354 let (id, _) = parse_group_spec(relative);
3355 id.to_string()
3356}
3357
3358fn strip_all_rep_indices(relative: &str) -> String {
3363 relative
3364 .split('.')
3365 .map(|part| {
3366 let (id, _) = parse_group_spec(part);
3367 id
3368 })
3369 .collect::<Vec<_>>()
3370 .join(".")
3371}
3372
3373fn is_collect_all_path(path: &str) -> bool {
3378 let tag_part = path.split('.').next().unwrap_or("");
3379 if let Some(bracket_start) = tag_part.find('[') {
3380 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3381 if let Some(comma_pos) = inner.find(',') {
3382 let qualifier = &inner[..comma_pos];
3383 let occ = &inner[comma_pos + 1..];
3384 qualifier != "*" && occ == "*"
3386 } else {
3387 false
3388 }
3389 } else {
3390 false
3391 }
3392}
3393
3394fn parse_tag_qualifier(tag_part: &str) -> (String, Option<&str>, usize) {
3401 if let Some(bracket_start) = tag_part.find('[') {
3402 let tag = tag_part[..bracket_start].to_uppercase();
3403 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3404 if let Some(comma_pos) = inner.find(',') {
3405 let qualifier = &inner[..comma_pos];
3406 let index = inner[comma_pos + 1..].parse::<usize>().unwrap_or(0);
3407 if qualifier == "*" {
3409 (tag, None, index)
3410 } else {
3411 (tag, Some(qualifier), index)
3412 }
3413 } else {
3414 (tag, Some(inner), 0)
3415 }
3416 } else {
3417 (tag_part.to_uppercase(), None, 0)
3418 }
3419}
3420
3421fn inject_bo4e_metadata(mut value: serde_json::Value, bo4e_type: &str) -> serde_json::Value {
3426 match &mut value {
3427 serde_json::Value::Object(map) => {
3428 map.entry("boTyp")
3429 .or_insert_with(|| serde_json::Value::String(bo4e_type.to_uppercase()));
3430 map.entry("versionStruktur")
3431 .or_insert_with(|| serde_json::Value::String("1".to_string()));
3432 }
3433 serde_json::Value::Array(items) => {
3434 for item in items {
3435 if let serde_json::Value::Object(map) = item {
3436 map.entry("boTyp")
3437 .or_insert_with(|| serde_json::Value::String(bo4e_type.to_uppercase()));
3438 map.entry("versionStruktur")
3439 .or_insert_with(|| serde_json::Value::String("1".to_string()));
3440 }
3441 }
3442 }
3443 _ => {}
3444 }
3445 value
3446}
3447
3448fn deep_merge_insert(
3454 result: &mut serde_json::Map<String, serde_json::Value>,
3455 entity: &str,
3456 bo4e: serde_json::Value,
3457) {
3458 if let Some(existing) = result.get_mut(entity) {
3459 if let (Some(existing_arr), Some(new_arr)) =
3462 (existing.as_array().map(|a| a.len()), bo4e.as_array())
3463 {
3464 if existing_arr == new_arr.len() {
3465 let existing_arr = existing.as_array_mut().unwrap();
3466 for (existing_elem, new_elem) in existing_arr.iter_mut().zip(new_arr) {
3467 if let (Some(existing_map), Some(new_map)) =
3468 (existing_elem.as_object_mut(), new_elem.as_object())
3469 {
3470 for (k, v) in new_map {
3471 if let Some(existing_v) = existing_map.get_mut(k) {
3472 if let (Some(existing_inner), Some(new_inner)) =
3473 (existing_v.as_object_mut(), v.as_object())
3474 {
3475 for (ik, iv) in new_inner {
3476 existing_inner
3477 .entry(ik.clone())
3478 .or_insert_with(|| iv.clone());
3479 }
3480 }
3481 } else {
3482 existing_map.insert(k.clone(), v.clone());
3483 }
3484 }
3485 }
3486 }
3487 return;
3488 }
3489 }
3490 if let (Some(existing_map), serde_json::Value::Object(new_map)) =
3492 (existing.as_object_mut(), &bo4e)
3493 {
3494 for (k, v) in new_map {
3495 if let Some(existing_v) = existing_map.get_mut(k) {
3496 if let (Some(existing_inner), Some(new_inner)) =
3498 (existing_v.as_object_mut(), v.as_object())
3499 {
3500 for (ik, iv) in new_inner {
3501 existing_inner
3502 .entry(ik.clone())
3503 .or_insert_with(|| iv.clone());
3504 }
3505 }
3506 } else {
3508 existing_map.insert(k.clone(), v.clone());
3509 }
3510 }
3511 return;
3512 }
3513 }
3514 result.insert(entity.to_string(), bo4e);
3515}
3516
3517fn is_map_keyed_object(value: &serde_json::Value) -> bool {
3528 let Some(obj) = value.as_object() else {
3529 return false;
3530 };
3531 if obj.is_empty() {
3532 return false;
3533 }
3534 obj.iter().all(|(k, v)| {
3536 k.len() <= 5
3537 && k.chars()
3538 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
3539 && v.is_object()
3540 })
3541}
3542
3543fn find_qualifier_companion_field(
3552 definitions: &[crate::definition::MappingDefinition],
3553 entity: &str,
3554) -> Option<String> {
3555 for def in definitions {
3556 if def.meta.entity != *entity {
3557 continue;
3558 }
3559 let disc = def.meta.discriminator.as_deref()?;
3560 let (disc_path, _) = disc.split_once('=')?;
3561 let disc_path_lower = disc_path.to_lowercase();
3562
3563 let sections: Vec<&indexmap::IndexMap<String, FieldMapping>> = [
3566 def.companion_fields.as_ref(),
3567 Some(&def.fields),
3568 ]
3569 .into_iter()
3570 .flatten()
3571 .collect();
3572
3573 for section in sections {
3574 for (path, mapping) in section {
3575 let cf_path = path.to_lowercase();
3576 let matches = cf_path == disc_path_lower
3577 || format!("{}.0", cf_path) == disc_path_lower;
3578 if matches {
3579 let target = match mapping {
3580 FieldMapping::Simple(t) => t.as_str(),
3581 FieldMapping::Structured(s) => s.target.as_str(),
3582 FieldMapping::Nested(_) => continue,
3583 };
3584 if !target.is_empty() {
3585 return Some(target.to_string());
3586 }
3587 }
3588 }
3589 }
3590 }
3591 None
3592}
3593
3594fn extract_child_from_parent(
3603 entities: &serde_json::Value,
3604 definitions: &[MappingDefinition],
3605 child_def: &MappingDefinition,
3606) -> Option<serde_json::Value> {
3607 extract_child_from_parent_with_indices(entities, definitions, child_def).map(|(v, _)| v)
3608}
3609
3610fn extract_child_from_parent_with_indices(
3615 entities: &serde_json::Value,
3616 definitions: &[MappingDefinition],
3617 child_def: &MappingDefinition,
3618) -> Option<(serde_json::Value, Vec<usize>)> {
3619 let parts: Vec<&str> = child_def.meta.source_group.split('.').collect();
3620 if parts.len() < 2 {
3621 return None;
3622 }
3623 let parent_group = parts[0];
3624 let parent_def = definitions
3625 .iter()
3626 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_def.meta.entity)?;
3627 let parent_key = to_camel_case(&parent_def.meta.entity);
3628 let child_key = to_camel_case(&child_def.meta.entity);
3629 let parent_value = entities.get(&parent_key)?;
3630
3631 if let Some(parent_map) = parent_value.as_object() {
3633 if is_map_keyed_value(parent_map) {
3634 let mut children: Vec<serde_json::Value> = Vec::new();
3635 let mut indices: Vec<usize> = Vec::new();
3636 for (i, (_key, inner)) in parent_map.iter().enumerate() {
3637 if let Some(child) = inner.get(&child_key) {
3638 if !child.is_null() {
3639 children.push(child.clone());
3640 indices.push(i);
3641 }
3642 }
3643 }
3644 return match children.len() {
3645 0 => None,
3646 1 => Some((children.into_iter().next().unwrap(), indices)),
3647 _ => Some((serde_json::Value::Array(children), indices)),
3648 };
3649 }
3650 }
3651
3652 if let Some(parent_arr) = parent_value.as_array() {
3654 let mut children: Vec<serde_json::Value> = Vec::new();
3655 let mut indices: Vec<usize> = Vec::new();
3656 for (i, item) in parent_arr.iter().enumerate() {
3657 if let Some(child) = item.get(&child_key) {
3658 if !child.is_null() {
3659 children.push(child.clone());
3660 indices.push(i);
3661 }
3662 }
3663 }
3664 return match children.len() {
3665 0 => None,
3666 1 => Some((children.into_iter().next().unwrap(), indices)),
3667 _ => Some((serde_json::Value::Array(children), indices)),
3668 };
3669 }
3670
3671 let child = parent_value.get(&child_key)?;
3673 if child.is_null() {
3674 return None;
3675 }
3676 Some((child.clone(), vec![0]))
3677}
3678
3679fn nest_child_entities_in_result(
3685 result: &mut serde_json::Map<String, serde_json::Value>,
3686 definitions: &[MappingDefinition],
3687 nesting_info: &std::collections::HashMap<String, Vec<usize>>,
3688) {
3689 let mut nesting_pairs: Vec<(String, String, String, Option<String>)> = Vec::new();
3692 for def in definitions {
3693 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
3694 if parts.len() < 2 {
3695 continue;
3696 }
3697 let parent_group = parts[0];
3698 let child_entity = def.meta.entity.clone();
3699 let child_has_parent_level_def = definitions
3703 .iter()
3704 .any(|d| d.meta.source_group == parent_group && d.meta.entity == child_entity);
3705 if child_has_parent_level_def {
3706 continue;
3707 }
3708 let parent_entity = definitions
3710 .iter()
3711 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_entity)
3712 .map(|d| d.meta.entity.clone());
3713 if let Some(ref parent_entity) = parent_entity {
3714 let child_key_lc = to_camel_case(&child_entity);
3719 let parent_defs: Vec<_> = definitions
3720 .iter()
3721 .filter(|d| d.meta.entity == *parent_entity)
3722 .collect();
3723 let has_conflicting_field = parent_defs.iter().any(|pd| {
3724 pd.fields.values().any(|fm| {
3725 let target = match fm {
3726 crate::definition::FieldMapping::Simple(t) => t.as_str(),
3727 crate::definition::FieldMapping::Structured(s) => s.target.as_str(),
3728 crate::definition::FieldMapping::Nested(_) => "",
3729 };
3730 target.starts_with(&child_key_lc)
3731 && target.get(child_key_lc.len()..child_key_lc.len() + 1) == Some(".")
3732 })
3733 });
3734 if has_conflicting_field {
3735 continue;
3736 }
3737 if nesting_pairs
3739 .iter()
3740 .any(|(_, pe, ce, _)| *pe == *parent_entity && *ce == child_entity)
3741 {
3742 continue;
3743 }
3744 nesting_pairs.push((
3745 parent_group.to_string(),
3746 parent_entity.clone(),
3747 child_entity,
3748 def.meta.source_path.clone(),
3749 ));
3750 }
3751 }
3752
3753 for (_parent_group, parent_entity, child_entity, child_source_path) in nesting_pairs {
3754 let parent_key = to_camel_case(&parent_entity);
3755 let child_key = to_camel_case(&child_entity);
3756
3757 let child_value = match result.remove(&child_key) {
3759 Some(v) => v,
3760 None => continue,
3761 };
3762
3763 let Some(parent_value) = result.get_mut(&parent_key) else {
3768 result.insert(child_key, child_value);
3770 continue;
3771 };
3772 if parent_value.is_array() {
3773 result.insert(child_key, child_value);
3774 continue;
3775 }
3776
3777 let distribution = child_source_path
3779 .as_deref()
3780 .and_then(|sp| nesting_info.get(sp));
3781
3782 let child_items: Vec<(usize, &serde_json::Value)> = match &child_value {
3784 serde_json::Value::Array(arr) => arr.iter().enumerate().collect(),
3785 other => vec![(0, other)],
3786 };
3787
3788 let insert_or_append =
3791 |obj: &mut serde_json::Map<String, serde_json::Value>,
3792 key: &str,
3793 val: &serde_json::Value| {
3794 match obj.get_mut(key) {
3795 Some(existing) => {
3796 if !existing.is_array() {
3798 let prev = existing.take();
3799 *existing = serde_json::Value::Array(vec![prev]);
3800 }
3801 if let Some(arr) = existing.as_array_mut() {
3802 arr.push(val.clone());
3803 }
3804 }
3805 None => {
3806 obj.insert(key.to_string(), val.clone());
3807 }
3808 }
3809 };
3810
3811 if let Some(parent_map) = parent_value.as_object_mut() {
3813 if is_map_keyed_value(parent_map) {
3814 let keys: Vec<String> = parent_map.keys().cloned().collect();
3816 for (i, child_item) in &child_items {
3817 let target_idx = distribution
3818 .and_then(|dist| dist.get(*i))
3819 .copied()
3820 .unwrap_or(0);
3821 if let Some(key) = keys.get(target_idx) {
3822 if let Some(inner) = parent_map.get_mut(key).and_then(|v| v.as_object_mut())
3823 {
3824 insert_or_append(inner, &child_key, child_item);
3825 }
3826 }
3827 }
3828 continue;
3829 }
3830 }
3831
3832 if let Some(parent_arr) = parent_value.as_array_mut() {
3834 for (i, child_item) in &child_items {
3835 let target_idx = distribution
3836 .and_then(|dist| dist.get(*i))
3837 .copied()
3838 .unwrap_or(0);
3839 if let Some(parent_obj) =
3840 parent_arr.get_mut(target_idx).and_then(|v| v.as_object_mut())
3841 {
3842 insert_or_append(parent_obj, &child_key, child_item);
3843 }
3844 }
3845 continue;
3846 }
3847
3848 if let Some(parent_obj) = parent_value.as_object_mut() {
3850 for (_i, child_item) in &child_items {
3851 insert_or_append(parent_obj, &child_key, child_item);
3852 }
3853 continue;
3854 }
3855
3856 result.insert(child_key, child_value);
3858 }
3859}
3860
3861fn is_map_keyed_value(map: &serde_json::Map<String, serde_json::Value>) -> bool {
3863 if map.is_empty() {
3864 return false;
3865 }
3866 map.values().all(|v| v.is_object())
3867 && map.keys().all(|k| k.len() <= 5 || k.chars().all(|c| c.is_ascii_uppercase() || c.is_ascii_digit()))
3868}
3869
3870fn to_camel_case(name: &str) -> String {
3871 let mut chars = name.chars();
3872 match chars.next() {
3873 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
3874 None => String::new(),
3875 }
3876}
3877
3878fn set_nested_value(map: &mut serde_json::Map<String, serde_json::Value>, path: &str, val: String) {
3881 set_nested_value_json(map, path, serde_json::Value::String(val));
3882}
3883
3884fn set_nested_value_json(
3886 map: &mut serde_json::Map<String, serde_json::Value>,
3887 path: &str,
3888 val: serde_json::Value,
3889) {
3890 if let Some((prefix, leaf)) = path.rsplit_once('.') {
3891 let mut current = map;
3892 for part in prefix.split('.') {
3893 let entry = current
3894 .entry(part.to_string())
3895 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
3896 current = entry.as_object_mut().expect("expected object in path");
3897 }
3898 current.insert(leaf.to_string(), val);
3899 } else {
3900 map.insert(path.to_string(), val);
3901 }
3902}
3903
3904#[derive(serde::Serialize, serde::Deserialize)]
3909pub struct VariantCache {
3910 pub message_defs: Vec<MappingDefinition>,
3912 pub transaction_defs: HashMap<String, Vec<MappingDefinition>>,
3914 pub combined_defs: HashMap<String, Vec<MappingDefinition>>,
3916 #[serde(default)]
3918 pub code_lookups: HashMap<String, crate::code_lookup::CodeLookup>,
3919 #[serde(default)]
3921 pub mig_schema: Option<mig_types::schema::mig::MigSchema>,
3922 #[serde(default)]
3924 pub segment_structure: Option<crate::segment_structure::SegmentStructure>,
3925 #[serde(default)]
3928 pub pid_segment_numbers: HashMap<String, Vec<String>>,
3929 #[serde(default)]
3932 pub pid_requirements: HashMap<String, crate::pid_requirements::PidRequirements>,
3933 #[serde(default)]
3937 pub tx_groups: HashMap<String, String>,
3938}
3939
3940impl VariantCache {
3941 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
3943 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
3944 path: path.display().to_string(),
3945 message: e.to_string(),
3946 })?;
3947 if let Some(parent) = path.parent() {
3948 std::fs::create_dir_all(parent)?;
3949 }
3950 std::fs::write(path, encoded)?;
3951 Ok(())
3952 }
3953
3954 pub fn load(path: &Path) -> Result<Self, MappingError> {
3956 let bytes = std::fs::read(path)?;
3957 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
3958 path: path.display().to_string(),
3959 message: e.to_string(),
3960 })
3961 }
3962
3963 pub fn tx_group(&self, pid: &str) -> Option<&str> {
3967 self.tx_groups
3968 .get(&format!("pid_{pid}"))
3969 .map(|s| s.as_str())
3970 }
3971
3972 pub fn msg_engine(&self) -> MappingEngine {
3974 MappingEngine::from_definitions(self.message_defs.clone())
3975 }
3976
3977 pub fn tx_engine(&self, pid: &str) -> Option<MappingEngine> {
3980 self.transaction_defs
3981 .get(&format!("pid_{pid}"))
3982 .map(|defs| MappingEngine::from_definitions(defs.clone()))
3983 }
3984
3985 pub fn filtered_mig(&self, pid: &str) -> Option<mig_types::schema::mig::MigSchema> {
3988 let mig = self.mig_schema.as_ref()?;
3989 let numbers = self.pid_segment_numbers.get(&format!("pid_{pid}"))?;
3990 let number_set: std::collections::HashSet<String> = numbers.iter().cloned().collect();
3991 Some(mig_assembly::pid_filter::filter_mig_for_pid(
3992 mig,
3993 &number_set,
3994 ))
3995 }
3996}
3997
3998#[derive(serde::Serialize, serde::Deserialize)]
4003pub struct DataBundle {
4004 pub format_version: String,
4005 pub bundle_version: u32,
4006 pub variants: HashMap<String, VariantCache>,
4007}
4008
4009impl DataBundle {
4010 pub const CURRENT_VERSION: u32 = 2;
4011
4012 pub fn variant(&self, name: &str) -> Option<&VariantCache> {
4013 self.variants.get(name)
4014 }
4015
4016 pub fn write_to<W: std::io::Write>(&self, writer: &mut W) -> Result<(), MappingError> {
4017 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
4018 path: "<stream>".to_string(),
4019 message: e.to_string(),
4020 })?;
4021 writer.write_all(&encoded).map_err(MappingError::Io)
4022 }
4023
4024 pub fn read_from<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
4025 let mut bytes = Vec::new();
4026 reader.read_to_end(&mut bytes).map_err(MappingError::Io)?;
4027 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
4028 path: "<stream>".to_string(),
4029 message: e.to_string(),
4030 })
4031 }
4032
4033 pub fn read_from_checked<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
4034 let bundle = Self::read_from(reader)?;
4035 if bundle.bundle_version != Self::CURRENT_VERSION {
4036 return Err(MappingError::CacheRead {
4037 path: "<stream>".to_string(),
4038 message: format!(
4039 "Incompatible bundle version {}, expected version {}. \
4040 Run `edifact-data update` to fetch compatible bundles.",
4041 bundle.bundle_version,
4042 Self::CURRENT_VERSION
4043 ),
4044 });
4045 }
4046 Ok(bundle)
4047 }
4048
4049 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
4050 if let Some(parent) = path.parent() {
4051 std::fs::create_dir_all(parent)?;
4052 }
4053 let mut file = std::fs::File::create(path).map_err(MappingError::Io)?;
4054 self.write_to(&mut file)
4055 }
4056
4057 pub fn load(path: &Path) -> Result<Self, MappingError> {
4058 let mut file = std::fs::File::open(path).map_err(MappingError::Io)?;
4059 Self::read_from_checked(&mut file)
4060 }
4061}
4062
4063fn sort_variant_reps_by_mig(
4076 child_groups: &mut [AssembledGroup],
4077 mig: &MigSchema,
4078 transaction_group: &str,
4079) {
4080 let tx_def = match mig
4081 .segment_groups
4082 .iter()
4083 .find(|sg| sg.id == transaction_group)
4084 {
4085 Some(d) => d,
4086 None => return,
4087 };
4088
4089 for cg in child_groups.iter_mut() {
4090 if cg.repetitions.len() <= 1 {
4091 continue;
4092 }
4093
4094 let variant_defs: Vec<(usize, &mig_types::schema::mig::MigSegmentGroup)> = tx_def
4096 .nested_groups
4097 .iter()
4098 .enumerate()
4099 .filter(|(_, ng)| ng.id == cg.group_id && ng.variant_code.is_some())
4100 .collect();
4101
4102 if variant_defs.is_empty() {
4103 continue;
4104 }
4105
4106 cg.repetitions.sort_by_key(|rep| {
4109 let entry_seg = rep.segments.first();
4110 for &(mig_pos, variant_def) in &variant_defs {
4111 let (ei, ci) = variant_def.variant_qualifier_position.unwrap_or((0, 0));
4112 let actual_qual = entry_seg
4113 .and_then(|s| s.elements.get(ei))
4114 .and_then(|e| e.get(ci))
4115 .map(|s| s.as_str())
4116 .unwrap_or("");
4117 let matches = if !variant_def.variant_codes.is_empty() {
4118 variant_def
4119 .variant_codes
4120 .iter()
4121 .any(|c| actual_qual.eq_ignore_ascii_case(c))
4122 } else if let Some(ref expected_code) = variant_def.variant_code {
4123 actual_qual.eq_ignore_ascii_case(expected_code)
4124 } else {
4125 false
4126 };
4127 if matches {
4128 return mig_pos;
4129 }
4130 }
4131 usize::MAX });
4133 }
4134}
4135
4136#[cfg(test)]
4137mod variant_cache_helper_tests {
4138 use super::*;
4139
4140 fn make_test_cache() -> VariantCache {
4141 let mut tx_groups = HashMap::new();
4142 tx_groups.insert("pid_55001".to_string(), "SG4".to_string());
4143 tx_groups.insert("pid_21007".to_string(), "SG14".to_string());
4144
4145 let mut transaction_defs = HashMap::new();
4146 transaction_defs.insert("pid_55001".to_string(), vec![]);
4147 transaction_defs.insert("pid_21007".to_string(), vec![]);
4148
4149 VariantCache {
4150 message_defs: vec![],
4151 transaction_defs,
4152 combined_defs: HashMap::new(),
4153 code_lookups: HashMap::new(),
4154 mig_schema: None,
4155 segment_structure: None,
4156 pid_segment_numbers: HashMap::new(),
4157 pid_requirements: HashMap::new(),
4158 tx_groups,
4159 }
4160 }
4161
4162 #[test]
4163 fn test_tx_group_returns_correct_group() {
4164 let vc = make_test_cache();
4165 assert_eq!(vc.tx_group("55001").unwrap(), "SG4");
4166 assert_eq!(vc.tx_group("21007").unwrap(), "SG14");
4167 }
4168
4169 #[test]
4170 fn test_tx_group_unknown_pid_returns_none() {
4171 let vc = make_test_cache();
4172 assert!(vc.tx_group("99999").is_none());
4173 }
4174
4175 #[test]
4176 fn test_msg_engine_returns_engine() {
4177 let vc = make_test_cache();
4178 let engine = vc.msg_engine();
4179 assert_eq!(engine.definitions().len(), 0);
4180 }
4181
4182 #[test]
4183 fn test_tx_engine_returns_engine_for_known_pid() {
4184 let vc = make_test_cache();
4185 assert!(vc.tx_engine("55001").is_some());
4186 }
4187
4188 #[test]
4189 fn test_tx_engine_returns_none_for_unknown_pid() {
4190 let vc = make_test_cache();
4191 assert!(vc.tx_engine("99999").is_none());
4192 }
4193}
4194
4195#[cfg(test)]
4196mod tests {
4197 use super::*;
4198 use crate::definition::{MappingDefinition, MappingMeta, StructuredFieldMapping};
4199 use indexmap::IndexMap;
4200
4201 fn make_def(fields: IndexMap<String, FieldMapping>) -> MappingDefinition {
4202 MappingDefinition {
4203 meta: MappingMeta {
4204 entity: "Test".to_string(),
4205 bo4e_type: "Test".to_string(),
4206 companion_type: None,
4207 source_group: "SG4".to_string(),
4208 source_path: None,
4209 discriminator: None,
4210 repeat_on_tag: None,
4211 },
4212 fields,
4213 companion_fields: None,
4214 complex_handlers: None,
4215 }
4216 }
4217
4218 #[test]
4219 fn test_map_interchange_single_transaction_backward_compat() {
4220 use mig_assembly::assembler::*;
4221
4222 let tree = AssembledTree {
4224 segments: vec![
4225 AssembledSegment {
4226 tag: "UNH".to_string(),
4227 elements: vec![vec!["001".to_string()]],
4228 mig_number: None,
4229 },
4230 AssembledSegment {
4231 tag: "BGM".to_string(),
4232 elements: vec![vec!["E01".to_string()], vec!["DOC001".to_string()]],
4233 mig_number: None,
4234 },
4235 ],
4236 groups: vec![
4237 AssembledGroup {
4238 group_id: "SG2".to_string(),
4239 repetitions: vec![AssembledGroupInstance {
4240 segments: vec![AssembledSegment {
4241 tag: "NAD".to_string(),
4242 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4243 mig_number: None,
4244 }],
4245 child_groups: vec![],
4246 entry_mig_number: None,
4247 variant_mig_numbers: vec![],
4248 skipped_segments: vec![],
4249 }],
4250 },
4251 AssembledGroup {
4252 group_id: "SG4".to_string(),
4253 repetitions: vec![AssembledGroupInstance {
4254 segments: vec![AssembledSegment {
4255 tag: "IDE".to_string(),
4256 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4257 mig_number: None,
4258 }],
4259 child_groups: vec![AssembledGroup {
4260 group_id: "SG5".to_string(),
4261 repetitions: vec![AssembledGroupInstance {
4262 segments: vec![AssembledSegment {
4263 tag: "LOC".to_string(),
4264 elements: vec![
4265 vec!["Z16".to_string()],
4266 vec!["DE000111222333".to_string()],
4267 ],
4268 mig_number: None,
4269 }],
4270 child_groups: vec![],
4271 entry_mig_number: None,
4272 variant_mig_numbers: vec![],
4273 skipped_segments: vec![],
4274 }],
4275 }],
4276 skipped_segments: vec![],
4277 }],
4278 },
4279 ],
4280 post_group_start: 2,
4281 inter_group_segments: std::collections::BTreeMap::new(),
4282 };
4283
4284 let msg_engine = MappingEngine::from_definitions(vec![]);
4286
4287 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4289 tx_fields.insert(
4290 "ide.1".to_string(),
4291 FieldMapping::Simple("vorgangId".to_string()),
4292 );
4293 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4294 malo_fields.insert(
4295 "loc.1".to_string(),
4296 FieldMapping::Simple("marktlokationsId".to_string()),
4297 );
4298
4299 let tx_engine = MappingEngine::from_definitions(vec![
4300 MappingDefinition {
4301 meta: MappingMeta {
4302 entity: "Prozessdaten".to_string(),
4303 bo4e_type: "Prozessdaten".to_string(),
4304 companion_type: None,
4305 source_group: "SG4".to_string(),
4306 source_path: None,
4307 discriminator: None,
4308 repeat_on_tag: None,
4309 },
4310 fields: tx_fields,
4311 companion_fields: None,
4312 complex_handlers: None,
4313 },
4314 MappingDefinition {
4315 meta: MappingMeta {
4316 entity: "Marktlokation".to_string(),
4317 bo4e_type: "Marktlokation".to_string(),
4318 companion_type: None,
4319 source_group: "SG4.SG5".to_string(),
4320 source_path: None,
4321 discriminator: None,
4322 repeat_on_tag: None,
4323 },
4324 fields: malo_fields,
4325 companion_fields: None,
4326 complex_handlers: None,
4327 },
4328 ]);
4329
4330 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
4331
4332 assert_eq!(result.transaktionen.len(), 1);
4333 assert_eq!(
4334 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
4335 .as_str()
4336 .unwrap(),
4337 "TX001"
4338 );
4339 assert_eq!(
4341 result.transaktionen[0].stammdaten["prozessdaten"]["marktlokation"]
4342 ["marktlokationsId"]
4343 .as_str()
4344 .unwrap(),
4345 "DE000111222333"
4346 );
4347 }
4348
4349 #[test]
4350 fn test_map_reverse_pads_intermediate_empty_elements() {
4351 let mut fields = IndexMap::new();
4353 fields.insert(
4354 "nad.0".to_string(),
4355 FieldMapping::Structured(StructuredFieldMapping {
4356 target: String::new(),
4357 transform: None,
4358 when: None,
4359 default: Some("Z09".to_string()),
4360 enum_map: None,
4361 when_filled: None,
4362 also_target: None,
4363 also_enum_map: None,
4364 }),
4365 );
4366 fields.insert(
4367 "nad.3.0".to_string(),
4368 FieldMapping::Simple("name".to_string()),
4369 );
4370 fields.insert(
4371 "nad.3.1".to_string(),
4372 FieldMapping::Simple("vorname".to_string()),
4373 );
4374
4375 let def = make_def(fields);
4376 let engine = MappingEngine::from_definitions(vec![]);
4377
4378 let bo4e = serde_json::json!({
4379 "name": "Muster",
4380 "vorname": "Max"
4381 });
4382
4383 let instance = engine.map_reverse(&bo4e, &def);
4384 assert_eq!(instance.segments.len(), 1);
4385
4386 let nad = &instance.segments[0];
4387 assert_eq!(nad.tag, "NAD");
4388 assert_eq!(nad.elements.len(), 4);
4389 assert_eq!(nad.elements[0], vec!["Z09"]);
4390 assert_eq!(nad.elements[1], vec![""]);
4392 assert_eq!(nad.elements[2], vec![""]);
4393 assert_eq!(nad.elements[3][0], "Muster");
4394 assert_eq!(nad.elements[3][1], "Max");
4395 }
4396
4397 #[test]
4398 fn test_map_reverse_no_padding_when_contiguous() {
4399 let mut fields = IndexMap::new();
4401 fields.insert(
4402 "dtm.0.0".to_string(),
4403 FieldMapping::Structured(StructuredFieldMapping {
4404 target: String::new(),
4405 transform: None,
4406 when: None,
4407 default: Some("92".to_string()),
4408 enum_map: None,
4409 when_filled: None,
4410 also_target: None,
4411 also_enum_map: None,
4412 }),
4413 );
4414 fields.insert(
4415 "dtm.0.1".to_string(),
4416 FieldMapping::Simple("value".to_string()),
4417 );
4418 fields.insert(
4419 "dtm.0.2".to_string(),
4420 FieldMapping::Structured(StructuredFieldMapping {
4421 target: String::new(),
4422 transform: None,
4423 when: None,
4424 default: Some("303".to_string()),
4425 enum_map: None,
4426 when_filled: None,
4427 also_target: None,
4428 also_enum_map: None,
4429 }),
4430 );
4431
4432 let def = make_def(fields);
4433 let engine = MappingEngine::from_definitions(vec![]);
4434
4435 let bo4e = serde_json::json!({ "value": "20250531" });
4436
4437 let instance = engine.map_reverse(&bo4e, &def);
4438 let dtm = &instance.segments[0];
4439 assert_eq!(dtm.elements.len(), 1);
4441 assert_eq!(dtm.elements[0], vec!["92", "20250531", "303"]);
4442 }
4443
4444 #[test]
4445 fn test_map_message_level_extracts_sg2_only() {
4446 use mig_assembly::assembler::*;
4447
4448 let tree = AssembledTree {
4450 segments: vec![
4451 AssembledSegment {
4452 tag: "UNH".to_string(),
4453 elements: vec![vec!["001".to_string()]],
4454 mig_number: None,
4455 },
4456 AssembledSegment {
4457 tag: "BGM".to_string(),
4458 elements: vec![vec!["E01".to_string()]],
4459 mig_number: None,
4460 },
4461 ],
4462 groups: vec![
4463 AssembledGroup {
4464 group_id: "SG2".to_string(),
4465 repetitions: vec![AssembledGroupInstance {
4466 segments: vec![AssembledSegment {
4467 tag: "NAD".to_string(),
4468 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4469 mig_number: None,
4470 }],
4471 child_groups: vec![],
4472 entry_mig_number: None,
4473 variant_mig_numbers: vec![],
4474 skipped_segments: vec![],
4475 }],
4476 },
4477 AssembledGroup {
4478 group_id: "SG4".to_string(),
4479 repetitions: vec![AssembledGroupInstance {
4480 segments: vec![AssembledSegment {
4481 tag: "IDE".to_string(),
4482 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4483 mig_number: None,
4484 }],
4485 child_groups: vec![],
4486 entry_mig_number: None,
4487 variant_mig_numbers: vec![],
4488 skipped_segments: vec![],
4489 }],
4490 },
4491 ],
4492 post_group_start: 2,
4493 inter_group_segments: std::collections::BTreeMap::new(),
4494 };
4495
4496 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4498 msg_fields.insert(
4499 "nad.0".to_string(),
4500 FieldMapping::Simple("marktrolle".to_string()),
4501 );
4502 msg_fields.insert(
4503 "nad.1".to_string(),
4504 FieldMapping::Simple("rollencodenummer".to_string()),
4505 );
4506 let msg_def = MappingDefinition {
4507 meta: MappingMeta {
4508 entity: "Marktteilnehmer".to_string(),
4509 bo4e_type: "Marktteilnehmer".to_string(),
4510 companion_type: None,
4511 source_group: "SG2".to_string(),
4512 source_path: None,
4513 discriminator: None,
4514 repeat_on_tag: None,
4515 },
4516 fields: msg_fields,
4517 companion_fields: None,
4518 complex_handlers: None,
4519 };
4520
4521 let engine = MappingEngine::from_definitions(vec![msg_def.clone()]);
4522 let result = engine.map_all_forward(&tree);
4523
4524 assert!(result.get("marktteilnehmer").is_some());
4526 let mt = &result["marktteilnehmer"];
4527 assert_eq!(mt["marktrolle"].as_str().unwrap(), "MS");
4528 assert_eq!(mt["rollencodenummer"].as_str().unwrap(), "9900123");
4529 }
4530
4531 #[test]
4532 fn test_map_transaction_scoped_to_sg4_instance() {
4533 use mig_assembly::assembler::*;
4534
4535 let tree = AssembledTree {
4537 segments: vec![
4538 AssembledSegment {
4539 tag: "UNH".to_string(),
4540 elements: vec![vec!["001".to_string()]],
4541 mig_number: None,
4542 },
4543 AssembledSegment {
4544 tag: "BGM".to_string(),
4545 elements: vec![vec!["E01".to_string()]],
4546 mig_number: None,
4547 },
4548 ],
4549 groups: vec![AssembledGroup {
4550 group_id: "SG4".to_string(),
4551 repetitions: vec![AssembledGroupInstance {
4552 segments: vec![AssembledSegment {
4553 tag: "IDE".to_string(),
4554 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4555 mig_number: None,
4556 }],
4557 child_groups: vec![AssembledGroup {
4558 group_id: "SG5".to_string(),
4559 repetitions: vec![AssembledGroupInstance {
4560 segments: vec![AssembledSegment {
4561 tag: "LOC".to_string(),
4562 elements: vec![
4563 vec!["Z16".to_string()],
4564 vec!["DE000111222333".to_string()],
4565 ],
4566 mig_number: None,
4567 }],
4568 child_groups: vec![],
4569 entry_mig_number: None,
4570 variant_mig_numbers: vec![],
4571 skipped_segments: vec![],
4572 }],
4573 }],
4574 skipped_segments: vec![],
4575 }],
4576 }],
4577 post_group_start: 2,
4578 inter_group_segments: std::collections::BTreeMap::new(),
4579 };
4580
4581 let mut proz_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4583 proz_fields.insert(
4584 "ide.1".to_string(),
4585 FieldMapping::Simple("vorgangId".to_string()),
4586 );
4587 let proz_def = MappingDefinition {
4588 meta: MappingMeta {
4589 entity: "Prozessdaten".to_string(),
4590 bo4e_type: "Prozessdaten".to_string(),
4591 companion_type: None,
4592 source_group: "".to_string(), source_path: None,
4594 discriminator: None,
4595 repeat_on_tag: None,
4596 },
4597 fields: proz_fields,
4598 companion_fields: None,
4599 complex_handlers: None,
4600 };
4601
4602 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4603 malo_fields.insert(
4604 "loc.1".to_string(),
4605 FieldMapping::Simple("marktlokationsId".to_string()),
4606 );
4607 let malo_def = MappingDefinition {
4608 meta: MappingMeta {
4609 entity: "Marktlokation".to_string(),
4610 bo4e_type: "Marktlokation".to_string(),
4611 companion_type: None,
4612 source_group: "SG5".to_string(), source_path: None,
4614 discriminator: None,
4615 repeat_on_tag: None,
4616 },
4617 fields: malo_fields,
4618 companion_fields: None,
4619 complex_handlers: None,
4620 };
4621
4622 let tx_engine = MappingEngine::from_definitions(vec![proz_def, malo_def]);
4623
4624 let sg4 = &tree.groups[0]; let sg4_instance = &sg4.repetitions[0];
4627 let sub_tree = sg4_instance.as_assembled_tree();
4628
4629 let result = tx_engine.map_all_forward(&sub_tree);
4630
4631 assert_eq!(
4633 result["prozessdaten"]["vorgangId"].as_str().unwrap(),
4634 "TX001"
4635 );
4636
4637 assert_eq!(
4639 result["marktlokation"]["marktlokationsId"]
4640 .as_str()
4641 .unwrap(),
4642 "DE000111222333"
4643 );
4644 }
4645
4646 #[test]
4647 fn test_map_interchange_produces_full_hierarchy() {
4648 use mig_assembly::assembler::*;
4649
4650 let tree = AssembledTree {
4652 segments: vec![
4653 AssembledSegment {
4654 tag: "UNH".to_string(),
4655 elements: vec![vec!["001".to_string()]],
4656 mig_number: None,
4657 },
4658 AssembledSegment {
4659 tag: "BGM".to_string(),
4660 elements: vec![vec!["E01".to_string()]],
4661 mig_number: None,
4662 },
4663 ],
4664 groups: vec![
4665 AssembledGroup {
4666 group_id: "SG2".to_string(),
4667 repetitions: vec![AssembledGroupInstance {
4668 segments: vec![AssembledSegment {
4669 tag: "NAD".to_string(),
4670 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4671 mig_number: None,
4672 }],
4673 child_groups: vec![],
4674 entry_mig_number: None,
4675 variant_mig_numbers: vec![],
4676 skipped_segments: vec![],
4677 }],
4678 },
4679 AssembledGroup {
4680 group_id: "SG4".to_string(),
4681 repetitions: vec![
4682 AssembledGroupInstance {
4683 segments: vec![AssembledSegment {
4684 tag: "IDE".to_string(),
4685 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4686 mig_number: None,
4687 }],
4688 child_groups: vec![],
4689 entry_mig_number: None,
4690 variant_mig_numbers: vec![],
4691 skipped_segments: vec![],
4692 },
4693 AssembledGroupInstance {
4694 segments: vec![AssembledSegment {
4695 tag: "IDE".to_string(),
4696 elements: vec![vec!["24".to_string()], vec!["TX002".to_string()]],
4697 mig_number: None,
4698 }],
4699 child_groups: vec![],
4700 entry_mig_number: None,
4701 variant_mig_numbers: vec![],
4702 skipped_segments: vec![],
4703 },
4704 ],
4705 },
4706 ],
4707 post_group_start: 2,
4708 inter_group_segments: std::collections::BTreeMap::new(),
4709 };
4710
4711 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4713 msg_fields.insert(
4714 "nad.0".to_string(),
4715 FieldMapping::Simple("marktrolle".to_string()),
4716 );
4717 let msg_defs = vec![MappingDefinition {
4718 meta: MappingMeta {
4719 entity: "Marktteilnehmer".to_string(),
4720 bo4e_type: "Marktteilnehmer".to_string(),
4721 companion_type: None,
4722 source_group: "SG2".to_string(),
4723 source_path: None,
4724 discriminator: None,
4725 repeat_on_tag: None,
4726 },
4727 fields: msg_fields,
4728 companion_fields: None,
4729 complex_handlers: None,
4730 }];
4731
4732 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4734 tx_fields.insert(
4735 "ide.1".to_string(),
4736 FieldMapping::Simple("vorgangId".to_string()),
4737 );
4738 let tx_defs = vec![MappingDefinition {
4739 meta: MappingMeta {
4740 entity: "Prozessdaten".to_string(),
4741 bo4e_type: "Prozessdaten".to_string(),
4742 companion_type: None,
4743 source_group: "SG4".to_string(),
4744 source_path: None,
4745 discriminator: None,
4746 repeat_on_tag: None,
4747 },
4748 fields: tx_fields,
4749 companion_fields: None,
4750 complex_handlers: None,
4751 }];
4752
4753 let msg_engine = MappingEngine::from_definitions(msg_defs);
4754 let tx_engine = MappingEngine::from_definitions(tx_defs);
4755
4756 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
4757
4758 assert!(result.stammdaten["marktteilnehmer"].is_object());
4760 assert_eq!(
4761 result.stammdaten["marktteilnehmer"]["marktrolle"]
4762 .as_str()
4763 .unwrap(),
4764 "MS"
4765 );
4766
4767 assert_eq!(result.transaktionen.len(), 2);
4769 assert_eq!(
4770 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
4771 .as_str()
4772 .unwrap(),
4773 "TX001"
4774 );
4775 assert_eq!(
4776 result.transaktionen[1].stammdaten["prozessdaten"]["vorgangId"]
4777 .as_str()
4778 .unwrap(),
4779 "TX002"
4780 );
4781 }
4782
4783 #[test]
4784 fn test_map_reverse_with_segment_structure_pads_trailing() {
4785 let mut fields = IndexMap::new();
4787 fields.insert(
4788 "sts.0".to_string(),
4789 FieldMapping::Structured(StructuredFieldMapping {
4790 target: String::new(),
4791 transform: None,
4792 when: None,
4793 default: Some("7".to_string()),
4794 enum_map: None,
4795 when_filled: None,
4796 also_target: None,
4797 also_enum_map: None,
4798 }),
4799 );
4800 fields.insert(
4801 "sts.2".to_string(),
4802 FieldMapping::Simple("grund".to_string()),
4803 );
4804
4805 let def = make_def(fields);
4806
4807 let mut counts = std::collections::HashMap::new();
4809 counts.insert("STS".to_string(), 5usize);
4810 let ss = SegmentStructure {
4811 element_counts: counts,
4812 };
4813
4814 let engine = MappingEngine::from_definitions(vec![]).with_segment_structure(ss);
4815
4816 let bo4e = serde_json::json!({ "grund": "E01" });
4817
4818 let instance = engine.map_reverse(&bo4e, &def);
4819 let sts = &instance.segments[0];
4820 assert_eq!(sts.elements.len(), 5);
4823 assert_eq!(sts.elements[0], vec!["7"]);
4824 assert_eq!(sts.elements[1], vec![""]);
4825 assert_eq!(sts.elements[2], vec!["E01"]);
4826 assert_eq!(sts.elements[3], vec![""]);
4827 assert_eq!(sts.elements[4], vec![""]);
4828 }
4829
4830 #[test]
4831 fn test_extract_companion_fields_with_code_enrichment() {
4832 use crate::code_lookup::CodeLookup;
4833 use mig_assembly::assembler::*;
4834
4835 let schema = serde_json::json!({
4836 "fields": {
4837 "sg4": {
4838 "children": {
4839 "sg8_z01": {
4840 "children": {
4841 "sg10": {
4842 "segments": [{
4843 "id": "CCI",
4844 "elements": [{
4845 "index": 2,
4846 "components": [{
4847 "sub_index": 0,
4848 "type": "code",
4849 "codes": [
4850 {"value": "Z15", "name": "Haushaltskunde"},
4851 {"value": "Z18", "name": "Kein Haushaltskunde"}
4852 ]
4853 }]
4854 }]
4855 }],
4856 "source_group": "SG10"
4857 }
4858 },
4859 "segments": [],
4860 "source_group": "SG8"
4861 }
4862 },
4863 "segments": [],
4864 "source_group": "SG4"
4865 }
4866 }
4867 });
4868
4869 let code_lookup = CodeLookup::from_schema_value(&schema);
4870
4871 let tree = AssembledTree {
4872 segments: vec![],
4873 groups: vec![AssembledGroup {
4874 group_id: "SG4".to_string(),
4875 repetitions: vec![AssembledGroupInstance {
4876 segments: vec![],
4877 child_groups: vec![AssembledGroup {
4878 group_id: "SG8".to_string(),
4879 repetitions: vec![AssembledGroupInstance {
4880 segments: vec![],
4881 child_groups: vec![AssembledGroup {
4882 group_id: "SG10".to_string(),
4883 repetitions: vec![AssembledGroupInstance {
4884 segments: vec![AssembledSegment {
4885 tag: "CCI".to_string(),
4886 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
4887 mig_number: None,
4888 }],
4889 child_groups: vec![],
4890 entry_mig_number: None,
4891 variant_mig_numbers: vec![],
4892 skipped_segments: vec![],
4893 }],
4894 }],
4895 skipped_segments: vec![],
4896 }],
4897 }],
4898 skipped_segments: vec![],
4899 }],
4900 }],
4901 post_group_start: 0,
4902 inter_group_segments: std::collections::BTreeMap::new(),
4903 };
4904
4905 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4906 companion_fields.insert(
4907 "cci.2".to_string(),
4908 FieldMapping::Simple("haushaltskunde".to_string()),
4909 );
4910
4911 let def = MappingDefinition {
4912 meta: MappingMeta {
4913 entity: "Marktlokation".to_string(),
4914 bo4e_type: "Marktlokation".to_string(),
4915 companion_type: Some("MarktlokationEdifact".to_string()),
4916 source_group: "SG4.SG8.SG10".to_string(),
4917 source_path: Some("sg4.sg8_z01.sg10".to_string()),
4918 discriminator: None,
4919 repeat_on_tag: None,
4920 },
4921 fields: IndexMap::new(),
4922 companion_fields: Some(companion_fields),
4923 complex_handlers: None,
4924 };
4925
4926 let engine_plain = MappingEngine::from_definitions(vec![]);
4928 let bo4e_plain = engine_plain.map_forward(&tree, &def, 0);
4929 assert_eq!(
4930 bo4e_plain["marktlokationEdifact"]["haushaltskunde"].as_str(),
4931 Some("Z15"),
4932 "Without code lookup, should be plain string"
4933 );
4934
4935 let engine_enriched = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
4937 let bo4e_enriched = engine_enriched.map_forward(&tree, &def, 0);
4938 let hk = &bo4e_enriched["marktlokationEdifact"]["haushaltskunde"];
4939 assert_eq!(hk["code"].as_str(), Some("Z15"));
4940 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
4941 assert!(hk.get("enum").is_none());
4943 }
4944
4945 #[test]
4946 fn test_extract_companion_fields_with_enum_enrichment() {
4947 use crate::code_lookup::CodeLookup;
4948 use mig_assembly::assembler::*;
4949
4950 let schema = serde_json::json!({
4952 "fields": {
4953 "sg4": {
4954 "children": {
4955 "sg8_z01": {
4956 "children": {
4957 "sg10": {
4958 "segments": [{
4959 "id": "CCI",
4960 "elements": [{
4961 "index": 2,
4962 "components": [{
4963 "sub_index": 0,
4964 "type": "code",
4965 "codes": [
4966 {"value": "Z15", "name": "Haushaltskunde", "enum": "HAUSHALTSKUNDE"},
4967 {"value": "Z18", "name": "Kein Haushaltskunde", "enum": "KEIN_HAUSHALTSKUNDE"}
4968 ]
4969 }]
4970 }]
4971 }],
4972 "source_group": "SG10"
4973 }
4974 },
4975 "segments": [],
4976 "source_group": "SG8"
4977 }
4978 },
4979 "segments": [],
4980 "source_group": "SG4"
4981 }
4982 }
4983 });
4984
4985 let code_lookup = CodeLookup::from_schema_value(&schema);
4986
4987 let tree = AssembledTree {
4988 segments: vec![],
4989 groups: vec![AssembledGroup {
4990 group_id: "SG4".to_string(),
4991 repetitions: vec![AssembledGroupInstance {
4992 segments: vec![],
4993 child_groups: vec![AssembledGroup {
4994 group_id: "SG8".to_string(),
4995 repetitions: vec![AssembledGroupInstance {
4996 segments: vec![],
4997 child_groups: vec![AssembledGroup {
4998 group_id: "SG10".to_string(),
4999 repetitions: vec![AssembledGroupInstance {
5000 segments: vec![AssembledSegment {
5001 tag: "CCI".to_string(),
5002 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
5003 mig_number: None,
5004 }],
5005 child_groups: vec![],
5006 entry_mig_number: None,
5007 variant_mig_numbers: vec![],
5008 skipped_segments: vec![],
5009 }],
5010 }],
5011 skipped_segments: vec![],
5012 }],
5013 }],
5014 skipped_segments: vec![],
5015 }],
5016 }],
5017 post_group_start: 0,
5018 inter_group_segments: std::collections::BTreeMap::new(),
5019 };
5020
5021 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5022 companion_fields.insert(
5023 "cci.2".to_string(),
5024 FieldMapping::Simple("haushaltskunde".to_string()),
5025 );
5026
5027 let def = MappingDefinition {
5028 meta: MappingMeta {
5029 entity: "Marktlokation".to_string(),
5030 bo4e_type: "Marktlokation".to_string(),
5031 companion_type: Some("MarktlokationEdifact".to_string()),
5032 source_group: "SG4.SG8.SG10".to_string(),
5033 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5034 discriminator: None,
5035 repeat_on_tag: None,
5036 },
5037 fields: IndexMap::new(),
5038 companion_fields: Some(companion_fields),
5039 complex_handlers: None,
5040 };
5041
5042 let engine = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
5043 let bo4e = engine.map_forward(&tree, &def, 0);
5044 let hk = &bo4e["marktlokationEdifact"]["haushaltskunde"];
5045 assert_eq!(hk["code"].as_str(), Some("Z15"));
5046 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
5047 assert_eq!(
5048 hk["enum"].as_str(),
5049 Some("HAUSHALTSKUNDE"),
5050 "enum field should be present"
5051 );
5052 }
5053
5054 #[test]
5055 fn test_reverse_mapping_accepts_enriched_with_enum() {
5056 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5058 companion_fields.insert(
5059 "cci.2".to_string(),
5060 FieldMapping::Simple("haushaltskunde".to_string()),
5061 );
5062
5063 let def = MappingDefinition {
5064 meta: MappingMeta {
5065 entity: "Test".to_string(),
5066 bo4e_type: "Test".to_string(),
5067 companion_type: Some("TestEdifact".to_string()),
5068 source_group: "SG4".to_string(),
5069 source_path: None,
5070 discriminator: None,
5071 repeat_on_tag: None,
5072 },
5073 fields: IndexMap::new(),
5074 companion_fields: Some(companion_fields),
5075 complex_handlers: None,
5076 };
5077
5078 let engine = MappingEngine::from_definitions(vec![]);
5079
5080 let bo4e = serde_json::json!({
5081 "testEdifact": {
5082 "haushaltskunde": {
5083 "code": "Z15",
5084 "meaning": "Haushaltskunde",
5085 "enum": "HAUSHALTSKUNDE"
5086 }
5087 }
5088 });
5089 let instance = engine.map_reverse(&bo4e, &def);
5090 assert_eq!(instance.segments[0].elements[2], vec!["Z15"]);
5091 }
5092
5093 #[test]
5094 fn test_reverse_mapping_accepts_enriched_companion() {
5095 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5097 companion_fields.insert(
5098 "cci.2".to_string(),
5099 FieldMapping::Simple("haushaltskunde".to_string()),
5100 );
5101
5102 let def = MappingDefinition {
5103 meta: MappingMeta {
5104 entity: "Test".to_string(),
5105 bo4e_type: "Test".to_string(),
5106 companion_type: Some("TestEdifact".to_string()),
5107 source_group: "SG4".to_string(),
5108 source_path: None,
5109 discriminator: None,
5110 repeat_on_tag: None,
5111 },
5112 fields: IndexMap::new(),
5113 companion_fields: Some(companion_fields),
5114 complex_handlers: None,
5115 };
5116
5117 let engine = MappingEngine::from_definitions(vec![]);
5118
5119 let bo4e_plain = serde_json::json!({
5121 "testEdifact": {
5122 "haushaltskunde": "Z15"
5123 }
5124 });
5125 let instance_plain = engine.map_reverse(&bo4e_plain, &def);
5126 assert_eq!(instance_plain.segments[0].elements[2], vec!["Z15"]);
5127
5128 let bo4e_enriched = serde_json::json!({
5130 "testEdifact": {
5131 "haushaltskunde": {
5132 "code": "Z15",
5133 "meaning": "Haushaltskunde gem. EnWG"
5134 }
5135 }
5136 });
5137 let instance_enriched = engine.map_reverse(&bo4e_enriched, &def);
5138 assert_eq!(instance_enriched.segments[0].elements[2], vec!["Z15"]);
5139 }
5140
5141 #[test]
5142 fn test_resolve_child_relative_with_source_path() {
5143 let mut map: std::collections::HashMap<String, Vec<usize>> =
5144 std::collections::HashMap::new();
5145 map.insert("sg4.sg8_ze1".to_string(), vec![6]);
5146 map.insert("sg4.sg8_z98".to_string(), vec![0]);
5147
5148 assert_eq!(
5150 resolve_child_relative("SG8.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5151 "SG8:6.SG10"
5152 );
5153
5154 assert_eq!(
5156 resolve_child_relative("SG8:3.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5157 "SG8:3.SG10"
5158 );
5159
5160 assert_eq!(
5162 resolve_child_relative("SG8.SG10", Some("sg4.sg8_unknown.sg10"), &map, 0),
5163 "SG8.SG10"
5164 );
5165
5166 assert_eq!(
5168 resolve_child_relative("SG8.SG10", None, &map, 0),
5169 "SG8.SG10"
5170 );
5171
5172 assert_eq!(
5174 resolve_child_relative("SG8.SG9", Some("sg4.sg8_z98.sg9"), &map, 0),
5175 "SG8:0.SG9"
5176 );
5177
5178 map.insert("sg4.sg8_zf3".to_string(), vec![3, 4]);
5180 assert_eq!(
5181 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 0),
5182 "SG8:3.SG10"
5183 );
5184 assert_eq!(
5185 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 1),
5186 "SG8:4.SG10"
5187 );
5188 }
5189
5190 #[test]
5191 fn test_place_in_groups_returns_rep_index() {
5192 let mut groups: Vec<AssembledGroup> = Vec::new();
5193
5194 let instance = AssembledGroupInstance {
5196 segments: vec![],
5197 child_groups: vec![],
5198 entry_mig_number: None,
5199 variant_mig_numbers: vec![],
5200 skipped_segments: vec![],
5201 };
5202 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 0);
5203
5204 let instance = AssembledGroupInstance {
5206 segments: vec![],
5207 child_groups: vec![],
5208 entry_mig_number: None,
5209 variant_mig_numbers: vec![],
5210 skipped_segments: vec![],
5211 };
5212 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 1);
5213
5214 let instance = AssembledGroupInstance {
5216 segments: vec![],
5217 child_groups: vec![],
5218 entry_mig_number: None,
5219 variant_mig_numbers: vec![],
5220 skipped_segments: vec![],
5221 };
5222 assert_eq!(place_in_groups(&mut groups, "SG8:5", instance), 5);
5223 }
5224
5225 #[test]
5226 fn test_resolve_by_source_path() {
5227 use mig_assembly::assembler::*;
5228
5229 let tree = AssembledTree {
5231 segments: vec![],
5232 groups: vec![AssembledGroup {
5233 group_id: "SG4".to_string(),
5234 repetitions: vec![AssembledGroupInstance {
5235 segments: vec![],
5236 child_groups: vec![AssembledGroup {
5237 group_id: "SG8".to_string(),
5238 repetitions: vec![
5239 AssembledGroupInstance {
5240 segments: vec![AssembledSegment {
5241 tag: "SEQ".to_string(),
5242 elements: vec![vec!["Z98".to_string()]],
5243 mig_number: None,
5244 }],
5245 child_groups: vec![AssembledGroup {
5246 group_id: "SG10".to_string(),
5247 repetitions: vec![AssembledGroupInstance {
5248 segments: vec![AssembledSegment {
5249 tag: "CCI".to_string(),
5250 elements: vec![vec![], vec![], vec!["ZB3".to_string()]],
5251 mig_number: None,
5252 }],
5253 child_groups: vec![],
5254 entry_mig_number: None,
5255 variant_mig_numbers: vec![],
5256 skipped_segments: vec![],
5257 }],
5258 }],
5259 skipped_segments: vec![],
5260 },
5261 AssembledGroupInstance {
5262 segments: vec![AssembledSegment {
5263 tag: "SEQ".to_string(),
5264 elements: vec![vec!["ZD7".to_string()]],
5265 mig_number: None,
5266 }],
5267 child_groups: vec![AssembledGroup {
5268 group_id: "SG10".to_string(),
5269 repetitions: vec![AssembledGroupInstance {
5270 segments: vec![AssembledSegment {
5271 tag: "CCI".to_string(),
5272 elements: vec![vec![], vec![], vec!["ZE6".to_string()]],
5273 mig_number: None,
5274 }],
5275 child_groups: vec![],
5276 entry_mig_number: None,
5277 variant_mig_numbers: vec![],
5278 skipped_segments: vec![],
5279 }],
5280 }],
5281 skipped_segments: vec![],
5282 },
5283 ],
5284 }],
5285 skipped_segments: vec![],
5286 }],
5287 }],
5288 post_group_start: 0,
5289 inter_group_segments: std::collections::BTreeMap::new(),
5290 };
5291
5292 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_z98.sg10");
5294 assert!(inst.is_some());
5295 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
5296
5297 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zd7.sg10");
5299 assert!(inst.is_some());
5300 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZE6");
5301
5302 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zzz.sg10");
5304 assert!(inst.is_none());
5305
5306 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8.sg10");
5308 assert!(inst.is_some());
5309 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
5310 }
5311
5312 #[test]
5313 fn test_parse_source_path_part() {
5314 assert_eq!(parse_source_path_part("sg4"), ("sg4", None));
5315 assert_eq!(parse_source_path_part("sg8_z98"), ("sg8", Some("z98")));
5316 assert_eq!(parse_source_path_part("sg10"), ("sg10", None));
5317 assert_eq!(parse_source_path_part("sg12_z04"), ("sg12", Some("z04")));
5318 }
5319
5320 #[test]
5321 fn test_has_source_path_qualifiers() {
5322 assert!(has_source_path_qualifiers("sg4.sg8_z98.sg10"));
5323 assert!(has_source_path_qualifiers("sg4.sg8_ze1.sg9"));
5324 assert!(!has_source_path_qualifiers("sg4.sg6"));
5325 assert!(!has_source_path_qualifiers("sg4.sg8.sg10"));
5326 }
5327
5328 #[test]
5329 fn test_companion_dotted_path_forward() {
5330 use mig_assembly::assembler::*;
5331
5332 let tree = AssembledTree {
5334 segments: vec![],
5335 groups: vec![AssembledGroup {
5336 group_id: "SG4".to_string(),
5337 repetitions: vec![AssembledGroupInstance {
5338 segments: vec![],
5339 child_groups: vec![AssembledGroup {
5340 group_id: "SG8".to_string(),
5341 repetitions: vec![AssembledGroupInstance {
5342 segments: vec![],
5343 child_groups: vec![AssembledGroup {
5344 group_id: "SG10".to_string(),
5345 repetitions: vec![AssembledGroupInstance {
5346 segments: vec![AssembledSegment {
5347 tag: "CCI".to_string(),
5348 elements: vec![
5349 vec!["11XAB-1234".to_string()],
5350 vec!["305".to_string()],
5351 ],
5352 mig_number: None,
5353 }],
5354 child_groups: vec![],
5355 entry_mig_number: None,
5356 variant_mig_numbers: vec![],
5357 skipped_segments: vec![],
5358 }],
5359 }],
5360 skipped_segments: vec![],
5361 }],
5362 }],
5363 skipped_segments: vec![],
5364 }],
5365 }],
5366 post_group_start: 0,
5367 inter_group_segments: std::collections::BTreeMap::new(),
5368 };
5369
5370 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5372 companion_fields.insert(
5373 "cci.0".to_string(),
5374 FieldMapping::Simple("bilanzkreis.id".to_string()),
5375 );
5376 companion_fields.insert(
5377 "cci.1".to_string(),
5378 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
5379 );
5380
5381 let def = MappingDefinition {
5382 meta: MappingMeta {
5383 entity: "Test".to_string(),
5384 bo4e_type: "Test".to_string(),
5385 companion_type: Some("TestEdifact".to_string()),
5386 source_group: "SG4.SG8.SG10".to_string(),
5387 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5388 discriminator: None,
5389 repeat_on_tag: None,
5390 },
5391 fields: IndexMap::new(),
5392 companion_fields: Some(companion_fields),
5393 complex_handlers: None,
5394 };
5395
5396 let engine = MappingEngine::from_definitions(vec![]);
5397 let bo4e = engine.map_forward(&tree, &def, 0);
5398
5399 let companion = &bo4e["testEdifact"];
5401 assert!(
5402 companion.is_object(),
5403 "testEdifact should be an object, got: {companion}"
5404 );
5405 let bilanzkreis = &companion["bilanzkreis"];
5406 assert!(
5407 bilanzkreis.is_object(),
5408 "bilanzkreis should be a nested object, got: {bilanzkreis}"
5409 );
5410 assert_eq!(
5411 bilanzkreis["id"].as_str(),
5412 Some("11XAB-1234"),
5413 "bilanzkreis.id should be 11XAB-1234"
5414 );
5415 assert_eq!(
5416 bilanzkreis["codelist"].as_str(),
5417 Some("305"),
5418 "bilanzkreis.codelist should be 305"
5419 );
5420 }
5421
5422 #[test]
5423 fn test_companion_dotted_path_reverse() {
5424 let engine = MappingEngine::from_definitions(vec![]);
5426
5427 let companion_value = serde_json::json!({
5428 "bilanzkreis": {
5429 "id": "11XAB-1234",
5430 "codelist": "305"
5431 }
5432 });
5433
5434 assert_eq!(
5435 engine.populate_field(&companion_value, "bilanzkreis.id"),
5436 Some("11XAB-1234".to_string()),
5437 "dotted path bilanzkreis.id should resolve"
5438 );
5439 assert_eq!(
5440 engine.populate_field(&companion_value, "bilanzkreis.codelist"),
5441 Some("305".to_string()),
5442 "dotted path bilanzkreis.codelist should resolve"
5443 );
5444
5445 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5447 companion_fields.insert(
5448 "cci.0".to_string(),
5449 FieldMapping::Simple("bilanzkreis.id".to_string()),
5450 );
5451 companion_fields.insert(
5452 "cci.1".to_string(),
5453 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
5454 );
5455
5456 let def = MappingDefinition {
5457 meta: MappingMeta {
5458 entity: "Test".to_string(),
5459 bo4e_type: "Test".to_string(),
5460 companion_type: Some("TestEdifact".to_string()),
5461 source_group: "SG4.SG8.SG10".to_string(),
5462 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5463 discriminator: None,
5464 repeat_on_tag: None,
5465 },
5466 fields: IndexMap::new(),
5467 companion_fields: Some(companion_fields),
5468 complex_handlers: None,
5469 };
5470
5471 let bo4e = serde_json::json!({
5472 "testEdifact": {
5473 "bilanzkreis": {
5474 "id": "11XAB-1234",
5475 "codelist": "305"
5476 }
5477 }
5478 });
5479
5480 let instance = engine.map_reverse(&bo4e, &def);
5481 assert_eq!(instance.segments.len(), 1, "should produce one CCI segment");
5482 let cci = &instance.segments[0];
5483 assert_eq!(cci.tag, "CCI");
5484 assert_eq!(
5485 cci.elements[0],
5486 vec!["11XAB-1234"],
5487 "element 0 should contain bilanzkreis.id"
5488 );
5489 assert_eq!(
5490 cci.elements[1],
5491 vec!["305"],
5492 "element 1 should contain bilanzkreis.codelist"
5493 );
5494 }
5495
5496 #[test]
5497 fn test_when_filled_injects_when_field_present() {
5498 let toml_str = r#"
5499[meta]
5500entity = "Test"
5501bo4e_type = "Test"
5502companion_type = "TestEdifact"
5503source_group = "SG4.SG8.SG10"
5504
5505[fields]
5506
5507[companion_fields]
5508"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmalCode"] }
5509"cav.0.0" = "merkmalCode"
5510"#;
5511 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5512
5513 let bo4e_with = serde_json::json!({
5515 "testEdifact": { "merkmalCode": "ZA7" }
5516 });
5517 let engine = MappingEngine::new_empty();
5518 let instance = engine.map_reverse(&bo4e_with, &def);
5519 let cci = instance
5520 .segments
5521 .iter()
5522 .find(|s| s.tag == "CCI")
5523 .expect("CCI should exist");
5524 assert_eq!(cci.elements[0][0], "Z83");
5525
5526 let bo4e_without = serde_json::json!({
5528 "testEdifact": {}
5529 });
5530 let instance2 = engine.map_reverse(&bo4e_without, &def);
5531 let cci2 = instance2.segments.iter().find(|s| s.tag == "CCI");
5532 assert!(
5533 cci2.is_none(),
5534 "CCI should not be emitted when merkmalCode is absent"
5535 );
5536 }
5537
5538 #[test]
5539 fn test_when_filled_checks_core_and_companion() {
5540 let toml_str = r#"
5541[meta]
5542entity = "Test"
5543bo4e_type = "Test"
5544companion_type = "TestEdifact"
5545source_group = "SG4.SG5"
5546
5547[fields]
5548"loc.1.0" = "marktlokationsId"
5549
5550[companion_fields]
5551"loc.0.0" = { target = "", default = "Z16", when_filled = ["marktlokationsId"] }
5552"#;
5553 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5554
5555 let bo4e_with = serde_json::json!({
5557 "marktlokationsId": "51234567890"
5558 });
5559 let engine = MappingEngine::new_empty();
5560 let instance = engine.map_reverse(&bo4e_with, &def);
5561 let loc = instance
5562 .segments
5563 .iter()
5564 .find(|s| s.tag == "LOC")
5565 .expect("LOC should exist");
5566 assert_eq!(loc.elements[0][0], "Z16");
5567 assert_eq!(loc.elements[1][0], "51234567890");
5568
5569 let bo4e_without = serde_json::json!({});
5571 let instance2 = engine.map_reverse(&bo4e_without, &def);
5572 let loc2 = instance2.segments.iter().find(|s| s.tag == "LOC");
5573 assert!(loc2.is_none());
5574 }
5575
5576 #[test]
5577 fn test_extract_all_from_instance_collects_all_qualifier_matches() {
5578 use mig_assembly::assembler::*;
5579
5580 let instance = AssembledGroupInstance {
5582 segments: vec![
5583 AssembledSegment {
5584 tag: "SEQ".to_string(),
5585 elements: vec![vec!["ZD6".to_string()]],
5586 mig_number: None,
5587 },
5588 AssembledSegment {
5589 tag: "RFF".to_string(),
5590 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
5591 mig_number: None,
5592 },
5593 AssembledSegment {
5594 tag: "RFF".to_string(),
5595 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
5596 mig_number: None,
5597 },
5598 AssembledSegment {
5599 tag: "RFF".to_string(),
5600 elements: vec![vec!["Z34".to_string(), "REF_C".to_string()]],
5601 mig_number: None,
5602 },
5603 AssembledSegment {
5604 tag: "RFF".to_string(),
5605 elements: vec![vec!["Z35".to_string(), "OTHER".to_string()]],
5606 mig_number: None,
5607 },
5608 ],
5609 child_groups: vec![],
5610 entry_mig_number: None,
5611 variant_mig_numbers: vec![],
5612 skipped_segments: vec![],
5613 };
5614
5615 let all = MappingEngine::extract_all_from_instance(&instance, "rff[Z34,*].0.1");
5617 assert_eq!(all, vec!["REF_A", "REF_B", "REF_C"]);
5618
5619 let single = MappingEngine::extract_from_instance(&instance, "rff[Z34].0.1");
5621 assert_eq!(single, Some("REF_A".to_string()));
5622
5623 let second = MappingEngine::extract_from_instance(&instance, "rff[Z34,1].0.1");
5624 assert_eq!(second, Some("REF_B".to_string()));
5625 }
5626
5627 #[test]
5628 fn test_forward_wildcard_collect_produces_json_array() {
5629 use mig_assembly::assembler::*;
5630
5631 let instance = AssembledGroupInstance {
5632 segments: vec![
5633 AssembledSegment {
5634 tag: "SEQ".to_string(),
5635 elements: vec![vec!["ZD6".to_string()]],
5636 mig_number: None,
5637 },
5638 AssembledSegment {
5639 tag: "RFF".to_string(),
5640 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
5641 mig_number: None,
5642 },
5643 AssembledSegment {
5644 tag: "RFF".to_string(),
5645 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
5646 mig_number: None,
5647 },
5648 ],
5649 child_groups: vec![],
5650 entry_mig_number: None,
5651 variant_mig_numbers: vec![],
5652 skipped_segments: vec![],
5653 };
5654
5655 let toml_str = r#"
5656[meta]
5657entity = "Test"
5658bo4e_type = "Test"
5659companion_type = "TestEdifact"
5660source_group = "SG4.SG8"
5661
5662[fields]
5663
5664[companion_fields]
5665"rff[Z34,*].0.1" = "messlokationsIdRefs"
5666"#;
5667 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5668 let engine = MappingEngine::new_empty();
5669
5670 let mut result = serde_json::Map::new();
5671 engine.extract_companion_fields(&instance, &def, &mut result, false);
5672
5673 let companion = result.get("testEdifact").unwrap().as_object().unwrap();
5674 let refs = companion
5675 .get("messlokationsIdRefs")
5676 .unwrap()
5677 .as_array()
5678 .unwrap();
5679 assert_eq!(refs.len(), 2);
5680 assert_eq!(refs[0].as_str().unwrap(), "REF_A");
5681 assert_eq!(refs[1].as_str().unwrap(), "REF_B");
5682 }
5683
5684 #[test]
5685 fn test_reverse_json_array_produces_multiple_segments() {
5686 let toml_str = r#"
5687[meta]
5688entity = "Test"
5689bo4e_type = "Test"
5690companion_type = "TestEdifact"
5691source_group = "SG4.SG8"
5692
5693[fields]
5694
5695[companion_fields]
5696"seq.0.0" = { target = "", default = "ZD6" }
5697"rff[Z34,*].0.1" = "messlokationsIdRefs"
5698"#;
5699 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5700 let engine = MappingEngine::new_empty();
5701
5702 let bo4e = serde_json::json!({
5703 "testEdifact": {
5704 "messlokationsIdRefs": ["REF_A", "REF_B", "REF_C"]
5705 }
5706 });
5707
5708 let instance = engine.map_reverse(&bo4e, &def);
5709
5710 let rff_segs: Vec<_> = instance
5712 .segments
5713 .iter()
5714 .filter(|s| s.tag == "RFF")
5715 .collect();
5716 assert_eq!(rff_segs.len(), 3);
5717 assert_eq!(rff_segs[0].elements[0][0], "Z34");
5718 assert_eq!(rff_segs[0].elements[0][1], "REF_A");
5719 assert_eq!(rff_segs[1].elements[0][0], "Z34");
5720 assert_eq!(rff_segs[1].elements[0][1], "REF_B");
5721 assert_eq!(rff_segs[2].elements[0][0], "Z34");
5722 assert_eq!(rff_segs[2].elements[0][1], "REF_C");
5723 }
5724
5725 #[test]
5726 fn test_when_filled_dotted_path() {
5727 let toml_str = r#"
5728[meta]
5729entity = "Test"
5730bo4e_type = "Test"
5731companion_type = "TestEdifact"
5732source_group = "SG4.SG8.SG10"
5733
5734[fields]
5735
5736[companion_fields]
5737"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmal.code"] }
5738"cav.0.0" = "merkmal.code"
5739"#;
5740 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5741
5742 let bo4e = serde_json::json!({
5743 "testEdifact": { "merkmal": { "code": "ZA7" } }
5744 });
5745 let engine = MappingEngine::new_empty();
5746 let instance = engine.map_reverse(&bo4e, &def);
5747 let cci = instance
5748 .segments
5749 .iter()
5750 .find(|s| s.tag == "CCI")
5751 .expect("CCI should exist");
5752 assert_eq!(cci.elements[0][0], "Z83");
5753 }
5754
5755 #[test]
5756 fn test_also_target_forward_extracts_both_fields() {
5757 use mig_assembly::assembler::*;
5758
5759 let instance = AssembledGroupInstance {
5760 segments: vec![AssembledSegment {
5761 tag: "NAD".to_string(),
5762 elements: vec![vec!["Z47".to_string()], vec!["12345".to_string()]],
5763 mig_number: None,
5764 }],
5765 child_groups: vec![],
5766 entry_mig_number: None,
5767 variant_mig_numbers: vec![],
5768 skipped_segments: vec![],
5769 };
5770
5771 let toml_str = r#"
5772[meta]
5773entity = "Geschaeftspartner"
5774bo4e_type = "Geschaeftspartner"
5775companion_type = "GeschaeftspartnerEdifact"
5776source_group = "SG4.SG12"
5777
5778[fields]
5779"nad.1.0" = "identifikation"
5780
5781[companion_fields."nad.0.0"]
5782target = "partnerrolle"
5783enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
5784also_target = "datenqualitaet"
5785also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
5786"#;
5787 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5788 let engine = MappingEngine::new_empty();
5789
5790 let mut result = serde_json::Map::new();
5791 engine.extract_companion_fields(&instance, &def, &mut result, false);
5792
5793 let companion = result
5794 .get("geschaeftspartnerEdifact")
5795 .unwrap()
5796 .as_object()
5797 .unwrap();
5798 assert_eq!(
5799 companion.get("partnerrolle").unwrap().as_str().unwrap(),
5800 "kundeDesLf"
5801 );
5802 assert_eq!(
5803 companion.get("datenqualitaet").unwrap().as_str().unwrap(),
5804 "erwartet"
5805 );
5806 }
5807
5808 #[test]
5809 fn test_also_target_reverse_joint_lookup() {
5810 let toml_str = r#"
5811[meta]
5812entity = "Geschaeftspartner"
5813bo4e_type = "Geschaeftspartner"
5814companion_type = "GeschaeftspartnerEdifact"
5815source_group = "SG4.SG12"
5816
5817[fields]
5818
5819[companion_fields."nad.0.0"]
5820target = "partnerrolle"
5821enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
5822also_target = "datenqualitaet"
5823also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
5824"#;
5825 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5826 let engine = MappingEngine::new_empty();
5827
5828 let bo4e = serde_json::json!({
5830 "geschaeftspartnerEdifact": {
5831 "partnerrolle": "kundeDesLf",
5832 "datenqualitaet": "erwartet"
5833 }
5834 });
5835 let instance = engine.map_reverse(&bo4e, &def);
5836 let nad = instance
5837 .segments
5838 .iter()
5839 .find(|s| s.tag == "NAD")
5840 .expect("NAD");
5841 assert_eq!(nad.elements[0][0], "Z47");
5842
5843 let bo4e2 = serde_json::json!({
5845 "geschaeftspartnerEdifact": {
5846 "partnerrolle": "kundeDesNb",
5847 "datenqualitaet": "imSystemVorhanden"
5848 }
5849 });
5850 let instance2 = engine.map_reverse(&bo4e2, &def);
5851 let nad2 = instance2
5852 .segments
5853 .iter()
5854 .find(|s| s.tag == "NAD")
5855 .expect("NAD");
5856 assert_eq!(nad2.elements[0][0], "Z52");
5857 }
5858
5859 #[test]
5860 fn test_also_target_mixed_codes_unpaired_skips_datenqualitaet() {
5861 use mig_assembly::assembler::*;
5862
5863 let toml_str = r#"
5865[meta]
5866entity = "Geschaeftspartner"
5867bo4e_type = "Geschaeftspartner"
5868companion_type = "GeschaeftspartnerEdifact"
5869source_group = "SG4.SG12"
5870
5871[fields]
5872
5873[companion_fields."nad.0.0"]
5874target = "partnerrolle"
5875enum_map = { "Z09" = "kundeDesLf", "Z47" = "kundeDesLf", "Z48" = "kundeDesLf" }
5876also_target = "datenqualitaet"
5877also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden" }
5878"#;
5879 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5880 let engine = MappingEngine::new_empty();
5881
5882 let instance_z09 = AssembledGroupInstance {
5884 segments: vec![AssembledSegment {
5885 tag: "NAD".to_string(),
5886 elements: vec![vec!["Z09".to_string()]],
5887 mig_number: None,
5888 }],
5889 child_groups: vec![],
5890 entry_mig_number: None,
5891 variant_mig_numbers: vec![],
5892 skipped_segments: vec![],
5893 };
5894 let mut result = serde_json::Map::new();
5895 engine.extract_companion_fields(&instance_z09, &def, &mut result, false);
5896 let comp = result
5897 .get("geschaeftspartnerEdifact")
5898 .unwrap()
5899 .as_object()
5900 .unwrap();
5901 assert_eq!(
5902 comp.get("partnerrolle").unwrap().as_str().unwrap(),
5903 "kundeDesLf"
5904 );
5905 assert!(
5906 comp.get("datenqualitaet").is_none(),
5907 "Z09 should not set datenqualitaet"
5908 );
5909
5910 let bo4e = serde_json::json!({
5912 "geschaeftspartnerEdifact": { "partnerrolle": "kundeDesLf" }
5913 });
5914 let instance = engine.map_reverse(&bo4e, &def);
5915 let nad = instance
5916 .segments
5917 .iter()
5918 .find(|s| s.tag == "NAD")
5919 .expect("NAD");
5920 assert_eq!(nad.elements[0][0], "Z09");
5921
5922 let bo4e2 = serde_json::json!({
5924 "geschaeftspartnerEdifact": {
5925 "partnerrolle": "kundeDesLf",
5926 "datenqualitaet": "erwartet"
5927 }
5928 });
5929 let instance2 = engine.map_reverse(&bo4e2, &def);
5930 let nad2 = instance2
5931 .segments
5932 .iter()
5933 .find(|s| s.tag == "NAD")
5934 .expect("NAD");
5935 assert_eq!(nad2.elements[0][0], "Z47");
5936 }
5937}