1use std::collections::{HashMap, HashSet};
7use std::path::Path;
8
9use mig_assembly::assembler::{
10 AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree,
11};
12use mig_types::schema::mig::MigSchema;
13use mig_types::segment::OwnedSegment;
14
15use crate::definition::{FieldMapping, MappingDefinition};
16use crate::error::MappingError;
17use crate::segment_structure::SegmentStructure;
18
19pub struct MappingEngine {
22 definitions: Vec<MappingDefinition>,
23 segment_structure: Option<SegmentStructure>,
24 code_lookup: Option<crate::code_lookup::CodeLookup>,
25}
26
27impl MappingEngine {
28 pub fn new_empty() -> Self {
30 Self {
31 definitions: Vec::new(),
32 segment_structure: None,
33 code_lookup: None,
34 }
35 }
36
37 pub fn load(dir: &Path) -> Result<Self, MappingError> {
39 let mut definitions = Vec::new();
40
41 let mut entries: Vec<_> = std::fs::read_dir(dir)?.filter_map(|e| e.ok()).collect();
42 entries.sort_by_key(|e| e.file_name());
43
44 for entry in entries {
45 let path = entry.path();
46 if path.extension().map(|e| e == "toml").unwrap_or(false) {
47 let content = std::fs::read_to_string(&path)?;
48 let def: MappingDefinition =
49 toml::from_str(&content).map_err(|e| MappingError::TomlParse {
50 file: path.display().to_string(),
51 message: e.to_string(),
52 })?;
53 definitions.push(def);
54 }
55 }
56
57 Ok(Self {
58 definitions,
59 segment_structure: None,
60 code_lookup: None,
61 })
62 }
63
64 pub fn load_split(
70 message_dir: &Path,
71 transaction_dir: &Path,
72 ) -> Result<(Self, Self), MappingError> {
73 let msg_engine = Self::load(message_dir)?;
74 let tx_engine = Self::load(transaction_dir)?;
75 Ok((msg_engine, tx_engine))
76 }
77
78 pub fn load_merged(dirs: &[&Path]) -> Result<Self, MappingError> {
83 let mut definitions = Vec::new();
84 for dir in dirs {
85 let engine = Self::load(dir)?;
86 definitions.extend(engine.definitions);
87 }
88 Ok(Self {
89 definitions,
90 segment_structure: None,
91 code_lookup: None,
92 })
93 }
94
95 pub fn load_with_common(
104 common_dir: &Path,
105 pid_dir: &Path,
106 schema_index: &crate::pid_schema_index::PidSchemaIndex,
107 ) -> Result<Self, MappingError> {
108 let mut common_defs = Self::load(common_dir)?.definitions;
109
110 common_defs.retain(|d| {
112 d.meta
113 .source_path
114 .as_deref()
115 .map(|sp| schema_index.has_group(sp))
116 .unwrap_or(true)
117 });
118
119 let pid_defs = Self::load(pid_dir)?.definitions;
120
121 let normalize_sg = |sg: &str| -> String {
126 sg.split('.')
127 .map(|part| part.split(':').next().unwrap_or(part))
128 .collect::<Vec<_>>()
129 .join(".")
130 };
131 let pid_keys: HashSet<(String, Option<String>)> = pid_defs
132 .iter()
133 .flat_map(|d| {
134 let sg = normalize_sg(&d.meta.source_group);
135 let disc = d.meta.discriminator.clone();
136 let mut keys = vec![(sg.clone(), disc.clone())];
137 if let Some(ref disc_str) = disc {
139 if let Some(base) = disc_str.rsplit_once('#') {
140 if base.1.chars().all(|c| c.is_ascii_digit()) {
141 keys.push((sg, Some(base.0.to_string())));
142 }
143 }
144 }
145 keys
146 })
147 .collect();
148
149 common_defs.retain(|d| {
151 let key = (
152 normalize_sg(&d.meta.source_group),
153 d.meta.discriminator.clone(),
154 );
155 !pid_keys.contains(&key)
156 });
157
158 let mut definitions = common_defs;
160 definitions.extend(pid_defs);
161
162 Ok(Self {
163 definitions,
164 segment_structure: None,
165 code_lookup: None,
166 })
167 }
168
169 pub fn load_common_only(
173 common_dir: &Path,
174 schema_index: &crate::pid_schema_index::PidSchemaIndex,
175 ) -> Result<Self, MappingError> {
176 let mut common_defs = Self::load(common_dir)?.definitions;
177
178 common_defs.retain(|d| {
180 d.meta
181 .source_path
182 .as_deref()
183 .map(|sp| schema_index.has_group(sp))
184 .unwrap_or(true)
185 });
186
187 Ok(Self {
188 definitions: common_defs,
189 segment_structure: None,
190 code_lookup: None,
191 })
192 }
193
194 pub fn load_split_with_common(
199 message_dir: &Path,
200 common_dir: &Path,
201 transaction_dir: &Path,
202 schema_index: &crate::pid_schema_index::PidSchemaIndex,
203 ) -> Result<(Self, Self), MappingError> {
204 let msg_engine = Self::load(message_dir)?;
205 let tx_engine = Self::load_with_common(common_dir, transaction_dir, schema_index)?;
206 Ok((msg_engine, tx_engine))
207 }
208
209 pub fn from_definitions(definitions: Vec<MappingDefinition>) -> Self {
211 Self {
212 definitions,
213 segment_structure: None,
214 code_lookup: None,
215 }
216 }
217
218 pub fn save_cached(&self, path: &Path) -> Result<(), MappingError> {
224 let encoded =
225 serde_json::to_vec(&self.definitions).map_err(|e| MappingError::CacheWrite {
226 path: path.display().to_string(),
227 message: e.to_string(),
228 })?;
229 if let Some(parent) = path.parent() {
230 std::fs::create_dir_all(parent)?;
231 }
232 std::fs::write(path, encoded)?;
233 Ok(())
234 }
235
236 pub fn load_cached_or_toml(cache_path: &Path, toml_dir: &Path) -> Result<Self, MappingError> {
241 if cache_path.exists() {
242 Self::load_cached(cache_path)
243 } else {
244 Self::load(toml_dir)
245 }
246 }
247
248 pub fn load_cached(path: &Path) -> Result<Self, MappingError> {
253 let bytes = std::fs::read(path)?;
254 let definitions: Vec<MappingDefinition> =
255 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
256 path: path.display().to_string(),
257 message: e.to_string(),
258 })?;
259 Ok(Self {
260 definitions,
261 segment_structure: None,
262 code_lookup: None,
263 })
264 }
265
266 pub fn with_segment_structure(mut self, ss: SegmentStructure) -> Self {
271 self.segment_structure = Some(ss);
272 self
273 }
274
275 pub fn with_code_lookup(mut self, cl: crate::code_lookup::CodeLookup) -> Self {
280 self.code_lookup = Some(cl);
281 self
282 }
283
284 pub fn with_path_resolver(mut self, resolver: crate::path_resolver::PathResolver) -> Self {
290 for def in &mut self.definitions {
291 def.normalize_paths(&resolver);
292 }
293 self
294 }
295
296 pub fn definitions(&self) -> &[MappingDefinition] {
298 &self.definitions
299 }
300
301 pub fn definition_for_entity(&self, entity: &str) -> Option<&MappingDefinition> {
303 self.definitions.iter().find(|d| d.meta.entity == entity)
304 }
305
306 pub fn extract_field(
315 &self,
316 tree: &AssembledTree,
317 group_path: &str,
318 path: &str,
319 repetition: usize,
320 ) -> Option<String> {
321 let instance = Self::resolve_group_instance(tree, group_path, repetition)?;
322 Self::extract_from_instance(instance, path)
323 }
324
325 pub fn resolve_group_instance<'a>(
334 tree: &'a AssembledTree,
335 group_path: &str,
336 repetition: usize,
337 ) -> Option<&'a AssembledGroupInstance> {
338 let parts: Vec<&str> = group_path.split('.').collect();
339
340 let (first_id, first_rep) = parse_group_spec(parts[0]);
341 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
342
343 if parts.len() == 1 {
344 let rep = first_rep.unwrap_or(repetition);
346 return first_group.repetitions.get(rep);
347 }
348
349 let mut current_instance = first_group.repetitions.get(first_rep.unwrap_or(0))?;
352
353 for (i, part) in parts[1..].iter().enumerate() {
354 let (group_id, explicit_rep) = parse_group_spec(part);
355 let child_group = current_instance
356 .child_groups
357 .iter()
358 .find(|g| g.group_id == group_id)?;
359
360 if i == parts.len() - 2 {
361 let rep = explicit_rep.unwrap_or(repetition);
363 return child_group.repetitions.get(rep);
364 }
365 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
367 }
368
369 None
370 }
371
372 pub fn resolve_by_source_path<'a>(
380 tree: &'a AssembledTree,
381 source_path: &str,
382 ) -> Option<&'a AssembledGroupInstance> {
383 let parts: Vec<&str> = source_path.split('.').collect();
384 if parts.is_empty() {
385 return None;
386 }
387
388 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
389 let first_group = tree
390 .groups
391 .iter()
392 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))?;
393
394 let mut current_instance = if let Some(q) = first_qualifier {
395 find_rep_by_entry_qualifier(&first_group.repetitions, q)?
396 } else {
397 first_group.repetitions.first()?
398 };
399
400 if parts.len() == 1 {
401 return Some(current_instance);
402 }
403
404 for part in &parts[1..] {
405 let (group_id, qualifier) = parse_source_path_part(part);
406 let child_group = current_instance
407 .child_groups
408 .iter()
409 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))?;
410
411 current_instance = if let Some(q) = qualifier {
412 find_rep_by_entry_qualifier(&child_group.repetitions, q)?
413 } else {
414 child_group.repetitions.first()?
415 };
416 }
417
418 Some(current_instance)
419 }
420
421 pub fn resolve_all_by_source_path<'a>(
429 tree: &'a AssembledTree,
430 source_path: &str,
431 ) -> Vec<&'a AssembledGroupInstance> {
432 let parts: Vec<&str> = source_path.split('.').collect();
433 if parts.is_empty() {
434 return vec![];
435 }
436
437 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
439 let first_group = match tree
440 .groups
441 .iter()
442 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
443 {
444 Some(g) => g,
445 None => return vec![],
446 };
447
448 let mut current_instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
449 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
450 } else {
451 first_group.repetitions.iter().collect()
452 };
453
454 for part in &parts[1..] {
457 let (group_id, qualifier) = parse_source_path_part(part);
458 let mut next_instances = Vec::new();
459
460 for instance in ¤t_instances {
461 if let Some(child_group) = instance
462 .child_groups
463 .iter()
464 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
465 {
466 if let Some(q) = qualifier {
467 next_instances.extend(find_all_reps_by_entry_qualifier(
468 &child_group.repetitions,
469 q,
470 ));
471 } else {
472 next_instances.extend(child_group.repetitions.iter());
473 }
474 }
475 }
476
477 current_instances = next_instances;
478 }
479
480 current_instances
481 }
482
483 fn compute_child_indices(
496 tree: &AssembledTree,
497 source_path: &str,
498 indexed: &[(usize, &AssembledGroupInstance)],
499 ) -> Vec<usize> {
500 let parts: Vec<&str> = source_path.split('.').collect();
501 if parts.len() < 2 {
502 return vec![];
503 }
504 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
506 let first_group = match tree
507 .groups
508 .iter()
509 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
510 {
511 Some(g) => g,
512 None => return vec![],
513 };
514 let parent_reps: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
515 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
516 } else {
517 first_group.repetitions.iter().collect()
518 };
519 let (child_id, _child_qualifier) = parse_source_path_part(parts[parts.len() - 1]);
521 let mut result = Vec::new();
522 for (_, inst) in indexed {
523 let mut found = false;
525 for parent in &parent_reps {
526 if let Some(child_group) = parent
527 .child_groups
528 .iter()
529 .find(|g| g.group_id.eq_ignore_ascii_case(child_id))
530 {
531 if let Some(pos) = child_group
532 .repetitions
533 .iter()
534 .position(|r| std::ptr::eq(r, *inst))
535 {
536 result.push(pos);
537 found = true;
538 break;
539 }
540 }
541 }
542 if !found {
543 result.push(usize::MAX); }
545 }
546 result
547 }
548
549 pub fn resolve_all_with_parent_indices<'a>(
551 tree: &'a AssembledTree,
552 source_path: &str,
553 ) -> Vec<(usize, &'a AssembledGroupInstance)> {
554 let parts: Vec<&str> = source_path.split('.').collect();
555 if parts.is_empty() {
556 return vec![];
557 }
558
559 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
561 let first_group = match tree
562 .groups
563 .iter()
564 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
565 {
566 Some(g) => g,
567 None => return vec![],
568 };
569
570 if parts.len() == 1 {
572 let instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
573 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
574 } else {
575 first_group.repetitions.iter().collect()
576 };
577 return instances.into_iter().map(|i| (0, i)).collect();
578 }
579
580 let first_reps: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = first_qualifier {
585 let matching = find_all_reps_by_entry_qualifier(&first_group.repetitions, q);
586 let mut result = Vec::new();
587 for m in matching {
588 let idx = first_group
589 .repetitions
590 .iter()
591 .position(|r| std::ptr::eq(r, m))
592 .unwrap_or(0);
593 result.push((idx, m));
594 }
595 result
596 } else {
597 first_group.repetitions.iter().enumerate().collect()
598 };
599
600 let mut current: Vec<(usize, &AssembledGroupInstance)> = first_reps;
601 let remaining = &parts[1..];
602
603 for (level, part) in remaining.iter().enumerate() {
604 let is_leaf = level == remaining.len() - 1;
605 let (group_id, qualifier) = parse_source_path_part(part);
606 let mut next: Vec<(usize, &AssembledGroupInstance)> = Vec::new();
607
608 for (prev_parent_idx, instance) in ¤t {
609 if let Some(child_group) = instance
610 .child_groups
611 .iter()
612 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
613 {
614 let matching: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = qualifier
615 {
616 let filtered =
617 find_all_reps_by_entry_qualifier(&child_group.repetitions, q);
618 filtered
619 .into_iter()
620 .map(|m| {
621 let idx = child_group
622 .repetitions
623 .iter()
624 .position(|r| std::ptr::eq(r, m))
625 .unwrap_or(0);
626 (idx, m)
627 })
628 .collect()
629 } else {
630 child_group.repetitions.iter().enumerate().collect()
631 };
632
633 for (rep_idx, child_rep) in matching {
634 if is_leaf {
635 next.push((*prev_parent_idx, child_rep));
637 } else {
638 next.push((rep_idx, child_rep));
640 }
641 }
642 }
643 }
644
645 current = next;
646 }
647
648 current
649 }
650
651 pub fn extract_from_instance(instance: &AssembledGroupInstance, path: &str) -> Option<String> {
657 let parts: Vec<&str> = path.split('.').collect();
658 if parts.is_empty() {
659 return None;
660 }
661
662 let (segment_tag, qualifier, occurrence) = parse_tag_qualifier(parts[0]);
665
666 let segment = if let Some(q) = qualifier {
667 instance
668 .segments
669 .iter()
670 .filter(|s| {
671 s.tag.eq_ignore_ascii_case(&segment_tag)
672 && s.elements
673 .first()
674 .and_then(|e| e.first())
675 .map(|v| v.as_str())
676 == Some(q)
677 })
678 .nth(occurrence)?
679 } else {
680 instance
681 .segments
682 .iter()
683 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
684 .nth(occurrence)?
685 };
686
687 Self::resolve_field_path(segment, &parts[1..])
688 }
689
690 pub fn extract_all_from_instance(instance: &AssembledGroupInstance, path: &str) -> Vec<String> {
696 let parts: Vec<&str> = path.split('.').collect();
697 if parts.is_empty() {
698 return vec![];
699 }
700
701 let (segment_tag, qualifier, _) = parse_tag_qualifier(parts[0]);
702
703 let matching_segments: Vec<&AssembledSegment> = if let Some(q) = qualifier {
704 instance
705 .segments
706 .iter()
707 .filter(|s| {
708 s.tag.eq_ignore_ascii_case(&segment_tag)
709 && s.elements
710 .first()
711 .and_then(|e| e.first())
712 .map(|v| v.as_str())
713 == Some(q)
714 })
715 .collect()
716 } else {
717 instance
718 .segments
719 .iter()
720 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
721 .collect()
722 };
723
724 matching_segments
725 .into_iter()
726 .filter_map(|seg| Self::resolve_field_path(seg, &parts[1..]))
727 .collect()
728 }
729
730 pub fn map_forward(
739 &self,
740 tree: &AssembledTree,
741 def: &MappingDefinition,
742 repetition: usize,
743 ) -> serde_json::Value {
744 self.map_forward_inner(tree, def, repetition, true)
745 }
746
747 fn map_forward_inner(
749 &self,
750 tree: &AssembledTree,
751 def: &MappingDefinition,
752 repetition: usize,
753 enrich_codes: bool,
754 ) -> serde_json::Value {
755 let mut result = serde_json::Map::new();
756
757 if def.meta.source_group.is_empty() {
762 let mut all_root_segs = tree.segments.clone();
763 for segs in tree.inter_group_segments.values() {
764 all_root_segs.extend(segs.iter().cloned());
765 }
766 let root_instance = AssembledGroupInstance {
767 segments: all_root_segs,
768 child_groups: vec![],
769 skipped_segments: Vec::new(),
770 };
771 self.extract_fields_from_instance(&root_instance, def, &mut result, enrich_codes);
772 self.extract_companion_fields(&root_instance, def, &mut result, enrich_codes);
773 return serde_json::Value::Object(result);
774 }
775
776 let instance = if let Some(ref sp) = def.meta.source_path {
782 if has_source_path_qualifiers(sp) && !def.meta.source_group.contains(':') {
783 Self::resolve_by_source_path(tree, sp).or_else(|| {
784 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
785 })
786 } else {
787 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
788 }
789 } else {
790 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
791 };
792
793 if let Some(instance) = instance {
794 if let Some(ref tag) = def.meta.repeat_on_tag {
796 let matching: Vec<_> = instance
797 .segments
798 .iter()
799 .filter(|s| s.tag.eq_ignore_ascii_case(tag))
800 .collect();
801
802 if matching.len() > 1 {
803 let mut arr = Vec::new();
804 for seg in &matching {
805 let sub_instance = AssembledGroupInstance {
806 segments: vec![(*seg).clone()],
807 child_groups: vec![],
808 skipped_segments: Vec::new(),
809 };
810 let mut elem_result = serde_json::Map::new();
811 self.extract_fields_from_instance(
812 &sub_instance,
813 def,
814 &mut elem_result,
815 enrich_codes,
816 );
817 self.extract_companion_fields(
818 &sub_instance,
819 def,
820 &mut elem_result,
821 enrich_codes,
822 );
823 if !elem_result.is_empty() {
824 arr.push(serde_json::Value::Object(elem_result));
825 }
826 }
827 if !arr.is_empty() {
828 return serde_json::Value::Array(arr);
829 }
830 }
831 }
832
833 self.extract_fields_from_instance(instance, def, &mut result, enrich_codes);
834 self.extract_companion_fields(instance, def, &mut result, enrich_codes);
835 }
836
837 serde_json::Value::Object(result)
838 }
839
840 fn extract_companion_fields(
845 &self,
846 instance: &AssembledGroupInstance,
847 def: &MappingDefinition,
848 result: &mut serde_json::Map<String, serde_json::Value>,
849 enrich_codes: bool,
850 ) {
851 if let Some(ref companion_fields) = def.companion_fields {
852 let raw_key = def.meta.companion_type.as_deref().unwrap_or("_companion");
853 let companion_key = to_camel_case(raw_key);
854 let mut companion_result = serde_json::Map::new();
855
856 for (path, field_mapping) in companion_fields {
857 let (target, enum_map, also_target, also_enum_map) = match field_mapping {
858 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
859 FieldMapping::Structured(s) => (
860 s.target.as_str(),
861 s.enum_map.as_ref(),
862 s.also_target.as_deref(),
863 s.also_enum_map.as_ref(),
864 ),
865 FieldMapping::Nested(_) => continue,
866 };
867 if target.is_empty() {
868 continue;
869 }
870
871 if is_collect_all_path(path) {
873 let all = Self::extract_all_from_instance(instance, path);
874 if !all.is_empty() {
875 let arr: Vec<serde_json::Value> = all
876 .into_iter()
877 .map(|v| {
878 let mapped = if let Some(map) = enum_map {
879 map.get(&v).cloned().unwrap_or_else(|| v.clone())
880 } else {
881 v
882 };
883 serde_json::Value::String(mapped)
884 })
885 .collect();
886 set_nested_value_json(
887 &mut companion_result,
888 target,
889 serde_json::Value::Array(arr),
890 );
891 }
892 continue;
893 }
894
895 if let Some(val) = Self::extract_from_instance(instance, path) {
896 let mapped_val = if let Some(map) = enum_map {
897 map.get(&val).cloned().unwrap_or_else(|| val.clone())
898 } else {
899 val.clone()
900 };
901
902 if enrich_codes {
904 if let (Some(ref code_lookup), Some(ref source_path)) =
905 (&self.code_lookup, &def.meta.source_path)
906 {
907 let parts: Vec<&str> = path.split('.').collect();
908 let (seg_tag, _qualifier, _occ) = parse_tag_qualifier(parts[0]);
909 let (element_idx, component_idx) =
910 Self::parse_element_component(&parts[1..]);
911
912 if code_lookup.is_code_field(
913 source_path,
914 &seg_tag,
915 element_idx,
916 component_idx,
917 ) {
918 let enrichment = code_lookup.enrichment_for(
922 source_path,
923 &seg_tag,
924 element_idx,
925 component_idx,
926 &val,
927 );
928 let meaning = enrichment
929 .map(|e| serde_json::Value::String(e.meaning.clone()))
930 .unwrap_or(serde_json::Value::Null);
931
932 let mut obj = serde_json::Map::new();
933 obj.insert("code".into(), serde_json::json!(mapped_val));
934 obj.insert("meaning".into(), meaning);
935 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref())
936 {
937 obj.insert("enum".into(), serde_json::json!(enum_key));
938 }
939 let enriched = serde_json::Value::Object(obj);
940 set_nested_value_json(&mut companion_result, target, enriched);
941 continue;
942 }
943 }
944 }
945
946 set_nested_value(&mut companion_result, target, mapped_val);
947
948 if let (Some(at), Some(am)) = (also_target, also_enum_map) {
952 if let Some(also_mapped) = am.get(&val) {
953 set_nested_value(&mut companion_result, at, also_mapped.clone());
954 }
955 }
956 }
957 }
958
959 if !companion_result.is_empty() {
960 result.insert(
961 companion_key.to_string(),
962 serde_json::Value::Object(companion_result),
963 );
964 }
965 }
966 }
967
968 fn extract_fields_from_instance(
973 &self,
974 instance: &AssembledGroupInstance,
975 def: &MappingDefinition,
976 result: &mut serde_json::Map<String, serde_json::Value>,
977 enrich_codes: bool,
978 ) {
979 for (path, field_mapping) in &def.fields {
980 let (target, enum_map) = match field_mapping {
981 FieldMapping::Simple(t) => (t.as_str(), None),
982 FieldMapping::Structured(s) => (s.target.as_str(), s.enum_map.as_ref()),
983 FieldMapping::Nested(_) => continue,
984 };
985 if target.is_empty() {
986 continue;
987 }
988 if let Some(val) = Self::extract_from_instance(instance, path) {
989 let mapped_val = if let Some(map) = enum_map {
990 map.get(&val).cloned().unwrap_or_else(|| val.clone())
991 } else {
992 val.clone()
993 };
994
995 if enrich_codes {
997 if let (Some(ref code_lookup), Some(ref source_path)) =
998 (&self.code_lookup, &def.meta.source_path)
999 {
1000 let parts: Vec<&str> = path.split('.').collect();
1001 let (seg_tag, _qualifier, _occ) = parse_tag_qualifier(parts[0]);
1002 let (element_idx, component_idx) =
1003 Self::parse_element_component(&parts[1..]);
1004
1005 if code_lookup.is_code_field(
1006 source_path,
1007 &seg_tag,
1008 element_idx,
1009 component_idx,
1010 ) {
1011 let enrichment = code_lookup.enrichment_for(
1015 source_path,
1016 &seg_tag,
1017 element_idx,
1018 component_idx,
1019 &val,
1020 );
1021 let meaning = enrichment
1022 .map(|e| serde_json::Value::String(e.meaning.clone()))
1023 .unwrap_or(serde_json::Value::Null);
1024
1025 let mut obj = serde_json::Map::new();
1026 obj.insert("code".into(), serde_json::json!(mapped_val));
1027 obj.insert("meaning".into(), meaning);
1028 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
1029 obj.insert("enum".into(), serde_json::json!(enum_key));
1030 }
1031 let enriched = serde_json::Value::Object(obj);
1032 set_nested_value_json(result, target, enriched);
1033 continue;
1034 }
1035 }
1036 }
1037
1038 set_nested_value(result, target, mapped_val);
1039 }
1040 }
1041 }
1042
1043 pub fn map_forward_from_segments(
1049 &self,
1050 segments: &[OwnedSegment],
1051 def: &MappingDefinition,
1052 ) -> serde_json::Value {
1053 let assembled_segments: Vec<AssembledSegment> = segments
1054 .iter()
1055 .map(|s| AssembledSegment {
1056 tag: s.id.clone(),
1057 elements: s.elements.clone(),
1058 })
1059 .collect();
1060
1061 let instance = AssembledGroupInstance {
1062 segments: assembled_segments,
1063 child_groups: vec![],
1064 skipped_segments: Vec::new(),
1065 };
1066
1067 let mut result = serde_json::Map::new();
1068 self.extract_fields_from_instance(&instance, def, &mut result, true);
1069 serde_json::Value::Object(result)
1070 }
1071
1072 pub fn map_reverse(
1085 &self,
1086 bo4e_value: &serde_json::Value,
1087 def: &MappingDefinition,
1088 ) -> AssembledGroupInstance {
1089 if def.meta.repeat_on_tag.is_some() {
1091 if let Some(arr) = bo4e_value.as_array() {
1092 let mut all_segments = Vec::new();
1093 for elem in arr {
1094 let sub = self.map_reverse_single(elem, def);
1095 all_segments.extend(sub.segments);
1096 }
1097 return AssembledGroupInstance {
1098 segments: all_segments,
1099 child_groups: vec![],
1100 skipped_segments: Vec::new(),
1101 };
1102 }
1103 }
1104 self.map_reverse_single(bo4e_value, def)
1105 }
1106
1107 fn map_reverse_single(
1108 &self,
1109 bo4e_value: &serde_json::Value,
1110 def: &MappingDefinition,
1111 ) -> AssembledGroupInstance {
1112 let mut field_values: Vec<(String, String, usize, usize, String)> =
1115 Vec::with_capacity(def.fields.len());
1116
1117 let mut has_real_data = false;
1124 let mut has_data_fields = false;
1125 let mut seg_has_data_field: HashSet<String> = HashSet::new();
1128 let mut seg_has_real_data: HashSet<String> = HashSet::new();
1129 let mut injected_qualifiers: HashSet<String> = HashSet::new();
1130
1131 for (path, field_mapping) in &def.fields {
1132 let (target, default, enum_map, when_filled) = match field_mapping {
1133 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
1134 FieldMapping::Structured(s) => (
1135 s.target.as_str(),
1136 s.default.as_ref(),
1137 s.enum_map.as_ref(),
1138 s.when_filled.as_ref(),
1139 ),
1140 FieldMapping::Nested(_) => continue,
1141 };
1142
1143 let parts: Vec<&str> = path.split('.').collect();
1144 if parts.len() < 2 {
1145 continue;
1146 }
1147
1148 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1149 let seg_key = parts[0].to_uppercase();
1152 let sub_path = &parts[1..];
1153
1154 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1156 let ci = if sub_path.len() > 1 {
1157 sub_path[1].parse::<usize>().unwrap_or(0)
1158 } else {
1159 0
1160 };
1161 (ei, ci)
1162 } else {
1163 match sub_path.len() {
1164 1 => (0, 0),
1165 2 => (1, 0),
1166 _ => continue,
1167 }
1168 };
1169
1170 let val = if target.is_empty() {
1172 match (default, when_filled) {
1173 (Some(d), Some(fields)) => {
1176 let companion_key_for_check =
1177 def.meta.companion_type.as_deref().map(to_camel_case);
1178 let companion_for_check = companion_key_for_check
1179 .as_ref()
1180 .and_then(|k| bo4e_value.get(k))
1181 .unwrap_or(&serde_json::Value::Null);
1182 let any_filled = fields.iter().any(|f| {
1183 self.populate_field(bo4e_value, f).is_some()
1184 || self.populate_field(companion_for_check, f).is_some()
1185 });
1186 if any_filled {
1187 has_real_data = true;
1191 Some(d.clone())
1192 } else {
1193 None
1194 }
1195 }
1196 (Some(d), None) => Some(d.clone()),
1198 (None, _) => None,
1199 }
1200 } else {
1201 has_data_fields = true;
1202 seg_has_data_field.insert(seg_key.clone());
1203 let bo4e_val = self.populate_field(bo4e_value, target);
1204 if bo4e_val.is_some() {
1205 has_real_data = true;
1206 seg_has_real_data.insert(seg_key.clone());
1207 }
1208 let mapped_val = match (bo4e_val, enum_map) {
1210 (Some(v), Some(map)) => {
1211 map.iter()
1213 .find(|(_, bo4e_v)| *bo4e_v == &v)
1214 .map(|(edifact_k, _)| edifact_k.clone())
1215 .or(Some(v))
1216 }
1217 (v, _) => v,
1218 };
1219 mapped_val.or_else(|| default.cloned())
1220 };
1221
1222 if let Some(val) = val {
1223 field_values.push((
1224 seg_key.clone(),
1225 seg_tag.clone(),
1226 element_idx,
1227 component_idx,
1228 val,
1229 ));
1230 }
1231
1232 if let Some(q) = qualifier {
1234 if injected_qualifiers.insert(seg_key.clone()) {
1235 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1236 }
1237 }
1238 }
1239
1240 if let Some(ref companion_fields) = def.companion_fields {
1242 let raw_key = def.meta.companion_type.as_deref().unwrap_or("_companion");
1243 let companion_key = to_camel_case(raw_key);
1244 let companion_value = bo4e_value
1245 .get(&companion_key)
1246 .unwrap_or(&serde_json::Value::Null);
1247
1248 for (path, field_mapping) in companion_fields {
1249 let (target, default, enum_map, when_filled, also_target, also_enum_map) =
1250 match field_mapping {
1251 FieldMapping::Simple(t) => (t.as_str(), None, None, None, None, None),
1252 FieldMapping::Structured(s) => (
1253 s.target.as_str(),
1254 s.default.as_ref(),
1255 s.enum_map.as_ref(),
1256 s.when_filled.as_ref(),
1257 s.also_target.as_deref(),
1258 s.also_enum_map.as_ref(),
1259 ),
1260 FieldMapping::Nested(_) => continue,
1261 };
1262
1263 let parts: Vec<&str> = path.split('.').collect();
1264 if parts.len() < 2 {
1265 continue;
1266 }
1267
1268 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1269 let seg_key = parts[0].to_uppercase();
1270 let sub_path = &parts[1..];
1271
1272 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1273 let ci = if sub_path.len() > 1 {
1274 sub_path[1].parse::<usize>().unwrap_or(0)
1275 } else {
1276 0
1277 };
1278 (ei, ci)
1279 } else {
1280 match sub_path.len() {
1281 1 => (0, 0),
1282 2 => (1, 0),
1283 _ => continue,
1284 }
1285 };
1286
1287 if is_collect_all_path(path) && !target.is_empty() {
1289 if let Some(arr) = self
1290 .populate_field_json(companion_value, target)
1291 .and_then(|v| v.as_array().cloned())
1292 {
1293 has_data_fields = true;
1294 if !arr.is_empty() {
1295 has_real_data = true;
1296 }
1297 for (idx, item) in arr.iter().enumerate() {
1298 if let Some(val_str) = item.as_str() {
1299 let mapped = if let Some(map) = enum_map {
1300 map.iter()
1301 .find(|(_, bo4e_v)| *bo4e_v == val_str)
1302 .map(|(edifact_k, _)| edifact_k.clone())
1303 .unwrap_or_else(|| val_str.to_string())
1304 } else {
1305 val_str.to_string()
1306 };
1307 let occ_key = if let Some(q) = qualifier {
1308 format!("{}[{},{}]", seg_tag, q, idx)
1309 } else {
1310 format!("{}[*,{}]", seg_tag, idx)
1311 };
1312 field_values.push((
1313 occ_key.clone(),
1314 seg_tag.clone(),
1315 element_idx,
1316 component_idx,
1317 mapped,
1318 ));
1319 if let Some(q) = qualifier {
1321 if injected_qualifiers.insert(occ_key.clone()) {
1322 field_values.push((
1323 occ_key,
1324 seg_tag.clone(),
1325 0,
1326 0,
1327 q.to_string(),
1328 ));
1329 }
1330 }
1331 }
1332 }
1333 }
1334 continue;
1335 }
1336
1337 let val = if target.is_empty() {
1338 match (default, when_filled) {
1339 (Some(d), Some(fields)) => {
1340 let any_filled = fields.iter().any(|f| {
1341 self.populate_field(bo4e_value, f).is_some()
1342 || self.populate_field(companion_value, f).is_some()
1343 });
1344 if any_filled {
1345 has_real_data = true;
1346 Some(d.clone())
1347 } else {
1348 None
1349 }
1350 }
1351 (Some(d), None) => Some(d.clone()),
1352 (None, _) => None,
1353 }
1354 } else {
1355 has_data_fields = true;
1356 seg_has_data_field.insert(seg_key.clone());
1357 let bo4e_val = self.populate_field(companion_value, target);
1358 if bo4e_val.is_some() {
1359 has_real_data = true;
1360 seg_has_real_data.insert(seg_key.clone());
1361 }
1362 let mapped_val = match (bo4e_val, enum_map) {
1363 (Some(v), Some(map)) => {
1364 if let (Some(at), Some(am)) = (also_target, also_enum_map) {
1365 let also_val = self.populate_field(companion_value, at);
1366 if let Some(av) = also_val.as_deref() {
1367 map.iter()
1369 .find(|(edifact_k, bo4e_v)| {
1370 *bo4e_v == &v
1371 && am.get(*edifact_k).is_some_and(|am_v| am_v == av)
1372 })
1373 .map(|(edifact_k, _)| edifact_k.clone())
1374 .or(Some(v))
1375 } else {
1376 map.iter()
1379 .find(|(edifact_k, bo4e_v)| {
1380 *bo4e_v == &v && !am.contains_key(*edifact_k)
1381 })
1382 .or_else(|| {
1383 map.iter().find(|(_, bo4e_v)| *bo4e_v == &v)
1385 })
1386 .map(|(edifact_k, _)| edifact_k.clone())
1387 .or(Some(v))
1388 }
1389 } else {
1390 map.iter()
1391 .find(|(_, bo4e_v)| *bo4e_v == &v)
1392 .map(|(edifact_k, _)| edifact_k.clone())
1393 .or(Some(v))
1394 }
1395 }
1396 (v, _) => v,
1397 };
1398 mapped_val.or_else(|| default.cloned())
1399 };
1400
1401 if let Some(val) = val {
1402 field_values.push((
1403 seg_key.clone(),
1404 seg_tag.clone(),
1405 element_idx,
1406 component_idx,
1407 val,
1408 ));
1409 }
1410
1411 if let Some(q) = qualifier {
1412 if injected_qualifiers.insert(seg_key.clone()) {
1413 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1414 }
1415 }
1416 }
1417 }
1418
1419 field_values.retain(|(seg_key, _, _, _, _)| {
1427 if !seg_key.contains('[') {
1428 return true; }
1430 !seg_has_data_field.contains(seg_key) || seg_has_real_data.contains(seg_key)
1431 });
1432
1433 if has_data_fields && !has_real_data {
1438 return AssembledGroupInstance {
1439 segments: vec![],
1440 child_groups: vec![],
1441 skipped_segments: Vec::new(),
1442 };
1443 }
1444
1445 let mut segments: Vec<AssembledSegment> = Vec::with_capacity(field_values.len());
1448 let mut seen_keys: HashMap<String, usize> = HashMap::new();
1449
1450 for (seg_key, seg_tag, element_idx, component_idx, val) in &field_values {
1451 let seg = if let Some(&pos) = seen_keys.get(seg_key) {
1452 &mut segments[pos]
1453 } else {
1454 let pos = segments.len();
1455 seen_keys.insert(seg_key.clone(), pos);
1456 segments.push(AssembledSegment {
1457 tag: seg_tag.clone(),
1458 elements: vec![],
1459 });
1460 &mut segments[pos]
1461 };
1462
1463 while seg.elements.len() <= *element_idx {
1464 seg.elements.push(vec![]);
1465 }
1466 while seg.elements[*element_idx].len() <= *component_idx {
1467 seg.elements[*element_idx].push(String::new());
1468 }
1469 seg.elements[*element_idx][*component_idx] = val.clone();
1470 }
1471
1472 for seg in &mut segments {
1475 let last_populated = seg.elements.iter().rposition(|e| !e.is_empty());
1476 if let Some(last_idx) = last_populated {
1477 for i in 0..last_idx {
1478 if seg.elements[i].is_empty() {
1479 seg.elements[i] = vec![String::new()];
1480 }
1481 }
1482 }
1483 }
1484
1485 if let Some(ref ss) = self.segment_structure {
1487 for seg in &mut segments {
1488 if let Some(expected) = ss.element_count(&seg.tag) {
1489 while seg.elements.len() < expected {
1490 seg.elements.push(vec![String::new()]);
1491 }
1492 }
1493 }
1494 }
1495
1496 AssembledGroupInstance {
1497 segments,
1498 child_groups: vec![],
1499 skipped_segments: Vec::new(),
1500 }
1501 }
1502
1503 fn resolve_field_path(segment: &AssembledSegment, path: &[&str]) -> Option<String> {
1516 if path.is_empty() {
1517 return None;
1518 }
1519
1520 if let Ok(element_idx) = path[0].parse::<usize>() {
1522 let component_idx = if path.len() > 1 {
1523 path[1].parse::<usize>().unwrap_or(0)
1524 } else {
1525 0
1526 };
1527 return segment
1528 .elements
1529 .get(element_idx)?
1530 .get(component_idx)
1531 .filter(|v| !v.is_empty())
1532 .cloned();
1533 }
1534
1535 match path.len() {
1537 1 => segment
1538 .elements
1539 .first()?
1540 .first()
1541 .filter(|v| !v.is_empty())
1542 .cloned(),
1543 2 => segment
1544 .elements
1545 .get(1)?
1546 .first()
1547 .filter(|v| !v.is_empty())
1548 .cloned(),
1549 _ => None,
1550 }
1551 }
1552
1553 fn parse_element_component(parts: &[&str]) -> (usize, usize) {
1556 if parts.is_empty() {
1557 return (0, 0);
1558 }
1559 let element_idx = parts[0].parse::<usize>().unwrap_or(0);
1560 let component_idx = if parts.len() > 1 {
1561 parts[1].parse::<usize>().unwrap_or(0)
1562 } else {
1563 0
1564 };
1565 (element_idx, component_idx)
1566 }
1567
1568 pub fn populate_field(
1571 &self,
1572 bo4e_value: &serde_json::Value,
1573 target_field: &str,
1574 ) -> Option<String> {
1575 let mut current = bo4e_value;
1576 for part in target_field.split('.') {
1577 current = current.get(part)?;
1578 }
1579 if let Some(code) = current.get("code").and_then(|v| v.as_str()) {
1581 return Some(code.to_string());
1582 }
1583 current.as_str().map(|s| s.to_string())
1584 }
1585
1586 fn populate_field_json<'a>(
1589 &self,
1590 bo4e_value: &'a serde_json::Value,
1591 target_field: &str,
1592 ) -> Option<&'a serde_json::Value> {
1593 let mut current = bo4e_value;
1594 for part in target_field.split('.') {
1595 current = current.get(part)?;
1596 }
1597 Some(current)
1598 }
1599
1600 pub fn build_segment_from_bo4e(
1602 &self,
1603 bo4e_value: &serde_json::Value,
1604 segment_tag: &str,
1605 target_field: &str,
1606 ) -> AssembledSegment {
1607 let value = self.populate_field(bo4e_value, target_field);
1608 let elements = if let Some(val) = value {
1609 vec![vec![val]]
1610 } else {
1611 vec![]
1612 };
1613 AssembledSegment {
1614 tag: segment_tag.to_uppercase(),
1615 elements,
1616 }
1617 }
1618
1619 pub fn resolve_repetition(
1628 tree: &AssembledTree,
1629 group_path: &str,
1630 discriminator: &str,
1631 ) -> Option<usize> {
1632 let (spec, expected) = discriminator.split_once('=')?;
1633 let parts: Vec<&str> = spec.split('.').collect();
1634 if parts.len() != 3 {
1635 return None;
1636 }
1637 let tag = parts[0];
1638 let element_idx: usize = parts[1].parse().ok()?;
1639 let component_idx: usize = parts[2].parse().ok()?;
1640
1641 let path_parts: Vec<&str> = group_path.split('.').collect();
1643
1644 let leaf_group = if path_parts.len() == 1 {
1645 let (group_id, _) = parse_group_spec(path_parts[0]);
1646 tree.groups.iter().find(|g| g.group_id == group_id)?
1647 } else {
1648 let parent_parts = &path_parts[..path_parts.len() - 1];
1650 let mut current_instance = {
1651 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1652 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
1653 first_group.repetitions.get(first_rep.unwrap_or(0))?
1654 };
1655 for part in &parent_parts[1..] {
1656 let (group_id, explicit_rep) = parse_group_spec(part);
1657 let child_group = current_instance
1658 .child_groups
1659 .iter()
1660 .find(|g| g.group_id == group_id)?;
1661 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
1662 }
1663 let (leaf_id, _) = parse_group_spec(path_parts.last()?);
1664 current_instance
1665 .child_groups
1666 .iter()
1667 .find(|g| g.group_id == leaf_id)?
1668 };
1669
1670 let expected_values: Vec<&str> = expected.split('|').collect();
1672 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1673 let matches = instance.segments.iter().any(|s| {
1674 s.tag.eq_ignore_ascii_case(tag)
1675 && s.elements
1676 .get(element_idx)
1677 .and_then(|e| e.get(component_idx))
1678 .map(|v| expected_values.iter().any(|ev| v == ev))
1679 .unwrap_or(false)
1680 });
1681 if matches {
1682 return Some(rep_idx);
1683 }
1684 }
1685
1686 None
1687 }
1688
1689 pub fn resolve_all_repetitions(
1694 tree: &AssembledTree,
1695 group_path: &str,
1696 discriminator: &str,
1697 ) -> Vec<usize> {
1698 let Some((spec, expected)) = discriminator.split_once('=') else {
1699 return Vec::new();
1700 };
1701 let parts: Vec<&str> = spec.split('.').collect();
1702 if parts.len() != 3 {
1703 return Vec::new();
1704 }
1705 let tag = parts[0];
1706 let element_idx: usize = match parts[1].parse() {
1707 Ok(v) => v,
1708 Err(_) => return Vec::new(),
1709 };
1710 let component_idx: usize = match parts[2].parse() {
1711 Ok(v) => v,
1712 Err(_) => return Vec::new(),
1713 };
1714
1715 let path_parts: Vec<&str> = group_path.split('.').collect();
1717
1718 let leaf_group = if path_parts.len() == 1 {
1719 let (group_id, _) = parse_group_spec(path_parts[0]);
1720 match tree.groups.iter().find(|g| g.group_id == group_id) {
1721 Some(g) => g,
1722 None => return Vec::new(),
1723 }
1724 } else {
1725 let parent_parts = &path_parts[..path_parts.len() - 1];
1726 let mut current_instance = {
1727 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1728 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
1729 Some(g) => g,
1730 None => return Vec::new(),
1731 };
1732 match first_group.repetitions.get(first_rep.unwrap_or(0)) {
1733 Some(i) => i,
1734 None => return Vec::new(),
1735 }
1736 };
1737 for part in &parent_parts[1..] {
1738 let (group_id, explicit_rep) = parse_group_spec(part);
1739 let child_group = match current_instance
1740 .child_groups
1741 .iter()
1742 .find(|g| g.group_id == group_id)
1743 {
1744 Some(g) => g,
1745 None => return Vec::new(),
1746 };
1747 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
1748 Some(i) => i,
1749 None => return Vec::new(),
1750 };
1751 }
1752 let (leaf_id, _) = match path_parts.last() {
1753 Some(p) => parse_group_spec(p),
1754 None => return Vec::new(),
1755 };
1756 match current_instance
1757 .child_groups
1758 .iter()
1759 .find(|g| g.group_id == leaf_id)
1760 {
1761 Some(g) => g,
1762 None => return Vec::new(),
1763 }
1764 };
1765
1766 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
1768
1769 let expected_values: Vec<&str> = expected_raw.split('|').collect();
1771 let mut result = Vec::new();
1772 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1773 let matches = instance.segments.iter().any(|s| {
1774 s.tag.eq_ignore_ascii_case(tag)
1775 && s.elements
1776 .get(element_idx)
1777 .and_then(|e| e.get(component_idx))
1778 .map(|v| expected_values.iter().any(|ev| v == ev))
1779 .unwrap_or(false)
1780 });
1781 if matches {
1782 result.push(rep_idx);
1783 }
1784 }
1785
1786 if let Some(occ) = occurrence {
1788 result.into_iter().nth(occ).into_iter().collect()
1789 } else {
1790 result
1791 }
1792 }
1793
1794 pub fn map_all_forward(&self, tree: &AssembledTree) -> serde_json::Value {
1816 self.map_all_forward_inner(tree, true).0
1817 }
1818
1819 pub fn map_all_forward_enriched(
1823 &self,
1824 tree: &AssembledTree,
1825 enrich_codes: bool,
1826 ) -> serde_json::Value {
1827 self.map_all_forward_inner(tree, enrich_codes).0
1828 }
1829
1830 fn map_all_forward_inner(
1837 &self,
1838 tree: &AssembledTree,
1839 enrich_codes: bool,
1840 ) -> (
1841 serde_json::Value,
1842 std::collections::HashMap<String, Vec<usize>>,
1843 ) {
1844 let mut result = serde_json::Map::new();
1845 let mut nesting_info: std::collections::HashMap<String, Vec<usize>> =
1846 std::collections::HashMap::new();
1847
1848 for def in &self.definitions {
1849 let entity = &def.meta.entity;
1850
1851 let bo4e = if let Some(ref disc) = def.meta.discriminator {
1852 let use_source_path = def
1857 .meta
1858 .source_path
1859 .as_ref()
1860 .is_some_and(|sp| has_source_path_qualifiers(sp));
1861 if use_source_path {
1862 let sp = def.meta.source_path.as_deref().unwrap();
1864 let all_instances = Self::resolve_all_by_source_path(tree, sp);
1865 let instances: Vec<_> = if let Some(matcher) = DiscriminatorMatcher::parse(disc)
1867 {
1868 matcher.filter_instances(all_instances)
1869 } else {
1870 all_instances
1871 };
1872 let extract = |instance: &AssembledGroupInstance| {
1873 let mut r = serde_json::Map::new();
1874 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
1875 self.extract_companion_fields(instance, def, &mut r, enrich_codes);
1876 serde_json::Value::Object(r)
1877 };
1878 match instances.len() {
1879 0 => None,
1880 1 => Some(extract(instances[0])),
1881 _ => Some(serde_json::Value::Array(
1882 instances.iter().map(|i| extract(i)).collect(),
1883 )),
1884 }
1885 } else {
1886 let reps = Self::resolve_all_repetitions(tree, &def.meta.source_group, disc);
1887 match reps.len() {
1888 0 => None,
1889 1 => Some(self.map_forward_inner(tree, def, reps[0], enrich_codes)),
1890 _ => Some(serde_json::Value::Array(
1891 reps.iter()
1892 .map(|&rep| self.map_forward_inner(tree, def, rep, enrich_codes))
1893 .collect(),
1894 )),
1895 }
1896 }
1897 } else if def.meta.source_group.is_empty() {
1898 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
1900 } else if def.meta.source_path.as_ref().is_some_and(|sp| {
1901 has_source_path_qualifiers(sp) || def.meta.source_group.contains('.')
1902 }) {
1903 let sp = def.meta.source_path.as_deref().unwrap();
1908 let mut indexed = Self::resolve_all_with_parent_indices(tree, sp);
1909
1910 if let Some(last_part) = sp.rsplit('.').next() {
1915 if !last_part.contains('_') {
1916 let base_prefix = if let Some(parent) = sp.rsplit_once('.') {
1920 format!("{}.", parent.0)
1921 } else {
1922 String::new()
1923 };
1924 let sibling_qualifiers: Vec<String> = self
1925 .definitions
1926 .iter()
1927 .filter_map(|d| d.meta.source_path.as_deref())
1928 .filter(|other_sp| {
1929 *other_sp != sp
1930 && other_sp.starts_with(&base_prefix)
1931 && other_sp.split('.').count() == sp.split('.').count()
1932 })
1933 .filter_map(|other_sp| {
1934 let other_last = other_sp.rsplit('.').next()?;
1935 let (base, q) = other_last.split_once('_')?;
1938 if base == last_part {
1939 Some(q.to_string())
1940 } else {
1941 None
1942 }
1943 })
1944 .collect();
1945
1946 if !sibling_qualifiers.is_empty() {
1947 indexed.retain(|(_, inst)| {
1948 let entry_qual = inst
1949 .segments
1950 .first()
1951 .and_then(|seg| seg.elements.first())
1952 .and_then(|el| el.first())
1953 .map(|v| v.to_lowercase());
1954 !entry_qual.is_some_and(|q| {
1957 sibling_qualifiers.iter().any(|sq| {
1958 sq.split('_').any(|part| part.eq_ignore_ascii_case(&q))
1959 })
1960 })
1961 });
1962 }
1963 }
1964 }
1965 let extract = |instance: &AssembledGroupInstance| {
1966 let mut r = serde_json::Map::new();
1967 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
1968 self.extract_companion_fields(instance, def, &mut r, enrich_codes);
1969 serde_json::Value::Object(r)
1970 };
1971 if def.meta.source_group.contains('.') && !indexed.is_empty() {
1976 if let Some(sp) = &def.meta.source_path {
1977 let parent_indices: Vec<usize> =
1978 indexed.iter().map(|(idx, _)| *idx).collect();
1979 nesting_info.entry(sp.clone()).or_insert(parent_indices);
1980
1981 let child_key = format!("{sp}#child");
1984 if let std::collections::hash_map::Entry::Vacant(e) =
1985 nesting_info.entry(child_key)
1986 {
1987 let child_indices: Vec<usize> =
1988 Self::compute_child_indices(tree, sp, &indexed);
1989 if !child_indices.is_empty() {
1990 e.insert(child_indices);
1991 }
1992 }
1993 }
1994 }
1995 match indexed.len() {
1996 0 => None,
1997 1 => Some(extract(indexed[0].1)),
1998 _ => Some(serde_json::Value::Array(
1999 indexed.iter().map(|(_, i)| extract(i)).collect(),
2000 )),
2001 }
2002 } else {
2003 let num_reps = Self::count_repetitions(tree, &def.meta.source_group);
2004 if num_reps <= 1 {
2005 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2006 } else {
2007 let mut items = Vec::with_capacity(num_reps);
2009 for rep in 0..num_reps {
2010 items.push(self.map_forward_inner(tree, def, rep, enrich_codes));
2011 }
2012 Some(serde_json::Value::Array(items))
2013 }
2014 };
2015
2016 if let Some(bo4e) = bo4e {
2017 let bo4e = inject_bo4e_metadata(bo4e, &def.meta.bo4e_type);
2018 let key = to_camel_case(entity);
2019 deep_merge_insert(&mut result, &key, bo4e);
2020 }
2021 }
2022
2023 (serde_json::Value::Object(result), nesting_info)
2024 }
2025
2026 pub fn map_all_reverse(
2035 &self,
2036 entities: &serde_json::Value,
2037 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2038 ) -> AssembledTree {
2039 let mut root_segments: Vec<AssembledSegment> = Vec::new();
2040 let mut groups: Vec<AssembledGroup> = Vec::new();
2041
2042 for def in &self.definitions {
2043 let entity_key = to_camel_case(&def.meta.entity);
2044
2045 let entity_value = entities.get(&entity_key);
2047
2048 if entity_value.is_none() {
2049 continue;
2050 }
2051 let entity_value = entity_value.unwrap();
2052
2053 let leaf_group = def
2055 .meta
2056 .source_group
2057 .rsplit('.')
2058 .next()
2059 .unwrap_or(&def.meta.source_group);
2060
2061 if def.meta.source_group.is_empty() {
2062 let instance = self.map_reverse(entity_value, def);
2064 root_segments.extend(instance.segments);
2065 } else if entity_value.is_array() {
2066 let arr = entity_value.as_array().unwrap();
2068 let reps: Vec<_> = arr.iter().map(|item| self.map_reverse(item, def)).collect();
2069
2070 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2072 existing.repetitions.extend(reps);
2073 } else {
2074 groups.push(AssembledGroup {
2075 group_id: leaf_group.to_string(),
2076 repetitions: reps,
2077 });
2078 }
2079 } else {
2080 let instance = self.map_reverse(entity_value, def);
2082
2083 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2084 existing.repetitions.push(instance);
2085 } else {
2086 groups.push(AssembledGroup {
2087 group_id: leaf_group.to_string(),
2088 repetitions: vec![instance],
2089 });
2090 }
2091 }
2092 }
2093
2094 let nested_specs: Vec<(String, String)> = self
2100 .definitions
2101 .iter()
2102 .filter_map(|def| {
2103 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
2104 if parts.len() > 1 {
2105 Some((parts[0].to_string(), parts[parts.len() - 1].to_string()))
2106 } else {
2107 None
2108 }
2109 })
2110 .collect();
2111 for (parent_id, child_id) in &nested_specs {
2112 let has_parent = groups.iter().any(|g| g.group_id == *parent_id);
2114 let has_child = groups.iter().any(|g| g.group_id == *child_id);
2115 if has_parent && has_child {
2116 let child_idx = groups.iter().position(|g| g.group_id == *child_id).unwrap();
2117 let child_group = groups.remove(child_idx);
2118 let parent = groups
2119 .iter_mut()
2120 .find(|g| g.group_id == *parent_id)
2121 .unwrap();
2122 let child_source_path = self
2126 .definitions
2127 .iter()
2128 .find(|d| {
2129 let parts: Vec<&str> = d.meta.source_group.split('.').collect();
2130 parts.len() > 1 && parts[parts.len() - 1] == *child_id
2131 })
2132 .and_then(|d| d.meta.source_path.as_deref());
2133 let distribution =
2134 child_source_path.and_then(|key| nesting_info.and_then(|ni| ni.get(key)));
2135 for (i, child_rep) in child_group.repetitions.into_iter().enumerate() {
2136 let target_idx = distribution
2137 .and_then(|dist| dist.get(i))
2138 .copied()
2139 .unwrap_or(0);
2140
2141 if let Some(target_rep) = parent.repetitions.get_mut(target_idx) {
2142 if let Some(existing) = target_rep
2143 .child_groups
2144 .iter_mut()
2145 .find(|g| g.group_id == *child_id)
2146 {
2147 existing.repetitions.push(child_rep);
2148 } else {
2149 target_rep.child_groups.push(AssembledGroup {
2150 group_id: child_id.clone(),
2151 repetitions: vec![child_rep],
2152 });
2153 }
2154 }
2155 }
2156 }
2157 }
2158
2159 let post_group_start = root_segments.len();
2160 AssembledTree {
2161 segments: root_segments,
2162 groups,
2163 post_group_start,
2164 inter_group_segments: std::collections::BTreeMap::new(),
2165 }
2166 }
2167
2168 fn count_repetitions(tree: &AssembledTree, group_path: &str) -> usize {
2170 let parts: Vec<&str> = group_path.split('.').collect();
2171
2172 let (first_id, first_rep) = parse_group_spec(parts[0]);
2173 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2174 Some(g) => g,
2175 None => return 0,
2176 };
2177
2178 if parts.len() == 1 {
2179 return first_group.repetitions.len();
2180 }
2181
2182 let mut current_instance = match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2184 Some(i) => i,
2185 None => return 0,
2186 };
2187
2188 for (i, part) in parts[1..].iter().enumerate() {
2189 let (group_id, explicit_rep) = parse_group_spec(part);
2190 let child_group = match current_instance
2191 .child_groups
2192 .iter()
2193 .find(|g| g.group_id == group_id)
2194 {
2195 Some(g) => g,
2196 None => return 0,
2197 };
2198
2199 if i == parts.len() - 2 {
2200 return child_group.repetitions.len();
2202 }
2203 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2204 Some(i) => i,
2205 None => return 0,
2206 };
2207 }
2208
2209 0
2210 }
2211
2212 pub fn map_interchange(
2221 msg_engine: &MappingEngine,
2222 tx_engine: &MappingEngine,
2223 tree: &AssembledTree,
2224 transaction_group: &str,
2225 enrich_codes: bool,
2226 ) -> crate::model::MappedMessage {
2227 let (stammdaten, nesting_info) = msg_engine.map_all_forward_inner(tree, enrich_codes);
2229
2230 let transaktionen = tree
2232 .groups
2233 .iter()
2234 .find(|g| g.group_id == transaction_group)
2235 .map(|sg| {
2236 sg.repetitions
2237 .iter()
2238 .map(|instance| {
2239 let wrapped_tree = AssembledTree {
2242 segments: vec![],
2243 groups: vec![AssembledGroup {
2244 group_id: transaction_group.to_string(),
2245 repetitions: vec![instance.clone()],
2246 }],
2247 post_group_start: 0,
2248 inter_group_segments: std::collections::BTreeMap::new(),
2249 };
2250
2251 let (tx_result, tx_nesting) =
2252 tx_engine.map_all_forward_inner(&wrapped_tree, enrich_codes);
2253
2254 crate::model::MappedTransaktion {
2255 stammdaten: tx_result,
2256 nesting_info: tx_nesting,
2257 }
2258 })
2259 .collect()
2260 })
2261 .unwrap_or_default();
2262
2263 crate::model::MappedMessage {
2264 stammdaten,
2265 transaktionen,
2266 nesting_info,
2267 }
2268 }
2269
2270 pub fn map_interchange_reverse(
2280 msg_engine: &MappingEngine,
2281 tx_engine: &MappingEngine,
2282 mapped: &crate::model::MappedMessage,
2283 transaction_group: &str,
2284 filtered_mig: Option<&MigSchema>,
2285 ) -> AssembledTree {
2286 let msg_tree = msg_engine.map_all_reverse(
2288 &mapped.stammdaten,
2289 if mapped.nesting_info.is_empty() {
2290 None
2291 } else {
2292 Some(&mapped.nesting_info)
2293 },
2294 );
2295
2296 let mut sg4_reps: Vec<AssembledGroupInstance> = Vec::new();
2298
2299 struct DefWithMeta<'a> {
2303 def: &'a MappingDefinition,
2304 relative: String,
2305 depth: usize,
2306 }
2307
2308 let mut sorted_defs: Vec<DefWithMeta> = tx_engine
2309 .definitions
2310 .iter()
2311 .map(|def| {
2312 let relative = strip_tx_group_prefix(&def.meta.source_group, transaction_group);
2313 let depth = if relative.is_empty() {
2314 0
2315 } else {
2316 relative.chars().filter(|c| *c == '.').count() + 1
2317 };
2318 DefWithMeta {
2319 def,
2320 relative,
2321 depth,
2322 }
2323 })
2324 .collect();
2325
2326 let mut parent_rep_map: std::collections::HashMap<String, usize> =
2330 std::collections::HashMap::new();
2331 for dm in &sorted_defs {
2332 if dm.depth >= 2 {
2333 let parts: Vec<&str> = dm.relative.split('.').collect();
2334 let (_, parent_rep) = parse_group_spec(parts[0]);
2335 if let Some(rep_idx) = parent_rep {
2336 if let Some(sp) = &dm.def.meta.source_path {
2337 if let Some((parent_path, _)) = sp.rsplit_once('.') {
2338 parent_rep_map
2339 .entry(parent_path.to_string())
2340 .or_insert(rep_idx);
2341 }
2342 }
2343 }
2344 }
2345 }
2346
2347 for dm in &mut sorted_defs {
2350 if dm.depth == 1 && !dm.relative.contains(':') {
2351 if let Some(sp) = &dm.def.meta.source_path {
2352 if let Some(rep_idx) = parent_rep_map.get(sp.as_str()) {
2353 dm.relative = format!("{}:{}", dm.relative, rep_idx);
2354 }
2355 }
2356 }
2357 }
2358
2359 if let Some(mig) = filtered_mig {
2366 let mig_order = build_reverse_mig_group_order(mig, transaction_group);
2367 sorted_defs.sort_by(|a, b| {
2368 a.depth.cmp(&b.depth).then_with(|| {
2369 let a_id = a.relative.split(':').next().unwrap_or(&a.relative);
2370 let b_id = b.relative.split(':').next().unwrap_or(&b.relative);
2371 let a_pos = variant_mig_position(a.def, a_id, &mig_order);
2373 let b_pos = variant_mig_position(b.def, b_id, &mig_order);
2374 a_pos.cmp(&b_pos).then(a.relative.cmp(&b.relative))
2375 })
2376 });
2377 } else {
2378 sorted_defs.sort_by(|a, b| a.depth.cmp(&b.depth).then(a.relative.cmp(&b.relative)));
2379 }
2380
2381 for tx in &mapped.transaktionen {
2382 let mut root_segs: Vec<AssembledSegment> = Vec::new();
2383 let mut child_groups: Vec<AssembledGroup> = Vec::new();
2384
2385 let mut source_path_to_rep: std::collections::HashMap<String, Vec<usize>> =
2390 std::collections::HashMap::new();
2391
2392 for dm in &sorted_defs {
2393 let entity_key = to_camel_case(&dm.def.meta.entity);
2395 let bo4e_value = match tx.stammdaten.get(&entity_key) {
2396 Some(v) => v,
2397 None => continue,
2398 };
2399
2400 let items: Vec<&serde_json::Value> = if bo4e_value.is_array() {
2404 bo4e_value.as_array().unwrap().iter().collect()
2405 } else {
2406 vec![bo4e_value]
2407 };
2408
2409 for (item_idx, item) in items.iter().enumerate() {
2410 let instance = tx_engine.map_reverse(item, dm.def);
2411
2412 if instance.segments.is_empty() && instance.child_groups.is_empty() {
2414 continue;
2415 }
2416
2417 if dm.relative.is_empty() {
2418 root_segs.extend(instance.segments);
2419 } else {
2420 let effective_relative = if dm.depth >= 2 {
2424 let rel = if items.len() > 1 {
2427 strip_all_rep_indices(&dm.relative)
2428 } else {
2429 dm.relative.clone()
2430 };
2431 let nesting_idx = if items.len() > 1 {
2437 dm.def
2438 .meta
2439 .source_path
2440 .as_ref()
2441 .and_then(|sp| tx.nesting_info.get(sp))
2442 .and_then(|dist| dist.get(item_idx))
2443 .copied()
2444 } else {
2445 None
2446 };
2447 if let Some(parent_rep) = nesting_idx {
2448 let parts: Vec<&str> = rel.split('.').collect();
2450 let parent_id = parts[0].split(':').next().unwrap_or(parts[0]);
2451 let rest = parts[1..].join(".");
2452 format!("{}:{}.{}", parent_id, parent_rep, rest)
2453 } else {
2454 resolve_child_relative(
2455 &rel,
2456 dm.def.meta.source_path.as_deref(),
2457 &source_path_to_rep,
2458 item_idx,
2459 )
2460 }
2461 } else if dm.depth == 1 {
2462 let child_key = dm
2465 .def
2466 .meta
2467 .source_path
2468 .as_ref()
2469 .map(|sp| format!("{sp}#child"));
2470 if let Some(child_indices) =
2471 child_key.as_ref().and_then(|ck| tx.nesting_info.get(ck))
2472 {
2473 if let Some(&target) = child_indices.get(item_idx) {
2474 if target != usize::MAX {
2475 let base =
2476 dm.relative.split(':').next().unwrap_or(&dm.relative);
2477 format!("{}:{}", base, target)
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)
2488 } else {
2489 dm.relative.clone()
2490 }
2491 } else if items.len() > 1 && item_idx > 0 {
2492 strip_rep_index(&dm.relative)
2495 } else {
2496 dm.relative.clone()
2497 };
2498
2499 let rep_used =
2500 place_in_groups(&mut child_groups, &effective_relative, instance);
2501
2502 if dm.depth == 1 {
2504 if let Some(sp) = &dm.def.meta.source_path {
2505 source_path_to_rep
2506 .entry(sp.clone())
2507 .or_default()
2508 .push(rep_used);
2509 }
2510 }
2511 }
2512 }
2513 }
2514
2515 if let Some(mig) = filtered_mig {
2520 sort_variant_reps_by_mig(&mut child_groups, mig, transaction_group);
2521 }
2522
2523 sg4_reps.push(AssembledGroupInstance {
2524 segments: root_segs,
2525 child_groups,
2526 skipped_segments: Vec::new(),
2527 });
2528 }
2529
2530 let mut root_segments = Vec::new();
2537 let mut uns_segments = Vec::new();
2538 let mut uns_is_summary = false;
2539 let mut found_uns = false;
2540 for seg in msg_tree.segments {
2541 if seg.tag == "UNS" {
2542 uns_is_summary = seg
2544 .elements
2545 .first()
2546 .and_then(|el| el.first())
2547 .map(|v| v == "S")
2548 .unwrap_or(false);
2549 uns_segments.push(seg);
2550 found_uns = true;
2551 } else if found_uns {
2552 uns_segments.push(seg);
2554 } else {
2555 root_segments.push(seg);
2556 }
2557 }
2558
2559 let pre_group_count = root_segments.len();
2560 let mut all_groups = msg_tree.groups;
2561 let mut inter_group = msg_tree.inter_group_segments;
2562
2563 let sg_num = |id: &str| -> usize {
2565 id.strip_prefix("SG")
2566 .and_then(|n| n.parse::<usize>().ok())
2567 .unwrap_or(0)
2568 };
2569
2570 if !sg4_reps.is_empty() {
2571 if uns_is_summary {
2572 all_groups.push(AssembledGroup {
2574 group_id: transaction_group.to_string(),
2575 repetitions: sg4_reps,
2576 });
2577 if !uns_segments.is_empty() {
2578 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2583 let tx_num = sg_num(transaction_group);
2584 let uns_pos = all_groups
2585 .iter()
2586 .rposition(|g| sg_num(&g.group_id) <= tx_num)
2587 .map(|i| i + 1)
2588 .unwrap_or(all_groups.len());
2589 inter_group.insert(uns_pos, uns_segments);
2590 }
2591 } else {
2592 if !uns_segments.is_empty() {
2594 inter_group.insert(all_groups.len(), uns_segments);
2595 }
2596 all_groups.push(AssembledGroup {
2597 group_id: transaction_group.to_string(),
2598 repetitions: sg4_reps,
2599 });
2600 }
2601 } else if !uns_segments.is_empty() {
2602 if transaction_group.is_empty() {
2603 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2608 if uns_is_summary {
2609 inter_group.insert(all_groups.len(), uns_segments);
2610 } else {
2611 inter_group.insert(0, uns_segments);
2612 }
2613 } else {
2614 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2618 let tx_num = sg_num(transaction_group);
2619 let uns_pos = all_groups
2620 .iter()
2621 .rposition(|g| sg_num(&g.group_id) <= tx_num)
2622 .map(|i| i + 1)
2623 .unwrap_or(all_groups.len());
2624 inter_group.insert(uns_pos, uns_segments);
2625 }
2626 }
2627
2628 AssembledTree {
2629 segments: root_segments,
2630 groups: all_groups,
2631 post_group_start: pre_group_count,
2632 inter_group_segments: inter_group,
2633 }
2634 }
2635
2636 pub fn build_group_from_bo4e(
2638 &self,
2639 bo4e_value: &serde_json::Value,
2640 def: &MappingDefinition,
2641 ) -> AssembledGroup {
2642 let instance = self.map_reverse(bo4e_value, def);
2643 let leaf_group = def
2644 .meta
2645 .source_group
2646 .rsplit('.')
2647 .next()
2648 .unwrap_or(&def.meta.source_group);
2649
2650 AssembledGroup {
2651 group_id: leaf_group.to_string(),
2652 repetitions: vec![instance],
2653 }
2654 }
2655
2656 pub fn map_interchange_typed<M, T>(
2664 msg_engine: &MappingEngine,
2665 tx_engine: &MappingEngine,
2666 tree: &AssembledTree,
2667 tx_group: &str,
2668 enrich_codes: bool,
2669 nachrichtendaten: crate::model::Nachrichtendaten,
2670 interchangedaten: crate::model::Interchangedaten,
2671 ) -> Result<crate::model::Interchange<M, T>, serde_json::Error>
2672 where
2673 M: serde::de::DeserializeOwned,
2674 T: serde::de::DeserializeOwned,
2675 {
2676 let mapped = Self::map_interchange(msg_engine, tx_engine, tree, tx_group, enrich_codes);
2677 let nachricht = mapped.into_dynamic_nachricht(nachrichtendaten);
2678 let dynamic = crate::model::DynamicInterchange {
2679 interchangedaten,
2680 nachrichten: vec![nachricht],
2681 };
2682 let value = serde_json::to_value(&dynamic)?;
2683 serde_json::from_value(value)
2684 }
2685
2686 pub fn map_interchange_reverse_typed<M, T>(
2693 msg_engine: &MappingEngine,
2694 tx_engine: &MappingEngine,
2695 nachricht: &crate::model::Nachricht<M, T>,
2696 tx_group: &str,
2697 ) -> Result<AssembledTree, serde_json::Error>
2698 where
2699 M: serde::Serialize,
2700 T: serde::Serialize,
2701 {
2702 let stammdaten = serde_json::to_value(&nachricht.stammdaten)?;
2703 let transaktionen: Vec<crate::model::MappedTransaktion> = nachricht
2704 .transaktionen
2705 .iter()
2706 .map(|t| {
2707 Ok(crate::model::MappedTransaktion {
2708 stammdaten: serde_json::to_value(t)?,
2709 nesting_info: Default::default(),
2710 })
2711 })
2712 .collect::<Result<Vec<_>, serde_json::Error>>()?;
2713 let mapped = crate::model::MappedMessage {
2714 stammdaten,
2715 transaktionen,
2716 nesting_info: Default::default(),
2717 };
2718 Ok(Self::map_interchange_reverse(
2719 msg_engine, tx_engine, &mapped, tx_group, None,
2720 ))
2721 }
2722}
2723
2724fn parse_source_path_part(part: &str) -> (&str, Option<&str>) {
2731 if let Some(pos) = part.find('_') {
2735 let group = &part[..pos];
2736 let qualifier = &part[pos + 1..];
2737 if !qualifier.is_empty() {
2738 return (group, Some(qualifier));
2739 }
2740 }
2741 (part, None)
2742}
2743
2744fn build_reverse_mig_group_order(mig: &MigSchema, tx_group_id: &str) -> HashMap<String, usize> {
2752 let mut order = HashMap::new();
2753 if let Some(tg) = mig.segment_groups.iter().find(|g| g.id == tx_group_id) {
2754 for (i, nested) in tg.nested_groups.iter().enumerate() {
2755 if let Some(ref vc) = nested.variant_code {
2757 let variant_key = format!("{}_{}", nested.id, vc.to_uppercase());
2758 order.insert(variant_key, i);
2759 }
2760 order.entry(nested.id.clone()).or_insert(i);
2762 }
2763 }
2764 order
2765}
2766
2767fn variant_mig_position(
2773 def: &MappingDefinition,
2774 base_group_id: &str,
2775 mig_order: &HashMap<String, usize>,
2776) -> usize {
2777 if let Some(ref sp) = def.meta.source_path {
2780 let base_lower = base_group_id.to_lowercase();
2782 for part in sp.split('.') {
2783 if part.starts_with(&base_lower)
2784 || part.starts_with(base_group_id.to_lowercase().as_str())
2785 {
2786 if let Some(underscore_pos) = part.find('_') {
2788 let qualifier = &part[underscore_pos + 1..];
2789 let variant_key = format!("{}_{}", base_group_id, qualifier.to_uppercase());
2790 if let Some(&pos) = mig_order.get(&variant_key) {
2791 return pos;
2792 }
2793 }
2794 }
2795 }
2796 }
2797 mig_order.get(base_group_id).copied().unwrap_or(usize::MAX)
2799}
2800
2801fn find_rep_by_entry_qualifier<'a>(
2806 reps: &'a [AssembledGroupInstance],
2807 qualifier: &str,
2808) -> Option<&'a AssembledGroupInstance> {
2809 let parts: Vec<&str> = qualifier.split('_').collect();
2811 reps.iter().find(|inst| {
2812 inst.segments.first().is_some_and(|seg| {
2813 seg.elements
2814 .first()
2815 .and_then(|e| e.first())
2816 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
2817 })
2818 })
2819}
2820
2821fn find_all_reps_by_entry_qualifier<'a>(
2823 reps: &'a [AssembledGroupInstance],
2824 qualifier: &str,
2825) -> Vec<&'a AssembledGroupInstance> {
2826 let parts: Vec<&str> = qualifier.split('_').collect();
2828 reps.iter()
2829 .filter(|inst| {
2830 inst.segments.first().is_some_and(|seg| {
2831 seg.elements
2832 .first()
2833 .and_then(|e| e.first())
2834 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
2835 })
2836 })
2837 .collect()
2838}
2839
2840fn has_source_path_qualifiers(source_path: &str) -> bool {
2842 source_path.split('.').any(|part| {
2843 if let Some(pos) = part.find('_') {
2844 pos < part.len() - 1
2845 } else {
2846 false
2847 }
2848 })
2849}
2850
2851fn parse_group_spec(part: &str) -> (&str, Option<usize>) {
2852 if let Some(colon_pos) = part.find(':') {
2853 let id = &part[..colon_pos];
2854 let rep = part[colon_pos + 1..].parse::<usize>().ok();
2855 (id, rep)
2856 } else {
2857 (part, None)
2858 }
2859}
2860
2861fn strip_tx_group_prefix(source_group: &str, tx_group: &str) -> String {
2867 if source_group == tx_group || source_group.is_empty() {
2868 String::new()
2869 } else if let Some(rest) = source_group.strip_prefix(tx_group) {
2870 rest.strip_prefix('.').unwrap_or(rest).to_string()
2871 } else {
2872 source_group.to_string()
2873 }
2874}
2875
2876fn place_in_groups(
2884 groups: &mut Vec<AssembledGroup>,
2885 relative_path: &str,
2886 instance: AssembledGroupInstance,
2887) -> usize {
2888 let parts: Vec<&str> = relative_path.split('.').collect();
2889
2890 if parts.len() == 1 {
2891 let (id, rep) = parse_group_spec(parts[0]);
2893
2894 let group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == id) {
2896 g
2897 } else {
2898 groups.push(AssembledGroup {
2899 group_id: id.to_string(),
2900 repetitions: vec![],
2901 });
2902 groups.last_mut().unwrap()
2903 };
2904
2905 if let Some(rep_idx) = rep {
2906 while group.repetitions.len() <= rep_idx {
2908 group.repetitions.push(AssembledGroupInstance {
2909 segments: vec![],
2910 child_groups: vec![],
2911 skipped_segments: Vec::new(),
2912 });
2913 }
2914 group.repetitions[rep_idx]
2915 .segments
2916 .extend(instance.segments);
2917 group.repetitions[rep_idx]
2918 .child_groups
2919 .extend(instance.child_groups);
2920 rep_idx
2921 } else {
2922 let pos = group.repetitions.len();
2924 group.repetitions.push(instance);
2925 pos
2926 }
2927 } else {
2928 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
2930 let rep_idx = parent_rep.unwrap_or(0);
2931
2932 let parent_group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == parent_id) {
2934 g
2935 } else {
2936 groups.push(AssembledGroup {
2937 group_id: parent_id.to_string(),
2938 repetitions: vec![],
2939 });
2940 groups.last_mut().unwrap()
2941 };
2942
2943 while parent_group.repetitions.len() <= rep_idx {
2945 parent_group.repetitions.push(AssembledGroupInstance {
2946 segments: vec![],
2947 child_groups: vec![],
2948 skipped_segments: Vec::new(),
2949 });
2950 }
2951
2952 let remaining = parts[1..].join(".");
2953 place_in_groups(
2954 &mut parent_group.repetitions[rep_idx].child_groups,
2955 &remaining,
2956 instance,
2957 );
2958 rep_idx
2959 }
2960}
2961
2962fn resolve_child_relative(
2974 relative: &str,
2975 source_path: Option<&str>,
2976 source_path_to_rep: &std::collections::HashMap<String, Vec<usize>>,
2977 item_idx: usize,
2978) -> String {
2979 let parts: Vec<&str> = relative.split('.').collect();
2980 if parts.is_empty() {
2981 return relative.to_string();
2982 }
2983
2984 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
2986 if parent_rep.is_some() {
2987 return relative.to_string();
2988 }
2989
2990 if let Some(sp) = source_path {
2992 if let Some((parent_path, _child)) = sp.rsplit_once('.') {
2993 if let Some(rep_indices) = source_path_to_rep.get(parent_path) {
2994 let rep_idx = rep_indices
2996 .get(item_idx)
2997 .or_else(|| rep_indices.last())
2998 .copied()
2999 .unwrap_or(0);
3000 let rest = parts[1..].join(".");
3001 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3002 }
3003 }
3004 }
3005
3006 relative.to_string()
3008}
3009
3010struct DiscriminatorMatcher<'a> {
3017 tag: &'a str,
3018 element_idx: usize,
3019 component_idx: usize,
3020 expected_values: Vec<&'a str>,
3021 occurrence: Option<usize>,
3023}
3024
3025impl<'a> DiscriminatorMatcher<'a> {
3026 fn parse(disc: &'a str) -> Option<Self> {
3027 let (spec, expected) = disc.split_once('=')?;
3028 let parts: Vec<&str> = spec.split('.').collect();
3029 if parts.len() != 3 {
3030 return None;
3031 }
3032 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
3033 Some(Self {
3034 tag: parts[0],
3035 element_idx: parts[1].parse().ok()?,
3036 component_idx: parts[2].parse().ok()?,
3037 expected_values: expected_raw.split('|').collect(),
3038 occurrence,
3039 })
3040 }
3041
3042 fn matches(&self, instance: &AssembledGroupInstance) -> bool {
3043 instance.segments.iter().any(|s| {
3044 s.tag.eq_ignore_ascii_case(self.tag)
3045 && s.elements
3046 .get(self.element_idx)
3047 .and_then(|e| e.get(self.component_idx))
3048 .map(|v| self.expected_values.iter().any(|ev| v == ev))
3049 .unwrap_or(false)
3050 })
3051 }
3052
3053 fn filter_instances<'b>(
3055 &self,
3056 instances: Vec<&'b AssembledGroupInstance>,
3057 ) -> Vec<&'b AssembledGroupInstance> {
3058 let matching: Vec<_> = instances
3059 .into_iter()
3060 .filter(|inst| self.matches(inst))
3061 .collect();
3062 if let Some(occ) = self.occurrence {
3063 matching.into_iter().nth(occ).into_iter().collect()
3064 } else {
3065 matching
3066 }
3067 }
3068}
3069
3070fn parse_discriminator_occurrence(expected: &str) -> (&str, Option<usize>) {
3076 if let Some(hash_pos) = expected.rfind('#') {
3077 if let Ok(occ) = expected[hash_pos + 1..].parse::<usize>() {
3078 return (&expected[..hash_pos], Some(occ));
3079 }
3080 }
3081 (expected, None)
3082}
3083
3084fn strip_rep_index(relative: &str) -> String {
3088 let (id, _) = parse_group_spec(relative);
3089 id.to_string()
3090}
3091
3092fn strip_all_rep_indices(relative: &str) -> String {
3097 relative
3098 .split('.')
3099 .map(|part| {
3100 let (id, _) = parse_group_spec(part);
3101 id
3102 })
3103 .collect::<Vec<_>>()
3104 .join(".")
3105}
3106
3107fn is_collect_all_path(path: &str) -> bool {
3112 let tag_part = path.split('.').next().unwrap_or("");
3113 if let Some(bracket_start) = tag_part.find('[') {
3114 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3115 if let Some(comma_pos) = inner.find(',') {
3116 let qualifier = &inner[..comma_pos];
3117 let occ = &inner[comma_pos + 1..];
3118 qualifier != "*" && occ == "*"
3120 } else {
3121 false
3122 }
3123 } else {
3124 false
3125 }
3126}
3127
3128fn parse_tag_qualifier(tag_part: &str) -> (String, Option<&str>, usize) {
3135 if let Some(bracket_start) = tag_part.find('[') {
3136 let tag = tag_part[..bracket_start].to_uppercase();
3137 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3138 if let Some(comma_pos) = inner.find(',') {
3139 let qualifier = &inner[..comma_pos];
3140 let index = inner[comma_pos + 1..].parse::<usize>().unwrap_or(0);
3141 if qualifier == "*" {
3143 (tag, None, index)
3144 } else {
3145 (tag, Some(qualifier), index)
3146 }
3147 } else {
3148 (tag, Some(inner), 0)
3149 }
3150 } else {
3151 (tag_part.to_uppercase(), None, 0)
3152 }
3153}
3154
3155fn inject_bo4e_metadata(mut value: serde_json::Value, bo4e_type: &str) -> serde_json::Value {
3160 match &mut value {
3161 serde_json::Value::Object(map) => {
3162 map.entry("boTyp")
3163 .or_insert_with(|| serde_json::Value::String(bo4e_type.to_uppercase()));
3164 map.entry("versionStruktur")
3165 .or_insert_with(|| serde_json::Value::String("1".to_string()));
3166 }
3167 serde_json::Value::Array(items) => {
3168 for item in items {
3169 if let serde_json::Value::Object(map) = item {
3170 map.entry("boTyp")
3171 .or_insert_with(|| serde_json::Value::String(bo4e_type.to_uppercase()));
3172 map.entry("versionStruktur")
3173 .or_insert_with(|| serde_json::Value::String("1".to_string()));
3174 }
3175 }
3176 }
3177 _ => {}
3178 }
3179 value
3180}
3181
3182fn deep_merge_insert(
3188 result: &mut serde_json::Map<String, serde_json::Value>,
3189 entity: &str,
3190 bo4e: serde_json::Value,
3191) {
3192 if let Some(existing) = result.get_mut(entity) {
3193 if let (Some(existing_arr), Some(new_arr)) =
3196 (existing.as_array().map(|a| a.len()), bo4e.as_array())
3197 {
3198 if existing_arr == new_arr.len() {
3199 let existing_arr = existing.as_array_mut().unwrap();
3200 for (existing_elem, new_elem) in existing_arr.iter_mut().zip(new_arr) {
3201 if let (Some(existing_map), Some(new_map)) =
3202 (existing_elem.as_object_mut(), new_elem.as_object())
3203 {
3204 for (k, v) in new_map {
3205 if let Some(existing_v) = existing_map.get_mut(k) {
3206 if let (Some(existing_inner), Some(new_inner)) =
3207 (existing_v.as_object_mut(), v.as_object())
3208 {
3209 for (ik, iv) in new_inner {
3210 existing_inner
3211 .entry(ik.clone())
3212 .or_insert_with(|| iv.clone());
3213 }
3214 }
3215 } else {
3216 existing_map.insert(k.clone(), v.clone());
3217 }
3218 }
3219 }
3220 }
3221 return;
3222 }
3223 }
3224 if let (Some(existing_map), serde_json::Value::Object(new_map)) =
3226 (existing.as_object_mut(), &bo4e)
3227 {
3228 for (k, v) in new_map {
3229 if let Some(existing_v) = existing_map.get_mut(k) {
3230 if let (Some(existing_inner), Some(new_inner)) =
3232 (existing_v.as_object_mut(), v.as_object())
3233 {
3234 for (ik, iv) in new_inner {
3235 existing_inner
3236 .entry(ik.clone())
3237 .or_insert_with(|| iv.clone());
3238 }
3239 }
3240 } else {
3242 existing_map.insert(k.clone(), v.clone());
3243 }
3244 }
3245 return;
3246 }
3247 }
3248 result.insert(entity.to_string(), bo4e);
3249}
3250
3251fn to_camel_case(name: &str) -> String {
3257 let mut chars = name.chars();
3258 match chars.next() {
3259 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
3260 None => String::new(),
3261 }
3262}
3263
3264fn set_nested_value(map: &mut serde_json::Map<String, serde_json::Value>, path: &str, val: String) {
3267 set_nested_value_json(map, path, serde_json::Value::String(val));
3268}
3269
3270fn set_nested_value_json(
3272 map: &mut serde_json::Map<String, serde_json::Value>,
3273 path: &str,
3274 val: serde_json::Value,
3275) {
3276 if let Some((prefix, leaf)) = path.rsplit_once('.') {
3277 let mut current = map;
3278 for part in prefix.split('.') {
3279 let entry = current
3280 .entry(part.to_string())
3281 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
3282 current = entry.as_object_mut().expect("expected object in path");
3283 }
3284 current.insert(leaf.to_string(), val);
3285 } else {
3286 map.insert(path.to_string(), val);
3287 }
3288}
3289
3290#[derive(serde::Serialize, serde::Deserialize)]
3295pub struct VariantCache {
3296 pub message_defs: Vec<MappingDefinition>,
3298 pub transaction_defs: HashMap<String, Vec<MappingDefinition>>,
3300 pub combined_defs: HashMap<String, Vec<MappingDefinition>>,
3302 #[serde(default)]
3304 pub code_lookups: HashMap<String, crate::code_lookup::CodeLookup>,
3305 #[serde(default)]
3307 pub mig_schema: Option<mig_types::schema::mig::MigSchema>,
3308 #[serde(default)]
3310 pub segment_structure: Option<crate::segment_structure::SegmentStructure>,
3311 #[serde(default)]
3314 pub pid_segment_numbers: HashMap<String, Vec<String>>,
3315 #[serde(default)]
3318 pub pid_requirements: HashMap<String, crate::pid_requirements::PidRequirements>,
3319}
3320
3321impl VariantCache {
3322 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
3324 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
3325 path: path.display().to_string(),
3326 message: e.to_string(),
3327 })?;
3328 if let Some(parent) = path.parent() {
3329 std::fs::create_dir_all(parent)?;
3330 }
3331 std::fs::write(path, encoded)?;
3332 Ok(())
3333 }
3334
3335 pub fn load(path: &Path) -> Result<Self, MappingError> {
3337 let bytes = std::fs::read(path)?;
3338 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
3339 path: path.display().to_string(),
3340 message: e.to_string(),
3341 })
3342 }
3343}
3344
3345#[derive(serde::Serialize, serde::Deserialize)]
3350pub struct DataBundle {
3351 pub format_version: String,
3352 pub bundle_version: u32,
3353 pub variants: HashMap<String, VariantCache>,
3354}
3355
3356impl DataBundle {
3357 pub const CURRENT_VERSION: u32 = 2;
3358
3359 pub fn variant(&self, name: &str) -> Option<&VariantCache> {
3360 self.variants.get(name)
3361 }
3362
3363 pub fn write_to<W: std::io::Write>(&self, writer: &mut W) -> Result<(), MappingError> {
3364 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
3365 path: "<stream>".to_string(),
3366 message: e.to_string(),
3367 })?;
3368 writer.write_all(&encoded).map_err(MappingError::Io)
3369 }
3370
3371 pub fn read_from<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
3372 let mut bytes = Vec::new();
3373 reader.read_to_end(&mut bytes).map_err(MappingError::Io)?;
3374 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
3375 path: "<stream>".to_string(),
3376 message: e.to_string(),
3377 })
3378 }
3379
3380 pub fn read_from_checked<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
3381 let bundle = Self::read_from(reader)?;
3382 if bundle.bundle_version != Self::CURRENT_VERSION {
3383 return Err(MappingError::CacheRead {
3384 path: "<stream>".to_string(),
3385 message: format!(
3386 "Incompatible bundle version {}, expected version {}. \
3387 Run `edifact-data update` to fetch compatible bundles.",
3388 bundle.bundle_version,
3389 Self::CURRENT_VERSION
3390 ),
3391 });
3392 }
3393 Ok(bundle)
3394 }
3395
3396 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
3397 if let Some(parent) = path.parent() {
3398 std::fs::create_dir_all(parent)?;
3399 }
3400 let mut file = std::fs::File::create(path).map_err(MappingError::Io)?;
3401 self.write_to(&mut file)
3402 }
3403
3404 pub fn load(path: &Path) -> Result<Self, MappingError> {
3405 let mut file = std::fs::File::open(path).map_err(MappingError::Io)?;
3406 Self::read_from_checked(&mut file)
3407 }
3408}
3409
3410fn sort_variant_reps_by_mig(
3423 child_groups: &mut [AssembledGroup],
3424 mig: &MigSchema,
3425 transaction_group: &str,
3426) {
3427 let tx_def = match mig
3428 .segment_groups
3429 .iter()
3430 .find(|sg| sg.id == transaction_group)
3431 {
3432 Some(d) => d,
3433 None => return,
3434 };
3435
3436 for cg in child_groups.iter_mut() {
3437 if cg.repetitions.len() <= 1 {
3438 continue;
3439 }
3440
3441 let variant_defs: Vec<(usize, &mig_types::schema::mig::MigSegmentGroup)> = tx_def
3443 .nested_groups
3444 .iter()
3445 .enumerate()
3446 .filter(|(_, ng)| ng.id == cg.group_id && ng.variant_code.is_some())
3447 .collect();
3448
3449 if variant_defs.is_empty() {
3450 continue;
3451 }
3452
3453 cg.repetitions.sort_by_key(|rep| {
3456 let entry_seg = rep.segments.first();
3457 for &(mig_pos, variant_def) in &variant_defs {
3458 let (ei, ci) = variant_def.variant_qualifier_position.unwrap_or((0, 0));
3459 let actual_qual = entry_seg
3460 .and_then(|s| s.elements.get(ei))
3461 .and_then(|e| e.get(ci))
3462 .map(|s| s.as_str())
3463 .unwrap_or("");
3464 let matches = if !variant_def.variant_codes.is_empty() {
3465 variant_def
3466 .variant_codes
3467 .iter()
3468 .any(|c| actual_qual.eq_ignore_ascii_case(c))
3469 } else if let Some(ref expected_code) = variant_def.variant_code {
3470 actual_qual.eq_ignore_ascii_case(expected_code)
3471 } else {
3472 false
3473 };
3474 if matches {
3475 return mig_pos;
3476 }
3477 }
3478 usize::MAX });
3480 }
3481}
3482
3483#[cfg(test)]
3484mod tests {
3485 use super::*;
3486 use crate::definition::{MappingDefinition, MappingMeta, StructuredFieldMapping};
3487 use indexmap::IndexMap;
3488
3489 fn make_def(fields: IndexMap<String, FieldMapping>) -> MappingDefinition {
3490 MappingDefinition {
3491 meta: MappingMeta {
3492 entity: "Test".to_string(),
3493 bo4e_type: "Test".to_string(),
3494 companion_type: None,
3495 source_group: "SG4".to_string(),
3496 source_path: None,
3497 discriminator: None,
3498 repeat_on_tag: None,
3499 },
3500 fields,
3501 companion_fields: None,
3502 complex_handlers: None,
3503 }
3504 }
3505
3506 #[test]
3507 fn test_map_interchange_single_transaction_backward_compat() {
3508 use mig_assembly::assembler::*;
3509
3510 let tree = AssembledTree {
3512 segments: vec![
3513 AssembledSegment {
3514 tag: "UNH".to_string(),
3515 elements: vec![vec!["001".to_string()]],
3516 },
3517 AssembledSegment {
3518 tag: "BGM".to_string(),
3519 elements: vec![vec!["E01".to_string()], vec!["DOC001".to_string()]],
3520 },
3521 ],
3522 groups: vec![
3523 AssembledGroup {
3524 group_id: "SG2".to_string(),
3525 repetitions: vec![AssembledGroupInstance {
3526 segments: vec![AssembledSegment {
3527 tag: "NAD".to_string(),
3528 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
3529 }],
3530 child_groups: vec![],
3531 skipped_segments: vec![],
3532 }],
3533 },
3534 AssembledGroup {
3535 group_id: "SG4".to_string(),
3536 repetitions: vec![AssembledGroupInstance {
3537 segments: vec![AssembledSegment {
3538 tag: "IDE".to_string(),
3539 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
3540 }],
3541 child_groups: vec![AssembledGroup {
3542 group_id: "SG5".to_string(),
3543 repetitions: vec![AssembledGroupInstance {
3544 segments: vec![AssembledSegment {
3545 tag: "LOC".to_string(),
3546 elements: vec![
3547 vec!["Z16".to_string()],
3548 vec!["DE000111222333".to_string()],
3549 ],
3550 }],
3551 child_groups: vec![],
3552 skipped_segments: vec![],
3553 }],
3554 }],
3555 skipped_segments: vec![],
3556 }],
3557 },
3558 ],
3559 post_group_start: 2,
3560 inter_group_segments: std::collections::BTreeMap::new(),
3561 };
3562
3563 let msg_engine = MappingEngine::from_definitions(vec![]);
3565
3566 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
3568 tx_fields.insert(
3569 "ide.1".to_string(),
3570 FieldMapping::Simple("vorgangId".to_string()),
3571 );
3572 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
3573 malo_fields.insert(
3574 "loc.1".to_string(),
3575 FieldMapping::Simple("marktlokationsId".to_string()),
3576 );
3577
3578 let tx_engine = MappingEngine::from_definitions(vec![
3579 MappingDefinition {
3580 meta: MappingMeta {
3581 entity: "Prozessdaten".to_string(),
3582 bo4e_type: "Prozessdaten".to_string(),
3583 companion_type: None,
3584 source_group: "SG4".to_string(),
3585 source_path: None,
3586 discriminator: None,
3587 repeat_on_tag: None,
3588 },
3589 fields: tx_fields,
3590 companion_fields: None,
3591 complex_handlers: None,
3592 },
3593 MappingDefinition {
3594 meta: MappingMeta {
3595 entity: "Marktlokation".to_string(),
3596 bo4e_type: "Marktlokation".to_string(),
3597 companion_type: None,
3598 source_group: "SG4.SG5".to_string(),
3599 source_path: None,
3600 discriminator: None,
3601 repeat_on_tag: None,
3602 },
3603 fields: malo_fields,
3604 companion_fields: None,
3605 complex_handlers: None,
3606 },
3607 ]);
3608
3609 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
3610
3611 assert_eq!(result.transaktionen.len(), 1);
3612 assert_eq!(
3613 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
3614 .as_str()
3615 .unwrap(),
3616 "TX001"
3617 );
3618 assert_eq!(
3619 result.transaktionen[0].stammdaten["marktlokation"]["marktlokationsId"]
3620 .as_str()
3621 .unwrap(),
3622 "DE000111222333"
3623 );
3624 }
3625
3626 #[test]
3627 fn test_map_reverse_pads_intermediate_empty_elements() {
3628 let mut fields = IndexMap::new();
3630 fields.insert(
3631 "nad.0".to_string(),
3632 FieldMapping::Structured(StructuredFieldMapping {
3633 target: String::new(),
3634 transform: None,
3635 when: None,
3636 default: Some("Z09".to_string()),
3637 enum_map: None,
3638 when_filled: None,
3639 also_target: None,
3640 also_enum_map: None,
3641 }),
3642 );
3643 fields.insert(
3644 "nad.3.0".to_string(),
3645 FieldMapping::Simple("name".to_string()),
3646 );
3647 fields.insert(
3648 "nad.3.1".to_string(),
3649 FieldMapping::Simple("vorname".to_string()),
3650 );
3651
3652 let def = make_def(fields);
3653 let engine = MappingEngine::from_definitions(vec![]);
3654
3655 let bo4e = serde_json::json!({
3656 "name": "Muster",
3657 "vorname": "Max"
3658 });
3659
3660 let instance = engine.map_reverse(&bo4e, &def);
3661 assert_eq!(instance.segments.len(), 1);
3662
3663 let nad = &instance.segments[0];
3664 assert_eq!(nad.tag, "NAD");
3665 assert_eq!(nad.elements.len(), 4);
3666 assert_eq!(nad.elements[0], vec!["Z09"]);
3667 assert_eq!(nad.elements[1], vec![""]);
3669 assert_eq!(nad.elements[2], vec![""]);
3670 assert_eq!(nad.elements[3][0], "Muster");
3671 assert_eq!(nad.elements[3][1], "Max");
3672 }
3673
3674 #[test]
3675 fn test_map_reverse_no_padding_when_contiguous() {
3676 let mut fields = IndexMap::new();
3678 fields.insert(
3679 "dtm.0.0".to_string(),
3680 FieldMapping::Structured(StructuredFieldMapping {
3681 target: String::new(),
3682 transform: None,
3683 when: None,
3684 default: Some("92".to_string()),
3685 enum_map: None,
3686 when_filled: None,
3687 also_target: None,
3688 also_enum_map: None,
3689 }),
3690 );
3691 fields.insert(
3692 "dtm.0.1".to_string(),
3693 FieldMapping::Simple("value".to_string()),
3694 );
3695 fields.insert(
3696 "dtm.0.2".to_string(),
3697 FieldMapping::Structured(StructuredFieldMapping {
3698 target: String::new(),
3699 transform: None,
3700 when: None,
3701 default: Some("303".to_string()),
3702 enum_map: None,
3703 when_filled: None,
3704 also_target: None,
3705 also_enum_map: None,
3706 }),
3707 );
3708
3709 let def = make_def(fields);
3710 let engine = MappingEngine::from_definitions(vec![]);
3711
3712 let bo4e = serde_json::json!({ "value": "20250531" });
3713
3714 let instance = engine.map_reverse(&bo4e, &def);
3715 let dtm = &instance.segments[0];
3716 assert_eq!(dtm.elements.len(), 1);
3718 assert_eq!(dtm.elements[0], vec!["92", "20250531", "303"]);
3719 }
3720
3721 #[test]
3722 fn test_map_message_level_extracts_sg2_only() {
3723 use mig_assembly::assembler::*;
3724
3725 let tree = AssembledTree {
3727 segments: vec![
3728 AssembledSegment {
3729 tag: "UNH".to_string(),
3730 elements: vec![vec!["001".to_string()]],
3731 },
3732 AssembledSegment {
3733 tag: "BGM".to_string(),
3734 elements: vec![vec!["E01".to_string()]],
3735 },
3736 ],
3737 groups: vec![
3738 AssembledGroup {
3739 group_id: "SG2".to_string(),
3740 repetitions: vec![AssembledGroupInstance {
3741 segments: vec![AssembledSegment {
3742 tag: "NAD".to_string(),
3743 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
3744 }],
3745 child_groups: vec![],
3746 skipped_segments: vec![],
3747 }],
3748 },
3749 AssembledGroup {
3750 group_id: "SG4".to_string(),
3751 repetitions: vec![AssembledGroupInstance {
3752 segments: vec![AssembledSegment {
3753 tag: "IDE".to_string(),
3754 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
3755 }],
3756 child_groups: vec![],
3757 skipped_segments: vec![],
3758 }],
3759 },
3760 ],
3761 post_group_start: 2,
3762 inter_group_segments: std::collections::BTreeMap::new(),
3763 };
3764
3765 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
3767 msg_fields.insert(
3768 "nad.0".to_string(),
3769 FieldMapping::Simple("marktrolle".to_string()),
3770 );
3771 msg_fields.insert(
3772 "nad.1".to_string(),
3773 FieldMapping::Simple("rollencodenummer".to_string()),
3774 );
3775 let msg_def = MappingDefinition {
3776 meta: MappingMeta {
3777 entity: "Marktteilnehmer".to_string(),
3778 bo4e_type: "Marktteilnehmer".to_string(),
3779 companion_type: None,
3780 source_group: "SG2".to_string(),
3781 source_path: None,
3782 discriminator: None,
3783 repeat_on_tag: None,
3784 },
3785 fields: msg_fields,
3786 companion_fields: None,
3787 complex_handlers: None,
3788 };
3789
3790 let engine = MappingEngine::from_definitions(vec![msg_def.clone()]);
3791 let result = engine.map_all_forward(&tree);
3792
3793 assert!(result.get("marktteilnehmer").is_some());
3795 let mt = &result["marktteilnehmer"];
3796 assert_eq!(mt["marktrolle"].as_str().unwrap(), "MS");
3797 assert_eq!(mt["rollencodenummer"].as_str().unwrap(), "9900123");
3798 }
3799
3800 #[test]
3801 fn test_map_transaction_scoped_to_sg4_instance() {
3802 use mig_assembly::assembler::*;
3803
3804 let tree = AssembledTree {
3806 segments: vec![
3807 AssembledSegment {
3808 tag: "UNH".to_string(),
3809 elements: vec![vec!["001".to_string()]],
3810 },
3811 AssembledSegment {
3812 tag: "BGM".to_string(),
3813 elements: vec![vec!["E01".to_string()]],
3814 },
3815 ],
3816 groups: vec![AssembledGroup {
3817 group_id: "SG4".to_string(),
3818 repetitions: vec![AssembledGroupInstance {
3819 segments: vec![AssembledSegment {
3820 tag: "IDE".to_string(),
3821 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
3822 }],
3823 child_groups: vec![AssembledGroup {
3824 group_id: "SG5".to_string(),
3825 repetitions: vec![AssembledGroupInstance {
3826 segments: vec![AssembledSegment {
3827 tag: "LOC".to_string(),
3828 elements: vec![
3829 vec!["Z16".to_string()],
3830 vec!["DE000111222333".to_string()],
3831 ],
3832 }],
3833 child_groups: vec![],
3834 skipped_segments: vec![],
3835 }],
3836 }],
3837 skipped_segments: vec![],
3838 }],
3839 }],
3840 post_group_start: 2,
3841 inter_group_segments: std::collections::BTreeMap::new(),
3842 };
3843
3844 let mut proz_fields: IndexMap<String, FieldMapping> = IndexMap::new();
3846 proz_fields.insert(
3847 "ide.1".to_string(),
3848 FieldMapping::Simple("vorgangId".to_string()),
3849 );
3850 let proz_def = MappingDefinition {
3851 meta: MappingMeta {
3852 entity: "Prozessdaten".to_string(),
3853 bo4e_type: "Prozessdaten".to_string(),
3854 companion_type: None,
3855 source_group: "".to_string(), source_path: None,
3857 discriminator: None,
3858 repeat_on_tag: None,
3859 },
3860 fields: proz_fields,
3861 companion_fields: None,
3862 complex_handlers: None,
3863 };
3864
3865 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
3866 malo_fields.insert(
3867 "loc.1".to_string(),
3868 FieldMapping::Simple("marktlokationsId".to_string()),
3869 );
3870 let malo_def = MappingDefinition {
3871 meta: MappingMeta {
3872 entity: "Marktlokation".to_string(),
3873 bo4e_type: "Marktlokation".to_string(),
3874 companion_type: None,
3875 source_group: "SG5".to_string(), source_path: None,
3877 discriminator: None,
3878 repeat_on_tag: None,
3879 },
3880 fields: malo_fields,
3881 companion_fields: None,
3882 complex_handlers: None,
3883 };
3884
3885 let tx_engine = MappingEngine::from_definitions(vec![proz_def, malo_def]);
3886
3887 let sg4 = &tree.groups[0]; let sg4_instance = &sg4.repetitions[0];
3890 let sub_tree = sg4_instance.as_assembled_tree();
3891
3892 let result = tx_engine.map_all_forward(&sub_tree);
3893
3894 assert_eq!(
3896 result["prozessdaten"]["vorgangId"].as_str().unwrap(),
3897 "TX001"
3898 );
3899
3900 assert_eq!(
3902 result["marktlokation"]["marktlokationsId"]
3903 .as_str()
3904 .unwrap(),
3905 "DE000111222333"
3906 );
3907 }
3908
3909 #[test]
3910 fn test_map_interchange_produces_full_hierarchy() {
3911 use mig_assembly::assembler::*;
3912
3913 let tree = AssembledTree {
3915 segments: vec![
3916 AssembledSegment {
3917 tag: "UNH".to_string(),
3918 elements: vec![vec!["001".to_string()]],
3919 },
3920 AssembledSegment {
3921 tag: "BGM".to_string(),
3922 elements: vec![vec!["E01".to_string()]],
3923 },
3924 ],
3925 groups: vec![
3926 AssembledGroup {
3927 group_id: "SG2".to_string(),
3928 repetitions: vec![AssembledGroupInstance {
3929 segments: vec![AssembledSegment {
3930 tag: "NAD".to_string(),
3931 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
3932 }],
3933 child_groups: vec![],
3934 skipped_segments: vec![],
3935 }],
3936 },
3937 AssembledGroup {
3938 group_id: "SG4".to_string(),
3939 repetitions: vec![
3940 AssembledGroupInstance {
3941 segments: vec![AssembledSegment {
3942 tag: "IDE".to_string(),
3943 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
3944 }],
3945 child_groups: vec![],
3946 skipped_segments: vec![],
3947 },
3948 AssembledGroupInstance {
3949 segments: vec![AssembledSegment {
3950 tag: "IDE".to_string(),
3951 elements: vec![vec!["24".to_string()], vec!["TX002".to_string()]],
3952 }],
3953 child_groups: vec![],
3954 skipped_segments: vec![],
3955 },
3956 ],
3957 },
3958 ],
3959 post_group_start: 2,
3960 inter_group_segments: std::collections::BTreeMap::new(),
3961 };
3962
3963 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
3965 msg_fields.insert(
3966 "nad.0".to_string(),
3967 FieldMapping::Simple("marktrolle".to_string()),
3968 );
3969 let msg_defs = vec![MappingDefinition {
3970 meta: MappingMeta {
3971 entity: "Marktteilnehmer".to_string(),
3972 bo4e_type: "Marktteilnehmer".to_string(),
3973 companion_type: None,
3974 source_group: "SG2".to_string(),
3975 source_path: None,
3976 discriminator: None,
3977 repeat_on_tag: None,
3978 },
3979 fields: msg_fields,
3980 companion_fields: None,
3981 complex_handlers: None,
3982 }];
3983
3984 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
3986 tx_fields.insert(
3987 "ide.1".to_string(),
3988 FieldMapping::Simple("vorgangId".to_string()),
3989 );
3990 let tx_defs = vec![MappingDefinition {
3991 meta: MappingMeta {
3992 entity: "Prozessdaten".to_string(),
3993 bo4e_type: "Prozessdaten".to_string(),
3994 companion_type: None,
3995 source_group: "SG4".to_string(),
3996 source_path: None,
3997 discriminator: None,
3998 repeat_on_tag: None,
3999 },
4000 fields: tx_fields,
4001 companion_fields: None,
4002 complex_handlers: None,
4003 }];
4004
4005 let msg_engine = MappingEngine::from_definitions(msg_defs);
4006 let tx_engine = MappingEngine::from_definitions(tx_defs);
4007
4008 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
4009
4010 assert!(result.stammdaten["marktteilnehmer"].is_object());
4012 assert_eq!(
4013 result.stammdaten["marktteilnehmer"]["marktrolle"]
4014 .as_str()
4015 .unwrap(),
4016 "MS"
4017 );
4018
4019 assert_eq!(result.transaktionen.len(), 2);
4021 assert_eq!(
4022 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
4023 .as_str()
4024 .unwrap(),
4025 "TX001"
4026 );
4027 assert_eq!(
4028 result.transaktionen[1].stammdaten["prozessdaten"]["vorgangId"]
4029 .as_str()
4030 .unwrap(),
4031 "TX002"
4032 );
4033 }
4034
4035 #[test]
4036 fn test_map_reverse_with_segment_structure_pads_trailing() {
4037 let mut fields = IndexMap::new();
4039 fields.insert(
4040 "sts.0".to_string(),
4041 FieldMapping::Structured(StructuredFieldMapping {
4042 target: String::new(),
4043 transform: None,
4044 when: None,
4045 default: Some("7".to_string()),
4046 enum_map: None,
4047 when_filled: None,
4048 also_target: None,
4049 also_enum_map: None,
4050 }),
4051 );
4052 fields.insert(
4053 "sts.2".to_string(),
4054 FieldMapping::Simple("grund".to_string()),
4055 );
4056
4057 let def = make_def(fields);
4058
4059 let mut counts = std::collections::HashMap::new();
4061 counts.insert("STS".to_string(), 5usize);
4062 let ss = SegmentStructure {
4063 element_counts: counts,
4064 };
4065
4066 let engine = MappingEngine::from_definitions(vec![]).with_segment_structure(ss);
4067
4068 let bo4e = serde_json::json!({ "grund": "E01" });
4069
4070 let instance = engine.map_reverse(&bo4e, &def);
4071 let sts = &instance.segments[0];
4072 assert_eq!(sts.elements.len(), 5);
4075 assert_eq!(sts.elements[0], vec!["7"]);
4076 assert_eq!(sts.elements[1], vec![""]);
4077 assert_eq!(sts.elements[2], vec!["E01"]);
4078 assert_eq!(sts.elements[3], vec![""]);
4079 assert_eq!(sts.elements[4], vec![""]);
4080 }
4081
4082 #[test]
4083 fn test_extract_companion_fields_with_code_enrichment() {
4084 use crate::code_lookup::CodeLookup;
4085 use mig_assembly::assembler::*;
4086
4087 let schema = serde_json::json!({
4088 "fields": {
4089 "sg4": {
4090 "children": {
4091 "sg8_z01": {
4092 "children": {
4093 "sg10": {
4094 "segments": [{
4095 "id": "CCI",
4096 "elements": [{
4097 "index": 2,
4098 "components": [{
4099 "sub_index": 0,
4100 "type": "code",
4101 "codes": [
4102 {"value": "Z15", "name": "Haushaltskunde"},
4103 {"value": "Z18", "name": "Kein Haushaltskunde"}
4104 ]
4105 }]
4106 }]
4107 }],
4108 "source_group": "SG10"
4109 }
4110 },
4111 "segments": [],
4112 "source_group": "SG8"
4113 }
4114 },
4115 "segments": [],
4116 "source_group": "SG4"
4117 }
4118 }
4119 });
4120
4121 let code_lookup = CodeLookup::from_schema_value(&schema);
4122
4123 let tree = AssembledTree {
4124 segments: vec![],
4125 groups: vec![AssembledGroup {
4126 group_id: "SG4".to_string(),
4127 repetitions: vec![AssembledGroupInstance {
4128 segments: vec![],
4129 child_groups: vec![AssembledGroup {
4130 group_id: "SG8".to_string(),
4131 repetitions: vec![AssembledGroupInstance {
4132 segments: vec![],
4133 child_groups: vec![AssembledGroup {
4134 group_id: "SG10".to_string(),
4135 repetitions: vec![AssembledGroupInstance {
4136 segments: vec![AssembledSegment {
4137 tag: "CCI".to_string(),
4138 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
4139 }],
4140 child_groups: vec![],
4141 skipped_segments: vec![],
4142 }],
4143 }],
4144 skipped_segments: vec![],
4145 }],
4146 }],
4147 skipped_segments: vec![],
4148 }],
4149 }],
4150 post_group_start: 0,
4151 inter_group_segments: std::collections::BTreeMap::new(),
4152 };
4153
4154 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4155 companion_fields.insert(
4156 "cci.2".to_string(),
4157 FieldMapping::Simple("haushaltskunde".to_string()),
4158 );
4159
4160 let def = MappingDefinition {
4161 meta: MappingMeta {
4162 entity: "Marktlokation".to_string(),
4163 bo4e_type: "Marktlokation".to_string(),
4164 companion_type: Some("MarktlokationEdifact".to_string()),
4165 source_group: "SG4.SG8.SG10".to_string(),
4166 source_path: Some("sg4.sg8_z01.sg10".to_string()),
4167 discriminator: None,
4168 repeat_on_tag: None,
4169 },
4170 fields: IndexMap::new(),
4171 companion_fields: Some(companion_fields),
4172 complex_handlers: None,
4173 };
4174
4175 let engine_plain = MappingEngine::from_definitions(vec![]);
4177 let bo4e_plain = engine_plain.map_forward(&tree, &def, 0);
4178 assert_eq!(
4179 bo4e_plain["marktlokationEdifact"]["haushaltskunde"].as_str(),
4180 Some("Z15"),
4181 "Without code lookup, should be plain string"
4182 );
4183
4184 let engine_enriched = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
4186 let bo4e_enriched = engine_enriched.map_forward(&tree, &def, 0);
4187 let hk = &bo4e_enriched["marktlokationEdifact"]["haushaltskunde"];
4188 assert_eq!(hk["code"].as_str(), Some("Z15"));
4189 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
4190 assert!(hk.get("enum").is_none());
4192 }
4193
4194 #[test]
4195 fn test_extract_companion_fields_with_enum_enrichment() {
4196 use crate::code_lookup::CodeLookup;
4197 use mig_assembly::assembler::*;
4198
4199 let schema = serde_json::json!({
4201 "fields": {
4202 "sg4": {
4203 "children": {
4204 "sg8_z01": {
4205 "children": {
4206 "sg10": {
4207 "segments": [{
4208 "id": "CCI",
4209 "elements": [{
4210 "index": 2,
4211 "components": [{
4212 "sub_index": 0,
4213 "type": "code",
4214 "codes": [
4215 {"value": "Z15", "name": "Haushaltskunde", "enum": "HAUSHALTSKUNDE"},
4216 {"value": "Z18", "name": "Kein Haushaltskunde", "enum": "KEIN_HAUSHALTSKUNDE"}
4217 ]
4218 }]
4219 }]
4220 }],
4221 "source_group": "SG10"
4222 }
4223 },
4224 "segments": [],
4225 "source_group": "SG8"
4226 }
4227 },
4228 "segments": [],
4229 "source_group": "SG4"
4230 }
4231 }
4232 });
4233
4234 let code_lookup = CodeLookup::from_schema_value(&schema);
4235
4236 let tree = AssembledTree {
4237 segments: vec![],
4238 groups: vec![AssembledGroup {
4239 group_id: "SG4".to_string(),
4240 repetitions: vec![AssembledGroupInstance {
4241 segments: vec![],
4242 child_groups: vec![AssembledGroup {
4243 group_id: "SG8".to_string(),
4244 repetitions: vec![AssembledGroupInstance {
4245 segments: vec![],
4246 child_groups: vec![AssembledGroup {
4247 group_id: "SG10".to_string(),
4248 repetitions: vec![AssembledGroupInstance {
4249 segments: vec![AssembledSegment {
4250 tag: "CCI".to_string(),
4251 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
4252 }],
4253 child_groups: vec![],
4254 skipped_segments: vec![],
4255 }],
4256 }],
4257 skipped_segments: vec![],
4258 }],
4259 }],
4260 skipped_segments: vec![],
4261 }],
4262 }],
4263 post_group_start: 0,
4264 inter_group_segments: std::collections::BTreeMap::new(),
4265 };
4266
4267 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4268 companion_fields.insert(
4269 "cci.2".to_string(),
4270 FieldMapping::Simple("haushaltskunde".to_string()),
4271 );
4272
4273 let def = MappingDefinition {
4274 meta: MappingMeta {
4275 entity: "Marktlokation".to_string(),
4276 bo4e_type: "Marktlokation".to_string(),
4277 companion_type: Some("MarktlokationEdifact".to_string()),
4278 source_group: "SG4.SG8.SG10".to_string(),
4279 source_path: Some("sg4.sg8_z01.sg10".to_string()),
4280 discriminator: None,
4281 repeat_on_tag: None,
4282 },
4283 fields: IndexMap::new(),
4284 companion_fields: Some(companion_fields),
4285 complex_handlers: None,
4286 };
4287
4288 let engine = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
4289 let bo4e = engine.map_forward(&tree, &def, 0);
4290 let hk = &bo4e["marktlokationEdifact"]["haushaltskunde"];
4291 assert_eq!(hk["code"].as_str(), Some("Z15"));
4292 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
4293 assert_eq!(
4294 hk["enum"].as_str(),
4295 Some("HAUSHALTSKUNDE"),
4296 "enum field should be present"
4297 );
4298 }
4299
4300 #[test]
4301 fn test_reverse_mapping_accepts_enriched_with_enum() {
4302 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4304 companion_fields.insert(
4305 "cci.2".to_string(),
4306 FieldMapping::Simple("haushaltskunde".to_string()),
4307 );
4308
4309 let def = MappingDefinition {
4310 meta: MappingMeta {
4311 entity: "Test".to_string(),
4312 bo4e_type: "Test".to_string(),
4313 companion_type: Some("TestEdifact".to_string()),
4314 source_group: "SG4".to_string(),
4315 source_path: None,
4316 discriminator: None,
4317 repeat_on_tag: None,
4318 },
4319 fields: IndexMap::new(),
4320 companion_fields: Some(companion_fields),
4321 complex_handlers: None,
4322 };
4323
4324 let engine = MappingEngine::from_definitions(vec![]);
4325
4326 let bo4e = serde_json::json!({
4327 "testEdifact": {
4328 "haushaltskunde": {
4329 "code": "Z15",
4330 "meaning": "Haushaltskunde",
4331 "enum": "HAUSHALTSKUNDE"
4332 }
4333 }
4334 });
4335 let instance = engine.map_reverse(&bo4e, &def);
4336 assert_eq!(instance.segments[0].elements[2], vec!["Z15"]);
4337 }
4338
4339 #[test]
4340 fn test_reverse_mapping_accepts_enriched_companion() {
4341 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4343 companion_fields.insert(
4344 "cci.2".to_string(),
4345 FieldMapping::Simple("haushaltskunde".to_string()),
4346 );
4347
4348 let def = MappingDefinition {
4349 meta: MappingMeta {
4350 entity: "Test".to_string(),
4351 bo4e_type: "Test".to_string(),
4352 companion_type: Some("TestEdifact".to_string()),
4353 source_group: "SG4".to_string(),
4354 source_path: None,
4355 discriminator: None,
4356 repeat_on_tag: None,
4357 },
4358 fields: IndexMap::new(),
4359 companion_fields: Some(companion_fields),
4360 complex_handlers: None,
4361 };
4362
4363 let engine = MappingEngine::from_definitions(vec![]);
4364
4365 let bo4e_plain = serde_json::json!({
4367 "testEdifact": {
4368 "haushaltskunde": "Z15"
4369 }
4370 });
4371 let instance_plain = engine.map_reverse(&bo4e_plain, &def);
4372 assert_eq!(instance_plain.segments[0].elements[2], vec!["Z15"]);
4373
4374 let bo4e_enriched = serde_json::json!({
4376 "testEdifact": {
4377 "haushaltskunde": {
4378 "code": "Z15",
4379 "meaning": "Haushaltskunde gem. EnWG"
4380 }
4381 }
4382 });
4383 let instance_enriched = engine.map_reverse(&bo4e_enriched, &def);
4384 assert_eq!(instance_enriched.segments[0].elements[2], vec!["Z15"]);
4385 }
4386
4387 #[test]
4388 fn test_resolve_child_relative_with_source_path() {
4389 let mut map: std::collections::HashMap<String, Vec<usize>> =
4390 std::collections::HashMap::new();
4391 map.insert("sg4.sg8_ze1".to_string(), vec![6]);
4392 map.insert("sg4.sg8_z98".to_string(), vec![0]);
4393
4394 assert_eq!(
4396 resolve_child_relative("SG8.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
4397 "SG8:6.SG10"
4398 );
4399
4400 assert_eq!(
4402 resolve_child_relative("SG8:3.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
4403 "SG8:3.SG10"
4404 );
4405
4406 assert_eq!(
4408 resolve_child_relative("SG8.SG10", Some("sg4.sg8_unknown.sg10"), &map, 0),
4409 "SG8.SG10"
4410 );
4411
4412 assert_eq!(
4414 resolve_child_relative("SG8.SG10", None, &map, 0),
4415 "SG8.SG10"
4416 );
4417
4418 assert_eq!(
4420 resolve_child_relative("SG8.SG9", Some("sg4.sg8_z98.sg9"), &map, 0),
4421 "SG8:0.SG9"
4422 );
4423
4424 map.insert("sg4.sg8_zf3".to_string(), vec![3, 4]);
4426 assert_eq!(
4427 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 0),
4428 "SG8:3.SG10"
4429 );
4430 assert_eq!(
4431 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 1),
4432 "SG8:4.SG10"
4433 );
4434 }
4435
4436 #[test]
4437 fn test_place_in_groups_returns_rep_index() {
4438 let mut groups: Vec<AssembledGroup> = Vec::new();
4439
4440 let instance = AssembledGroupInstance {
4442 segments: vec![],
4443 child_groups: vec![],
4444 skipped_segments: vec![],
4445 };
4446 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 0);
4447
4448 let instance = AssembledGroupInstance {
4450 segments: vec![],
4451 child_groups: vec![],
4452 skipped_segments: vec![],
4453 };
4454 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 1);
4455
4456 let instance = AssembledGroupInstance {
4458 segments: vec![],
4459 child_groups: vec![],
4460 skipped_segments: vec![],
4461 };
4462 assert_eq!(place_in_groups(&mut groups, "SG8:5", instance), 5);
4463 }
4464
4465 #[test]
4466 fn test_resolve_by_source_path() {
4467 use mig_assembly::assembler::*;
4468
4469 let tree = AssembledTree {
4471 segments: vec![],
4472 groups: vec![AssembledGroup {
4473 group_id: "SG4".to_string(),
4474 repetitions: vec![AssembledGroupInstance {
4475 segments: vec![],
4476 child_groups: vec![AssembledGroup {
4477 group_id: "SG8".to_string(),
4478 repetitions: vec![
4479 AssembledGroupInstance {
4480 segments: vec![AssembledSegment {
4481 tag: "SEQ".to_string(),
4482 elements: vec![vec!["Z98".to_string()]],
4483 }],
4484 child_groups: vec![AssembledGroup {
4485 group_id: "SG10".to_string(),
4486 repetitions: vec![AssembledGroupInstance {
4487 segments: vec![AssembledSegment {
4488 tag: "CCI".to_string(),
4489 elements: vec![vec![], vec![], vec!["ZB3".to_string()]],
4490 }],
4491 child_groups: vec![],
4492 skipped_segments: vec![],
4493 }],
4494 }],
4495 skipped_segments: vec![],
4496 },
4497 AssembledGroupInstance {
4498 segments: vec![AssembledSegment {
4499 tag: "SEQ".to_string(),
4500 elements: vec![vec!["ZD7".to_string()]],
4501 }],
4502 child_groups: vec![AssembledGroup {
4503 group_id: "SG10".to_string(),
4504 repetitions: vec![AssembledGroupInstance {
4505 segments: vec![AssembledSegment {
4506 tag: "CCI".to_string(),
4507 elements: vec![vec![], vec![], vec!["ZE6".to_string()]],
4508 }],
4509 child_groups: vec![],
4510 skipped_segments: vec![],
4511 }],
4512 }],
4513 skipped_segments: vec![],
4514 },
4515 ],
4516 }],
4517 skipped_segments: vec![],
4518 }],
4519 }],
4520 post_group_start: 0,
4521 inter_group_segments: std::collections::BTreeMap::new(),
4522 };
4523
4524 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_z98.sg10");
4526 assert!(inst.is_some());
4527 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
4528
4529 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zd7.sg10");
4531 assert!(inst.is_some());
4532 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZE6");
4533
4534 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zzz.sg10");
4536 assert!(inst.is_none());
4537
4538 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8.sg10");
4540 assert!(inst.is_some());
4541 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
4542 }
4543
4544 #[test]
4545 fn test_parse_source_path_part() {
4546 assert_eq!(parse_source_path_part("sg4"), ("sg4", None));
4547 assert_eq!(parse_source_path_part("sg8_z98"), ("sg8", Some("z98")));
4548 assert_eq!(parse_source_path_part("sg10"), ("sg10", None));
4549 assert_eq!(parse_source_path_part("sg12_z04"), ("sg12", Some("z04")));
4550 }
4551
4552 #[test]
4553 fn test_has_source_path_qualifiers() {
4554 assert!(has_source_path_qualifiers("sg4.sg8_z98.sg10"));
4555 assert!(has_source_path_qualifiers("sg4.sg8_ze1.sg9"));
4556 assert!(!has_source_path_qualifiers("sg4.sg6"));
4557 assert!(!has_source_path_qualifiers("sg4.sg8.sg10"));
4558 }
4559
4560 #[test]
4561 fn test_companion_dotted_path_forward() {
4562 use mig_assembly::assembler::*;
4563
4564 let tree = AssembledTree {
4566 segments: vec![],
4567 groups: vec![AssembledGroup {
4568 group_id: "SG4".to_string(),
4569 repetitions: vec![AssembledGroupInstance {
4570 segments: vec![],
4571 child_groups: vec![AssembledGroup {
4572 group_id: "SG8".to_string(),
4573 repetitions: vec![AssembledGroupInstance {
4574 segments: vec![],
4575 child_groups: vec![AssembledGroup {
4576 group_id: "SG10".to_string(),
4577 repetitions: vec![AssembledGroupInstance {
4578 segments: vec![AssembledSegment {
4579 tag: "CCI".to_string(),
4580 elements: vec![
4581 vec!["11XAB-1234".to_string()],
4582 vec!["305".to_string()],
4583 ],
4584 }],
4585 child_groups: vec![],
4586 skipped_segments: vec![],
4587 }],
4588 }],
4589 skipped_segments: vec![],
4590 }],
4591 }],
4592 skipped_segments: vec![],
4593 }],
4594 }],
4595 post_group_start: 0,
4596 inter_group_segments: std::collections::BTreeMap::new(),
4597 };
4598
4599 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4601 companion_fields.insert(
4602 "cci.0".to_string(),
4603 FieldMapping::Simple("bilanzkreis.id".to_string()),
4604 );
4605 companion_fields.insert(
4606 "cci.1".to_string(),
4607 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
4608 );
4609
4610 let def = MappingDefinition {
4611 meta: MappingMeta {
4612 entity: "Test".to_string(),
4613 bo4e_type: "Test".to_string(),
4614 companion_type: Some("TestEdifact".to_string()),
4615 source_group: "SG4.SG8.SG10".to_string(),
4616 source_path: Some("sg4.sg8_z01.sg10".to_string()),
4617 discriminator: None,
4618 repeat_on_tag: None,
4619 },
4620 fields: IndexMap::new(),
4621 companion_fields: Some(companion_fields),
4622 complex_handlers: None,
4623 };
4624
4625 let engine = MappingEngine::from_definitions(vec![]);
4626 let bo4e = engine.map_forward(&tree, &def, 0);
4627
4628 let companion = &bo4e["testEdifact"];
4630 assert!(
4631 companion.is_object(),
4632 "testEdifact should be an object, got: {companion}"
4633 );
4634 let bilanzkreis = &companion["bilanzkreis"];
4635 assert!(
4636 bilanzkreis.is_object(),
4637 "bilanzkreis should be a nested object, got: {bilanzkreis}"
4638 );
4639 assert_eq!(
4640 bilanzkreis["id"].as_str(),
4641 Some("11XAB-1234"),
4642 "bilanzkreis.id should be 11XAB-1234"
4643 );
4644 assert_eq!(
4645 bilanzkreis["codelist"].as_str(),
4646 Some("305"),
4647 "bilanzkreis.codelist should be 305"
4648 );
4649 }
4650
4651 #[test]
4652 fn test_companion_dotted_path_reverse() {
4653 let engine = MappingEngine::from_definitions(vec![]);
4655
4656 let companion_value = serde_json::json!({
4657 "bilanzkreis": {
4658 "id": "11XAB-1234",
4659 "codelist": "305"
4660 }
4661 });
4662
4663 assert_eq!(
4664 engine.populate_field(&companion_value, "bilanzkreis.id"),
4665 Some("11XAB-1234".to_string()),
4666 "dotted path bilanzkreis.id should resolve"
4667 );
4668 assert_eq!(
4669 engine.populate_field(&companion_value, "bilanzkreis.codelist"),
4670 Some("305".to_string()),
4671 "dotted path bilanzkreis.codelist should resolve"
4672 );
4673
4674 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4676 companion_fields.insert(
4677 "cci.0".to_string(),
4678 FieldMapping::Simple("bilanzkreis.id".to_string()),
4679 );
4680 companion_fields.insert(
4681 "cci.1".to_string(),
4682 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
4683 );
4684
4685 let def = MappingDefinition {
4686 meta: MappingMeta {
4687 entity: "Test".to_string(),
4688 bo4e_type: "Test".to_string(),
4689 companion_type: Some("TestEdifact".to_string()),
4690 source_group: "SG4.SG8.SG10".to_string(),
4691 source_path: Some("sg4.sg8_z01.sg10".to_string()),
4692 discriminator: None,
4693 repeat_on_tag: None,
4694 },
4695 fields: IndexMap::new(),
4696 companion_fields: Some(companion_fields),
4697 complex_handlers: None,
4698 };
4699
4700 let bo4e = serde_json::json!({
4701 "testEdifact": {
4702 "bilanzkreis": {
4703 "id": "11XAB-1234",
4704 "codelist": "305"
4705 }
4706 }
4707 });
4708
4709 let instance = engine.map_reverse(&bo4e, &def);
4710 assert_eq!(instance.segments.len(), 1, "should produce one CCI segment");
4711 let cci = &instance.segments[0];
4712 assert_eq!(cci.tag, "CCI");
4713 assert_eq!(
4714 cci.elements[0],
4715 vec!["11XAB-1234"],
4716 "element 0 should contain bilanzkreis.id"
4717 );
4718 assert_eq!(
4719 cci.elements[1],
4720 vec!["305"],
4721 "element 1 should contain bilanzkreis.codelist"
4722 );
4723 }
4724
4725 #[test]
4726 fn test_when_filled_injects_when_field_present() {
4727 let toml_str = r#"
4728[meta]
4729entity = "Test"
4730bo4e_type = "Test"
4731companion_type = "TestEdifact"
4732source_group = "SG4.SG8.SG10"
4733
4734[fields]
4735
4736[companion_fields]
4737"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmalCode"] }
4738"cav.0.0" = "merkmalCode"
4739"#;
4740 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
4741
4742 let bo4e_with = serde_json::json!({
4744 "testEdifact": { "merkmalCode": "ZA7" }
4745 });
4746 let engine = MappingEngine::new_empty();
4747 let instance = engine.map_reverse(&bo4e_with, &def);
4748 let cci = instance
4749 .segments
4750 .iter()
4751 .find(|s| s.tag == "CCI")
4752 .expect("CCI should exist");
4753 assert_eq!(cci.elements[0][0], "Z83");
4754
4755 let bo4e_without = serde_json::json!({
4757 "testEdifact": {}
4758 });
4759 let instance2 = engine.map_reverse(&bo4e_without, &def);
4760 let cci2 = instance2.segments.iter().find(|s| s.tag == "CCI");
4761 assert!(
4762 cci2.is_none(),
4763 "CCI should not be emitted when merkmalCode is absent"
4764 );
4765 }
4766
4767 #[test]
4768 fn test_when_filled_checks_core_and_companion() {
4769 let toml_str = r#"
4770[meta]
4771entity = "Test"
4772bo4e_type = "Test"
4773companion_type = "TestEdifact"
4774source_group = "SG4.SG5"
4775
4776[fields]
4777"loc.1.0" = "marktlokationsId"
4778
4779[companion_fields]
4780"loc.0.0" = { target = "", default = "Z16", when_filled = ["marktlokationsId"] }
4781"#;
4782 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
4783
4784 let bo4e_with = serde_json::json!({
4786 "marktlokationsId": "51234567890"
4787 });
4788 let engine = MappingEngine::new_empty();
4789 let instance = engine.map_reverse(&bo4e_with, &def);
4790 let loc = instance
4791 .segments
4792 .iter()
4793 .find(|s| s.tag == "LOC")
4794 .expect("LOC should exist");
4795 assert_eq!(loc.elements[0][0], "Z16");
4796 assert_eq!(loc.elements[1][0], "51234567890");
4797
4798 let bo4e_without = serde_json::json!({});
4800 let instance2 = engine.map_reverse(&bo4e_without, &def);
4801 let loc2 = instance2.segments.iter().find(|s| s.tag == "LOC");
4802 assert!(loc2.is_none());
4803 }
4804
4805 #[test]
4806 fn test_extract_all_from_instance_collects_all_qualifier_matches() {
4807 use mig_assembly::assembler::*;
4808
4809 let instance = AssembledGroupInstance {
4811 segments: vec![
4812 AssembledSegment {
4813 tag: "SEQ".to_string(),
4814 elements: vec![vec!["ZD6".to_string()]],
4815 },
4816 AssembledSegment {
4817 tag: "RFF".to_string(),
4818 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
4819 },
4820 AssembledSegment {
4821 tag: "RFF".to_string(),
4822 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
4823 },
4824 AssembledSegment {
4825 tag: "RFF".to_string(),
4826 elements: vec![vec!["Z34".to_string(), "REF_C".to_string()]],
4827 },
4828 AssembledSegment {
4829 tag: "RFF".to_string(),
4830 elements: vec![vec!["Z35".to_string(), "OTHER".to_string()]],
4831 },
4832 ],
4833 child_groups: vec![],
4834 skipped_segments: vec![],
4835 };
4836
4837 let all = MappingEngine::extract_all_from_instance(&instance, "rff[Z34,*].0.1");
4839 assert_eq!(all, vec!["REF_A", "REF_B", "REF_C"]);
4840
4841 let single = MappingEngine::extract_from_instance(&instance, "rff[Z34].0.1");
4843 assert_eq!(single, Some("REF_A".to_string()));
4844
4845 let second = MappingEngine::extract_from_instance(&instance, "rff[Z34,1].0.1");
4846 assert_eq!(second, Some("REF_B".to_string()));
4847 }
4848
4849 #[test]
4850 fn test_forward_wildcard_collect_produces_json_array() {
4851 use mig_assembly::assembler::*;
4852
4853 let instance = AssembledGroupInstance {
4854 segments: vec![
4855 AssembledSegment {
4856 tag: "SEQ".to_string(),
4857 elements: vec![vec!["ZD6".to_string()]],
4858 },
4859 AssembledSegment {
4860 tag: "RFF".to_string(),
4861 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
4862 },
4863 AssembledSegment {
4864 tag: "RFF".to_string(),
4865 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
4866 },
4867 ],
4868 child_groups: vec![],
4869 skipped_segments: vec![],
4870 };
4871
4872 let toml_str = r#"
4873[meta]
4874entity = "Test"
4875bo4e_type = "Test"
4876companion_type = "TestEdifact"
4877source_group = "SG4.SG8"
4878
4879[fields]
4880
4881[companion_fields]
4882"rff[Z34,*].0.1" = "messlokationsIdRefs"
4883"#;
4884 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
4885 let engine = MappingEngine::new_empty();
4886
4887 let mut result = serde_json::Map::new();
4888 engine.extract_companion_fields(&instance, &def, &mut result, false);
4889
4890 let companion = result.get("testEdifact").unwrap().as_object().unwrap();
4891 let refs = companion
4892 .get("messlokationsIdRefs")
4893 .unwrap()
4894 .as_array()
4895 .unwrap();
4896 assert_eq!(refs.len(), 2);
4897 assert_eq!(refs[0].as_str().unwrap(), "REF_A");
4898 assert_eq!(refs[1].as_str().unwrap(), "REF_B");
4899 }
4900
4901 #[test]
4902 fn test_reverse_json_array_produces_multiple_segments() {
4903 let toml_str = r#"
4904[meta]
4905entity = "Test"
4906bo4e_type = "Test"
4907companion_type = "TestEdifact"
4908source_group = "SG4.SG8"
4909
4910[fields]
4911
4912[companion_fields]
4913"seq.0.0" = { target = "", default = "ZD6" }
4914"rff[Z34,*].0.1" = "messlokationsIdRefs"
4915"#;
4916 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
4917 let engine = MappingEngine::new_empty();
4918
4919 let bo4e = serde_json::json!({
4920 "testEdifact": {
4921 "messlokationsIdRefs": ["REF_A", "REF_B", "REF_C"]
4922 }
4923 });
4924
4925 let instance = engine.map_reverse(&bo4e, &def);
4926
4927 let rff_segs: Vec<_> = instance
4929 .segments
4930 .iter()
4931 .filter(|s| s.tag == "RFF")
4932 .collect();
4933 assert_eq!(rff_segs.len(), 3);
4934 assert_eq!(rff_segs[0].elements[0][0], "Z34");
4935 assert_eq!(rff_segs[0].elements[0][1], "REF_A");
4936 assert_eq!(rff_segs[1].elements[0][0], "Z34");
4937 assert_eq!(rff_segs[1].elements[0][1], "REF_B");
4938 assert_eq!(rff_segs[2].elements[0][0], "Z34");
4939 assert_eq!(rff_segs[2].elements[0][1], "REF_C");
4940 }
4941
4942 #[test]
4943 fn test_when_filled_dotted_path() {
4944 let toml_str = r#"
4945[meta]
4946entity = "Test"
4947bo4e_type = "Test"
4948companion_type = "TestEdifact"
4949source_group = "SG4.SG8.SG10"
4950
4951[fields]
4952
4953[companion_fields]
4954"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmal.code"] }
4955"cav.0.0" = "merkmal.code"
4956"#;
4957 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
4958
4959 let bo4e = serde_json::json!({
4960 "testEdifact": { "merkmal": { "code": "ZA7" } }
4961 });
4962 let engine = MappingEngine::new_empty();
4963 let instance = engine.map_reverse(&bo4e, &def);
4964 let cci = instance
4965 .segments
4966 .iter()
4967 .find(|s| s.tag == "CCI")
4968 .expect("CCI should exist");
4969 assert_eq!(cci.elements[0][0], "Z83");
4970 }
4971
4972 #[test]
4973 fn test_also_target_forward_extracts_both_fields() {
4974 use mig_assembly::assembler::*;
4975
4976 let instance = AssembledGroupInstance {
4977 segments: vec![AssembledSegment {
4978 tag: "NAD".to_string(),
4979 elements: vec![vec!["Z47".to_string()], vec!["12345".to_string()]],
4980 }],
4981 child_groups: vec![],
4982 skipped_segments: vec![],
4983 };
4984
4985 let toml_str = r#"
4986[meta]
4987entity = "Geschaeftspartner"
4988bo4e_type = "Geschaeftspartner"
4989companion_type = "GeschaeftspartnerEdifact"
4990source_group = "SG4.SG12"
4991
4992[fields]
4993"nad.1.0" = "identifikation"
4994
4995[companion_fields."nad.0.0"]
4996target = "partnerrolle"
4997enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
4998also_target = "datenqualitaet"
4999also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
5000"#;
5001 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5002 let engine = MappingEngine::new_empty();
5003
5004 let mut result = serde_json::Map::new();
5005 engine.extract_companion_fields(&instance, &def, &mut result, false);
5006
5007 let companion = result
5008 .get("geschaeftspartnerEdifact")
5009 .unwrap()
5010 .as_object()
5011 .unwrap();
5012 assert_eq!(
5013 companion.get("partnerrolle").unwrap().as_str().unwrap(),
5014 "kundeDesLf"
5015 );
5016 assert_eq!(
5017 companion.get("datenqualitaet").unwrap().as_str().unwrap(),
5018 "erwartet"
5019 );
5020 }
5021
5022 #[test]
5023 fn test_also_target_reverse_joint_lookup() {
5024 let toml_str = r#"
5025[meta]
5026entity = "Geschaeftspartner"
5027bo4e_type = "Geschaeftspartner"
5028companion_type = "GeschaeftspartnerEdifact"
5029source_group = "SG4.SG12"
5030
5031[fields]
5032
5033[companion_fields."nad.0.0"]
5034target = "partnerrolle"
5035enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
5036also_target = "datenqualitaet"
5037also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
5038"#;
5039 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5040 let engine = MappingEngine::new_empty();
5041
5042 let bo4e = serde_json::json!({
5044 "geschaeftspartnerEdifact": {
5045 "partnerrolle": "kundeDesLf",
5046 "datenqualitaet": "erwartet"
5047 }
5048 });
5049 let instance = engine.map_reverse(&bo4e, &def);
5050 let nad = instance
5051 .segments
5052 .iter()
5053 .find(|s| s.tag == "NAD")
5054 .expect("NAD");
5055 assert_eq!(nad.elements[0][0], "Z47");
5056
5057 let bo4e2 = serde_json::json!({
5059 "geschaeftspartnerEdifact": {
5060 "partnerrolle": "kundeDesNb",
5061 "datenqualitaet": "imSystemVorhanden"
5062 }
5063 });
5064 let instance2 = engine.map_reverse(&bo4e2, &def);
5065 let nad2 = instance2
5066 .segments
5067 .iter()
5068 .find(|s| s.tag == "NAD")
5069 .expect("NAD");
5070 assert_eq!(nad2.elements[0][0], "Z52");
5071 }
5072
5073 #[test]
5074 fn test_also_target_mixed_codes_unpaired_skips_datenqualitaet() {
5075 use mig_assembly::assembler::*;
5076
5077 let toml_str = r#"
5079[meta]
5080entity = "Geschaeftspartner"
5081bo4e_type = "Geschaeftspartner"
5082companion_type = "GeschaeftspartnerEdifact"
5083source_group = "SG4.SG12"
5084
5085[fields]
5086
5087[companion_fields."nad.0.0"]
5088target = "partnerrolle"
5089enum_map = { "Z09" = "kundeDesLf", "Z47" = "kundeDesLf", "Z48" = "kundeDesLf" }
5090also_target = "datenqualitaet"
5091also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden" }
5092"#;
5093 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5094 let engine = MappingEngine::new_empty();
5095
5096 let instance_z09 = AssembledGroupInstance {
5098 segments: vec![AssembledSegment {
5099 tag: "NAD".to_string(),
5100 elements: vec![vec!["Z09".to_string()]],
5101 }],
5102 child_groups: vec![],
5103 skipped_segments: vec![],
5104 };
5105 let mut result = serde_json::Map::new();
5106 engine.extract_companion_fields(&instance_z09, &def, &mut result, false);
5107 let comp = result
5108 .get("geschaeftspartnerEdifact")
5109 .unwrap()
5110 .as_object()
5111 .unwrap();
5112 assert_eq!(
5113 comp.get("partnerrolle").unwrap().as_str().unwrap(),
5114 "kundeDesLf"
5115 );
5116 assert!(
5117 comp.get("datenqualitaet").is_none(),
5118 "Z09 should not set datenqualitaet"
5119 );
5120
5121 let bo4e = serde_json::json!({
5123 "geschaeftspartnerEdifact": { "partnerrolle": "kundeDesLf" }
5124 });
5125 let instance = engine.map_reverse(&bo4e, &def);
5126 let nad = instance
5127 .segments
5128 .iter()
5129 .find(|s| s.tag == "NAD")
5130 .expect("NAD");
5131 assert_eq!(nad.elements[0][0], "Z09");
5132
5133 let bo4e2 = serde_json::json!({
5135 "geschaeftspartnerEdifact": {
5136 "partnerrolle": "kundeDesLf",
5137 "datenqualitaet": "erwartet"
5138 }
5139 });
5140 let instance2 = engine.map_reverse(&bo4e2, &def);
5141 let nad2 = instance2
5142 .segments
5143 .iter()
5144 .find(|s| s.tag == "NAD")
5145 .expect("NAD");
5146 assert_eq!(nad2.elements[0][0], "Z47");
5147 }
5148}