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 skipped_segments: Vec::new(),
770 };
771 self.extract_fields_from_instance(&root_instance, def, &mut result, enrich_codes);
772 self.extract_companion_fields(&root_instance, def, &mut result, enrich_codes);
773 return serde_json::Value::Object(result);
774 }
775
776 let instance = if let Some(ref sp) = def.meta.source_path {
782 if has_source_path_qualifiers(sp) && !def.meta.source_group.contains(':') {
783 Self::resolve_by_source_path(tree, sp).or_else(|| {
784 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
785 })
786 } else {
787 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
788 }
789 } else {
790 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
791 };
792
793 if let Some(instance) = instance {
794 if let Some(ref tag) = def.meta.repeat_on_tag {
796 let matching: Vec<_> = instance
797 .segments
798 .iter()
799 .filter(|s| s.tag.eq_ignore_ascii_case(tag))
800 .collect();
801
802 if matching.len() > 1 {
803 let mut arr = Vec::new();
804 for seg in &matching {
805 let sub_instance = AssembledGroupInstance {
806 segments: vec![(*seg).clone()],
807 child_groups: vec![],
808 skipped_segments: Vec::new(),
809 };
810 let mut elem_result = serde_json::Map::new();
811 self.extract_fields_from_instance(
812 &sub_instance,
813 def,
814 &mut elem_result,
815 enrich_codes,
816 );
817 self.extract_companion_fields(
818 &sub_instance,
819 def,
820 &mut elem_result,
821 enrich_codes,
822 );
823 if !elem_result.is_empty() {
824 arr.push(serde_json::Value::Object(elem_result));
825 }
826 }
827 if !arr.is_empty() {
828 return serde_json::Value::Array(arr);
829 }
830 }
831 }
832
833 self.extract_fields_from_instance(instance, def, &mut result, enrich_codes);
834 self.extract_companion_fields(instance, def, &mut result, enrich_codes);
835 }
836
837 serde_json::Value::Object(result)
838 }
839
840 fn extract_companion_fields(
845 &self,
846 instance: &AssembledGroupInstance,
847 def: &MappingDefinition,
848 result: &mut serde_json::Map<String, serde_json::Value>,
849 enrich_codes: bool,
850 ) {
851 if let Some(ref companion_fields) = def.companion_fields {
852 let raw_key = def.meta.companion_type.as_deref().unwrap_or("_companion");
853 let companion_key = to_camel_case(raw_key);
854 let mut companion_result = serde_json::Map::new();
855
856 for (path, field_mapping) in companion_fields {
857 let (target, enum_map, also_target, also_enum_map) = match field_mapping {
858 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
859 FieldMapping::Structured(s) => (
860 s.target.as_str(),
861 s.enum_map.as_ref(),
862 s.also_target.as_deref(),
863 s.also_enum_map.as_ref(),
864 ),
865 FieldMapping::Nested(_) => continue,
866 };
867 if target.is_empty() {
868 continue;
869 }
870
871 if is_collect_all_path(path) {
873 let all = Self::extract_all_from_instance(instance, path);
874 if !all.is_empty() {
875 let arr: Vec<serde_json::Value> = all
876 .into_iter()
877 .map(|v| {
878 let mapped = if let Some(map) = enum_map {
879 map.get(&v).cloned().unwrap_or_else(|| v.clone())
880 } else {
881 v
882 };
883 serde_json::Value::String(mapped)
884 })
885 .collect();
886 set_nested_value_json(
887 &mut companion_result,
888 target,
889 serde_json::Value::Array(arr),
890 );
891 }
892 continue;
893 }
894
895 if let Some(val) = Self::extract_from_instance(instance, path) {
896 let mapped_val = if let Some(map) = enum_map {
897 map.get(&val).cloned().unwrap_or_else(|| val.clone())
898 } else {
899 val.clone()
900 };
901
902 if enrich_codes {
904 if let (Some(ref code_lookup), Some(ref source_path)) =
905 (&self.code_lookup, &def.meta.source_path)
906 {
907 let parts: Vec<&str> = path.split('.').collect();
908 let (seg_tag, _qualifier, _occ) = parse_tag_qualifier(parts[0]);
909 let (element_idx, component_idx) =
910 Self::parse_element_component(&parts[1..]);
911
912 if code_lookup.is_code_field(
913 source_path,
914 &seg_tag,
915 element_idx,
916 component_idx,
917 ) {
918 let enrichment = code_lookup.enrichment_for(
922 source_path,
923 &seg_tag,
924 element_idx,
925 component_idx,
926 &val,
927 );
928 let meaning = enrichment
929 .map(|e| serde_json::Value::String(e.meaning.clone()))
930 .unwrap_or(serde_json::Value::Null);
931
932 let mut obj = serde_json::Map::new();
933 obj.insert("code".into(), serde_json::json!(mapped_val));
934 obj.insert("meaning".into(), meaning);
935 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref())
936 {
937 obj.insert("enum".into(), serde_json::json!(enum_key));
938 }
939 let enriched = serde_json::Value::Object(obj);
940 set_nested_value_json(&mut companion_result, target, enriched);
941 continue;
942 }
943 }
944 }
945
946 set_nested_value(&mut companion_result, target, mapped_val);
947
948 if let (Some(at), Some(am)) = (also_target, also_enum_map) {
952 if let Some(also_mapped) = am.get(&val) {
953 set_nested_value(&mut companion_result, at, also_mapped.clone());
954 }
955 }
956 }
957 }
958
959 if !companion_result.is_empty() {
960 result.insert(
961 companion_key.to_string(),
962 serde_json::Value::Object(companion_result),
963 );
964 }
965 }
966 }
967
968 fn extract_fields_from_instance(
973 &self,
974 instance: &AssembledGroupInstance,
975 def: &MappingDefinition,
976 result: &mut serde_json::Map<String, serde_json::Value>,
977 enrich_codes: bool,
978 ) {
979 for (path, field_mapping) in &def.fields {
980 let (target, enum_map) = match field_mapping {
981 FieldMapping::Simple(t) => (t.as_str(), None),
982 FieldMapping::Structured(s) => (s.target.as_str(), s.enum_map.as_ref()),
983 FieldMapping::Nested(_) => continue,
984 };
985 if target.is_empty() {
986 continue;
987 }
988 if let Some(val) = Self::extract_from_instance(instance, path) {
989 let mapped_val = if let Some(map) = enum_map {
990 map.get(&val).cloned().unwrap_or_else(|| val.clone())
991 } else {
992 val.clone()
993 };
994
995 if enrich_codes {
997 if let (Some(ref code_lookup), Some(ref source_path)) =
998 (&self.code_lookup, &def.meta.source_path)
999 {
1000 let parts: Vec<&str> = path.split('.').collect();
1001 let (seg_tag, _qualifier, _occ) = parse_tag_qualifier(parts[0]);
1002 let (element_idx, component_idx) =
1003 Self::parse_element_component(&parts[1..]);
1004
1005 if code_lookup.is_code_field(
1006 source_path,
1007 &seg_tag,
1008 element_idx,
1009 component_idx,
1010 ) {
1011 let enrichment = code_lookup.enrichment_for(
1015 source_path,
1016 &seg_tag,
1017 element_idx,
1018 component_idx,
1019 &val,
1020 );
1021 let meaning = enrichment
1022 .map(|e| serde_json::Value::String(e.meaning.clone()))
1023 .unwrap_or(serde_json::Value::Null);
1024
1025 let mut obj = serde_json::Map::new();
1026 obj.insert("code".into(), serde_json::json!(mapped_val));
1027 obj.insert("meaning".into(), meaning);
1028 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
1029 obj.insert("enum".into(), serde_json::json!(enum_key));
1030 }
1031 let enriched = serde_json::Value::Object(obj);
1032 set_nested_value_json(result, target, enriched);
1033 continue;
1034 }
1035 }
1036 }
1037
1038 set_nested_value(result, target, mapped_val);
1039 }
1040 }
1041 }
1042
1043 pub fn map_forward_from_segments(
1049 &self,
1050 segments: &[OwnedSegment],
1051 def: &MappingDefinition,
1052 ) -> serde_json::Value {
1053 let assembled_segments: Vec<AssembledSegment> = segments
1054 .iter()
1055 .map(|s| AssembledSegment {
1056 tag: s.id.clone(),
1057 elements: s.elements.clone(),
1058 })
1059 .collect();
1060
1061 let instance = AssembledGroupInstance {
1062 segments: assembled_segments,
1063 child_groups: vec![],
1064 skipped_segments: Vec::new(),
1065 };
1066
1067 let mut result = serde_json::Map::new();
1068 self.extract_fields_from_instance(&instance, def, &mut result, true);
1069 serde_json::Value::Object(result)
1070 }
1071
1072 pub fn map_reverse(
1085 &self,
1086 bo4e_value: &serde_json::Value,
1087 def: &MappingDefinition,
1088 ) -> AssembledGroupInstance {
1089 if def.meta.repeat_on_tag.is_some() {
1091 if let Some(arr) = bo4e_value.as_array() {
1092 let mut all_segments = Vec::new();
1093 for elem in arr {
1094 let sub = self.map_reverse_single(elem, def);
1095 all_segments.extend(sub.segments);
1096 }
1097 return AssembledGroupInstance {
1098 segments: all_segments,
1099 child_groups: vec![],
1100 skipped_segments: Vec::new(),
1101 };
1102 }
1103 }
1104 self.map_reverse_single(bo4e_value, def)
1105 }
1106
1107 fn map_reverse_single(
1108 &self,
1109 bo4e_value: &serde_json::Value,
1110 def: &MappingDefinition,
1111 ) -> AssembledGroupInstance {
1112 let mut field_values: Vec<(String, String, usize, usize, String)> =
1115 Vec::with_capacity(def.fields.len());
1116
1117 let mut has_real_data = false;
1124 let mut has_data_fields = false;
1125 let mut seg_has_data_field: HashSet<String> = HashSet::new();
1128 let mut seg_has_real_data: HashSet<String> = HashSet::new();
1129 let mut injected_qualifiers: HashSet<String> = HashSet::new();
1130
1131 for (path, field_mapping) in &def.fields {
1132 let (target, default, enum_map, when_filled) = match field_mapping {
1133 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
1134 FieldMapping::Structured(s) => (
1135 s.target.as_str(),
1136 s.default.as_ref(),
1137 s.enum_map.as_ref(),
1138 s.when_filled.as_ref(),
1139 ),
1140 FieldMapping::Nested(_) => continue,
1141 };
1142
1143 let parts: Vec<&str> = path.split('.').collect();
1144 if parts.len() < 2 {
1145 continue;
1146 }
1147
1148 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1149 let seg_key = parts[0].to_uppercase();
1152 let sub_path = &parts[1..];
1153
1154 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1156 let ci = if sub_path.len() > 1 {
1157 sub_path[1].parse::<usize>().unwrap_or(0)
1158 } else {
1159 0
1160 };
1161 (ei, ci)
1162 } else {
1163 match sub_path.len() {
1164 1 => (0, 0),
1165 2 => (1, 0),
1166 _ => continue,
1167 }
1168 };
1169
1170 let val = if target.is_empty() {
1172 match (default, when_filled) {
1173 (Some(d), Some(fields)) => {
1176 let companion_key_for_check =
1177 def.meta.companion_type.as_deref().map(to_camel_case);
1178 let companion_for_check = companion_key_for_check
1179 .as_ref()
1180 .and_then(|k| bo4e_value.get(k))
1181 .unwrap_or(&serde_json::Value::Null);
1182 let any_filled = fields.iter().any(|f| {
1183 self.populate_field(bo4e_value, f).is_some()
1184 || self.populate_field(companion_for_check, f).is_some()
1185 });
1186 if any_filled {
1187 has_real_data = true;
1191 Some(d.clone())
1192 } else {
1193 None
1194 }
1195 }
1196 (Some(d), None) => Some(d.clone()),
1198 (None, _) => None,
1199 }
1200 } else {
1201 has_data_fields = true;
1202 seg_has_data_field.insert(seg_key.clone());
1203 let bo4e_val = self.populate_field(bo4e_value, target);
1204 if bo4e_val.is_some() {
1205 has_real_data = true;
1206 seg_has_real_data.insert(seg_key.clone());
1207 }
1208 let mapped_val = match (bo4e_val, enum_map) {
1210 (Some(v), Some(map)) => {
1211 map.iter()
1213 .find(|(_, bo4e_v)| *bo4e_v == &v)
1214 .map(|(edifact_k, _)| edifact_k.clone())
1215 .or(Some(v))
1216 }
1217 (v, _) => v,
1218 };
1219 mapped_val.or_else(|| default.cloned())
1220 };
1221
1222 if let Some(val) = val {
1223 field_values.push((
1224 seg_key.clone(),
1225 seg_tag.clone(),
1226 element_idx,
1227 component_idx,
1228 val,
1229 ));
1230 }
1231
1232 if let Some(q) = qualifier {
1234 if injected_qualifiers.insert(seg_key.clone()) {
1235 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1236 }
1237 }
1238 }
1239
1240 if let Some(ref companion_fields) = def.companion_fields {
1244 let raw_key = def.meta.companion_type.as_deref().unwrap_or("_companion");
1245 let companion_key = to_camel_case(raw_key);
1246 let companion_value = bo4e_value
1247 .get(&companion_key)
1248 .unwrap_or(bo4e_value);
1249
1250 for (path, field_mapping) in companion_fields {
1251 let (target, default, enum_map, when_filled, also_target, also_enum_map) =
1252 match field_mapping {
1253 FieldMapping::Simple(t) => (t.as_str(), None, None, None, None, None),
1254 FieldMapping::Structured(s) => (
1255 s.target.as_str(),
1256 s.default.as_ref(),
1257 s.enum_map.as_ref(),
1258 s.when_filled.as_ref(),
1259 s.also_target.as_deref(),
1260 s.also_enum_map.as_ref(),
1261 ),
1262 FieldMapping::Nested(_) => continue,
1263 };
1264
1265 let parts: Vec<&str> = path.split('.').collect();
1266 if parts.len() < 2 {
1267 continue;
1268 }
1269
1270 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1271 let seg_key = parts[0].to_uppercase();
1272 let sub_path = &parts[1..];
1273
1274 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1275 let ci = if sub_path.len() > 1 {
1276 sub_path[1].parse::<usize>().unwrap_or(0)
1277 } else {
1278 0
1279 };
1280 (ei, ci)
1281 } else {
1282 match sub_path.len() {
1283 1 => (0, 0),
1284 2 => (1, 0),
1285 _ => continue,
1286 }
1287 };
1288
1289 if is_collect_all_path(path) && !target.is_empty() {
1291 if let Some(arr) = self
1292 .populate_field_json(companion_value, target)
1293 .and_then(|v| v.as_array().cloned())
1294 {
1295 has_data_fields = true;
1296 if !arr.is_empty() {
1297 has_real_data = true;
1298 }
1299 for (idx, item) in arr.iter().enumerate() {
1300 if let Some(val_str) = item.as_str() {
1301 let mapped = if let Some(map) = enum_map {
1302 map.iter()
1303 .find(|(_, bo4e_v)| *bo4e_v == val_str)
1304 .map(|(edifact_k, _)| edifact_k.clone())
1305 .unwrap_or_else(|| val_str.to_string())
1306 } else {
1307 val_str.to_string()
1308 };
1309 let occ_key = if let Some(q) = qualifier {
1310 format!("{}[{},{}]", seg_tag, q, idx)
1311 } else {
1312 format!("{}[*,{}]", seg_tag, idx)
1313 };
1314 field_values.push((
1315 occ_key.clone(),
1316 seg_tag.clone(),
1317 element_idx,
1318 component_idx,
1319 mapped,
1320 ));
1321 if let Some(q) = qualifier {
1323 if injected_qualifiers.insert(occ_key.clone()) {
1324 field_values.push((
1325 occ_key,
1326 seg_tag.clone(),
1327 0,
1328 0,
1329 q.to_string(),
1330 ));
1331 }
1332 }
1333 }
1334 }
1335 }
1336 continue;
1337 }
1338
1339 let val = if target.is_empty() {
1340 match (default, when_filled) {
1341 (Some(d), Some(fields)) => {
1342 let any_filled = fields.iter().any(|f| {
1343 self.populate_field(bo4e_value, f).is_some()
1344 || self.populate_field(companion_value, f).is_some()
1345 });
1346 if any_filled {
1347 has_real_data = true;
1348 Some(d.clone())
1349 } else {
1350 None
1351 }
1352 }
1353 (Some(d), None) => Some(d.clone()),
1354 (None, _) => None,
1355 }
1356 } else {
1357 has_data_fields = true;
1358 seg_has_data_field.insert(seg_key.clone());
1359 let bo4e_val = self.populate_field(companion_value, target);
1360 if bo4e_val.is_some() {
1361 has_real_data = true;
1362 seg_has_real_data.insert(seg_key.clone());
1363 }
1364 let mapped_val = match (bo4e_val, enum_map) {
1365 (Some(v), Some(map)) => {
1366 if let (Some(at), Some(am)) = (also_target, also_enum_map) {
1367 let also_val = self.populate_field(companion_value, at);
1368 if let Some(av) = also_val.as_deref() {
1369 map.iter()
1371 .find(|(edifact_k, bo4e_v)| {
1372 *bo4e_v == &v
1373 && am.get(*edifact_k).is_some_and(|am_v| am_v == av)
1374 })
1375 .map(|(edifact_k, _)| edifact_k.clone())
1376 .or(Some(v))
1377 } else {
1378 map.iter()
1381 .find(|(edifact_k, bo4e_v)| {
1382 *bo4e_v == &v && !am.contains_key(*edifact_k)
1383 })
1384 .or_else(|| {
1385 map.iter().find(|(_, bo4e_v)| *bo4e_v == &v)
1387 })
1388 .map(|(edifact_k, _)| edifact_k.clone())
1389 .or(Some(v))
1390 }
1391 } else {
1392 map.iter()
1393 .find(|(_, bo4e_v)| *bo4e_v == &v)
1394 .map(|(edifact_k, _)| edifact_k.clone())
1395 .or(Some(v))
1396 }
1397 }
1398 (v, _) => v,
1399 };
1400 mapped_val.or_else(|| default.cloned())
1401 };
1402
1403 if let Some(val) = val {
1404 field_values.push((
1405 seg_key.clone(),
1406 seg_tag.clone(),
1407 element_idx,
1408 component_idx,
1409 val,
1410 ));
1411 }
1412
1413 if let Some(q) = qualifier {
1414 if injected_qualifiers.insert(seg_key.clone()) {
1415 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1416 }
1417 }
1418 }
1419 }
1420
1421 field_values.retain(|(seg_key, _, _, _, _)| {
1429 if !seg_key.contains('[') {
1430 return true; }
1432 !seg_has_data_field.contains(seg_key) || seg_has_real_data.contains(seg_key)
1433 });
1434
1435 if has_data_fields && !has_real_data {
1440 return AssembledGroupInstance {
1441 segments: vec![],
1442 child_groups: vec![],
1443 skipped_segments: Vec::new(),
1444 };
1445 }
1446
1447 let mut segments: Vec<AssembledSegment> = Vec::with_capacity(field_values.len());
1450 let mut seen_keys: HashMap<String, usize> = HashMap::new();
1451
1452 for (seg_key, seg_tag, element_idx, component_idx, val) in &field_values {
1453 let seg = if let Some(&pos) = seen_keys.get(seg_key) {
1454 &mut segments[pos]
1455 } else {
1456 let pos = segments.len();
1457 seen_keys.insert(seg_key.clone(), pos);
1458 segments.push(AssembledSegment {
1459 tag: seg_tag.clone(),
1460 elements: vec![],
1461 });
1462 &mut segments[pos]
1463 };
1464
1465 while seg.elements.len() <= *element_idx {
1466 seg.elements.push(vec![]);
1467 }
1468 while seg.elements[*element_idx].len() <= *component_idx {
1469 seg.elements[*element_idx].push(String::new());
1470 }
1471 seg.elements[*element_idx][*component_idx] = val.clone();
1472 }
1473
1474 for seg in &mut segments {
1477 let last_populated = seg.elements.iter().rposition(|e| !e.is_empty());
1478 if let Some(last_idx) = last_populated {
1479 for i in 0..last_idx {
1480 if seg.elements[i].is_empty() {
1481 seg.elements[i] = vec![String::new()];
1482 }
1483 }
1484 }
1485 }
1486
1487 if let Some(ref ss) = self.segment_structure {
1489 for seg in &mut segments {
1490 if let Some(expected) = ss.element_count(&seg.tag) {
1491 while seg.elements.len() < expected {
1492 seg.elements.push(vec![String::new()]);
1493 }
1494 }
1495 }
1496 }
1497
1498 AssembledGroupInstance {
1499 segments,
1500 child_groups: vec![],
1501 skipped_segments: Vec::new(),
1502 }
1503 }
1504
1505 fn resolve_field_path(segment: &AssembledSegment, path: &[&str]) -> Option<String> {
1518 if path.is_empty() {
1519 return None;
1520 }
1521
1522 if let Ok(element_idx) = path[0].parse::<usize>() {
1524 let component_idx = if path.len() > 1 {
1525 path[1].parse::<usize>().unwrap_or(0)
1526 } else {
1527 0
1528 };
1529 return segment
1530 .elements
1531 .get(element_idx)?
1532 .get(component_idx)
1533 .filter(|v| !v.is_empty())
1534 .cloned();
1535 }
1536
1537 match path.len() {
1539 1 => segment
1540 .elements
1541 .first()?
1542 .first()
1543 .filter(|v| !v.is_empty())
1544 .cloned(),
1545 2 => segment
1546 .elements
1547 .get(1)?
1548 .first()
1549 .filter(|v| !v.is_empty())
1550 .cloned(),
1551 _ => None,
1552 }
1553 }
1554
1555 fn parse_element_component(parts: &[&str]) -> (usize, usize) {
1558 if parts.is_empty() {
1559 return (0, 0);
1560 }
1561 let element_idx = parts[0].parse::<usize>().unwrap_or(0);
1562 let component_idx = if parts.len() > 1 {
1563 parts[1].parse::<usize>().unwrap_or(0)
1564 } else {
1565 0
1566 };
1567 (element_idx, component_idx)
1568 }
1569
1570 pub fn populate_field(
1573 &self,
1574 bo4e_value: &serde_json::Value,
1575 target_field: &str,
1576 ) -> Option<String> {
1577 let mut current = bo4e_value;
1578 for part in target_field.split('.') {
1579 current = current.get(part)?;
1580 }
1581 if let Some(code) = current.get("code").and_then(|v| v.as_str()) {
1583 return Some(code.to_string());
1584 }
1585 current.as_str().map(|s| s.to_string())
1586 }
1587
1588 fn populate_field_json<'a>(
1591 &self,
1592 bo4e_value: &'a serde_json::Value,
1593 target_field: &str,
1594 ) -> Option<&'a serde_json::Value> {
1595 let mut current = bo4e_value;
1596 for part in target_field.split('.') {
1597 current = current.get(part)?;
1598 }
1599 Some(current)
1600 }
1601
1602 pub fn build_segment_from_bo4e(
1604 &self,
1605 bo4e_value: &serde_json::Value,
1606 segment_tag: &str,
1607 target_field: &str,
1608 ) -> AssembledSegment {
1609 let value = self.populate_field(bo4e_value, target_field);
1610 let elements = if let Some(val) = value {
1611 vec![vec![val]]
1612 } else {
1613 vec![]
1614 };
1615 AssembledSegment {
1616 tag: segment_tag.to_uppercase(),
1617 elements,
1618 }
1619 }
1620
1621 pub fn resolve_repetition(
1630 tree: &AssembledTree,
1631 group_path: &str,
1632 discriminator: &str,
1633 ) -> Option<usize> {
1634 let (spec, expected) = discriminator.split_once('=')?;
1635 let parts: Vec<&str> = spec.split('.').collect();
1636 if parts.len() != 3 {
1637 return None;
1638 }
1639 let tag = parts[0];
1640 let element_idx: usize = parts[1].parse().ok()?;
1641 let component_idx: usize = parts[2].parse().ok()?;
1642
1643 let path_parts: Vec<&str> = group_path.split('.').collect();
1645
1646 let leaf_group = if path_parts.len() == 1 {
1647 let (group_id, _) = parse_group_spec(path_parts[0]);
1648 tree.groups.iter().find(|g| g.group_id == group_id)?
1649 } else {
1650 let parent_parts = &path_parts[..path_parts.len() - 1];
1652 let mut current_instance = {
1653 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1654 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
1655 first_group.repetitions.get(first_rep.unwrap_or(0))?
1656 };
1657 for part in &parent_parts[1..] {
1658 let (group_id, explicit_rep) = parse_group_spec(part);
1659 let child_group = current_instance
1660 .child_groups
1661 .iter()
1662 .find(|g| g.group_id == group_id)?;
1663 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
1664 }
1665 let (leaf_id, _) = parse_group_spec(path_parts.last()?);
1666 current_instance
1667 .child_groups
1668 .iter()
1669 .find(|g| g.group_id == leaf_id)?
1670 };
1671
1672 let expected_values: Vec<&str> = expected.split('|').collect();
1674 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1675 let matches = instance.segments.iter().any(|s| {
1676 s.tag.eq_ignore_ascii_case(tag)
1677 && s.elements
1678 .get(element_idx)
1679 .and_then(|e| e.get(component_idx))
1680 .map(|v| expected_values.iter().any(|ev| v == ev))
1681 .unwrap_or(false)
1682 });
1683 if matches {
1684 return Some(rep_idx);
1685 }
1686 }
1687
1688 None
1689 }
1690
1691 pub fn resolve_all_repetitions(
1696 tree: &AssembledTree,
1697 group_path: &str,
1698 discriminator: &str,
1699 ) -> Vec<usize> {
1700 let Some((spec, expected)) = discriminator.split_once('=') else {
1701 return Vec::new();
1702 };
1703 let parts: Vec<&str> = spec.split('.').collect();
1704 if parts.len() != 3 {
1705 return Vec::new();
1706 }
1707 let tag = parts[0];
1708 let element_idx: usize = match parts[1].parse() {
1709 Ok(v) => v,
1710 Err(_) => return Vec::new(),
1711 };
1712 let component_idx: usize = match parts[2].parse() {
1713 Ok(v) => v,
1714 Err(_) => return Vec::new(),
1715 };
1716
1717 let path_parts: Vec<&str> = group_path.split('.').collect();
1719
1720 let leaf_group = if path_parts.len() == 1 {
1721 let (group_id, _) = parse_group_spec(path_parts[0]);
1722 match tree.groups.iter().find(|g| g.group_id == group_id) {
1723 Some(g) => g,
1724 None => return Vec::new(),
1725 }
1726 } else {
1727 let parent_parts = &path_parts[..path_parts.len() - 1];
1728 let mut current_instance = {
1729 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1730 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
1731 Some(g) => g,
1732 None => return Vec::new(),
1733 };
1734 match first_group.repetitions.get(first_rep.unwrap_or(0)) {
1735 Some(i) => i,
1736 None => return Vec::new(),
1737 }
1738 };
1739 for part in &parent_parts[1..] {
1740 let (group_id, explicit_rep) = parse_group_spec(part);
1741 let child_group = match current_instance
1742 .child_groups
1743 .iter()
1744 .find(|g| g.group_id == group_id)
1745 {
1746 Some(g) => g,
1747 None => return Vec::new(),
1748 };
1749 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
1750 Some(i) => i,
1751 None => return Vec::new(),
1752 };
1753 }
1754 let (leaf_id, _) = match path_parts.last() {
1755 Some(p) => parse_group_spec(p),
1756 None => return Vec::new(),
1757 };
1758 match current_instance
1759 .child_groups
1760 .iter()
1761 .find(|g| g.group_id == leaf_id)
1762 {
1763 Some(g) => g,
1764 None => return Vec::new(),
1765 }
1766 };
1767
1768 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
1770
1771 let expected_values: Vec<&str> = expected_raw.split('|').collect();
1773 let mut result = Vec::new();
1774 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1775 let matches = instance.segments.iter().any(|s| {
1776 s.tag.eq_ignore_ascii_case(tag)
1777 && s.elements
1778 .get(element_idx)
1779 .and_then(|e| e.get(component_idx))
1780 .map(|v| expected_values.iter().any(|ev| v == ev))
1781 .unwrap_or(false)
1782 });
1783 if matches {
1784 result.push(rep_idx);
1785 }
1786 }
1787
1788 if let Some(occ) = occurrence {
1790 result.into_iter().nth(occ).into_iter().collect()
1791 } else {
1792 result
1793 }
1794 }
1795
1796 pub fn map_all_forward(&self, tree: &AssembledTree) -> serde_json::Value {
1818 self.map_all_forward_inner(tree, true).0
1819 }
1820
1821 pub fn map_all_forward_enriched(
1825 &self,
1826 tree: &AssembledTree,
1827 enrich_codes: bool,
1828 ) -> serde_json::Value {
1829 self.map_all_forward_inner(tree, enrich_codes).0
1830 }
1831
1832 fn map_all_forward_inner(
1839 &self,
1840 tree: &AssembledTree,
1841 enrich_codes: bool,
1842 ) -> (
1843 serde_json::Value,
1844 std::collections::HashMap<String, Vec<usize>>,
1845 ) {
1846 let mut result = serde_json::Map::new();
1847 let mut nesting_info: std::collections::HashMap<String, Vec<usize>> =
1848 std::collections::HashMap::new();
1849
1850 for def in &self.definitions {
1851 let entity = &def.meta.entity;
1852
1853 let bo4e = if let Some(ref disc) = def.meta.discriminator {
1854 let use_source_path = def
1859 .meta
1860 .source_path
1861 .as_ref()
1862 .is_some_and(|sp| has_source_path_qualifiers(sp));
1863 if use_source_path {
1864 let sp = def.meta.source_path.as_deref().unwrap();
1866 let all_instances = Self::resolve_all_by_source_path(tree, sp);
1867 let instances: Vec<_> = if let Some(matcher) = DiscriminatorMatcher::parse(disc)
1869 {
1870 matcher.filter_instances(all_instances)
1871 } else {
1872 all_instances
1873 };
1874 let extract = |instance: &AssembledGroupInstance| {
1875 let mut r = serde_json::Map::new();
1876 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
1877 self.extract_companion_fields(instance, def, &mut r, enrich_codes);
1878 serde_json::Value::Object(r)
1879 };
1880 match instances.len() {
1881 0 => None,
1882 1 => Some(extract(instances[0])),
1883 _ => Some(serde_json::Value::Array(
1884 instances.iter().map(|i| extract(i)).collect(),
1885 )),
1886 }
1887 } else {
1888 let reps = Self::resolve_all_repetitions(tree, &def.meta.source_group, disc);
1889 match reps.len() {
1890 0 => None,
1891 1 => Some(self.map_forward_inner(tree, def, reps[0], enrich_codes)),
1892 _ => Some(serde_json::Value::Array(
1893 reps.iter()
1894 .map(|&rep| self.map_forward_inner(tree, def, rep, enrich_codes))
1895 .collect(),
1896 )),
1897 }
1898 }
1899 } else if def.meta.source_group.is_empty() {
1900 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
1902 } else if def.meta.source_path.as_ref().is_some_and(|sp| {
1903 has_source_path_qualifiers(sp) || def.meta.source_group.contains('.')
1904 }) {
1905 let sp = def.meta.source_path.as_deref().unwrap();
1910 let mut indexed = Self::resolve_all_with_parent_indices(tree, sp);
1911
1912 if let Some(last_part) = sp.rsplit('.').next() {
1917 if !last_part.contains('_') {
1918 let base_prefix = if let Some(parent) = sp.rsplit_once('.') {
1922 format!("{}.", parent.0)
1923 } else {
1924 String::new()
1925 };
1926 let sibling_qualifiers: Vec<String> = self
1927 .definitions
1928 .iter()
1929 .filter_map(|d| d.meta.source_path.as_deref())
1930 .filter(|other_sp| {
1931 *other_sp != sp
1932 && other_sp.starts_with(&base_prefix)
1933 && other_sp.split('.').count() == sp.split('.').count()
1934 })
1935 .filter_map(|other_sp| {
1936 let other_last = other_sp.rsplit('.').next()?;
1937 let (base, q) = other_last.split_once('_')?;
1940 if base == last_part {
1941 Some(q.to_string())
1942 } else {
1943 None
1944 }
1945 })
1946 .collect();
1947
1948 if !sibling_qualifiers.is_empty() {
1949 indexed.retain(|(_, inst)| {
1950 let entry_qual = inst
1951 .segments
1952 .first()
1953 .and_then(|seg| seg.elements.first())
1954 .and_then(|el| el.first())
1955 .map(|v| v.to_lowercase());
1956 !entry_qual.is_some_and(|q| {
1959 sibling_qualifiers.iter().any(|sq| {
1960 sq.split('_').any(|part| part.eq_ignore_ascii_case(&q))
1961 })
1962 })
1963 });
1964 }
1965 }
1966 }
1967 let extract = |instance: &AssembledGroupInstance| {
1968 let mut r = serde_json::Map::new();
1969 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
1970 self.extract_companion_fields(instance, def, &mut r, enrich_codes);
1971 serde_json::Value::Object(r)
1972 };
1973 if def.meta.source_group.contains('.') && !indexed.is_empty() {
1978 if let Some(sp) = &def.meta.source_path {
1979 let parent_indices: Vec<usize> =
1980 indexed.iter().map(|(idx, _)| *idx).collect();
1981 nesting_info.entry(sp.clone()).or_insert(parent_indices);
1982
1983 let child_key = format!("{sp}#child");
1986 if let std::collections::hash_map::Entry::Vacant(e) =
1987 nesting_info.entry(child_key)
1988 {
1989 let child_indices: Vec<usize> =
1990 Self::compute_child_indices(tree, sp, &indexed);
1991 if !child_indices.is_empty() {
1992 e.insert(child_indices);
1993 }
1994 }
1995 }
1996 }
1997 match indexed.len() {
1998 0 => None,
1999 1 => Some(extract(indexed[0].1)),
2000 _ => Some(serde_json::Value::Array(
2001 indexed.iter().map(|(_, i)| extract(i)).collect(),
2002 )),
2003 }
2004 } else {
2005 let num_reps = Self::count_repetitions(tree, &def.meta.source_group);
2006 if num_reps <= 1 {
2007 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2008 } else {
2009 let mut items = Vec::with_capacity(num_reps);
2011 for rep in 0..num_reps {
2012 items.push(self.map_forward_inner(tree, def, rep, enrich_codes));
2013 }
2014 Some(serde_json::Value::Array(items))
2015 }
2016 };
2017
2018 if let Some(bo4e) = bo4e {
2019 let bo4e = inject_bo4e_metadata(bo4e, &def.meta.bo4e_type);
2020 let key = to_camel_case(entity);
2021 deep_merge_insert(&mut result, &key, bo4e);
2022 }
2023 }
2024
2025 nest_child_entities_in_result(&mut result, &self.definitions, &nesting_info);
2028
2029 (serde_json::Value::Object(result), nesting_info)
2030 }
2031
2032 pub fn map_all_reverse(
2041 &self,
2042 entities: &serde_json::Value,
2043 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2044 ) -> AssembledTree {
2045 let mut root_segments: Vec<AssembledSegment> = Vec::new();
2046 let mut groups: Vec<AssembledGroup> = Vec::new();
2047
2048 for def in &self.definitions {
2049 let entity_key = to_camel_case(&def.meta.entity);
2050
2051 let _extracted: Option<serde_json::Value>;
2054 let entity_value = if let Some(v) = entities.get(&entity_key) {
2055 _extracted = None;
2056 v
2057 } else if def.meta.source_group.contains('.') {
2058 match extract_child_from_parent(entities, &self.definitions, def) {
2060 Some(v) => {
2061 _extracted = Some(v);
2062 _extracted.as_ref().unwrap()
2063 }
2064 None => continue,
2065 }
2066 } else {
2067 continue;
2068 };
2069
2070 let unwrapped: Option<serde_json::Value>;
2078 let entity_value = if entity_value.is_object() && !entity_value.is_array() {
2079 if let Some(disc_value) = def
2080 .meta
2081 .discriminator
2082 .as_deref()
2083 .and_then(|d| d.split_once('='))
2084 .map(|(_, v)| v)
2085 {
2086 if let Some(inner) = entity_value.get(disc_value) {
2088 let mut injected = inner.clone();
2089 if let Some(ref cf) = def.companion_fields {
2092 let disc_path = def
2093 .meta
2094 .discriminator
2095 .as_deref()
2096 .unwrap()
2097 .split_once('=')
2098 .unwrap()
2099 .0
2100 .to_lowercase();
2101 for (path, mapping) in cf {
2102 let cf_path = path.to_lowercase();
2106 let matches = cf_path == disc_path
2107 || format!("{}.0", cf_path) == disc_path;
2108 if matches {
2109 let target = match mapping {
2110 FieldMapping::Simple(t) => t.as_str(),
2111 FieldMapping::Structured(s) => s.target.as_str(),
2112 FieldMapping::Nested(_) => continue,
2113 };
2114 if !target.is_empty() {
2115 if let Some(obj) = injected.as_object_mut() {
2116 let entry = obj.entry(target.to_string())
2117 .or_insert(serde_json::Value::Null);
2118 if entry.is_null() {
2119 *entry = serde_json::Value::String(
2120 disc_value.to_string(),
2121 );
2122 }
2123 }
2124 }
2125 break;
2126 }
2127 }
2128 }
2129 unwrapped = Some(injected);
2130 unwrapped.as_ref().unwrap()
2131 } else {
2132 entity_value
2133 }
2134 } else if is_map_keyed_object(entity_value) {
2135 let map = entity_value.as_object().unwrap();
2140 let arr: Vec<serde_json::Value> = map
2141 .iter()
2142 .map(|(key, val)| {
2143 let mut item = val.clone();
2144 if let Some(obj) = item.as_object_mut() {
2147 if let Some(qualifier_field) =
2148 find_qualifier_companion_field(&self.definitions, &def.meta.entity)
2149 {
2150 let entry = obj.entry(qualifier_field).or_insert(serde_json::Value::Null);
2151 if entry.is_null() {
2152 *entry = serde_json::Value::String(key.clone());
2153 }
2154 }
2155 }
2156 item
2157 })
2158 .collect();
2159 unwrapped = Some(serde_json::Value::Array(arr));
2160 unwrapped.as_ref().unwrap()
2161 } else {
2162 entity_value
2163 }
2164 } else {
2165 entity_value
2166 };
2167
2168 let leaf_group = def
2170 .meta
2171 .source_group
2172 .rsplit('.')
2173 .next()
2174 .unwrap_or(&def.meta.source_group);
2175
2176 if def.meta.source_group.is_empty() {
2177 let instance = self.map_reverse(entity_value, def);
2179 root_segments.extend(instance.segments);
2180 } else if entity_value.is_array() {
2181 let arr = entity_value.as_array().unwrap();
2183 let reps: Vec<_> = arr.iter().map(|item| self.map_reverse(item, def)).collect();
2184
2185 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2187 existing.repetitions.extend(reps);
2188 } else {
2189 groups.push(AssembledGroup {
2190 group_id: leaf_group.to_string(),
2191 repetitions: reps,
2192 });
2193 }
2194 } else {
2195 let instance = self.map_reverse(entity_value, def);
2197
2198 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2199 existing.repetitions.push(instance);
2200 } else {
2201 groups.push(AssembledGroup {
2202 group_id: leaf_group.to_string(),
2203 repetitions: vec![instance],
2204 });
2205 }
2206 }
2207 }
2208
2209 let nested_specs: Vec<(String, String)> = self
2215 .definitions
2216 .iter()
2217 .filter_map(|def| {
2218 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
2219 if parts.len() > 1 {
2220 Some((parts[0].to_string(), parts[parts.len() - 1].to_string()))
2221 } else {
2222 None
2223 }
2224 })
2225 .collect();
2226 for (parent_id, child_id) in &nested_specs {
2227 let has_parent = groups.iter().any(|g| g.group_id == *parent_id);
2229 let has_child = groups.iter().any(|g| g.group_id == *child_id);
2230 if has_parent && has_child {
2231 let child_idx = groups.iter().position(|g| g.group_id == *child_id).unwrap();
2232 let child_group = groups.remove(child_idx);
2233 let parent = groups
2234 .iter_mut()
2235 .find(|g| g.group_id == *parent_id)
2236 .unwrap();
2237 let child_source_path = self
2241 .definitions
2242 .iter()
2243 .find(|d| {
2244 let parts: Vec<&str> = d.meta.source_group.split('.').collect();
2245 parts.len() > 1 && parts[parts.len() - 1] == *child_id
2246 })
2247 .and_then(|d| d.meta.source_path.as_deref());
2248 let distribution =
2249 child_source_path.and_then(|key| nesting_info.and_then(|ni| ni.get(key)));
2250 for (i, child_rep) in child_group.repetitions.into_iter().enumerate() {
2251 let target_idx = distribution
2252 .and_then(|dist| dist.get(i))
2253 .copied()
2254 .unwrap_or(0);
2255
2256 if let Some(target_rep) = parent.repetitions.get_mut(target_idx) {
2257 if let Some(existing) = target_rep
2258 .child_groups
2259 .iter_mut()
2260 .find(|g| g.group_id == *child_id)
2261 {
2262 existing.repetitions.push(child_rep);
2263 } else {
2264 target_rep.child_groups.push(AssembledGroup {
2265 group_id: child_id.clone(),
2266 repetitions: vec![child_rep],
2267 });
2268 }
2269 }
2270 }
2271 }
2272 }
2273
2274 let post_group_start = root_segments.len();
2275 AssembledTree {
2276 segments: root_segments,
2277 groups,
2278 post_group_start,
2279 inter_group_segments: std::collections::BTreeMap::new(),
2280 }
2281 }
2282
2283 fn count_repetitions(tree: &AssembledTree, group_path: &str) -> usize {
2285 let parts: Vec<&str> = group_path.split('.').collect();
2286
2287 let (first_id, first_rep) = parse_group_spec(parts[0]);
2288 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2289 Some(g) => g,
2290 None => return 0,
2291 };
2292
2293 if parts.len() == 1 {
2294 return first_group.repetitions.len();
2295 }
2296
2297 let mut current_instance = match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2299 Some(i) => i,
2300 None => return 0,
2301 };
2302
2303 for (i, part) in parts[1..].iter().enumerate() {
2304 let (group_id, explicit_rep) = parse_group_spec(part);
2305 let child_group = match current_instance
2306 .child_groups
2307 .iter()
2308 .find(|g| g.group_id == group_id)
2309 {
2310 Some(g) => g,
2311 None => return 0,
2312 };
2313
2314 if i == parts.len() - 2 {
2315 return child_group.repetitions.len();
2317 }
2318 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2319 Some(i) => i,
2320 None => return 0,
2321 };
2322 }
2323
2324 0
2325 }
2326
2327 pub fn map_interchange(
2336 msg_engine: &MappingEngine,
2337 tx_engine: &MappingEngine,
2338 tree: &AssembledTree,
2339 transaction_group: &str,
2340 enrich_codes: bool,
2341 ) -> crate::model::MappedMessage {
2342 let (stammdaten, nesting_info) = msg_engine.map_all_forward_inner(tree, enrich_codes);
2344
2345 let transaktionen = tree
2347 .groups
2348 .iter()
2349 .find(|g| g.group_id == transaction_group)
2350 .map(|sg| {
2351 sg.repetitions
2352 .iter()
2353 .map(|instance| {
2354 let wrapped_tree = AssembledTree {
2357 segments: vec![],
2358 groups: vec![AssembledGroup {
2359 group_id: transaction_group.to_string(),
2360 repetitions: vec![instance.clone()],
2361 }],
2362 post_group_start: 0,
2363 inter_group_segments: std::collections::BTreeMap::new(),
2364 };
2365
2366 let (tx_result, tx_nesting) =
2367 tx_engine.map_all_forward_inner(&wrapped_tree, enrich_codes);
2368
2369 crate::model::MappedTransaktion {
2370 stammdaten: tx_result,
2371 nesting_info: tx_nesting,
2372 }
2373 })
2374 .collect()
2375 })
2376 .unwrap_or_default();
2377
2378 crate::model::MappedMessage {
2379 stammdaten,
2380 transaktionen,
2381 nesting_info,
2382 }
2383 }
2384
2385 pub fn map_interchange_reverse(
2395 msg_engine: &MappingEngine,
2396 tx_engine: &MappingEngine,
2397 mapped: &crate::model::MappedMessage,
2398 transaction_group: &str,
2399 filtered_mig: Option<&MigSchema>,
2400 ) -> AssembledTree {
2401 let msg_tree = msg_engine.map_all_reverse(
2403 &mapped.stammdaten,
2404 if mapped.nesting_info.is_empty() {
2405 None
2406 } else {
2407 Some(&mapped.nesting_info)
2408 },
2409 );
2410
2411 let mut sg4_reps: Vec<AssembledGroupInstance> = Vec::new();
2413
2414 struct DefWithMeta<'a> {
2418 def: &'a MappingDefinition,
2419 relative: String,
2420 depth: usize,
2421 }
2422
2423 let mut sorted_defs: Vec<DefWithMeta> = tx_engine
2424 .definitions
2425 .iter()
2426 .map(|def| {
2427 let relative = strip_tx_group_prefix(&def.meta.source_group, transaction_group);
2428 let depth = if relative.is_empty() {
2429 0
2430 } else {
2431 relative.chars().filter(|c| *c == '.').count() + 1
2432 };
2433 DefWithMeta {
2434 def,
2435 relative,
2436 depth,
2437 }
2438 })
2439 .collect();
2440
2441 let mut parent_rep_map: std::collections::HashMap<String, usize> =
2445 std::collections::HashMap::new();
2446 for dm in &sorted_defs {
2447 if dm.depth >= 2 {
2448 let parts: Vec<&str> = dm.relative.split('.').collect();
2449 let (_, parent_rep) = parse_group_spec(parts[0]);
2450 if let Some(rep_idx) = parent_rep {
2451 if let Some(sp) = &dm.def.meta.source_path {
2452 if let Some((parent_path, _)) = sp.rsplit_once('.') {
2453 parent_rep_map
2454 .entry(parent_path.to_string())
2455 .or_insert(rep_idx);
2456 }
2457 }
2458 }
2459 }
2460 }
2461
2462 for dm in &mut sorted_defs {
2465 if dm.depth == 1 && !dm.relative.contains(':') {
2466 if let Some(sp) = &dm.def.meta.source_path {
2467 if let Some(rep_idx) = parent_rep_map.get(sp.as_str()) {
2468 dm.relative = format!("{}:{}", dm.relative, rep_idx);
2469 }
2470 }
2471 }
2472 }
2473
2474 if let Some(mig) = filtered_mig {
2481 let mig_order = build_reverse_mig_group_order(mig, transaction_group);
2482 sorted_defs.sort_by(|a, b| {
2483 a.depth.cmp(&b.depth).then_with(|| {
2484 let a_id = a.relative.split(':').next().unwrap_or(&a.relative);
2485 let b_id = b.relative.split(':').next().unwrap_or(&b.relative);
2486 let a_pos = variant_mig_position(a.def, a_id, &mig_order);
2488 let b_pos = variant_mig_position(b.def, b_id, &mig_order);
2489 a_pos.cmp(&b_pos).then(a.relative.cmp(&b.relative))
2490 })
2491 });
2492 } else {
2493 sorted_defs.sort_by(|a, b| a.depth.cmp(&b.depth).then(a.relative.cmp(&b.relative)));
2494 }
2495
2496 for tx in &mapped.transaktionen {
2497 let mut root_segs: Vec<AssembledSegment> = Vec::new();
2498 let mut child_groups: Vec<AssembledGroup> = Vec::new();
2499
2500 let mut source_path_to_rep: std::collections::HashMap<String, Vec<usize>> =
2505 std::collections::HashMap::new();
2506
2507 for dm in &sorted_defs {
2508 let entity_key = to_camel_case(&dm.def.meta.entity);
2511 let _tx_extracted: Option<serde_json::Value>;
2512 let bo4e_value = if let Some(v) = tx.stammdaten.get(&entity_key) {
2513 _tx_extracted = None;
2514 v
2515 } else if dm.def.meta.source_group.contains('.') {
2516 match extract_child_from_parent(
2517 &tx.stammdaten,
2518 &tx_engine.definitions,
2519 dm.def,
2520 ) {
2521 Some(v) => {
2522 _tx_extracted = Some(v);
2523 _tx_extracted.as_ref().unwrap()
2524 }
2525 None => continue,
2526 }
2527 } else {
2528 continue;
2529 };
2530
2531 let unwrapped_value: Option<serde_json::Value>;
2533 let bo4e_value = if bo4e_value.is_object() && !bo4e_value.is_array() {
2534 if let Some(disc_value) = dm
2535 .def
2536 .meta
2537 .discriminator
2538 .as_deref()
2539 .and_then(|d| d.split_once('='))
2540 .map(|(_, v)| v)
2541 {
2542 if let Some(inner) = bo4e_value.get(disc_value) {
2543 let mut injected = inner.clone();
2544 if let Some(ref cf) = dm.def.companion_fields {
2545 let disc_path = dm
2546 .def
2547 .meta
2548 .discriminator
2549 .as_deref()
2550 .unwrap()
2551 .split_once('=')
2552 .unwrap()
2553 .0
2554 .to_lowercase();
2555 for (path, mapping) in cf {
2556 let cf_path = path.to_lowercase();
2557 let matches = cf_path == disc_path
2558 || format!("{}.0", cf_path) == disc_path;
2559 if matches {
2560 let target = match mapping {
2561 FieldMapping::Simple(t) => t.as_str(),
2562 FieldMapping::Structured(s) => s.target.as_str(),
2563 FieldMapping::Nested(_) => continue,
2564 };
2565 if !target.is_empty() {
2566 if let Some(obj) = injected.as_object_mut() {
2567 obj.entry(target.to_string()).or_insert_with(
2568 || {
2569 serde_json::Value::String(
2570 disc_value.to_string(),
2571 )
2572 },
2573 );
2574 }
2575 }
2576 break;
2577 }
2578 }
2579 }
2580 unwrapped_value = Some(injected);
2581 unwrapped_value.as_ref().unwrap()
2582 } else {
2583 bo4e_value
2584 }
2585 } else if is_map_keyed_object(bo4e_value) {
2586 let map = bo4e_value.as_object().unwrap();
2587 let arr: Vec<serde_json::Value> = map
2588 .iter()
2589 .map(|(key, val)| {
2590 let mut item = val.clone();
2591 if let Some(obj) = item.as_object_mut() {
2592 if let Some(qualifier_field) =
2593 find_qualifier_companion_field(
2594 &tx_engine.definitions,
2595 &dm.def.meta.entity,
2596 )
2597 {
2598 let entry = obj.entry(qualifier_field).or_insert(serde_json::Value::Null);
2599 if entry.is_null() {
2600 *entry = serde_json::Value::String(key.clone());
2601 }
2602 }
2603 }
2604 item
2605 })
2606 .collect();
2607 unwrapped_value = Some(serde_json::Value::Array(arr));
2608 unwrapped_value.as_ref().unwrap()
2609 } else {
2610 bo4e_value
2611 }
2612 } else {
2613 bo4e_value
2614 };
2615
2616 let items: Vec<&serde_json::Value> = if bo4e_value.is_array() {
2620 bo4e_value.as_array().unwrap().iter().collect()
2621 } else {
2622 vec![bo4e_value]
2623 };
2624
2625 for (item_idx, item) in items.iter().enumerate() {
2626 let instance = tx_engine.map_reverse(item, dm.def);
2627
2628 if instance.segments.is_empty() && instance.child_groups.is_empty() {
2630 continue;
2631 }
2632
2633 if dm.relative.is_empty() {
2634 root_segs.extend(instance.segments);
2635 } else {
2636 let effective_relative = if dm.depth >= 2 {
2640 let rel = if items.len() > 1 {
2643 strip_all_rep_indices(&dm.relative)
2644 } else {
2645 dm.relative.clone()
2646 };
2647 let skip_nesting = dm
2654 .def
2655 .meta
2656 .source_path
2657 .as_ref()
2658 .and_then(|sp| sp.rsplit_once('.'))
2659 .and_then(|(parent_path, _)| {
2660 source_path_to_rep.get(parent_path)
2661 })
2662 .is_some_and(|reps| reps.len() == 1);
2663 let nesting_idx = if items.len() > 1 && !skip_nesting {
2664 dm.def
2665 .meta
2666 .source_path
2667 .as_ref()
2668 .and_then(|sp| tx.nesting_info.get(sp))
2669 .and_then(|dist| dist.get(item_idx))
2670 .copied()
2671 } else {
2672 None
2673 };
2674 if let Some(parent_rep) = nesting_idx {
2675 let parts: Vec<&str> = rel.split('.').collect();
2677 let parent_id = parts[0].split(':').next().unwrap_or(parts[0]);
2678 let rest = parts[1..].join(".");
2679 format!("{}:{}.{}", parent_id, parent_rep, rest)
2680 } else {
2681 resolve_child_relative(
2682 &rel,
2683 dm.def.meta.source_path.as_deref(),
2684 &source_path_to_rep,
2685 item_idx,
2686 )
2687 }
2688 } else if dm.depth == 1 {
2689 let child_key = dm
2692 .def
2693 .meta
2694 .source_path
2695 .as_ref()
2696 .map(|sp| format!("{sp}#child"));
2697 if let Some(child_indices) =
2698 child_key.as_ref().and_then(|ck| tx.nesting_info.get(ck))
2699 {
2700 if let Some(&target) = child_indices.get(item_idx) {
2701 if target != usize::MAX {
2702 let base =
2703 dm.relative.split(':').next().unwrap_or(&dm.relative);
2704 format!("{}:{}", base, target)
2705 } else {
2706 dm.relative.clone()
2707 }
2708 } else if items.len() > 1 && item_idx > 0 {
2709 strip_rep_index(&dm.relative)
2710 } else {
2711 dm.relative.clone()
2712 }
2713 } else if items.len() > 1 && item_idx > 0 {
2714 strip_rep_index(&dm.relative)
2715 } else {
2716 dm.relative.clone()
2717 }
2718 } else if items.len() > 1 && item_idx > 0 {
2719 strip_rep_index(&dm.relative)
2722 } else {
2723 dm.relative.clone()
2724 };
2725
2726 let rep_used =
2727 place_in_groups(&mut child_groups, &effective_relative, instance);
2728
2729 if dm.depth == 1 {
2731 if let Some(sp) = &dm.def.meta.source_path {
2732 source_path_to_rep
2733 .entry(sp.clone())
2734 .or_default()
2735 .push(rep_used);
2736 }
2737 }
2738 }
2739 }
2740 }
2741
2742 if let Some(mig) = filtered_mig {
2747 sort_variant_reps_by_mig(&mut child_groups, mig, transaction_group);
2748 }
2749
2750 sg4_reps.push(AssembledGroupInstance {
2751 segments: root_segs,
2752 child_groups,
2753 skipped_segments: Vec::new(),
2754 });
2755 }
2756
2757 let mut root_segments = Vec::new();
2764 let mut uns_segments = Vec::new();
2765 let mut uns_is_summary = false;
2766 let mut found_uns = false;
2767 for seg in msg_tree.segments {
2768 if seg.tag == "UNS" {
2769 uns_is_summary = seg
2771 .elements
2772 .first()
2773 .and_then(|el| el.first())
2774 .map(|v| v == "S")
2775 .unwrap_or(false);
2776 uns_segments.push(seg);
2777 found_uns = true;
2778 } else if found_uns {
2779 uns_segments.push(seg);
2781 } else {
2782 root_segments.push(seg);
2783 }
2784 }
2785
2786 let pre_group_count = root_segments.len();
2787 let mut all_groups = msg_tree.groups;
2788 let mut inter_group = msg_tree.inter_group_segments;
2789
2790 let sg_num = |id: &str| -> usize {
2792 id.strip_prefix("SG")
2793 .and_then(|n| n.parse::<usize>().ok())
2794 .unwrap_or(0)
2795 };
2796
2797 if !sg4_reps.is_empty() {
2798 if uns_is_summary {
2799 all_groups.push(AssembledGroup {
2801 group_id: transaction_group.to_string(),
2802 repetitions: sg4_reps,
2803 });
2804 if !uns_segments.is_empty() {
2805 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2810 let tx_num = sg_num(transaction_group);
2811 let uns_pos = all_groups
2812 .iter()
2813 .rposition(|g| sg_num(&g.group_id) <= tx_num)
2814 .map(|i| i + 1)
2815 .unwrap_or(all_groups.len());
2816 inter_group.insert(uns_pos, uns_segments);
2817 }
2818 } else {
2819 if !uns_segments.is_empty() {
2821 inter_group.insert(all_groups.len(), uns_segments);
2822 }
2823 all_groups.push(AssembledGroup {
2824 group_id: transaction_group.to_string(),
2825 repetitions: sg4_reps,
2826 });
2827 }
2828 } else if !uns_segments.is_empty() {
2829 if transaction_group.is_empty() {
2830 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2835 if uns_is_summary {
2836 inter_group.insert(all_groups.len(), uns_segments);
2837 } else {
2838 inter_group.insert(0, uns_segments);
2839 }
2840 } else {
2841 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 }
2854
2855 AssembledTree {
2856 segments: root_segments,
2857 groups: all_groups,
2858 post_group_start: pre_group_count,
2859 inter_group_segments: inter_group,
2860 }
2861 }
2862
2863 pub fn build_group_from_bo4e(
2865 &self,
2866 bo4e_value: &serde_json::Value,
2867 def: &MappingDefinition,
2868 ) -> AssembledGroup {
2869 let instance = self.map_reverse(bo4e_value, def);
2870 let leaf_group = def
2871 .meta
2872 .source_group
2873 .rsplit('.')
2874 .next()
2875 .unwrap_or(&def.meta.source_group);
2876
2877 AssembledGroup {
2878 group_id: leaf_group.to_string(),
2879 repetitions: vec![instance],
2880 }
2881 }
2882
2883 pub fn map_interchange_typed<M, T>(
2891 msg_engine: &MappingEngine,
2892 tx_engine: &MappingEngine,
2893 tree: &AssembledTree,
2894 tx_group: &str,
2895 enrich_codes: bool,
2896 nachrichtendaten: crate::model::Nachrichtendaten,
2897 interchangedaten: crate::model::Interchangedaten,
2898 ) -> Result<crate::model::Interchange<M, T>, serde_json::Error>
2899 where
2900 M: serde::de::DeserializeOwned,
2901 T: serde::de::DeserializeOwned,
2902 {
2903 let mapped = Self::map_interchange(msg_engine, tx_engine, tree, tx_group, enrich_codes);
2904 let nachricht = mapped.into_dynamic_nachricht(nachrichtendaten);
2905 let dynamic = crate::model::DynamicInterchange {
2906 interchangedaten,
2907 nachrichten: vec![nachricht],
2908 };
2909 let value = serde_json::to_value(&dynamic)?;
2910 serde_json::from_value(value)
2911 }
2912
2913 pub fn map_interchange_reverse_typed<M, T>(
2920 msg_engine: &MappingEngine,
2921 tx_engine: &MappingEngine,
2922 nachricht: &crate::model::Nachricht<M, T>,
2923 tx_group: &str,
2924 ) -> Result<AssembledTree, serde_json::Error>
2925 where
2926 M: serde::Serialize,
2927 T: serde::Serialize,
2928 {
2929 let stammdaten = serde_json::to_value(&nachricht.stammdaten)?;
2930 let transaktionen: Vec<crate::model::MappedTransaktion> = nachricht
2931 .transaktionen
2932 .iter()
2933 .map(|t| {
2934 Ok(crate::model::MappedTransaktion {
2935 stammdaten: serde_json::to_value(t)?,
2936 nesting_info: Default::default(),
2937 })
2938 })
2939 .collect::<Result<Vec<_>, serde_json::Error>>()?;
2940 let mapped = crate::model::MappedMessage {
2941 stammdaten,
2942 transaktionen,
2943 nesting_info: Default::default(),
2944 };
2945 Ok(Self::map_interchange_reverse(
2946 msg_engine, tx_engine, &mapped, tx_group, None,
2947 ))
2948 }
2949}
2950
2951fn parse_source_path_part(part: &str) -> (&str, Option<&str>) {
2958 if let Some(pos) = part.find('_') {
2962 let group = &part[..pos];
2963 let qualifier = &part[pos + 1..];
2964 if !qualifier.is_empty() {
2965 return (group, Some(qualifier));
2966 }
2967 }
2968 (part, None)
2969}
2970
2971fn build_reverse_mig_group_order(mig: &MigSchema, tx_group_id: &str) -> HashMap<String, usize> {
2979 let mut order = HashMap::new();
2980 if let Some(tg) = mig.segment_groups.iter().find(|g| g.id == tx_group_id) {
2981 for (i, nested) in tg.nested_groups.iter().enumerate() {
2982 if let Some(ref vc) = nested.variant_code {
2984 let variant_key = format!("{}_{}", nested.id, vc.to_uppercase());
2985 order.insert(variant_key, i);
2986 }
2987 order.entry(nested.id.clone()).or_insert(i);
2989 }
2990 }
2991 order
2992}
2993
2994fn variant_mig_position(
3000 def: &MappingDefinition,
3001 base_group_id: &str,
3002 mig_order: &HashMap<String, usize>,
3003) -> usize {
3004 if let Some(ref sp) = def.meta.source_path {
3007 let base_lower = base_group_id.to_lowercase();
3009 for part in sp.split('.') {
3010 if part.starts_with(&base_lower)
3011 || part.starts_with(base_group_id.to_lowercase().as_str())
3012 {
3013 if let Some(underscore_pos) = part.find('_') {
3015 let qualifier = &part[underscore_pos + 1..];
3016 let variant_key = format!("{}_{}", base_group_id, qualifier.to_uppercase());
3017 if let Some(&pos) = mig_order.get(&variant_key) {
3018 return pos;
3019 }
3020 }
3021 }
3022 }
3023 }
3024 mig_order.get(base_group_id).copied().unwrap_or(usize::MAX)
3026}
3027
3028fn find_rep_by_entry_qualifier<'a>(
3033 reps: &'a [AssembledGroupInstance],
3034 qualifier: &str,
3035) -> Option<&'a AssembledGroupInstance> {
3036 let parts: Vec<&str> = qualifier.split('_').collect();
3038 reps.iter().find(|inst| {
3039 inst.segments.first().is_some_and(|seg| {
3040 seg.elements
3041 .first()
3042 .and_then(|e| e.first())
3043 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3044 })
3045 })
3046}
3047
3048fn find_all_reps_by_entry_qualifier<'a>(
3050 reps: &'a [AssembledGroupInstance],
3051 qualifier: &str,
3052) -> Vec<&'a AssembledGroupInstance> {
3053 let parts: Vec<&str> = qualifier.split('_').collect();
3055 reps.iter()
3056 .filter(|inst| {
3057 inst.segments.first().is_some_and(|seg| {
3058 seg.elements
3059 .first()
3060 .and_then(|e| e.first())
3061 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3062 })
3063 })
3064 .collect()
3065}
3066
3067fn has_source_path_qualifiers(source_path: &str) -> bool {
3069 source_path.split('.').any(|part| {
3070 if let Some(pos) = part.find('_') {
3071 pos < part.len() - 1
3072 } else {
3073 false
3074 }
3075 })
3076}
3077
3078fn parse_group_spec(part: &str) -> (&str, Option<usize>) {
3079 if let Some(colon_pos) = part.find(':') {
3080 let id = &part[..colon_pos];
3081 let rep = part[colon_pos + 1..].parse::<usize>().ok();
3082 (id, rep)
3083 } else {
3084 (part, None)
3085 }
3086}
3087
3088fn strip_tx_group_prefix(source_group: &str, tx_group: &str) -> String {
3094 if source_group == tx_group || source_group.is_empty() {
3095 String::new()
3096 } else if let Some(rest) = source_group.strip_prefix(tx_group) {
3097 rest.strip_prefix('.').unwrap_or(rest).to_string()
3098 } else {
3099 source_group.to_string()
3100 }
3101}
3102
3103fn place_in_groups(
3111 groups: &mut Vec<AssembledGroup>,
3112 relative_path: &str,
3113 instance: AssembledGroupInstance,
3114) -> usize {
3115 let parts: Vec<&str> = relative_path.split('.').collect();
3116
3117 if parts.len() == 1 {
3118 let (id, rep) = parse_group_spec(parts[0]);
3120
3121 let group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == id) {
3123 g
3124 } else {
3125 groups.push(AssembledGroup {
3126 group_id: id.to_string(),
3127 repetitions: vec![],
3128 });
3129 groups.last_mut().unwrap()
3130 };
3131
3132 if let Some(rep_idx) = rep {
3133 while group.repetitions.len() <= rep_idx {
3135 group.repetitions.push(AssembledGroupInstance {
3136 segments: vec![],
3137 child_groups: vec![],
3138 skipped_segments: Vec::new(),
3139 });
3140 }
3141 group.repetitions[rep_idx]
3142 .segments
3143 .extend(instance.segments);
3144 group.repetitions[rep_idx]
3145 .child_groups
3146 .extend(instance.child_groups);
3147 rep_idx
3148 } else {
3149 let pos = group.repetitions.len();
3151 group.repetitions.push(instance);
3152 pos
3153 }
3154 } else {
3155 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3157 let rep_idx = parent_rep.unwrap_or(0);
3158
3159 let parent_group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == parent_id) {
3161 g
3162 } else {
3163 groups.push(AssembledGroup {
3164 group_id: parent_id.to_string(),
3165 repetitions: vec![],
3166 });
3167 groups.last_mut().unwrap()
3168 };
3169
3170 while parent_group.repetitions.len() <= rep_idx {
3172 parent_group.repetitions.push(AssembledGroupInstance {
3173 segments: vec![],
3174 child_groups: vec![],
3175 skipped_segments: Vec::new(),
3176 });
3177 }
3178
3179 let remaining = parts[1..].join(".");
3180 place_in_groups(
3181 &mut parent_group.repetitions[rep_idx].child_groups,
3182 &remaining,
3183 instance,
3184 );
3185 rep_idx
3186 }
3187}
3188
3189fn resolve_child_relative(
3201 relative: &str,
3202 source_path: Option<&str>,
3203 source_path_to_rep: &std::collections::HashMap<String, Vec<usize>>,
3204 item_idx: usize,
3205) -> String {
3206 let parts: Vec<&str> = relative.split('.').collect();
3207 if parts.is_empty() {
3208 return relative.to_string();
3209 }
3210
3211 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3213 if parent_rep.is_some() {
3214 return relative.to_string();
3215 }
3216
3217 if let Some(sp) = source_path {
3219 if let Some((parent_path, _child)) = sp.rsplit_once('.') {
3220 if let Some(rep_indices) = source_path_to_rep.get(parent_path) {
3221 let rep_idx = rep_indices
3223 .get(item_idx)
3224 .or_else(|| rep_indices.last())
3225 .copied()
3226 .unwrap_or(0);
3227 let rest = parts[1..].join(".");
3228 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3229 }
3230 }
3231 }
3232
3233 relative.to_string()
3235}
3236
3237struct DiscriminatorMatcher<'a> {
3244 tag: &'a str,
3245 element_idx: usize,
3246 component_idx: usize,
3247 expected_values: Vec<&'a str>,
3248 occurrence: Option<usize>,
3250}
3251
3252impl<'a> DiscriminatorMatcher<'a> {
3253 fn parse(disc: &'a str) -> Option<Self> {
3254 let (spec, expected) = disc.split_once('=')?;
3255 let parts: Vec<&str> = spec.split('.').collect();
3256 if parts.len() != 3 {
3257 return None;
3258 }
3259 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
3260 Some(Self {
3261 tag: parts[0],
3262 element_idx: parts[1].parse().ok()?,
3263 component_idx: parts[2].parse().ok()?,
3264 expected_values: expected_raw.split('|').collect(),
3265 occurrence,
3266 })
3267 }
3268
3269 fn matches(&self, instance: &AssembledGroupInstance) -> bool {
3270 instance.segments.iter().any(|s| {
3271 s.tag.eq_ignore_ascii_case(self.tag)
3272 && s.elements
3273 .get(self.element_idx)
3274 .and_then(|e| e.get(self.component_idx))
3275 .map(|v| self.expected_values.iter().any(|ev| v == ev))
3276 .unwrap_or(false)
3277 })
3278 }
3279
3280 fn filter_instances<'b>(
3282 &self,
3283 instances: Vec<&'b AssembledGroupInstance>,
3284 ) -> Vec<&'b AssembledGroupInstance> {
3285 let matching: Vec<_> = instances
3286 .into_iter()
3287 .filter(|inst| self.matches(inst))
3288 .collect();
3289 if let Some(occ) = self.occurrence {
3290 matching.into_iter().nth(occ).into_iter().collect()
3291 } else {
3292 matching
3293 }
3294 }
3295}
3296
3297fn parse_discriminator_occurrence(expected: &str) -> (&str, Option<usize>) {
3303 if let Some(hash_pos) = expected.rfind('#') {
3304 if let Ok(occ) = expected[hash_pos + 1..].parse::<usize>() {
3305 return (&expected[..hash_pos], Some(occ));
3306 }
3307 }
3308 (expected, None)
3309}
3310
3311fn strip_rep_index(relative: &str) -> String {
3315 let (id, _) = parse_group_spec(relative);
3316 id.to_string()
3317}
3318
3319fn strip_all_rep_indices(relative: &str) -> String {
3324 relative
3325 .split('.')
3326 .map(|part| {
3327 let (id, _) = parse_group_spec(part);
3328 id
3329 })
3330 .collect::<Vec<_>>()
3331 .join(".")
3332}
3333
3334fn is_collect_all_path(path: &str) -> bool {
3339 let tag_part = path.split('.').next().unwrap_or("");
3340 if let Some(bracket_start) = tag_part.find('[') {
3341 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3342 if let Some(comma_pos) = inner.find(',') {
3343 let qualifier = &inner[..comma_pos];
3344 let occ = &inner[comma_pos + 1..];
3345 qualifier != "*" && occ == "*"
3347 } else {
3348 false
3349 }
3350 } else {
3351 false
3352 }
3353}
3354
3355fn parse_tag_qualifier(tag_part: &str) -> (String, Option<&str>, usize) {
3362 if let Some(bracket_start) = tag_part.find('[') {
3363 let tag = tag_part[..bracket_start].to_uppercase();
3364 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3365 if let Some(comma_pos) = inner.find(',') {
3366 let qualifier = &inner[..comma_pos];
3367 let index = inner[comma_pos + 1..].parse::<usize>().unwrap_or(0);
3368 if qualifier == "*" {
3370 (tag, None, index)
3371 } else {
3372 (tag, Some(qualifier), index)
3373 }
3374 } else {
3375 (tag, Some(inner), 0)
3376 }
3377 } else {
3378 (tag_part.to_uppercase(), None, 0)
3379 }
3380}
3381
3382fn inject_bo4e_metadata(mut value: serde_json::Value, bo4e_type: &str) -> serde_json::Value {
3387 match &mut value {
3388 serde_json::Value::Object(map) => {
3389 map.entry("boTyp")
3390 .or_insert_with(|| serde_json::Value::String(bo4e_type.to_uppercase()));
3391 map.entry("versionStruktur")
3392 .or_insert_with(|| serde_json::Value::String("1".to_string()));
3393 }
3394 serde_json::Value::Array(items) => {
3395 for item in items {
3396 if let serde_json::Value::Object(map) = item {
3397 map.entry("boTyp")
3398 .or_insert_with(|| serde_json::Value::String(bo4e_type.to_uppercase()));
3399 map.entry("versionStruktur")
3400 .or_insert_with(|| serde_json::Value::String("1".to_string()));
3401 }
3402 }
3403 }
3404 _ => {}
3405 }
3406 value
3407}
3408
3409fn deep_merge_insert(
3415 result: &mut serde_json::Map<String, serde_json::Value>,
3416 entity: &str,
3417 bo4e: serde_json::Value,
3418) {
3419 if let Some(existing) = result.get_mut(entity) {
3420 if let (Some(existing_arr), Some(new_arr)) =
3423 (existing.as_array().map(|a| a.len()), bo4e.as_array())
3424 {
3425 if existing_arr == new_arr.len() {
3426 let existing_arr = existing.as_array_mut().unwrap();
3427 for (existing_elem, new_elem) in existing_arr.iter_mut().zip(new_arr) {
3428 if let (Some(existing_map), Some(new_map)) =
3429 (existing_elem.as_object_mut(), new_elem.as_object())
3430 {
3431 for (k, v) in new_map {
3432 if let Some(existing_v) = existing_map.get_mut(k) {
3433 if let (Some(existing_inner), Some(new_inner)) =
3434 (existing_v.as_object_mut(), v.as_object())
3435 {
3436 for (ik, iv) in new_inner {
3437 existing_inner
3438 .entry(ik.clone())
3439 .or_insert_with(|| iv.clone());
3440 }
3441 }
3442 } else {
3443 existing_map.insert(k.clone(), v.clone());
3444 }
3445 }
3446 }
3447 }
3448 return;
3449 }
3450 }
3451 if let (Some(existing_map), serde_json::Value::Object(new_map)) =
3453 (existing.as_object_mut(), &bo4e)
3454 {
3455 for (k, v) in new_map {
3456 if let Some(existing_v) = existing_map.get_mut(k) {
3457 if let (Some(existing_inner), Some(new_inner)) =
3459 (existing_v.as_object_mut(), v.as_object())
3460 {
3461 for (ik, iv) in new_inner {
3462 existing_inner
3463 .entry(ik.clone())
3464 .or_insert_with(|| iv.clone());
3465 }
3466 }
3467 } else {
3469 existing_map.insert(k.clone(), v.clone());
3470 }
3471 }
3472 return;
3473 }
3474 }
3475 result.insert(entity.to_string(), bo4e);
3476}
3477
3478fn is_map_keyed_object(value: &serde_json::Value) -> bool {
3489 let Some(obj) = value.as_object() else {
3490 return false;
3491 };
3492 if obj.is_empty() {
3493 return false;
3494 }
3495 obj.iter().all(|(k, v)| {
3497 k.len() <= 5
3498 && k.chars()
3499 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
3500 && v.is_object()
3501 })
3502}
3503
3504fn find_qualifier_companion_field(
3513 definitions: &[crate::definition::MappingDefinition],
3514 entity: &str,
3515) -> Option<String> {
3516 for def in definitions {
3517 if def.meta.entity != *entity {
3518 continue;
3519 }
3520 let disc = def.meta.discriminator.as_deref()?;
3521 let (disc_path, _) = disc.split_once('=')?;
3522 let disc_path_lower = disc_path.to_lowercase();
3523
3524 let sections: Vec<&indexmap::IndexMap<String, FieldMapping>> = [
3527 def.companion_fields.as_ref(),
3528 Some(&def.fields),
3529 ]
3530 .into_iter()
3531 .flatten()
3532 .collect();
3533
3534 for section in sections {
3535 for (path, mapping) in section {
3536 let cf_path = path.to_lowercase();
3537 let matches = cf_path == disc_path_lower
3538 || format!("{}.0", cf_path) == disc_path_lower;
3539 if matches {
3540 let target = match mapping {
3541 FieldMapping::Simple(t) => t.as_str(),
3542 FieldMapping::Structured(s) => s.target.as_str(),
3543 FieldMapping::Nested(_) => continue,
3544 };
3545 if !target.is_empty() {
3546 return Some(target.to_string());
3547 }
3548 }
3549 }
3550 }
3551 }
3552 None
3553}
3554
3555fn extract_child_from_parent(
3564 entities: &serde_json::Value,
3565 definitions: &[MappingDefinition],
3566 child_def: &MappingDefinition,
3567) -> Option<serde_json::Value> {
3568 let parts: Vec<&str> = child_def.meta.source_group.split('.').collect();
3569 if parts.len() < 2 {
3570 return None;
3571 }
3572 let parent_group = parts[0];
3573 let parent_def = definitions
3574 .iter()
3575 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_def.meta.entity)?;
3576 let parent_key = to_camel_case(&parent_def.meta.entity);
3577 let child_key = to_camel_case(&child_def.meta.entity);
3578 let parent_value = entities.get(&parent_key)?;
3579
3580 if let Some(parent_map) = parent_value.as_object() {
3582 if is_map_keyed_value(parent_map) {
3583 let mut children: Vec<serde_json::Value> = Vec::new();
3584 for (_key, inner) in parent_map {
3585 if let Some(child) = inner.get(&child_key) {
3586 if !child.is_null() {
3587 children.push(child.clone());
3588 }
3589 }
3590 }
3591 return match children.len() {
3592 0 => None,
3593 1 => Some(children.into_iter().next().unwrap()),
3594 _ => Some(serde_json::Value::Array(children)),
3595 };
3596 }
3597 }
3598
3599 if let Some(parent_arr) = parent_value.as_array() {
3601 let mut children: Vec<serde_json::Value> = Vec::new();
3602 for item in parent_arr {
3603 if let Some(child) = item.get(&child_key) {
3604 if !child.is_null() {
3605 children.push(child.clone());
3606 }
3607 }
3608 }
3609 return match children.len() {
3610 0 => None,
3611 1 => Some(children.into_iter().next().unwrap()),
3612 _ => Some(serde_json::Value::Array(children)),
3613 };
3614 }
3615
3616 let child = parent_value.get(&child_key)?;
3618 if child.is_null() {
3619 return None;
3620 }
3621 Some(child.clone())
3622}
3623
3624fn nest_child_entities_in_result(
3630 result: &mut serde_json::Map<String, serde_json::Value>,
3631 definitions: &[MappingDefinition],
3632 nesting_info: &std::collections::HashMap<String, Vec<usize>>,
3633) {
3634 let mut nesting_pairs: Vec<(String, String, String, Option<String>)> = Vec::new();
3637 for def in definitions {
3638 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
3639 if parts.len() < 2 {
3640 continue;
3641 }
3642 let parent_group = parts[0];
3643 let child_entity = def.meta.entity.clone();
3644 let child_has_parent_level_def = definitions
3648 .iter()
3649 .any(|d| d.meta.source_group == parent_group && d.meta.entity == child_entity);
3650 if child_has_parent_level_def {
3651 continue;
3652 }
3653 let parent_entity = definitions
3655 .iter()
3656 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_entity)
3657 .map(|d| d.meta.entity.clone());
3658 if let Some(ref parent_entity) = parent_entity {
3659 let child_key_lc = to_camel_case(&child_entity);
3664 let parent_defs: Vec<_> = definitions
3665 .iter()
3666 .filter(|d| d.meta.entity == *parent_entity)
3667 .collect();
3668 let has_conflicting_field = parent_defs.iter().any(|pd| {
3669 pd.fields.values().any(|fm| {
3670 let target = match fm {
3671 crate::definition::FieldMapping::Simple(t) => t.as_str(),
3672 crate::definition::FieldMapping::Structured(s) => s.target.as_str(),
3673 crate::definition::FieldMapping::Nested(_) => "",
3674 };
3675 target.starts_with(&child_key_lc)
3676 && target.get(child_key_lc.len()..child_key_lc.len() + 1) == Some(".")
3677 })
3678 });
3679 if has_conflicting_field {
3680 continue;
3681 }
3682 if nesting_pairs
3684 .iter()
3685 .any(|(_, pe, ce, _)| *pe == *parent_entity && *ce == child_entity)
3686 {
3687 continue;
3688 }
3689 nesting_pairs.push((
3690 parent_group.to_string(),
3691 parent_entity.clone(),
3692 child_entity,
3693 def.meta.source_path.clone(),
3694 ));
3695 }
3696 }
3697
3698 for (_parent_group, parent_entity, child_entity, child_source_path) in nesting_pairs {
3699 let parent_key = to_camel_case(&parent_entity);
3700 let child_key = to_camel_case(&child_entity);
3701
3702 let child_value = match result.remove(&child_key) {
3704 Some(v) => v,
3705 None => continue,
3706 };
3707
3708 let Some(parent_value) = result.get_mut(&parent_key) else {
3710 result.insert(child_key, child_value);
3712 continue;
3713 };
3714
3715 let distribution = child_source_path
3717 .as_deref()
3718 .and_then(|sp| nesting_info.get(sp));
3719
3720 let child_items: Vec<(usize, &serde_json::Value)> = match &child_value {
3722 serde_json::Value::Array(arr) => arr.iter().enumerate().collect(),
3723 other => vec![(0, other)],
3724 };
3725
3726 let insert_or_append =
3729 |obj: &mut serde_json::Map<String, serde_json::Value>,
3730 key: &str,
3731 val: &serde_json::Value| {
3732 match obj.get_mut(key) {
3733 Some(existing) => {
3734 if !existing.is_array() {
3736 let prev = existing.take();
3737 *existing = serde_json::Value::Array(vec![prev]);
3738 }
3739 if let Some(arr) = existing.as_array_mut() {
3740 arr.push(val.clone());
3741 }
3742 }
3743 None => {
3744 obj.insert(key.to_string(), val.clone());
3745 }
3746 }
3747 };
3748
3749 if let Some(parent_map) = parent_value.as_object_mut() {
3751 if is_map_keyed_value(parent_map) {
3752 let keys: Vec<String> = parent_map.keys().cloned().collect();
3754 for (i, child_item) in &child_items {
3755 let target_idx = distribution
3756 .and_then(|dist| dist.get(*i))
3757 .copied()
3758 .unwrap_or(0);
3759 if let Some(key) = keys.get(target_idx) {
3760 if let Some(inner) = parent_map.get_mut(key).and_then(|v| v.as_object_mut())
3761 {
3762 insert_or_append(inner, &child_key, child_item);
3763 }
3764 }
3765 }
3766 continue;
3767 }
3768 }
3769
3770 if let Some(parent_arr) = parent_value.as_array_mut() {
3772 for (i, child_item) in &child_items {
3773 let target_idx = distribution
3774 .and_then(|dist| dist.get(*i))
3775 .copied()
3776 .unwrap_or(0);
3777 if let Some(parent_obj) =
3778 parent_arr.get_mut(target_idx).and_then(|v| v.as_object_mut())
3779 {
3780 insert_or_append(parent_obj, &child_key, child_item);
3781 }
3782 }
3783 continue;
3784 }
3785
3786 if let Some(parent_obj) = parent_value.as_object_mut() {
3788 for (_i, child_item) in &child_items {
3789 insert_or_append(parent_obj, &child_key, child_item);
3790 }
3791 continue;
3792 }
3793
3794 result.insert(child_key, child_value);
3796 }
3797}
3798
3799fn is_map_keyed_value(map: &serde_json::Map<String, serde_json::Value>) -> bool {
3801 if map.is_empty() {
3802 return false;
3803 }
3804 map.values().all(|v| v.is_object())
3805 && map.keys().all(|k| k.len() <= 5 || k.chars().all(|c| c.is_ascii_uppercase() || c.is_ascii_digit()))
3806}
3807
3808fn to_camel_case(name: &str) -> String {
3809 let mut chars = name.chars();
3810 match chars.next() {
3811 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
3812 None => String::new(),
3813 }
3814}
3815
3816fn set_nested_value(map: &mut serde_json::Map<String, serde_json::Value>, path: &str, val: String) {
3819 set_nested_value_json(map, path, serde_json::Value::String(val));
3820}
3821
3822fn set_nested_value_json(
3824 map: &mut serde_json::Map<String, serde_json::Value>,
3825 path: &str,
3826 val: serde_json::Value,
3827) {
3828 if let Some((prefix, leaf)) = path.rsplit_once('.') {
3829 let mut current = map;
3830 for part in prefix.split('.') {
3831 let entry = current
3832 .entry(part.to_string())
3833 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
3834 current = entry.as_object_mut().expect("expected object in path");
3835 }
3836 current.insert(leaf.to_string(), val);
3837 } else {
3838 map.insert(path.to_string(), val);
3839 }
3840}
3841
3842#[derive(serde::Serialize, serde::Deserialize)]
3847pub struct VariantCache {
3848 pub message_defs: Vec<MappingDefinition>,
3850 pub transaction_defs: HashMap<String, Vec<MappingDefinition>>,
3852 pub combined_defs: HashMap<String, Vec<MappingDefinition>>,
3854 #[serde(default)]
3856 pub code_lookups: HashMap<String, crate::code_lookup::CodeLookup>,
3857 #[serde(default)]
3859 pub mig_schema: Option<mig_types::schema::mig::MigSchema>,
3860 #[serde(default)]
3862 pub segment_structure: Option<crate::segment_structure::SegmentStructure>,
3863 #[serde(default)]
3866 pub pid_segment_numbers: HashMap<String, Vec<String>>,
3867 #[serde(default)]
3870 pub pid_requirements: HashMap<String, crate::pid_requirements::PidRequirements>,
3871 #[serde(default)]
3875 pub tx_groups: HashMap<String, String>,
3876}
3877
3878impl VariantCache {
3879 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
3881 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
3882 path: path.display().to_string(),
3883 message: e.to_string(),
3884 })?;
3885 if let Some(parent) = path.parent() {
3886 std::fs::create_dir_all(parent)?;
3887 }
3888 std::fs::write(path, encoded)?;
3889 Ok(())
3890 }
3891
3892 pub fn load(path: &Path) -> Result<Self, MappingError> {
3894 let bytes = std::fs::read(path)?;
3895 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
3896 path: path.display().to_string(),
3897 message: e.to_string(),
3898 })
3899 }
3900
3901 pub fn tx_group(&self, pid: &str) -> Option<&str> {
3905 self.tx_groups
3906 .get(&format!("pid_{pid}"))
3907 .map(|s| s.as_str())
3908 }
3909
3910 pub fn msg_engine(&self) -> MappingEngine {
3912 MappingEngine::from_definitions(self.message_defs.clone())
3913 }
3914
3915 pub fn tx_engine(&self, pid: &str) -> Option<MappingEngine> {
3918 self.transaction_defs
3919 .get(&format!("pid_{pid}"))
3920 .map(|defs| MappingEngine::from_definitions(defs.clone()))
3921 }
3922
3923 pub fn filtered_mig(&self, pid: &str) -> Option<mig_types::schema::mig::MigSchema> {
3926 let mig = self.mig_schema.as_ref()?;
3927 let numbers = self.pid_segment_numbers.get(&format!("pid_{pid}"))?;
3928 let number_set: std::collections::HashSet<String> = numbers.iter().cloned().collect();
3929 Some(mig_assembly::pid_filter::filter_mig_for_pid(
3930 mig,
3931 &number_set,
3932 ))
3933 }
3934}
3935
3936#[derive(serde::Serialize, serde::Deserialize)]
3941pub struct DataBundle {
3942 pub format_version: String,
3943 pub bundle_version: u32,
3944 pub variants: HashMap<String, VariantCache>,
3945}
3946
3947impl DataBundle {
3948 pub const CURRENT_VERSION: u32 = 2;
3949
3950 pub fn variant(&self, name: &str) -> Option<&VariantCache> {
3951 self.variants.get(name)
3952 }
3953
3954 pub fn write_to<W: std::io::Write>(&self, writer: &mut W) -> Result<(), MappingError> {
3955 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
3956 path: "<stream>".to_string(),
3957 message: e.to_string(),
3958 })?;
3959 writer.write_all(&encoded).map_err(MappingError::Io)
3960 }
3961
3962 pub fn read_from<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
3963 let mut bytes = Vec::new();
3964 reader.read_to_end(&mut bytes).map_err(MappingError::Io)?;
3965 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
3966 path: "<stream>".to_string(),
3967 message: e.to_string(),
3968 })
3969 }
3970
3971 pub fn read_from_checked<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
3972 let bundle = Self::read_from(reader)?;
3973 if bundle.bundle_version != Self::CURRENT_VERSION {
3974 return Err(MappingError::CacheRead {
3975 path: "<stream>".to_string(),
3976 message: format!(
3977 "Incompatible bundle version {}, expected version {}. \
3978 Run `edifact-data update` to fetch compatible bundles.",
3979 bundle.bundle_version,
3980 Self::CURRENT_VERSION
3981 ),
3982 });
3983 }
3984 Ok(bundle)
3985 }
3986
3987 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
3988 if let Some(parent) = path.parent() {
3989 std::fs::create_dir_all(parent)?;
3990 }
3991 let mut file = std::fs::File::create(path).map_err(MappingError::Io)?;
3992 self.write_to(&mut file)
3993 }
3994
3995 pub fn load(path: &Path) -> Result<Self, MappingError> {
3996 let mut file = std::fs::File::open(path).map_err(MappingError::Io)?;
3997 Self::read_from_checked(&mut file)
3998 }
3999}
4000
4001fn sort_variant_reps_by_mig(
4014 child_groups: &mut [AssembledGroup],
4015 mig: &MigSchema,
4016 transaction_group: &str,
4017) {
4018 let tx_def = match mig
4019 .segment_groups
4020 .iter()
4021 .find(|sg| sg.id == transaction_group)
4022 {
4023 Some(d) => d,
4024 None => return,
4025 };
4026
4027 for cg in child_groups.iter_mut() {
4028 if cg.repetitions.len() <= 1 {
4029 continue;
4030 }
4031
4032 let variant_defs: Vec<(usize, &mig_types::schema::mig::MigSegmentGroup)> = tx_def
4034 .nested_groups
4035 .iter()
4036 .enumerate()
4037 .filter(|(_, ng)| ng.id == cg.group_id && ng.variant_code.is_some())
4038 .collect();
4039
4040 if variant_defs.is_empty() {
4041 continue;
4042 }
4043
4044 cg.repetitions.sort_by_key(|rep| {
4047 let entry_seg = rep.segments.first();
4048 for &(mig_pos, variant_def) in &variant_defs {
4049 let (ei, ci) = variant_def.variant_qualifier_position.unwrap_or((0, 0));
4050 let actual_qual = entry_seg
4051 .and_then(|s| s.elements.get(ei))
4052 .and_then(|e| e.get(ci))
4053 .map(|s| s.as_str())
4054 .unwrap_or("");
4055 let matches = if !variant_def.variant_codes.is_empty() {
4056 variant_def
4057 .variant_codes
4058 .iter()
4059 .any(|c| actual_qual.eq_ignore_ascii_case(c))
4060 } else if let Some(ref expected_code) = variant_def.variant_code {
4061 actual_qual.eq_ignore_ascii_case(expected_code)
4062 } else {
4063 false
4064 };
4065 if matches {
4066 return mig_pos;
4067 }
4068 }
4069 usize::MAX });
4071 }
4072}
4073
4074#[cfg(test)]
4075mod variant_cache_helper_tests {
4076 use super::*;
4077
4078 fn make_test_cache() -> VariantCache {
4079 let mut tx_groups = HashMap::new();
4080 tx_groups.insert("pid_55001".to_string(), "SG4".to_string());
4081 tx_groups.insert("pid_21007".to_string(), "SG14".to_string());
4082
4083 let mut transaction_defs = HashMap::new();
4084 transaction_defs.insert("pid_55001".to_string(), vec![]);
4085 transaction_defs.insert("pid_21007".to_string(), vec![]);
4086
4087 VariantCache {
4088 message_defs: vec![],
4089 transaction_defs,
4090 combined_defs: HashMap::new(),
4091 code_lookups: HashMap::new(),
4092 mig_schema: None,
4093 segment_structure: None,
4094 pid_segment_numbers: HashMap::new(),
4095 pid_requirements: HashMap::new(),
4096 tx_groups,
4097 }
4098 }
4099
4100 #[test]
4101 fn test_tx_group_returns_correct_group() {
4102 let vc = make_test_cache();
4103 assert_eq!(vc.tx_group("55001").unwrap(), "SG4");
4104 assert_eq!(vc.tx_group("21007").unwrap(), "SG14");
4105 }
4106
4107 #[test]
4108 fn test_tx_group_unknown_pid_returns_none() {
4109 let vc = make_test_cache();
4110 assert!(vc.tx_group("99999").is_none());
4111 }
4112
4113 #[test]
4114 fn test_msg_engine_returns_engine() {
4115 let vc = make_test_cache();
4116 let engine = vc.msg_engine();
4117 assert_eq!(engine.definitions().len(), 0);
4118 }
4119
4120 #[test]
4121 fn test_tx_engine_returns_engine_for_known_pid() {
4122 let vc = make_test_cache();
4123 assert!(vc.tx_engine("55001").is_some());
4124 }
4125
4126 #[test]
4127 fn test_tx_engine_returns_none_for_unknown_pid() {
4128 let vc = make_test_cache();
4129 assert!(vc.tx_engine("99999").is_none());
4130 }
4131}
4132
4133#[cfg(test)]
4134mod tests {
4135 use super::*;
4136 use crate::definition::{MappingDefinition, MappingMeta, StructuredFieldMapping};
4137 use indexmap::IndexMap;
4138
4139 fn make_def(fields: IndexMap<String, FieldMapping>) -> MappingDefinition {
4140 MappingDefinition {
4141 meta: MappingMeta {
4142 entity: "Test".to_string(),
4143 bo4e_type: "Test".to_string(),
4144 companion_type: None,
4145 source_group: "SG4".to_string(),
4146 source_path: None,
4147 discriminator: None,
4148 repeat_on_tag: None,
4149 },
4150 fields,
4151 companion_fields: None,
4152 complex_handlers: None,
4153 }
4154 }
4155
4156 #[test]
4157 fn test_map_interchange_single_transaction_backward_compat() {
4158 use mig_assembly::assembler::*;
4159
4160 let tree = AssembledTree {
4162 segments: vec![
4163 AssembledSegment {
4164 tag: "UNH".to_string(),
4165 elements: vec![vec!["001".to_string()]],
4166 },
4167 AssembledSegment {
4168 tag: "BGM".to_string(),
4169 elements: vec![vec!["E01".to_string()], vec!["DOC001".to_string()]],
4170 },
4171 ],
4172 groups: vec![
4173 AssembledGroup {
4174 group_id: "SG2".to_string(),
4175 repetitions: vec![AssembledGroupInstance {
4176 segments: vec![AssembledSegment {
4177 tag: "NAD".to_string(),
4178 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4179 }],
4180 child_groups: vec![],
4181 skipped_segments: vec![],
4182 }],
4183 },
4184 AssembledGroup {
4185 group_id: "SG4".to_string(),
4186 repetitions: vec![AssembledGroupInstance {
4187 segments: vec![AssembledSegment {
4188 tag: "IDE".to_string(),
4189 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4190 }],
4191 child_groups: vec![AssembledGroup {
4192 group_id: "SG5".to_string(),
4193 repetitions: vec![AssembledGroupInstance {
4194 segments: vec![AssembledSegment {
4195 tag: "LOC".to_string(),
4196 elements: vec![
4197 vec!["Z16".to_string()],
4198 vec!["DE000111222333".to_string()],
4199 ],
4200 }],
4201 child_groups: vec![],
4202 skipped_segments: vec![],
4203 }],
4204 }],
4205 skipped_segments: vec![],
4206 }],
4207 },
4208 ],
4209 post_group_start: 2,
4210 inter_group_segments: std::collections::BTreeMap::new(),
4211 };
4212
4213 let msg_engine = MappingEngine::from_definitions(vec![]);
4215
4216 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4218 tx_fields.insert(
4219 "ide.1".to_string(),
4220 FieldMapping::Simple("vorgangId".to_string()),
4221 );
4222 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4223 malo_fields.insert(
4224 "loc.1".to_string(),
4225 FieldMapping::Simple("marktlokationsId".to_string()),
4226 );
4227
4228 let tx_engine = MappingEngine::from_definitions(vec![
4229 MappingDefinition {
4230 meta: MappingMeta {
4231 entity: "Prozessdaten".to_string(),
4232 bo4e_type: "Prozessdaten".to_string(),
4233 companion_type: None,
4234 source_group: "SG4".to_string(),
4235 source_path: None,
4236 discriminator: None,
4237 repeat_on_tag: None,
4238 },
4239 fields: tx_fields,
4240 companion_fields: None,
4241 complex_handlers: None,
4242 },
4243 MappingDefinition {
4244 meta: MappingMeta {
4245 entity: "Marktlokation".to_string(),
4246 bo4e_type: "Marktlokation".to_string(),
4247 companion_type: None,
4248 source_group: "SG4.SG5".to_string(),
4249 source_path: None,
4250 discriminator: None,
4251 repeat_on_tag: None,
4252 },
4253 fields: malo_fields,
4254 companion_fields: None,
4255 complex_handlers: None,
4256 },
4257 ]);
4258
4259 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
4260
4261 assert_eq!(result.transaktionen.len(), 1);
4262 assert_eq!(
4263 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
4264 .as_str()
4265 .unwrap(),
4266 "TX001"
4267 );
4268 assert_eq!(
4269 result.transaktionen[0].stammdaten["marktlokation"]["marktlokationsId"]
4270 .as_str()
4271 .unwrap(),
4272 "DE000111222333"
4273 );
4274 }
4275
4276 #[test]
4277 fn test_map_reverse_pads_intermediate_empty_elements() {
4278 let mut fields = IndexMap::new();
4280 fields.insert(
4281 "nad.0".to_string(),
4282 FieldMapping::Structured(StructuredFieldMapping {
4283 target: String::new(),
4284 transform: None,
4285 when: None,
4286 default: Some("Z09".to_string()),
4287 enum_map: None,
4288 when_filled: None,
4289 also_target: None,
4290 also_enum_map: None,
4291 }),
4292 );
4293 fields.insert(
4294 "nad.3.0".to_string(),
4295 FieldMapping::Simple("name".to_string()),
4296 );
4297 fields.insert(
4298 "nad.3.1".to_string(),
4299 FieldMapping::Simple("vorname".to_string()),
4300 );
4301
4302 let def = make_def(fields);
4303 let engine = MappingEngine::from_definitions(vec![]);
4304
4305 let bo4e = serde_json::json!({
4306 "name": "Muster",
4307 "vorname": "Max"
4308 });
4309
4310 let instance = engine.map_reverse(&bo4e, &def);
4311 assert_eq!(instance.segments.len(), 1);
4312
4313 let nad = &instance.segments[0];
4314 assert_eq!(nad.tag, "NAD");
4315 assert_eq!(nad.elements.len(), 4);
4316 assert_eq!(nad.elements[0], vec!["Z09"]);
4317 assert_eq!(nad.elements[1], vec![""]);
4319 assert_eq!(nad.elements[2], vec![""]);
4320 assert_eq!(nad.elements[3][0], "Muster");
4321 assert_eq!(nad.elements[3][1], "Max");
4322 }
4323
4324 #[test]
4325 fn test_map_reverse_no_padding_when_contiguous() {
4326 let mut fields = IndexMap::new();
4328 fields.insert(
4329 "dtm.0.0".to_string(),
4330 FieldMapping::Structured(StructuredFieldMapping {
4331 target: String::new(),
4332 transform: None,
4333 when: None,
4334 default: Some("92".to_string()),
4335 enum_map: None,
4336 when_filled: None,
4337 also_target: None,
4338 also_enum_map: None,
4339 }),
4340 );
4341 fields.insert(
4342 "dtm.0.1".to_string(),
4343 FieldMapping::Simple("value".to_string()),
4344 );
4345 fields.insert(
4346 "dtm.0.2".to_string(),
4347 FieldMapping::Structured(StructuredFieldMapping {
4348 target: String::new(),
4349 transform: None,
4350 when: None,
4351 default: Some("303".to_string()),
4352 enum_map: None,
4353 when_filled: None,
4354 also_target: None,
4355 also_enum_map: None,
4356 }),
4357 );
4358
4359 let def = make_def(fields);
4360 let engine = MappingEngine::from_definitions(vec![]);
4361
4362 let bo4e = serde_json::json!({ "value": "20250531" });
4363
4364 let instance = engine.map_reverse(&bo4e, &def);
4365 let dtm = &instance.segments[0];
4366 assert_eq!(dtm.elements.len(), 1);
4368 assert_eq!(dtm.elements[0], vec!["92", "20250531", "303"]);
4369 }
4370
4371 #[test]
4372 fn test_map_message_level_extracts_sg2_only() {
4373 use mig_assembly::assembler::*;
4374
4375 let tree = AssembledTree {
4377 segments: vec![
4378 AssembledSegment {
4379 tag: "UNH".to_string(),
4380 elements: vec![vec!["001".to_string()]],
4381 },
4382 AssembledSegment {
4383 tag: "BGM".to_string(),
4384 elements: vec![vec!["E01".to_string()]],
4385 },
4386 ],
4387 groups: vec![
4388 AssembledGroup {
4389 group_id: "SG2".to_string(),
4390 repetitions: vec![AssembledGroupInstance {
4391 segments: vec![AssembledSegment {
4392 tag: "NAD".to_string(),
4393 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4394 }],
4395 child_groups: vec![],
4396 skipped_segments: vec![],
4397 }],
4398 },
4399 AssembledGroup {
4400 group_id: "SG4".to_string(),
4401 repetitions: vec![AssembledGroupInstance {
4402 segments: vec![AssembledSegment {
4403 tag: "IDE".to_string(),
4404 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4405 }],
4406 child_groups: vec![],
4407 skipped_segments: vec![],
4408 }],
4409 },
4410 ],
4411 post_group_start: 2,
4412 inter_group_segments: std::collections::BTreeMap::new(),
4413 };
4414
4415 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4417 msg_fields.insert(
4418 "nad.0".to_string(),
4419 FieldMapping::Simple("marktrolle".to_string()),
4420 );
4421 msg_fields.insert(
4422 "nad.1".to_string(),
4423 FieldMapping::Simple("rollencodenummer".to_string()),
4424 );
4425 let msg_def = MappingDefinition {
4426 meta: MappingMeta {
4427 entity: "Marktteilnehmer".to_string(),
4428 bo4e_type: "Marktteilnehmer".to_string(),
4429 companion_type: None,
4430 source_group: "SG2".to_string(),
4431 source_path: None,
4432 discriminator: None,
4433 repeat_on_tag: None,
4434 },
4435 fields: msg_fields,
4436 companion_fields: None,
4437 complex_handlers: None,
4438 };
4439
4440 let engine = MappingEngine::from_definitions(vec![msg_def.clone()]);
4441 let result = engine.map_all_forward(&tree);
4442
4443 assert!(result.get("marktteilnehmer").is_some());
4445 let mt = &result["marktteilnehmer"];
4446 assert_eq!(mt["marktrolle"].as_str().unwrap(), "MS");
4447 assert_eq!(mt["rollencodenummer"].as_str().unwrap(), "9900123");
4448 }
4449
4450 #[test]
4451 fn test_map_transaction_scoped_to_sg4_instance() {
4452 use mig_assembly::assembler::*;
4453
4454 let tree = AssembledTree {
4456 segments: vec![
4457 AssembledSegment {
4458 tag: "UNH".to_string(),
4459 elements: vec![vec!["001".to_string()]],
4460 },
4461 AssembledSegment {
4462 tag: "BGM".to_string(),
4463 elements: vec![vec!["E01".to_string()]],
4464 },
4465 ],
4466 groups: vec![AssembledGroup {
4467 group_id: "SG4".to_string(),
4468 repetitions: vec![AssembledGroupInstance {
4469 segments: vec![AssembledSegment {
4470 tag: "IDE".to_string(),
4471 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4472 }],
4473 child_groups: vec![AssembledGroup {
4474 group_id: "SG5".to_string(),
4475 repetitions: vec![AssembledGroupInstance {
4476 segments: vec![AssembledSegment {
4477 tag: "LOC".to_string(),
4478 elements: vec![
4479 vec!["Z16".to_string()],
4480 vec!["DE000111222333".to_string()],
4481 ],
4482 }],
4483 child_groups: vec![],
4484 skipped_segments: vec![],
4485 }],
4486 }],
4487 skipped_segments: vec![],
4488 }],
4489 }],
4490 post_group_start: 2,
4491 inter_group_segments: std::collections::BTreeMap::new(),
4492 };
4493
4494 let mut proz_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4496 proz_fields.insert(
4497 "ide.1".to_string(),
4498 FieldMapping::Simple("vorgangId".to_string()),
4499 );
4500 let proz_def = MappingDefinition {
4501 meta: MappingMeta {
4502 entity: "Prozessdaten".to_string(),
4503 bo4e_type: "Prozessdaten".to_string(),
4504 companion_type: None,
4505 source_group: "".to_string(), source_path: None,
4507 discriminator: None,
4508 repeat_on_tag: None,
4509 },
4510 fields: proz_fields,
4511 companion_fields: None,
4512 complex_handlers: None,
4513 };
4514
4515 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4516 malo_fields.insert(
4517 "loc.1".to_string(),
4518 FieldMapping::Simple("marktlokationsId".to_string()),
4519 );
4520 let malo_def = MappingDefinition {
4521 meta: MappingMeta {
4522 entity: "Marktlokation".to_string(),
4523 bo4e_type: "Marktlokation".to_string(),
4524 companion_type: None,
4525 source_group: "SG5".to_string(), source_path: None,
4527 discriminator: None,
4528 repeat_on_tag: None,
4529 },
4530 fields: malo_fields,
4531 companion_fields: None,
4532 complex_handlers: None,
4533 };
4534
4535 let tx_engine = MappingEngine::from_definitions(vec![proz_def, malo_def]);
4536
4537 let sg4 = &tree.groups[0]; let sg4_instance = &sg4.repetitions[0];
4540 let sub_tree = sg4_instance.as_assembled_tree();
4541
4542 let result = tx_engine.map_all_forward(&sub_tree);
4543
4544 assert_eq!(
4546 result["prozessdaten"]["vorgangId"].as_str().unwrap(),
4547 "TX001"
4548 );
4549
4550 assert_eq!(
4552 result["marktlokation"]["marktlokationsId"]
4553 .as_str()
4554 .unwrap(),
4555 "DE000111222333"
4556 );
4557 }
4558
4559 #[test]
4560 fn test_map_interchange_produces_full_hierarchy() {
4561 use mig_assembly::assembler::*;
4562
4563 let tree = AssembledTree {
4565 segments: vec![
4566 AssembledSegment {
4567 tag: "UNH".to_string(),
4568 elements: vec![vec!["001".to_string()]],
4569 },
4570 AssembledSegment {
4571 tag: "BGM".to_string(),
4572 elements: vec![vec!["E01".to_string()]],
4573 },
4574 ],
4575 groups: vec![
4576 AssembledGroup {
4577 group_id: "SG2".to_string(),
4578 repetitions: vec![AssembledGroupInstance {
4579 segments: vec![AssembledSegment {
4580 tag: "NAD".to_string(),
4581 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4582 }],
4583 child_groups: vec![],
4584 skipped_segments: vec![],
4585 }],
4586 },
4587 AssembledGroup {
4588 group_id: "SG4".to_string(),
4589 repetitions: vec![
4590 AssembledGroupInstance {
4591 segments: vec![AssembledSegment {
4592 tag: "IDE".to_string(),
4593 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4594 }],
4595 child_groups: vec![],
4596 skipped_segments: vec![],
4597 },
4598 AssembledGroupInstance {
4599 segments: vec![AssembledSegment {
4600 tag: "IDE".to_string(),
4601 elements: vec![vec!["24".to_string()], vec!["TX002".to_string()]],
4602 }],
4603 child_groups: vec![],
4604 skipped_segments: vec![],
4605 },
4606 ],
4607 },
4608 ],
4609 post_group_start: 2,
4610 inter_group_segments: std::collections::BTreeMap::new(),
4611 };
4612
4613 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4615 msg_fields.insert(
4616 "nad.0".to_string(),
4617 FieldMapping::Simple("marktrolle".to_string()),
4618 );
4619 let msg_defs = vec![MappingDefinition {
4620 meta: MappingMeta {
4621 entity: "Marktteilnehmer".to_string(),
4622 bo4e_type: "Marktteilnehmer".to_string(),
4623 companion_type: None,
4624 source_group: "SG2".to_string(),
4625 source_path: None,
4626 discriminator: None,
4627 repeat_on_tag: None,
4628 },
4629 fields: msg_fields,
4630 companion_fields: None,
4631 complex_handlers: None,
4632 }];
4633
4634 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4636 tx_fields.insert(
4637 "ide.1".to_string(),
4638 FieldMapping::Simple("vorgangId".to_string()),
4639 );
4640 let tx_defs = vec![MappingDefinition {
4641 meta: MappingMeta {
4642 entity: "Prozessdaten".to_string(),
4643 bo4e_type: "Prozessdaten".to_string(),
4644 companion_type: None,
4645 source_group: "SG4".to_string(),
4646 source_path: None,
4647 discriminator: None,
4648 repeat_on_tag: None,
4649 },
4650 fields: tx_fields,
4651 companion_fields: None,
4652 complex_handlers: None,
4653 }];
4654
4655 let msg_engine = MappingEngine::from_definitions(msg_defs);
4656 let tx_engine = MappingEngine::from_definitions(tx_defs);
4657
4658 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
4659
4660 assert!(result.stammdaten["marktteilnehmer"].is_object());
4662 assert_eq!(
4663 result.stammdaten["marktteilnehmer"]["marktrolle"]
4664 .as_str()
4665 .unwrap(),
4666 "MS"
4667 );
4668
4669 assert_eq!(result.transaktionen.len(), 2);
4671 assert_eq!(
4672 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
4673 .as_str()
4674 .unwrap(),
4675 "TX001"
4676 );
4677 assert_eq!(
4678 result.transaktionen[1].stammdaten["prozessdaten"]["vorgangId"]
4679 .as_str()
4680 .unwrap(),
4681 "TX002"
4682 );
4683 }
4684
4685 #[test]
4686 fn test_map_reverse_with_segment_structure_pads_trailing() {
4687 let mut fields = IndexMap::new();
4689 fields.insert(
4690 "sts.0".to_string(),
4691 FieldMapping::Structured(StructuredFieldMapping {
4692 target: String::new(),
4693 transform: None,
4694 when: None,
4695 default: Some("7".to_string()),
4696 enum_map: None,
4697 when_filled: None,
4698 also_target: None,
4699 also_enum_map: None,
4700 }),
4701 );
4702 fields.insert(
4703 "sts.2".to_string(),
4704 FieldMapping::Simple("grund".to_string()),
4705 );
4706
4707 let def = make_def(fields);
4708
4709 let mut counts = std::collections::HashMap::new();
4711 counts.insert("STS".to_string(), 5usize);
4712 let ss = SegmentStructure {
4713 element_counts: counts,
4714 };
4715
4716 let engine = MappingEngine::from_definitions(vec![]).with_segment_structure(ss);
4717
4718 let bo4e = serde_json::json!({ "grund": "E01" });
4719
4720 let instance = engine.map_reverse(&bo4e, &def);
4721 let sts = &instance.segments[0];
4722 assert_eq!(sts.elements.len(), 5);
4725 assert_eq!(sts.elements[0], vec!["7"]);
4726 assert_eq!(sts.elements[1], vec![""]);
4727 assert_eq!(sts.elements[2], vec!["E01"]);
4728 assert_eq!(sts.elements[3], vec![""]);
4729 assert_eq!(sts.elements[4], vec![""]);
4730 }
4731
4732 #[test]
4733 fn test_extract_companion_fields_with_code_enrichment() {
4734 use crate::code_lookup::CodeLookup;
4735 use mig_assembly::assembler::*;
4736
4737 let schema = serde_json::json!({
4738 "fields": {
4739 "sg4": {
4740 "children": {
4741 "sg8_z01": {
4742 "children": {
4743 "sg10": {
4744 "segments": [{
4745 "id": "CCI",
4746 "elements": [{
4747 "index": 2,
4748 "components": [{
4749 "sub_index": 0,
4750 "type": "code",
4751 "codes": [
4752 {"value": "Z15", "name": "Haushaltskunde"},
4753 {"value": "Z18", "name": "Kein Haushaltskunde"}
4754 ]
4755 }]
4756 }]
4757 }],
4758 "source_group": "SG10"
4759 }
4760 },
4761 "segments": [],
4762 "source_group": "SG8"
4763 }
4764 },
4765 "segments": [],
4766 "source_group": "SG4"
4767 }
4768 }
4769 });
4770
4771 let code_lookup = CodeLookup::from_schema_value(&schema);
4772
4773 let tree = AssembledTree {
4774 segments: vec![],
4775 groups: vec![AssembledGroup {
4776 group_id: "SG4".to_string(),
4777 repetitions: vec![AssembledGroupInstance {
4778 segments: vec![],
4779 child_groups: vec![AssembledGroup {
4780 group_id: "SG8".to_string(),
4781 repetitions: vec![AssembledGroupInstance {
4782 segments: vec![],
4783 child_groups: vec![AssembledGroup {
4784 group_id: "SG10".to_string(),
4785 repetitions: vec![AssembledGroupInstance {
4786 segments: vec![AssembledSegment {
4787 tag: "CCI".to_string(),
4788 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
4789 }],
4790 child_groups: vec![],
4791 skipped_segments: vec![],
4792 }],
4793 }],
4794 skipped_segments: vec![],
4795 }],
4796 }],
4797 skipped_segments: vec![],
4798 }],
4799 }],
4800 post_group_start: 0,
4801 inter_group_segments: std::collections::BTreeMap::new(),
4802 };
4803
4804 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4805 companion_fields.insert(
4806 "cci.2".to_string(),
4807 FieldMapping::Simple("haushaltskunde".to_string()),
4808 );
4809
4810 let def = MappingDefinition {
4811 meta: MappingMeta {
4812 entity: "Marktlokation".to_string(),
4813 bo4e_type: "Marktlokation".to_string(),
4814 companion_type: Some("MarktlokationEdifact".to_string()),
4815 source_group: "SG4.SG8.SG10".to_string(),
4816 source_path: Some("sg4.sg8_z01.sg10".to_string()),
4817 discriminator: None,
4818 repeat_on_tag: None,
4819 },
4820 fields: IndexMap::new(),
4821 companion_fields: Some(companion_fields),
4822 complex_handlers: None,
4823 };
4824
4825 let engine_plain = MappingEngine::from_definitions(vec![]);
4827 let bo4e_plain = engine_plain.map_forward(&tree, &def, 0);
4828 assert_eq!(
4829 bo4e_plain["marktlokationEdifact"]["haushaltskunde"].as_str(),
4830 Some("Z15"),
4831 "Without code lookup, should be plain string"
4832 );
4833
4834 let engine_enriched = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
4836 let bo4e_enriched = engine_enriched.map_forward(&tree, &def, 0);
4837 let hk = &bo4e_enriched["marktlokationEdifact"]["haushaltskunde"];
4838 assert_eq!(hk["code"].as_str(), Some("Z15"));
4839 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
4840 assert!(hk.get("enum").is_none());
4842 }
4843
4844 #[test]
4845 fn test_extract_companion_fields_with_enum_enrichment() {
4846 use crate::code_lookup::CodeLookup;
4847 use mig_assembly::assembler::*;
4848
4849 let schema = serde_json::json!({
4851 "fields": {
4852 "sg4": {
4853 "children": {
4854 "sg8_z01": {
4855 "children": {
4856 "sg10": {
4857 "segments": [{
4858 "id": "CCI",
4859 "elements": [{
4860 "index": 2,
4861 "components": [{
4862 "sub_index": 0,
4863 "type": "code",
4864 "codes": [
4865 {"value": "Z15", "name": "Haushaltskunde", "enum": "HAUSHALTSKUNDE"},
4866 {"value": "Z18", "name": "Kein Haushaltskunde", "enum": "KEIN_HAUSHALTSKUNDE"}
4867 ]
4868 }]
4869 }]
4870 }],
4871 "source_group": "SG10"
4872 }
4873 },
4874 "segments": [],
4875 "source_group": "SG8"
4876 }
4877 },
4878 "segments": [],
4879 "source_group": "SG4"
4880 }
4881 }
4882 });
4883
4884 let code_lookup = CodeLookup::from_schema_value(&schema);
4885
4886 let tree = AssembledTree {
4887 segments: vec![],
4888 groups: vec![AssembledGroup {
4889 group_id: "SG4".to_string(),
4890 repetitions: vec![AssembledGroupInstance {
4891 segments: vec![],
4892 child_groups: vec![AssembledGroup {
4893 group_id: "SG8".to_string(),
4894 repetitions: vec![AssembledGroupInstance {
4895 segments: vec![],
4896 child_groups: vec![AssembledGroup {
4897 group_id: "SG10".to_string(),
4898 repetitions: vec![AssembledGroupInstance {
4899 segments: vec![AssembledSegment {
4900 tag: "CCI".to_string(),
4901 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
4902 }],
4903 child_groups: vec![],
4904 skipped_segments: vec![],
4905 }],
4906 }],
4907 skipped_segments: vec![],
4908 }],
4909 }],
4910 skipped_segments: vec![],
4911 }],
4912 }],
4913 post_group_start: 0,
4914 inter_group_segments: std::collections::BTreeMap::new(),
4915 };
4916
4917 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4918 companion_fields.insert(
4919 "cci.2".to_string(),
4920 FieldMapping::Simple("haushaltskunde".to_string()),
4921 );
4922
4923 let def = MappingDefinition {
4924 meta: MappingMeta {
4925 entity: "Marktlokation".to_string(),
4926 bo4e_type: "Marktlokation".to_string(),
4927 companion_type: Some("MarktlokationEdifact".to_string()),
4928 source_group: "SG4.SG8.SG10".to_string(),
4929 source_path: Some("sg4.sg8_z01.sg10".to_string()),
4930 discriminator: None,
4931 repeat_on_tag: None,
4932 },
4933 fields: IndexMap::new(),
4934 companion_fields: Some(companion_fields),
4935 complex_handlers: None,
4936 };
4937
4938 let engine = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
4939 let bo4e = engine.map_forward(&tree, &def, 0);
4940 let hk = &bo4e["marktlokationEdifact"]["haushaltskunde"];
4941 assert_eq!(hk["code"].as_str(), Some("Z15"));
4942 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
4943 assert_eq!(
4944 hk["enum"].as_str(),
4945 Some("HAUSHALTSKUNDE"),
4946 "enum field should be present"
4947 );
4948 }
4949
4950 #[test]
4951 fn test_reverse_mapping_accepts_enriched_with_enum() {
4952 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4954 companion_fields.insert(
4955 "cci.2".to_string(),
4956 FieldMapping::Simple("haushaltskunde".to_string()),
4957 );
4958
4959 let def = MappingDefinition {
4960 meta: MappingMeta {
4961 entity: "Test".to_string(),
4962 bo4e_type: "Test".to_string(),
4963 companion_type: Some("TestEdifact".to_string()),
4964 source_group: "SG4".to_string(),
4965 source_path: None,
4966 discriminator: None,
4967 repeat_on_tag: None,
4968 },
4969 fields: IndexMap::new(),
4970 companion_fields: Some(companion_fields),
4971 complex_handlers: None,
4972 };
4973
4974 let engine = MappingEngine::from_definitions(vec![]);
4975
4976 let bo4e = serde_json::json!({
4977 "testEdifact": {
4978 "haushaltskunde": {
4979 "code": "Z15",
4980 "meaning": "Haushaltskunde",
4981 "enum": "HAUSHALTSKUNDE"
4982 }
4983 }
4984 });
4985 let instance = engine.map_reverse(&bo4e, &def);
4986 assert_eq!(instance.segments[0].elements[2], vec!["Z15"]);
4987 }
4988
4989 #[test]
4990 fn test_reverse_mapping_accepts_enriched_companion() {
4991 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4993 companion_fields.insert(
4994 "cci.2".to_string(),
4995 FieldMapping::Simple("haushaltskunde".to_string()),
4996 );
4997
4998 let def = MappingDefinition {
4999 meta: MappingMeta {
5000 entity: "Test".to_string(),
5001 bo4e_type: "Test".to_string(),
5002 companion_type: Some("TestEdifact".to_string()),
5003 source_group: "SG4".to_string(),
5004 source_path: None,
5005 discriminator: None,
5006 repeat_on_tag: None,
5007 },
5008 fields: IndexMap::new(),
5009 companion_fields: Some(companion_fields),
5010 complex_handlers: None,
5011 };
5012
5013 let engine = MappingEngine::from_definitions(vec![]);
5014
5015 let bo4e_plain = serde_json::json!({
5017 "testEdifact": {
5018 "haushaltskunde": "Z15"
5019 }
5020 });
5021 let instance_plain = engine.map_reverse(&bo4e_plain, &def);
5022 assert_eq!(instance_plain.segments[0].elements[2], vec!["Z15"]);
5023
5024 let bo4e_enriched = serde_json::json!({
5026 "testEdifact": {
5027 "haushaltskunde": {
5028 "code": "Z15",
5029 "meaning": "Haushaltskunde gem. EnWG"
5030 }
5031 }
5032 });
5033 let instance_enriched = engine.map_reverse(&bo4e_enriched, &def);
5034 assert_eq!(instance_enriched.segments[0].elements[2], vec!["Z15"]);
5035 }
5036
5037 #[test]
5038 fn test_resolve_child_relative_with_source_path() {
5039 let mut map: std::collections::HashMap<String, Vec<usize>> =
5040 std::collections::HashMap::new();
5041 map.insert("sg4.sg8_ze1".to_string(), vec![6]);
5042 map.insert("sg4.sg8_z98".to_string(), vec![0]);
5043
5044 assert_eq!(
5046 resolve_child_relative("SG8.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5047 "SG8:6.SG10"
5048 );
5049
5050 assert_eq!(
5052 resolve_child_relative("SG8:3.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5053 "SG8:3.SG10"
5054 );
5055
5056 assert_eq!(
5058 resolve_child_relative("SG8.SG10", Some("sg4.sg8_unknown.sg10"), &map, 0),
5059 "SG8.SG10"
5060 );
5061
5062 assert_eq!(
5064 resolve_child_relative("SG8.SG10", None, &map, 0),
5065 "SG8.SG10"
5066 );
5067
5068 assert_eq!(
5070 resolve_child_relative("SG8.SG9", Some("sg4.sg8_z98.sg9"), &map, 0),
5071 "SG8:0.SG9"
5072 );
5073
5074 map.insert("sg4.sg8_zf3".to_string(), vec![3, 4]);
5076 assert_eq!(
5077 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 0),
5078 "SG8:3.SG10"
5079 );
5080 assert_eq!(
5081 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 1),
5082 "SG8:4.SG10"
5083 );
5084 }
5085
5086 #[test]
5087 fn test_place_in_groups_returns_rep_index() {
5088 let mut groups: Vec<AssembledGroup> = Vec::new();
5089
5090 let instance = AssembledGroupInstance {
5092 segments: vec![],
5093 child_groups: vec![],
5094 skipped_segments: vec![],
5095 };
5096 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 0);
5097
5098 let instance = AssembledGroupInstance {
5100 segments: vec![],
5101 child_groups: vec![],
5102 skipped_segments: vec![],
5103 };
5104 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 1);
5105
5106 let instance = AssembledGroupInstance {
5108 segments: vec![],
5109 child_groups: vec![],
5110 skipped_segments: vec![],
5111 };
5112 assert_eq!(place_in_groups(&mut groups, "SG8:5", instance), 5);
5113 }
5114
5115 #[test]
5116 fn test_resolve_by_source_path() {
5117 use mig_assembly::assembler::*;
5118
5119 let tree = AssembledTree {
5121 segments: vec![],
5122 groups: vec![AssembledGroup {
5123 group_id: "SG4".to_string(),
5124 repetitions: vec![AssembledGroupInstance {
5125 segments: vec![],
5126 child_groups: vec![AssembledGroup {
5127 group_id: "SG8".to_string(),
5128 repetitions: vec![
5129 AssembledGroupInstance {
5130 segments: vec![AssembledSegment {
5131 tag: "SEQ".to_string(),
5132 elements: vec![vec!["Z98".to_string()]],
5133 }],
5134 child_groups: vec![AssembledGroup {
5135 group_id: "SG10".to_string(),
5136 repetitions: vec![AssembledGroupInstance {
5137 segments: vec![AssembledSegment {
5138 tag: "CCI".to_string(),
5139 elements: vec![vec![], vec![], vec!["ZB3".to_string()]],
5140 }],
5141 child_groups: vec![],
5142 skipped_segments: vec![],
5143 }],
5144 }],
5145 skipped_segments: vec![],
5146 },
5147 AssembledGroupInstance {
5148 segments: vec![AssembledSegment {
5149 tag: "SEQ".to_string(),
5150 elements: vec![vec!["ZD7".to_string()]],
5151 }],
5152 child_groups: vec![AssembledGroup {
5153 group_id: "SG10".to_string(),
5154 repetitions: vec![AssembledGroupInstance {
5155 segments: vec![AssembledSegment {
5156 tag: "CCI".to_string(),
5157 elements: vec![vec![], vec![], vec!["ZE6".to_string()]],
5158 }],
5159 child_groups: vec![],
5160 skipped_segments: vec![],
5161 }],
5162 }],
5163 skipped_segments: vec![],
5164 },
5165 ],
5166 }],
5167 skipped_segments: vec![],
5168 }],
5169 }],
5170 post_group_start: 0,
5171 inter_group_segments: std::collections::BTreeMap::new(),
5172 };
5173
5174 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_z98.sg10");
5176 assert!(inst.is_some());
5177 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
5178
5179 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zd7.sg10");
5181 assert!(inst.is_some());
5182 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZE6");
5183
5184 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zzz.sg10");
5186 assert!(inst.is_none());
5187
5188 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8.sg10");
5190 assert!(inst.is_some());
5191 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
5192 }
5193
5194 #[test]
5195 fn test_parse_source_path_part() {
5196 assert_eq!(parse_source_path_part("sg4"), ("sg4", None));
5197 assert_eq!(parse_source_path_part("sg8_z98"), ("sg8", Some("z98")));
5198 assert_eq!(parse_source_path_part("sg10"), ("sg10", None));
5199 assert_eq!(parse_source_path_part("sg12_z04"), ("sg12", Some("z04")));
5200 }
5201
5202 #[test]
5203 fn test_has_source_path_qualifiers() {
5204 assert!(has_source_path_qualifiers("sg4.sg8_z98.sg10"));
5205 assert!(has_source_path_qualifiers("sg4.sg8_ze1.sg9"));
5206 assert!(!has_source_path_qualifiers("sg4.sg6"));
5207 assert!(!has_source_path_qualifiers("sg4.sg8.sg10"));
5208 }
5209
5210 #[test]
5211 fn test_companion_dotted_path_forward() {
5212 use mig_assembly::assembler::*;
5213
5214 let tree = AssembledTree {
5216 segments: vec![],
5217 groups: vec![AssembledGroup {
5218 group_id: "SG4".to_string(),
5219 repetitions: vec![AssembledGroupInstance {
5220 segments: vec![],
5221 child_groups: vec![AssembledGroup {
5222 group_id: "SG8".to_string(),
5223 repetitions: vec![AssembledGroupInstance {
5224 segments: vec![],
5225 child_groups: vec![AssembledGroup {
5226 group_id: "SG10".to_string(),
5227 repetitions: vec![AssembledGroupInstance {
5228 segments: vec![AssembledSegment {
5229 tag: "CCI".to_string(),
5230 elements: vec![
5231 vec!["11XAB-1234".to_string()],
5232 vec!["305".to_string()],
5233 ],
5234 }],
5235 child_groups: vec![],
5236 skipped_segments: vec![],
5237 }],
5238 }],
5239 skipped_segments: vec![],
5240 }],
5241 }],
5242 skipped_segments: vec![],
5243 }],
5244 }],
5245 post_group_start: 0,
5246 inter_group_segments: std::collections::BTreeMap::new(),
5247 };
5248
5249 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5251 companion_fields.insert(
5252 "cci.0".to_string(),
5253 FieldMapping::Simple("bilanzkreis.id".to_string()),
5254 );
5255 companion_fields.insert(
5256 "cci.1".to_string(),
5257 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
5258 );
5259
5260 let def = MappingDefinition {
5261 meta: MappingMeta {
5262 entity: "Test".to_string(),
5263 bo4e_type: "Test".to_string(),
5264 companion_type: Some("TestEdifact".to_string()),
5265 source_group: "SG4.SG8.SG10".to_string(),
5266 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5267 discriminator: None,
5268 repeat_on_tag: None,
5269 },
5270 fields: IndexMap::new(),
5271 companion_fields: Some(companion_fields),
5272 complex_handlers: None,
5273 };
5274
5275 let engine = MappingEngine::from_definitions(vec![]);
5276 let bo4e = engine.map_forward(&tree, &def, 0);
5277
5278 let companion = &bo4e["testEdifact"];
5280 assert!(
5281 companion.is_object(),
5282 "testEdifact should be an object, got: {companion}"
5283 );
5284 let bilanzkreis = &companion["bilanzkreis"];
5285 assert!(
5286 bilanzkreis.is_object(),
5287 "bilanzkreis should be a nested object, got: {bilanzkreis}"
5288 );
5289 assert_eq!(
5290 bilanzkreis["id"].as_str(),
5291 Some("11XAB-1234"),
5292 "bilanzkreis.id should be 11XAB-1234"
5293 );
5294 assert_eq!(
5295 bilanzkreis["codelist"].as_str(),
5296 Some("305"),
5297 "bilanzkreis.codelist should be 305"
5298 );
5299 }
5300
5301 #[test]
5302 fn test_companion_dotted_path_reverse() {
5303 let engine = MappingEngine::from_definitions(vec![]);
5305
5306 let companion_value = serde_json::json!({
5307 "bilanzkreis": {
5308 "id": "11XAB-1234",
5309 "codelist": "305"
5310 }
5311 });
5312
5313 assert_eq!(
5314 engine.populate_field(&companion_value, "bilanzkreis.id"),
5315 Some("11XAB-1234".to_string()),
5316 "dotted path bilanzkreis.id should resolve"
5317 );
5318 assert_eq!(
5319 engine.populate_field(&companion_value, "bilanzkreis.codelist"),
5320 Some("305".to_string()),
5321 "dotted path bilanzkreis.codelist should resolve"
5322 );
5323
5324 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5326 companion_fields.insert(
5327 "cci.0".to_string(),
5328 FieldMapping::Simple("bilanzkreis.id".to_string()),
5329 );
5330 companion_fields.insert(
5331 "cci.1".to_string(),
5332 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
5333 );
5334
5335 let def = MappingDefinition {
5336 meta: MappingMeta {
5337 entity: "Test".to_string(),
5338 bo4e_type: "Test".to_string(),
5339 companion_type: Some("TestEdifact".to_string()),
5340 source_group: "SG4.SG8.SG10".to_string(),
5341 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5342 discriminator: None,
5343 repeat_on_tag: None,
5344 },
5345 fields: IndexMap::new(),
5346 companion_fields: Some(companion_fields),
5347 complex_handlers: None,
5348 };
5349
5350 let bo4e = serde_json::json!({
5351 "testEdifact": {
5352 "bilanzkreis": {
5353 "id": "11XAB-1234",
5354 "codelist": "305"
5355 }
5356 }
5357 });
5358
5359 let instance = engine.map_reverse(&bo4e, &def);
5360 assert_eq!(instance.segments.len(), 1, "should produce one CCI segment");
5361 let cci = &instance.segments[0];
5362 assert_eq!(cci.tag, "CCI");
5363 assert_eq!(
5364 cci.elements[0],
5365 vec!["11XAB-1234"],
5366 "element 0 should contain bilanzkreis.id"
5367 );
5368 assert_eq!(
5369 cci.elements[1],
5370 vec!["305"],
5371 "element 1 should contain bilanzkreis.codelist"
5372 );
5373 }
5374
5375 #[test]
5376 fn test_when_filled_injects_when_field_present() {
5377 let toml_str = r#"
5378[meta]
5379entity = "Test"
5380bo4e_type = "Test"
5381companion_type = "TestEdifact"
5382source_group = "SG4.SG8.SG10"
5383
5384[fields]
5385
5386[companion_fields]
5387"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmalCode"] }
5388"cav.0.0" = "merkmalCode"
5389"#;
5390 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5391
5392 let bo4e_with = serde_json::json!({
5394 "testEdifact": { "merkmalCode": "ZA7" }
5395 });
5396 let engine = MappingEngine::new_empty();
5397 let instance = engine.map_reverse(&bo4e_with, &def);
5398 let cci = instance
5399 .segments
5400 .iter()
5401 .find(|s| s.tag == "CCI")
5402 .expect("CCI should exist");
5403 assert_eq!(cci.elements[0][0], "Z83");
5404
5405 let bo4e_without = serde_json::json!({
5407 "testEdifact": {}
5408 });
5409 let instance2 = engine.map_reverse(&bo4e_without, &def);
5410 let cci2 = instance2.segments.iter().find(|s| s.tag == "CCI");
5411 assert!(
5412 cci2.is_none(),
5413 "CCI should not be emitted when merkmalCode is absent"
5414 );
5415 }
5416
5417 #[test]
5418 fn test_when_filled_checks_core_and_companion() {
5419 let toml_str = r#"
5420[meta]
5421entity = "Test"
5422bo4e_type = "Test"
5423companion_type = "TestEdifact"
5424source_group = "SG4.SG5"
5425
5426[fields]
5427"loc.1.0" = "marktlokationsId"
5428
5429[companion_fields]
5430"loc.0.0" = { target = "", default = "Z16", when_filled = ["marktlokationsId"] }
5431"#;
5432 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5433
5434 let bo4e_with = serde_json::json!({
5436 "marktlokationsId": "51234567890"
5437 });
5438 let engine = MappingEngine::new_empty();
5439 let instance = engine.map_reverse(&bo4e_with, &def);
5440 let loc = instance
5441 .segments
5442 .iter()
5443 .find(|s| s.tag == "LOC")
5444 .expect("LOC should exist");
5445 assert_eq!(loc.elements[0][0], "Z16");
5446 assert_eq!(loc.elements[1][0], "51234567890");
5447
5448 let bo4e_without = serde_json::json!({});
5450 let instance2 = engine.map_reverse(&bo4e_without, &def);
5451 let loc2 = instance2.segments.iter().find(|s| s.tag == "LOC");
5452 assert!(loc2.is_none());
5453 }
5454
5455 #[test]
5456 fn test_extract_all_from_instance_collects_all_qualifier_matches() {
5457 use mig_assembly::assembler::*;
5458
5459 let instance = AssembledGroupInstance {
5461 segments: vec![
5462 AssembledSegment {
5463 tag: "SEQ".to_string(),
5464 elements: vec![vec!["ZD6".to_string()]],
5465 },
5466 AssembledSegment {
5467 tag: "RFF".to_string(),
5468 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
5469 },
5470 AssembledSegment {
5471 tag: "RFF".to_string(),
5472 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
5473 },
5474 AssembledSegment {
5475 tag: "RFF".to_string(),
5476 elements: vec![vec!["Z34".to_string(), "REF_C".to_string()]],
5477 },
5478 AssembledSegment {
5479 tag: "RFF".to_string(),
5480 elements: vec![vec!["Z35".to_string(), "OTHER".to_string()]],
5481 },
5482 ],
5483 child_groups: vec![],
5484 skipped_segments: vec![],
5485 };
5486
5487 let all = MappingEngine::extract_all_from_instance(&instance, "rff[Z34,*].0.1");
5489 assert_eq!(all, vec!["REF_A", "REF_B", "REF_C"]);
5490
5491 let single = MappingEngine::extract_from_instance(&instance, "rff[Z34].0.1");
5493 assert_eq!(single, Some("REF_A".to_string()));
5494
5495 let second = MappingEngine::extract_from_instance(&instance, "rff[Z34,1].0.1");
5496 assert_eq!(second, Some("REF_B".to_string()));
5497 }
5498
5499 #[test]
5500 fn test_forward_wildcard_collect_produces_json_array() {
5501 use mig_assembly::assembler::*;
5502
5503 let instance = AssembledGroupInstance {
5504 segments: vec![
5505 AssembledSegment {
5506 tag: "SEQ".to_string(),
5507 elements: vec![vec!["ZD6".to_string()]],
5508 },
5509 AssembledSegment {
5510 tag: "RFF".to_string(),
5511 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
5512 },
5513 AssembledSegment {
5514 tag: "RFF".to_string(),
5515 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
5516 },
5517 ],
5518 child_groups: vec![],
5519 skipped_segments: vec![],
5520 };
5521
5522 let toml_str = r#"
5523[meta]
5524entity = "Test"
5525bo4e_type = "Test"
5526companion_type = "TestEdifact"
5527source_group = "SG4.SG8"
5528
5529[fields]
5530
5531[companion_fields]
5532"rff[Z34,*].0.1" = "messlokationsIdRefs"
5533"#;
5534 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5535 let engine = MappingEngine::new_empty();
5536
5537 let mut result = serde_json::Map::new();
5538 engine.extract_companion_fields(&instance, &def, &mut result, false);
5539
5540 let companion = result.get("testEdifact").unwrap().as_object().unwrap();
5541 let refs = companion
5542 .get("messlokationsIdRefs")
5543 .unwrap()
5544 .as_array()
5545 .unwrap();
5546 assert_eq!(refs.len(), 2);
5547 assert_eq!(refs[0].as_str().unwrap(), "REF_A");
5548 assert_eq!(refs[1].as_str().unwrap(), "REF_B");
5549 }
5550
5551 #[test]
5552 fn test_reverse_json_array_produces_multiple_segments() {
5553 let toml_str = r#"
5554[meta]
5555entity = "Test"
5556bo4e_type = "Test"
5557companion_type = "TestEdifact"
5558source_group = "SG4.SG8"
5559
5560[fields]
5561
5562[companion_fields]
5563"seq.0.0" = { target = "", default = "ZD6" }
5564"rff[Z34,*].0.1" = "messlokationsIdRefs"
5565"#;
5566 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5567 let engine = MappingEngine::new_empty();
5568
5569 let bo4e = serde_json::json!({
5570 "testEdifact": {
5571 "messlokationsIdRefs": ["REF_A", "REF_B", "REF_C"]
5572 }
5573 });
5574
5575 let instance = engine.map_reverse(&bo4e, &def);
5576
5577 let rff_segs: Vec<_> = instance
5579 .segments
5580 .iter()
5581 .filter(|s| s.tag == "RFF")
5582 .collect();
5583 assert_eq!(rff_segs.len(), 3);
5584 assert_eq!(rff_segs[0].elements[0][0], "Z34");
5585 assert_eq!(rff_segs[0].elements[0][1], "REF_A");
5586 assert_eq!(rff_segs[1].elements[0][0], "Z34");
5587 assert_eq!(rff_segs[1].elements[0][1], "REF_B");
5588 assert_eq!(rff_segs[2].elements[0][0], "Z34");
5589 assert_eq!(rff_segs[2].elements[0][1], "REF_C");
5590 }
5591
5592 #[test]
5593 fn test_when_filled_dotted_path() {
5594 let toml_str = r#"
5595[meta]
5596entity = "Test"
5597bo4e_type = "Test"
5598companion_type = "TestEdifact"
5599source_group = "SG4.SG8.SG10"
5600
5601[fields]
5602
5603[companion_fields]
5604"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmal.code"] }
5605"cav.0.0" = "merkmal.code"
5606"#;
5607 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5608
5609 let bo4e = serde_json::json!({
5610 "testEdifact": { "merkmal": { "code": "ZA7" } }
5611 });
5612 let engine = MappingEngine::new_empty();
5613 let instance = engine.map_reverse(&bo4e, &def);
5614 let cci = instance
5615 .segments
5616 .iter()
5617 .find(|s| s.tag == "CCI")
5618 .expect("CCI should exist");
5619 assert_eq!(cci.elements[0][0], "Z83");
5620 }
5621
5622 #[test]
5623 fn test_also_target_forward_extracts_both_fields() {
5624 use mig_assembly::assembler::*;
5625
5626 let instance = AssembledGroupInstance {
5627 segments: vec![AssembledSegment {
5628 tag: "NAD".to_string(),
5629 elements: vec![vec!["Z47".to_string()], vec!["12345".to_string()]],
5630 }],
5631 child_groups: vec![],
5632 skipped_segments: vec![],
5633 };
5634
5635 let toml_str = r#"
5636[meta]
5637entity = "Geschaeftspartner"
5638bo4e_type = "Geschaeftspartner"
5639companion_type = "GeschaeftspartnerEdifact"
5640source_group = "SG4.SG12"
5641
5642[fields]
5643"nad.1.0" = "identifikation"
5644
5645[companion_fields."nad.0.0"]
5646target = "partnerrolle"
5647enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
5648also_target = "datenqualitaet"
5649also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
5650"#;
5651 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5652 let engine = MappingEngine::new_empty();
5653
5654 let mut result = serde_json::Map::new();
5655 engine.extract_companion_fields(&instance, &def, &mut result, false);
5656
5657 let companion = result
5658 .get("geschaeftspartnerEdifact")
5659 .unwrap()
5660 .as_object()
5661 .unwrap();
5662 assert_eq!(
5663 companion.get("partnerrolle").unwrap().as_str().unwrap(),
5664 "kundeDesLf"
5665 );
5666 assert_eq!(
5667 companion.get("datenqualitaet").unwrap().as_str().unwrap(),
5668 "erwartet"
5669 );
5670 }
5671
5672 #[test]
5673 fn test_also_target_reverse_joint_lookup() {
5674 let toml_str = r#"
5675[meta]
5676entity = "Geschaeftspartner"
5677bo4e_type = "Geschaeftspartner"
5678companion_type = "GeschaeftspartnerEdifact"
5679source_group = "SG4.SG12"
5680
5681[fields]
5682
5683[companion_fields."nad.0.0"]
5684target = "partnerrolle"
5685enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
5686also_target = "datenqualitaet"
5687also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
5688"#;
5689 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5690 let engine = MappingEngine::new_empty();
5691
5692 let bo4e = serde_json::json!({
5694 "geschaeftspartnerEdifact": {
5695 "partnerrolle": "kundeDesLf",
5696 "datenqualitaet": "erwartet"
5697 }
5698 });
5699 let instance = engine.map_reverse(&bo4e, &def);
5700 let nad = instance
5701 .segments
5702 .iter()
5703 .find(|s| s.tag == "NAD")
5704 .expect("NAD");
5705 assert_eq!(nad.elements[0][0], "Z47");
5706
5707 let bo4e2 = serde_json::json!({
5709 "geschaeftspartnerEdifact": {
5710 "partnerrolle": "kundeDesNb",
5711 "datenqualitaet": "imSystemVorhanden"
5712 }
5713 });
5714 let instance2 = engine.map_reverse(&bo4e2, &def);
5715 let nad2 = instance2
5716 .segments
5717 .iter()
5718 .find(|s| s.tag == "NAD")
5719 .expect("NAD");
5720 assert_eq!(nad2.elements[0][0], "Z52");
5721 }
5722
5723 #[test]
5724 fn test_also_target_mixed_codes_unpaired_skips_datenqualitaet() {
5725 use mig_assembly::assembler::*;
5726
5727 let toml_str = r#"
5729[meta]
5730entity = "Geschaeftspartner"
5731bo4e_type = "Geschaeftspartner"
5732companion_type = "GeschaeftspartnerEdifact"
5733source_group = "SG4.SG12"
5734
5735[fields]
5736
5737[companion_fields."nad.0.0"]
5738target = "partnerrolle"
5739enum_map = { "Z09" = "kundeDesLf", "Z47" = "kundeDesLf", "Z48" = "kundeDesLf" }
5740also_target = "datenqualitaet"
5741also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden" }
5742"#;
5743 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5744 let engine = MappingEngine::new_empty();
5745
5746 let instance_z09 = AssembledGroupInstance {
5748 segments: vec![AssembledSegment {
5749 tag: "NAD".to_string(),
5750 elements: vec![vec!["Z09".to_string()]],
5751 }],
5752 child_groups: vec![],
5753 skipped_segments: vec![],
5754 };
5755 let mut result = serde_json::Map::new();
5756 engine.extract_companion_fields(&instance_z09, &def, &mut result, false);
5757 let comp = result
5758 .get("geschaeftspartnerEdifact")
5759 .unwrap()
5760 .as_object()
5761 .unwrap();
5762 assert_eq!(
5763 comp.get("partnerrolle").unwrap().as_str().unwrap(),
5764 "kundeDesLf"
5765 );
5766 assert!(
5767 comp.get("datenqualitaet").is_none(),
5768 "Z09 should not set datenqualitaet"
5769 );
5770
5771 let bo4e = serde_json::json!({
5773 "geschaeftspartnerEdifact": { "partnerrolle": "kundeDesLf" }
5774 });
5775 let instance = engine.map_reverse(&bo4e, &def);
5776 let nad = instance
5777 .segments
5778 .iter()
5779 .find(|s| s.tag == "NAD")
5780 .expect("NAD");
5781 assert_eq!(nad.elements[0][0], "Z09");
5782
5783 let bo4e2 = serde_json::json!({
5785 "geschaeftspartnerEdifact": {
5786 "partnerrolle": "kundeDesLf",
5787 "datenqualitaet": "erwartet"
5788 }
5789 });
5790 let instance2 = engine.map_reverse(&bo4e2, &def);
5791 let nad2 = instance2
5792 .segments
5793 .iter()
5794 .find(|s| s.tag == "NAD")
5795 .expect("NAD");
5796 assert_eq!(nad2.elements[0][0], "Z47");
5797 }
5798}