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!(
4270 result.transaktionen[0].stammdaten["prozessdaten"]["marktlokation"]
4271 ["marktlokationsId"]
4272 .as_str()
4273 .unwrap(),
4274 "DE000111222333"
4275 );
4276 }
4277
4278 #[test]
4279 fn test_map_reverse_pads_intermediate_empty_elements() {
4280 let mut fields = IndexMap::new();
4282 fields.insert(
4283 "nad.0".to_string(),
4284 FieldMapping::Structured(StructuredFieldMapping {
4285 target: String::new(),
4286 transform: None,
4287 when: None,
4288 default: Some("Z09".to_string()),
4289 enum_map: None,
4290 when_filled: None,
4291 also_target: None,
4292 also_enum_map: None,
4293 }),
4294 );
4295 fields.insert(
4296 "nad.3.0".to_string(),
4297 FieldMapping::Simple("name".to_string()),
4298 );
4299 fields.insert(
4300 "nad.3.1".to_string(),
4301 FieldMapping::Simple("vorname".to_string()),
4302 );
4303
4304 let def = make_def(fields);
4305 let engine = MappingEngine::from_definitions(vec![]);
4306
4307 let bo4e = serde_json::json!({
4308 "name": "Muster",
4309 "vorname": "Max"
4310 });
4311
4312 let instance = engine.map_reverse(&bo4e, &def);
4313 assert_eq!(instance.segments.len(), 1);
4314
4315 let nad = &instance.segments[0];
4316 assert_eq!(nad.tag, "NAD");
4317 assert_eq!(nad.elements.len(), 4);
4318 assert_eq!(nad.elements[0], vec!["Z09"]);
4319 assert_eq!(nad.elements[1], vec![""]);
4321 assert_eq!(nad.elements[2], vec![""]);
4322 assert_eq!(nad.elements[3][0], "Muster");
4323 assert_eq!(nad.elements[3][1], "Max");
4324 }
4325
4326 #[test]
4327 fn test_map_reverse_no_padding_when_contiguous() {
4328 let mut fields = IndexMap::new();
4330 fields.insert(
4331 "dtm.0.0".to_string(),
4332 FieldMapping::Structured(StructuredFieldMapping {
4333 target: String::new(),
4334 transform: None,
4335 when: None,
4336 default: Some("92".to_string()),
4337 enum_map: None,
4338 when_filled: None,
4339 also_target: None,
4340 also_enum_map: None,
4341 }),
4342 );
4343 fields.insert(
4344 "dtm.0.1".to_string(),
4345 FieldMapping::Simple("value".to_string()),
4346 );
4347 fields.insert(
4348 "dtm.0.2".to_string(),
4349 FieldMapping::Structured(StructuredFieldMapping {
4350 target: String::new(),
4351 transform: None,
4352 when: None,
4353 default: Some("303".to_string()),
4354 enum_map: None,
4355 when_filled: None,
4356 also_target: None,
4357 also_enum_map: None,
4358 }),
4359 );
4360
4361 let def = make_def(fields);
4362 let engine = MappingEngine::from_definitions(vec![]);
4363
4364 let bo4e = serde_json::json!({ "value": "20250531" });
4365
4366 let instance = engine.map_reverse(&bo4e, &def);
4367 let dtm = &instance.segments[0];
4368 assert_eq!(dtm.elements.len(), 1);
4370 assert_eq!(dtm.elements[0], vec!["92", "20250531", "303"]);
4371 }
4372
4373 #[test]
4374 fn test_map_message_level_extracts_sg2_only() {
4375 use mig_assembly::assembler::*;
4376
4377 let tree = AssembledTree {
4379 segments: vec![
4380 AssembledSegment {
4381 tag: "UNH".to_string(),
4382 elements: vec![vec!["001".to_string()]],
4383 },
4384 AssembledSegment {
4385 tag: "BGM".to_string(),
4386 elements: vec![vec!["E01".to_string()]],
4387 },
4388 ],
4389 groups: vec![
4390 AssembledGroup {
4391 group_id: "SG2".to_string(),
4392 repetitions: vec![AssembledGroupInstance {
4393 segments: vec![AssembledSegment {
4394 tag: "NAD".to_string(),
4395 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4396 }],
4397 child_groups: vec![],
4398 skipped_segments: vec![],
4399 }],
4400 },
4401 AssembledGroup {
4402 group_id: "SG4".to_string(),
4403 repetitions: vec![AssembledGroupInstance {
4404 segments: vec![AssembledSegment {
4405 tag: "IDE".to_string(),
4406 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4407 }],
4408 child_groups: vec![],
4409 skipped_segments: vec![],
4410 }],
4411 },
4412 ],
4413 post_group_start: 2,
4414 inter_group_segments: std::collections::BTreeMap::new(),
4415 };
4416
4417 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4419 msg_fields.insert(
4420 "nad.0".to_string(),
4421 FieldMapping::Simple("marktrolle".to_string()),
4422 );
4423 msg_fields.insert(
4424 "nad.1".to_string(),
4425 FieldMapping::Simple("rollencodenummer".to_string()),
4426 );
4427 let msg_def = MappingDefinition {
4428 meta: MappingMeta {
4429 entity: "Marktteilnehmer".to_string(),
4430 bo4e_type: "Marktteilnehmer".to_string(),
4431 companion_type: None,
4432 source_group: "SG2".to_string(),
4433 source_path: None,
4434 discriminator: None,
4435 repeat_on_tag: None,
4436 },
4437 fields: msg_fields,
4438 companion_fields: None,
4439 complex_handlers: None,
4440 };
4441
4442 let engine = MappingEngine::from_definitions(vec![msg_def.clone()]);
4443 let result = engine.map_all_forward(&tree);
4444
4445 assert!(result.get("marktteilnehmer").is_some());
4447 let mt = &result["marktteilnehmer"];
4448 assert_eq!(mt["marktrolle"].as_str().unwrap(), "MS");
4449 assert_eq!(mt["rollencodenummer"].as_str().unwrap(), "9900123");
4450 }
4451
4452 #[test]
4453 fn test_map_transaction_scoped_to_sg4_instance() {
4454 use mig_assembly::assembler::*;
4455
4456 let tree = AssembledTree {
4458 segments: vec![
4459 AssembledSegment {
4460 tag: "UNH".to_string(),
4461 elements: vec![vec!["001".to_string()]],
4462 },
4463 AssembledSegment {
4464 tag: "BGM".to_string(),
4465 elements: vec![vec!["E01".to_string()]],
4466 },
4467 ],
4468 groups: vec![AssembledGroup {
4469 group_id: "SG4".to_string(),
4470 repetitions: vec![AssembledGroupInstance {
4471 segments: vec![AssembledSegment {
4472 tag: "IDE".to_string(),
4473 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4474 }],
4475 child_groups: vec![AssembledGroup {
4476 group_id: "SG5".to_string(),
4477 repetitions: vec![AssembledGroupInstance {
4478 segments: vec![AssembledSegment {
4479 tag: "LOC".to_string(),
4480 elements: vec![
4481 vec!["Z16".to_string()],
4482 vec!["DE000111222333".to_string()],
4483 ],
4484 }],
4485 child_groups: vec![],
4486 skipped_segments: vec![],
4487 }],
4488 }],
4489 skipped_segments: vec![],
4490 }],
4491 }],
4492 post_group_start: 2,
4493 inter_group_segments: std::collections::BTreeMap::new(),
4494 };
4495
4496 let mut proz_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4498 proz_fields.insert(
4499 "ide.1".to_string(),
4500 FieldMapping::Simple("vorgangId".to_string()),
4501 );
4502 let proz_def = MappingDefinition {
4503 meta: MappingMeta {
4504 entity: "Prozessdaten".to_string(),
4505 bo4e_type: "Prozessdaten".to_string(),
4506 companion_type: None,
4507 source_group: "".to_string(), source_path: None,
4509 discriminator: None,
4510 repeat_on_tag: None,
4511 },
4512 fields: proz_fields,
4513 companion_fields: None,
4514 complex_handlers: None,
4515 };
4516
4517 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4518 malo_fields.insert(
4519 "loc.1".to_string(),
4520 FieldMapping::Simple("marktlokationsId".to_string()),
4521 );
4522 let malo_def = MappingDefinition {
4523 meta: MappingMeta {
4524 entity: "Marktlokation".to_string(),
4525 bo4e_type: "Marktlokation".to_string(),
4526 companion_type: None,
4527 source_group: "SG5".to_string(), source_path: None,
4529 discriminator: None,
4530 repeat_on_tag: None,
4531 },
4532 fields: malo_fields,
4533 companion_fields: None,
4534 complex_handlers: None,
4535 };
4536
4537 let tx_engine = MappingEngine::from_definitions(vec![proz_def, malo_def]);
4538
4539 let sg4 = &tree.groups[0]; let sg4_instance = &sg4.repetitions[0];
4542 let sub_tree = sg4_instance.as_assembled_tree();
4543
4544 let result = tx_engine.map_all_forward(&sub_tree);
4545
4546 assert_eq!(
4548 result["prozessdaten"]["vorgangId"].as_str().unwrap(),
4549 "TX001"
4550 );
4551
4552 assert_eq!(
4554 result["marktlokation"]["marktlokationsId"]
4555 .as_str()
4556 .unwrap(),
4557 "DE000111222333"
4558 );
4559 }
4560
4561 #[test]
4562 fn test_map_interchange_produces_full_hierarchy() {
4563 use mig_assembly::assembler::*;
4564
4565 let tree = AssembledTree {
4567 segments: vec![
4568 AssembledSegment {
4569 tag: "UNH".to_string(),
4570 elements: vec![vec!["001".to_string()]],
4571 },
4572 AssembledSegment {
4573 tag: "BGM".to_string(),
4574 elements: vec![vec!["E01".to_string()]],
4575 },
4576 ],
4577 groups: vec![
4578 AssembledGroup {
4579 group_id: "SG2".to_string(),
4580 repetitions: vec![AssembledGroupInstance {
4581 segments: vec![AssembledSegment {
4582 tag: "NAD".to_string(),
4583 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4584 }],
4585 child_groups: vec![],
4586 skipped_segments: vec![],
4587 }],
4588 },
4589 AssembledGroup {
4590 group_id: "SG4".to_string(),
4591 repetitions: vec![
4592 AssembledGroupInstance {
4593 segments: vec![AssembledSegment {
4594 tag: "IDE".to_string(),
4595 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4596 }],
4597 child_groups: vec![],
4598 skipped_segments: vec![],
4599 },
4600 AssembledGroupInstance {
4601 segments: vec![AssembledSegment {
4602 tag: "IDE".to_string(),
4603 elements: vec![vec!["24".to_string()], vec!["TX002".to_string()]],
4604 }],
4605 child_groups: vec![],
4606 skipped_segments: vec![],
4607 },
4608 ],
4609 },
4610 ],
4611 post_group_start: 2,
4612 inter_group_segments: std::collections::BTreeMap::new(),
4613 };
4614
4615 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4617 msg_fields.insert(
4618 "nad.0".to_string(),
4619 FieldMapping::Simple("marktrolle".to_string()),
4620 );
4621 let msg_defs = vec![MappingDefinition {
4622 meta: MappingMeta {
4623 entity: "Marktteilnehmer".to_string(),
4624 bo4e_type: "Marktteilnehmer".to_string(),
4625 companion_type: None,
4626 source_group: "SG2".to_string(),
4627 source_path: None,
4628 discriminator: None,
4629 repeat_on_tag: None,
4630 },
4631 fields: msg_fields,
4632 companion_fields: None,
4633 complex_handlers: None,
4634 }];
4635
4636 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4638 tx_fields.insert(
4639 "ide.1".to_string(),
4640 FieldMapping::Simple("vorgangId".to_string()),
4641 );
4642 let tx_defs = vec![MappingDefinition {
4643 meta: MappingMeta {
4644 entity: "Prozessdaten".to_string(),
4645 bo4e_type: "Prozessdaten".to_string(),
4646 companion_type: None,
4647 source_group: "SG4".to_string(),
4648 source_path: None,
4649 discriminator: None,
4650 repeat_on_tag: None,
4651 },
4652 fields: tx_fields,
4653 companion_fields: None,
4654 complex_handlers: None,
4655 }];
4656
4657 let msg_engine = MappingEngine::from_definitions(msg_defs);
4658 let tx_engine = MappingEngine::from_definitions(tx_defs);
4659
4660 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
4661
4662 assert!(result.stammdaten["marktteilnehmer"].is_object());
4664 assert_eq!(
4665 result.stammdaten["marktteilnehmer"]["marktrolle"]
4666 .as_str()
4667 .unwrap(),
4668 "MS"
4669 );
4670
4671 assert_eq!(result.transaktionen.len(), 2);
4673 assert_eq!(
4674 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
4675 .as_str()
4676 .unwrap(),
4677 "TX001"
4678 );
4679 assert_eq!(
4680 result.transaktionen[1].stammdaten["prozessdaten"]["vorgangId"]
4681 .as_str()
4682 .unwrap(),
4683 "TX002"
4684 );
4685 }
4686
4687 #[test]
4688 fn test_map_reverse_with_segment_structure_pads_trailing() {
4689 let mut fields = IndexMap::new();
4691 fields.insert(
4692 "sts.0".to_string(),
4693 FieldMapping::Structured(StructuredFieldMapping {
4694 target: String::new(),
4695 transform: None,
4696 when: None,
4697 default: Some("7".to_string()),
4698 enum_map: None,
4699 when_filled: None,
4700 also_target: None,
4701 also_enum_map: None,
4702 }),
4703 );
4704 fields.insert(
4705 "sts.2".to_string(),
4706 FieldMapping::Simple("grund".to_string()),
4707 );
4708
4709 let def = make_def(fields);
4710
4711 let mut counts = std::collections::HashMap::new();
4713 counts.insert("STS".to_string(), 5usize);
4714 let ss = SegmentStructure {
4715 element_counts: counts,
4716 };
4717
4718 let engine = MappingEngine::from_definitions(vec![]).with_segment_structure(ss);
4719
4720 let bo4e = serde_json::json!({ "grund": "E01" });
4721
4722 let instance = engine.map_reverse(&bo4e, &def);
4723 let sts = &instance.segments[0];
4724 assert_eq!(sts.elements.len(), 5);
4727 assert_eq!(sts.elements[0], vec!["7"]);
4728 assert_eq!(sts.elements[1], vec![""]);
4729 assert_eq!(sts.elements[2], vec!["E01"]);
4730 assert_eq!(sts.elements[3], vec![""]);
4731 assert_eq!(sts.elements[4], vec![""]);
4732 }
4733
4734 #[test]
4735 fn test_extract_companion_fields_with_code_enrichment() {
4736 use crate::code_lookup::CodeLookup;
4737 use mig_assembly::assembler::*;
4738
4739 let schema = serde_json::json!({
4740 "fields": {
4741 "sg4": {
4742 "children": {
4743 "sg8_z01": {
4744 "children": {
4745 "sg10": {
4746 "segments": [{
4747 "id": "CCI",
4748 "elements": [{
4749 "index": 2,
4750 "components": [{
4751 "sub_index": 0,
4752 "type": "code",
4753 "codes": [
4754 {"value": "Z15", "name": "Haushaltskunde"},
4755 {"value": "Z18", "name": "Kein Haushaltskunde"}
4756 ]
4757 }]
4758 }]
4759 }],
4760 "source_group": "SG10"
4761 }
4762 },
4763 "segments": [],
4764 "source_group": "SG8"
4765 }
4766 },
4767 "segments": [],
4768 "source_group": "SG4"
4769 }
4770 }
4771 });
4772
4773 let code_lookup = CodeLookup::from_schema_value(&schema);
4774
4775 let tree = AssembledTree {
4776 segments: vec![],
4777 groups: vec![AssembledGroup {
4778 group_id: "SG4".to_string(),
4779 repetitions: vec![AssembledGroupInstance {
4780 segments: vec![],
4781 child_groups: vec![AssembledGroup {
4782 group_id: "SG8".to_string(),
4783 repetitions: vec![AssembledGroupInstance {
4784 segments: vec![],
4785 child_groups: vec![AssembledGroup {
4786 group_id: "SG10".to_string(),
4787 repetitions: vec![AssembledGroupInstance {
4788 segments: vec![AssembledSegment {
4789 tag: "CCI".to_string(),
4790 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
4791 }],
4792 child_groups: vec![],
4793 skipped_segments: vec![],
4794 }],
4795 }],
4796 skipped_segments: vec![],
4797 }],
4798 }],
4799 skipped_segments: vec![],
4800 }],
4801 }],
4802 post_group_start: 0,
4803 inter_group_segments: std::collections::BTreeMap::new(),
4804 };
4805
4806 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4807 companion_fields.insert(
4808 "cci.2".to_string(),
4809 FieldMapping::Simple("haushaltskunde".to_string()),
4810 );
4811
4812 let def = MappingDefinition {
4813 meta: MappingMeta {
4814 entity: "Marktlokation".to_string(),
4815 bo4e_type: "Marktlokation".to_string(),
4816 companion_type: Some("MarktlokationEdifact".to_string()),
4817 source_group: "SG4.SG8.SG10".to_string(),
4818 source_path: Some("sg4.sg8_z01.sg10".to_string()),
4819 discriminator: None,
4820 repeat_on_tag: None,
4821 },
4822 fields: IndexMap::new(),
4823 companion_fields: Some(companion_fields),
4824 complex_handlers: None,
4825 };
4826
4827 let engine_plain = MappingEngine::from_definitions(vec![]);
4829 let bo4e_plain = engine_plain.map_forward(&tree, &def, 0);
4830 assert_eq!(
4831 bo4e_plain["marktlokationEdifact"]["haushaltskunde"].as_str(),
4832 Some("Z15"),
4833 "Without code lookup, should be plain string"
4834 );
4835
4836 let engine_enriched = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
4838 let bo4e_enriched = engine_enriched.map_forward(&tree, &def, 0);
4839 let hk = &bo4e_enriched["marktlokationEdifact"]["haushaltskunde"];
4840 assert_eq!(hk["code"].as_str(), Some("Z15"));
4841 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
4842 assert!(hk.get("enum").is_none());
4844 }
4845
4846 #[test]
4847 fn test_extract_companion_fields_with_enum_enrichment() {
4848 use crate::code_lookup::CodeLookup;
4849 use mig_assembly::assembler::*;
4850
4851 let schema = serde_json::json!({
4853 "fields": {
4854 "sg4": {
4855 "children": {
4856 "sg8_z01": {
4857 "children": {
4858 "sg10": {
4859 "segments": [{
4860 "id": "CCI",
4861 "elements": [{
4862 "index": 2,
4863 "components": [{
4864 "sub_index": 0,
4865 "type": "code",
4866 "codes": [
4867 {"value": "Z15", "name": "Haushaltskunde", "enum": "HAUSHALTSKUNDE"},
4868 {"value": "Z18", "name": "Kein Haushaltskunde", "enum": "KEIN_HAUSHALTSKUNDE"}
4869 ]
4870 }]
4871 }]
4872 }],
4873 "source_group": "SG10"
4874 }
4875 },
4876 "segments": [],
4877 "source_group": "SG8"
4878 }
4879 },
4880 "segments": [],
4881 "source_group": "SG4"
4882 }
4883 }
4884 });
4885
4886 let code_lookup = CodeLookup::from_schema_value(&schema);
4887
4888 let tree = AssembledTree {
4889 segments: vec![],
4890 groups: vec![AssembledGroup {
4891 group_id: "SG4".to_string(),
4892 repetitions: vec![AssembledGroupInstance {
4893 segments: vec![],
4894 child_groups: vec![AssembledGroup {
4895 group_id: "SG8".to_string(),
4896 repetitions: vec![AssembledGroupInstance {
4897 segments: vec![],
4898 child_groups: vec![AssembledGroup {
4899 group_id: "SG10".to_string(),
4900 repetitions: vec![AssembledGroupInstance {
4901 segments: vec![AssembledSegment {
4902 tag: "CCI".to_string(),
4903 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
4904 }],
4905 child_groups: vec![],
4906 skipped_segments: vec![],
4907 }],
4908 }],
4909 skipped_segments: vec![],
4910 }],
4911 }],
4912 skipped_segments: vec![],
4913 }],
4914 }],
4915 post_group_start: 0,
4916 inter_group_segments: std::collections::BTreeMap::new(),
4917 };
4918
4919 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4920 companion_fields.insert(
4921 "cci.2".to_string(),
4922 FieldMapping::Simple("haushaltskunde".to_string()),
4923 );
4924
4925 let def = MappingDefinition {
4926 meta: MappingMeta {
4927 entity: "Marktlokation".to_string(),
4928 bo4e_type: "Marktlokation".to_string(),
4929 companion_type: Some("MarktlokationEdifact".to_string()),
4930 source_group: "SG4.SG8.SG10".to_string(),
4931 source_path: Some("sg4.sg8_z01.sg10".to_string()),
4932 discriminator: None,
4933 repeat_on_tag: None,
4934 },
4935 fields: IndexMap::new(),
4936 companion_fields: Some(companion_fields),
4937 complex_handlers: None,
4938 };
4939
4940 let engine = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
4941 let bo4e = engine.map_forward(&tree, &def, 0);
4942 let hk = &bo4e["marktlokationEdifact"]["haushaltskunde"];
4943 assert_eq!(hk["code"].as_str(), Some("Z15"));
4944 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
4945 assert_eq!(
4946 hk["enum"].as_str(),
4947 Some("HAUSHALTSKUNDE"),
4948 "enum field should be present"
4949 );
4950 }
4951
4952 #[test]
4953 fn test_reverse_mapping_accepts_enriched_with_enum() {
4954 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4956 companion_fields.insert(
4957 "cci.2".to_string(),
4958 FieldMapping::Simple("haushaltskunde".to_string()),
4959 );
4960
4961 let def = MappingDefinition {
4962 meta: MappingMeta {
4963 entity: "Test".to_string(),
4964 bo4e_type: "Test".to_string(),
4965 companion_type: Some("TestEdifact".to_string()),
4966 source_group: "SG4".to_string(),
4967 source_path: None,
4968 discriminator: None,
4969 repeat_on_tag: None,
4970 },
4971 fields: IndexMap::new(),
4972 companion_fields: Some(companion_fields),
4973 complex_handlers: None,
4974 };
4975
4976 let engine = MappingEngine::from_definitions(vec![]);
4977
4978 let bo4e = serde_json::json!({
4979 "testEdifact": {
4980 "haushaltskunde": {
4981 "code": "Z15",
4982 "meaning": "Haushaltskunde",
4983 "enum": "HAUSHALTSKUNDE"
4984 }
4985 }
4986 });
4987 let instance = engine.map_reverse(&bo4e, &def);
4988 assert_eq!(instance.segments[0].elements[2], vec!["Z15"]);
4989 }
4990
4991 #[test]
4992 fn test_reverse_mapping_accepts_enriched_companion() {
4993 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4995 companion_fields.insert(
4996 "cci.2".to_string(),
4997 FieldMapping::Simple("haushaltskunde".to_string()),
4998 );
4999
5000 let def = MappingDefinition {
5001 meta: MappingMeta {
5002 entity: "Test".to_string(),
5003 bo4e_type: "Test".to_string(),
5004 companion_type: Some("TestEdifact".to_string()),
5005 source_group: "SG4".to_string(),
5006 source_path: None,
5007 discriminator: None,
5008 repeat_on_tag: None,
5009 },
5010 fields: IndexMap::new(),
5011 companion_fields: Some(companion_fields),
5012 complex_handlers: None,
5013 };
5014
5015 let engine = MappingEngine::from_definitions(vec![]);
5016
5017 let bo4e_plain = serde_json::json!({
5019 "testEdifact": {
5020 "haushaltskunde": "Z15"
5021 }
5022 });
5023 let instance_plain = engine.map_reverse(&bo4e_plain, &def);
5024 assert_eq!(instance_plain.segments[0].elements[2], vec!["Z15"]);
5025
5026 let bo4e_enriched = serde_json::json!({
5028 "testEdifact": {
5029 "haushaltskunde": {
5030 "code": "Z15",
5031 "meaning": "Haushaltskunde gem. EnWG"
5032 }
5033 }
5034 });
5035 let instance_enriched = engine.map_reverse(&bo4e_enriched, &def);
5036 assert_eq!(instance_enriched.segments[0].elements[2], vec!["Z15"]);
5037 }
5038
5039 #[test]
5040 fn test_resolve_child_relative_with_source_path() {
5041 let mut map: std::collections::HashMap<String, Vec<usize>> =
5042 std::collections::HashMap::new();
5043 map.insert("sg4.sg8_ze1".to_string(), vec![6]);
5044 map.insert("sg4.sg8_z98".to_string(), vec![0]);
5045
5046 assert_eq!(
5048 resolve_child_relative("SG8.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5049 "SG8:6.SG10"
5050 );
5051
5052 assert_eq!(
5054 resolve_child_relative("SG8:3.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5055 "SG8:3.SG10"
5056 );
5057
5058 assert_eq!(
5060 resolve_child_relative("SG8.SG10", Some("sg4.sg8_unknown.sg10"), &map, 0),
5061 "SG8.SG10"
5062 );
5063
5064 assert_eq!(
5066 resolve_child_relative("SG8.SG10", None, &map, 0),
5067 "SG8.SG10"
5068 );
5069
5070 assert_eq!(
5072 resolve_child_relative("SG8.SG9", Some("sg4.sg8_z98.sg9"), &map, 0),
5073 "SG8:0.SG9"
5074 );
5075
5076 map.insert("sg4.sg8_zf3".to_string(), vec![3, 4]);
5078 assert_eq!(
5079 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 0),
5080 "SG8:3.SG10"
5081 );
5082 assert_eq!(
5083 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 1),
5084 "SG8:4.SG10"
5085 );
5086 }
5087
5088 #[test]
5089 fn test_place_in_groups_returns_rep_index() {
5090 let mut groups: Vec<AssembledGroup> = Vec::new();
5091
5092 let instance = AssembledGroupInstance {
5094 segments: vec![],
5095 child_groups: vec![],
5096 skipped_segments: vec![],
5097 };
5098 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 0);
5099
5100 let instance = AssembledGroupInstance {
5102 segments: vec![],
5103 child_groups: vec![],
5104 skipped_segments: vec![],
5105 };
5106 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 1);
5107
5108 let instance = AssembledGroupInstance {
5110 segments: vec![],
5111 child_groups: vec![],
5112 skipped_segments: vec![],
5113 };
5114 assert_eq!(place_in_groups(&mut groups, "SG8:5", instance), 5);
5115 }
5116
5117 #[test]
5118 fn test_resolve_by_source_path() {
5119 use mig_assembly::assembler::*;
5120
5121 let tree = AssembledTree {
5123 segments: vec![],
5124 groups: vec![AssembledGroup {
5125 group_id: "SG4".to_string(),
5126 repetitions: vec![AssembledGroupInstance {
5127 segments: vec![],
5128 child_groups: vec![AssembledGroup {
5129 group_id: "SG8".to_string(),
5130 repetitions: vec![
5131 AssembledGroupInstance {
5132 segments: vec![AssembledSegment {
5133 tag: "SEQ".to_string(),
5134 elements: vec![vec!["Z98".to_string()]],
5135 }],
5136 child_groups: vec![AssembledGroup {
5137 group_id: "SG10".to_string(),
5138 repetitions: vec![AssembledGroupInstance {
5139 segments: vec![AssembledSegment {
5140 tag: "CCI".to_string(),
5141 elements: vec![vec![], vec![], vec!["ZB3".to_string()]],
5142 }],
5143 child_groups: vec![],
5144 skipped_segments: vec![],
5145 }],
5146 }],
5147 skipped_segments: vec![],
5148 },
5149 AssembledGroupInstance {
5150 segments: vec![AssembledSegment {
5151 tag: "SEQ".to_string(),
5152 elements: vec![vec!["ZD7".to_string()]],
5153 }],
5154 child_groups: vec![AssembledGroup {
5155 group_id: "SG10".to_string(),
5156 repetitions: vec![AssembledGroupInstance {
5157 segments: vec![AssembledSegment {
5158 tag: "CCI".to_string(),
5159 elements: vec![vec![], vec![], vec!["ZE6".to_string()]],
5160 }],
5161 child_groups: vec![],
5162 skipped_segments: vec![],
5163 }],
5164 }],
5165 skipped_segments: vec![],
5166 },
5167 ],
5168 }],
5169 skipped_segments: vec![],
5170 }],
5171 }],
5172 post_group_start: 0,
5173 inter_group_segments: std::collections::BTreeMap::new(),
5174 };
5175
5176 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_z98.sg10");
5178 assert!(inst.is_some());
5179 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
5180
5181 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zd7.sg10");
5183 assert!(inst.is_some());
5184 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZE6");
5185
5186 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zzz.sg10");
5188 assert!(inst.is_none());
5189
5190 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8.sg10");
5192 assert!(inst.is_some());
5193 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
5194 }
5195
5196 #[test]
5197 fn test_parse_source_path_part() {
5198 assert_eq!(parse_source_path_part("sg4"), ("sg4", None));
5199 assert_eq!(parse_source_path_part("sg8_z98"), ("sg8", Some("z98")));
5200 assert_eq!(parse_source_path_part("sg10"), ("sg10", None));
5201 assert_eq!(parse_source_path_part("sg12_z04"), ("sg12", Some("z04")));
5202 }
5203
5204 #[test]
5205 fn test_has_source_path_qualifiers() {
5206 assert!(has_source_path_qualifiers("sg4.sg8_z98.sg10"));
5207 assert!(has_source_path_qualifiers("sg4.sg8_ze1.sg9"));
5208 assert!(!has_source_path_qualifiers("sg4.sg6"));
5209 assert!(!has_source_path_qualifiers("sg4.sg8.sg10"));
5210 }
5211
5212 #[test]
5213 fn test_companion_dotted_path_forward() {
5214 use mig_assembly::assembler::*;
5215
5216 let tree = AssembledTree {
5218 segments: vec![],
5219 groups: vec![AssembledGroup {
5220 group_id: "SG4".to_string(),
5221 repetitions: vec![AssembledGroupInstance {
5222 segments: vec![],
5223 child_groups: vec![AssembledGroup {
5224 group_id: "SG8".to_string(),
5225 repetitions: vec![AssembledGroupInstance {
5226 segments: vec![],
5227 child_groups: vec![AssembledGroup {
5228 group_id: "SG10".to_string(),
5229 repetitions: vec![AssembledGroupInstance {
5230 segments: vec![AssembledSegment {
5231 tag: "CCI".to_string(),
5232 elements: vec![
5233 vec!["11XAB-1234".to_string()],
5234 vec!["305".to_string()],
5235 ],
5236 }],
5237 child_groups: vec![],
5238 skipped_segments: vec![],
5239 }],
5240 }],
5241 skipped_segments: vec![],
5242 }],
5243 }],
5244 skipped_segments: vec![],
5245 }],
5246 }],
5247 post_group_start: 0,
5248 inter_group_segments: std::collections::BTreeMap::new(),
5249 };
5250
5251 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5253 companion_fields.insert(
5254 "cci.0".to_string(),
5255 FieldMapping::Simple("bilanzkreis.id".to_string()),
5256 );
5257 companion_fields.insert(
5258 "cci.1".to_string(),
5259 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
5260 );
5261
5262 let def = MappingDefinition {
5263 meta: MappingMeta {
5264 entity: "Test".to_string(),
5265 bo4e_type: "Test".to_string(),
5266 companion_type: Some("TestEdifact".to_string()),
5267 source_group: "SG4.SG8.SG10".to_string(),
5268 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5269 discriminator: None,
5270 repeat_on_tag: None,
5271 },
5272 fields: IndexMap::new(),
5273 companion_fields: Some(companion_fields),
5274 complex_handlers: None,
5275 };
5276
5277 let engine = MappingEngine::from_definitions(vec![]);
5278 let bo4e = engine.map_forward(&tree, &def, 0);
5279
5280 let companion = &bo4e["testEdifact"];
5282 assert!(
5283 companion.is_object(),
5284 "testEdifact should be an object, got: {companion}"
5285 );
5286 let bilanzkreis = &companion["bilanzkreis"];
5287 assert!(
5288 bilanzkreis.is_object(),
5289 "bilanzkreis should be a nested object, got: {bilanzkreis}"
5290 );
5291 assert_eq!(
5292 bilanzkreis["id"].as_str(),
5293 Some("11XAB-1234"),
5294 "bilanzkreis.id should be 11XAB-1234"
5295 );
5296 assert_eq!(
5297 bilanzkreis["codelist"].as_str(),
5298 Some("305"),
5299 "bilanzkreis.codelist should be 305"
5300 );
5301 }
5302
5303 #[test]
5304 fn test_companion_dotted_path_reverse() {
5305 let engine = MappingEngine::from_definitions(vec![]);
5307
5308 let companion_value = serde_json::json!({
5309 "bilanzkreis": {
5310 "id": "11XAB-1234",
5311 "codelist": "305"
5312 }
5313 });
5314
5315 assert_eq!(
5316 engine.populate_field(&companion_value, "bilanzkreis.id"),
5317 Some("11XAB-1234".to_string()),
5318 "dotted path bilanzkreis.id should resolve"
5319 );
5320 assert_eq!(
5321 engine.populate_field(&companion_value, "bilanzkreis.codelist"),
5322 Some("305".to_string()),
5323 "dotted path bilanzkreis.codelist should resolve"
5324 );
5325
5326 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5328 companion_fields.insert(
5329 "cci.0".to_string(),
5330 FieldMapping::Simple("bilanzkreis.id".to_string()),
5331 );
5332 companion_fields.insert(
5333 "cci.1".to_string(),
5334 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
5335 );
5336
5337 let def = MappingDefinition {
5338 meta: MappingMeta {
5339 entity: "Test".to_string(),
5340 bo4e_type: "Test".to_string(),
5341 companion_type: Some("TestEdifact".to_string()),
5342 source_group: "SG4.SG8.SG10".to_string(),
5343 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5344 discriminator: None,
5345 repeat_on_tag: None,
5346 },
5347 fields: IndexMap::new(),
5348 companion_fields: Some(companion_fields),
5349 complex_handlers: None,
5350 };
5351
5352 let bo4e = serde_json::json!({
5353 "testEdifact": {
5354 "bilanzkreis": {
5355 "id": "11XAB-1234",
5356 "codelist": "305"
5357 }
5358 }
5359 });
5360
5361 let instance = engine.map_reverse(&bo4e, &def);
5362 assert_eq!(instance.segments.len(), 1, "should produce one CCI segment");
5363 let cci = &instance.segments[0];
5364 assert_eq!(cci.tag, "CCI");
5365 assert_eq!(
5366 cci.elements[0],
5367 vec!["11XAB-1234"],
5368 "element 0 should contain bilanzkreis.id"
5369 );
5370 assert_eq!(
5371 cci.elements[1],
5372 vec!["305"],
5373 "element 1 should contain bilanzkreis.codelist"
5374 );
5375 }
5376
5377 #[test]
5378 fn test_when_filled_injects_when_field_present() {
5379 let toml_str = r#"
5380[meta]
5381entity = "Test"
5382bo4e_type = "Test"
5383companion_type = "TestEdifact"
5384source_group = "SG4.SG8.SG10"
5385
5386[fields]
5387
5388[companion_fields]
5389"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmalCode"] }
5390"cav.0.0" = "merkmalCode"
5391"#;
5392 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5393
5394 let bo4e_with = serde_json::json!({
5396 "testEdifact": { "merkmalCode": "ZA7" }
5397 });
5398 let engine = MappingEngine::new_empty();
5399 let instance = engine.map_reverse(&bo4e_with, &def);
5400 let cci = instance
5401 .segments
5402 .iter()
5403 .find(|s| s.tag == "CCI")
5404 .expect("CCI should exist");
5405 assert_eq!(cci.elements[0][0], "Z83");
5406
5407 let bo4e_without = serde_json::json!({
5409 "testEdifact": {}
5410 });
5411 let instance2 = engine.map_reverse(&bo4e_without, &def);
5412 let cci2 = instance2.segments.iter().find(|s| s.tag == "CCI");
5413 assert!(
5414 cci2.is_none(),
5415 "CCI should not be emitted when merkmalCode is absent"
5416 );
5417 }
5418
5419 #[test]
5420 fn test_when_filled_checks_core_and_companion() {
5421 let toml_str = r#"
5422[meta]
5423entity = "Test"
5424bo4e_type = "Test"
5425companion_type = "TestEdifact"
5426source_group = "SG4.SG5"
5427
5428[fields]
5429"loc.1.0" = "marktlokationsId"
5430
5431[companion_fields]
5432"loc.0.0" = { target = "", default = "Z16", when_filled = ["marktlokationsId"] }
5433"#;
5434 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5435
5436 let bo4e_with = serde_json::json!({
5438 "marktlokationsId": "51234567890"
5439 });
5440 let engine = MappingEngine::new_empty();
5441 let instance = engine.map_reverse(&bo4e_with, &def);
5442 let loc = instance
5443 .segments
5444 .iter()
5445 .find(|s| s.tag == "LOC")
5446 .expect("LOC should exist");
5447 assert_eq!(loc.elements[0][0], "Z16");
5448 assert_eq!(loc.elements[1][0], "51234567890");
5449
5450 let bo4e_without = serde_json::json!({});
5452 let instance2 = engine.map_reverse(&bo4e_without, &def);
5453 let loc2 = instance2.segments.iter().find(|s| s.tag == "LOC");
5454 assert!(loc2.is_none());
5455 }
5456
5457 #[test]
5458 fn test_extract_all_from_instance_collects_all_qualifier_matches() {
5459 use mig_assembly::assembler::*;
5460
5461 let instance = AssembledGroupInstance {
5463 segments: vec![
5464 AssembledSegment {
5465 tag: "SEQ".to_string(),
5466 elements: vec![vec!["ZD6".to_string()]],
5467 },
5468 AssembledSegment {
5469 tag: "RFF".to_string(),
5470 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
5471 },
5472 AssembledSegment {
5473 tag: "RFF".to_string(),
5474 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
5475 },
5476 AssembledSegment {
5477 tag: "RFF".to_string(),
5478 elements: vec![vec!["Z34".to_string(), "REF_C".to_string()]],
5479 },
5480 AssembledSegment {
5481 tag: "RFF".to_string(),
5482 elements: vec![vec!["Z35".to_string(), "OTHER".to_string()]],
5483 },
5484 ],
5485 child_groups: vec![],
5486 skipped_segments: vec![],
5487 };
5488
5489 let all = MappingEngine::extract_all_from_instance(&instance, "rff[Z34,*].0.1");
5491 assert_eq!(all, vec!["REF_A", "REF_B", "REF_C"]);
5492
5493 let single = MappingEngine::extract_from_instance(&instance, "rff[Z34].0.1");
5495 assert_eq!(single, Some("REF_A".to_string()));
5496
5497 let second = MappingEngine::extract_from_instance(&instance, "rff[Z34,1].0.1");
5498 assert_eq!(second, Some("REF_B".to_string()));
5499 }
5500
5501 #[test]
5502 fn test_forward_wildcard_collect_produces_json_array() {
5503 use mig_assembly::assembler::*;
5504
5505 let instance = AssembledGroupInstance {
5506 segments: vec![
5507 AssembledSegment {
5508 tag: "SEQ".to_string(),
5509 elements: vec![vec!["ZD6".to_string()]],
5510 },
5511 AssembledSegment {
5512 tag: "RFF".to_string(),
5513 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
5514 },
5515 AssembledSegment {
5516 tag: "RFF".to_string(),
5517 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
5518 },
5519 ],
5520 child_groups: vec![],
5521 skipped_segments: vec![],
5522 };
5523
5524 let toml_str = r#"
5525[meta]
5526entity = "Test"
5527bo4e_type = "Test"
5528companion_type = "TestEdifact"
5529source_group = "SG4.SG8"
5530
5531[fields]
5532
5533[companion_fields]
5534"rff[Z34,*].0.1" = "messlokationsIdRefs"
5535"#;
5536 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5537 let engine = MappingEngine::new_empty();
5538
5539 let mut result = serde_json::Map::new();
5540 engine.extract_companion_fields(&instance, &def, &mut result, false);
5541
5542 let companion = result.get("testEdifact").unwrap().as_object().unwrap();
5543 let refs = companion
5544 .get("messlokationsIdRefs")
5545 .unwrap()
5546 .as_array()
5547 .unwrap();
5548 assert_eq!(refs.len(), 2);
5549 assert_eq!(refs[0].as_str().unwrap(), "REF_A");
5550 assert_eq!(refs[1].as_str().unwrap(), "REF_B");
5551 }
5552
5553 #[test]
5554 fn test_reverse_json_array_produces_multiple_segments() {
5555 let toml_str = r#"
5556[meta]
5557entity = "Test"
5558bo4e_type = "Test"
5559companion_type = "TestEdifact"
5560source_group = "SG4.SG8"
5561
5562[fields]
5563
5564[companion_fields]
5565"seq.0.0" = { target = "", default = "ZD6" }
5566"rff[Z34,*].0.1" = "messlokationsIdRefs"
5567"#;
5568 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5569 let engine = MappingEngine::new_empty();
5570
5571 let bo4e = serde_json::json!({
5572 "testEdifact": {
5573 "messlokationsIdRefs": ["REF_A", "REF_B", "REF_C"]
5574 }
5575 });
5576
5577 let instance = engine.map_reverse(&bo4e, &def);
5578
5579 let rff_segs: Vec<_> = instance
5581 .segments
5582 .iter()
5583 .filter(|s| s.tag == "RFF")
5584 .collect();
5585 assert_eq!(rff_segs.len(), 3);
5586 assert_eq!(rff_segs[0].elements[0][0], "Z34");
5587 assert_eq!(rff_segs[0].elements[0][1], "REF_A");
5588 assert_eq!(rff_segs[1].elements[0][0], "Z34");
5589 assert_eq!(rff_segs[1].elements[0][1], "REF_B");
5590 assert_eq!(rff_segs[2].elements[0][0], "Z34");
5591 assert_eq!(rff_segs[2].elements[0][1], "REF_C");
5592 }
5593
5594 #[test]
5595 fn test_when_filled_dotted_path() {
5596 let toml_str = r#"
5597[meta]
5598entity = "Test"
5599bo4e_type = "Test"
5600companion_type = "TestEdifact"
5601source_group = "SG4.SG8.SG10"
5602
5603[fields]
5604
5605[companion_fields]
5606"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmal.code"] }
5607"cav.0.0" = "merkmal.code"
5608"#;
5609 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5610
5611 let bo4e = serde_json::json!({
5612 "testEdifact": { "merkmal": { "code": "ZA7" } }
5613 });
5614 let engine = MappingEngine::new_empty();
5615 let instance = engine.map_reverse(&bo4e, &def);
5616 let cci = instance
5617 .segments
5618 .iter()
5619 .find(|s| s.tag == "CCI")
5620 .expect("CCI should exist");
5621 assert_eq!(cci.elements[0][0], "Z83");
5622 }
5623
5624 #[test]
5625 fn test_also_target_forward_extracts_both_fields() {
5626 use mig_assembly::assembler::*;
5627
5628 let instance = AssembledGroupInstance {
5629 segments: vec![AssembledSegment {
5630 tag: "NAD".to_string(),
5631 elements: vec![vec!["Z47".to_string()], vec!["12345".to_string()]],
5632 }],
5633 child_groups: vec![],
5634 skipped_segments: vec![],
5635 };
5636
5637 let toml_str = r#"
5638[meta]
5639entity = "Geschaeftspartner"
5640bo4e_type = "Geschaeftspartner"
5641companion_type = "GeschaeftspartnerEdifact"
5642source_group = "SG4.SG12"
5643
5644[fields]
5645"nad.1.0" = "identifikation"
5646
5647[companion_fields."nad.0.0"]
5648target = "partnerrolle"
5649enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
5650also_target = "datenqualitaet"
5651also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
5652"#;
5653 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5654 let engine = MappingEngine::new_empty();
5655
5656 let mut result = serde_json::Map::new();
5657 engine.extract_companion_fields(&instance, &def, &mut result, false);
5658
5659 let companion = result
5660 .get("geschaeftspartnerEdifact")
5661 .unwrap()
5662 .as_object()
5663 .unwrap();
5664 assert_eq!(
5665 companion.get("partnerrolle").unwrap().as_str().unwrap(),
5666 "kundeDesLf"
5667 );
5668 assert_eq!(
5669 companion.get("datenqualitaet").unwrap().as_str().unwrap(),
5670 "erwartet"
5671 );
5672 }
5673
5674 #[test]
5675 fn test_also_target_reverse_joint_lookup() {
5676 let toml_str = r#"
5677[meta]
5678entity = "Geschaeftspartner"
5679bo4e_type = "Geschaeftspartner"
5680companion_type = "GeschaeftspartnerEdifact"
5681source_group = "SG4.SG12"
5682
5683[fields]
5684
5685[companion_fields."nad.0.0"]
5686target = "partnerrolle"
5687enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
5688also_target = "datenqualitaet"
5689also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
5690"#;
5691 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5692 let engine = MappingEngine::new_empty();
5693
5694 let bo4e = serde_json::json!({
5696 "geschaeftspartnerEdifact": {
5697 "partnerrolle": "kundeDesLf",
5698 "datenqualitaet": "erwartet"
5699 }
5700 });
5701 let instance = engine.map_reverse(&bo4e, &def);
5702 let nad = instance
5703 .segments
5704 .iter()
5705 .find(|s| s.tag == "NAD")
5706 .expect("NAD");
5707 assert_eq!(nad.elements[0][0], "Z47");
5708
5709 let bo4e2 = serde_json::json!({
5711 "geschaeftspartnerEdifact": {
5712 "partnerrolle": "kundeDesNb",
5713 "datenqualitaet": "imSystemVorhanden"
5714 }
5715 });
5716 let instance2 = engine.map_reverse(&bo4e2, &def);
5717 let nad2 = instance2
5718 .segments
5719 .iter()
5720 .find(|s| s.tag == "NAD")
5721 .expect("NAD");
5722 assert_eq!(nad2.elements[0][0], "Z52");
5723 }
5724
5725 #[test]
5726 fn test_also_target_mixed_codes_unpaired_skips_datenqualitaet() {
5727 use mig_assembly::assembler::*;
5728
5729 let toml_str = r#"
5731[meta]
5732entity = "Geschaeftspartner"
5733bo4e_type = "Geschaeftspartner"
5734companion_type = "GeschaeftspartnerEdifact"
5735source_group = "SG4.SG12"
5736
5737[fields]
5738
5739[companion_fields."nad.0.0"]
5740target = "partnerrolle"
5741enum_map = { "Z09" = "kundeDesLf", "Z47" = "kundeDesLf", "Z48" = "kundeDesLf" }
5742also_target = "datenqualitaet"
5743also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden" }
5744"#;
5745 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5746 let engine = MappingEngine::new_empty();
5747
5748 let instance_z09 = AssembledGroupInstance {
5750 segments: vec![AssembledSegment {
5751 tag: "NAD".to_string(),
5752 elements: vec![vec!["Z09".to_string()]],
5753 }],
5754 child_groups: vec![],
5755 skipped_segments: vec![],
5756 };
5757 let mut result = serde_json::Map::new();
5758 engine.extract_companion_fields(&instance_z09, &def, &mut result, false);
5759 let comp = result
5760 .get("geschaeftspartnerEdifact")
5761 .unwrap()
5762 .as_object()
5763 .unwrap();
5764 assert_eq!(
5765 comp.get("partnerrolle").unwrap().as_str().unwrap(),
5766 "kundeDesLf"
5767 );
5768 assert!(
5769 comp.get("datenqualitaet").is_none(),
5770 "Z09 should not set datenqualitaet"
5771 );
5772
5773 let bo4e = serde_json::json!({
5775 "geschaeftspartnerEdifact": { "partnerrolle": "kundeDesLf" }
5776 });
5777 let instance = engine.map_reverse(&bo4e, &def);
5778 let nad = instance
5779 .segments
5780 .iter()
5781 .find(|s| s.tag == "NAD")
5782 .expect("NAD");
5783 assert_eq!(nad.elements[0][0], "Z09");
5784
5785 let bo4e2 = serde_json::json!({
5787 "geschaeftspartnerEdifact": {
5788 "partnerrolle": "kundeDesLf",
5789 "datenqualitaet": "erwartet"
5790 }
5791 });
5792 let instance2 = engine.map_reverse(&bo4e2, &def);
5793 let nad2 = instance2
5794 .segments
5795 .iter()
5796 .find(|s| s.tag == "NAD")
5797 .expect("NAD");
5798 assert_eq!(nad2.elements[0][0], "Z47");
5799 }
5800}