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 let mut inferred_nesting: std::collections::HashMap<String, Vec<usize>> =
2050 std::collections::HashMap::new();
2051
2052 for def in &self.definitions {
2053 let entity_key = to_camel_case(&def.meta.entity);
2054
2055 let _extracted: Option<serde_json::Value>;
2058 let entity_value = if let Some(v) = entities.get(&entity_key) {
2059 _extracted = None;
2060 v
2061 } else if def.meta.source_group.contains('.') {
2062 match extract_child_from_parent_with_indices(
2064 entities,
2065 &self.definitions,
2066 def,
2067 ) {
2068 Some((v, parent_indices)) => {
2069 if let Some(sp) = def.meta.source_path.as_deref() {
2071 inferred_nesting
2072 .entry(sp.to_string())
2073 .or_insert(parent_indices);
2074 }
2075 _extracted = Some(v);
2076 _extracted.as_ref().unwrap()
2077 }
2078 None => continue,
2079 }
2080 } else {
2081 continue;
2082 };
2083
2084 let unwrapped: Option<serde_json::Value>;
2092 let entity_value = if entity_value.is_object() && !entity_value.is_array() {
2093 if let Some(disc_value) = def
2094 .meta
2095 .discriminator
2096 .as_deref()
2097 .and_then(|d| d.split_once('='))
2098 .map(|(_, v)| v)
2099 {
2100 if let Some(inner) = entity_value.get(disc_value) {
2102 let mut injected = inner.clone();
2103 if let Some(ref cf) = def.companion_fields {
2106 let disc_path = def
2107 .meta
2108 .discriminator
2109 .as_deref()
2110 .unwrap()
2111 .split_once('=')
2112 .unwrap()
2113 .0
2114 .to_lowercase();
2115 for (path, mapping) in cf {
2116 let cf_path = path.to_lowercase();
2120 let matches = cf_path == disc_path
2121 || format!("{}.0", cf_path) == disc_path;
2122 if matches {
2123 let target = match mapping {
2124 FieldMapping::Simple(t) => t.as_str(),
2125 FieldMapping::Structured(s) => s.target.as_str(),
2126 FieldMapping::Nested(_) => continue,
2127 };
2128 if !target.is_empty() {
2129 if let Some(obj) = injected.as_object_mut() {
2130 let entry = obj.entry(target.to_string())
2131 .or_insert(serde_json::Value::Null);
2132 if entry.is_null() {
2133 *entry = serde_json::Value::String(
2134 disc_value.to_string(),
2135 );
2136 }
2137 }
2138 }
2139 break;
2140 }
2141 }
2142 }
2143 unwrapped = Some(injected);
2144 unwrapped.as_ref().unwrap()
2145 } else {
2146 entity_value
2147 }
2148 } else if is_map_keyed_object(entity_value) {
2149 let map = entity_value.as_object().unwrap();
2154 let arr: Vec<serde_json::Value> = map
2155 .iter()
2156 .map(|(key, val)| {
2157 let mut item = val.clone();
2158 if let Some(obj) = item.as_object_mut() {
2161 if let Some(qualifier_field) =
2162 find_qualifier_companion_field(&self.definitions, &def.meta.entity)
2163 {
2164 let entry = obj.entry(qualifier_field).or_insert(serde_json::Value::Null);
2165 if entry.is_null() {
2166 *entry = serde_json::Value::String(key.clone());
2167 }
2168 }
2169 }
2170 item
2171 })
2172 .collect();
2173 unwrapped = Some(serde_json::Value::Array(arr));
2174 unwrapped.as_ref().unwrap()
2175 } else {
2176 entity_value
2177 }
2178 } else {
2179 entity_value
2180 };
2181
2182 let leaf_group = def
2184 .meta
2185 .source_group
2186 .rsplit('.')
2187 .next()
2188 .unwrap_or(&def.meta.source_group);
2189
2190 if def.meta.source_group.is_empty() {
2191 let instance = self.map_reverse(entity_value, def);
2193 root_segments.extend(instance.segments);
2194 } else if entity_value.is_array() {
2195 let arr = entity_value.as_array().unwrap();
2197 let reps: Vec<_> = arr.iter().map(|item| self.map_reverse(item, def)).collect();
2198
2199 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2201 existing.repetitions.extend(reps);
2202 } else {
2203 groups.push(AssembledGroup {
2204 group_id: leaf_group.to_string(),
2205 repetitions: reps,
2206 });
2207 }
2208 } else {
2209 let instance = self.map_reverse(entity_value, def);
2211
2212 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2213 existing.repetitions.push(instance);
2214 } else {
2215 groups.push(AssembledGroup {
2216 group_id: leaf_group.to_string(),
2217 repetitions: vec![instance],
2218 });
2219 }
2220 }
2221 }
2222
2223 let nested_specs: Vec<(String, String)> = self
2229 .definitions
2230 .iter()
2231 .filter_map(|def| {
2232 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
2233 if parts.len() > 1 {
2234 Some((parts[0].to_string(), parts[parts.len() - 1].to_string()))
2235 } else {
2236 None
2237 }
2238 })
2239 .collect();
2240 for (parent_id, child_id) in &nested_specs {
2241 let has_parent = groups.iter().any(|g| g.group_id == *parent_id);
2243 let has_child = groups.iter().any(|g| g.group_id == *child_id);
2244 if has_parent && has_child {
2245 let child_idx = groups.iter().position(|g| g.group_id == *child_id).unwrap();
2246 let child_group = groups.remove(child_idx);
2247 let parent = groups
2248 .iter_mut()
2249 .find(|g| g.group_id == *parent_id)
2250 .unwrap();
2251 let child_source_path = self
2255 .definitions
2256 .iter()
2257 .find(|d| {
2258 let parts: Vec<&str> = d.meta.source_group.split('.').collect();
2259 parts.len() > 1 && parts[parts.len() - 1] == *child_id
2260 })
2261 .and_then(|d| d.meta.source_path.as_deref());
2262 let distribution = child_source_path
2263 .and_then(|key| {
2264 nesting_info
2265 .and_then(|ni| ni.get(key))
2266 .or_else(|| inferred_nesting.get(key))
2267 });
2268 for (i, child_rep) in child_group.repetitions.into_iter().enumerate() {
2269 let target_idx = distribution
2270 .and_then(|dist| dist.get(i))
2271 .copied()
2272 .unwrap_or(0);
2273
2274 if let Some(target_rep) = parent.repetitions.get_mut(target_idx) {
2275 if let Some(existing) = target_rep
2276 .child_groups
2277 .iter_mut()
2278 .find(|g| g.group_id == *child_id)
2279 {
2280 existing.repetitions.push(child_rep);
2281 } else {
2282 target_rep.child_groups.push(AssembledGroup {
2283 group_id: child_id.clone(),
2284 repetitions: vec![child_rep],
2285 });
2286 }
2287 }
2288 }
2289 }
2290 }
2291
2292 let post_group_start = root_segments.len();
2293 AssembledTree {
2294 segments: root_segments,
2295 groups,
2296 post_group_start,
2297 inter_group_segments: std::collections::BTreeMap::new(),
2298 }
2299 }
2300
2301 fn count_repetitions(tree: &AssembledTree, group_path: &str) -> usize {
2303 let parts: Vec<&str> = group_path.split('.').collect();
2304
2305 let (first_id, first_rep) = parse_group_spec(parts[0]);
2306 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2307 Some(g) => g,
2308 None => return 0,
2309 };
2310
2311 if parts.len() == 1 {
2312 return first_group.repetitions.len();
2313 }
2314
2315 let mut current_instance = match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2317 Some(i) => i,
2318 None => return 0,
2319 };
2320
2321 for (i, part) in parts[1..].iter().enumerate() {
2322 let (group_id, explicit_rep) = parse_group_spec(part);
2323 let child_group = match current_instance
2324 .child_groups
2325 .iter()
2326 .find(|g| g.group_id == group_id)
2327 {
2328 Some(g) => g,
2329 None => return 0,
2330 };
2331
2332 if i == parts.len() - 2 {
2333 return child_group.repetitions.len();
2335 }
2336 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2337 Some(i) => i,
2338 None => return 0,
2339 };
2340 }
2341
2342 0
2343 }
2344
2345 pub fn map_interchange(
2354 msg_engine: &MappingEngine,
2355 tx_engine: &MappingEngine,
2356 tree: &AssembledTree,
2357 transaction_group: &str,
2358 enrich_codes: bool,
2359 ) -> crate::model::MappedMessage {
2360 let (stammdaten, nesting_info) = msg_engine.map_all_forward_inner(tree, enrich_codes);
2362
2363 let transaktionen = tree
2365 .groups
2366 .iter()
2367 .find(|g| g.group_id == transaction_group)
2368 .map(|sg| {
2369 sg.repetitions
2370 .iter()
2371 .map(|instance| {
2372 let wrapped_tree = AssembledTree {
2375 segments: vec![],
2376 groups: vec![AssembledGroup {
2377 group_id: transaction_group.to_string(),
2378 repetitions: vec![instance.clone()],
2379 }],
2380 post_group_start: 0,
2381 inter_group_segments: std::collections::BTreeMap::new(),
2382 };
2383
2384 let (tx_result, tx_nesting) =
2385 tx_engine.map_all_forward_inner(&wrapped_tree, enrich_codes);
2386
2387 crate::model::MappedTransaktion {
2388 stammdaten: tx_result,
2389 nesting_info: tx_nesting,
2390 }
2391 })
2392 .collect()
2393 })
2394 .unwrap_or_default();
2395
2396 crate::model::MappedMessage {
2397 stammdaten,
2398 transaktionen,
2399 nesting_info,
2400 }
2401 }
2402
2403 pub fn map_interchange_reverse(
2413 msg_engine: &MappingEngine,
2414 tx_engine: &MappingEngine,
2415 mapped: &crate::model::MappedMessage,
2416 transaction_group: &str,
2417 filtered_mig: Option<&MigSchema>,
2418 ) -> AssembledTree {
2419 let msg_tree = msg_engine.map_all_reverse(
2421 &mapped.stammdaten,
2422 if mapped.nesting_info.is_empty() {
2423 None
2424 } else {
2425 Some(&mapped.nesting_info)
2426 },
2427 );
2428
2429 let mut sg4_reps: Vec<AssembledGroupInstance> = Vec::new();
2431
2432 struct DefWithMeta<'a> {
2436 def: &'a MappingDefinition,
2437 relative: String,
2438 depth: usize,
2439 }
2440
2441 let mut sorted_defs: Vec<DefWithMeta> = tx_engine
2442 .definitions
2443 .iter()
2444 .map(|def| {
2445 let relative = strip_tx_group_prefix(&def.meta.source_group, transaction_group);
2446 let depth = if relative.is_empty() {
2447 0
2448 } else {
2449 relative.chars().filter(|c| *c == '.').count() + 1
2450 };
2451 DefWithMeta {
2452 def,
2453 relative,
2454 depth,
2455 }
2456 })
2457 .collect();
2458
2459 let mut parent_rep_map: std::collections::HashMap<String, usize> =
2463 std::collections::HashMap::new();
2464 for dm in &sorted_defs {
2465 if dm.depth >= 2 {
2466 let parts: Vec<&str> = dm.relative.split('.').collect();
2467 let (_, parent_rep) = parse_group_spec(parts[0]);
2468 if let Some(rep_idx) = parent_rep {
2469 if let Some(sp) = &dm.def.meta.source_path {
2470 if let Some((parent_path, _)) = sp.rsplit_once('.') {
2471 parent_rep_map
2472 .entry(parent_path.to_string())
2473 .or_insert(rep_idx);
2474 }
2475 }
2476 }
2477 }
2478 }
2479
2480 for dm in &mut sorted_defs {
2483 if dm.depth == 1 && !dm.relative.contains(':') {
2484 if let Some(sp) = &dm.def.meta.source_path {
2485 if let Some(rep_idx) = parent_rep_map.get(sp.as_str()) {
2486 dm.relative = format!("{}:{}", dm.relative, rep_idx);
2487 }
2488 }
2489 }
2490 }
2491
2492 if let Some(mig) = filtered_mig {
2499 let mig_order = build_reverse_mig_group_order(mig, transaction_group);
2500 sorted_defs.sort_by(|a, b| {
2501 a.depth.cmp(&b.depth).then_with(|| {
2502 let a_id = a.relative.split(':').next().unwrap_or(&a.relative);
2503 let b_id = b.relative.split(':').next().unwrap_or(&b.relative);
2504 let a_pos = variant_mig_position(a.def, a_id, &mig_order);
2506 let b_pos = variant_mig_position(b.def, b_id, &mig_order);
2507 a_pos.cmp(&b_pos).then(a.relative.cmp(&b.relative))
2508 })
2509 });
2510 } else {
2511 sorted_defs.sort_by(|a, b| a.depth.cmp(&b.depth).then(a.relative.cmp(&b.relative)));
2512 }
2513
2514 for tx in &mapped.transaktionen {
2515 let mut root_segs: Vec<AssembledSegment> = Vec::new();
2516 let mut child_groups: Vec<AssembledGroup> = Vec::new();
2517
2518 let mut source_path_to_rep: std::collections::HashMap<String, Vec<usize>> =
2523 std::collections::HashMap::new();
2524
2525 for dm in &sorted_defs {
2526 let entity_key = to_camel_case(&dm.def.meta.entity);
2529 let _tx_extracted: Option<serde_json::Value>;
2530 let bo4e_value = if let Some(v) = tx.stammdaten.get(&entity_key) {
2531 _tx_extracted = None;
2532 v
2533 } else if dm.def.meta.source_group.contains('.') {
2534 match extract_child_from_parent(
2535 &tx.stammdaten,
2536 &tx_engine.definitions,
2537 dm.def,
2538 ) {
2539 Some(v) => {
2540 _tx_extracted = Some(v);
2541 _tx_extracted.as_ref().unwrap()
2542 }
2543 None => continue,
2544 }
2545 } else {
2546 continue;
2547 };
2548
2549 let unwrapped_value: Option<serde_json::Value>;
2551 let bo4e_value = if bo4e_value.is_object() && !bo4e_value.is_array() {
2552 if let Some(disc_value) = dm
2553 .def
2554 .meta
2555 .discriminator
2556 .as_deref()
2557 .and_then(|d| d.split_once('='))
2558 .map(|(_, v)| v)
2559 {
2560 if let Some(inner) = bo4e_value.get(disc_value) {
2561 let mut injected = inner.clone();
2562 if let Some(ref cf) = dm.def.companion_fields {
2563 let disc_path = dm
2564 .def
2565 .meta
2566 .discriminator
2567 .as_deref()
2568 .unwrap()
2569 .split_once('=')
2570 .unwrap()
2571 .0
2572 .to_lowercase();
2573 for (path, mapping) in cf {
2574 let cf_path = path.to_lowercase();
2575 let matches = cf_path == disc_path
2576 || format!("{}.0", cf_path) == disc_path;
2577 if matches {
2578 let target = match mapping {
2579 FieldMapping::Simple(t) => t.as_str(),
2580 FieldMapping::Structured(s) => s.target.as_str(),
2581 FieldMapping::Nested(_) => continue,
2582 };
2583 if !target.is_empty() {
2584 if let Some(obj) = injected.as_object_mut() {
2585 obj.entry(target.to_string()).or_insert_with(
2586 || {
2587 serde_json::Value::String(
2588 disc_value.to_string(),
2589 )
2590 },
2591 );
2592 }
2593 }
2594 break;
2595 }
2596 }
2597 }
2598 unwrapped_value = Some(injected);
2599 unwrapped_value.as_ref().unwrap()
2600 } else {
2601 bo4e_value
2602 }
2603 } else if is_map_keyed_object(bo4e_value) {
2604 let map = bo4e_value.as_object().unwrap();
2605 let arr: Vec<serde_json::Value> = map
2606 .iter()
2607 .map(|(key, val)| {
2608 let mut item = val.clone();
2609 if let Some(obj) = item.as_object_mut() {
2610 if let Some(qualifier_field) =
2611 find_qualifier_companion_field(
2612 &tx_engine.definitions,
2613 &dm.def.meta.entity,
2614 )
2615 {
2616 let entry = obj.entry(qualifier_field).or_insert(serde_json::Value::Null);
2617 if entry.is_null() {
2618 *entry = serde_json::Value::String(key.clone());
2619 }
2620 }
2621 }
2622 item
2623 })
2624 .collect();
2625 unwrapped_value = Some(serde_json::Value::Array(arr));
2626 unwrapped_value.as_ref().unwrap()
2627 } else {
2628 bo4e_value
2629 }
2630 } else {
2631 bo4e_value
2632 };
2633
2634 let items: Vec<&serde_json::Value> = if bo4e_value.is_array() {
2638 bo4e_value.as_array().unwrap().iter().collect()
2639 } else {
2640 vec![bo4e_value]
2641 };
2642
2643 for (item_idx, item) in items.iter().enumerate() {
2644 let instance = tx_engine.map_reverse(item, dm.def);
2645
2646 if instance.segments.is_empty() && instance.child_groups.is_empty() {
2648 continue;
2649 }
2650
2651 if dm.relative.is_empty() {
2652 root_segs.extend(instance.segments);
2653 } else {
2654 let effective_relative = if dm.depth >= 2 {
2658 let rel = if items.len() > 1 {
2661 strip_all_rep_indices(&dm.relative)
2662 } else {
2663 dm.relative.clone()
2664 };
2665 let skip_nesting = dm
2672 .def
2673 .meta
2674 .source_path
2675 .as_ref()
2676 .and_then(|sp| sp.rsplit_once('.'))
2677 .and_then(|(parent_path, _)| {
2678 source_path_to_rep.get(parent_path)
2679 })
2680 .is_some_and(|reps| reps.len() == 1);
2681 let nesting_idx = if items.len() > 1 && !skip_nesting {
2682 dm.def
2683 .meta
2684 .source_path
2685 .as_ref()
2686 .and_then(|sp| tx.nesting_info.get(sp))
2687 .and_then(|dist| dist.get(item_idx))
2688 .copied()
2689 } else {
2690 None
2691 };
2692 if let Some(parent_rep) = nesting_idx {
2693 let parts: Vec<&str> = rel.split('.').collect();
2695 let parent_id = parts[0].split(':').next().unwrap_or(parts[0]);
2696 let rest = parts[1..].join(".");
2697 format!("{}:{}.{}", parent_id, parent_rep, rest)
2698 } else {
2699 resolve_child_relative(
2700 &rel,
2701 dm.def.meta.source_path.as_deref(),
2702 &source_path_to_rep,
2703 item_idx,
2704 )
2705 }
2706 } else if dm.depth == 1 {
2707 let child_key = dm
2710 .def
2711 .meta
2712 .source_path
2713 .as_ref()
2714 .map(|sp| format!("{sp}#child"));
2715 if let Some(child_indices) =
2716 child_key.as_ref().and_then(|ck| tx.nesting_info.get(ck))
2717 {
2718 if let Some(&target) = child_indices.get(item_idx) {
2719 if target != usize::MAX {
2720 let base =
2721 dm.relative.split(':').next().unwrap_or(&dm.relative);
2722 format!("{}:{}", base, target)
2723 } else {
2724 dm.relative.clone()
2725 }
2726 } else if items.len() > 1 && item_idx > 0 {
2727 strip_rep_index(&dm.relative)
2728 } else {
2729 dm.relative.clone()
2730 }
2731 } else if items.len() > 1 && item_idx > 0 {
2732 strip_rep_index(&dm.relative)
2733 } else {
2734 dm.relative.clone()
2735 }
2736 } else if items.len() > 1 && item_idx > 0 {
2737 strip_rep_index(&dm.relative)
2740 } else {
2741 dm.relative.clone()
2742 };
2743
2744 let rep_used =
2745 place_in_groups(&mut child_groups, &effective_relative, instance);
2746
2747 if dm.depth == 1 {
2749 if let Some(sp) = &dm.def.meta.source_path {
2750 source_path_to_rep
2751 .entry(sp.clone())
2752 .or_default()
2753 .push(rep_used);
2754 }
2755 }
2756 }
2757 }
2758 }
2759
2760 if let Some(mig) = filtered_mig {
2765 sort_variant_reps_by_mig(&mut child_groups, mig, transaction_group);
2766 }
2767
2768 sg4_reps.push(AssembledGroupInstance {
2769 segments: root_segs,
2770 child_groups,
2771 skipped_segments: Vec::new(),
2772 });
2773 }
2774
2775 let mut root_segments = Vec::new();
2782 let mut uns_segments = Vec::new();
2783 let mut uns_is_summary = false;
2784 let mut found_uns = false;
2785 for seg in msg_tree.segments {
2786 if seg.tag == "UNS" {
2787 uns_is_summary = seg
2789 .elements
2790 .first()
2791 .and_then(|el| el.first())
2792 .map(|v| v == "S")
2793 .unwrap_or(false);
2794 uns_segments.push(seg);
2795 found_uns = true;
2796 } else if found_uns {
2797 uns_segments.push(seg);
2799 } else {
2800 root_segments.push(seg);
2801 }
2802 }
2803
2804 let pre_group_count = root_segments.len();
2805 let mut all_groups = msg_tree.groups;
2806 let mut inter_group = msg_tree.inter_group_segments;
2807
2808 let sg_num = |id: &str| -> usize {
2810 id.strip_prefix("SG")
2811 .and_then(|n| n.parse::<usize>().ok())
2812 .unwrap_or(0)
2813 };
2814
2815 if !sg4_reps.is_empty() {
2816 if uns_is_summary {
2817 all_groups.push(AssembledGroup {
2819 group_id: transaction_group.to_string(),
2820 repetitions: sg4_reps,
2821 });
2822 if !uns_segments.is_empty() {
2823 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2828 let tx_num = sg_num(transaction_group);
2829 let uns_pos = all_groups
2830 .iter()
2831 .rposition(|g| sg_num(&g.group_id) <= tx_num)
2832 .map(|i| i + 1)
2833 .unwrap_or(all_groups.len());
2834 inter_group.insert(uns_pos, uns_segments);
2835 }
2836 } else {
2837 if !uns_segments.is_empty() {
2839 inter_group.insert(all_groups.len(), uns_segments);
2840 }
2841 all_groups.push(AssembledGroup {
2842 group_id: transaction_group.to_string(),
2843 repetitions: sg4_reps,
2844 });
2845 }
2846 } else if !uns_segments.is_empty() {
2847 if transaction_group.is_empty() {
2848 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2853 if uns_is_summary {
2854 inter_group.insert(all_groups.len(), uns_segments);
2855 } else {
2856 inter_group.insert(0, uns_segments);
2857 }
2858 } else {
2859 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2863 let tx_num = sg_num(transaction_group);
2864 let uns_pos = all_groups
2865 .iter()
2866 .rposition(|g| sg_num(&g.group_id) <= tx_num)
2867 .map(|i| i + 1)
2868 .unwrap_or(all_groups.len());
2869 inter_group.insert(uns_pos, uns_segments);
2870 }
2871 }
2872
2873 AssembledTree {
2874 segments: root_segments,
2875 groups: all_groups,
2876 post_group_start: pre_group_count,
2877 inter_group_segments: inter_group,
2878 }
2879 }
2880
2881 pub fn build_group_from_bo4e(
2883 &self,
2884 bo4e_value: &serde_json::Value,
2885 def: &MappingDefinition,
2886 ) -> AssembledGroup {
2887 let instance = self.map_reverse(bo4e_value, def);
2888 let leaf_group = def
2889 .meta
2890 .source_group
2891 .rsplit('.')
2892 .next()
2893 .unwrap_or(&def.meta.source_group);
2894
2895 AssembledGroup {
2896 group_id: leaf_group.to_string(),
2897 repetitions: vec![instance],
2898 }
2899 }
2900
2901 pub fn map_interchange_typed<M, T>(
2909 msg_engine: &MappingEngine,
2910 tx_engine: &MappingEngine,
2911 tree: &AssembledTree,
2912 tx_group: &str,
2913 enrich_codes: bool,
2914 nachrichtendaten: crate::model::Nachrichtendaten,
2915 interchangedaten: crate::model::Interchangedaten,
2916 ) -> Result<crate::model::Interchange<M, T>, serde_json::Error>
2917 where
2918 M: serde::de::DeserializeOwned,
2919 T: serde::de::DeserializeOwned,
2920 {
2921 let mapped = Self::map_interchange(msg_engine, tx_engine, tree, tx_group, enrich_codes);
2922 let nachricht = mapped.into_dynamic_nachricht(nachrichtendaten);
2923 let dynamic = crate::model::DynamicInterchange {
2924 interchangedaten,
2925 nachrichten: vec![nachricht],
2926 };
2927 let value = serde_json::to_value(&dynamic)?;
2928 serde_json::from_value(value)
2929 }
2930
2931 pub fn map_interchange_reverse_typed<M, T>(
2938 msg_engine: &MappingEngine,
2939 tx_engine: &MappingEngine,
2940 nachricht: &crate::model::Nachricht<M, T>,
2941 tx_group: &str,
2942 ) -> Result<AssembledTree, serde_json::Error>
2943 where
2944 M: serde::Serialize,
2945 T: serde::Serialize,
2946 {
2947 let stammdaten = serde_json::to_value(&nachricht.stammdaten)?;
2948 let transaktionen: Vec<crate::model::MappedTransaktion> = nachricht
2949 .transaktionen
2950 .iter()
2951 .map(|t| {
2952 Ok(crate::model::MappedTransaktion {
2953 stammdaten: serde_json::to_value(t)?,
2954 nesting_info: Default::default(),
2955 })
2956 })
2957 .collect::<Result<Vec<_>, serde_json::Error>>()?;
2958 let mapped = crate::model::MappedMessage {
2959 stammdaten,
2960 transaktionen,
2961 nesting_info: Default::default(),
2962 };
2963 Ok(Self::map_interchange_reverse(
2964 msg_engine, tx_engine, &mapped, tx_group, None,
2965 ))
2966 }
2967}
2968
2969fn parse_source_path_part(part: &str) -> (&str, Option<&str>) {
2976 if let Some(pos) = part.find('_') {
2980 let group = &part[..pos];
2981 let qualifier = &part[pos + 1..];
2982 if !qualifier.is_empty() {
2983 return (group, Some(qualifier));
2984 }
2985 }
2986 (part, None)
2987}
2988
2989fn build_reverse_mig_group_order(mig: &MigSchema, tx_group_id: &str) -> HashMap<String, usize> {
2997 let mut order = HashMap::new();
2998 if let Some(tg) = mig.segment_groups.iter().find(|g| g.id == tx_group_id) {
2999 for (i, nested) in tg.nested_groups.iter().enumerate() {
3000 if let Some(ref vc) = nested.variant_code {
3002 let variant_key = format!("{}_{}", nested.id, vc.to_uppercase());
3003 order.insert(variant_key, i);
3004 }
3005 order.entry(nested.id.clone()).or_insert(i);
3007 }
3008 }
3009 order
3010}
3011
3012fn variant_mig_position(
3018 def: &MappingDefinition,
3019 base_group_id: &str,
3020 mig_order: &HashMap<String, usize>,
3021) -> usize {
3022 if let Some(ref sp) = def.meta.source_path {
3025 let base_lower = base_group_id.to_lowercase();
3027 for part in sp.split('.') {
3028 if part.starts_with(&base_lower)
3029 || part.starts_with(base_group_id.to_lowercase().as_str())
3030 {
3031 if let Some(underscore_pos) = part.find('_') {
3033 let qualifier = &part[underscore_pos + 1..];
3034 let variant_key = format!("{}_{}", base_group_id, qualifier.to_uppercase());
3035 if let Some(&pos) = mig_order.get(&variant_key) {
3036 return pos;
3037 }
3038 }
3039 }
3040 }
3041 }
3042 mig_order.get(base_group_id).copied().unwrap_or(usize::MAX)
3044}
3045
3046fn find_rep_by_entry_qualifier<'a>(
3051 reps: &'a [AssembledGroupInstance],
3052 qualifier: &str,
3053) -> Option<&'a AssembledGroupInstance> {
3054 let parts: Vec<&str> = qualifier.split('_').collect();
3056 reps.iter().find(|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}
3065
3066fn find_all_reps_by_entry_qualifier<'a>(
3068 reps: &'a [AssembledGroupInstance],
3069 qualifier: &str,
3070) -> Vec<&'a AssembledGroupInstance> {
3071 let parts: Vec<&str> = qualifier.split('_').collect();
3073 reps.iter()
3074 .filter(|inst| {
3075 inst.segments.first().is_some_and(|seg| {
3076 seg.elements
3077 .first()
3078 .and_then(|e| e.first())
3079 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3080 })
3081 })
3082 .collect()
3083}
3084
3085fn has_source_path_qualifiers(source_path: &str) -> bool {
3087 source_path.split('.').any(|part| {
3088 if let Some(pos) = part.find('_') {
3089 pos < part.len() - 1
3090 } else {
3091 false
3092 }
3093 })
3094}
3095
3096fn parse_group_spec(part: &str) -> (&str, Option<usize>) {
3097 if let Some(colon_pos) = part.find(':') {
3098 let id = &part[..colon_pos];
3099 let rep = part[colon_pos + 1..].parse::<usize>().ok();
3100 (id, rep)
3101 } else {
3102 (part, None)
3103 }
3104}
3105
3106fn strip_tx_group_prefix(source_group: &str, tx_group: &str) -> String {
3112 if source_group == tx_group || source_group.is_empty() {
3113 String::new()
3114 } else if let Some(rest) = source_group.strip_prefix(tx_group) {
3115 rest.strip_prefix('.').unwrap_or(rest).to_string()
3116 } else {
3117 source_group.to_string()
3118 }
3119}
3120
3121fn place_in_groups(
3129 groups: &mut Vec<AssembledGroup>,
3130 relative_path: &str,
3131 instance: AssembledGroupInstance,
3132) -> usize {
3133 let parts: Vec<&str> = relative_path.split('.').collect();
3134
3135 if parts.len() == 1 {
3136 let (id, rep) = parse_group_spec(parts[0]);
3138
3139 let group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == id) {
3141 g
3142 } else {
3143 groups.push(AssembledGroup {
3144 group_id: id.to_string(),
3145 repetitions: vec![],
3146 });
3147 groups.last_mut().unwrap()
3148 };
3149
3150 if let Some(rep_idx) = rep {
3151 while group.repetitions.len() <= rep_idx {
3153 group.repetitions.push(AssembledGroupInstance {
3154 segments: vec![],
3155 child_groups: vec![],
3156 skipped_segments: Vec::new(),
3157 });
3158 }
3159 group.repetitions[rep_idx]
3160 .segments
3161 .extend(instance.segments);
3162 group.repetitions[rep_idx]
3163 .child_groups
3164 .extend(instance.child_groups);
3165 rep_idx
3166 } else {
3167 let pos = group.repetitions.len();
3169 group.repetitions.push(instance);
3170 pos
3171 }
3172 } else {
3173 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3175 let rep_idx = parent_rep.unwrap_or(0);
3176
3177 let parent_group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == parent_id) {
3179 g
3180 } else {
3181 groups.push(AssembledGroup {
3182 group_id: parent_id.to_string(),
3183 repetitions: vec![],
3184 });
3185 groups.last_mut().unwrap()
3186 };
3187
3188 while parent_group.repetitions.len() <= rep_idx {
3190 parent_group.repetitions.push(AssembledGroupInstance {
3191 segments: vec![],
3192 child_groups: vec![],
3193 skipped_segments: Vec::new(),
3194 });
3195 }
3196
3197 let remaining = parts[1..].join(".");
3198 place_in_groups(
3199 &mut parent_group.repetitions[rep_idx].child_groups,
3200 &remaining,
3201 instance,
3202 );
3203 rep_idx
3204 }
3205}
3206
3207fn resolve_child_relative(
3219 relative: &str,
3220 source_path: Option<&str>,
3221 source_path_to_rep: &std::collections::HashMap<String, Vec<usize>>,
3222 item_idx: usize,
3223) -> String {
3224 let parts: Vec<&str> = relative.split('.').collect();
3225 if parts.is_empty() {
3226 return relative.to_string();
3227 }
3228
3229 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3231 if parent_rep.is_some() {
3232 return relative.to_string();
3233 }
3234
3235 if let Some(sp) = source_path {
3237 if let Some((parent_path, _child)) = sp.rsplit_once('.') {
3238 if let Some(rep_indices) = source_path_to_rep.get(parent_path) {
3239 let rep_idx = rep_indices
3241 .get(item_idx)
3242 .or_else(|| rep_indices.last())
3243 .copied()
3244 .unwrap_or(0);
3245 let rest = parts[1..].join(".");
3246 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3247 }
3248 }
3249 }
3250
3251 relative.to_string()
3253}
3254
3255struct DiscriminatorMatcher<'a> {
3262 tag: &'a str,
3263 element_idx: usize,
3264 component_idx: usize,
3265 expected_values: Vec<&'a str>,
3266 occurrence: Option<usize>,
3268}
3269
3270impl<'a> DiscriminatorMatcher<'a> {
3271 fn parse(disc: &'a str) -> Option<Self> {
3272 let (spec, expected) = disc.split_once('=')?;
3273 let parts: Vec<&str> = spec.split('.').collect();
3274 if parts.len() != 3 {
3275 return None;
3276 }
3277 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
3278 Some(Self {
3279 tag: parts[0],
3280 element_idx: parts[1].parse().ok()?,
3281 component_idx: parts[2].parse().ok()?,
3282 expected_values: expected_raw.split('|').collect(),
3283 occurrence,
3284 })
3285 }
3286
3287 fn matches(&self, instance: &AssembledGroupInstance) -> bool {
3288 instance.segments.iter().any(|s| {
3289 s.tag.eq_ignore_ascii_case(self.tag)
3290 && s.elements
3291 .get(self.element_idx)
3292 .and_then(|e| e.get(self.component_idx))
3293 .map(|v| self.expected_values.iter().any(|ev| v == ev))
3294 .unwrap_or(false)
3295 })
3296 }
3297
3298 fn filter_instances<'b>(
3300 &self,
3301 instances: Vec<&'b AssembledGroupInstance>,
3302 ) -> Vec<&'b AssembledGroupInstance> {
3303 let matching: Vec<_> = instances
3304 .into_iter()
3305 .filter(|inst| self.matches(inst))
3306 .collect();
3307 if let Some(occ) = self.occurrence {
3308 matching.into_iter().nth(occ).into_iter().collect()
3309 } else {
3310 matching
3311 }
3312 }
3313}
3314
3315fn parse_discriminator_occurrence(expected: &str) -> (&str, Option<usize>) {
3321 if let Some(hash_pos) = expected.rfind('#') {
3322 if let Ok(occ) = expected[hash_pos + 1..].parse::<usize>() {
3323 return (&expected[..hash_pos], Some(occ));
3324 }
3325 }
3326 (expected, None)
3327}
3328
3329fn strip_rep_index(relative: &str) -> String {
3333 let (id, _) = parse_group_spec(relative);
3334 id.to_string()
3335}
3336
3337fn strip_all_rep_indices(relative: &str) -> String {
3342 relative
3343 .split('.')
3344 .map(|part| {
3345 let (id, _) = parse_group_spec(part);
3346 id
3347 })
3348 .collect::<Vec<_>>()
3349 .join(".")
3350}
3351
3352fn is_collect_all_path(path: &str) -> bool {
3357 let tag_part = path.split('.').next().unwrap_or("");
3358 if let Some(bracket_start) = tag_part.find('[') {
3359 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3360 if let Some(comma_pos) = inner.find(',') {
3361 let qualifier = &inner[..comma_pos];
3362 let occ = &inner[comma_pos + 1..];
3363 qualifier != "*" && occ == "*"
3365 } else {
3366 false
3367 }
3368 } else {
3369 false
3370 }
3371}
3372
3373fn parse_tag_qualifier(tag_part: &str) -> (String, Option<&str>, usize) {
3380 if let Some(bracket_start) = tag_part.find('[') {
3381 let tag = tag_part[..bracket_start].to_uppercase();
3382 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3383 if let Some(comma_pos) = inner.find(',') {
3384 let qualifier = &inner[..comma_pos];
3385 let index = inner[comma_pos + 1..].parse::<usize>().unwrap_or(0);
3386 if qualifier == "*" {
3388 (tag, None, index)
3389 } else {
3390 (tag, Some(qualifier), index)
3391 }
3392 } else {
3393 (tag, Some(inner), 0)
3394 }
3395 } else {
3396 (tag_part.to_uppercase(), None, 0)
3397 }
3398}
3399
3400fn inject_bo4e_metadata(mut value: serde_json::Value, bo4e_type: &str) -> serde_json::Value {
3405 match &mut value {
3406 serde_json::Value::Object(map) => {
3407 map.entry("boTyp")
3408 .or_insert_with(|| serde_json::Value::String(bo4e_type.to_uppercase()));
3409 map.entry("versionStruktur")
3410 .or_insert_with(|| serde_json::Value::String("1".to_string()));
3411 }
3412 serde_json::Value::Array(items) => {
3413 for item in items {
3414 if let serde_json::Value::Object(map) = item {
3415 map.entry("boTyp")
3416 .or_insert_with(|| serde_json::Value::String(bo4e_type.to_uppercase()));
3417 map.entry("versionStruktur")
3418 .or_insert_with(|| serde_json::Value::String("1".to_string()));
3419 }
3420 }
3421 }
3422 _ => {}
3423 }
3424 value
3425}
3426
3427fn deep_merge_insert(
3433 result: &mut serde_json::Map<String, serde_json::Value>,
3434 entity: &str,
3435 bo4e: serde_json::Value,
3436) {
3437 if let Some(existing) = result.get_mut(entity) {
3438 if let (Some(existing_arr), Some(new_arr)) =
3441 (existing.as_array().map(|a| a.len()), bo4e.as_array())
3442 {
3443 if existing_arr == new_arr.len() {
3444 let existing_arr = existing.as_array_mut().unwrap();
3445 for (existing_elem, new_elem) in existing_arr.iter_mut().zip(new_arr) {
3446 if let (Some(existing_map), Some(new_map)) =
3447 (existing_elem.as_object_mut(), new_elem.as_object())
3448 {
3449 for (k, v) in new_map {
3450 if let Some(existing_v) = existing_map.get_mut(k) {
3451 if let (Some(existing_inner), Some(new_inner)) =
3452 (existing_v.as_object_mut(), v.as_object())
3453 {
3454 for (ik, iv) in new_inner {
3455 existing_inner
3456 .entry(ik.clone())
3457 .or_insert_with(|| iv.clone());
3458 }
3459 }
3460 } else {
3461 existing_map.insert(k.clone(), v.clone());
3462 }
3463 }
3464 }
3465 }
3466 return;
3467 }
3468 }
3469 if let (Some(existing_map), serde_json::Value::Object(new_map)) =
3471 (existing.as_object_mut(), &bo4e)
3472 {
3473 for (k, v) in new_map {
3474 if let Some(existing_v) = existing_map.get_mut(k) {
3475 if let (Some(existing_inner), Some(new_inner)) =
3477 (existing_v.as_object_mut(), v.as_object())
3478 {
3479 for (ik, iv) in new_inner {
3480 existing_inner
3481 .entry(ik.clone())
3482 .or_insert_with(|| iv.clone());
3483 }
3484 }
3485 } else {
3487 existing_map.insert(k.clone(), v.clone());
3488 }
3489 }
3490 return;
3491 }
3492 }
3493 result.insert(entity.to_string(), bo4e);
3494}
3495
3496fn is_map_keyed_object(value: &serde_json::Value) -> bool {
3507 let Some(obj) = value.as_object() else {
3508 return false;
3509 };
3510 if obj.is_empty() {
3511 return false;
3512 }
3513 obj.iter().all(|(k, v)| {
3515 k.len() <= 5
3516 && k.chars()
3517 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
3518 && v.is_object()
3519 })
3520}
3521
3522fn find_qualifier_companion_field(
3531 definitions: &[crate::definition::MappingDefinition],
3532 entity: &str,
3533) -> Option<String> {
3534 for def in definitions {
3535 if def.meta.entity != *entity {
3536 continue;
3537 }
3538 let disc = def.meta.discriminator.as_deref()?;
3539 let (disc_path, _) = disc.split_once('=')?;
3540 let disc_path_lower = disc_path.to_lowercase();
3541
3542 let sections: Vec<&indexmap::IndexMap<String, FieldMapping>> = [
3545 def.companion_fields.as_ref(),
3546 Some(&def.fields),
3547 ]
3548 .into_iter()
3549 .flatten()
3550 .collect();
3551
3552 for section in sections {
3553 for (path, mapping) in section {
3554 let cf_path = path.to_lowercase();
3555 let matches = cf_path == disc_path_lower
3556 || format!("{}.0", cf_path) == disc_path_lower;
3557 if matches {
3558 let target = match mapping {
3559 FieldMapping::Simple(t) => t.as_str(),
3560 FieldMapping::Structured(s) => s.target.as_str(),
3561 FieldMapping::Nested(_) => continue,
3562 };
3563 if !target.is_empty() {
3564 return Some(target.to_string());
3565 }
3566 }
3567 }
3568 }
3569 }
3570 None
3571}
3572
3573fn extract_child_from_parent(
3582 entities: &serde_json::Value,
3583 definitions: &[MappingDefinition],
3584 child_def: &MappingDefinition,
3585) -> Option<serde_json::Value> {
3586 extract_child_from_parent_with_indices(entities, definitions, child_def).map(|(v, _)| v)
3587}
3588
3589fn extract_child_from_parent_with_indices(
3594 entities: &serde_json::Value,
3595 definitions: &[MappingDefinition],
3596 child_def: &MappingDefinition,
3597) -> Option<(serde_json::Value, Vec<usize>)> {
3598 let parts: Vec<&str> = child_def.meta.source_group.split('.').collect();
3599 if parts.len() < 2 {
3600 return None;
3601 }
3602 let parent_group = parts[0];
3603 let parent_def = definitions
3604 .iter()
3605 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_def.meta.entity)?;
3606 let parent_key = to_camel_case(&parent_def.meta.entity);
3607 let child_key = to_camel_case(&child_def.meta.entity);
3608 let parent_value = entities.get(&parent_key)?;
3609
3610 if let Some(parent_map) = parent_value.as_object() {
3612 if is_map_keyed_value(parent_map) {
3613 let mut children: Vec<serde_json::Value> = Vec::new();
3614 let mut indices: Vec<usize> = Vec::new();
3615 for (i, (_key, inner)) in parent_map.iter().enumerate() {
3616 if let Some(child) = inner.get(&child_key) {
3617 if !child.is_null() {
3618 children.push(child.clone());
3619 indices.push(i);
3620 }
3621 }
3622 }
3623 return match children.len() {
3624 0 => None,
3625 1 => Some((children.into_iter().next().unwrap(), indices)),
3626 _ => Some((serde_json::Value::Array(children), indices)),
3627 };
3628 }
3629 }
3630
3631 if let Some(parent_arr) = parent_value.as_array() {
3633 let mut children: Vec<serde_json::Value> = Vec::new();
3634 let mut indices: Vec<usize> = Vec::new();
3635 for (i, item) in parent_arr.iter().enumerate() {
3636 if let Some(child) = item.get(&child_key) {
3637 if !child.is_null() {
3638 children.push(child.clone());
3639 indices.push(i);
3640 }
3641 }
3642 }
3643 return match children.len() {
3644 0 => None,
3645 1 => Some((children.into_iter().next().unwrap(), indices)),
3646 _ => Some((serde_json::Value::Array(children), indices)),
3647 };
3648 }
3649
3650 let child = parent_value.get(&child_key)?;
3652 if child.is_null() {
3653 return None;
3654 }
3655 Some((child.clone(), vec![0]))
3656}
3657
3658fn nest_child_entities_in_result(
3664 result: &mut serde_json::Map<String, serde_json::Value>,
3665 definitions: &[MappingDefinition],
3666 nesting_info: &std::collections::HashMap<String, Vec<usize>>,
3667) {
3668 let mut nesting_pairs: Vec<(String, String, String, Option<String>)> = Vec::new();
3671 for def in definitions {
3672 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
3673 if parts.len() < 2 {
3674 continue;
3675 }
3676 let parent_group = parts[0];
3677 let child_entity = def.meta.entity.clone();
3678 let child_has_parent_level_def = definitions
3682 .iter()
3683 .any(|d| d.meta.source_group == parent_group && d.meta.entity == child_entity);
3684 if child_has_parent_level_def {
3685 continue;
3686 }
3687 let parent_entity = definitions
3689 .iter()
3690 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_entity)
3691 .map(|d| d.meta.entity.clone());
3692 if let Some(ref parent_entity) = parent_entity {
3693 let child_key_lc = to_camel_case(&child_entity);
3698 let parent_defs: Vec<_> = definitions
3699 .iter()
3700 .filter(|d| d.meta.entity == *parent_entity)
3701 .collect();
3702 let has_conflicting_field = parent_defs.iter().any(|pd| {
3703 pd.fields.values().any(|fm| {
3704 let target = match fm {
3705 crate::definition::FieldMapping::Simple(t) => t.as_str(),
3706 crate::definition::FieldMapping::Structured(s) => s.target.as_str(),
3707 crate::definition::FieldMapping::Nested(_) => "",
3708 };
3709 target.starts_with(&child_key_lc)
3710 && target.get(child_key_lc.len()..child_key_lc.len() + 1) == Some(".")
3711 })
3712 });
3713 if has_conflicting_field {
3714 continue;
3715 }
3716 if nesting_pairs
3718 .iter()
3719 .any(|(_, pe, ce, _)| *pe == *parent_entity && *ce == child_entity)
3720 {
3721 continue;
3722 }
3723 nesting_pairs.push((
3724 parent_group.to_string(),
3725 parent_entity.clone(),
3726 child_entity,
3727 def.meta.source_path.clone(),
3728 ));
3729 }
3730 }
3731
3732 for (_parent_group, parent_entity, child_entity, child_source_path) in nesting_pairs {
3733 let parent_key = to_camel_case(&parent_entity);
3734 let child_key = to_camel_case(&child_entity);
3735
3736 let child_value = match result.remove(&child_key) {
3738 Some(v) => v,
3739 None => continue,
3740 };
3741
3742 let Some(parent_value) = result.get_mut(&parent_key) else {
3747 result.insert(child_key, child_value);
3749 continue;
3750 };
3751 if parent_value.is_array() {
3752 result.insert(child_key, child_value);
3753 continue;
3754 }
3755
3756 let distribution = child_source_path
3758 .as_deref()
3759 .and_then(|sp| nesting_info.get(sp));
3760
3761 let child_items: Vec<(usize, &serde_json::Value)> = match &child_value {
3763 serde_json::Value::Array(arr) => arr.iter().enumerate().collect(),
3764 other => vec![(0, other)],
3765 };
3766
3767 let insert_or_append =
3770 |obj: &mut serde_json::Map<String, serde_json::Value>,
3771 key: &str,
3772 val: &serde_json::Value| {
3773 match obj.get_mut(key) {
3774 Some(existing) => {
3775 if !existing.is_array() {
3777 let prev = existing.take();
3778 *existing = serde_json::Value::Array(vec![prev]);
3779 }
3780 if let Some(arr) = existing.as_array_mut() {
3781 arr.push(val.clone());
3782 }
3783 }
3784 None => {
3785 obj.insert(key.to_string(), val.clone());
3786 }
3787 }
3788 };
3789
3790 if let Some(parent_map) = parent_value.as_object_mut() {
3792 if is_map_keyed_value(parent_map) {
3793 let keys: Vec<String> = parent_map.keys().cloned().collect();
3795 for (i, child_item) in &child_items {
3796 let target_idx = distribution
3797 .and_then(|dist| dist.get(*i))
3798 .copied()
3799 .unwrap_or(0);
3800 if let Some(key) = keys.get(target_idx) {
3801 if let Some(inner) = parent_map.get_mut(key).and_then(|v| v.as_object_mut())
3802 {
3803 insert_or_append(inner, &child_key, child_item);
3804 }
3805 }
3806 }
3807 continue;
3808 }
3809 }
3810
3811 if let Some(parent_arr) = parent_value.as_array_mut() {
3813 for (i, child_item) in &child_items {
3814 let target_idx = distribution
3815 .and_then(|dist| dist.get(*i))
3816 .copied()
3817 .unwrap_or(0);
3818 if let Some(parent_obj) =
3819 parent_arr.get_mut(target_idx).and_then(|v| v.as_object_mut())
3820 {
3821 insert_or_append(parent_obj, &child_key, child_item);
3822 }
3823 }
3824 continue;
3825 }
3826
3827 if let Some(parent_obj) = parent_value.as_object_mut() {
3829 for (_i, child_item) in &child_items {
3830 insert_or_append(parent_obj, &child_key, child_item);
3831 }
3832 continue;
3833 }
3834
3835 result.insert(child_key, child_value);
3837 }
3838}
3839
3840fn is_map_keyed_value(map: &serde_json::Map<String, serde_json::Value>) -> bool {
3842 if map.is_empty() {
3843 return false;
3844 }
3845 map.values().all(|v| v.is_object())
3846 && map.keys().all(|k| k.len() <= 5 || k.chars().all(|c| c.is_ascii_uppercase() || c.is_ascii_digit()))
3847}
3848
3849fn to_camel_case(name: &str) -> String {
3850 let mut chars = name.chars();
3851 match chars.next() {
3852 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
3853 None => String::new(),
3854 }
3855}
3856
3857fn set_nested_value(map: &mut serde_json::Map<String, serde_json::Value>, path: &str, val: String) {
3860 set_nested_value_json(map, path, serde_json::Value::String(val));
3861}
3862
3863fn set_nested_value_json(
3865 map: &mut serde_json::Map<String, serde_json::Value>,
3866 path: &str,
3867 val: serde_json::Value,
3868) {
3869 if let Some((prefix, leaf)) = path.rsplit_once('.') {
3870 let mut current = map;
3871 for part in prefix.split('.') {
3872 let entry = current
3873 .entry(part.to_string())
3874 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
3875 current = entry.as_object_mut().expect("expected object in path");
3876 }
3877 current.insert(leaf.to_string(), val);
3878 } else {
3879 map.insert(path.to_string(), val);
3880 }
3881}
3882
3883#[derive(serde::Serialize, serde::Deserialize)]
3888pub struct VariantCache {
3889 pub message_defs: Vec<MappingDefinition>,
3891 pub transaction_defs: HashMap<String, Vec<MappingDefinition>>,
3893 pub combined_defs: HashMap<String, Vec<MappingDefinition>>,
3895 #[serde(default)]
3897 pub code_lookups: HashMap<String, crate::code_lookup::CodeLookup>,
3898 #[serde(default)]
3900 pub mig_schema: Option<mig_types::schema::mig::MigSchema>,
3901 #[serde(default)]
3903 pub segment_structure: Option<crate::segment_structure::SegmentStructure>,
3904 #[serde(default)]
3907 pub pid_segment_numbers: HashMap<String, Vec<String>>,
3908 #[serde(default)]
3911 pub pid_requirements: HashMap<String, crate::pid_requirements::PidRequirements>,
3912 #[serde(default)]
3916 pub tx_groups: HashMap<String, String>,
3917}
3918
3919impl VariantCache {
3920 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
3922 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
3923 path: path.display().to_string(),
3924 message: e.to_string(),
3925 })?;
3926 if let Some(parent) = path.parent() {
3927 std::fs::create_dir_all(parent)?;
3928 }
3929 std::fs::write(path, encoded)?;
3930 Ok(())
3931 }
3932
3933 pub fn load(path: &Path) -> Result<Self, MappingError> {
3935 let bytes = std::fs::read(path)?;
3936 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
3937 path: path.display().to_string(),
3938 message: e.to_string(),
3939 })
3940 }
3941
3942 pub fn tx_group(&self, pid: &str) -> Option<&str> {
3946 self.tx_groups
3947 .get(&format!("pid_{pid}"))
3948 .map(|s| s.as_str())
3949 }
3950
3951 pub fn msg_engine(&self) -> MappingEngine {
3953 MappingEngine::from_definitions(self.message_defs.clone())
3954 }
3955
3956 pub fn tx_engine(&self, pid: &str) -> Option<MappingEngine> {
3959 self.transaction_defs
3960 .get(&format!("pid_{pid}"))
3961 .map(|defs| MappingEngine::from_definitions(defs.clone()))
3962 }
3963
3964 pub fn filtered_mig(&self, pid: &str) -> Option<mig_types::schema::mig::MigSchema> {
3967 let mig = self.mig_schema.as_ref()?;
3968 let numbers = self.pid_segment_numbers.get(&format!("pid_{pid}"))?;
3969 let number_set: std::collections::HashSet<String> = numbers.iter().cloned().collect();
3970 Some(mig_assembly::pid_filter::filter_mig_for_pid(
3971 mig,
3972 &number_set,
3973 ))
3974 }
3975}
3976
3977#[derive(serde::Serialize, serde::Deserialize)]
3982pub struct DataBundle {
3983 pub format_version: String,
3984 pub bundle_version: u32,
3985 pub variants: HashMap<String, VariantCache>,
3986}
3987
3988impl DataBundle {
3989 pub const CURRENT_VERSION: u32 = 2;
3990
3991 pub fn variant(&self, name: &str) -> Option<&VariantCache> {
3992 self.variants.get(name)
3993 }
3994
3995 pub fn write_to<W: std::io::Write>(&self, writer: &mut W) -> Result<(), MappingError> {
3996 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
3997 path: "<stream>".to_string(),
3998 message: e.to_string(),
3999 })?;
4000 writer.write_all(&encoded).map_err(MappingError::Io)
4001 }
4002
4003 pub fn read_from<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
4004 let mut bytes = Vec::new();
4005 reader.read_to_end(&mut bytes).map_err(MappingError::Io)?;
4006 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
4007 path: "<stream>".to_string(),
4008 message: e.to_string(),
4009 })
4010 }
4011
4012 pub fn read_from_checked<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
4013 let bundle = Self::read_from(reader)?;
4014 if bundle.bundle_version != Self::CURRENT_VERSION {
4015 return Err(MappingError::CacheRead {
4016 path: "<stream>".to_string(),
4017 message: format!(
4018 "Incompatible bundle version {}, expected version {}. \
4019 Run `edifact-data update` to fetch compatible bundles.",
4020 bundle.bundle_version,
4021 Self::CURRENT_VERSION
4022 ),
4023 });
4024 }
4025 Ok(bundle)
4026 }
4027
4028 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
4029 if let Some(parent) = path.parent() {
4030 std::fs::create_dir_all(parent)?;
4031 }
4032 let mut file = std::fs::File::create(path).map_err(MappingError::Io)?;
4033 self.write_to(&mut file)
4034 }
4035
4036 pub fn load(path: &Path) -> Result<Self, MappingError> {
4037 let mut file = std::fs::File::open(path).map_err(MappingError::Io)?;
4038 Self::read_from_checked(&mut file)
4039 }
4040}
4041
4042fn sort_variant_reps_by_mig(
4055 child_groups: &mut [AssembledGroup],
4056 mig: &MigSchema,
4057 transaction_group: &str,
4058) {
4059 let tx_def = match mig
4060 .segment_groups
4061 .iter()
4062 .find(|sg| sg.id == transaction_group)
4063 {
4064 Some(d) => d,
4065 None => return,
4066 };
4067
4068 for cg in child_groups.iter_mut() {
4069 if cg.repetitions.len() <= 1 {
4070 continue;
4071 }
4072
4073 let variant_defs: Vec<(usize, &mig_types::schema::mig::MigSegmentGroup)> = tx_def
4075 .nested_groups
4076 .iter()
4077 .enumerate()
4078 .filter(|(_, ng)| ng.id == cg.group_id && ng.variant_code.is_some())
4079 .collect();
4080
4081 if variant_defs.is_empty() {
4082 continue;
4083 }
4084
4085 cg.repetitions.sort_by_key(|rep| {
4088 let entry_seg = rep.segments.first();
4089 for &(mig_pos, variant_def) in &variant_defs {
4090 let (ei, ci) = variant_def.variant_qualifier_position.unwrap_or((0, 0));
4091 let actual_qual = entry_seg
4092 .and_then(|s| s.elements.get(ei))
4093 .and_then(|e| e.get(ci))
4094 .map(|s| s.as_str())
4095 .unwrap_or("");
4096 let matches = if !variant_def.variant_codes.is_empty() {
4097 variant_def
4098 .variant_codes
4099 .iter()
4100 .any(|c| actual_qual.eq_ignore_ascii_case(c))
4101 } else if let Some(ref expected_code) = variant_def.variant_code {
4102 actual_qual.eq_ignore_ascii_case(expected_code)
4103 } else {
4104 false
4105 };
4106 if matches {
4107 return mig_pos;
4108 }
4109 }
4110 usize::MAX });
4112 }
4113}
4114
4115#[cfg(test)]
4116mod variant_cache_helper_tests {
4117 use super::*;
4118
4119 fn make_test_cache() -> VariantCache {
4120 let mut tx_groups = HashMap::new();
4121 tx_groups.insert("pid_55001".to_string(), "SG4".to_string());
4122 tx_groups.insert("pid_21007".to_string(), "SG14".to_string());
4123
4124 let mut transaction_defs = HashMap::new();
4125 transaction_defs.insert("pid_55001".to_string(), vec![]);
4126 transaction_defs.insert("pid_21007".to_string(), vec![]);
4127
4128 VariantCache {
4129 message_defs: vec![],
4130 transaction_defs,
4131 combined_defs: HashMap::new(),
4132 code_lookups: HashMap::new(),
4133 mig_schema: None,
4134 segment_structure: None,
4135 pid_segment_numbers: HashMap::new(),
4136 pid_requirements: HashMap::new(),
4137 tx_groups,
4138 }
4139 }
4140
4141 #[test]
4142 fn test_tx_group_returns_correct_group() {
4143 let vc = make_test_cache();
4144 assert_eq!(vc.tx_group("55001").unwrap(), "SG4");
4145 assert_eq!(vc.tx_group("21007").unwrap(), "SG14");
4146 }
4147
4148 #[test]
4149 fn test_tx_group_unknown_pid_returns_none() {
4150 let vc = make_test_cache();
4151 assert!(vc.tx_group("99999").is_none());
4152 }
4153
4154 #[test]
4155 fn test_msg_engine_returns_engine() {
4156 let vc = make_test_cache();
4157 let engine = vc.msg_engine();
4158 assert_eq!(engine.definitions().len(), 0);
4159 }
4160
4161 #[test]
4162 fn test_tx_engine_returns_engine_for_known_pid() {
4163 let vc = make_test_cache();
4164 assert!(vc.tx_engine("55001").is_some());
4165 }
4166
4167 #[test]
4168 fn test_tx_engine_returns_none_for_unknown_pid() {
4169 let vc = make_test_cache();
4170 assert!(vc.tx_engine("99999").is_none());
4171 }
4172}
4173
4174#[cfg(test)]
4175mod tests {
4176 use super::*;
4177 use crate::definition::{MappingDefinition, MappingMeta, StructuredFieldMapping};
4178 use indexmap::IndexMap;
4179
4180 fn make_def(fields: IndexMap<String, FieldMapping>) -> MappingDefinition {
4181 MappingDefinition {
4182 meta: MappingMeta {
4183 entity: "Test".to_string(),
4184 bo4e_type: "Test".to_string(),
4185 companion_type: None,
4186 source_group: "SG4".to_string(),
4187 source_path: None,
4188 discriminator: None,
4189 repeat_on_tag: None,
4190 },
4191 fields,
4192 companion_fields: None,
4193 complex_handlers: None,
4194 }
4195 }
4196
4197 #[test]
4198 fn test_map_interchange_single_transaction_backward_compat() {
4199 use mig_assembly::assembler::*;
4200
4201 let tree = AssembledTree {
4203 segments: vec![
4204 AssembledSegment {
4205 tag: "UNH".to_string(),
4206 elements: vec![vec!["001".to_string()]],
4207 },
4208 AssembledSegment {
4209 tag: "BGM".to_string(),
4210 elements: vec![vec!["E01".to_string()], vec!["DOC001".to_string()]],
4211 },
4212 ],
4213 groups: vec![
4214 AssembledGroup {
4215 group_id: "SG2".to_string(),
4216 repetitions: vec![AssembledGroupInstance {
4217 segments: vec![AssembledSegment {
4218 tag: "NAD".to_string(),
4219 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4220 }],
4221 child_groups: vec![],
4222 skipped_segments: vec![],
4223 }],
4224 },
4225 AssembledGroup {
4226 group_id: "SG4".to_string(),
4227 repetitions: vec![AssembledGroupInstance {
4228 segments: vec![AssembledSegment {
4229 tag: "IDE".to_string(),
4230 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4231 }],
4232 child_groups: vec![AssembledGroup {
4233 group_id: "SG5".to_string(),
4234 repetitions: vec![AssembledGroupInstance {
4235 segments: vec![AssembledSegment {
4236 tag: "LOC".to_string(),
4237 elements: vec![
4238 vec!["Z16".to_string()],
4239 vec!["DE000111222333".to_string()],
4240 ],
4241 }],
4242 child_groups: vec![],
4243 skipped_segments: vec![],
4244 }],
4245 }],
4246 skipped_segments: vec![],
4247 }],
4248 },
4249 ],
4250 post_group_start: 2,
4251 inter_group_segments: std::collections::BTreeMap::new(),
4252 };
4253
4254 let msg_engine = MappingEngine::from_definitions(vec![]);
4256
4257 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4259 tx_fields.insert(
4260 "ide.1".to_string(),
4261 FieldMapping::Simple("vorgangId".to_string()),
4262 );
4263 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4264 malo_fields.insert(
4265 "loc.1".to_string(),
4266 FieldMapping::Simple("marktlokationsId".to_string()),
4267 );
4268
4269 let tx_engine = MappingEngine::from_definitions(vec![
4270 MappingDefinition {
4271 meta: MappingMeta {
4272 entity: "Prozessdaten".to_string(),
4273 bo4e_type: "Prozessdaten".to_string(),
4274 companion_type: None,
4275 source_group: "SG4".to_string(),
4276 source_path: None,
4277 discriminator: None,
4278 repeat_on_tag: None,
4279 },
4280 fields: tx_fields,
4281 companion_fields: None,
4282 complex_handlers: None,
4283 },
4284 MappingDefinition {
4285 meta: MappingMeta {
4286 entity: "Marktlokation".to_string(),
4287 bo4e_type: "Marktlokation".to_string(),
4288 companion_type: None,
4289 source_group: "SG4.SG5".to_string(),
4290 source_path: None,
4291 discriminator: None,
4292 repeat_on_tag: None,
4293 },
4294 fields: malo_fields,
4295 companion_fields: None,
4296 complex_handlers: None,
4297 },
4298 ]);
4299
4300 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
4301
4302 assert_eq!(result.transaktionen.len(), 1);
4303 assert_eq!(
4304 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
4305 .as_str()
4306 .unwrap(),
4307 "TX001"
4308 );
4309 assert_eq!(
4311 result.transaktionen[0].stammdaten["prozessdaten"]["marktlokation"]
4312 ["marktlokationsId"]
4313 .as_str()
4314 .unwrap(),
4315 "DE000111222333"
4316 );
4317 }
4318
4319 #[test]
4320 fn test_map_reverse_pads_intermediate_empty_elements() {
4321 let mut fields = IndexMap::new();
4323 fields.insert(
4324 "nad.0".to_string(),
4325 FieldMapping::Structured(StructuredFieldMapping {
4326 target: String::new(),
4327 transform: None,
4328 when: None,
4329 default: Some("Z09".to_string()),
4330 enum_map: None,
4331 when_filled: None,
4332 also_target: None,
4333 also_enum_map: None,
4334 }),
4335 );
4336 fields.insert(
4337 "nad.3.0".to_string(),
4338 FieldMapping::Simple("name".to_string()),
4339 );
4340 fields.insert(
4341 "nad.3.1".to_string(),
4342 FieldMapping::Simple("vorname".to_string()),
4343 );
4344
4345 let def = make_def(fields);
4346 let engine = MappingEngine::from_definitions(vec![]);
4347
4348 let bo4e = serde_json::json!({
4349 "name": "Muster",
4350 "vorname": "Max"
4351 });
4352
4353 let instance = engine.map_reverse(&bo4e, &def);
4354 assert_eq!(instance.segments.len(), 1);
4355
4356 let nad = &instance.segments[0];
4357 assert_eq!(nad.tag, "NAD");
4358 assert_eq!(nad.elements.len(), 4);
4359 assert_eq!(nad.elements[0], vec!["Z09"]);
4360 assert_eq!(nad.elements[1], vec![""]);
4362 assert_eq!(nad.elements[2], vec![""]);
4363 assert_eq!(nad.elements[3][0], "Muster");
4364 assert_eq!(nad.elements[3][1], "Max");
4365 }
4366
4367 #[test]
4368 fn test_map_reverse_no_padding_when_contiguous() {
4369 let mut fields = IndexMap::new();
4371 fields.insert(
4372 "dtm.0.0".to_string(),
4373 FieldMapping::Structured(StructuredFieldMapping {
4374 target: String::new(),
4375 transform: None,
4376 when: None,
4377 default: Some("92".to_string()),
4378 enum_map: None,
4379 when_filled: None,
4380 also_target: None,
4381 also_enum_map: None,
4382 }),
4383 );
4384 fields.insert(
4385 "dtm.0.1".to_string(),
4386 FieldMapping::Simple("value".to_string()),
4387 );
4388 fields.insert(
4389 "dtm.0.2".to_string(),
4390 FieldMapping::Structured(StructuredFieldMapping {
4391 target: String::new(),
4392 transform: None,
4393 when: None,
4394 default: Some("303".to_string()),
4395 enum_map: None,
4396 when_filled: None,
4397 also_target: None,
4398 also_enum_map: None,
4399 }),
4400 );
4401
4402 let def = make_def(fields);
4403 let engine = MappingEngine::from_definitions(vec![]);
4404
4405 let bo4e = serde_json::json!({ "value": "20250531" });
4406
4407 let instance = engine.map_reverse(&bo4e, &def);
4408 let dtm = &instance.segments[0];
4409 assert_eq!(dtm.elements.len(), 1);
4411 assert_eq!(dtm.elements[0], vec!["92", "20250531", "303"]);
4412 }
4413
4414 #[test]
4415 fn test_map_message_level_extracts_sg2_only() {
4416 use mig_assembly::assembler::*;
4417
4418 let tree = AssembledTree {
4420 segments: vec![
4421 AssembledSegment {
4422 tag: "UNH".to_string(),
4423 elements: vec![vec!["001".to_string()]],
4424 },
4425 AssembledSegment {
4426 tag: "BGM".to_string(),
4427 elements: vec![vec!["E01".to_string()]],
4428 },
4429 ],
4430 groups: vec![
4431 AssembledGroup {
4432 group_id: "SG2".to_string(),
4433 repetitions: vec![AssembledGroupInstance {
4434 segments: vec![AssembledSegment {
4435 tag: "NAD".to_string(),
4436 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4437 }],
4438 child_groups: vec![],
4439 skipped_segments: vec![],
4440 }],
4441 },
4442 AssembledGroup {
4443 group_id: "SG4".to_string(),
4444 repetitions: vec![AssembledGroupInstance {
4445 segments: vec![AssembledSegment {
4446 tag: "IDE".to_string(),
4447 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4448 }],
4449 child_groups: vec![],
4450 skipped_segments: vec![],
4451 }],
4452 },
4453 ],
4454 post_group_start: 2,
4455 inter_group_segments: std::collections::BTreeMap::new(),
4456 };
4457
4458 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4460 msg_fields.insert(
4461 "nad.0".to_string(),
4462 FieldMapping::Simple("marktrolle".to_string()),
4463 );
4464 msg_fields.insert(
4465 "nad.1".to_string(),
4466 FieldMapping::Simple("rollencodenummer".to_string()),
4467 );
4468 let msg_def = MappingDefinition {
4469 meta: MappingMeta {
4470 entity: "Marktteilnehmer".to_string(),
4471 bo4e_type: "Marktteilnehmer".to_string(),
4472 companion_type: None,
4473 source_group: "SG2".to_string(),
4474 source_path: None,
4475 discriminator: None,
4476 repeat_on_tag: None,
4477 },
4478 fields: msg_fields,
4479 companion_fields: None,
4480 complex_handlers: None,
4481 };
4482
4483 let engine = MappingEngine::from_definitions(vec![msg_def.clone()]);
4484 let result = engine.map_all_forward(&tree);
4485
4486 assert!(result.get("marktteilnehmer").is_some());
4488 let mt = &result["marktteilnehmer"];
4489 assert_eq!(mt["marktrolle"].as_str().unwrap(), "MS");
4490 assert_eq!(mt["rollencodenummer"].as_str().unwrap(), "9900123");
4491 }
4492
4493 #[test]
4494 fn test_map_transaction_scoped_to_sg4_instance() {
4495 use mig_assembly::assembler::*;
4496
4497 let tree = AssembledTree {
4499 segments: vec![
4500 AssembledSegment {
4501 tag: "UNH".to_string(),
4502 elements: vec![vec!["001".to_string()]],
4503 },
4504 AssembledSegment {
4505 tag: "BGM".to_string(),
4506 elements: vec![vec!["E01".to_string()]],
4507 },
4508 ],
4509 groups: vec![AssembledGroup {
4510 group_id: "SG4".to_string(),
4511 repetitions: vec![AssembledGroupInstance {
4512 segments: vec![AssembledSegment {
4513 tag: "IDE".to_string(),
4514 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4515 }],
4516 child_groups: vec![AssembledGroup {
4517 group_id: "SG5".to_string(),
4518 repetitions: vec![AssembledGroupInstance {
4519 segments: vec![AssembledSegment {
4520 tag: "LOC".to_string(),
4521 elements: vec![
4522 vec!["Z16".to_string()],
4523 vec!["DE000111222333".to_string()],
4524 ],
4525 }],
4526 child_groups: vec![],
4527 skipped_segments: vec![],
4528 }],
4529 }],
4530 skipped_segments: vec![],
4531 }],
4532 }],
4533 post_group_start: 2,
4534 inter_group_segments: std::collections::BTreeMap::new(),
4535 };
4536
4537 let mut proz_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4539 proz_fields.insert(
4540 "ide.1".to_string(),
4541 FieldMapping::Simple("vorgangId".to_string()),
4542 );
4543 let proz_def = MappingDefinition {
4544 meta: MappingMeta {
4545 entity: "Prozessdaten".to_string(),
4546 bo4e_type: "Prozessdaten".to_string(),
4547 companion_type: None,
4548 source_group: "".to_string(), source_path: None,
4550 discriminator: None,
4551 repeat_on_tag: None,
4552 },
4553 fields: proz_fields,
4554 companion_fields: None,
4555 complex_handlers: None,
4556 };
4557
4558 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4559 malo_fields.insert(
4560 "loc.1".to_string(),
4561 FieldMapping::Simple("marktlokationsId".to_string()),
4562 );
4563 let malo_def = MappingDefinition {
4564 meta: MappingMeta {
4565 entity: "Marktlokation".to_string(),
4566 bo4e_type: "Marktlokation".to_string(),
4567 companion_type: None,
4568 source_group: "SG5".to_string(), source_path: None,
4570 discriminator: None,
4571 repeat_on_tag: None,
4572 },
4573 fields: malo_fields,
4574 companion_fields: None,
4575 complex_handlers: None,
4576 };
4577
4578 let tx_engine = MappingEngine::from_definitions(vec![proz_def, malo_def]);
4579
4580 let sg4 = &tree.groups[0]; let sg4_instance = &sg4.repetitions[0];
4583 let sub_tree = sg4_instance.as_assembled_tree();
4584
4585 let result = tx_engine.map_all_forward(&sub_tree);
4586
4587 assert_eq!(
4589 result["prozessdaten"]["vorgangId"].as_str().unwrap(),
4590 "TX001"
4591 );
4592
4593 assert_eq!(
4595 result["marktlokation"]["marktlokationsId"]
4596 .as_str()
4597 .unwrap(),
4598 "DE000111222333"
4599 );
4600 }
4601
4602 #[test]
4603 fn test_map_interchange_produces_full_hierarchy() {
4604 use mig_assembly::assembler::*;
4605
4606 let tree = AssembledTree {
4608 segments: vec![
4609 AssembledSegment {
4610 tag: "UNH".to_string(),
4611 elements: vec![vec!["001".to_string()]],
4612 },
4613 AssembledSegment {
4614 tag: "BGM".to_string(),
4615 elements: vec![vec!["E01".to_string()]],
4616 },
4617 ],
4618 groups: vec![
4619 AssembledGroup {
4620 group_id: "SG2".to_string(),
4621 repetitions: vec![AssembledGroupInstance {
4622 segments: vec![AssembledSegment {
4623 tag: "NAD".to_string(),
4624 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4625 }],
4626 child_groups: vec![],
4627 skipped_segments: vec![],
4628 }],
4629 },
4630 AssembledGroup {
4631 group_id: "SG4".to_string(),
4632 repetitions: vec![
4633 AssembledGroupInstance {
4634 segments: vec![AssembledSegment {
4635 tag: "IDE".to_string(),
4636 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4637 }],
4638 child_groups: vec![],
4639 skipped_segments: vec![],
4640 },
4641 AssembledGroupInstance {
4642 segments: vec![AssembledSegment {
4643 tag: "IDE".to_string(),
4644 elements: vec![vec!["24".to_string()], vec!["TX002".to_string()]],
4645 }],
4646 child_groups: vec![],
4647 skipped_segments: vec![],
4648 },
4649 ],
4650 },
4651 ],
4652 post_group_start: 2,
4653 inter_group_segments: std::collections::BTreeMap::new(),
4654 };
4655
4656 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4658 msg_fields.insert(
4659 "nad.0".to_string(),
4660 FieldMapping::Simple("marktrolle".to_string()),
4661 );
4662 let msg_defs = vec![MappingDefinition {
4663 meta: MappingMeta {
4664 entity: "Marktteilnehmer".to_string(),
4665 bo4e_type: "Marktteilnehmer".to_string(),
4666 companion_type: None,
4667 source_group: "SG2".to_string(),
4668 source_path: None,
4669 discriminator: None,
4670 repeat_on_tag: None,
4671 },
4672 fields: msg_fields,
4673 companion_fields: None,
4674 complex_handlers: None,
4675 }];
4676
4677 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4679 tx_fields.insert(
4680 "ide.1".to_string(),
4681 FieldMapping::Simple("vorgangId".to_string()),
4682 );
4683 let tx_defs = vec![MappingDefinition {
4684 meta: MappingMeta {
4685 entity: "Prozessdaten".to_string(),
4686 bo4e_type: "Prozessdaten".to_string(),
4687 companion_type: None,
4688 source_group: "SG4".to_string(),
4689 source_path: None,
4690 discriminator: None,
4691 repeat_on_tag: None,
4692 },
4693 fields: tx_fields,
4694 companion_fields: None,
4695 complex_handlers: None,
4696 }];
4697
4698 let msg_engine = MappingEngine::from_definitions(msg_defs);
4699 let tx_engine = MappingEngine::from_definitions(tx_defs);
4700
4701 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
4702
4703 assert!(result.stammdaten["marktteilnehmer"].is_object());
4705 assert_eq!(
4706 result.stammdaten["marktteilnehmer"]["marktrolle"]
4707 .as_str()
4708 .unwrap(),
4709 "MS"
4710 );
4711
4712 assert_eq!(result.transaktionen.len(), 2);
4714 assert_eq!(
4715 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
4716 .as_str()
4717 .unwrap(),
4718 "TX001"
4719 );
4720 assert_eq!(
4721 result.transaktionen[1].stammdaten["prozessdaten"]["vorgangId"]
4722 .as_str()
4723 .unwrap(),
4724 "TX002"
4725 );
4726 }
4727
4728 #[test]
4729 fn test_map_reverse_with_segment_structure_pads_trailing() {
4730 let mut fields = IndexMap::new();
4732 fields.insert(
4733 "sts.0".to_string(),
4734 FieldMapping::Structured(StructuredFieldMapping {
4735 target: String::new(),
4736 transform: None,
4737 when: None,
4738 default: Some("7".to_string()),
4739 enum_map: None,
4740 when_filled: None,
4741 also_target: None,
4742 also_enum_map: None,
4743 }),
4744 );
4745 fields.insert(
4746 "sts.2".to_string(),
4747 FieldMapping::Simple("grund".to_string()),
4748 );
4749
4750 let def = make_def(fields);
4751
4752 let mut counts = std::collections::HashMap::new();
4754 counts.insert("STS".to_string(), 5usize);
4755 let ss = SegmentStructure {
4756 element_counts: counts,
4757 };
4758
4759 let engine = MappingEngine::from_definitions(vec![]).with_segment_structure(ss);
4760
4761 let bo4e = serde_json::json!({ "grund": "E01" });
4762
4763 let instance = engine.map_reverse(&bo4e, &def);
4764 let sts = &instance.segments[0];
4765 assert_eq!(sts.elements.len(), 5);
4768 assert_eq!(sts.elements[0], vec!["7"]);
4769 assert_eq!(sts.elements[1], vec![""]);
4770 assert_eq!(sts.elements[2], vec!["E01"]);
4771 assert_eq!(sts.elements[3], vec![""]);
4772 assert_eq!(sts.elements[4], vec![""]);
4773 }
4774
4775 #[test]
4776 fn test_extract_companion_fields_with_code_enrichment() {
4777 use crate::code_lookup::CodeLookup;
4778 use mig_assembly::assembler::*;
4779
4780 let schema = serde_json::json!({
4781 "fields": {
4782 "sg4": {
4783 "children": {
4784 "sg8_z01": {
4785 "children": {
4786 "sg10": {
4787 "segments": [{
4788 "id": "CCI",
4789 "elements": [{
4790 "index": 2,
4791 "components": [{
4792 "sub_index": 0,
4793 "type": "code",
4794 "codes": [
4795 {"value": "Z15", "name": "Haushaltskunde"},
4796 {"value": "Z18", "name": "Kein Haushaltskunde"}
4797 ]
4798 }]
4799 }]
4800 }],
4801 "source_group": "SG10"
4802 }
4803 },
4804 "segments": [],
4805 "source_group": "SG8"
4806 }
4807 },
4808 "segments": [],
4809 "source_group": "SG4"
4810 }
4811 }
4812 });
4813
4814 let code_lookup = CodeLookup::from_schema_value(&schema);
4815
4816 let tree = AssembledTree {
4817 segments: vec![],
4818 groups: vec![AssembledGroup {
4819 group_id: "SG4".to_string(),
4820 repetitions: vec![AssembledGroupInstance {
4821 segments: vec![],
4822 child_groups: vec![AssembledGroup {
4823 group_id: "SG8".to_string(),
4824 repetitions: vec![AssembledGroupInstance {
4825 segments: vec![],
4826 child_groups: vec![AssembledGroup {
4827 group_id: "SG10".to_string(),
4828 repetitions: vec![AssembledGroupInstance {
4829 segments: vec![AssembledSegment {
4830 tag: "CCI".to_string(),
4831 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
4832 }],
4833 child_groups: vec![],
4834 skipped_segments: vec![],
4835 }],
4836 }],
4837 skipped_segments: vec![],
4838 }],
4839 }],
4840 skipped_segments: vec![],
4841 }],
4842 }],
4843 post_group_start: 0,
4844 inter_group_segments: std::collections::BTreeMap::new(),
4845 };
4846
4847 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4848 companion_fields.insert(
4849 "cci.2".to_string(),
4850 FieldMapping::Simple("haushaltskunde".to_string()),
4851 );
4852
4853 let def = MappingDefinition {
4854 meta: MappingMeta {
4855 entity: "Marktlokation".to_string(),
4856 bo4e_type: "Marktlokation".to_string(),
4857 companion_type: Some("MarktlokationEdifact".to_string()),
4858 source_group: "SG4.SG8.SG10".to_string(),
4859 source_path: Some("sg4.sg8_z01.sg10".to_string()),
4860 discriminator: None,
4861 repeat_on_tag: None,
4862 },
4863 fields: IndexMap::new(),
4864 companion_fields: Some(companion_fields),
4865 complex_handlers: None,
4866 };
4867
4868 let engine_plain = MappingEngine::from_definitions(vec![]);
4870 let bo4e_plain = engine_plain.map_forward(&tree, &def, 0);
4871 assert_eq!(
4872 bo4e_plain["marktlokationEdifact"]["haushaltskunde"].as_str(),
4873 Some("Z15"),
4874 "Without code lookup, should be plain string"
4875 );
4876
4877 let engine_enriched = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
4879 let bo4e_enriched = engine_enriched.map_forward(&tree, &def, 0);
4880 let hk = &bo4e_enriched["marktlokationEdifact"]["haushaltskunde"];
4881 assert_eq!(hk["code"].as_str(), Some("Z15"));
4882 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
4883 assert!(hk.get("enum").is_none());
4885 }
4886
4887 #[test]
4888 fn test_extract_companion_fields_with_enum_enrichment() {
4889 use crate::code_lookup::CodeLookup;
4890 use mig_assembly::assembler::*;
4891
4892 let schema = serde_json::json!({
4894 "fields": {
4895 "sg4": {
4896 "children": {
4897 "sg8_z01": {
4898 "children": {
4899 "sg10": {
4900 "segments": [{
4901 "id": "CCI",
4902 "elements": [{
4903 "index": 2,
4904 "components": [{
4905 "sub_index": 0,
4906 "type": "code",
4907 "codes": [
4908 {"value": "Z15", "name": "Haushaltskunde", "enum": "HAUSHALTSKUNDE"},
4909 {"value": "Z18", "name": "Kein Haushaltskunde", "enum": "KEIN_HAUSHALTSKUNDE"}
4910 ]
4911 }]
4912 }]
4913 }],
4914 "source_group": "SG10"
4915 }
4916 },
4917 "segments": [],
4918 "source_group": "SG8"
4919 }
4920 },
4921 "segments": [],
4922 "source_group": "SG4"
4923 }
4924 }
4925 });
4926
4927 let code_lookup = CodeLookup::from_schema_value(&schema);
4928
4929 let tree = AssembledTree {
4930 segments: vec![],
4931 groups: vec![AssembledGroup {
4932 group_id: "SG4".to_string(),
4933 repetitions: vec![AssembledGroupInstance {
4934 segments: vec![],
4935 child_groups: vec![AssembledGroup {
4936 group_id: "SG8".to_string(),
4937 repetitions: vec![AssembledGroupInstance {
4938 segments: vec![],
4939 child_groups: vec![AssembledGroup {
4940 group_id: "SG10".to_string(),
4941 repetitions: vec![AssembledGroupInstance {
4942 segments: vec![AssembledSegment {
4943 tag: "CCI".to_string(),
4944 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
4945 }],
4946 child_groups: vec![],
4947 skipped_segments: vec![],
4948 }],
4949 }],
4950 skipped_segments: vec![],
4951 }],
4952 }],
4953 skipped_segments: vec![],
4954 }],
4955 }],
4956 post_group_start: 0,
4957 inter_group_segments: std::collections::BTreeMap::new(),
4958 };
4959
4960 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4961 companion_fields.insert(
4962 "cci.2".to_string(),
4963 FieldMapping::Simple("haushaltskunde".to_string()),
4964 );
4965
4966 let def = MappingDefinition {
4967 meta: MappingMeta {
4968 entity: "Marktlokation".to_string(),
4969 bo4e_type: "Marktlokation".to_string(),
4970 companion_type: Some("MarktlokationEdifact".to_string()),
4971 source_group: "SG4.SG8.SG10".to_string(),
4972 source_path: Some("sg4.sg8_z01.sg10".to_string()),
4973 discriminator: None,
4974 repeat_on_tag: None,
4975 },
4976 fields: IndexMap::new(),
4977 companion_fields: Some(companion_fields),
4978 complex_handlers: None,
4979 };
4980
4981 let engine = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
4982 let bo4e = engine.map_forward(&tree, &def, 0);
4983 let hk = &bo4e["marktlokationEdifact"]["haushaltskunde"];
4984 assert_eq!(hk["code"].as_str(), Some("Z15"));
4985 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
4986 assert_eq!(
4987 hk["enum"].as_str(),
4988 Some("HAUSHALTSKUNDE"),
4989 "enum field should be present"
4990 );
4991 }
4992
4993 #[test]
4994 fn test_reverse_mapping_accepts_enriched_with_enum() {
4995 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4997 companion_fields.insert(
4998 "cci.2".to_string(),
4999 FieldMapping::Simple("haushaltskunde".to_string()),
5000 );
5001
5002 let def = MappingDefinition {
5003 meta: MappingMeta {
5004 entity: "Test".to_string(),
5005 bo4e_type: "Test".to_string(),
5006 companion_type: Some("TestEdifact".to_string()),
5007 source_group: "SG4".to_string(),
5008 source_path: None,
5009 discriminator: None,
5010 repeat_on_tag: None,
5011 },
5012 fields: IndexMap::new(),
5013 companion_fields: Some(companion_fields),
5014 complex_handlers: None,
5015 };
5016
5017 let engine = MappingEngine::from_definitions(vec![]);
5018
5019 let bo4e = serde_json::json!({
5020 "testEdifact": {
5021 "haushaltskunde": {
5022 "code": "Z15",
5023 "meaning": "Haushaltskunde",
5024 "enum": "HAUSHALTSKUNDE"
5025 }
5026 }
5027 });
5028 let instance = engine.map_reverse(&bo4e, &def);
5029 assert_eq!(instance.segments[0].elements[2], vec!["Z15"]);
5030 }
5031
5032 #[test]
5033 fn test_reverse_mapping_accepts_enriched_companion() {
5034 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5036 companion_fields.insert(
5037 "cci.2".to_string(),
5038 FieldMapping::Simple("haushaltskunde".to_string()),
5039 );
5040
5041 let def = MappingDefinition {
5042 meta: MappingMeta {
5043 entity: "Test".to_string(),
5044 bo4e_type: "Test".to_string(),
5045 companion_type: Some("TestEdifact".to_string()),
5046 source_group: "SG4".to_string(),
5047 source_path: None,
5048 discriminator: None,
5049 repeat_on_tag: None,
5050 },
5051 fields: IndexMap::new(),
5052 companion_fields: Some(companion_fields),
5053 complex_handlers: None,
5054 };
5055
5056 let engine = MappingEngine::from_definitions(vec![]);
5057
5058 let bo4e_plain = serde_json::json!({
5060 "testEdifact": {
5061 "haushaltskunde": "Z15"
5062 }
5063 });
5064 let instance_plain = engine.map_reverse(&bo4e_plain, &def);
5065 assert_eq!(instance_plain.segments[0].elements[2], vec!["Z15"]);
5066
5067 let bo4e_enriched = serde_json::json!({
5069 "testEdifact": {
5070 "haushaltskunde": {
5071 "code": "Z15",
5072 "meaning": "Haushaltskunde gem. EnWG"
5073 }
5074 }
5075 });
5076 let instance_enriched = engine.map_reverse(&bo4e_enriched, &def);
5077 assert_eq!(instance_enriched.segments[0].elements[2], vec!["Z15"]);
5078 }
5079
5080 #[test]
5081 fn test_resolve_child_relative_with_source_path() {
5082 let mut map: std::collections::HashMap<String, Vec<usize>> =
5083 std::collections::HashMap::new();
5084 map.insert("sg4.sg8_ze1".to_string(), vec![6]);
5085 map.insert("sg4.sg8_z98".to_string(), vec![0]);
5086
5087 assert_eq!(
5089 resolve_child_relative("SG8.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5090 "SG8:6.SG10"
5091 );
5092
5093 assert_eq!(
5095 resolve_child_relative("SG8:3.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5096 "SG8:3.SG10"
5097 );
5098
5099 assert_eq!(
5101 resolve_child_relative("SG8.SG10", Some("sg4.sg8_unknown.sg10"), &map, 0),
5102 "SG8.SG10"
5103 );
5104
5105 assert_eq!(
5107 resolve_child_relative("SG8.SG10", None, &map, 0),
5108 "SG8.SG10"
5109 );
5110
5111 assert_eq!(
5113 resolve_child_relative("SG8.SG9", Some("sg4.sg8_z98.sg9"), &map, 0),
5114 "SG8:0.SG9"
5115 );
5116
5117 map.insert("sg4.sg8_zf3".to_string(), vec![3, 4]);
5119 assert_eq!(
5120 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 0),
5121 "SG8:3.SG10"
5122 );
5123 assert_eq!(
5124 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 1),
5125 "SG8:4.SG10"
5126 );
5127 }
5128
5129 #[test]
5130 fn test_place_in_groups_returns_rep_index() {
5131 let mut groups: Vec<AssembledGroup> = Vec::new();
5132
5133 let instance = AssembledGroupInstance {
5135 segments: vec![],
5136 child_groups: vec![],
5137 skipped_segments: vec![],
5138 };
5139 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 0);
5140
5141 let instance = AssembledGroupInstance {
5143 segments: vec![],
5144 child_groups: vec![],
5145 skipped_segments: vec![],
5146 };
5147 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 1);
5148
5149 let instance = AssembledGroupInstance {
5151 segments: vec![],
5152 child_groups: vec![],
5153 skipped_segments: vec![],
5154 };
5155 assert_eq!(place_in_groups(&mut groups, "SG8:5", instance), 5);
5156 }
5157
5158 #[test]
5159 fn test_resolve_by_source_path() {
5160 use mig_assembly::assembler::*;
5161
5162 let tree = AssembledTree {
5164 segments: vec![],
5165 groups: vec![AssembledGroup {
5166 group_id: "SG4".to_string(),
5167 repetitions: vec![AssembledGroupInstance {
5168 segments: vec![],
5169 child_groups: vec![AssembledGroup {
5170 group_id: "SG8".to_string(),
5171 repetitions: vec![
5172 AssembledGroupInstance {
5173 segments: vec![AssembledSegment {
5174 tag: "SEQ".to_string(),
5175 elements: vec![vec!["Z98".to_string()]],
5176 }],
5177 child_groups: vec![AssembledGroup {
5178 group_id: "SG10".to_string(),
5179 repetitions: vec![AssembledGroupInstance {
5180 segments: vec![AssembledSegment {
5181 tag: "CCI".to_string(),
5182 elements: vec![vec![], vec![], vec!["ZB3".to_string()]],
5183 }],
5184 child_groups: vec![],
5185 skipped_segments: vec![],
5186 }],
5187 }],
5188 skipped_segments: vec![],
5189 },
5190 AssembledGroupInstance {
5191 segments: vec![AssembledSegment {
5192 tag: "SEQ".to_string(),
5193 elements: vec![vec!["ZD7".to_string()]],
5194 }],
5195 child_groups: vec![AssembledGroup {
5196 group_id: "SG10".to_string(),
5197 repetitions: vec![AssembledGroupInstance {
5198 segments: vec![AssembledSegment {
5199 tag: "CCI".to_string(),
5200 elements: vec![vec![], vec![], vec!["ZE6".to_string()]],
5201 }],
5202 child_groups: vec![],
5203 skipped_segments: vec![],
5204 }],
5205 }],
5206 skipped_segments: vec![],
5207 },
5208 ],
5209 }],
5210 skipped_segments: vec![],
5211 }],
5212 }],
5213 post_group_start: 0,
5214 inter_group_segments: std::collections::BTreeMap::new(),
5215 };
5216
5217 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_z98.sg10");
5219 assert!(inst.is_some());
5220 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
5221
5222 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zd7.sg10");
5224 assert!(inst.is_some());
5225 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZE6");
5226
5227 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zzz.sg10");
5229 assert!(inst.is_none());
5230
5231 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8.sg10");
5233 assert!(inst.is_some());
5234 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
5235 }
5236
5237 #[test]
5238 fn test_parse_source_path_part() {
5239 assert_eq!(parse_source_path_part("sg4"), ("sg4", None));
5240 assert_eq!(parse_source_path_part("sg8_z98"), ("sg8", Some("z98")));
5241 assert_eq!(parse_source_path_part("sg10"), ("sg10", None));
5242 assert_eq!(parse_source_path_part("sg12_z04"), ("sg12", Some("z04")));
5243 }
5244
5245 #[test]
5246 fn test_has_source_path_qualifiers() {
5247 assert!(has_source_path_qualifiers("sg4.sg8_z98.sg10"));
5248 assert!(has_source_path_qualifiers("sg4.sg8_ze1.sg9"));
5249 assert!(!has_source_path_qualifiers("sg4.sg6"));
5250 assert!(!has_source_path_qualifiers("sg4.sg8.sg10"));
5251 }
5252
5253 #[test]
5254 fn test_companion_dotted_path_forward() {
5255 use mig_assembly::assembler::*;
5256
5257 let tree = AssembledTree {
5259 segments: vec![],
5260 groups: vec![AssembledGroup {
5261 group_id: "SG4".to_string(),
5262 repetitions: vec![AssembledGroupInstance {
5263 segments: vec![],
5264 child_groups: vec![AssembledGroup {
5265 group_id: "SG8".to_string(),
5266 repetitions: vec![AssembledGroupInstance {
5267 segments: vec![],
5268 child_groups: vec![AssembledGroup {
5269 group_id: "SG10".to_string(),
5270 repetitions: vec![AssembledGroupInstance {
5271 segments: vec![AssembledSegment {
5272 tag: "CCI".to_string(),
5273 elements: vec![
5274 vec!["11XAB-1234".to_string()],
5275 vec!["305".to_string()],
5276 ],
5277 }],
5278 child_groups: vec![],
5279 skipped_segments: vec![],
5280 }],
5281 }],
5282 skipped_segments: vec![],
5283 }],
5284 }],
5285 skipped_segments: vec![],
5286 }],
5287 }],
5288 post_group_start: 0,
5289 inter_group_segments: std::collections::BTreeMap::new(),
5290 };
5291
5292 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5294 companion_fields.insert(
5295 "cci.0".to_string(),
5296 FieldMapping::Simple("bilanzkreis.id".to_string()),
5297 );
5298 companion_fields.insert(
5299 "cci.1".to_string(),
5300 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
5301 );
5302
5303 let def = MappingDefinition {
5304 meta: MappingMeta {
5305 entity: "Test".to_string(),
5306 bo4e_type: "Test".to_string(),
5307 companion_type: Some("TestEdifact".to_string()),
5308 source_group: "SG4.SG8.SG10".to_string(),
5309 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5310 discriminator: None,
5311 repeat_on_tag: None,
5312 },
5313 fields: IndexMap::new(),
5314 companion_fields: Some(companion_fields),
5315 complex_handlers: None,
5316 };
5317
5318 let engine = MappingEngine::from_definitions(vec![]);
5319 let bo4e = engine.map_forward(&tree, &def, 0);
5320
5321 let companion = &bo4e["testEdifact"];
5323 assert!(
5324 companion.is_object(),
5325 "testEdifact should be an object, got: {companion}"
5326 );
5327 let bilanzkreis = &companion["bilanzkreis"];
5328 assert!(
5329 bilanzkreis.is_object(),
5330 "bilanzkreis should be a nested object, got: {bilanzkreis}"
5331 );
5332 assert_eq!(
5333 bilanzkreis["id"].as_str(),
5334 Some("11XAB-1234"),
5335 "bilanzkreis.id should be 11XAB-1234"
5336 );
5337 assert_eq!(
5338 bilanzkreis["codelist"].as_str(),
5339 Some("305"),
5340 "bilanzkreis.codelist should be 305"
5341 );
5342 }
5343
5344 #[test]
5345 fn test_companion_dotted_path_reverse() {
5346 let engine = MappingEngine::from_definitions(vec![]);
5348
5349 let companion_value = serde_json::json!({
5350 "bilanzkreis": {
5351 "id": "11XAB-1234",
5352 "codelist": "305"
5353 }
5354 });
5355
5356 assert_eq!(
5357 engine.populate_field(&companion_value, "bilanzkreis.id"),
5358 Some("11XAB-1234".to_string()),
5359 "dotted path bilanzkreis.id should resolve"
5360 );
5361 assert_eq!(
5362 engine.populate_field(&companion_value, "bilanzkreis.codelist"),
5363 Some("305".to_string()),
5364 "dotted path bilanzkreis.codelist should resolve"
5365 );
5366
5367 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5369 companion_fields.insert(
5370 "cci.0".to_string(),
5371 FieldMapping::Simple("bilanzkreis.id".to_string()),
5372 );
5373 companion_fields.insert(
5374 "cci.1".to_string(),
5375 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
5376 );
5377
5378 let def = MappingDefinition {
5379 meta: MappingMeta {
5380 entity: "Test".to_string(),
5381 bo4e_type: "Test".to_string(),
5382 companion_type: Some("TestEdifact".to_string()),
5383 source_group: "SG4.SG8.SG10".to_string(),
5384 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5385 discriminator: None,
5386 repeat_on_tag: None,
5387 },
5388 fields: IndexMap::new(),
5389 companion_fields: Some(companion_fields),
5390 complex_handlers: None,
5391 };
5392
5393 let bo4e = serde_json::json!({
5394 "testEdifact": {
5395 "bilanzkreis": {
5396 "id": "11XAB-1234",
5397 "codelist": "305"
5398 }
5399 }
5400 });
5401
5402 let instance = engine.map_reverse(&bo4e, &def);
5403 assert_eq!(instance.segments.len(), 1, "should produce one CCI segment");
5404 let cci = &instance.segments[0];
5405 assert_eq!(cci.tag, "CCI");
5406 assert_eq!(
5407 cci.elements[0],
5408 vec!["11XAB-1234"],
5409 "element 0 should contain bilanzkreis.id"
5410 );
5411 assert_eq!(
5412 cci.elements[1],
5413 vec!["305"],
5414 "element 1 should contain bilanzkreis.codelist"
5415 );
5416 }
5417
5418 #[test]
5419 fn test_when_filled_injects_when_field_present() {
5420 let toml_str = r#"
5421[meta]
5422entity = "Test"
5423bo4e_type = "Test"
5424companion_type = "TestEdifact"
5425source_group = "SG4.SG8.SG10"
5426
5427[fields]
5428
5429[companion_fields]
5430"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmalCode"] }
5431"cav.0.0" = "merkmalCode"
5432"#;
5433 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5434
5435 let bo4e_with = serde_json::json!({
5437 "testEdifact": { "merkmalCode": "ZA7" }
5438 });
5439 let engine = MappingEngine::new_empty();
5440 let instance = engine.map_reverse(&bo4e_with, &def);
5441 let cci = instance
5442 .segments
5443 .iter()
5444 .find(|s| s.tag == "CCI")
5445 .expect("CCI should exist");
5446 assert_eq!(cci.elements[0][0], "Z83");
5447
5448 let bo4e_without = serde_json::json!({
5450 "testEdifact": {}
5451 });
5452 let instance2 = engine.map_reverse(&bo4e_without, &def);
5453 let cci2 = instance2.segments.iter().find(|s| s.tag == "CCI");
5454 assert!(
5455 cci2.is_none(),
5456 "CCI should not be emitted when merkmalCode is absent"
5457 );
5458 }
5459
5460 #[test]
5461 fn test_when_filled_checks_core_and_companion() {
5462 let toml_str = r#"
5463[meta]
5464entity = "Test"
5465bo4e_type = "Test"
5466companion_type = "TestEdifact"
5467source_group = "SG4.SG5"
5468
5469[fields]
5470"loc.1.0" = "marktlokationsId"
5471
5472[companion_fields]
5473"loc.0.0" = { target = "", default = "Z16", when_filled = ["marktlokationsId"] }
5474"#;
5475 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5476
5477 let bo4e_with = serde_json::json!({
5479 "marktlokationsId": "51234567890"
5480 });
5481 let engine = MappingEngine::new_empty();
5482 let instance = engine.map_reverse(&bo4e_with, &def);
5483 let loc = instance
5484 .segments
5485 .iter()
5486 .find(|s| s.tag == "LOC")
5487 .expect("LOC should exist");
5488 assert_eq!(loc.elements[0][0], "Z16");
5489 assert_eq!(loc.elements[1][0], "51234567890");
5490
5491 let bo4e_without = serde_json::json!({});
5493 let instance2 = engine.map_reverse(&bo4e_without, &def);
5494 let loc2 = instance2.segments.iter().find(|s| s.tag == "LOC");
5495 assert!(loc2.is_none());
5496 }
5497
5498 #[test]
5499 fn test_extract_all_from_instance_collects_all_qualifier_matches() {
5500 use mig_assembly::assembler::*;
5501
5502 let instance = AssembledGroupInstance {
5504 segments: vec![
5505 AssembledSegment {
5506 tag: "SEQ".to_string(),
5507 elements: vec![vec!["ZD6".to_string()]],
5508 },
5509 AssembledSegment {
5510 tag: "RFF".to_string(),
5511 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
5512 },
5513 AssembledSegment {
5514 tag: "RFF".to_string(),
5515 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
5516 },
5517 AssembledSegment {
5518 tag: "RFF".to_string(),
5519 elements: vec![vec!["Z34".to_string(), "REF_C".to_string()]],
5520 },
5521 AssembledSegment {
5522 tag: "RFF".to_string(),
5523 elements: vec![vec!["Z35".to_string(), "OTHER".to_string()]],
5524 },
5525 ],
5526 child_groups: vec![],
5527 skipped_segments: vec![],
5528 };
5529
5530 let all = MappingEngine::extract_all_from_instance(&instance, "rff[Z34,*].0.1");
5532 assert_eq!(all, vec!["REF_A", "REF_B", "REF_C"]);
5533
5534 let single = MappingEngine::extract_from_instance(&instance, "rff[Z34].0.1");
5536 assert_eq!(single, Some("REF_A".to_string()));
5537
5538 let second = MappingEngine::extract_from_instance(&instance, "rff[Z34,1].0.1");
5539 assert_eq!(second, Some("REF_B".to_string()));
5540 }
5541
5542 #[test]
5543 fn test_forward_wildcard_collect_produces_json_array() {
5544 use mig_assembly::assembler::*;
5545
5546 let instance = AssembledGroupInstance {
5547 segments: vec![
5548 AssembledSegment {
5549 tag: "SEQ".to_string(),
5550 elements: vec![vec!["ZD6".to_string()]],
5551 },
5552 AssembledSegment {
5553 tag: "RFF".to_string(),
5554 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
5555 },
5556 AssembledSegment {
5557 tag: "RFF".to_string(),
5558 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
5559 },
5560 ],
5561 child_groups: vec![],
5562 skipped_segments: vec![],
5563 };
5564
5565 let toml_str = r#"
5566[meta]
5567entity = "Test"
5568bo4e_type = "Test"
5569companion_type = "TestEdifact"
5570source_group = "SG4.SG8"
5571
5572[fields]
5573
5574[companion_fields]
5575"rff[Z34,*].0.1" = "messlokationsIdRefs"
5576"#;
5577 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5578 let engine = MappingEngine::new_empty();
5579
5580 let mut result = serde_json::Map::new();
5581 engine.extract_companion_fields(&instance, &def, &mut result, false);
5582
5583 let companion = result.get("testEdifact").unwrap().as_object().unwrap();
5584 let refs = companion
5585 .get("messlokationsIdRefs")
5586 .unwrap()
5587 .as_array()
5588 .unwrap();
5589 assert_eq!(refs.len(), 2);
5590 assert_eq!(refs[0].as_str().unwrap(), "REF_A");
5591 assert_eq!(refs[1].as_str().unwrap(), "REF_B");
5592 }
5593
5594 #[test]
5595 fn test_reverse_json_array_produces_multiple_segments() {
5596 let toml_str = r#"
5597[meta]
5598entity = "Test"
5599bo4e_type = "Test"
5600companion_type = "TestEdifact"
5601source_group = "SG4.SG8"
5602
5603[fields]
5604
5605[companion_fields]
5606"seq.0.0" = { target = "", default = "ZD6" }
5607"rff[Z34,*].0.1" = "messlokationsIdRefs"
5608"#;
5609 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5610 let engine = MappingEngine::new_empty();
5611
5612 let bo4e = serde_json::json!({
5613 "testEdifact": {
5614 "messlokationsIdRefs": ["REF_A", "REF_B", "REF_C"]
5615 }
5616 });
5617
5618 let instance = engine.map_reverse(&bo4e, &def);
5619
5620 let rff_segs: Vec<_> = instance
5622 .segments
5623 .iter()
5624 .filter(|s| s.tag == "RFF")
5625 .collect();
5626 assert_eq!(rff_segs.len(), 3);
5627 assert_eq!(rff_segs[0].elements[0][0], "Z34");
5628 assert_eq!(rff_segs[0].elements[0][1], "REF_A");
5629 assert_eq!(rff_segs[1].elements[0][0], "Z34");
5630 assert_eq!(rff_segs[1].elements[0][1], "REF_B");
5631 assert_eq!(rff_segs[2].elements[0][0], "Z34");
5632 assert_eq!(rff_segs[2].elements[0][1], "REF_C");
5633 }
5634
5635 #[test]
5636 fn test_when_filled_dotted_path() {
5637 let toml_str = r#"
5638[meta]
5639entity = "Test"
5640bo4e_type = "Test"
5641companion_type = "TestEdifact"
5642source_group = "SG4.SG8.SG10"
5643
5644[fields]
5645
5646[companion_fields]
5647"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmal.code"] }
5648"cav.0.0" = "merkmal.code"
5649"#;
5650 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5651
5652 let bo4e = serde_json::json!({
5653 "testEdifact": { "merkmal": { "code": "ZA7" } }
5654 });
5655 let engine = MappingEngine::new_empty();
5656 let instance = engine.map_reverse(&bo4e, &def);
5657 let cci = instance
5658 .segments
5659 .iter()
5660 .find(|s| s.tag == "CCI")
5661 .expect("CCI should exist");
5662 assert_eq!(cci.elements[0][0], "Z83");
5663 }
5664
5665 #[test]
5666 fn test_also_target_forward_extracts_both_fields() {
5667 use mig_assembly::assembler::*;
5668
5669 let instance = AssembledGroupInstance {
5670 segments: vec![AssembledSegment {
5671 tag: "NAD".to_string(),
5672 elements: vec![vec!["Z47".to_string()], vec!["12345".to_string()]],
5673 }],
5674 child_groups: vec![],
5675 skipped_segments: vec![],
5676 };
5677
5678 let toml_str = r#"
5679[meta]
5680entity = "Geschaeftspartner"
5681bo4e_type = "Geschaeftspartner"
5682companion_type = "GeschaeftspartnerEdifact"
5683source_group = "SG4.SG12"
5684
5685[fields]
5686"nad.1.0" = "identifikation"
5687
5688[companion_fields."nad.0.0"]
5689target = "partnerrolle"
5690enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
5691also_target = "datenqualitaet"
5692also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
5693"#;
5694 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5695 let engine = MappingEngine::new_empty();
5696
5697 let mut result = serde_json::Map::new();
5698 engine.extract_companion_fields(&instance, &def, &mut result, false);
5699
5700 let companion = result
5701 .get("geschaeftspartnerEdifact")
5702 .unwrap()
5703 .as_object()
5704 .unwrap();
5705 assert_eq!(
5706 companion.get("partnerrolle").unwrap().as_str().unwrap(),
5707 "kundeDesLf"
5708 );
5709 assert_eq!(
5710 companion.get("datenqualitaet").unwrap().as_str().unwrap(),
5711 "erwartet"
5712 );
5713 }
5714
5715 #[test]
5716 fn test_also_target_reverse_joint_lookup() {
5717 let toml_str = r#"
5718[meta]
5719entity = "Geschaeftspartner"
5720bo4e_type = "Geschaeftspartner"
5721companion_type = "GeschaeftspartnerEdifact"
5722source_group = "SG4.SG12"
5723
5724[fields]
5725
5726[companion_fields."nad.0.0"]
5727target = "partnerrolle"
5728enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
5729also_target = "datenqualitaet"
5730also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
5731"#;
5732 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5733 let engine = MappingEngine::new_empty();
5734
5735 let bo4e = serde_json::json!({
5737 "geschaeftspartnerEdifact": {
5738 "partnerrolle": "kundeDesLf",
5739 "datenqualitaet": "erwartet"
5740 }
5741 });
5742 let instance = engine.map_reverse(&bo4e, &def);
5743 let nad = instance
5744 .segments
5745 .iter()
5746 .find(|s| s.tag == "NAD")
5747 .expect("NAD");
5748 assert_eq!(nad.elements[0][0], "Z47");
5749
5750 let bo4e2 = serde_json::json!({
5752 "geschaeftspartnerEdifact": {
5753 "partnerrolle": "kundeDesNb",
5754 "datenqualitaet": "imSystemVorhanden"
5755 }
5756 });
5757 let instance2 = engine.map_reverse(&bo4e2, &def);
5758 let nad2 = instance2
5759 .segments
5760 .iter()
5761 .find(|s| s.tag == "NAD")
5762 .expect("NAD");
5763 assert_eq!(nad2.elements[0][0], "Z52");
5764 }
5765
5766 #[test]
5767 fn test_also_target_mixed_codes_unpaired_skips_datenqualitaet() {
5768 use mig_assembly::assembler::*;
5769
5770 let toml_str = r#"
5772[meta]
5773entity = "Geschaeftspartner"
5774bo4e_type = "Geschaeftspartner"
5775companion_type = "GeschaeftspartnerEdifact"
5776source_group = "SG4.SG12"
5777
5778[fields]
5779
5780[companion_fields."nad.0.0"]
5781target = "partnerrolle"
5782enum_map = { "Z09" = "kundeDesLf", "Z47" = "kundeDesLf", "Z48" = "kundeDesLf" }
5783also_target = "datenqualitaet"
5784also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden" }
5785"#;
5786 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5787 let engine = MappingEngine::new_empty();
5788
5789 let instance_z09 = AssembledGroupInstance {
5791 segments: vec![AssembledSegment {
5792 tag: "NAD".to_string(),
5793 elements: vec![vec!["Z09".to_string()]],
5794 }],
5795 child_groups: vec![],
5796 skipped_segments: vec![],
5797 };
5798 let mut result = serde_json::Map::new();
5799 engine.extract_companion_fields(&instance_z09, &def, &mut result, false);
5800 let comp = result
5801 .get("geschaeftspartnerEdifact")
5802 .unwrap()
5803 .as_object()
5804 .unwrap();
5805 assert_eq!(
5806 comp.get("partnerrolle").unwrap().as_str().unwrap(),
5807 "kundeDesLf"
5808 );
5809 assert!(
5810 comp.get("datenqualitaet").is_none(),
5811 "Z09 should not set datenqualitaet"
5812 );
5813
5814 let bo4e = serde_json::json!({
5816 "geschaeftspartnerEdifact": { "partnerrolle": "kundeDesLf" }
5817 });
5818 let instance = engine.map_reverse(&bo4e, &def);
5819 let nad = instance
5820 .segments
5821 .iter()
5822 .find(|s| s.tag == "NAD")
5823 .expect("NAD");
5824 assert_eq!(nad.elements[0][0], "Z09");
5825
5826 let bo4e2 = serde_json::json!({
5828 "geschaeftspartnerEdifact": {
5829 "partnerrolle": "kundeDesLf",
5830 "datenqualitaet": "erwartet"
5831 }
5832 });
5833 let instance2 = engine.map_reverse(&bo4e2, &def);
5834 let nad2 = instance2
5835 .segments
5836 .iter()
5837 .find(|s| s.tag == "NAD")
5838 .expect("NAD");
5839 assert_eq!(nad2.elements[0][0], "Z47");
5840 }
5841}