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 Some(d.clone())
1188 } else {
1189 None
1190 }
1191 }
1192 (Some(d), None) => Some(d.clone()),
1194 (None, _) => None,
1195 }
1196 } else {
1197 has_data_fields = true;
1198 seg_has_data_field.insert(seg_key.clone());
1199 let bo4e_val = self.populate_field(bo4e_value, target);
1200 if bo4e_val.is_some() {
1201 has_real_data = true;
1202 seg_has_real_data.insert(seg_key.clone());
1203 }
1204 let mapped_val = match (bo4e_val, enum_map) {
1206 (Some(v), Some(map)) => {
1207 map.iter()
1209 .find(|(_, bo4e_v)| *bo4e_v == &v)
1210 .map(|(edifact_k, _)| edifact_k.clone())
1211 .or(Some(v))
1212 }
1213 (v, _) => v,
1214 };
1215 mapped_val.or_else(|| default.cloned())
1216 };
1217
1218 if let Some(val) = val {
1219 field_values.push((
1220 seg_key.clone(),
1221 seg_tag.clone(),
1222 element_idx,
1223 component_idx,
1224 val,
1225 ));
1226 }
1227
1228 if let Some(q) = qualifier {
1230 if injected_qualifiers.insert(seg_key.clone()) {
1231 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1232 }
1233 }
1234 }
1235
1236 if let Some(ref companion_fields) = def.companion_fields {
1238 let raw_key = def.meta.companion_type.as_deref().unwrap_or("_companion");
1239 let companion_key = to_camel_case(raw_key);
1240 let companion_value = bo4e_value
1241 .get(&companion_key)
1242 .unwrap_or(&serde_json::Value::Null);
1243
1244 for (path, field_mapping) in companion_fields {
1245 let (target, default, enum_map, when_filled, also_target, also_enum_map) =
1246 match field_mapping {
1247 FieldMapping::Simple(t) => (t.as_str(), None, None, None, None, None),
1248 FieldMapping::Structured(s) => (
1249 s.target.as_str(),
1250 s.default.as_ref(),
1251 s.enum_map.as_ref(),
1252 s.when_filled.as_ref(),
1253 s.also_target.as_deref(),
1254 s.also_enum_map.as_ref(),
1255 ),
1256 FieldMapping::Nested(_) => continue,
1257 };
1258
1259 let parts: Vec<&str> = path.split('.').collect();
1260 if parts.len() < 2 {
1261 continue;
1262 }
1263
1264 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1265 let seg_key = parts[0].to_uppercase();
1266 let sub_path = &parts[1..];
1267
1268 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1269 let ci = if sub_path.len() > 1 {
1270 sub_path[1].parse::<usize>().unwrap_or(0)
1271 } else {
1272 0
1273 };
1274 (ei, ci)
1275 } else {
1276 match sub_path.len() {
1277 1 => (0, 0),
1278 2 => (1, 0),
1279 _ => continue,
1280 }
1281 };
1282
1283 if is_collect_all_path(path) && !target.is_empty() {
1285 if let Some(arr) = self
1286 .populate_field_json(companion_value, target)
1287 .and_then(|v| v.as_array().cloned())
1288 {
1289 has_data_fields = true;
1290 if !arr.is_empty() {
1291 has_real_data = true;
1292 }
1293 for (idx, item) in arr.iter().enumerate() {
1294 if let Some(val_str) = item.as_str() {
1295 let mapped = if let Some(map) = enum_map {
1296 map.iter()
1297 .find(|(_, bo4e_v)| *bo4e_v == val_str)
1298 .map(|(edifact_k, _)| edifact_k.clone())
1299 .unwrap_or_else(|| val_str.to_string())
1300 } else {
1301 val_str.to_string()
1302 };
1303 let occ_key = if let Some(q) = qualifier {
1304 format!("{}[{},{}]", seg_tag, q, idx)
1305 } else {
1306 format!("{}[*,{}]", seg_tag, idx)
1307 };
1308 field_values.push((
1309 occ_key.clone(),
1310 seg_tag.clone(),
1311 element_idx,
1312 component_idx,
1313 mapped,
1314 ));
1315 if let Some(q) = qualifier {
1317 if injected_qualifiers.insert(occ_key.clone()) {
1318 field_values.push((
1319 occ_key,
1320 seg_tag.clone(),
1321 0,
1322 0,
1323 q.to_string(),
1324 ));
1325 }
1326 }
1327 }
1328 }
1329 }
1330 continue;
1331 }
1332
1333 let val = if target.is_empty() {
1334 match (default, when_filled) {
1335 (Some(d), Some(fields)) => {
1336 let any_filled = fields.iter().any(|f| {
1337 self.populate_field(bo4e_value, f).is_some()
1338 || self.populate_field(companion_value, f).is_some()
1339 });
1340 if any_filled {
1341 Some(d.clone())
1342 } else {
1343 None
1344 }
1345 }
1346 (Some(d), None) => Some(d.clone()),
1347 (None, _) => None,
1348 }
1349 } else {
1350 has_data_fields = true;
1351 seg_has_data_field.insert(seg_key.clone());
1352 let bo4e_val = self.populate_field(companion_value, target);
1353 if bo4e_val.is_some() {
1354 has_real_data = true;
1355 seg_has_real_data.insert(seg_key.clone());
1356 }
1357 let mapped_val = match (bo4e_val, enum_map) {
1358 (Some(v), Some(map)) => {
1359 if let (Some(at), Some(am)) = (also_target, also_enum_map) {
1360 let also_val = self.populate_field(companion_value, at);
1361 if let Some(av) = also_val.as_deref() {
1362 map.iter()
1364 .find(|(edifact_k, bo4e_v)| {
1365 *bo4e_v == &v
1366 && am.get(*edifact_k).is_some_and(|am_v| am_v == av)
1367 })
1368 .map(|(edifact_k, _)| edifact_k.clone())
1369 .or(Some(v))
1370 } else {
1371 map.iter()
1374 .find(|(edifact_k, bo4e_v)| {
1375 *bo4e_v == &v && !am.contains_key(*edifact_k)
1376 })
1377 .or_else(|| {
1378 map.iter().find(|(_, bo4e_v)| *bo4e_v == &v)
1380 })
1381 .map(|(edifact_k, _)| edifact_k.clone())
1382 .or(Some(v))
1383 }
1384 } else {
1385 map.iter()
1386 .find(|(_, bo4e_v)| *bo4e_v == &v)
1387 .map(|(edifact_k, _)| edifact_k.clone())
1388 .or(Some(v))
1389 }
1390 }
1391 (v, _) => v,
1392 };
1393 mapped_val.or_else(|| default.cloned())
1394 };
1395
1396 if let Some(val) = val {
1397 field_values.push((
1398 seg_key.clone(),
1399 seg_tag.clone(),
1400 element_idx,
1401 component_idx,
1402 val,
1403 ));
1404 }
1405
1406 if let Some(q) = qualifier {
1407 if injected_qualifiers.insert(seg_key.clone()) {
1408 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1409 }
1410 }
1411 }
1412 }
1413
1414 field_values.retain(|(seg_key, _, _, _, _)| {
1422 if !seg_key.contains('[') {
1423 return true; }
1425 !seg_has_data_field.contains(seg_key) || seg_has_real_data.contains(seg_key)
1426 });
1427
1428 if has_data_fields && !has_real_data {
1433 return AssembledGroupInstance {
1434 segments: vec![],
1435 child_groups: vec![],
1436 skipped_segments: Vec::new(),
1437 };
1438 }
1439
1440 let mut segments: Vec<AssembledSegment> = Vec::with_capacity(field_values.len());
1443 let mut seen_keys: HashMap<String, usize> = HashMap::new();
1444
1445 for (seg_key, seg_tag, element_idx, component_idx, val) in &field_values {
1446 let seg = if let Some(&pos) = seen_keys.get(seg_key) {
1447 &mut segments[pos]
1448 } else {
1449 let pos = segments.len();
1450 seen_keys.insert(seg_key.clone(), pos);
1451 segments.push(AssembledSegment {
1452 tag: seg_tag.clone(),
1453 elements: vec![],
1454 });
1455 &mut segments[pos]
1456 };
1457
1458 while seg.elements.len() <= *element_idx {
1459 seg.elements.push(vec![]);
1460 }
1461 while seg.elements[*element_idx].len() <= *component_idx {
1462 seg.elements[*element_idx].push(String::new());
1463 }
1464 seg.elements[*element_idx][*component_idx] = val.clone();
1465 }
1466
1467 for seg in &mut segments {
1470 let last_populated = seg.elements.iter().rposition(|e| !e.is_empty());
1471 if let Some(last_idx) = last_populated {
1472 for i in 0..last_idx {
1473 if seg.elements[i].is_empty() {
1474 seg.elements[i] = vec![String::new()];
1475 }
1476 }
1477 }
1478 }
1479
1480 if let Some(ref ss) = self.segment_structure {
1482 for seg in &mut segments {
1483 if let Some(expected) = ss.element_count(&seg.tag) {
1484 while seg.elements.len() < expected {
1485 seg.elements.push(vec![String::new()]);
1486 }
1487 }
1488 }
1489 }
1490
1491 AssembledGroupInstance {
1492 segments,
1493 child_groups: vec![],
1494 skipped_segments: Vec::new(),
1495 }
1496 }
1497
1498 fn resolve_field_path(segment: &AssembledSegment, path: &[&str]) -> Option<String> {
1511 if path.is_empty() {
1512 return None;
1513 }
1514
1515 if let Ok(element_idx) = path[0].parse::<usize>() {
1517 let component_idx = if path.len() > 1 {
1518 path[1].parse::<usize>().unwrap_or(0)
1519 } else {
1520 0
1521 };
1522 return segment
1523 .elements
1524 .get(element_idx)?
1525 .get(component_idx)
1526 .filter(|v| !v.is_empty())
1527 .cloned();
1528 }
1529
1530 match path.len() {
1532 1 => segment
1533 .elements
1534 .first()?
1535 .first()
1536 .filter(|v| !v.is_empty())
1537 .cloned(),
1538 2 => segment
1539 .elements
1540 .get(1)?
1541 .first()
1542 .filter(|v| !v.is_empty())
1543 .cloned(),
1544 _ => None,
1545 }
1546 }
1547
1548 fn parse_element_component(parts: &[&str]) -> (usize, usize) {
1551 if parts.is_empty() {
1552 return (0, 0);
1553 }
1554 let element_idx = parts[0].parse::<usize>().unwrap_or(0);
1555 let component_idx = if parts.len() > 1 {
1556 parts[1].parse::<usize>().unwrap_or(0)
1557 } else {
1558 0
1559 };
1560 (element_idx, component_idx)
1561 }
1562
1563 pub fn populate_field(
1566 &self,
1567 bo4e_value: &serde_json::Value,
1568 target_field: &str,
1569 ) -> Option<String> {
1570 let mut current = bo4e_value;
1571 for part in target_field.split('.') {
1572 current = current.get(part)?;
1573 }
1574 if let Some(code) = current.get("code").and_then(|v| v.as_str()) {
1576 return Some(code.to_string());
1577 }
1578 current.as_str().map(|s| s.to_string())
1579 }
1580
1581 fn populate_field_json<'a>(
1584 &self,
1585 bo4e_value: &'a serde_json::Value,
1586 target_field: &str,
1587 ) -> Option<&'a serde_json::Value> {
1588 let mut current = bo4e_value;
1589 for part in target_field.split('.') {
1590 current = current.get(part)?;
1591 }
1592 Some(current)
1593 }
1594
1595 pub fn build_segment_from_bo4e(
1597 &self,
1598 bo4e_value: &serde_json::Value,
1599 segment_tag: &str,
1600 target_field: &str,
1601 ) -> AssembledSegment {
1602 let value = self.populate_field(bo4e_value, target_field);
1603 let elements = if let Some(val) = value {
1604 vec![vec![val]]
1605 } else {
1606 vec![]
1607 };
1608 AssembledSegment {
1609 tag: segment_tag.to_uppercase(),
1610 elements,
1611 }
1612 }
1613
1614 pub fn resolve_repetition(
1623 tree: &AssembledTree,
1624 group_path: &str,
1625 discriminator: &str,
1626 ) -> Option<usize> {
1627 let (spec, expected) = discriminator.split_once('=')?;
1628 let parts: Vec<&str> = spec.split('.').collect();
1629 if parts.len() != 3 {
1630 return None;
1631 }
1632 let tag = parts[0];
1633 let element_idx: usize = parts[1].parse().ok()?;
1634 let component_idx: usize = parts[2].parse().ok()?;
1635
1636 let path_parts: Vec<&str> = group_path.split('.').collect();
1638
1639 let leaf_group = if path_parts.len() == 1 {
1640 let (group_id, _) = parse_group_spec(path_parts[0]);
1641 tree.groups.iter().find(|g| g.group_id == group_id)?
1642 } else {
1643 let parent_parts = &path_parts[..path_parts.len() - 1];
1645 let mut current_instance = {
1646 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1647 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
1648 first_group.repetitions.get(first_rep.unwrap_or(0))?
1649 };
1650 for part in &parent_parts[1..] {
1651 let (group_id, explicit_rep) = parse_group_spec(part);
1652 let child_group = current_instance
1653 .child_groups
1654 .iter()
1655 .find(|g| g.group_id == group_id)?;
1656 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
1657 }
1658 let (leaf_id, _) = parse_group_spec(path_parts.last()?);
1659 current_instance
1660 .child_groups
1661 .iter()
1662 .find(|g| g.group_id == leaf_id)?
1663 };
1664
1665 let expected_values: Vec<&str> = expected.split('|').collect();
1667 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1668 let matches = instance.segments.iter().any(|s| {
1669 s.tag.eq_ignore_ascii_case(tag)
1670 && s.elements
1671 .get(element_idx)
1672 .and_then(|e| e.get(component_idx))
1673 .map(|v| expected_values.iter().any(|ev| v == ev))
1674 .unwrap_or(false)
1675 });
1676 if matches {
1677 return Some(rep_idx);
1678 }
1679 }
1680
1681 None
1682 }
1683
1684 pub fn resolve_all_repetitions(
1689 tree: &AssembledTree,
1690 group_path: &str,
1691 discriminator: &str,
1692 ) -> Vec<usize> {
1693 let Some((spec, expected)) = discriminator.split_once('=') else {
1694 return Vec::new();
1695 };
1696 let parts: Vec<&str> = spec.split('.').collect();
1697 if parts.len() != 3 {
1698 return Vec::new();
1699 }
1700 let tag = parts[0];
1701 let element_idx: usize = match parts[1].parse() {
1702 Ok(v) => v,
1703 Err(_) => return Vec::new(),
1704 };
1705 let component_idx: usize = match parts[2].parse() {
1706 Ok(v) => v,
1707 Err(_) => return Vec::new(),
1708 };
1709
1710 let path_parts: Vec<&str> = group_path.split('.').collect();
1712
1713 let leaf_group = if path_parts.len() == 1 {
1714 let (group_id, _) = parse_group_spec(path_parts[0]);
1715 match tree.groups.iter().find(|g| g.group_id == group_id) {
1716 Some(g) => g,
1717 None => return Vec::new(),
1718 }
1719 } else {
1720 let parent_parts = &path_parts[..path_parts.len() - 1];
1721 let mut current_instance = {
1722 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1723 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
1724 Some(g) => g,
1725 None => return Vec::new(),
1726 };
1727 match first_group.repetitions.get(first_rep.unwrap_or(0)) {
1728 Some(i) => i,
1729 None => return Vec::new(),
1730 }
1731 };
1732 for part in &parent_parts[1..] {
1733 let (group_id, explicit_rep) = parse_group_spec(part);
1734 let child_group = match current_instance
1735 .child_groups
1736 .iter()
1737 .find(|g| g.group_id == group_id)
1738 {
1739 Some(g) => g,
1740 None => return Vec::new(),
1741 };
1742 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
1743 Some(i) => i,
1744 None => return Vec::new(),
1745 };
1746 }
1747 let (leaf_id, _) = match path_parts.last() {
1748 Some(p) => parse_group_spec(p),
1749 None => return Vec::new(),
1750 };
1751 match current_instance
1752 .child_groups
1753 .iter()
1754 .find(|g| g.group_id == leaf_id)
1755 {
1756 Some(g) => g,
1757 None => return Vec::new(),
1758 }
1759 };
1760
1761 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
1763
1764 let expected_values: Vec<&str> = expected_raw.split('|').collect();
1766 let mut result = Vec::new();
1767 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1768 let matches = instance.segments.iter().any(|s| {
1769 s.tag.eq_ignore_ascii_case(tag)
1770 && s.elements
1771 .get(element_idx)
1772 .and_then(|e| e.get(component_idx))
1773 .map(|v| expected_values.iter().any(|ev| v == ev))
1774 .unwrap_or(false)
1775 });
1776 if matches {
1777 result.push(rep_idx);
1778 }
1779 }
1780
1781 if let Some(occ) = occurrence {
1783 result.into_iter().nth(occ).into_iter().collect()
1784 } else {
1785 result
1786 }
1787 }
1788
1789 pub fn map_all_forward(&self, tree: &AssembledTree) -> serde_json::Value {
1811 self.map_all_forward_inner(tree, true).0
1812 }
1813
1814 pub fn map_all_forward_enriched(
1818 &self,
1819 tree: &AssembledTree,
1820 enrich_codes: bool,
1821 ) -> serde_json::Value {
1822 self.map_all_forward_inner(tree, enrich_codes).0
1823 }
1824
1825 fn map_all_forward_inner(
1832 &self,
1833 tree: &AssembledTree,
1834 enrich_codes: bool,
1835 ) -> (
1836 serde_json::Value,
1837 std::collections::HashMap<String, Vec<usize>>,
1838 ) {
1839 let mut result = serde_json::Map::new();
1840 let mut nesting_info: std::collections::HashMap<String, Vec<usize>> =
1841 std::collections::HashMap::new();
1842
1843 for def in &self.definitions {
1844 let entity = &def.meta.entity;
1845
1846 let bo4e = if let Some(ref disc) = def.meta.discriminator {
1847 let use_source_path = def
1852 .meta
1853 .source_path
1854 .as_ref()
1855 .is_some_and(|sp| has_source_path_qualifiers(sp));
1856 if use_source_path {
1857 let sp = def.meta.source_path.as_deref().unwrap();
1859 let all_instances = Self::resolve_all_by_source_path(tree, sp);
1860 let instances: Vec<_> = if let Some(matcher) = DiscriminatorMatcher::parse(disc)
1862 {
1863 matcher.filter_instances(all_instances)
1864 } else {
1865 all_instances
1866 };
1867 let extract = |instance: &AssembledGroupInstance| {
1868 let mut r = serde_json::Map::new();
1869 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
1870 self.extract_companion_fields(instance, def, &mut r, enrich_codes);
1871 serde_json::Value::Object(r)
1872 };
1873 match instances.len() {
1874 0 => None,
1875 1 => Some(extract(instances[0])),
1876 _ => Some(serde_json::Value::Array(
1877 instances.iter().map(|i| extract(i)).collect(),
1878 )),
1879 }
1880 } else {
1881 let reps = Self::resolve_all_repetitions(tree, &def.meta.source_group, disc);
1882 match reps.len() {
1883 0 => None,
1884 1 => Some(self.map_forward_inner(tree, def, reps[0], enrich_codes)),
1885 _ => Some(serde_json::Value::Array(
1886 reps.iter()
1887 .map(|&rep| self.map_forward_inner(tree, def, rep, enrich_codes))
1888 .collect(),
1889 )),
1890 }
1891 }
1892 } else if def.meta.source_group.is_empty() {
1893 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
1895 } else if def.meta.source_path.as_ref().is_some_and(|sp| {
1896 has_source_path_qualifiers(sp) || def.meta.source_group.contains('.')
1897 }) {
1898 let sp = def.meta.source_path.as_deref().unwrap();
1903 let mut indexed = Self::resolve_all_with_parent_indices(tree, sp);
1904
1905 if let Some(last_part) = sp.rsplit('.').next() {
1910 if !last_part.contains('_') {
1911 let base_prefix = if let Some(parent) = sp.rsplit_once('.') {
1915 format!("{}.", parent.0)
1916 } else {
1917 String::new()
1918 };
1919 let sibling_qualifiers: Vec<String> = self
1920 .definitions
1921 .iter()
1922 .filter_map(|d| d.meta.source_path.as_deref())
1923 .filter(|other_sp| {
1924 *other_sp != sp
1925 && other_sp.starts_with(&base_prefix)
1926 && other_sp.split('.').count() == sp.split('.').count()
1927 })
1928 .filter_map(|other_sp| {
1929 let other_last = other_sp.rsplit('.').next()?;
1930 let (base, q) = other_last.split_once('_')?;
1933 if base == last_part {
1934 Some(q.to_string())
1935 } else {
1936 None
1937 }
1938 })
1939 .collect();
1940
1941 if !sibling_qualifiers.is_empty() {
1942 indexed.retain(|(_, inst)| {
1943 let entry_qual = inst
1944 .segments
1945 .first()
1946 .and_then(|seg| seg.elements.first())
1947 .and_then(|el| el.first())
1948 .map(|v| v.to_lowercase());
1949 !entry_qual.is_some_and(|q| {
1952 sibling_qualifiers.iter().any(|sq| {
1953 sq.split('_').any(|part| part.eq_ignore_ascii_case(&q))
1954 })
1955 })
1956 });
1957 }
1958 }
1959 }
1960 let extract = |instance: &AssembledGroupInstance| {
1961 let mut r = serde_json::Map::new();
1962 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
1963 self.extract_companion_fields(instance, def, &mut r, enrich_codes);
1964 serde_json::Value::Object(r)
1965 };
1966 if def.meta.source_group.contains('.') && !indexed.is_empty() {
1971 if let Some(sp) = &def.meta.source_path {
1972 let parent_indices: Vec<usize> =
1973 indexed.iter().map(|(idx, _)| *idx).collect();
1974 nesting_info.entry(sp.clone()).or_insert(parent_indices);
1975
1976 let child_key = format!("{sp}#child");
1979 if let std::collections::hash_map::Entry::Vacant(e) =
1980 nesting_info.entry(child_key)
1981 {
1982 let child_indices: Vec<usize> =
1983 Self::compute_child_indices(tree, sp, &indexed);
1984 if !child_indices.is_empty() {
1985 e.insert(child_indices);
1986 }
1987 }
1988 }
1989 }
1990 match indexed.len() {
1991 0 => None,
1992 1 => Some(extract(indexed[0].1)),
1993 _ => Some(serde_json::Value::Array(
1994 indexed.iter().map(|(_, i)| extract(i)).collect(),
1995 )),
1996 }
1997 } else {
1998 let num_reps = Self::count_repetitions(tree, &def.meta.source_group);
1999 if num_reps <= 1 {
2000 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2001 } else {
2002 let mut items = Vec::with_capacity(num_reps);
2004 for rep in 0..num_reps {
2005 items.push(self.map_forward_inner(tree, def, rep, enrich_codes));
2006 }
2007 Some(serde_json::Value::Array(items))
2008 }
2009 };
2010
2011 if let Some(bo4e) = bo4e {
2012 let bo4e = inject_bo4e_metadata(bo4e, &def.meta.bo4e_type);
2013 let key = to_camel_case(entity);
2014 deep_merge_insert(&mut result, &key, bo4e);
2015 }
2016 }
2017
2018 (serde_json::Value::Object(result), nesting_info)
2019 }
2020
2021 pub fn map_all_reverse(
2030 &self,
2031 entities: &serde_json::Value,
2032 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2033 ) -> AssembledTree {
2034 let mut root_segments: Vec<AssembledSegment> = Vec::new();
2035 let mut groups: Vec<AssembledGroup> = Vec::new();
2036
2037 for def in &self.definitions {
2038 let entity_key = to_camel_case(&def.meta.entity);
2039
2040 let entity_value = entities.get(&entity_key);
2042
2043 if entity_value.is_none() {
2044 continue;
2045 }
2046 let entity_value = entity_value.unwrap();
2047
2048 let leaf_group = def
2050 .meta
2051 .source_group
2052 .rsplit('.')
2053 .next()
2054 .unwrap_or(&def.meta.source_group);
2055
2056 if def.meta.source_group.is_empty() {
2057 let instance = self.map_reverse(entity_value, def);
2059 root_segments.extend(instance.segments);
2060 } else if entity_value.is_array() {
2061 let arr = entity_value.as_array().unwrap();
2063 let reps: Vec<_> = arr.iter().map(|item| self.map_reverse(item, def)).collect();
2064
2065 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2067 existing.repetitions.extend(reps);
2068 } else {
2069 groups.push(AssembledGroup {
2070 group_id: leaf_group.to_string(),
2071 repetitions: reps,
2072 });
2073 }
2074 } else {
2075 let instance = self.map_reverse(entity_value, def);
2077
2078 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2079 existing.repetitions.push(instance);
2080 } else {
2081 groups.push(AssembledGroup {
2082 group_id: leaf_group.to_string(),
2083 repetitions: vec![instance],
2084 });
2085 }
2086 }
2087 }
2088
2089 let nested_specs: Vec<(String, String)> = self
2095 .definitions
2096 .iter()
2097 .filter_map(|def| {
2098 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
2099 if parts.len() > 1 {
2100 Some((parts[0].to_string(), parts[parts.len() - 1].to_string()))
2101 } else {
2102 None
2103 }
2104 })
2105 .collect();
2106 for (parent_id, child_id) in &nested_specs {
2107 let has_parent = groups.iter().any(|g| g.group_id == *parent_id);
2109 let has_child = groups.iter().any(|g| g.group_id == *child_id);
2110 if has_parent && has_child {
2111 let child_idx = groups.iter().position(|g| g.group_id == *child_id).unwrap();
2112 let child_group = groups.remove(child_idx);
2113 let parent = groups
2114 .iter_mut()
2115 .find(|g| g.group_id == *parent_id)
2116 .unwrap();
2117 let child_source_path = self
2121 .definitions
2122 .iter()
2123 .find(|d| {
2124 let parts: Vec<&str> = d.meta.source_group.split('.').collect();
2125 parts.len() > 1 && parts[parts.len() - 1] == *child_id
2126 })
2127 .and_then(|d| d.meta.source_path.as_deref());
2128 let distribution =
2129 child_source_path.and_then(|key| nesting_info.and_then(|ni| ni.get(key)));
2130 for (i, child_rep) in child_group.repetitions.into_iter().enumerate() {
2131 let target_idx = distribution
2132 .and_then(|dist| dist.get(i))
2133 .copied()
2134 .unwrap_or(0);
2135
2136 if let Some(target_rep) = parent.repetitions.get_mut(target_idx) {
2137 if let Some(existing) = target_rep
2138 .child_groups
2139 .iter_mut()
2140 .find(|g| g.group_id == *child_id)
2141 {
2142 existing.repetitions.push(child_rep);
2143 } else {
2144 target_rep.child_groups.push(AssembledGroup {
2145 group_id: child_id.clone(),
2146 repetitions: vec![child_rep],
2147 });
2148 }
2149 }
2150 }
2151 }
2152 }
2153
2154 let post_group_start = root_segments.len();
2155 AssembledTree {
2156 segments: root_segments,
2157 groups,
2158 post_group_start,
2159 inter_group_segments: std::collections::BTreeMap::new(),
2160 }
2161 }
2162
2163 fn count_repetitions(tree: &AssembledTree, group_path: &str) -> usize {
2165 let parts: Vec<&str> = group_path.split('.').collect();
2166
2167 let (first_id, first_rep) = parse_group_spec(parts[0]);
2168 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2169 Some(g) => g,
2170 None => return 0,
2171 };
2172
2173 if parts.len() == 1 {
2174 return first_group.repetitions.len();
2175 }
2176
2177 let mut current_instance = match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2179 Some(i) => i,
2180 None => return 0,
2181 };
2182
2183 for (i, part) in parts[1..].iter().enumerate() {
2184 let (group_id, explicit_rep) = parse_group_spec(part);
2185 let child_group = match current_instance
2186 .child_groups
2187 .iter()
2188 .find(|g| g.group_id == group_id)
2189 {
2190 Some(g) => g,
2191 None => return 0,
2192 };
2193
2194 if i == parts.len() - 2 {
2195 return child_group.repetitions.len();
2197 }
2198 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2199 Some(i) => i,
2200 None => return 0,
2201 };
2202 }
2203
2204 0
2205 }
2206
2207 pub fn map_interchange(
2216 msg_engine: &MappingEngine,
2217 tx_engine: &MappingEngine,
2218 tree: &AssembledTree,
2219 transaction_group: &str,
2220 enrich_codes: bool,
2221 ) -> crate::model::MappedMessage {
2222 let (stammdaten, nesting_info) = msg_engine.map_all_forward_inner(tree, enrich_codes);
2224
2225 let transaktionen = tree
2227 .groups
2228 .iter()
2229 .find(|g| g.group_id == transaction_group)
2230 .map(|sg| {
2231 sg.repetitions
2232 .iter()
2233 .map(|instance| {
2234 let wrapped_tree = AssembledTree {
2237 segments: vec![],
2238 groups: vec![AssembledGroup {
2239 group_id: transaction_group.to_string(),
2240 repetitions: vec![instance.clone()],
2241 }],
2242 post_group_start: 0,
2243 inter_group_segments: std::collections::BTreeMap::new(),
2244 };
2245
2246 let (tx_result, tx_nesting) =
2247 tx_engine.map_all_forward_inner(&wrapped_tree, enrich_codes);
2248
2249 crate::model::MappedTransaktion {
2250 stammdaten: tx_result,
2251 nesting_info: tx_nesting,
2252 }
2253 })
2254 .collect()
2255 })
2256 .unwrap_or_default();
2257
2258 crate::model::MappedMessage {
2259 stammdaten,
2260 transaktionen,
2261 nesting_info,
2262 }
2263 }
2264
2265 pub fn map_interchange_reverse(
2275 msg_engine: &MappingEngine,
2276 tx_engine: &MappingEngine,
2277 mapped: &crate::model::MappedMessage,
2278 transaction_group: &str,
2279 filtered_mig: Option<&MigSchema>,
2280 ) -> AssembledTree {
2281 let msg_tree = msg_engine.map_all_reverse(
2283 &mapped.stammdaten,
2284 if mapped.nesting_info.is_empty() {
2285 None
2286 } else {
2287 Some(&mapped.nesting_info)
2288 },
2289 );
2290
2291 let mut sg4_reps: Vec<AssembledGroupInstance> = Vec::new();
2293
2294 struct DefWithMeta<'a> {
2298 def: &'a MappingDefinition,
2299 relative: String,
2300 depth: usize,
2301 }
2302
2303 let mut sorted_defs: Vec<DefWithMeta> = tx_engine
2304 .definitions
2305 .iter()
2306 .map(|def| {
2307 let relative = strip_tx_group_prefix(&def.meta.source_group, transaction_group);
2308 let depth = if relative.is_empty() {
2309 0
2310 } else {
2311 relative.chars().filter(|c| *c == '.').count() + 1
2312 };
2313 DefWithMeta {
2314 def,
2315 relative,
2316 depth,
2317 }
2318 })
2319 .collect();
2320
2321 let mut parent_rep_map: std::collections::HashMap<String, usize> =
2325 std::collections::HashMap::new();
2326 for dm in &sorted_defs {
2327 if dm.depth >= 2 {
2328 let parts: Vec<&str> = dm.relative.split('.').collect();
2329 let (_, parent_rep) = parse_group_spec(parts[0]);
2330 if let Some(rep_idx) = parent_rep {
2331 if let Some(sp) = &dm.def.meta.source_path {
2332 if let Some((parent_path, _)) = sp.rsplit_once('.') {
2333 parent_rep_map
2334 .entry(parent_path.to_string())
2335 .or_insert(rep_idx);
2336 }
2337 }
2338 }
2339 }
2340 }
2341
2342 for dm in &mut sorted_defs {
2345 if dm.depth == 1 && !dm.relative.contains(':') {
2346 if let Some(sp) = &dm.def.meta.source_path {
2347 if let Some(rep_idx) = parent_rep_map.get(sp.as_str()) {
2348 dm.relative = format!("{}:{}", dm.relative, rep_idx);
2349 }
2350 }
2351 }
2352 }
2353
2354 if let Some(mig) = filtered_mig {
2361 let mig_order = build_reverse_mig_group_order(mig, transaction_group);
2362 sorted_defs.sort_by(|a, b| {
2363 a.depth.cmp(&b.depth).then_with(|| {
2364 let a_id = a.relative.split(':').next().unwrap_or(&a.relative);
2365 let b_id = b.relative.split(':').next().unwrap_or(&b.relative);
2366 let a_pos = variant_mig_position(a.def, a_id, &mig_order);
2368 let b_pos = variant_mig_position(b.def, b_id, &mig_order);
2369 a_pos.cmp(&b_pos).then(a.relative.cmp(&b.relative))
2370 })
2371 });
2372 } else {
2373 sorted_defs.sort_by(|a, b| a.depth.cmp(&b.depth).then(a.relative.cmp(&b.relative)));
2374 }
2375
2376 for tx in &mapped.transaktionen {
2377 let mut root_segs: Vec<AssembledSegment> = Vec::new();
2378 let mut child_groups: Vec<AssembledGroup> = Vec::new();
2379
2380 let mut source_path_to_rep: std::collections::HashMap<String, Vec<usize>> =
2385 std::collections::HashMap::new();
2386
2387 for dm in &sorted_defs {
2388 let entity_key = to_camel_case(&dm.def.meta.entity);
2390 let bo4e_value = match tx.stammdaten.get(&entity_key) {
2391 Some(v) => v,
2392 None => continue,
2393 };
2394
2395 let items: Vec<&serde_json::Value> = if bo4e_value.is_array() {
2399 bo4e_value.as_array().unwrap().iter().collect()
2400 } else {
2401 vec![bo4e_value]
2402 };
2403
2404 for (item_idx, item) in items.iter().enumerate() {
2405 let instance = tx_engine.map_reverse(item, dm.def);
2406
2407 if instance.segments.is_empty() && instance.child_groups.is_empty() {
2409 continue;
2410 }
2411
2412 if dm.relative.is_empty() {
2413 root_segs.extend(instance.segments);
2414 } else {
2415 let effective_relative = if dm.depth >= 2 {
2419 let rel = if items.len() > 1 {
2422 strip_all_rep_indices(&dm.relative)
2423 } else {
2424 dm.relative.clone()
2425 };
2426 let nesting_idx = if items.len() > 1 {
2432 dm.def
2433 .meta
2434 .source_path
2435 .as_ref()
2436 .and_then(|sp| tx.nesting_info.get(sp))
2437 .and_then(|dist| dist.get(item_idx))
2438 .copied()
2439 } else {
2440 None
2441 };
2442 if let Some(parent_rep) = nesting_idx {
2443 let parts: Vec<&str> = rel.split('.').collect();
2445 let parent_id = parts[0].split(':').next().unwrap_or(parts[0]);
2446 let rest = parts[1..].join(".");
2447 format!("{}:{}.{}", parent_id, parent_rep, rest)
2448 } else {
2449 resolve_child_relative(
2450 &rel,
2451 dm.def.meta.source_path.as_deref(),
2452 &source_path_to_rep,
2453 item_idx,
2454 )
2455 }
2456 } else if dm.depth == 1 {
2457 let child_key = dm
2460 .def
2461 .meta
2462 .source_path
2463 .as_ref()
2464 .map(|sp| format!("{sp}#child"));
2465 if let Some(child_indices) =
2466 child_key.as_ref().and_then(|ck| tx.nesting_info.get(ck))
2467 {
2468 if let Some(&target) = child_indices.get(item_idx) {
2469 if target != usize::MAX {
2470 let base =
2471 dm.relative.split(':').next().unwrap_or(&dm.relative);
2472 format!("{}:{}", base, target)
2473 } else {
2474 dm.relative.clone()
2475 }
2476 } else if items.len() > 1 && item_idx > 0 {
2477 strip_rep_index(&dm.relative)
2478 } else {
2479 dm.relative.clone()
2480 }
2481 } else if items.len() > 1 && item_idx > 0 {
2482 strip_rep_index(&dm.relative)
2483 } else {
2484 dm.relative.clone()
2485 }
2486 } else if items.len() > 1 && item_idx > 0 {
2487 strip_rep_index(&dm.relative)
2490 } else {
2491 dm.relative.clone()
2492 };
2493
2494 let rep_used =
2495 place_in_groups(&mut child_groups, &effective_relative, instance);
2496
2497 if dm.depth == 1 {
2499 if let Some(sp) = &dm.def.meta.source_path {
2500 source_path_to_rep
2501 .entry(sp.clone())
2502 .or_default()
2503 .push(rep_used);
2504 }
2505 }
2506 }
2507 }
2508 }
2509
2510 if let Some(mig) = filtered_mig {
2515 sort_variant_reps_by_mig(&mut child_groups, mig, transaction_group);
2516 }
2517
2518 sg4_reps.push(AssembledGroupInstance {
2519 segments: root_segs,
2520 child_groups,
2521 skipped_segments: Vec::new(),
2522 });
2523 }
2524
2525 let mut root_segments = Vec::new();
2532 let mut uns_segments = Vec::new();
2533 let mut uns_is_summary = false;
2534 let mut found_uns = false;
2535 for seg in msg_tree.segments {
2536 if seg.tag == "UNS" {
2537 uns_is_summary = seg
2539 .elements
2540 .first()
2541 .and_then(|el| el.first())
2542 .map(|v| v == "S")
2543 .unwrap_or(false);
2544 uns_segments.push(seg);
2545 found_uns = true;
2546 } else if found_uns {
2547 uns_segments.push(seg);
2549 } else {
2550 root_segments.push(seg);
2551 }
2552 }
2553
2554 let pre_group_count = root_segments.len();
2555 let mut all_groups = msg_tree.groups;
2556 let mut inter_group = msg_tree.inter_group_segments;
2557
2558 let sg_num = |id: &str| -> usize {
2560 id.strip_prefix("SG")
2561 .and_then(|n| n.parse::<usize>().ok())
2562 .unwrap_or(0)
2563 };
2564
2565 if !sg4_reps.is_empty() {
2566 if uns_is_summary {
2567 all_groups.push(AssembledGroup {
2569 group_id: transaction_group.to_string(),
2570 repetitions: sg4_reps,
2571 });
2572 if !uns_segments.is_empty() {
2573 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2578 let tx_num = sg_num(transaction_group);
2579 let uns_pos = all_groups
2580 .iter()
2581 .rposition(|g| sg_num(&g.group_id) <= tx_num)
2582 .map(|i| i + 1)
2583 .unwrap_or(all_groups.len());
2584 inter_group.insert(uns_pos, uns_segments);
2585 }
2586 } else {
2587 if !uns_segments.is_empty() {
2589 inter_group.insert(all_groups.len(), uns_segments);
2590 }
2591 all_groups.push(AssembledGroup {
2592 group_id: transaction_group.to_string(),
2593 repetitions: sg4_reps,
2594 });
2595 }
2596 } else if !uns_segments.is_empty() {
2597 if transaction_group.is_empty() {
2598 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2605 inter_group.insert(0, uns_segments);
2606 } else {
2607 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2611 let tx_num = sg_num(transaction_group);
2612 let uns_pos = all_groups
2613 .iter()
2614 .rposition(|g| sg_num(&g.group_id) <= tx_num)
2615 .map(|i| i + 1)
2616 .unwrap_or(all_groups.len());
2617 inter_group.insert(uns_pos, uns_segments);
2618 }
2619 }
2620
2621 AssembledTree {
2622 segments: root_segments,
2623 groups: all_groups,
2624 post_group_start: pre_group_count,
2625 inter_group_segments: inter_group,
2626 }
2627 }
2628
2629 pub fn build_group_from_bo4e(
2631 &self,
2632 bo4e_value: &serde_json::Value,
2633 def: &MappingDefinition,
2634 ) -> AssembledGroup {
2635 let instance = self.map_reverse(bo4e_value, def);
2636 let leaf_group = def
2637 .meta
2638 .source_group
2639 .rsplit('.')
2640 .next()
2641 .unwrap_or(&def.meta.source_group);
2642
2643 AssembledGroup {
2644 group_id: leaf_group.to_string(),
2645 repetitions: vec![instance],
2646 }
2647 }
2648
2649 pub fn map_interchange_typed<M, T>(
2657 msg_engine: &MappingEngine,
2658 tx_engine: &MappingEngine,
2659 tree: &AssembledTree,
2660 tx_group: &str,
2661 enrich_codes: bool,
2662 nachrichtendaten: crate::model::Nachrichtendaten,
2663 interchangedaten: crate::model::Interchangedaten,
2664 ) -> Result<crate::model::Interchange<M, T>, serde_json::Error>
2665 where
2666 M: serde::de::DeserializeOwned,
2667 T: serde::de::DeserializeOwned,
2668 {
2669 let mapped = Self::map_interchange(msg_engine, tx_engine, tree, tx_group, enrich_codes);
2670 let nachricht = mapped.into_dynamic_nachricht(nachrichtendaten);
2671 let dynamic = crate::model::DynamicInterchange {
2672 interchangedaten,
2673 nachrichten: vec![nachricht],
2674 };
2675 let value = serde_json::to_value(&dynamic)?;
2676 serde_json::from_value(value)
2677 }
2678
2679 pub fn map_interchange_reverse_typed<M, T>(
2686 msg_engine: &MappingEngine,
2687 tx_engine: &MappingEngine,
2688 nachricht: &crate::model::Nachricht<M, T>,
2689 tx_group: &str,
2690 ) -> Result<AssembledTree, serde_json::Error>
2691 where
2692 M: serde::Serialize,
2693 T: serde::Serialize,
2694 {
2695 let stammdaten = serde_json::to_value(&nachricht.stammdaten)?;
2696 let transaktionen: Vec<crate::model::MappedTransaktion> = nachricht
2697 .transaktionen
2698 .iter()
2699 .map(|t| {
2700 Ok(crate::model::MappedTransaktion {
2701 stammdaten: serde_json::to_value(t)?,
2702 nesting_info: Default::default(),
2703 })
2704 })
2705 .collect::<Result<Vec<_>, serde_json::Error>>()?;
2706 let mapped = crate::model::MappedMessage {
2707 stammdaten,
2708 transaktionen,
2709 nesting_info: Default::default(),
2710 };
2711 Ok(Self::map_interchange_reverse(
2712 msg_engine, tx_engine, &mapped, tx_group, None,
2713 ))
2714 }
2715}
2716
2717fn parse_source_path_part(part: &str) -> (&str, Option<&str>) {
2724 if let Some(pos) = part.find('_') {
2728 let group = &part[..pos];
2729 let qualifier = &part[pos + 1..];
2730 if !qualifier.is_empty() {
2731 return (group, Some(qualifier));
2732 }
2733 }
2734 (part, None)
2735}
2736
2737fn build_reverse_mig_group_order(mig: &MigSchema, tx_group_id: &str) -> HashMap<String, usize> {
2745 let mut order = HashMap::new();
2746 if let Some(tg) = mig.segment_groups.iter().find(|g| g.id == tx_group_id) {
2747 for (i, nested) in tg.nested_groups.iter().enumerate() {
2748 if let Some(ref vc) = nested.variant_code {
2750 let variant_key = format!("{}_{}", nested.id, vc.to_uppercase());
2751 order.insert(variant_key, i);
2752 }
2753 order.entry(nested.id.clone()).or_insert(i);
2755 }
2756 }
2757 order
2758}
2759
2760fn variant_mig_position(
2766 def: &MappingDefinition,
2767 base_group_id: &str,
2768 mig_order: &HashMap<String, usize>,
2769) -> usize {
2770 if let Some(ref sp) = def.meta.source_path {
2773 let base_lower = base_group_id.to_lowercase();
2775 for part in sp.split('.') {
2776 if part.starts_with(&base_lower)
2777 || part.starts_with(base_group_id.to_lowercase().as_str())
2778 {
2779 if let Some(underscore_pos) = part.find('_') {
2781 let qualifier = &part[underscore_pos + 1..];
2782 let variant_key = format!("{}_{}", base_group_id, qualifier.to_uppercase());
2783 if let Some(&pos) = mig_order.get(&variant_key) {
2784 return pos;
2785 }
2786 }
2787 }
2788 }
2789 }
2790 mig_order.get(base_group_id).copied().unwrap_or(usize::MAX)
2792}
2793
2794fn find_rep_by_entry_qualifier<'a>(
2799 reps: &'a [AssembledGroupInstance],
2800 qualifier: &str,
2801) -> Option<&'a AssembledGroupInstance> {
2802 let parts: Vec<&str> = qualifier.split('_').collect();
2804 reps.iter().find(|inst| {
2805 inst.segments.first().is_some_and(|seg| {
2806 seg.elements
2807 .first()
2808 .and_then(|e| e.first())
2809 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
2810 })
2811 })
2812}
2813
2814fn find_all_reps_by_entry_qualifier<'a>(
2816 reps: &'a [AssembledGroupInstance],
2817 qualifier: &str,
2818) -> Vec<&'a AssembledGroupInstance> {
2819 let parts: Vec<&str> = qualifier.split('_').collect();
2821 reps.iter()
2822 .filter(|inst| {
2823 inst.segments.first().is_some_and(|seg| {
2824 seg.elements
2825 .first()
2826 .and_then(|e| e.first())
2827 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
2828 })
2829 })
2830 .collect()
2831}
2832
2833fn has_source_path_qualifiers(source_path: &str) -> bool {
2835 source_path.split('.').any(|part| {
2836 if let Some(pos) = part.find('_') {
2837 pos < part.len() - 1
2838 } else {
2839 false
2840 }
2841 })
2842}
2843
2844fn parse_group_spec(part: &str) -> (&str, Option<usize>) {
2845 if let Some(colon_pos) = part.find(':') {
2846 let id = &part[..colon_pos];
2847 let rep = part[colon_pos + 1..].parse::<usize>().ok();
2848 (id, rep)
2849 } else {
2850 (part, None)
2851 }
2852}
2853
2854fn strip_tx_group_prefix(source_group: &str, tx_group: &str) -> String {
2860 if source_group == tx_group || source_group.is_empty() {
2861 String::new()
2862 } else if let Some(rest) = source_group.strip_prefix(tx_group) {
2863 rest.strip_prefix('.').unwrap_or(rest).to_string()
2864 } else {
2865 source_group.to_string()
2866 }
2867}
2868
2869fn place_in_groups(
2877 groups: &mut Vec<AssembledGroup>,
2878 relative_path: &str,
2879 instance: AssembledGroupInstance,
2880) -> usize {
2881 let parts: Vec<&str> = relative_path.split('.').collect();
2882
2883 if parts.len() == 1 {
2884 let (id, rep) = parse_group_spec(parts[0]);
2886
2887 let group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == id) {
2889 g
2890 } else {
2891 groups.push(AssembledGroup {
2892 group_id: id.to_string(),
2893 repetitions: vec![],
2894 });
2895 groups.last_mut().unwrap()
2896 };
2897
2898 if let Some(rep_idx) = rep {
2899 while group.repetitions.len() <= rep_idx {
2901 group.repetitions.push(AssembledGroupInstance {
2902 segments: vec![],
2903 child_groups: vec![],
2904 skipped_segments: Vec::new(),
2905 });
2906 }
2907 group.repetitions[rep_idx]
2908 .segments
2909 .extend(instance.segments);
2910 group.repetitions[rep_idx]
2911 .child_groups
2912 .extend(instance.child_groups);
2913 rep_idx
2914 } else {
2915 let pos = group.repetitions.len();
2917 group.repetitions.push(instance);
2918 pos
2919 }
2920 } else {
2921 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
2923 let rep_idx = parent_rep.unwrap_or(0);
2924
2925 let parent_group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == parent_id) {
2927 g
2928 } else {
2929 groups.push(AssembledGroup {
2930 group_id: parent_id.to_string(),
2931 repetitions: vec![],
2932 });
2933 groups.last_mut().unwrap()
2934 };
2935
2936 while parent_group.repetitions.len() <= rep_idx {
2938 parent_group.repetitions.push(AssembledGroupInstance {
2939 segments: vec![],
2940 child_groups: vec![],
2941 skipped_segments: Vec::new(),
2942 });
2943 }
2944
2945 let remaining = parts[1..].join(".");
2946 place_in_groups(
2947 &mut parent_group.repetitions[rep_idx].child_groups,
2948 &remaining,
2949 instance,
2950 );
2951 rep_idx
2952 }
2953}
2954
2955fn resolve_child_relative(
2967 relative: &str,
2968 source_path: Option<&str>,
2969 source_path_to_rep: &std::collections::HashMap<String, Vec<usize>>,
2970 item_idx: usize,
2971) -> String {
2972 let parts: Vec<&str> = relative.split('.').collect();
2973 if parts.is_empty() {
2974 return relative.to_string();
2975 }
2976
2977 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
2979 if parent_rep.is_some() {
2980 return relative.to_string();
2981 }
2982
2983 if let Some(sp) = source_path {
2985 if let Some((parent_path, _child)) = sp.rsplit_once('.') {
2986 if let Some(rep_indices) = source_path_to_rep.get(parent_path) {
2987 let rep_idx = rep_indices
2989 .get(item_idx)
2990 .or_else(|| rep_indices.last())
2991 .copied()
2992 .unwrap_or(0);
2993 let rest = parts[1..].join(".");
2994 return format!("{}:{}.{}", parent_id, rep_idx, rest);
2995 }
2996 }
2997 }
2998
2999 relative.to_string()
3001}
3002
3003struct DiscriminatorMatcher<'a> {
3010 tag: &'a str,
3011 element_idx: usize,
3012 component_idx: usize,
3013 expected_values: Vec<&'a str>,
3014 occurrence: Option<usize>,
3016}
3017
3018impl<'a> DiscriminatorMatcher<'a> {
3019 fn parse(disc: &'a str) -> Option<Self> {
3020 let (spec, expected) = disc.split_once('=')?;
3021 let parts: Vec<&str> = spec.split('.').collect();
3022 if parts.len() != 3 {
3023 return None;
3024 }
3025 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
3026 Some(Self {
3027 tag: parts[0],
3028 element_idx: parts[1].parse().ok()?,
3029 component_idx: parts[2].parse().ok()?,
3030 expected_values: expected_raw.split('|').collect(),
3031 occurrence,
3032 })
3033 }
3034
3035 fn matches(&self, instance: &AssembledGroupInstance) -> bool {
3036 instance.segments.iter().any(|s| {
3037 s.tag.eq_ignore_ascii_case(self.tag)
3038 && s.elements
3039 .get(self.element_idx)
3040 .and_then(|e| e.get(self.component_idx))
3041 .map(|v| self.expected_values.iter().any(|ev| v == ev))
3042 .unwrap_or(false)
3043 })
3044 }
3045
3046 fn filter_instances<'b>(
3048 &self,
3049 instances: Vec<&'b AssembledGroupInstance>,
3050 ) -> Vec<&'b AssembledGroupInstance> {
3051 let matching: Vec<_> = instances
3052 .into_iter()
3053 .filter(|inst| self.matches(inst))
3054 .collect();
3055 if let Some(occ) = self.occurrence {
3056 matching.into_iter().nth(occ).into_iter().collect()
3057 } else {
3058 matching
3059 }
3060 }
3061}
3062
3063fn parse_discriminator_occurrence(expected: &str) -> (&str, Option<usize>) {
3069 if let Some(hash_pos) = expected.rfind('#') {
3070 if let Ok(occ) = expected[hash_pos + 1..].parse::<usize>() {
3071 return (&expected[..hash_pos], Some(occ));
3072 }
3073 }
3074 (expected, None)
3075}
3076
3077fn strip_rep_index(relative: &str) -> String {
3081 let (id, _) = parse_group_spec(relative);
3082 id.to_string()
3083}
3084
3085fn strip_all_rep_indices(relative: &str) -> String {
3090 relative
3091 .split('.')
3092 .map(|part| {
3093 let (id, _) = parse_group_spec(part);
3094 id
3095 })
3096 .collect::<Vec<_>>()
3097 .join(".")
3098}
3099
3100fn is_collect_all_path(path: &str) -> bool {
3105 let tag_part = path.split('.').next().unwrap_or("");
3106 if let Some(bracket_start) = tag_part.find('[') {
3107 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3108 if let Some(comma_pos) = inner.find(',') {
3109 let qualifier = &inner[..comma_pos];
3110 let occ = &inner[comma_pos + 1..];
3111 qualifier != "*" && occ == "*"
3113 } else {
3114 false
3115 }
3116 } else {
3117 false
3118 }
3119}
3120
3121fn parse_tag_qualifier(tag_part: &str) -> (String, Option<&str>, usize) {
3128 if let Some(bracket_start) = tag_part.find('[') {
3129 let tag = tag_part[..bracket_start].to_uppercase();
3130 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3131 if let Some(comma_pos) = inner.find(',') {
3132 let qualifier = &inner[..comma_pos];
3133 let index = inner[comma_pos + 1..].parse::<usize>().unwrap_or(0);
3134 if qualifier == "*" {
3136 (tag, None, index)
3137 } else {
3138 (tag, Some(qualifier), index)
3139 }
3140 } else {
3141 (tag, Some(inner), 0)
3142 }
3143 } else {
3144 (tag_part.to_uppercase(), None, 0)
3145 }
3146}
3147
3148fn inject_bo4e_metadata(mut value: serde_json::Value, bo4e_type: &str) -> serde_json::Value {
3153 match &mut value {
3154 serde_json::Value::Object(map) => {
3155 map.entry("boTyp")
3156 .or_insert_with(|| serde_json::Value::String(bo4e_type.to_uppercase()));
3157 map.entry("versionStruktur")
3158 .or_insert_with(|| serde_json::Value::String("1".to_string()));
3159 }
3160 serde_json::Value::Array(items) => {
3161 for item in items {
3162 if let serde_json::Value::Object(map) = item {
3163 map.entry("boTyp")
3164 .or_insert_with(|| serde_json::Value::String(bo4e_type.to_uppercase()));
3165 map.entry("versionStruktur")
3166 .or_insert_with(|| serde_json::Value::String("1".to_string()));
3167 }
3168 }
3169 }
3170 _ => {}
3171 }
3172 value
3173}
3174
3175fn deep_merge_insert(
3181 result: &mut serde_json::Map<String, serde_json::Value>,
3182 entity: &str,
3183 bo4e: serde_json::Value,
3184) {
3185 if let Some(existing) = result.get_mut(entity) {
3186 if let (Some(existing_arr), Some(new_arr)) =
3189 (existing.as_array().map(|a| a.len()), bo4e.as_array())
3190 {
3191 if existing_arr == new_arr.len() {
3192 let existing_arr = existing.as_array_mut().unwrap();
3193 for (existing_elem, new_elem) in existing_arr.iter_mut().zip(new_arr) {
3194 if let (Some(existing_map), Some(new_map)) =
3195 (existing_elem.as_object_mut(), new_elem.as_object())
3196 {
3197 for (k, v) in new_map {
3198 if let Some(existing_v) = existing_map.get_mut(k) {
3199 if let (Some(existing_inner), Some(new_inner)) =
3200 (existing_v.as_object_mut(), v.as_object())
3201 {
3202 for (ik, iv) in new_inner {
3203 existing_inner
3204 .entry(ik.clone())
3205 .or_insert_with(|| iv.clone());
3206 }
3207 }
3208 } else {
3209 existing_map.insert(k.clone(), v.clone());
3210 }
3211 }
3212 }
3213 }
3214 return;
3215 }
3216 }
3217 if let (Some(existing_map), serde_json::Value::Object(new_map)) =
3219 (existing.as_object_mut(), &bo4e)
3220 {
3221 for (k, v) in new_map {
3222 if let Some(existing_v) = existing_map.get_mut(k) {
3223 if let (Some(existing_inner), Some(new_inner)) =
3225 (existing_v.as_object_mut(), v.as_object())
3226 {
3227 for (ik, iv) in new_inner {
3228 existing_inner
3229 .entry(ik.clone())
3230 .or_insert_with(|| iv.clone());
3231 }
3232 }
3233 } else {
3235 existing_map.insert(k.clone(), v.clone());
3236 }
3237 }
3238 return;
3239 }
3240 }
3241 result.insert(entity.to_string(), bo4e);
3242}
3243
3244fn to_camel_case(name: &str) -> String {
3250 let mut chars = name.chars();
3251 match chars.next() {
3252 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
3253 None => String::new(),
3254 }
3255}
3256
3257fn set_nested_value(map: &mut serde_json::Map<String, serde_json::Value>, path: &str, val: String) {
3260 set_nested_value_json(map, path, serde_json::Value::String(val));
3261}
3262
3263fn set_nested_value_json(
3265 map: &mut serde_json::Map<String, serde_json::Value>,
3266 path: &str,
3267 val: serde_json::Value,
3268) {
3269 if let Some((prefix, leaf)) = path.rsplit_once('.') {
3270 let mut current = map;
3271 for part in prefix.split('.') {
3272 let entry = current
3273 .entry(part.to_string())
3274 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
3275 current = entry.as_object_mut().expect("expected object in path");
3276 }
3277 current.insert(leaf.to_string(), val);
3278 } else {
3279 map.insert(path.to_string(), val);
3280 }
3281}
3282
3283#[derive(serde::Serialize, serde::Deserialize)]
3288pub struct VariantCache {
3289 pub message_defs: Vec<MappingDefinition>,
3291 pub transaction_defs: HashMap<String, Vec<MappingDefinition>>,
3293 pub combined_defs: HashMap<String, Vec<MappingDefinition>>,
3295 #[serde(default)]
3297 pub code_lookups: HashMap<String, crate::code_lookup::CodeLookup>,
3298 #[serde(default)]
3300 pub mig_schema: Option<mig_types::schema::mig::MigSchema>,
3301 #[serde(default)]
3303 pub segment_structure: Option<crate::segment_structure::SegmentStructure>,
3304 #[serde(default)]
3307 pub pid_segment_numbers: HashMap<String, Vec<String>>,
3308 #[serde(default)]
3311 pub pid_requirements: HashMap<String, crate::pid_requirements::PidRequirements>,
3312}
3313
3314impl VariantCache {
3315 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
3317 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
3318 path: path.display().to_string(),
3319 message: e.to_string(),
3320 })?;
3321 if let Some(parent) = path.parent() {
3322 std::fs::create_dir_all(parent)?;
3323 }
3324 std::fs::write(path, encoded)?;
3325 Ok(())
3326 }
3327
3328 pub fn load(path: &Path) -> Result<Self, MappingError> {
3330 let bytes = std::fs::read(path)?;
3331 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
3332 path: path.display().to_string(),
3333 message: e.to_string(),
3334 })
3335 }
3336}
3337
3338#[derive(serde::Serialize, serde::Deserialize)]
3343pub struct DataBundle {
3344 pub format_version: String,
3345 pub bundle_version: u32,
3346 pub variants: HashMap<String, VariantCache>,
3347}
3348
3349impl DataBundle {
3350 pub const CURRENT_VERSION: u32 = 2;
3351
3352 pub fn variant(&self, name: &str) -> Option<&VariantCache> {
3353 self.variants.get(name)
3354 }
3355
3356 pub fn write_to<W: std::io::Write>(&self, writer: &mut W) -> Result<(), MappingError> {
3357 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
3358 path: "<stream>".to_string(),
3359 message: e.to_string(),
3360 })?;
3361 writer.write_all(&encoded).map_err(MappingError::Io)
3362 }
3363
3364 pub fn read_from<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
3365 let mut bytes = Vec::new();
3366 reader.read_to_end(&mut bytes).map_err(MappingError::Io)?;
3367 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
3368 path: "<stream>".to_string(),
3369 message: e.to_string(),
3370 })
3371 }
3372
3373 pub fn read_from_checked<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
3374 let bundle = Self::read_from(reader)?;
3375 if bundle.bundle_version != Self::CURRENT_VERSION {
3376 return Err(MappingError::CacheRead {
3377 path: "<stream>".to_string(),
3378 message: format!(
3379 "Incompatible bundle version {}, expected version {}. \
3380 Run `edifact-data update` to fetch compatible bundles.",
3381 bundle.bundle_version,
3382 Self::CURRENT_VERSION
3383 ),
3384 });
3385 }
3386 Ok(bundle)
3387 }
3388
3389 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
3390 if let Some(parent) = path.parent() {
3391 std::fs::create_dir_all(parent)?;
3392 }
3393 let mut file = std::fs::File::create(path).map_err(MappingError::Io)?;
3394 self.write_to(&mut file)
3395 }
3396
3397 pub fn load(path: &Path) -> Result<Self, MappingError> {
3398 let mut file = std::fs::File::open(path).map_err(MappingError::Io)?;
3399 Self::read_from_checked(&mut file)
3400 }
3401}
3402
3403fn sort_variant_reps_by_mig(
3416 child_groups: &mut [AssembledGroup],
3417 mig: &MigSchema,
3418 transaction_group: &str,
3419) {
3420 let tx_def = match mig
3421 .segment_groups
3422 .iter()
3423 .find(|sg| sg.id == transaction_group)
3424 {
3425 Some(d) => d,
3426 None => return,
3427 };
3428
3429 for cg in child_groups.iter_mut() {
3430 if cg.repetitions.len() <= 1 {
3431 continue;
3432 }
3433
3434 let variant_defs: Vec<(usize, &mig_types::schema::mig::MigSegmentGroup)> = tx_def
3436 .nested_groups
3437 .iter()
3438 .enumerate()
3439 .filter(|(_, ng)| ng.id == cg.group_id && ng.variant_code.is_some())
3440 .collect();
3441
3442 if variant_defs.is_empty() {
3443 continue;
3444 }
3445
3446 cg.repetitions.sort_by_key(|rep| {
3449 let entry_seg = rep.segments.first();
3450 for &(mig_pos, variant_def) in &variant_defs {
3451 let (ei, ci) = variant_def.variant_qualifier_position.unwrap_or((0, 0));
3452 let actual_qual = entry_seg
3453 .and_then(|s| s.elements.get(ei))
3454 .and_then(|e| e.get(ci))
3455 .map(|s| s.as_str())
3456 .unwrap_or("");
3457 let matches = if !variant_def.variant_codes.is_empty() {
3458 variant_def
3459 .variant_codes
3460 .iter()
3461 .any(|c| actual_qual.eq_ignore_ascii_case(c))
3462 } else if let Some(ref expected_code) = variant_def.variant_code {
3463 actual_qual.eq_ignore_ascii_case(expected_code)
3464 } else {
3465 false
3466 };
3467 if matches {
3468 return mig_pos;
3469 }
3470 }
3471 usize::MAX });
3473 }
3474}
3475
3476#[cfg(test)]
3477mod tests {
3478 use super::*;
3479 use crate::definition::{MappingDefinition, MappingMeta, StructuredFieldMapping};
3480 use indexmap::IndexMap;
3481
3482 fn make_def(fields: IndexMap<String, FieldMapping>) -> MappingDefinition {
3483 MappingDefinition {
3484 meta: MappingMeta {
3485 entity: "Test".to_string(),
3486 bo4e_type: "Test".to_string(),
3487 companion_type: None,
3488 source_group: "SG4".to_string(),
3489 source_path: None,
3490 discriminator: None,
3491 repeat_on_tag: None,
3492 },
3493 fields,
3494 companion_fields: None,
3495 complex_handlers: None,
3496 }
3497 }
3498
3499 #[test]
3500 fn test_map_interchange_single_transaction_backward_compat() {
3501 use mig_assembly::assembler::*;
3502
3503 let tree = AssembledTree {
3505 segments: vec![
3506 AssembledSegment {
3507 tag: "UNH".to_string(),
3508 elements: vec![vec!["001".to_string()]],
3509 },
3510 AssembledSegment {
3511 tag: "BGM".to_string(),
3512 elements: vec![vec!["E01".to_string()], vec!["DOC001".to_string()]],
3513 },
3514 ],
3515 groups: vec![
3516 AssembledGroup {
3517 group_id: "SG2".to_string(),
3518 repetitions: vec![AssembledGroupInstance {
3519 segments: vec![AssembledSegment {
3520 tag: "NAD".to_string(),
3521 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
3522 }],
3523 child_groups: vec![],
3524 skipped_segments: vec![],
3525 }],
3526 },
3527 AssembledGroup {
3528 group_id: "SG4".to_string(),
3529 repetitions: vec![AssembledGroupInstance {
3530 segments: vec![AssembledSegment {
3531 tag: "IDE".to_string(),
3532 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
3533 }],
3534 child_groups: vec![AssembledGroup {
3535 group_id: "SG5".to_string(),
3536 repetitions: vec![AssembledGroupInstance {
3537 segments: vec![AssembledSegment {
3538 tag: "LOC".to_string(),
3539 elements: vec![
3540 vec!["Z16".to_string()],
3541 vec!["DE000111222333".to_string()],
3542 ],
3543 }],
3544 child_groups: vec![],
3545 skipped_segments: vec![],
3546 }],
3547 }],
3548 skipped_segments: vec![],
3549 }],
3550 },
3551 ],
3552 post_group_start: 2,
3553 inter_group_segments: std::collections::BTreeMap::new(),
3554 };
3555
3556 let msg_engine = MappingEngine::from_definitions(vec![]);
3558
3559 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
3561 tx_fields.insert(
3562 "ide.1".to_string(),
3563 FieldMapping::Simple("vorgangId".to_string()),
3564 );
3565 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
3566 malo_fields.insert(
3567 "loc.1".to_string(),
3568 FieldMapping::Simple("marktlokationsId".to_string()),
3569 );
3570
3571 let tx_engine = MappingEngine::from_definitions(vec![
3572 MappingDefinition {
3573 meta: MappingMeta {
3574 entity: "Prozessdaten".to_string(),
3575 bo4e_type: "Prozessdaten".to_string(),
3576 companion_type: None,
3577 source_group: "SG4".to_string(),
3578 source_path: None,
3579 discriminator: None,
3580 repeat_on_tag: None,
3581 },
3582 fields: tx_fields,
3583 companion_fields: None,
3584 complex_handlers: None,
3585 },
3586 MappingDefinition {
3587 meta: MappingMeta {
3588 entity: "Marktlokation".to_string(),
3589 bo4e_type: "Marktlokation".to_string(),
3590 companion_type: None,
3591 source_group: "SG4.SG5".to_string(),
3592 source_path: None,
3593 discriminator: None,
3594 repeat_on_tag: None,
3595 },
3596 fields: malo_fields,
3597 companion_fields: None,
3598 complex_handlers: None,
3599 },
3600 ]);
3601
3602 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
3603
3604 assert_eq!(result.transaktionen.len(), 1);
3605 assert_eq!(
3606 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
3607 .as_str()
3608 .unwrap(),
3609 "TX001"
3610 );
3611 assert_eq!(
3612 result.transaktionen[0].stammdaten["marktlokation"]["marktlokationsId"]
3613 .as_str()
3614 .unwrap(),
3615 "DE000111222333"
3616 );
3617 }
3618
3619 #[test]
3620 fn test_map_reverse_pads_intermediate_empty_elements() {
3621 let mut fields = IndexMap::new();
3623 fields.insert(
3624 "nad.0".to_string(),
3625 FieldMapping::Structured(StructuredFieldMapping {
3626 target: String::new(),
3627 transform: None,
3628 when: None,
3629 default: Some("Z09".to_string()),
3630 enum_map: None,
3631 when_filled: None,
3632 also_target: None,
3633 also_enum_map: None,
3634 }),
3635 );
3636 fields.insert(
3637 "nad.3.0".to_string(),
3638 FieldMapping::Simple("name".to_string()),
3639 );
3640 fields.insert(
3641 "nad.3.1".to_string(),
3642 FieldMapping::Simple("vorname".to_string()),
3643 );
3644
3645 let def = make_def(fields);
3646 let engine = MappingEngine::from_definitions(vec![]);
3647
3648 let bo4e = serde_json::json!({
3649 "name": "Muster",
3650 "vorname": "Max"
3651 });
3652
3653 let instance = engine.map_reverse(&bo4e, &def);
3654 assert_eq!(instance.segments.len(), 1);
3655
3656 let nad = &instance.segments[0];
3657 assert_eq!(nad.tag, "NAD");
3658 assert_eq!(nad.elements.len(), 4);
3659 assert_eq!(nad.elements[0], vec!["Z09"]);
3660 assert_eq!(nad.elements[1], vec![""]);
3662 assert_eq!(nad.elements[2], vec![""]);
3663 assert_eq!(nad.elements[3][0], "Muster");
3664 assert_eq!(nad.elements[3][1], "Max");
3665 }
3666
3667 #[test]
3668 fn test_map_reverse_no_padding_when_contiguous() {
3669 let mut fields = IndexMap::new();
3671 fields.insert(
3672 "dtm.0.0".to_string(),
3673 FieldMapping::Structured(StructuredFieldMapping {
3674 target: String::new(),
3675 transform: None,
3676 when: None,
3677 default: Some("92".to_string()),
3678 enum_map: None,
3679 when_filled: None,
3680 also_target: None,
3681 also_enum_map: None,
3682 }),
3683 );
3684 fields.insert(
3685 "dtm.0.1".to_string(),
3686 FieldMapping::Simple("value".to_string()),
3687 );
3688 fields.insert(
3689 "dtm.0.2".to_string(),
3690 FieldMapping::Structured(StructuredFieldMapping {
3691 target: String::new(),
3692 transform: None,
3693 when: None,
3694 default: Some("303".to_string()),
3695 enum_map: None,
3696 when_filled: None,
3697 also_target: None,
3698 also_enum_map: None,
3699 }),
3700 );
3701
3702 let def = make_def(fields);
3703 let engine = MappingEngine::from_definitions(vec![]);
3704
3705 let bo4e = serde_json::json!({ "value": "20250531" });
3706
3707 let instance = engine.map_reverse(&bo4e, &def);
3708 let dtm = &instance.segments[0];
3709 assert_eq!(dtm.elements.len(), 1);
3711 assert_eq!(dtm.elements[0], vec!["92", "20250531", "303"]);
3712 }
3713
3714 #[test]
3715 fn test_map_message_level_extracts_sg2_only() {
3716 use mig_assembly::assembler::*;
3717
3718 let tree = AssembledTree {
3720 segments: vec![
3721 AssembledSegment {
3722 tag: "UNH".to_string(),
3723 elements: vec![vec!["001".to_string()]],
3724 },
3725 AssembledSegment {
3726 tag: "BGM".to_string(),
3727 elements: vec![vec!["E01".to_string()]],
3728 },
3729 ],
3730 groups: vec![
3731 AssembledGroup {
3732 group_id: "SG2".to_string(),
3733 repetitions: vec![AssembledGroupInstance {
3734 segments: vec![AssembledSegment {
3735 tag: "NAD".to_string(),
3736 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
3737 }],
3738 child_groups: vec![],
3739 skipped_segments: vec![],
3740 }],
3741 },
3742 AssembledGroup {
3743 group_id: "SG4".to_string(),
3744 repetitions: vec![AssembledGroupInstance {
3745 segments: vec![AssembledSegment {
3746 tag: "IDE".to_string(),
3747 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
3748 }],
3749 child_groups: vec![],
3750 skipped_segments: vec![],
3751 }],
3752 },
3753 ],
3754 post_group_start: 2,
3755 inter_group_segments: std::collections::BTreeMap::new(),
3756 };
3757
3758 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
3760 msg_fields.insert(
3761 "nad.0".to_string(),
3762 FieldMapping::Simple("marktrolle".to_string()),
3763 );
3764 msg_fields.insert(
3765 "nad.1".to_string(),
3766 FieldMapping::Simple("rollencodenummer".to_string()),
3767 );
3768 let msg_def = MappingDefinition {
3769 meta: MappingMeta {
3770 entity: "Marktteilnehmer".to_string(),
3771 bo4e_type: "Marktteilnehmer".to_string(),
3772 companion_type: None,
3773 source_group: "SG2".to_string(),
3774 source_path: None,
3775 discriminator: None,
3776 repeat_on_tag: None,
3777 },
3778 fields: msg_fields,
3779 companion_fields: None,
3780 complex_handlers: None,
3781 };
3782
3783 let engine = MappingEngine::from_definitions(vec![msg_def.clone()]);
3784 let result = engine.map_all_forward(&tree);
3785
3786 assert!(result.get("marktteilnehmer").is_some());
3788 let mt = &result["marktteilnehmer"];
3789 assert_eq!(mt["marktrolle"].as_str().unwrap(), "MS");
3790 assert_eq!(mt["rollencodenummer"].as_str().unwrap(), "9900123");
3791 }
3792
3793 #[test]
3794 fn test_map_transaction_scoped_to_sg4_instance() {
3795 use mig_assembly::assembler::*;
3796
3797 let tree = AssembledTree {
3799 segments: vec![
3800 AssembledSegment {
3801 tag: "UNH".to_string(),
3802 elements: vec![vec!["001".to_string()]],
3803 },
3804 AssembledSegment {
3805 tag: "BGM".to_string(),
3806 elements: vec![vec!["E01".to_string()]],
3807 },
3808 ],
3809 groups: vec![AssembledGroup {
3810 group_id: "SG4".to_string(),
3811 repetitions: vec![AssembledGroupInstance {
3812 segments: vec![AssembledSegment {
3813 tag: "IDE".to_string(),
3814 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
3815 }],
3816 child_groups: vec![AssembledGroup {
3817 group_id: "SG5".to_string(),
3818 repetitions: vec![AssembledGroupInstance {
3819 segments: vec![AssembledSegment {
3820 tag: "LOC".to_string(),
3821 elements: vec![
3822 vec!["Z16".to_string()],
3823 vec!["DE000111222333".to_string()],
3824 ],
3825 }],
3826 child_groups: vec![],
3827 skipped_segments: vec![],
3828 }],
3829 }],
3830 skipped_segments: vec![],
3831 }],
3832 }],
3833 post_group_start: 2,
3834 inter_group_segments: std::collections::BTreeMap::new(),
3835 };
3836
3837 let mut proz_fields: IndexMap<String, FieldMapping> = IndexMap::new();
3839 proz_fields.insert(
3840 "ide.1".to_string(),
3841 FieldMapping::Simple("vorgangId".to_string()),
3842 );
3843 let proz_def = MappingDefinition {
3844 meta: MappingMeta {
3845 entity: "Prozessdaten".to_string(),
3846 bo4e_type: "Prozessdaten".to_string(),
3847 companion_type: None,
3848 source_group: "".to_string(), source_path: None,
3850 discriminator: None,
3851 repeat_on_tag: None,
3852 },
3853 fields: proz_fields,
3854 companion_fields: None,
3855 complex_handlers: None,
3856 };
3857
3858 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
3859 malo_fields.insert(
3860 "loc.1".to_string(),
3861 FieldMapping::Simple("marktlokationsId".to_string()),
3862 );
3863 let malo_def = MappingDefinition {
3864 meta: MappingMeta {
3865 entity: "Marktlokation".to_string(),
3866 bo4e_type: "Marktlokation".to_string(),
3867 companion_type: None,
3868 source_group: "SG5".to_string(), source_path: None,
3870 discriminator: None,
3871 repeat_on_tag: None,
3872 },
3873 fields: malo_fields,
3874 companion_fields: None,
3875 complex_handlers: None,
3876 };
3877
3878 let tx_engine = MappingEngine::from_definitions(vec![proz_def, malo_def]);
3879
3880 let sg4 = &tree.groups[0]; let sg4_instance = &sg4.repetitions[0];
3883 let sub_tree = sg4_instance.as_assembled_tree();
3884
3885 let result = tx_engine.map_all_forward(&sub_tree);
3886
3887 assert_eq!(
3889 result["prozessdaten"]["vorgangId"].as_str().unwrap(),
3890 "TX001"
3891 );
3892
3893 assert_eq!(
3895 result["marktlokation"]["marktlokationsId"]
3896 .as_str()
3897 .unwrap(),
3898 "DE000111222333"
3899 );
3900 }
3901
3902 #[test]
3903 fn test_map_interchange_produces_full_hierarchy() {
3904 use mig_assembly::assembler::*;
3905
3906 let tree = AssembledTree {
3908 segments: vec![
3909 AssembledSegment {
3910 tag: "UNH".to_string(),
3911 elements: vec![vec!["001".to_string()]],
3912 },
3913 AssembledSegment {
3914 tag: "BGM".to_string(),
3915 elements: vec![vec!["E01".to_string()]],
3916 },
3917 ],
3918 groups: vec![
3919 AssembledGroup {
3920 group_id: "SG2".to_string(),
3921 repetitions: vec![AssembledGroupInstance {
3922 segments: vec![AssembledSegment {
3923 tag: "NAD".to_string(),
3924 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
3925 }],
3926 child_groups: vec![],
3927 skipped_segments: vec![],
3928 }],
3929 },
3930 AssembledGroup {
3931 group_id: "SG4".to_string(),
3932 repetitions: vec![
3933 AssembledGroupInstance {
3934 segments: vec![AssembledSegment {
3935 tag: "IDE".to_string(),
3936 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
3937 }],
3938 child_groups: vec![],
3939 skipped_segments: vec![],
3940 },
3941 AssembledGroupInstance {
3942 segments: vec![AssembledSegment {
3943 tag: "IDE".to_string(),
3944 elements: vec![vec!["24".to_string()], vec!["TX002".to_string()]],
3945 }],
3946 child_groups: vec![],
3947 skipped_segments: vec![],
3948 },
3949 ],
3950 },
3951 ],
3952 post_group_start: 2,
3953 inter_group_segments: std::collections::BTreeMap::new(),
3954 };
3955
3956 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
3958 msg_fields.insert(
3959 "nad.0".to_string(),
3960 FieldMapping::Simple("marktrolle".to_string()),
3961 );
3962 let msg_defs = vec![MappingDefinition {
3963 meta: MappingMeta {
3964 entity: "Marktteilnehmer".to_string(),
3965 bo4e_type: "Marktteilnehmer".to_string(),
3966 companion_type: None,
3967 source_group: "SG2".to_string(),
3968 source_path: None,
3969 discriminator: None,
3970 repeat_on_tag: None,
3971 },
3972 fields: msg_fields,
3973 companion_fields: None,
3974 complex_handlers: None,
3975 }];
3976
3977 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
3979 tx_fields.insert(
3980 "ide.1".to_string(),
3981 FieldMapping::Simple("vorgangId".to_string()),
3982 );
3983 let tx_defs = vec![MappingDefinition {
3984 meta: MappingMeta {
3985 entity: "Prozessdaten".to_string(),
3986 bo4e_type: "Prozessdaten".to_string(),
3987 companion_type: None,
3988 source_group: "SG4".to_string(),
3989 source_path: None,
3990 discriminator: None,
3991 repeat_on_tag: None,
3992 },
3993 fields: tx_fields,
3994 companion_fields: None,
3995 complex_handlers: None,
3996 }];
3997
3998 let msg_engine = MappingEngine::from_definitions(msg_defs);
3999 let tx_engine = MappingEngine::from_definitions(tx_defs);
4000
4001 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
4002
4003 assert!(result.stammdaten["marktteilnehmer"].is_object());
4005 assert_eq!(
4006 result.stammdaten["marktteilnehmer"]["marktrolle"]
4007 .as_str()
4008 .unwrap(),
4009 "MS"
4010 );
4011
4012 assert_eq!(result.transaktionen.len(), 2);
4014 assert_eq!(
4015 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
4016 .as_str()
4017 .unwrap(),
4018 "TX001"
4019 );
4020 assert_eq!(
4021 result.transaktionen[1].stammdaten["prozessdaten"]["vorgangId"]
4022 .as_str()
4023 .unwrap(),
4024 "TX002"
4025 );
4026 }
4027
4028 #[test]
4029 fn test_map_reverse_with_segment_structure_pads_trailing() {
4030 let mut fields = IndexMap::new();
4032 fields.insert(
4033 "sts.0".to_string(),
4034 FieldMapping::Structured(StructuredFieldMapping {
4035 target: String::new(),
4036 transform: None,
4037 when: None,
4038 default: Some("7".to_string()),
4039 enum_map: None,
4040 when_filled: None,
4041 also_target: None,
4042 also_enum_map: None,
4043 }),
4044 );
4045 fields.insert(
4046 "sts.2".to_string(),
4047 FieldMapping::Simple("grund".to_string()),
4048 );
4049
4050 let def = make_def(fields);
4051
4052 let mut counts = std::collections::HashMap::new();
4054 counts.insert("STS".to_string(), 5usize);
4055 let ss = SegmentStructure {
4056 element_counts: counts,
4057 };
4058
4059 let engine = MappingEngine::from_definitions(vec![]).with_segment_structure(ss);
4060
4061 let bo4e = serde_json::json!({ "grund": "E01" });
4062
4063 let instance = engine.map_reverse(&bo4e, &def);
4064 let sts = &instance.segments[0];
4065 assert_eq!(sts.elements.len(), 5);
4068 assert_eq!(sts.elements[0], vec!["7"]);
4069 assert_eq!(sts.elements[1], vec![""]);
4070 assert_eq!(sts.elements[2], vec!["E01"]);
4071 assert_eq!(sts.elements[3], vec![""]);
4072 assert_eq!(sts.elements[4], vec![""]);
4073 }
4074
4075 #[test]
4076 fn test_extract_companion_fields_with_code_enrichment() {
4077 use crate::code_lookup::CodeLookup;
4078 use mig_assembly::assembler::*;
4079
4080 let schema = serde_json::json!({
4081 "fields": {
4082 "sg4": {
4083 "children": {
4084 "sg8_z01": {
4085 "children": {
4086 "sg10": {
4087 "segments": [{
4088 "id": "CCI",
4089 "elements": [{
4090 "index": 2,
4091 "components": [{
4092 "sub_index": 0,
4093 "type": "code",
4094 "codes": [
4095 {"value": "Z15", "name": "Haushaltskunde"},
4096 {"value": "Z18", "name": "Kein Haushaltskunde"}
4097 ]
4098 }]
4099 }]
4100 }],
4101 "source_group": "SG10"
4102 }
4103 },
4104 "segments": [],
4105 "source_group": "SG8"
4106 }
4107 },
4108 "segments": [],
4109 "source_group": "SG4"
4110 }
4111 }
4112 });
4113
4114 let code_lookup = CodeLookup::from_schema_value(&schema);
4115
4116 let tree = AssembledTree {
4117 segments: vec![],
4118 groups: vec![AssembledGroup {
4119 group_id: "SG4".to_string(),
4120 repetitions: vec![AssembledGroupInstance {
4121 segments: vec![],
4122 child_groups: vec![AssembledGroup {
4123 group_id: "SG8".to_string(),
4124 repetitions: vec![AssembledGroupInstance {
4125 segments: vec![],
4126 child_groups: vec![AssembledGroup {
4127 group_id: "SG10".to_string(),
4128 repetitions: vec![AssembledGroupInstance {
4129 segments: vec![AssembledSegment {
4130 tag: "CCI".to_string(),
4131 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
4132 }],
4133 child_groups: vec![],
4134 skipped_segments: vec![],
4135 }],
4136 }],
4137 skipped_segments: vec![],
4138 }],
4139 }],
4140 skipped_segments: vec![],
4141 }],
4142 }],
4143 post_group_start: 0,
4144 inter_group_segments: std::collections::BTreeMap::new(),
4145 };
4146
4147 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4148 companion_fields.insert(
4149 "cci.2".to_string(),
4150 FieldMapping::Simple("haushaltskunde".to_string()),
4151 );
4152
4153 let def = MappingDefinition {
4154 meta: MappingMeta {
4155 entity: "Marktlokation".to_string(),
4156 bo4e_type: "Marktlokation".to_string(),
4157 companion_type: Some("MarktlokationEdifact".to_string()),
4158 source_group: "SG4.SG8.SG10".to_string(),
4159 source_path: Some("sg4.sg8_z01.sg10".to_string()),
4160 discriminator: None,
4161 repeat_on_tag: None,
4162 },
4163 fields: IndexMap::new(),
4164 companion_fields: Some(companion_fields),
4165 complex_handlers: None,
4166 };
4167
4168 let engine_plain = MappingEngine::from_definitions(vec![]);
4170 let bo4e_plain = engine_plain.map_forward(&tree, &def, 0);
4171 assert_eq!(
4172 bo4e_plain["marktlokationEdifact"]["haushaltskunde"].as_str(),
4173 Some("Z15"),
4174 "Without code lookup, should be plain string"
4175 );
4176
4177 let engine_enriched = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
4179 let bo4e_enriched = engine_enriched.map_forward(&tree, &def, 0);
4180 let hk = &bo4e_enriched["marktlokationEdifact"]["haushaltskunde"];
4181 assert_eq!(hk["code"].as_str(), Some("Z15"));
4182 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
4183 assert!(hk.get("enum").is_none());
4185 }
4186
4187 #[test]
4188 fn test_extract_companion_fields_with_enum_enrichment() {
4189 use crate::code_lookup::CodeLookup;
4190 use mig_assembly::assembler::*;
4191
4192 let schema = serde_json::json!({
4194 "fields": {
4195 "sg4": {
4196 "children": {
4197 "sg8_z01": {
4198 "children": {
4199 "sg10": {
4200 "segments": [{
4201 "id": "CCI",
4202 "elements": [{
4203 "index": 2,
4204 "components": [{
4205 "sub_index": 0,
4206 "type": "code",
4207 "codes": [
4208 {"value": "Z15", "name": "Haushaltskunde", "enum": "HAUSHALTSKUNDE"},
4209 {"value": "Z18", "name": "Kein Haushaltskunde", "enum": "KEIN_HAUSHALTSKUNDE"}
4210 ]
4211 }]
4212 }]
4213 }],
4214 "source_group": "SG10"
4215 }
4216 },
4217 "segments": [],
4218 "source_group": "SG8"
4219 }
4220 },
4221 "segments": [],
4222 "source_group": "SG4"
4223 }
4224 }
4225 });
4226
4227 let code_lookup = CodeLookup::from_schema_value(&schema);
4228
4229 let tree = AssembledTree {
4230 segments: vec![],
4231 groups: vec![AssembledGroup {
4232 group_id: "SG4".to_string(),
4233 repetitions: vec![AssembledGroupInstance {
4234 segments: vec![],
4235 child_groups: vec![AssembledGroup {
4236 group_id: "SG8".to_string(),
4237 repetitions: vec![AssembledGroupInstance {
4238 segments: vec![],
4239 child_groups: vec![AssembledGroup {
4240 group_id: "SG10".to_string(),
4241 repetitions: vec![AssembledGroupInstance {
4242 segments: vec![AssembledSegment {
4243 tag: "CCI".to_string(),
4244 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
4245 }],
4246 child_groups: vec![],
4247 skipped_segments: vec![],
4248 }],
4249 }],
4250 skipped_segments: vec![],
4251 }],
4252 }],
4253 skipped_segments: vec![],
4254 }],
4255 }],
4256 post_group_start: 0,
4257 inter_group_segments: std::collections::BTreeMap::new(),
4258 };
4259
4260 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4261 companion_fields.insert(
4262 "cci.2".to_string(),
4263 FieldMapping::Simple("haushaltskunde".to_string()),
4264 );
4265
4266 let def = MappingDefinition {
4267 meta: MappingMeta {
4268 entity: "Marktlokation".to_string(),
4269 bo4e_type: "Marktlokation".to_string(),
4270 companion_type: Some("MarktlokationEdifact".to_string()),
4271 source_group: "SG4.SG8.SG10".to_string(),
4272 source_path: Some("sg4.sg8_z01.sg10".to_string()),
4273 discriminator: None,
4274 repeat_on_tag: None,
4275 },
4276 fields: IndexMap::new(),
4277 companion_fields: Some(companion_fields),
4278 complex_handlers: None,
4279 };
4280
4281 let engine = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
4282 let bo4e = engine.map_forward(&tree, &def, 0);
4283 let hk = &bo4e["marktlokationEdifact"]["haushaltskunde"];
4284 assert_eq!(hk["code"].as_str(), Some("Z15"));
4285 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
4286 assert_eq!(
4287 hk["enum"].as_str(),
4288 Some("HAUSHALTSKUNDE"),
4289 "enum field should be present"
4290 );
4291 }
4292
4293 #[test]
4294 fn test_reverse_mapping_accepts_enriched_with_enum() {
4295 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4297 companion_fields.insert(
4298 "cci.2".to_string(),
4299 FieldMapping::Simple("haushaltskunde".to_string()),
4300 );
4301
4302 let def = MappingDefinition {
4303 meta: MappingMeta {
4304 entity: "Test".to_string(),
4305 bo4e_type: "Test".to_string(),
4306 companion_type: Some("TestEdifact".to_string()),
4307 source_group: "SG4".to_string(),
4308 source_path: None,
4309 discriminator: None,
4310 repeat_on_tag: None,
4311 },
4312 fields: IndexMap::new(),
4313 companion_fields: Some(companion_fields),
4314 complex_handlers: None,
4315 };
4316
4317 let engine = MappingEngine::from_definitions(vec![]);
4318
4319 let bo4e = serde_json::json!({
4320 "testEdifact": {
4321 "haushaltskunde": {
4322 "code": "Z15",
4323 "meaning": "Haushaltskunde",
4324 "enum": "HAUSHALTSKUNDE"
4325 }
4326 }
4327 });
4328 let instance = engine.map_reverse(&bo4e, &def);
4329 assert_eq!(instance.segments[0].elements[2], vec!["Z15"]);
4330 }
4331
4332 #[test]
4333 fn test_reverse_mapping_accepts_enriched_companion() {
4334 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4336 companion_fields.insert(
4337 "cci.2".to_string(),
4338 FieldMapping::Simple("haushaltskunde".to_string()),
4339 );
4340
4341 let def = MappingDefinition {
4342 meta: MappingMeta {
4343 entity: "Test".to_string(),
4344 bo4e_type: "Test".to_string(),
4345 companion_type: Some("TestEdifact".to_string()),
4346 source_group: "SG4".to_string(),
4347 source_path: None,
4348 discriminator: None,
4349 repeat_on_tag: None,
4350 },
4351 fields: IndexMap::new(),
4352 companion_fields: Some(companion_fields),
4353 complex_handlers: None,
4354 };
4355
4356 let engine = MappingEngine::from_definitions(vec![]);
4357
4358 let bo4e_plain = serde_json::json!({
4360 "testEdifact": {
4361 "haushaltskunde": "Z15"
4362 }
4363 });
4364 let instance_plain = engine.map_reverse(&bo4e_plain, &def);
4365 assert_eq!(instance_plain.segments[0].elements[2], vec!["Z15"]);
4366
4367 let bo4e_enriched = serde_json::json!({
4369 "testEdifact": {
4370 "haushaltskunde": {
4371 "code": "Z15",
4372 "meaning": "Haushaltskunde gem. EnWG"
4373 }
4374 }
4375 });
4376 let instance_enriched = engine.map_reverse(&bo4e_enriched, &def);
4377 assert_eq!(instance_enriched.segments[0].elements[2], vec!["Z15"]);
4378 }
4379
4380 #[test]
4381 fn test_resolve_child_relative_with_source_path() {
4382 let mut map: std::collections::HashMap<String, Vec<usize>> =
4383 std::collections::HashMap::new();
4384 map.insert("sg4.sg8_ze1".to_string(), vec![6]);
4385 map.insert("sg4.sg8_z98".to_string(), vec![0]);
4386
4387 assert_eq!(
4389 resolve_child_relative("SG8.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
4390 "SG8:6.SG10"
4391 );
4392
4393 assert_eq!(
4395 resolve_child_relative("SG8:3.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
4396 "SG8:3.SG10"
4397 );
4398
4399 assert_eq!(
4401 resolve_child_relative("SG8.SG10", Some("sg4.sg8_unknown.sg10"), &map, 0),
4402 "SG8.SG10"
4403 );
4404
4405 assert_eq!(
4407 resolve_child_relative("SG8.SG10", None, &map, 0),
4408 "SG8.SG10"
4409 );
4410
4411 assert_eq!(
4413 resolve_child_relative("SG8.SG9", Some("sg4.sg8_z98.sg9"), &map, 0),
4414 "SG8:0.SG9"
4415 );
4416
4417 map.insert("sg4.sg8_zf3".to_string(), vec![3, 4]);
4419 assert_eq!(
4420 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 0),
4421 "SG8:3.SG10"
4422 );
4423 assert_eq!(
4424 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 1),
4425 "SG8:4.SG10"
4426 );
4427 }
4428
4429 #[test]
4430 fn test_place_in_groups_returns_rep_index() {
4431 let mut groups: Vec<AssembledGroup> = Vec::new();
4432
4433 let instance = AssembledGroupInstance {
4435 segments: vec![],
4436 child_groups: vec![],
4437 skipped_segments: vec![],
4438 };
4439 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 0);
4440
4441 let instance = AssembledGroupInstance {
4443 segments: vec![],
4444 child_groups: vec![],
4445 skipped_segments: vec![],
4446 };
4447 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 1);
4448
4449 let instance = AssembledGroupInstance {
4451 segments: vec![],
4452 child_groups: vec![],
4453 skipped_segments: vec![],
4454 };
4455 assert_eq!(place_in_groups(&mut groups, "SG8:5", instance), 5);
4456 }
4457
4458 #[test]
4459 fn test_resolve_by_source_path() {
4460 use mig_assembly::assembler::*;
4461
4462 let tree = AssembledTree {
4464 segments: vec![],
4465 groups: vec![AssembledGroup {
4466 group_id: "SG4".to_string(),
4467 repetitions: vec![AssembledGroupInstance {
4468 segments: vec![],
4469 child_groups: vec![AssembledGroup {
4470 group_id: "SG8".to_string(),
4471 repetitions: vec![
4472 AssembledGroupInstance {
4473 segments: vec![AssembledSegment {
4474 tag: "SEQ".to_string(),
4475 elements: vec![vec!["Z98".to_string()]],
4476 }],
4477 child_groups: vec![AssembledGroup {
4478 group_id: "SG10".to_string(),
4479 repetitions: vec![AssembledGroupInstance {
4480 segments: vec![AssembledSegment {
4481 tag: "CCI".to_string(),
4482 elements: vec![vec![], vec![], vec!["ZB3".to_string()]],
4483 }],
4484 child_groups: vec![],
4485 skipped_segments: vec![],
4486 }],
4487 }],
4488 skipped_segments: vec![],
4489 },
4490 AssembledGroupInstance {
4491 segments: vec![AssembledSegment {
4492 tag: "SEQ".to_string(),
4493 elements: vec![vec!["ZD7".to_string()]],
4494 }],
4495 child_groups: vec![AssembledGroup {
4496 group_id: "SG10".to_string(),
4497 repetitions: vec![AssembledGroupInstance {
4498 segments: vec![AssembledSegment {
4499 tag: "CCI".to_string(),
4500 elements: vec![vec![], vec![], vec!["ZE6".to_string()]],
4501 }],
4502 child_groups: vec![],
4503 skipped_segments: vec![],
4504 }],
4505 }],
4506 skipped_segments: vec![],
4507 },
4508 ],
4509 }],
4510 skipped_segments: vec![],
4511 }],
4512 }],
4513 post_group_start: 0,
4514 inter_group_segments: std::collections::BTreeMap::new(),
4515 };
4516
4517 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_z98.sg10");
4519 assert!(inst.is_some());
4520 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
4521
4522 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zd7.sg10");
4524 assert!(inst.is_some());
4525 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZE6");
4526
4527 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zzz.sg10");
4529 assert!(inst.is_none());
4530
4531 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8.sg10");
4533 assert!(inst.is_some());
4534 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
4535 }
4536
4537 #[test]
4538 fn test_parse_source_path_part() {
4539 assert_eq!(parse_source_path_part("sg4"), ("sg4", None));
4540 assert_eq!(parse_source_path_part("sg8_z98"), ("sg8", Some("z98")));
4541 assert_eq!(parse_source_path_part("sg10"), ("sg10", None));
4542 assert_eq!(parse_source_path_part("sg12_z04"), ("sg12", Some("z04")));
4543 }
4544
4545 #[test]
4546 fn test_has_source_path_qualifiers() {
4547 assert!(has_source_path_qualifiers("sg4.sg8_z98.sg10"));
4548 assert!(has_source_path_qualifiers("sg4.sg8_ze1.sg9"));
4549 assert!(!has_source_path_qualifiers("sg4.sg6"));
4550 assert!(!has_source_path_qualifiers("sg4.sg8.sg10"));
4551 }
4552
4553 #[test]
4554 fn test_companion_dotted_path_forward() {
4555 use mig_assembly::assembler::*;
4556
4557 let tree = AssembledTree {
4559 segments: vec![],
4560 groups: vec![AssembledGroup {
4561 group_id: "SG4".to_string(),
4562 repetitions: vec![AssembledGroupInstance {
4563 segments: vec![],
4564 child_groups: vec![AssembledGroup {
4565 group_id: "SG8".to_string(),
4566 repetitions: vec![AssembledGroupInstance {
4567 segments: vec![],
4568 child_groups: vec![AssembledGroup {
4569 group_id: "SG10".to_string(),
4570 repetitions: vec![AssembledGroupInstance {
4571 segments: vec![AssembledSegment {
4572 tag: "CCI".to_string(),
4573 elements: vec![
4574 vec!["11XAB-1234".to_string()],
4575 vec!["305".to_string()],
4576 ],
4577 }],
4578 child_groups: vec![],
4579 skipped_segments: vec![],
4580 }],
4581 }],
4582 skipped_segments: vec![],
4583 }],
4584 }],
4585 skipped_segments: vec![],
4586 }],
4587 }],
4588 post_group_start: 0,
4589 inter_group_segments: std::collections::BTreeMap::new(),
4590 };
4591
4592 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4594 companion_fields.insert(
4595 "cci.0".to_string(),
4596 FieldMapping::Simple("bilanzkreis.id".to_string()),
4597 );
4598 companion_fields.insert(
4599 "cci.1".to_string(),
4600 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
4601 );
4602
4603 let def = MappingDefinition {
4604 meta: MappingMeta {
4605 entity: "Test".to_string(),
4606 bo4e_type: "Test".to_string(),
4607 companion_type: Some("TestEdifact".to_string()),
4608 source_group: "SG4.SG8.SG10".to_string(),
4609 source_path: Some("sg4.sg8_z01.sg10".to_string()),
4610 discriminator: None,
4611 repeat_on_tag: None,
4612 },
4613 fields: IndexMap::new(),
4614 companion_fields: Some(companion_fields),
4615 complex_handlers: None,
4616 };
4617
4618 let engine = MappingEngine::from_definitions(vec![]);
4619 let bo4e = engine.map_forward(&tree, &def, 0);
4620
4621 let companion = &bo4e["testEdifact"];
4623 assert!(
4624 companion.is_object(),
4625 "testEdifact should be an object, got: {companion}"
4626 );
4627 let bilanzkreis = &companion["bilanzkreis"];
4628 assert!(
4629 bilanzkreis.is_object(),
4630 "bilanzkreis should be a nested object, got: {bilanzkreis}"
4631 );
4632 assert_eq!(
4633 bilanzkreis["id"].as_str(),
4634 Some("11XAB-1234"),
4635 "bilanzkreis.id should be 11XAB-1234"
4636 );
4637 assert_eq!(
4638 bilanzkreis["codelist"].as_str(),
4639 Some("305"),
4640 "bilanzkreis.codelist should be 305"
4641 );
4642 }
4643
4644 #[test]
4645 fn test_companion_dotted_path_reverse() {
4646 let engine = MappingEngine::from_definitions(vec![]);
4648
4649 let companion_value = serde_json::json!({
4650 "bilanzkreis": {
4651 "id": "11XAB-1234",
4652 "codelist": "305"
4653 }
4654 });
4655
4656 assert_eq!(
4657 engine.populate_field(&companion_value, "bilanzkreis.id"),
4658 Some("11XAB-1234".to_string()),
4659 "dotted path bilanzkreis.id should resolve"
4660 );
4661 assert_eq!(
4662 engine.populate_field(&companion_value, "bilanzkreis.codelist"),
4663 Some("305".to_string()),
4664 "dotted path bilanzkreis.codelist should resolve"
4665 );
4666
4667 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4669 companion_fields.insert(
4670 "cci.0".to_string(),
4671 FieldMapping::Simple("bilanzkreis.id".to_string()),
4672 );
4673 companion_fields.insert(
4674 "cci.1".to_string(),
4675 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
4676 );
4677
4678 let def = MappingDefinition {
4679 meta: MappingMeta {
4680 entity: "Test".to_string(),
4681 bo4e_type: "Test".to_string(),
4682 companion_type: Some("TestEdifact".to_string()),
4683 source_group: "SG4.SG8.SG10".to_string(),
4684 source_path: Some("sg4.sg8_z01.sg10".to_string()),
4685 discriminator: None,
4686 repeat_on_tag: None,
4687 },
4688 fields: IndexMap::new(),
4689 companion_fields: Some(companion_fields),
4690 complex_handlers: None,
4691 };
4692
4693 let bo4e = serde_json::json!({
4694 "testEdifact": {
4695 "bilanzkreis": {
4696 "id": "11XAB-1234",
4697 "codelist": "305"
4698 }
4699 }
4700 });
4701
4702 let instance = engine.map_reverse(&bo4e, &def);
4703 assert_eq!(instance.segments.len(), 1, "should produce one CCI segment");
4704 let cci = &instance.segments[0];
4705 assert_eq!(cci.tag, "CCI");
4706 assert_eq!(
4707 cci.elements[0],
4708 vec!["11XAB-1234"],
4709 "element 0 should contain bilanzkreis.id"
4710 );
4711 assert_eq!(
4712 cci.elements[1],
4713 vec!["305"],
4714 "element 1 should contain bilanzkreis.codelist"
4715 );
4716 }
4717
4718 #[test]
4719 fn test_when_filled_injects_when_field_present() {
4720 let toml_str = r#"
4721[meta]
4722entity = "Test"
4723bo4e_type = "Test"
4724companion_type = "TestEdifact"
4725source_group = "SG4.SG8.SG10"
4726
4727[fields]
4728
4729[companion_fields]
4730"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmalCode"] }
4731"cav.0.0" = "merkmalCode"
4732"#;
4733 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
4734
4735 let bo4e_with = serde_json::json!({
4737 "testEdifact": { "merkmalCode": "ZA7" }
4738 });
4739 let engine = MappingEngine::new_empty();
4740 let instance = engine.map_reverse(&bo4e_with, &def);
4741 let cci = instance
4742 .segments
4743 .iter()
4744 .find(|s| s.tag == "CCI")
4745 .expect("CCI should exist");
4746 assert_eq!(cci.elements[0][0], "Z83");
4747
4748 let bo4e_without = serde_json::json!({
4750 "testEdifact": {}
4751 });
4752 let instance2 = engine.map_reverse(&bo4e_without, &def);
4753 let cci2 = instance2.segments.iter().find(|s| s.tag == "CCI");
4754 assert!(
4755 cci2.is_none(),
4756 "CCI should not be emitted when merkmalCode is absent"
4757 );
4758 }
4759
4760 #[test]
4761 fn test_when_filled_checks_core_and_companion() {
4762 let toml_str = r#"
4763[meta]
4764entity = "Test"
4765bo4e_type = "Test"
4766companion_type = "TestEdifact"
4767source_group = "SG4.SG5"
4768
4769[fields]
4770"loc.1.0" = "marktlokationsId"
4771
4772[companion_fields]
4773"loc.0.0" = { target = "", default = "Z16", when_filled = ["marktlokationsId"] }
4774"#;
4775 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
4776
4777 let bo4e_with = serde_json::json!({
4779 "marktlokationsId": "51234567890"
4780 });
4781 let engine = MappingEngine::new_empty();
4782 let instance = engine.map_reverse(&bo4e_with, &def);
4783 let loc = instance
4784 .segments
4785 .iter()
4786 .find(|s| s.tag == "LOC")
4787 .expect("LOC should exist");
4788 assert_eq!(loc.elements[0][0], "Z16");
4789 assert_eq!(loc.elements[1][0], "51234567890");
4790
4791 let bo4e_without = serde_json::json!({});
4793 let instance2 = engine.map_reverse(&bo4e_without, &def);
4794 let loc2 = instance2.segments.iter().find(|s| s.tag == "LOC");
4795 assert!(loc2.is_none());
4796 }
4797
4798 #[test]
4799 fn test_extract_all_from_instance_collects_all_qualifier_matches() {
4800 use mig_assembly::assembler::*;
4801
4802 let instance = AssembledGroupInstance {
4804 segments: vec![
4805 AssembledSegment {
4806 tag: "SEQ".to_string(),
4807 elements: vec![vec!["ZD6".to_string()]],
4808 },
4809 AssembledSegment {
4810 tag: "RFF".to_string(),
4811 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
4812 },
4813 AssembledSegment {
4814 tag: "RFF".to_string(),
4815 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
4816 },
4817 AssembledSegment {
4818 tag: "RFF".to_string(),
4819 elements: vec![vec!["Z34".to_string(), "REF_C".to_string()]],
4820 },
4821 AssembledSegment {
4822 tag: "RFF".to_string(),
4823 elements: vec![vec!["Z35".to_string(), "OTHER".to_string()]],
4824 },
4825 ],
4826 child_groups: vec![],
4827 skipped_segments: vec![],
4828 };
4829
4830 let all = MappingEngine::extract_all_from_instance(&instance, "rff[Z34,*].0.1");
4832 assert_eq!(all, vec!["REF_A", "REF_B", "REF_C"]);
4833
4834 let single = MappingEngine::extract_from_instance(&instance, "rff[Z34].0.1");
4836 assert_eq!(single, Some("REF_A".to_string()));
4837
4838 let second = MappingEngine::extract_from_instance(&instance, "rff[Z34,1].0.1");
4839 assert_eq!(second, Some("REF_B".to_string()));
4840 }
4841
4842 #[test]
4843 fn test_forward_wildcard_collect_produces_json_array() {
4844 use mig_assembly::assembler::*;
4845
4846 let instance = AssembledGroupInstance {
4847 segments: vec![
4848 AssembledSegment {
4849 tag: "SEQ".to_string(),
4850 elements: vec![vec!["ZD6".to_string()]],
4851 },
4852 AssembledSegment {
4853 tag: "RFF".to_string(),
4854 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
4855 },
4856 AssembledSegment {
4857 tag: "RFF".to_string(),
4858 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
4859 },
4860 ],
4861 child_groups: vec![],
4862 skipped_segments: vec![],
4863 };
4864
4865 let toml_str = r#"
4866[meta]
4867entity = "Test"
4868bo4e_type = "Test"
4869companion_type = "TestEdifact"
4870source_group = "SG4.SG8"
4871
4872[fields]
4873
4874[companion_fields]
4875"rff[Z34,*].0.1" = "messlokationsIdRefs"
4876"#;
4877 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
4878 let engine = MappingEngine::new_empty();
4879
4880 let mut result = serde_json::Map::new();
4881 engine.extract_companion_fields(&instance, &def, &mut result, false);
4882
4883 let companion = result.get("testEdifact").unwrap().as_object().unwrap();
4884 let refs = companion
4885 .get("messlokationsIdRefs")
4886 .unwrap()
4887 .as_array()
4888 .unwrap();
4889 assert_eq!(refs.len(), 2);
4890 assert_eq!(refs[0].as_str().unwrap(), "REF_A");
4891 assert_eq!(refs[1].as_str().unwrap(), "REF_B");
4892 }
4893
4894 #[test]
4895 fn test_reverse_json_array_produces_multiple_segments() {
4896 let toml_str = r#"
4897[meta]
4898entity = "Test"
4899bo4e_type = "Test"
4900companion_type = "TestEdifact"
4901source_group = "SG4.SG8"
4902
4903[fields]
4904
4905[companion_fields]
4906"seq.0.0" = { target = "", default = "ZD6" }
4907"rff[Z34,*].0.1" = "messlokationsIdRefs"
4908"#;
4909 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
4910 let engine = MappingEngine::new_empty();
4911
4912 let bo4e = serde_json::json!({
4913 "testEdifact": {
4914 "messlokationsIdRefs": ["REF_A", "REF_B", "REF_C"]
4915 }
4916 });
4917
4918 let instance = engine.map_reverse(&bo4e, &def);
4919
4920 let rff_segs: Vec<_> = instance
4922 .segments
4923 .iter()
4924 .filter(|s| s.tag == "RFF")
4925 .collect();
4926 assert_eq!(rff_segs.len(), 3);
4927 assert_eq!(rff_segs[0].elements[0][0], "Z34");
4928 assert_eq!(rff_segs[0].elements[0][1], "REF_A");
4929 assert_eq!(rff_segs[1].elements[0][0], "Z34");
4930 assert_eq!(rff_segs[1].elements[0][1], "REF_B");
4931 assert_eq!(rff_segs[2].elements[0][0], "Z34");
4932 assert_eq!(rff_segs[2].elements[0][1], "REF_C");
4933 }
4934
4935 #[test]
4936 fn test_when_filled_dotted_path() {
4937 let toml_str = r#"
4938[meta]
4939entity = "Test"
4940bo4e_type = "Test"
4941companion_type = "TestEdifact"
4942source_group = "SG4.SG8.SG10"
4943
4944[fields]
4945
4946[companion_fields]
4947"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmal.code"] }
4948"cav.0.0" = "merkmal.code"
4949"#;
4950 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
4951
4952 let bo4e = serde_json::json!({
4953 "testEdifact": { "merkmal": { "code": "ZA7" } }
4954 });
4955 let engine = MappingEngine::new_empty();
4956 let instance = engine.map_reverse(&bo4e, &def);
4957 let cci = instance
4958 .segments
4959 .iter()
4960 .find(|s| s.tag == "CCI")
4961 .expect("CCI should exist");
4962 assert_eq!(cci.elements[0][0], "Z83");
4963 }
4964
4965 #[test]
4966 fn test_also_target_forward_extracts_both_fields() {
4967 use mig_assembly::assembler::*;
4968
4969 let instance = AssembledGroupInstance {
4970 segments: vec![AssembledSegment {
4971 tag: "NAD".to_string(),
4972 elements: vec![vec!["Z47".to_string()], vec!["12345".to_string()]],
4973 }],
4974 child_groups: vec![],
4975 skipped_segments: vec![],
4976 };
4977
4978 let toml_str = r#"
4979[meta]
4980entity = "Geschaeftspartner"
4981bo4e_type = "Geschaeftspartner"
4982companion_type = "GeschaeftspartnerEdifact"
4983source_group = "SG4.SG12"
4984
4985[fields]
4986"nad.1.0" = "identifikation"
4987
4988[companion_fields."nad.0.0"]
4989target = "partnerrolle"
4990enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
4991also_target = "datenqualitaet"
4992also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
4993"#;
4994 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
4995 let engine = MappingEngine::new_empty();
4996
4997 let mut result = serde_json::Map::new();
4998 engine.extract_companion_fields(&instance, &def, &mut result, false);
4999
5000 let companion = result
5001 .get("geschaeftspartnerEdifact")
5002 .unwrap()
5003 .as_object()
5004 .unwrap();
5005 assert_eq!(
5006 companion.get("partnerrolle").unwrap().as_str().unwrap(),
5007 "kundeDesLf"
5008 );
5009 assert_eq!(
5010 companion.get("datenqualitaet").unwrap().as_str().unwrap(),
5011 "erwartet"
5012 );
5013 }
5014
5015 #[test]
5016 fn test_also_target_reverse_joint_lookup() {
5017 let toml_str = r#"
5018[meta]
5019entity = "Geschaeftspartner"
5020bo4e_type = "Geschaeftspartner"
5021companion_type = "GeschaeftspartnerEdifact"
5022source_group = "SG4.SG12"
5023
5024[fields]
5025
5026[companion_fields."nad.0.0"]
5027target = "partnerrolle"
5028enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
5029also_target = "datenqualitaet"
5030also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
5031"#;
5032 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5033 let engine = MappingEngine::new_empty();
5034
5035 let bo4e = serde_json::json!({
5037 "geschaeftspartnerEdifact": {
5038 "partnerrolle": "kundeDesLf",
5039 "datenqualitaet": "erwartet"
5040 }
5041 });
5042 let instance = engine.map_reverse(&bo4e, &def);
5043 let nad = instance
5044 .segments
5045 .iter()
5046 .find(|s| s.tag == "NAD")
5047 .expect("NAD");
5048 assert_eq!(nad.elements[0][0], "Z47");
5049
5050 let bo4e2 = serde_json::json!({
5052 "geschaeftspartnerEdifact": {
5053 "partnerrolle": "kundeDesNb",
5054 "datenqualitaet": "imSystemVorhanden"
5055 }
5056 });
5057 let instance2 = engine.map_reverse(&bo4e2, &def);
5058 let nad2 = instance2
5059 .segments
5060 .iter()
5061 .find(|s| s.tag == "NAD")
5062 .expect("NAD");
5063 assert_eq!(nad2.elements[0][0], "Z52");
5064 }
5065
5066 #[test]
5067 fn test_also_target_mixed_codes_unpaired_skips_datenqualitaet() {
5068 use mig_assembly::assembler::*;
5069
5070 let toml_str = r#"
5072[meta]
5073entity = "Geschaeftspartner"
5074bo4e_type = "Geschaeftspartner"
5075companion_type = "GeschaeftspartnerEdifact"
5076source_group = "SG4.SG12"
5077
5078[fields]
5079
5080[companion_fields."nad.0.0"]
5081target = "partnerrolle"
5082enum_map = { "Z09" = "kundeDesLf", "Z47" = "kundeDesLf", "Z48" = "kundeDesLf" }
5083also_target = "datenqualitaet"
5084also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden" }
5085"#;
5086 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5087 let engine = MappingEngine::new_empty();
5088
5089 let instance_z09 = AssembledGroupInstance {
5091 segments: vec![AssembledSegment {
5092 tag: "NAD".to_string(),
5093 elements: vec![vec!["Z09".to_string()]],
5094 }],
5095 child_groups: vec![],
5096 skipped_segments: vec![],
5097 };
5098 let mut result = serde_json::Map::new();
5099 engine.extract_companion_fields(&instance_z09, &def, &mut result, false);
5100 let comp = result
5101 .get("geschaeftspartnerEdifact")
5102 .unwrap()
5103 .as_object()
5104 .unwrap();
5105 assert_eq!(
5106 comp.get("partnerrolle").unwrap().as_str().unwrap(),
5107 "kundeDesLf"
5108 );
5109 assert!(
5110 comp.get("datenqualitaet").is_none(),
5111 "Z09 should not set datenqualitaet"
5112 );
5113
5114 let bo4e = serde_json::json!({
5116 "geschaeftspartnerEdifact": { "partnerrolle": "kundeDesLf" }
5117 });
5118 let instance = engine.map_reverse(&bo4e, &def);
5119 let nad = instance
5120 .segments
5121 .iter()
5122 .find(|s| s.tag == "NAD")
5123 .expect("NAD");
5124 assert_eq!(nad.elements[0][0], "Z09");
5125
5126 let bo4e2 = serde_json::json!({
5128 "geschaeftspartnerEdifact": {
5129 "partnerrolle": "kundeDesLf",
5130 "datenqualitaet": "erwartet"
5131 }
5132 });
5133 let instance2 = engine.map_reverse(&bo4e2, &def);
5134 let nad2 = instance2
5135 .segments
5136 .iter()
5137 .find(|s| s.tag == "NAD")
5138 .expect("NAD");
5139 assert_eq!(nad2.elements[0][0], "Z47");
5140 }
5141}