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 transaction_group: Option<String>,
36}
37
38impl MappingEngine {
39 pub fn new_empty() -> Self {
41 Self {
42 definitions: Vec::new(),
43 segment_structure: None,
44 code_lookup: None,
45 transaction_group: None,
46 }
47 }
48
49 pub fn load(dir: &Path) -> Result<Self, MappingError> {
51 let mut definitions = Vec::new();
52
53 let mut entries: Vec<_> = std::fs::read_dir(dir)?.filter_map(|e| e.ok()).collect();
54 entries.sort_by_key(|e| e.file_name());
55
56 for entry in entries {
57 let path = entry.path();
58 if path.extension().map(|e| e == "toml").unwrap_or(false) {
59 let content = std::fs::read_to_string(&path)?;
60 let def: MappingDefinition =
61 toml::from_str(&content).map_err(|e| MappingError::TomlParse {
62 file: path.display().to_string(),
63 message: e.to_string(),
64 })?;
65 definitions.push(def);
66 }
67 }
68
69 Ok(Self {
70 definitions,
71 segment_structure: None,
72 code_lookup: None,
73 transaction_group: None,
74 })
75 }
76
77 pub fn load_split(
83 message_dir: &Path,
84 transaction_dir: &Path,
85 ) -> Result<(Self, Self), MappingError> {
86 let msg_engine = Self::load(message_dir)?;
87 let tx_engine = Self::load(transaction_dir)?;
88 Ok((msg_engine, tx_engine))
89 }
90
91 pub fn load_merged(dirs: &[&Path]) -> Result<Self, MappingError> {
96 let mut definitions = Vec::new();
97 for dir in dirs {
98 let engine = Self::load(dir)?;
99 definitions.extend(engine.definitions);
100 }
101 Ok(Self {
102 definitions,
103 segment_structure: None,
104 code_lookup: None,
105 transaction_group: None,
106 })
107 }
108
109 pub fn load_with_common(
118 common_dir: &Path,
119 pid_dir: &Path,
120 schema_index: &crate::pid_schema_index::PidSchemaIndex,
121 ) -> Result<Self, MappingError> {
122 let mut common_defs = Self::load(common_dir)?.definitions;
123
124 common_defs.retain(|d| {
126 d.meta
127 .source_path
128 .as_deref()
129 .map(|sp| schema_index.has_group(sp))
130 .unwrap_or(true)
131 });
132
133 let pid_defs = Self::load(pid_dir)?.definitions;
134
135 let normalize_sg = |sg: &str| -> String {
140 sg.split('.')
141 .map(|part| part.split(':').next().unwrap_or(part))
142 .collect::<Vec<_>>()
143 .join(".")
144 };
145 let pid_keys: HashSet<(String, Option<String>)> = pid_defs
146 .iter()
147 .flat_map(|d| {
148 let sg = normalize_sg(&d.meta.source_group);
149 let disc = d.meta.discriminator.clone();
150 let mut keys = vec![(sg.clone(), disc.clone())];
151 if let Some(ref disc_str) = disc {
153 if let Some(base) = disc_str.rsplit_once('#') {
154 if base.1.chars().all(|c| c.is_ascii_digit()) {
155 keys.push((sg, Some(base.0.to_string())));
156 }
157 }
158 }
159 keys
160 })
161 .collect();
162
163 common_defs.retain(|d| {
165 let key = (
166 normalize_sg(&d.meta.source_group),
167 d.meta.discriminator.clone(),
168 );
169 !pid_keys.contains(&key)
170 });
171
172 let mut definitions = common_defs;
174 definitions.extend(pid_defs);
175
176 Ok(Self {
177 definitions,
178 segment_structure: None,
179 code_lookup: None,
180 transaction_group: None,
181 })
182 }
183
184 pub fn load_common_only(
188 common_dir: &Path,
189 schema_index: &crate::pid_schema_index::PidSchemaIndex,
190 ) -> Result<Self, MappingError> {
191 let mut common_defs = Self::load(common_dir)?.definitions;
192
193 common_defs.retain(|d| {
195 d.meta
196 .source_path
197 .as_deref()
198 .map(|sp| schema_index.has_group(sp))
199 .unwrap_or(true)
200 });
201
202 Ok(Self {
203 definitions: common_defs,
204 segment_structure: None,
205 code_lookup: None,
206 transaction_group: None,
207 })
208 }
209
210 pub fn load_split_with_common(
215 message_dir: &Path,
216 common_dir: &Path,
217 transaction_dir: &Path,
218 schema_index: &crate::pid_schema_index::PidSchemaIndex,
219 ) -> Result<(Self, Self), MappingError> {
220 let msg_engine = Self::load(message_dir)?;
221 let tx_engine = Self::load_with_common(common_dir, transaction_dir, schema_index)?;
222 Ok((msg_engine, tx_engine))
223 }
224
225 pub fn from_definitions(definitions: Vec<MappingDefinition>) -> Self {
227 Self {
228 definitions,
229 segment_structure: None,
230 code_lookup: None,
231 transaction_group: None,
232 }
233 }
234
235 pub fn save_cached(&self, path: &Path) -> Result<(), MappingError> {
241 let encoded =
242 serde_json::to_vec(&self.definitions).map_err(|e| MappingError::CacheWrite {
243 path: path.display().to_string(),
244 message: e.to_string(),
245 })?;
246 if let Some(parent) = path.parent() {
247 std::fs::create_dir_all(parent)?;
248 }
249 std::fs::write(path, encoded)?;
250 Ok(())
251 }
252
253 pub fn load_cached_or_toml(cache_path: &Path, toml_dir: &Path) -> Result<Self, MappingError> {
258 if cache_path.exists() {
259 Self::load_cached(cache_path)
260 } else {
261 Self::load(toml_dir)
262 }
263 }
264
265 pub fn load_cached(path: &Path) -> Result<Self, MappingError> {
270 let bytes = std::fs::read(path)?;
271 let definitions: Vec<MappingDefinition> =
272 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
273 path: path.display().to_string(),
274 message: e.to_string(),
275 })?;
276 Ok(Self {
277 definitions,
278 segment_structure: None,
279 code_lookup: None,
280 transaction_group: None,
281 })
282 }
283
284 pub fn with_segment_structure(mut self, ss: SegmentStructure) -> Self {
289 self.segment_structure = Some(ss);
290 self
291 }
292
293 pub fn with_code_lookup(mut self, cl: crate::code_lookup::CodeLookup) -> Self {
298 self.code_lookup = Some(cl);
299 self
300 }
301
302 pub fn with_path_resolver(mut self, resolver: crate::path_resolver::PathResolver) -> Self {
308 for def in &mut self.definitions {
309 def.normalize_paths(&resolver);
310 }
311 self
312 }
313
314 pub fn with_transaction_group(mut self, tx: impl Into<String>) -> Self {
321 self.transaction_group = Some(tx.into());
322 self
323 }
324
325 pub fn definitions(&self) -> &[MappingDefinition] {
327 &self.definitions
328 }
329
330 pub fn definition_for_entity(&self, entity: &str) -> Option<&MappingDefinition> {
332 self.definitions.iter().find(|d| d.meta.entity == entity)
333 }
334
335 pub fn extract_field(
344 &self,
345 tree: &AssembledTree,
346 group_path: &str,
347 path: &str,
348 repetition: usize,
349 ) -> Option<String> {
350 let instance = Self::resolve_group_instance(tree, group_path, repetition)?;
351 Self::extract_from_instance(instance, path)
352 }
353
354 pub fn resolve_group_instance<'a>(
363 tree: &'a AssembledTree,
364 group_path: &str,
365 repetition: usize,
366 ) -> Option<&'a AssembledGroupInstance> {
367 let parts: Vec<&str> = group_path.split('.').collect();
368
369 let (first_id, first_rep) = parse_group_spec(parts[0]);
370 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
371
372 if parts.len() == 1 {
373 let rep = first_rep.unwrap_or(repetition);
375 return first_group.repetitions.get(rep);
376 }
377
378 let mut current_instance = first_group.repetitions.get(first_rep.unwrap_or(0))?;
381
382 for (i, part) in parts[1..].iter().enumerate() {
383 let (group_id, explicit_rep) = parse_group_spec(part);
384 let child_group = current_instance
385 .child_groups
386 .iter()
387 .find(|g| g.group_id == group_id)?;
388
389 if i == parts.len() - 2 {
390 let rep = explicit_rep.unwrap_or(repetition);
392 return child_group.repetitions.get(rep);
393 }
394 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
396 }
397
398 None
399 }
400
401 pub fn resolve_by_source_path<'a>(
409 tree: &'a AssembledTree,
410 source_path: &str,
411 ) -> Option<&'a AssembledGroupInstance> {
412 let parts: Vec<&str> = source_path.split('.').collect();
413 if parts.is_empty() {
414 return None;
415 }
416
417 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
418 let first_group = tree
419 .groups
420 .iter()
421 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))?;
422
423 let mut current_instance = if let Some(q) = first_qualifier {
424 find_rep_by_entry_qualifier(&first_group.repetitions, q)?
425 } else {
426 first_group.repetitions.first()?
427 };
428
429 if parts.len() == 1 {
430 return Some(current_instance);
431 }
432
433 for part in &parts[1..] {
434 let (group_id, qualifier) = parse_source_path_part(part);
435 let child_group = current_instance
436 .child_groups
437 .iter()
438 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))?;
439
440 current_instance = if let Some(q) = qualifier {
441 find_rep_by_entry_qualifier(&child_group.repetitions, q)?
442 } else {
443 child_group.repetitions.first()?
444 };
445 }
446
447 Some(current_instance)
448 }
449
450 pub fn resolve_all_by_source_path<'a>(
458 tree: &'a AssembledTree,
459 source_path: &str,
460 ) -> Vec<&'a AssembledGroupInstance> {
461 let parts: Vec<&str> = source_path.split('.').collect();
462 if parts.is_empty() {
463 return vec![];
464 }
465
466 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
468 let first_group = match tree
469 .groups
470 .iter()
471 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
472 {
473 Some(g) => g,
474 None => return vec![],
475 };
476
477 let mut current_instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
478 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
479 } else {
480 first_group.repetitions.iter().collect()
481 };
482
483 for part in &parts[1..] {
486 let (group_id, qualifier) = parse_source_path_part(part);
487 let mut next_instances = Vec::new();
488
489 for instance in ¤t_instances {
490 if let Some(child_group) = instance
491 .child_groups
492 .iter()
493 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
494 {
495 if let Some(q) = qualifier {
496 next_instances.extend(find_all_reps_by_entry_qualifier(
497 &child_group.repetitions,
498 q,
499 ));
500 } else {
501 next_instances.extend(child_group.repetitions.iter());
502 }
503 }
504 }
505
506 current_instances = next_instances;
507 }
508
509 current_instances
510 }
511
512 fn compute_child_indices(
525 tree: &AssembledTree,
526 source_path: &str,
527 indexed: &[(usize, &AssembledGroupInstance)],
528 ) -> Vec<usize> {
529 let parts: Vec<&str> = source_path.split('.').collect();
530 if parts.len() < 2 {
531 return vec![];
532 }
533 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
535 let first_group = match tree
536 .groups
537 .iter()
538 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
539 {
540 Some(g) => g,
541 None => return vec![],
542 };
543 let parent_reps: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
544 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
545 } else {
546 first_group.repetitions.iter().collect()
547 };
548 let (child_id, _child_qualifier) = parse_source_path_part(parts[parts.len() - 1]);
550 let mut result = Vec::new();
551 for (_, inst) in indexed {
552 let mut found = false;
554 for parent in &parent_reps {
555 if let Some(child_group) = parent
556 .child_groups
557 .iter()
558 .find(|g| g.group_id.eq_ignore_ascii_case(child_id))
559 {
560 if let Some(pos) = child_group
561 .repetitions
562 .iter()
563 .position(|r| std::ptr::eq(r, *inst))
564 {
565 result.push(pos);
566 found = true;
567 break;
568 }
569 }
570 }
571 if !found {
572 result.push(usize::MAX); }
574 }
575 result
576 }
577
578 pub fn resolve_all_with_parent_indices<'a>(
580 tree: &'a AssembledTree,
581 source_path: &str,
582 ) -> Vec<(usize, &'a AssembledGroupInstance)> {
583 let parts: Vec<&str> = source_path.split('.').collect();
584 if parts.is_empty() {
585 return vec![];
586 }
587
588 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
590 let first_group = match tree
591 .groups
592 .iter()
593 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
594 {
595 Some(g) => g,
596 None => return vec![],
597 };
598
599 if parts.len() == 1 {
601 let instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
602 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
603 } else {
604 first_group.repetitions.iter().collect()
605 };
606 return instances.into_iter().map(|i| (0, i)).collect();
607 }
608
609 let first_reps: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = first_qualifier {
614 let matching = find_all_reps_by_entry_qualifier(&first_group.repetitions, q);
615 let mut result = Vec::new();
616 for m in matching {
617 let idx = first_group
618 .repetitions
619 .iter()
620 .position(|r| std::ptr::eq(r, m))
621 .unwrap_or(0);
622 result.push((idx, m));
623 }
624 result
625 } else {
626 first_group.repetitions.iter().enumerate().collect()
627 };
628
629 let mut current: Vec<(usize, &AssembledGroupInstance)> = first_reps;
630 let remaining = &parts[1..];
631
632 for (level, part) in remaining.iter().enumerate() {
633 let is_leaf = level == remaining.len() - 1;
634 let (group_id, qualifier) = parse_source_path_part(part);
635 let mut next: Vec<(usize, &AssembledGroupInstance)> = Vec::new();
636
637 for (prev_parent_idx, instance) in ¤t {
638 if let Some(child_group) = instance
639 .child_groups
640 .iter()
641 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
642 {
643 let matching: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = qualifier
644 {
645 let filtered =
646 find_all_reps_by_entry_qualifier(&child_group.repetitions, q);
647 filtered
648 .into_iter()
649 .map(|m| {
650 let idx = child_group
651 .repetitions
652 .iter()
653 .position(|r| std::ptr::eq(r, m))
654 .unwrap_or(0);
655 (idx, m)
656 })
657 .collect()
658 } else {
659 child_group.repetitions.iter().enumerate().collect()
660 };
661
662 for (rep_idx, child_rep) in matching {
663 if is_leaf {
664 next.push((*prev_parent_idx, child_rep));
666 } else {
667 next.push((rep_idx, child_rep));
669 }
670 }
671 }
672 }
673
674 current = next;
675 }
676
677 current
678 }
679
680 pub fn extract_from_instance(instance: &AssembledGroupInstance, path: &str) -> Option<String> {
686 let parts: Vec<&str> = path.split('.').collect();
687 if parts.is_empty() {
688 return None;
689 }
690
691 let (segment_tag, qualifier, occurrence) = parse_tag_qualifier(parts[0]);
694
695 let segment = if let Some(q) = qualifier {
696 instance
697 .segments
698 .iter()
699 .filter(|s| {
700 s.tag.eq_ignore_ascii_case(&segment_tag)
701 && s.elements
702 .first()
703 .and_then(|e| e.first())
704 .map(|v| v.as_str())
705 == Some(q)
706 })
707 .nth(occurrence)?
708 } else {
709 instance
710 .segments
711 .iter()
712 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
713 .nth(occurrence)?
714 };
715
716 Self::resolve_field_path(segment, &parts[1..])
717 }
718
719 pub fn extract_all_from_instance(instance: &AssembledGroupInstance, path: &str) -> Vec<String> {
725 let parts: Vec<&str> = path.split('.').collect();
726 if parts.is_empty() {
727 return vec![];
728 }
729
730 let (segment_tag, qualifier, _) = parse_tag_qualifier(parts[0]);
731
732 let matching_segments: Vec<&AssembledSegment> = if let Some(q) = qualifier {
733 instance
734 .segments
735 .iter()
736 .filter(|s| {
737 s.tag.eq_ignore_ascii_case(&segment_tag)
738 && s.elements
739 .first()
740 .and_then(|e| e.first())
741 .map(|v| v.as_str())
742 == Some(q)
743 })
744 .collect()
745 } else {
746 instance
747 .segments
748 .iter()
749 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
750 .collect()
751 };
752
753 matching_segments
754 .into_iter()
755 .filter_map(|seg| Self::resolve_field_path(seg, &parts[1..]))
756 .collect()
757 }
758
759 pub fn map_forward(
768 &self,
769 tree: &AssembledTree,
770 def: &MappingDefinition,
771 repetition: usize,
772 ) -> serde_json::Value {
773 self.map_forward_inner(tree, def, repetition, true)
774 }
775
776 fn map_forward_inner(
778 &self,
779 tree: &AssembledTree,
780 def: &MappingDefinition,
781 repetition: usize,
782 enrich_codes: bool,
783 ) -> serde_json::Value {
784 let mut result = serde_json::Map::new();
785
786 if def.meta.source_group.is_empty() {
791 let mut all_root_segs = tree.segments.clone();
792 for segs in tree.inter_group_segments.values() {
793 all_root_segs.extend(segs.iter().cloned());
794 }
795 let root_instance = AssembledGroupInstance {
796 segments: all_root_segs,
797 child_groups: vec![],
798 entry_mig_number: None,
799 variant_mig_numbers: vec![],
800 skipped_segments: Vec::new(),
801 };
802 self.extract_fields_from_instance(&root_instance, def, &mut result, enrich_codes);
803 self.extract_companion_fields(&root_instance, def, &mut result, enrich_codes);
804 return serde_json::Value::Object(result);
805 }
806
807 let instance = if let Some(ref sp) = def.meta.source_path {
813 if has_source_path_qualifiers(sp) && !def.meta.source_group.contains(':') {
814 Self::resolve_by_source_path(tree, sp).or_else(|| {
815 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
816 })
817 } else {
818 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
819 }
820 } else {
821 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
822 };
823
824 if let Some(instance) = instance {
825 if let Some(ref tag) = def.meta.repeat_on_tag {
827 let matching: Vec<_> = instance
828 .segments
829 .iter()
830 .filter(|s| s.tag.eq_ignore_ascii_case(tag))
831 .collect();
832
833 if matching.len() > 1 {
834 let mut arr = Vec::new();
835 for seg in &matching {
836 let sub_instance = AssembledGroupInstance {
837 segments: vec![(*seg).clone()],
838 child_groups: vec![],
839 entry_mig_number: None,
840 variant_mig_numbers: vec![],
841 skipped_segments: Vec::new(),
842 };
843 let mut elem_result = serde_json::Map::new();
844 self.extract_fields_from_instance(
845 &sub_instance,
846 def,
847 &mut elem_result,
848 enrich_codes,
849 );
850 self.extract_companion_fields(
851 &sub_instance,
852 def,
853 &mut elem_result,
854 enrich_codes,
855 );
856 if !elem_result.is_empty() {
857 arr.push(serde_json::Value::Object(elem_result));
858 }
859 }
860 if !arr.is_empty() {
861 return serde_json::Value::Array(arr);
862 }
863 }
864 }
865
866 self.extract_fields_from_instance(instance, def, &mut result, enrich_codes);
867 self.extract_companion_fields(instance, def, &mut result, enrich_codes);
868 }
869
870 serde_json::Value::Object(result)
871 }
872
873 fn extract_companion_fields(
878 &self,
879 instance: &AssembledGroupInstance,
880 def: &MappingDefinition,
881 result: &mut serde_json::Map<String, serde_json::Value>,
882 enrich_codes: bool,
883 ) {
884 if let Some(ref companion_fields) = def.companion_fields {
885 let raw_key = def.meta.companion_type.as_deref().unwrap_or("_companion");
886 let companion_key = to_camel_case(raw_key);
887 let mut companion_result = serde_json::Map::new();
888
889 for (path, field_mapping) in companion_fields {
890 let (target, enum_map, also_target, also_enum_map) = match field_mapping {
891 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
892 FieldMapping::Structured(s) => (
893 s.target.as_str(),
894 s.enum_map.as_ref(),
895 s.also_target.as_deref(),
896 s.also_enum_map.as_ref(),
897 ),
898 FieldMapping::Nested(_) => continue,
899 };
900 if target.is_empty() {
901 continue;
902 }
903
904 if is_collect_all_path(path) {
906 let all = Self::extract_all_from_instance(instance, path);
907 if !all.is_empty() {
908 let arr: Vec<serde_json::Value> = all
909 .into_iter()
910 .map(|v| {
911 let mapped = if let Some(map) = enum_map {
912 map.get(&v).cloned().unwrap_or_else(|| v.clone())
913 } else {
914 v
915 };
916 serde_json::Value::String(mapped)
917 })
918 .collect();
919 set_nested_value_json(
920 &mut companion_result,
921 target,
922 serde_json::Value::Array(arr),
923 );
924 }
925 continue;
926 }
927
928 if let Some(val) = Self::extract_from_instance(instance, path) {
929 let mapped_val = if let Some(map) = enum_map {
930 map.get(&val).cloned().unwrap_or_else(|| val.clone())
931 } else {
932 val.clone()
933 };
934
935 if enrich_codes {
937 if let (Some(ref code_lookup), Some(ref source_path)) =
938 (&self.code_lookup, &def.meta.source_path)
939 {
940 let parts: Vec<&str> = path.split('.').collect();
941 let (seg_tag, _qualifier, _occ) = parse_tag_qualifier(parts[0]);
942 let (element_idx, component_idx) =
943 Self::parse_element_component(&parts[1..]);
944
945 if code_lookup.is_code_field(
946 source_path,
947 &seg_tag,
948 element_idx,
949 component_idx,
950 ) {
951 let enrichment = code_lookup.enrichment_for(
955 source_path,
956 &seg_tag,
957 element_idx,
958 component_idx,
959 &val,
960 );
961 let meaning = enrichment
962 .map(|e| serde_json::Value::String(e.meaning.clone()))
963 .unwrap_or(serde_json::Value::Null);
964
965 let mut obj = serde_json::Map::new();
966 obj.insert("code".into(), serde_json::json!(mapped_val));
967 obj.insert("meaning".into(), meaning);
968 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref())
969 {
970 obj.insert("enum".into(), serde_json::json!(enum_key));
971 }
972 let enriched = serde_json::Value::Object(obj);
973 set_nested_value_json(&mut companion_result, target, enriched);
974 continue;
975 }
976 }
977 }
978
979 set_nested_value(&mut companion_result, target, mapped_val);
980
981 if let (Some(at), Some(am)) = (also_target, also_enum_map) {
985 if let Some(also_mapped) = am.get(&val) {
986 set_nested_value(&mut companion_result, at, also_mapped.clone());
987 }
988 }
989 }
990 }
991
992 if !companion_result.is_empty() {
993 result.insert(
994 companion_key.to_string(),
995 serde_json::Value::Object(companion_result),
996 );
997 }
998 }
999 }
1000
1001 fn extract_fields_from_instance(
1006 &self,
1007 instance: &AssembledGroupInstance,
1008 def: &MappingDefinition,
1009 result: &mut serde_json::Map<String, serde_json::Value>,
1010 enrich_codes: bool,
1011 ) {
1012 for (path, field_mapping) in &def.fields {
1013 let (target, enum_map) = match field_mapping {
1014 FieldMapping::Simple(t) => (t.as_str(), None),
1015 FieldMapping::Structured(s) => (s.target.as_str(), s.enum_map.as_ref()),
1016 FieldMapping::Nested(_) => continue,
1017 };
1018 if target.is_empty() {
1019 continue;
1020 }
1021 if let Some(val) = Self::extract_from_instance(instance, path) {
1022 let mapped_val = if let Some(map) = enum_map {
1023 map.get(&val).cloned().unwrap_or_else(|| val.clone())
1024 } else {
1025 val.clone()
1026 };
1027
1028 if enrich_codes {
1030 if let (Some(ref code_lookup), Some(ref source_path)) =
1031 (&self.code_lookup, &def.meta.source_path)
1032 {
1033 let parts: Vec<&str> = path.split('.').collect();
1034 let (seg_tag, _qualifier, _occ) = parse_tag_qualifier(parts[0]);
1035 let (element_idx, component_idx) =
1036 Self::parse_element_component(&parts[1..]);
1037
1038 if code_lookup.is_code_field(
1039 source_path,
1040 &seg_tag,
1041 element_idx,
1042 component_idx,
1043 ) {
1044 let enrichment = code_lookup.enrichment_for(
1048 source_path,
1049 &seg_tag,
1050 element_idx,
1051 component_idx,
1052 &val,
1053 );
1054 let meaning = enrichment
1055 .map(|e| serde_json::Value::String(e.meaning.clone()))
1056 .unwrap_or(serde_json::Value::Null);
1057
1058 let mut obj = serde_json::Map::new();
1059 obj.insert("code".into(), serde_json::json!(mapped_val));
1060 obj.insert("meaning".into(), meaning);
1061 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
1062 obj.insert("enum".into(), serde_json::json!(enum_key));
1063 }
1064 let enriched = serde_json::Value::Object(obj);
1065 set_nested_value_json(result, target, enriched);
1066 continue;
1067 }
1068 }
1069 }
1070
1071 set_nested_value(result, target, mapped_val);
1072 }
1073 }
1074 }
1075
1076 pub fn map_forward_from_segments(
1082 &self,
1083 segments: &[OwnedSegment],
1084 def: &MappingDefinition,
1085 ) -> serde_json::Value {
1086 let assembled_segments: Vec<AssembledSegment> = segments
1087 .iter()
1088 .map(|s| AssembledSegment {
1089 tag: s.id.clone(),
1090 elements: s.elements.clone(),
1091 mig_number: None,
1092 })
1093 .collect();
1094
1095 let instance = AssembledGroupInstance {
1096 segments: assembled_segments,
1097 child_groups: vec![],
1098 entry_mig_number: None,
1099 variant_mig_numbers: vec![],
1100 skipped_segments: Vec::new(),
1101 };
1102
1103 let mut result = serde_json::Map::new();
1104 self.extract_fields_from_instance(&instance, def, &mut result, true);
1105 serde_json::Value::Object(result)
1106 }
1107
1108 pub fn map_reverse(
1121 &self,
1122 bo4e_value: &serde_json::Value,
1123 def: &MappingDefinition,
1124 ) -> AssembledGroupInstance {
1125 if def.meta.repeat_on_tag.is_some() {
1127 if let Some(arr) = bo4e_value.as_array() {
1128 let mut all_segments = Vec::new();
1129 for elem in arr {
1130 let sub = self.map_reverse_single(elem, def);
1131 all_segments.extend(sub.segments);
1132 }
1133 return AssembledGroupInstance {
1134 segments: all_segments,
1135 child_groups: vec![],
1136 entry_mig_number: None,
1137 variant_mig_numbers: vec![],
1138 skipped_segments: Vec::new(),
1139 };
1140 }
1141 }
1142 self.map_reverse_single(bo4e_value, def)
1143 }
1144
1145 fn map_reverse_single(
1146 &self,
1147 bo4e_value: &serde_json::Value,
1148 def: &MappingDefinition,
1149 ) -> AssembledGroupInstance {
1150 let mut field_values: Vec<(String, String, usize, usize, String)> =
1153 Vec::with_capacity(def.fields.len());
1154
1155 let mut has_real_data = false;
1162 let mut has_data_fields = false;
1163 let mut seg_has_data_field: HashSet<String> = HashSet::new();
1166 let mut seg_has_real_data: HashSet<String> = HashSet::new();
1167 let mut injected_qualifiers: HashSet<String> = HashSet::new();
1168
1169 for (path, field_mapping) in &def.fields {
1170 let (target, default, enum_map, when_filled) = match field_mapping {
1171 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
1172 FieldMapping::Structured(s) => (
1173 s.target.as_str(),
1174 s.default.as_ref(),
1175 s.enum_map.as_ref(),
1176 s.when_filled.as_ref(),
1177 ),
1178 FieldMapping::Nested(_) => continue,
1179 };
1180
1181 let parts: Vec<&str> = path.split('.').collect();
1182 if parts.len() < 2 {
1183 continue;
1184 }
1185
1186 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1187 let seg_key = parts[0].to_uppercase();
1190 let sub_path = &parts[1..];
1191
1192 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1194 let ci = if sub_path.len() > 1 {
1195 sub_path[1].parse::<usize>().unwrap_or(0)
1196 } else {
1197 0
1198 };
1199 (ei, ci)
1200 } else {
1201 match sub_path.len() {
1202 1 => (0, 0),
1203 2 => (1, 0),
1204 _ => continue,
1205 }
1206 };
1207
1208 let val = if target.is_empty() {
1210 match (default, when_filled) {
1211 (Some(d), Some(fields)) => {
1214 let companion_key_for_check =
1215 def.meta.companion_type.as_deref().map(to_camel_case);
1216 let companion_for_check = companion_key_for_check
1217 .as_ref()
1218 .and_then(|k| bo4e_value.get(k))
1219 .unwrap_or(&serde_json::Value::Null);
1220 let any_filled = fields.iter().any(|f| {
1221 self.populate_field(bo4e_value, f).is_some()
1222 || self.populate_field(companion_for_check, f).is_some()
1223 });
1224 if any_filled {
1225 has_real_data = true;
1229 Some(d.clone())
1230 } else {
1231 None
1232 }
1233 }
1234 (Some(d), None) => Some(d.clone()),
1236 (None, _) => None,
1237 }
1238 } else {
1239 has_data_fields = true;
1240 seg_has_data_field.insert(seg_key.clone());
1241 let bo4e_val = self.populate_field(bo4e_value, target);
1242 if bo4e_val.is_some() {
1243 has_real_data = true;
1244 seg_has_real_data.insert(seg_key.clone());
1245 }
1246 let mapped_val = match (bo4e_val, enum_map) {
1248 (Some(v), Some(map)) => {
1249 map.iter()
1251 .find(|(_, bo4e_v)| *bo4e_v == &v)
1252 .map(|(edifact_k, _)| edifact_k.clone())
1253 .or(Some(v))
1254 }
1255 (v, _) => v,
1256 };
1257 mapped_val.or_else(|| default.cloned())
1258 };
1259
1260 if let Some(val) = val {
1261 field_values.push((
1262 seg_key.clone(),
1263 seg_tag.clone(),
1264 element_idx,
1265 component_idx,
1266 val,
1267 ));
1268 }
1269
1270 if let Some(q) = qualifier {
1272 if injected_qualifiers.insert(seg_key.clone()) {
1273 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1274 }
1275 }
1276 }
1277
1278 if let Some(ref companion_fields) = def.companion_fields {
1282 let raw_key = def.meta.companion_type.as_deref().unwrap_or("_companion");
1283 let companion_key = to_camel_case(raw_key);
1284 let companion_value = bo4e_value.get(&companion_key).unwrap_or(bo4e_value);
1285
1286 for (path, field_mapping) in companion_fields {
1287 let (target, default, enum_map, when_filled, also_target, also_enum_map) =
1288 match field_mapping {
1289 FieldMapping::Simple(t) => (t.as_str(), None, None, None, None, None),
1290 FieldMapping::Structured(s) => (
1291 s.target.as_str(),
1292 s.default.as_ref(),
1293 s.enum_map.as_ref(),
1294 s.when_filled.as_ref(),
1295 s.also_target.as_deref(),
1296 s.also_enum_map.as_ref(),
1297 ),
1298 FieldMapping::Nested(_) => continue,
1299 };
1300
1301 let parts: Vec<&str> = path.split('.').collect();
1302 if parts.len() < 2 {
1303 continue;
1304 }
1305
1306 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1307 let seg_key = parts[0].to_uppercase();
1308 let sub_path = &parts[1..];
1309
1310 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1311 let ci = if sub_path.len() > 1 {
1312 sub_path[1].parse::<usize>().unwrap_or(0)
1313 } else {
1314 0
1315 };
1316 (ei, ci)
1317 } else {
1318 match sub_path.len() {
1319 1 => (0, 0),
1320 2 => (1, 0),
1321 _ => continue,
1322 }
1323 };
1324
1325 if is_collect_all_path(path) && !target.is_empty() {
1327 if let Some(arr) = self
1328 .populate_field_json(companion_value, target)
1329 .and_then(|v| v.as_array().cloned())
1330 {
1331 has_data_fields = true;
1332 if !arr.is_empty() {
1333 has_real_data = true;
1334 }
1335 for (idx, item) in arr.iter().enumerate() {
1336 if let Some(val_str) = item.as_str() {
1337 let mapped = if let Some(map) = enum_map {
1338 map.iter()
1339 .find(|(_, bo4e_v)| *bo4e_v == val_str)
1340 .map(|(edifact_k, _)| edifact_k.clone())
1341 .unwrap_or_else(|| val_str.to_string())
1342 } else {
1343 val_str.to_string()
1344 };
1345 let occ_key = if let Some(q) = qualifier {
1346 format!("{}[{},{}]", seg_tag, q, idx)
1347 } else {
1348 format!("{}[*,{}]", seg_tag, idx)
1349 };
1350 field_values.push((
1351 occ_key.clone(),
1352 seg_tag.clone(),
1353 element_idx,
1354 component_idx,
1355 mapped,
1356 ));
1357 if let Some(q) = qualifier {
1359 if injected_qualifiers.insert(occ_key.clone()) {
1360 field_values.push((
1361 occ_key,
1362 seg_tag.clone(),
1363 0,
1364 0,
1365 q.to_string(),
1366 ));
1367 }
1368 }
1369 }
1370 }
1371 }
1372 continue;
1373 }
1374
1375 let val = if target.is_empty() {
1376 match (default, when_filled) {
1377 (Some(d), Some(fields)) => {
1378 let any_filled = fields.iter().any(|f| {
1379 self.populate_field(bo4e_value, f).is_some()
1380 || self.populate_field(companion_value, f).is_some()
1381 });
1382 if any_filled {
1383 has_real_data = true;
1384 Some(d.clone())
1385 } else {
1386 None
1387 }
1388 }
1389 (Some(d), None) => Some(d.clone()),
1390 (None, _) => None,
1391 }
1392 } else {
1393 has_data_fields = true;
1394 seg_has_data_field.insert(seg_key.clone());
1395 let bo4e_val = self.populate_field(companion_value, target);
1396 if bo4e_val.is_some() {
1397 has_real_data = true;
1398 seg_has_real_data.insert(seg_key.clone());
1399 }
1400 let mapped_val = match (bo4e_val, enum_map) {
1401 (Some(v), Some(map)) => {
1402 if let (Some(at), Some(am)) = (also_target, also_enum_map) {
1403 let also_val = self.populate_field(companion_value, at);
1404 if let Some(av) = also_val.as_deref() {
1405 map.iter()
1407 .find(|(edifact_k, bo4e_v)| {
1408 *bo4e_v == &v
1409 && am.get(*edifact_k).is_some_and(|am_v| am_v == av)
1410 })
1411 .map(|(edifact_k, _)| edifact_k.clone())
1412 .or(Some(v))
1413 } else {
1414 map.iter()
1417 .find(|(edifact_k, bo4e_v)| {
1418 *bo4e_v == &v && !am.contains_key(*edifact_k)
1419 })
1420 .or_else(|| {
1421 map.iter().find(|(_, bo4e_v)| *bo4e_v == &v)
1423 })
1424 .map(|(edifact_k, _)| edifact_k.clone())
1425 .or(Some(v))
1426 }
1427 } else {
1428 map.iter()
1429 .find(|(_, bo4e_v)| *bo4e_v == &v)
1430 .map(|(edifact_k, _)| edifact_k.clone())
1431 .or(Some(v))
1432 }
1433 }
1434 (v, _) => v,
1435 };
1436 mapped_val.or_else(|| default.cloned())
1437 };
1438
1439 if let Some(val) = val {
1440 field_values.push((
1441 seg_key.clone(),
1442 seg_tag.clone(),
1443 element_idx,
1444 component_idx,
1445 val,
1446 ));
1447 }
1448
1449 if let Some(q) = qualifier {
1450 if injected_qualifiers.insert(seg_key.clone()) {
1451 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1452 }
1453 }
1454 }
1455 }
1456
1457 field_values.retain(|(seg_key, _, _, _, _)| {
1465 if !seg_key.contains('[') {
1466 return true; }
1468 !seg_has_data_field.contains(seg_key) || seg_has_real_data.contains(seg_key)
1469 });
1470
1471 if has_data_fields && !has_real_data {
1476 return AssembledGroupInstance {
1477 segments: vec![],
1478 child_groups: vec![],
1479 entry_mig_number: None,
1480 variant_mig_numbers: vec![],
1481 skipped_segments: Vec::new(),
1482 };
1483 }
1484
1485 let mut segments: Vec<AssembledSegment> = Vec::with_capacity(field_values.len());
1488 let mut seen_keys: HashMap<String, usize> = HashMap::new();
1489
1490 for (seg_key, seg_tag, element_idx, component_idx, val) in &field_values {
1491 let seg = if let Some(&pos) = seen_keys.get(seg_key) {
1492 &mut segments[pos]
1493 } else {
1494 let pos = segments.len();
1495 seen_keys.insert(seg_key.clone(), pos);
1496 segments.push(AssembledSegment {
1497 tag: seg_tag.clone(),
1498 elements: vec![],
1499 mig_number: None,
1500 });
1501 &mut segments[pos]
1502 };
1503
1504 while seg.elements.len() <= *element_idx {
1505 seg.elements.push(vec![]);
1506 }
1507 while seg.elements[*element_idx].len() <= *component_idx {
1508 seg.elements[*element_idx].push(String::new());
1509 }
1510 seg.elements[*element_idx][*component_idx] = val.clone();
1511 }
1512
1513 for seg in &mut segments {
1516 let last_populated = seg.elements.iter().rposition(|e| !e.is_empty());
1517 if let Some(last_idx) = last_populated {
1518 for i in 0..last_idx {
1519 if seg.elements[i].is_empty() {
1520 seg.elements[i] = vec![String::new()];
1521 }
1522 }
1523 }
1524 }
1525
1526 if let Some(ref ss) = self.segment_structure {
1528 for seg in &mut segments {
1529 if let Some(expected) = ss.element_count(&seg.tag) {
1530 while seg.elements.len() < expected {
1531 seg.elements.push(vec![String::new()]);
1532 }
1533 }
1534 }
1535 }
1536
1537 AssembledGroupInstance {
1538 segments,
1539 child_groups: vec![],
1540 entry_mig_number: None,
1541 variant_mig_numbers: vec![],
1542 skipped_segments: Vec::new(),
1543 }
1544 }
1545
1546 fn resolve_field_path(segment: &AssembledSegment, path: &[&str]) -> Option<String> {
1559 if path.is_empty() {
1560 return None;
1561 }
1562
1563 if let Ok(element_idx) = path[0].parse::<usize>() {
1565 let component_idx = if path.len() > 1 {
1566 path[1].parse::<usize>().unwrap_or(0)
1567 } else {
1568 0
1569 };
1570 return segment
1571 .elements
1572 .get(element_idx)?
1573 .get(component_idx)
1574 .filter(|v| !v.is_empty())
1575 .cloned();
1576 }
1577
1578 match path.len() {
1580 1 => segment
1581 .elements
1582 .first()?
1583 .first()
1584 .filter(|v| !v.is_empty())
1585 .cloned(),
1586 2 => segment
1587 .elements
1588 .get(1)?
1589 .first()
1590 .filter(|v| !v.is_empty())
1591 .cloned(),
1592 _ => None,
1593 }
1594 }
1595
1596 fn parse_element_component(parts: &[&str]) -> (usize, usize) {
1599 if parts.is_empty() {
1600 return (0, 0);
1601 }
1602 let element_idx = parts[0].parse::<usize>().unwrap_or(0);
1603 let component_idx = if parts.len() > 1 {
1604 parts[1].parse::<usize>().unwrap_or(0)
1605 } else {
1606 0
1607 };
1608 (element_idx, component_idx)
1609 }
1610
1611 pub fn populate_field(
1614 &self,
1615 bo4e_value: &serde_json::Value,
1616 target_field: &str,
1617 ) -> Option<String> {
1618 let mut current = bo4e_value;
1619 for part in target_field.split('.') {
1620 current = current.get(part)?;
1621 }
1622 if let Some(code) = current.get("code").and_then(|v| v.as_str()) {
1624 return Some(code.to_string());
1625 }
1626 current.as_str().map(|s| s.to_string())
1627 }
1628
1629 fn populate_field_json<'a>(
1632 &self,
1633 bo4e_value: &'a serde_json::Value,
1634 target_field: &str,
1635 ) -> Option<&'a serde_json::Value> {
1636 let mut current = bo4e_value;
1637 for part in target_field.split('.') {
1638 current = current.get(part)?;
1639 }
1640 Some(current)
1641 }
1642
1643 pub fn build_segment_from_bo4e(
1645 &self,
1646 bo4e_value: &serde_json::Value,
1647 segment_tag: &str,
1648 target_field: &str,
1649 ) -> AssembledSegment {
1650 let value = self.populate_field(bo4e_value, target_field);
1651 let elements = if let Some(val) = value {
1652 vec![vec![val]]
1653 } else {
1654 vec![]
1655 };
1656 AssembledSegment {
1657 tag: segment_tag.to_uppercase(),
1658 elements,
1659 mig_number: None,
1660 }
1661 }
1662
1663 pub fn resolve_repetition(
1672 tree: &AssembledTree,
1673 group_path: &str,
1674 discriminator: &str,
1675 ) -> Option<usize> {
1676 let (spec, expected) = discriminator.split_once('=')?;
1677 let parts: Vec<&str> = spec.split('.').collect();
1678 if parts.len() != 3 {
1679 return None;
1680 }
1681 let tag = parts[0];
1682 let element_idx: usize = parts[1].parse().ok()?;
1683 let component_idx: usize = parts[2].parse().ok()?;
1684
1685 let path_parts: Vec<&str> = group_path.split('.').collect();
1687
1688 let leaf_group = if path_parts.len() == 1 {
1689 let (group_id, _) = parse_group_spec(path_parts[0]);
1690 tree.groups.iter().find(|g| g.group_id == group_id)?
1691 } else {
1692 let parent_parts = &path_parts[..path_parts.len() - 1];
1694 let mut current_instance = {
1695 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1696 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
1697 first_group.repetitions.get(first_rep.unwrap_or(0))?
1698 };
1699 for part in &parent_parts[1..] {
1700 let (group_id, explicit_rep) = parse_group_spec(part);
1701 let child_group = current_instance
1702 .child_groups
1703 .iter()
1704 .find(|g| g.group_id == group_id)?;
1705 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
1706 }
1707 let (leaf_id, _) = parse_group_spec(path_parts.last()?);
1708 current_instance
1709 .child_groups
1710 .iter()
1711 .find(|g| g.group_id == leaf_id)?
1712 };
1713
1714 let expected_values: Vec<&str> = expected.split('|').collect();
1716 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1717 let matches = instance.segments.iter().any(|s| {
1718 s.tag.eq_ignore_ascii_case(tag)
1719 && s.elements
1720 .get(element_idx)
1721 .and_then(|e| e.get(component_idx))
1722 .map(|v| expected_values.iter().any(|ev| v == ev))
1723 .unwrap_or(false)
1724 });
1725 if matches {
1726 return Some(rep_idx);
1727 }
1728 }
1729
1730 None
1731 }
1732
1733 pub fn resolve_all_repetitions(
1738 tree: &AssembledTree,
1739 group_path: &str,
1740 discriminator: &str,
1741 ) -> Vec<usize> {
1742 let Some((spec, expected)) = discriminator.split_once('=') else {
1743 return Vec::new();
1744 };
1745 let parts: Vec<&str> = spec.split('.').collect();
1746 if parts.len() != 3 {
1747 return Vec::new();
1748 }
1749 let tag = parts[0];
1750 let element_idx: usize = match parts[1].parse() {
1751 Ok(v) => v,
1752 Err(_) => return Vec::new(),
1753 };
1754 let component_idx: usize = match parts[2].parse() {
1755 Ok(v) => v,
1756 Err(_) => return Vec::new(),
1757 };
1758
1759 let path_parts: Vec<&str> = group_path.split('.').collect();
1761
1762 let leaf_group = if path_parts.len() == 1 {
1763 let (group_id, _) = parse_group_spec(path_parts[0]);
1764 match tree.groups.iter().find(|g| g.group_id == group_id) {
1765 Some(g) => g,
1766 None => return Vec::new(),
1767 }
1768 } else {
1769 let parent_parts = &path_parts[..path_parts.len() - 1];
1770 let mut current_instance = {
1771 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1772 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
1773 Some(g) => g,
1774 None => return Vec::new(),
1775 };
1776 match first_group.repetitions.get(first_rep.unwrap_or(0)) {
1777 Some(i) => i,
1778 None => return Vec::new(),
1779 }
1780 };
1781 for part in &parent_parts[1..] {
1782 let (group_id, explicit_rep) = parse_group_spec(part);
1783 let child_group = match current_instance
1784 .child_groups
1785 .iter()
1786 .find(|g| g.group_id == group_id)
1787 {
1788 Some(g) => g,
1789 None => return Vec::new(),
1790 };
1791 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
1792 Some(i) => i,
1793 None => return Vec::new(),
1794 };
1795 }
1796 let (leaf_id, _) = match path_parts.last() {
1797 Some(p) => parse_group_spec(p),
1798 None => return Vec::new(),
1799 };
1800 match current_instance
1801 .child_groups
1802 .iter()
1803 .find(|g| g.group_id == leaf_id)
1804 {
1805 Some(g) => g,
1806 None => return Vec::new(),
1807 }
1808 };
1809
1810 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
1812
1813 let expected_values: Vec<&str> = expected_raw.split('|').collect();
1815 let mut result = Vec::new();
1816 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1817 let matches = instance.segments.iter().any(|s| {
1818 s.tag.eq_ignore_ascii_case(tag)
1819 && s.elements
1820 .get(element_idx)
1821 .and_then(|e| e.get(component_idx))
1822 .map(|v| expected_values.iter().any(|ev| v == ev))
1823 .unwrap_or(false)
1824 });
1825 if matches {
1826 result.push(rep_idx);
1827 }
1828 }
1829
1830 if let Some(occ) = occurrence {
1832 result.into_iter().nth(occ).into_iter().collect()
1833 } else {
1834 result
1835 }
1836 }
1837
1838 pub fn map_all_forward(&self, tree: &AssembledTree) -> serde_json::Value {
1860 self.map_all_forward_inner(tree, true).0
1861 }
1862
1863 pub fn map_all_forward_enriched(
1867 &self,
1868 tree: &AssembledTree,
1869 enrich_codes: bool,
1870 ) -> serde_json::Value {
1871 self.map_all_forward_inner(tree, enrich_codes).0
1872 }
1873
1874 fn map_all_forward_inner(
1881 &self,
1882 tree: &AssembledTree,
1883 enrich_codes: bool,
1884 ) -> (
1885 serde_json::Value,
1886 std::collections::HashMap<String, Vec<usize>>,
1887 ) {
1888 self.map_all_forward_inner_with_tx(tree, enrich_codes, self.transaction_group.as_deref())
1889 }
1890
1891 fn map_all_forward_inner_with_tx(
1895 &self,
1896 tree: &AssembledTree,
1897 enrich_codes: bool,
1898 tx_group_override: Option<&str>,
1899 ) -> (
1900 serde_json::Value,
1901 std::collections::HashMap<String, Vec<usize>>,
1902 ) {
1903 let mut result = serde_json::Map::new();
1904 let mut nesting_info: std::collections::HashMap<String, Vec<usize>> =
1905 std::collections::HashMap::new();
1906
1907 for def in &self.definitions {
1908 let entity = &def.meta.entity;
1909
1910 let bo4e = if let Some(ref disc) = def.meta.discriminator {
1911 let use_source_path = def
1916 .meta
1917 .source_path
1918 .as_ref()
1919 .is_some_and(|sp| has_source_path_qualifiers(sp));
1920 if use_source_path {
1921 let sp = def.meta.source_path.as_deref().unwrap();
1923 let all_instances = Self::resolve_all_by_source_path(tree, sp);
1924 let instances: Vec<_> = if let Some(matcher) = DiscriminatorMatcher::parse(disc)
1926 {
1927 matcher.filter_instances(all_instances)
1928 } else {
1929 all_instances
1930 };
1931 let extract = |instance: &AssembledGroupInstance| {
1932 let mut r = serde_json::Map::new();
1933 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
1934 self.extract_companion_fields(instance, def, &mut r, enrich_codes);
1935 serde_json::Value::Object(r)
1936 };
1937 match instances.len() {
1938 0 => None,
1939 1 => Some(extract(instances[0])),
1940 _ => Some(serde_json::Value::Array(
1941 instances.iter().map(|i| extract(i)).collect(),
1942 )),
1943 }
1944 } else {
1945 let reps = Self::resolve_all_repetitions(tree, &def.meta.source_group, disc);
1946 match reps.len() {
1947 0 => None,
1948 1 => Some(self.map_forward_inner(tree, def, reps[0], enrich_codes)),
1949 _ => Some(serde_json::Value::Array(
1950 reps.iter()
1951 .map(|&rep| self.map_forward_inner(tree, def, rep, enrich_codes))
1952 .collect(),
1953 )),
1954 }
1955 }
1956 } else if def.meta.source_group.is_empty() {
1957 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
1959 } else if def.meta.source_path.as_ref().is_some_and(|sp| {
1960 has_source_path_qualifiers(sp) || def.meta.source_group.contains('.')
1961 }) {
1962 let sp = def.meta.source_path.as_deref().unwrap();
1967 let mut indexed = Self::resolve_all_with_parent_indices(tree, sp);
1968
1969 if let Some(last_part) = sp.rsplit('.').next() {
1974 if !last_part.contains('_') {
1975 let base_prefix = if let Some(parent) = sp.rsplit_once('.') {
1979 format!("{}.", parent.0)
1980 } else {
1981 String::new()
1982 };
1983 let sibling_qualifiers: Vec<String> = self
1984 .definitions
1985 .iter()
1986 .filter_map(|d| d.meta.source_path.as_deref())
1987 .filter(|other_sp| {
1988 *other_sp != sp
1989 && other_sp.starts_with(&base_prefix)
1990 && other_sp.split('.').count() == sp.split('.').count()
1991 })
1992 .filter_map(|other_sp| {
1993 let other_last = other_sp.rsplit('.').next()?;
1994 let (base, q) = other_last.split_once('_')?;
1997 if base == last_part {
1998 Some(q.to_string())
1999 } else {
2000 None
2001 }
2002 })
2003 .collect();
2004
2005 if !sibling_qualifiers.is_empty() {
2006 indexed.retain(|(_, inst)| {
2007 let entry_qual = inst
2008 .segments
2009 .first()
2010 .and_then(|seg| seg.elements.first())
2011 .and_then(|el| el.first())
2012 .map(|v| v.to_lowercase());
2013 !entry_qual.is_some_and(|q| {
2016 sibling_qualifiers.iter().any(|sq| {
2017 sq.split('_').any(|part| part.eq_ignore_ascii_case(&q))
2018 })
2019 })
2020 });
2021 }
2022 }
2023 }
2024 let extract = |instance: &AssembledGroupInstance| {
2025 let mut r = serde_json::Map::new();
2026 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
2027 self.extract_companion_fields(instance, def, &mut r, enrich_codes);
2028 serde_json::Value::Object(r)
2029 };
2030 if def.meta.source_group.contains('.') && !indexed.is_empty() {
2035 if let Some(sp) = &def.meta.source_path {
2036 let parent_indices: Vec<usize> =
2037 indexed.iter().map(|(idx, _)| *idx).collect();
2038 nesting_info.entry(sp.clone()).or_insert(parent_indices);
2039
2040 let child_key = format!("{sp}#child");
2043 if let std::collections::hash_map::Entry::Vacant(e) =
2044 nesting_info.entry(child_key)
2045 {
2046 let child_indices: Vec<usize> =
2047 Self::compute_child_indices(tree, sp, &indexed);
2048 if !child_indices.is_empty() {
2049 e.insert(child_indices);
2050 }
2051 }
2052 }
2053 }
2054 match indexed.len() {
2055 0 => None,
2056 1 => Some(extract(indexed[0].1)),
2057 _ => Some(serde_json::Value::Array(
2058 indexed.iter().map(|(_, i)| extract(i)).collect(),
2059 )),
2060 }
2061 } else {
2062 let num_reps = Self::count_repetitions(tree, &def.meta.source_group);
2063 if num_reps <= 1 {
2064 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2065 } else {
2066 let mut items = Vec::with_capacity(num_reps);
2068 for rep in 0..num_reps {
2069 items.push(self.map_forward_inner(tree, def, rep, enrich_codes));
2070 }
2071 Some(serde_json::Value::Array(items))
2072 }
2073 };
2074
2075 if let Some(bo4e) = bo4e {
2076 let bo4e = inject_bo4e_metadata(bo4e, &def.meta.bo4e_type);
2077 let key = to_camel_case(entity);
2078 deep_merge_insert(&mut result, &key, bo4e);
2079 }
2080 }
2081
2082 nest_child_entities_in_result(
2087 &mut result,
2088 &self.definitions,
2089 &nesting_info,
2090 tx_group_override,
2091 );
2092
2093 (serde_json::Value::Object(result), nesting_info)
2094 }
2095
2096 pub fn map_all_reverse(
2105 &self,
2106 entities: &serde_json::Value,
2107 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2108 ) -> AssembledTree {
2109 let mut root_segments: Vec<AssembledSegment> = Vec::new();
2110 let mut groups: Vec<AssembledGroup> = Vec::new();
2111 let mut inferred_nesting: std::collections::HashMap<String, Vec<usize>> =
2114 std::collections::HashMap::new();
2115
2116 for def in &self.definitions {
2117 let entity_key = to_camel_case(&def.meta.entity);
2118
2119 let _extracted: Option<serde_json::Value>;
2122 let entity_value = if let Some(v) = entities.get(&entity_key) {
2123 _extracted = None;
2124 v
2125 } else if def.meta.source_group.contains('.') {
2126 match extract_child_from_parent_with_indices(entities, &self.definitions, def) {
2128 Some((v, parent_indices)) => {
2129 if let Some(sp) = def.meta.source_path.as_deref() {
2131 inferred_nesting
2132 .entry(sp.to_string())
2133 .or_insert(parent_indices);
2134 }
2135 _extracted = Some(v);
2136 _extracted.as_ref().unwrap()
2137 }
2138 None => continue,
2139 }
2140 } else {
2141 continue;
2142 };
2143
2144 let unwrapped: Option<serde_json::Value>;
2152 let entity_value = if entity_value.is_object() && !entity_value.is_array() {
2153 if let Some(disc_value) = def
2154 .meta
2155 .discriminator
2156 .as_deref()
2157 .and_then(|d| d.split_once('='))
2158 .map(|(_, v)| v)
2159 {
2160 if let Some(inner) = entity_value.get(disc_value) {
2162 let mut injected = inner.clone();
2163 if let Some(ref cf) = def.companion_fields {
2166 let disc_path = def
2167 .meta
2168 .discriminator
2169 .as_deref()
2170 .unwrap()
2171 .split_once('=')
2172 .unwrap()
2173 .0
2174 .to_lowercase();
2175 for (path, mapping) in cf {
2176 let cf_path = path.to_lowercase();
2180 let matches =
2181 cf_path == disc_path || format!("{}.0", cf_path) == disc_path;
2182 if matches {
2183 let target = match mapping {
2184 FieldMapping::Simple(t) => t.as_str(),
2185 FieldMapping::Structured(s) => s.target.as_str(),
2186 FieldMapping::Nested(_) => continue,
2187 };
2188 if !target.is_empty() {
2189 if let Some(obj) = injected.as_object_mut() {
2190 let entry = obj
2191 .entry(target.to_string())
2192 .or_insert(serde_json::Value::Null);
2193 if entry.is_null() {
2194 *entry = serde_json::Value::String(
2195 disc_value.to_string(),
2196 );
2197 }
2198 }
2199 }
2200 break;
2201 }
2202 }
2203 }
2204 unwrapped = Some(injected);
2205 unwrapped.as_ref().unwrap()
2206 } else {
2207 entity_value
2208 }
2209 } else if is_map_keyed_object(entity_value) {
2210 let map = entity_value.as_object().unwrap();
2215 let arr: Vec<serde_json::Value> = map
2216 .iter()
2217 .map(|(key, val)| {
2218 let mut item = val.clone();
2219 if let Some(obj) = item.as_object_mut() {
2222 if let Some(qualifier_field) = find_qualifier_companion_field(
2223 &self.definitions,
2224 &def.meta.entity,
2225 ) {
2226 let entry = obj
2227 .entry(qualifier_field)
2228 .or_insert(serde_json::Value::Null);
2229 if entry.is_null() {
2230 *entry = serde_json::Value::String(key.clone());
2231 }
2232 }
2233 }
2234 item
2235 })
2236 .collect();
2237 unwrapped = Some(serde_json::Value::Array(arr));
2238 unwrapped.as_ref().unwrap()
2239 } else {
2240 entity_value
2241 }
2242 } else {
2243 entity_value
2244 };
2245
2246 let leaf_group = def
2248 .meta
2249 .source_group
2250 .rsplit('.')
2251 .next()
2252 .unwrap_or(&def.meta.source_group);
2253
2254 if def.meta.source_group.is_empty() {
2255 let instance = self.map_reverse(entity_value, def);
2257 root_segments.extend(instance.segments);
2258 } else if entity_value.is_array() {
2259 let arr = entity_value.as_array().unwrap();
2261 let reps: Vec<_> = arr.iter().map(|item| self.map_reverse(item, def)).collect();
2262
2263 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2265 existing.repetitions.extend(reps);
2266 } else {
2267 groups.push(AssembledGroup {
2268 group_id: leaf_group.to_string(),
2269 repetitions: reps,
2270 });
2271 }
2272 } else {
2273 let instance = self.map_reverse(entity_value, def);
2275
2276 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2277 existing.repetitions.push(instance);
2278 } else {
2279 groups.push(AssembledGroup {
2280 group_id: leaf_group.to_string(),
2281 repetitions: vec![instance],
2282 });
2283 }
2284 }
2285 }
2286
2287 let nested_specs: Vec<(String, String)> = self
2293 .definitions
2294 .iter()
2295 .filter_map(|def| {
2296 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
2297 if parts.len() > 1 {
2298 Some((parts[0].to_string(), parts[parts.len() - 1].to_string()))
2299 } else {
2300 None
2301 }
2302 })
2303 .collect();
2304 for (parent_id, child_id) in &nested_specs {
2305 let has_parent = groups.iter().any(|g| g.group_id == *parent_id);
2307 let has_child = groups.iter().any(|g| g.group_id == *child_id);
2308 if has_parent && has_child {
2309 let child_idx = groups.iter().position(|g| g.group_id == *child_id).unwrap();
2310 let child_group = groups.remove(child_idx);
2311 let parent = groups
2312 .iter_mut()
2313 .find(|g| g.group_id == *parent_id)
2314 .unwrap();
2315 let child_source_path = self
2319 .definitions
2320 .iter()
2321 .find(|d| {
2322 let parts: Vec<&str> = d.meta.source_group.split('.').collect();
2323 parts.len() > 1 && parts[parts.len() - 1] == *child_id
2324 })
2325 .and_then(|d| d.meta.source_path.as_deref());
2326 let distribution = child_source_path.and_then(|key| {
2327 nesting_info
2328 .and_then(|ni| ni.get(key))
2329 .or_else(|| inferred_nesting.get(key))
2330 });
2331 for (i, child_rep) in child_group.repetitions.into_iter().enumerate() {
2332 let target_idx = distribution
2333 .and_then(|dist| dist.get(i))
2334 .copied()
2335 .unwrap_or(0);
2336
2337 if let Some(target_rep) = parent.repetitions.get_mut(target_idx) {
2338 if let Some(existing) = target_rep
2339 .child_groups
2340 .iter_mut()
2341 .find(|g| g.group_id == *child_id)
2342 {
2343 existing.repetitions.push(child_rep);
2344 } else {
2345 target_rep.child_groups.push(AssembledGroup {
2346 group_id: child_id.clone(),
2347 repetitions: vec![child_rep],
2348 });
2349 }
2350 }
2351 }
2352 }
2353 }
2354
2355 let post_group_start = root_segments.len();
2356 AssembledTree {
2357 segments: root_segments,
2358 groups,
2359 post_group_start,
2360 inter_group_segments: std::collections::BTreeMap::new(),
2361 }
2362 }
2363
2364 fn count_repetitions(tree: &AssembledTree, group_path: &str) -> usize {
2366 let parts: Vec<&str> = group_path.split('.').collect();
2367
2368 let (first_id, first_rep) = parse_group_spec(parts[0]);
2369 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2370 Some(g) => g,
2371 None => return 0,
2372 };
2373
2374 if parts.len() == 1 {
2375 return first_group.repetitions.len();
2376 }
2377
2378 let mut current_instance = match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2380 Some(i) => i,
2381 None => return 0,
2382 };
2383
2384 for (i, part) in parts[1..].iter().enumerate() {
2385 let (group_id, explicit_rep) = parse_group_spec(part);
2386 let child_group = match current_instance
2387 .child_groups
2388 .iter()
2389 .find(|g| g.group_id == group_id)
2390 {
2391 Some(g) => g,
2392 None => return 0,
2393 };
2394
2395 if i == parts.len() - 2 {
2396 return child_group.repetitions.len();
2398 }
2399 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2400 Some(i) => i,
2401 None => return 0,
2402 };
2403 }
2404
2405 0
2406 }
2407
2408 pub fn map_interchange(
2417 msg_engine: &MappingEngine,
2418 tx_engine: &MappingEngine,
2419 tree: &AssembledTree,
2420 transaction_group: &str,
2421 enrich_codes: bool,
2422 ) -> crate::model::MappedMessage {
2423 let (stammdaten, nesting_info) = msg_engine.map_all_forward_inner(tree, enrich_codes);
2425
2426 let transaktionen = tree
2428 .groups
2429 .iter()
2430 .find(|g| g.group_id == transaction_group)
2431 .map(|sg| {
2432 sg.repetitions
2433 .iter()
2434 .map(|instance| {
2435 let wrapped_tree = AssembledTree {
2438 segments: vec![],
2439 groups: vec![AssembledGroup {
2440 group_id: transaction_group.to_string(),
2441 repetitions: vec![instance.clone()],
2442 }],
2443 post_group_start: 0,
2444 inter_group_segments: std::collections::BTreeMap::new(),
2445 };
2446
2447 let (tx_result, tx_nesting) = tx_engine.map_all_forward_inner_with_tx(
2451 &wrapped_tree,
2452 enrich_codes,
2453 Some(transaction_group),
2454 );
2455
2456 crate::model::MappedTransaktion {
2457 stammdaten: tx_result,
2458 nesting_info: tx_nesting,
2459 }
2460 })
2461 .collect()
2462 })
2463 .unwrap_or_default();
2464
2465 crate::model::MappedMessage {
2466 stammdaten,
2467 transaktionen,
2468 nesting_info,
2469 }
2470 }
2471
2472 pub fn map_interchange_reverse(
2482 msg_engine: &MappingEngine,
2483 tx_engine: &MappingEngine,
2484 mapped: &crate::model::MappedMessage,
2485 transaction_group: &str,
2486 filtered_mig: Option<&MigSchema>,
2487 ) -> AssembledTree {
2488 let msg_tree = msg_engine.map_all_reverse(
2490 &mapped.stammdaten,
2491 if mapped.nesting_info.is_empty() {
2492 None
2493 } else {
2494 Some(&mapped.nesting_info)
2495 },
2496 );
2497
2498 let mut sg4_reps: Vec<AssembledGroupInstance> = Vec::new();
2500
2501 struct DefWithMeta<'a> {
2505 def: &'a MappingDefinition,
2506 relative: String,
2507 depth: usize,
2508 }
2509
2510 let mut sorted_defs: Vec<DefWithMeta> = tx_engine
2511 .definitions
2512 .iter()
2513 .map(|def| {
2514 let relative = strip_tx_group_prefix(&def.meta.source_group, transaction_group);
2515 let depth = if relative.is_empty() {
2516 0
2517 } else {
2518 relative.chars().filter(|c| *c == '.').count() + 1
2519 };
2520 DefWithMeta {
2521 def,
2522 relative,
2523 depth,
2524 }
2525 })
2526 .collect();
2527
2528 let mut parent_rep_map: std::collections::HashMap<String, usize> =
2532 std::collections::HashMap::new();
2533 for dm in &sorted_defs {
2534 if dm.depth >= 2 {
2535 let parts: Vec<&str> = dm.relative.split('.').collect();
2536 let (_, parent_rep) = parse_group_spec(parts[0]);
2537 if let Some(rep_idx) = parent_rep {
2538 if let Some(sp) = &dm.def.meta.source_path {
2539 if let Some((parent_path, _)) = sp.rsplit_once('.') {
2540 parent_rep_map
2541 .entry(parent_path.to_string())
2542 .or_insert(rep_idx);
2543 }
2544 }
2545 }
2546 }
2547 }
2548
2549 for dm in &mut sorted_defs {
2552 if dm.depth == 1 && !dm.relative.contains(':') {
2553 if let Some(sp) = &dm.def.meta.source_path {
2554 if let Some(rep_idx) = parent_rep_map.get(sp.as_str()) {
2555 dm.relative = format!("{}:{}", dm.relative, rep_idx);
2556 }
2557 }
2558 }
2559 }
2560
2561 if let Some(mig) = filtered_mig {
2568 let mig_order = build_reverse_mig_group_order(mig, transaction_group);
2569 sorted_defs.sort_by(|a, b| {
2570 a.depth.cmp(&b.depth).then_with(|| {
2571 let a_id = a.relative.split(':').next().unwrap_or(&a.relative);
2572 let b_id = b.relative.split(':').next().unwrap_or(&b.relative);
2573 let a_pos = variant_mig_position(a.def, a_id, &mig_order);
2575 let b_pos = variant_mig_position(b.def, b_id, &mig_order);
2576 a_pos.cmp(&b_pos).then(a.relative.cmp(&b.relative))
2577 })
2578 });
2579 } else {
2580 sorted_defs.sort_by(|a, b| a.depth.cmp(&b.depth).then(a.relative.cmp(&b.relative)));
2581 }
2582
2583 for tx in &mapped.transaktionen {
2584 let mut root_segs: Vec<AssembledSegment> = Vec::new();
2585 let mut child_groups: Vec<AssembledGroup> = Vec::new();
2586
2587 let mut source_path_to_rep: std::collections::HashMap<String, Vec<usize>> =
2592 std::collections::HashMap::new();
2593
2594 for dm in &sorted_defs {
2595 let entity_key = to_camel_case(&dm.def.meta.entity);
2598 let _tx_extracted: Option<serde_json::Value>;
2599 let bo4e_value = if let Some(v) = tx.stammdaten.get(&entity_key) {
2600 _tx_extracted = None;
2601 v
2602 } else if dm.def.meta.source_group.contains('.') {
2603 match extract_child_from_parent(&tx.stammdaten, &tx_engine.definitions, dm.def)
2604 {
2605 Some(v) => {
2606 _tx_extracted = Some(v);
2607 _tx_extracted.as_ref().unwrap()
2608 }
2609 None => continue,
2610 }
2611 } else {
2612 continue;
2613 };
2614
2615 let unwrapped_value: Option<serde_json::Value>;
2617 let bo4e_value = if bo4e_value.is_object() && !bo4e_value.is_array() {
2618 if let Some(disc_value) = dm
2619 .def
2620 .meta
2621 .discriminator
2622 .as_deref()
2623 .and_then(|d| d.split_once('='))
2624 .map(|(_, v)| v)
2625 {
2626 if let Some(inner) = bo4e_value.get(disc_value) {
2627 let mut injected = inner.clone();
2628 if let Some(ref cf) = dm.def.companion_fields {
2629 let disc_path = dm
2630 .def
2631 .meta
2632 .discriminator
2633 .as_deref()
2634 .unwrap()
2635 .split_once('=')
2636 .unwrap()
2637 .0
2638 .to_lowercase();
2639 for (path, mapping) in cf {
2640 let cf_path = path.to_lowercase();
2641 let matches = cf_path == disc_path
2642 || format!("{}.0", cf_path) == disc_path;
2643 if matches {
2644 let target = match mapping {
2645 FieldMapping::Simple(t) => t.as_str(),
2646 FieldMapping::Structured(s) => s.target.as_str(),
2647 FieldMapping::Nested(_) => continue,
2648 };
2649 if !target.is_empty() {
2650 if let Some(obj) = injected.as_object_mut() {
2651 obj.entry(target.to_string()).or_insert_with(
2652 || {
2653 serde_json::Value::String(
2654 disc_value.to_string(),
2655 )
2656 },
2657 );
2658 }
2659 }
2660 break;
2661 }
2662 }
2663 }
2664 unwrapped_value = Some(injected);
2665 unwrapped_value.as_ref().unwrap()
2666 } else {
2667 bo4e_value
2668 }
2669 } else if is_map_keyed_object(bo4e_value) {
2670 let map = bo4e_value.as_object().unwrap();
2671 let arr: Vec<serde_json::Value> = map
2672 .iter()
2673 .map(|(key, val)| {
2674 let mut item = val.clone();
2675 if let Some(obj) = item.as_object_mut() {
2676 if let Some(qualifier_field) = find_qualifier_companion_field(
2677 &tx_engine.definitions,
2678 &dm.def.meta.entity,
2679 ) {
2680 let entry = obj
2681 .entry(qualifier_field)
2682 .or_insert(serde_json::Value::Null);
2683 if entry.is_null() {
2684 *entry = serde_json::Value::String(key.clone());
2685 }
2686 }
2687 }
2688 item
2689 })
2690 .collect();
2691 unwrapped_value = Some(serde_json::Value::Array(arr));
2692 unwrapped_value.as_ref().unwrap()
2693 } else {
2694 bo4e_value
2695 }
2696 } else {
2697 bo4e_value
2698 };
2699
2700 let items: Vec<&serde_json::Value> = if bo4e_value.is_array() {
2704 bo4e_value.as_array().unwrap().iter().collect()
2705 } else {
2706 vec![bo4e_value]
2707 };
2708
2709 for (item_idx, item) in items.iter().enumerate() {
2710 let instance = tx_engine.map_reverse(item, dm.def);
2711
2712 if instance.segments.is_empty() && instance.child_groups.is_empty() {
2714 continue;
2715 }
2716
2717 if dm.relative.is_empty() {
2718 root_segs.extend(instance.segments);
2719 } else {
2720 let effective_relative = if dm.depth >= 2 {
2724 let rel = if items.len() > 1 {
2727 strip_all_rep_indices(&dm.relative)
2728 } else {
2729 dm.relative.clone()
2730 };
2731 let skip_nesting = dm
2738 .def
2739 .meta
2740 .source_path
2741 .as_ref()
2742 .and_then(|sp| sp.rsplit_once('.'))
2743 .and_then(|(parent_path, _)| source_path_to_rep.get(parent_path))
2744 .is_some_and(|reps| reps.len() == 1);
2745 let nesting_idx = if items.len() > 1 && !skip_nesting {
2746 dm.def
2747 .meta
2748 .source_path
2749 .as_ref()
2750 .and_then(|sp| tx.nesting_info.get(sp))
2751 .and_then(|dist| dist.get(item_idx))
2752 .copied()
2753 } else {
2754 None
2755 };
2756 if let Some(parent_rep) = nesting_idx {
2757 let parts: Vec<&str> = rel.split('.').collect();
2759 let parent_id = parts[0].split(':').next().unwrap_or(parts[0]);
2760 let rest = parts[1..].join(".");
2761 format!("{}:{}.{}", parent_id, parent_rep, rest)
2762 } else {
2763 resolve_child_relative(
2764 &rel,
2765 dm.def.meta.source_path.as_deref(),
2766 &source_path_to_rep,
2767 item_idx,
2768 )
2769 }
2770 } else if dm.depth == 1 {
2771 let child_key = dm
2774 .def
2775 .meta
2776 .source_path
2777 .as_ref()
2778 .map(|sp| format!("{sp}#child"));
2779 if let Some(child_indices) =
2780 child_key.as_ref().and_then(|ck| tx.nesting_info.get(ck))
2781 {
2782 if let Some(&target) = child_indices.get(item_idx) {
2783 if target != usize::MAX {
2784 let base =
2785 dm.relative.split(':').next().unwrap_or(&dm.relative);
2786 format!("{}:{}", base, target)
2787 } else {
2788 dm.relative.clone()
2789 }
2790 } else if items.len() > 1 && item_idx > 0 {
2791 strip_rep_index(&dm.relative)
2792 } else {
2793 dm.relative.clone()
2794 }
2795 } else if items.len() > 1 && item_idx > 0 {
2796 strip_rep_index(&dm.relative)
2797 } else {
2798 dm.relative.clone()
2799 }
2800 } else if items.len() > 1 && item_idx > 0 {
2801 strip_rep_index(&dm.relative)
2804 } else {
2805 dm.relative.clone()
2806 };
2807
2808 let rep_used =
2809 place_in_groups(&mut child_groups, &effective_relative, instance);
2810
2811 if dm.depth == 1 {
2813 if let Some(sp) = &dm.def.meta.source_path {
2814 source_path_to_rep
2815 .entry(sp.clone())
2816 .or_default()
2817 .push(rep_used);
2818 }
2819 }
2820 }
2821 }
2822 }
2823
2824 if let Some(mig) = filtered_mig {
2829 sort_variant_reps_by_mig(&mut child_groups, mig, transaction_group);
2830 }
2831
2832 sg4_reps.push(AssembledGroupInstance {
2833 segments: root_segs,
2834 child_groups,
2835 entry_mig_number: None,
2836 variant_mig_numbers: vec![],
2837 skipped_segments: Vec::new(),
2838 });
2839 }
2840
2841 let mut root_segments = Vec::new();
2848 let mut uns_segments = Vec::new();
2849 let mut uns_is_summary = false;
2850 let mut found_uns = false;
2851 for seg in msg_tree.segments {
2852 if seg.tag == "UNS" {
2853 uns_is_summary = seg
2855 .elements
2856 .first()
2857 .and_then(|el| el.first())
2858 .map(|v| v == "S")
2859 .unwrap_or(false);
2860 uns_segments.push(seg);
2861 found_uns = true;
2862 } else if found_uns {
2863 uns_segments.push(seg);
2865 } else {
2866 root_segments.push(seg);
2867 }
2868 }
2869
2870 let pre_group_count = root_segments.len();
2871 let mut all_groups = msg_tree.groups;
2872 let mut inter_group = msg_tree.inter_group_segments;
2873
2874 let sg_num = |id: &str| -> usize {
2876 id.strip_prefix("SG")
2877 .and_then(|n| n.parse::<usize>().ok())
2878 .unwrap_or(0)
2879 };
2880
2881 if !sg4_reps.is_empty() {
2882 if uns_is_summary {
2883 all_groups.push(AssembledGroup {
2885 group_id: transaction_group.to_string(),
2886 repetitions: sg4_reps,
2887 });
2888 if !uns_segments.is_empty() {
2889 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2894 let tx_num = sg_num(transaction_group);
2895 let uns_pos = all_groups
2896 .iter()
2897 .rposition(|g| sg_num(&g.group_id) <= tx_num)
2898 .map(|i| i + 1)
2899 .unwrap_or(all_groups.len());
2900 inter_group.insert(uns_pos, uns_segments);
2901 }
2902 } else {
2903 if !uns_segments.is_empty() {
2905 inter_group.insert(all_groups.len(), uns_segments);
2906 }
2907 all_groups.push(AssembledGroup {
2908 group_id: transaction_group.to_string(),
2909 repetitions: sg4_reps,
2910 });
2911 }
2912 } else if !uns_segments.is_empty() {
2913 if transaction_group.is_empty() {
2914 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2919 if uns_is_summary {
2920 inter_group.insert(all_groups.len(), uns_segments);
2921 } else {
2922 inter_group.insert(0, uns_segments);
2923 }
2924 } else {
2925 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2929 let tx_num = sg_num(transaction_group);
2930 let uns_pos = all_groups
2931 .iter()
2932 .rposition(|g| sg_num(&g.group_id) <= tx_num)
2933 .map(|i| i + 1)
2934 .unwrap_or(all_groups.len());
2935 inter_group.insert(uns_pos, uns_segments);
2936 }
2937 }
2938
2939 AssembledTree {
2940 segments: root_segments,
2941 groups: all_groups,
2942 post_group_start: pre_group_count,
2943 inter_group_segments: inter_group,
2944 }
2945 }
2946
2947 pub fn build_group_from_bo4e(
2949 &self,
2950 bo4e_value: &serde_json::Value,
2951 def: &MappingDefinition,
2952 ) -> AssembledGroup {
2953 let instance = self.map_reverse(bo4e_value, def);
2954 let leaf_group = def
2955 .meta
2956 .source_group
2957 .rsplit('.')
2958 .next()
2959 .unwrap_or(&def.meta.source_group);
2960
2961 AssembledGroup {
2962 group_id: leaf_group.to_string(),
2963 repetitions: vec![instance],
2964 }
2965 }
2966
2967 pub fn map_interchange_typed<M, T>(
2975 msg_engine: &MappingEngine,
2976 tx_engine: &MappingEngine,
2977 tree: &AssembledTree,
2978 tx_group: &str,
2979 enrich_codes: bool,
2980 nachrichtendaten: crate::model::Nachrichtendaten,
2981 interchangedaten: crate::model::Interchangedaten,
2982 ) -> Result<crate::model::Interchange<M, T>, serde_json::Error>
2983 where
2984 M: serde::de::DeserializeOwned,
2985 T: serde::de::DeserializeOwned,
2986 {
2987 let mapped = Self::map_interchange(msg_engine, tx_engine, tree, tx_group, enrich_codes);
2988 let nachricht = mapped.into_dynamic_nachricht(nachrichtendaten);
2989 let dynamic = crate::model::DynamicInterchange {
2990 interchangedaten,
2991 nachrichten: vec![nachricht],
2992 };
2993 let value = serde_json::to_value(&dynamic)?;
2994 serde_json::from_value(value)
2995 }
2996
2997 pub fn map_interchange_reverse_typed<M, T>(
3004 msg_engine: &MappingEngine,
3005 tx_engine: &MappingEngine,
3006 nachricht: &crate::model::Nachricht<M, T>,
3007 tx_group: &str,
3008 ) -> Result<AssembledTree, serde_json::Error>
3009 where
3010 M: serde::Serialize,
3011 T: serde::Serialize,
3012 {
3013 let stammdaten = serde_json::to_value(&nachricht.stammdaten)?;
3014 let transaktionen: Vec<crate::model::MappedTransaktion> = nachricht
3015 .transaktionen
3016 .iter()
3017 .map(|t| {
3018 Ok(crate::model::MappedTransaktion {
3019 stammdaten: serde_json::to_value(t)?,
3020 nesting_info: Default::default(),
3021 })
3022 })
3023 .collect::<Result<Vec<_>, serde_json::Error>>()?;
3024 let mapped = crate::model::MappedMessage {
3025 stammdaten,
3026 transaktionen,
3027 nesting_info: Default::default(),
3028 };
3029 Ok(Self::map_interchange_reverse(
3030 msg_engine, tx_engine, &mapped, tx_group, None,
3031 ))
3032 }
3033}
3034
3035fn parse_source_path_part(part: &str) -> (&str, Option<&str>) {
3042 if let Some(pos) = part.find('_') {
3046 let group = &part[..pos];
3047 let qualifier = &part[pos + 1..];
3048 if !qualifier.is_empty() {
3049 return (group, Some(qualifier));
3050 }
3051 }
3052 (part, None)
3053}
3054
3055fn build_reverse_mig_group_order(mig: &MigSchema, tx_group_id: &str) -> HashMap<String, usize> {
3063 let mut order = HashMap::new();
3064 if let Some(tg) = mig.segment_groups.iter().find(|g| g.id == tx_group_id) {
3065 for (i, nested) in tg.nested_groups.iter().enumerate() {
3066 if let Some(ref vc) = nested.variant_code {
3068 let variant_key = format!("{}_{}", nested.id, vc.to_uppercase());
3069 order.insert(variant_key, i);
3070 }
3071 order.entry(nested.id.clone()).or_insert(i);
3073 }
3074 }
3075 order
3076}
3077
3078fn variant_mig_position(
3084 def: &MappingDefinition,
3085 base_group_id: &str,
3086 mig_order: &HashMap<String, usize>,
3087) -> usize {
3088 if let Some(ref sp) = def.meta.source_path {
3091 let base_lower = base_group_id.to_lowercase();
3093 for part in sp.split('.') {
3094 if part.starts_with(&base_lower)
3095 || part.starts_with(base_group_id.to_lowercase().as_str())
3096 {
3097 if let Some(underscore_pos) = part.find('_') {
3099 let qualifier = &part[underscore_pos + 1..];
3100 let variant_key = format!("{}_{}", base_group_id, qualifier.to_uppercase());
3101 if let Some(&pos) = mig_order.get(&variant_key) {
3102 return pos;
3103 }
3104 }
3105 }
3106 }
3107 }
3108 mig_order.get(base_group_id).copied().unwrap_or(usize::MAX)
3110}
3111
3112fn find_rep_by_entry_qualifier<'a>(
3117 reps: &'a [AssembledGroupInstance],
3118 qualifier: &str,
3119) -> Option<&'a AssembledGroupInstance> {
3120 let parts: Vec<&str> = qualifier.split('_').collect();
3122 reps.iter().find(|inst| {
3123 inst.segments.first().is_some_and(|seg| {
3124 seg.elements
3125 .first()
3126 .and_then(|e| e.first())
3127 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3128 })
3129 })
3130}
3131
3132fn find_all_reps_by_entry_qualifier<'a>(
3134 reps: &'a [AssembledGroupInstance],
3135 qualifier: &str,
3136) -> Vec<&'a AssembledGroupInstance> {
3137 let parts: Vec<&str> = qualifier.split('_').collect();
3139 reps.iter()
3140 .filter(|inst| {
3141 inst.segments.first().is_some_and(|seg| {
3142 seg.elements
3143 .first()
3144 .and_then(|e| e.first())
3145 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3146 })
3147 })
3148 .collect()
3149}
3150
3151fn has_source_path_qualifiers(source_path: &str) -> bool {
3153 source_path.split('.').any(|part| {
3154 if let Some(pos) = part.find('_') {
3155 pos < part.len() - 1
3156 } else {
3157 false
3158 }
3159 })
3160}
3161
3162fn parse_group_spec(part: &str) -> (&str, Option<usize>) {
3163 if let Some(colon_pos) = part.find(':') {
3164 let id = &part[..colon_pos];
3165 let rep = part[colon_pos + 1..].parse::<usize>().ok();
3166 (id, rep)
3167 } else {
3168 (part, None)
3169 }
3170}
3171
3172fn strip_tx_group_prefix(source_group: &str, tx_group: &str) -> String {
3178 if source_group == tx_group || source_group.is_empty() {
3179 String::new()
3180 } else if let Some(rest) = source_group.strip_prefix(tx_group) {
3181 rest.strip_prefix('.').unwrap_or(rest).to_string()
3182 } else {
3183 source_group.to_string()
3184 }
3185}
3186
3187fn place_in_groups(
3195 groups: &mut Vec<AssembledGroup>,
3196 relative_path: &str,
3197 instance: AssembledGroupInstance,
3198) -> usize {
3199 let parts: Vec<&str> = relative_path.split('.').collect();
3200
3201 if parts.len() == 1 {
3202 let (id, rep) = parse_group_spec(parts[0]);
3204
3205 let group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == id) {
3207 g
3208 } else {
3209 groups.push(AssembledGroup {
3210 group_id: id.to_string(),
3211 repetitions: vec![],
3212 });
3213 groups.last_mut().unwrap()
3214 };
3215
3216 if let Some(rep_idx) = rep {
3217 while group.repetitions.len() <= rep_idx {
3219 group.repetitions.push(AssembledGroupInstance {
3220 segments: vec![],
3221 child_groups: vec![],
3222 entry_mig_number: None,
3223 variant_mig_numbers: vec![],
3224 skipped_segments: Vec::new(),
3225 });
3226 }
3227 group.repetitions[rep_idx]
3228 .segments
3229 .extend(instance.segments);
3230 group.repetitions[rep_idx]
3231 .child_groups
3232 .extend(instance.child_groups);
3233 rep_idx
3234 } else {
3235 let pos = group.repetitions.len();
3237 group.repetitions.push(instance);
3238 pos
3239 }
3240 } else {
3241 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3243 let rep_idx = parent_rep.unwrap_or(0);
3244
3245 let parent_group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == parent_id) {
3247 g
3248 } else {
3249 groups.push(AssembledGroup {
3250 group_id: parent_id.to_string(),
3251 repetitions: vec![],
3252 });
3253 groups.last_mut().unwrap()
3254 };
3255
3256 while parent_group.repetitions.len() <= rep_idx {
3258 parent_group.repetitions.push(AssembledGroupInstance {
3259 segments: vec![],
3260 child_groups: vec![],
3261 entry_mig_number: None,
3262 variant_mig_numbers: vec![],
3263 skipped_segments: Vec::new(),
3264 });
3265 }
3266
3267 let remaining = parts[1..].join(".");
3268 place_in_groups(
3269 &mut parent_group.repetitions[rep_idx].child_groups,
3270 &remaining,
3271 instance,
3272 );
3273 rep_idx
3274 }
3275}
3276
3277fn resolve_child_relative(
3289 relative: &str,
3290 source_path: Option<&str>,
3291 source_path_to_rep: &std::collections::HashMap<String, Vec<usize>>,
3292 item_idx: usize,
3293) -> String {
3294 let parts: Vec<&str> = relative.split('.').collect();
3295 if parts.is_empty() {
3296 return relative.to_string();
3297 }
3298
3299 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3301 if parent_rep.is_some() {
3302 return relative.to_string();
3303 }
3304
3305 if let Some(sp) = source_path {
3307 if let Some((parent_path, _child)) = sp.rsplit_once('.') {
3308 if let Some(rep_indices) = source_path_to_rep.get(parent_path) {
3310 let rep_idx = rep_indices
3311 .get(item_idx)
3312 .or_else(|| rep_indices.last())
3313 .copied()
3314 .unwrap_or(0);
3315 let rest = parts[1..].join(".");
3316 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3317 }
3318 let prefix = format!("{}_", parent_path);
3326 let mut unioned: Vec<usize> = source_path_to_rep
3327 .iter()
3328 .filter(|(k, _)| k.starts_with(&prefix))
3329 .flat_map(|(_, v)| v.iter().copied())
3330 .collect();
3331 if !unioned.is_empty() {
3332 unioned.sort_unstable();
3333 unioned.dedup();
3334 let rep_idx = unioned
3335 .get(item_idx)
3336 .or_else(|| unioned.last())
3337 .copied()
3338 .unwrap_or(0);
3339 let rest = parts[1..].join(".");
3340 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3341 }
3342 }
3343 }
3344
3345 relative.to_string()
3347}
3348
3349struct DiscriminatorMatcher<'a> {
3356 tag: &'a str,
3357 element_idx: usize,
3358 component_idx: usize,
3359 expected_values: Vec<&'a str>,
3360 occurrence: Option<usize>,
3362}
3363
3364impl<'a> DiscriminatorMatcher<'a> {
3365 fn parse(disc: &'a str) -> Option<Self> {
3366 let (spec, expected) = disc.split_once('=')?;
3367 let parts: Vec<&str> = spec.split('.').collect();
3368 if parts.len() != 3 {
3369 return None;
3370 }
3371 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
3372 Some(Self {
3373 tag: parts[0],
3374 element_idx: parts[1].parse().ok()?,
3375 component_idx: parts[2].parse().ok()?,
3376 expected_values: expected_raw.split('|').collect(),
3377 occurrence,
3378 })
3379 }
3380
3381 fn matches(&self, instance: &AssembledGroupInstance) -> bool {
3382 instance.segments.iter().any(|s| {
3383 s.tag.eq_ignore_ascii_case(self.tag)
3384 && s.elements
3385 .get(self.element_idx)
3386 .and_then(|e| e.get(self.component_idx))
3387 .map(|v| self.expected_values.iter().any(|ev| v == ev))
3388 .unwrap_or(false)
3389 })
3390 }
3391
3392 fn filter_instances<'b>(
3394 &self,
3395 instances: Vec<&'b AssembledGroupInstance>,
3396 ) -> Vec<&'b AssembledGroupInstance> {
3397 let matching: Vec<_> = instances
3398 .into_iter()
3399 .filter(|inst| self.matches(inst))
3400 .collect();
3401 if let Some(occ) = self.occurrence {
3402 matching.into_iter().nth(occ).into_iter().collect()
3403 } else {
3404 matching
3405 }
3406 }
3407}
3408
3409fn parse_discriminator_occurrence(expected: &str) -> (&str, Option<usize>) {
3415 if let Some(hash_pos) = expected.rfind('#') {
3416 if let Ok(occ) = expected[hash_pos + 1..].parse::<usize>() {
3417 return (&expected[..hash_pos], Some(occ));
3418 }
3419 }
3420 (expected, None)
3421}
3422
3423fn strip_rep_index(relative: &str) -> String {
3427 let (id, _) = parse_group_spec(relative);
3428 id.to_string()
3429}
3430
3431fn strip_all_rep_indices(relative: &str) -> String {
3436 relative
3437 .split('.')
3438 .map(|part| {
3439 let (id, _) = parse_group_spec(part);
3440 id
3441 })
3442 .collect::<Vec<_>>()
3443 .join(".")
3444}
3445
3446fn is_collect_all_path(path: &str) -> bool {
3451 let tag_part = path.split('.').next().unwrap_or("");
3452 if let Some(bracket_start) = tag_part.find('[') {
3453 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3454 if let Some(comma_pos) = inner.find(',') {
3455 let qualifier = &inner[..comma_pos];
3456 let occ = &inner[comma_pos + 1..];
3457 qualifier != "*" && occ == "*"
3459 } else {
3460 false
3461 }
3462 } else {
3463 false
3464 }
3465}
3466
3467fn parse_tag_qualifier(tag_part: &str) -> (String, Option<&str>, usize) {
3474 if let Some(bracket_start) = tag_part.find('[') {
3475 let tag = tag_part[..bracket_start].to_uppercase();
3476 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3477 if let Some(comma_pos) = inner.find(',') {
3478 let qualifier = &inner[..comma_pos];
3479 let index = inner[comma_pos + 1..].parse::<usize>().unwrap_or(0);
3480 if qualifier == "*" {
3482 (tag, None, index)
3483 } else {
3484 (tag, Some(qualifier), index)
3485 }
3486 } else {
3487 (tag, Some(inner), 0)
3488 }
3489 } else {
3490 (tag_part.to_uppercase(), None, 0)
3491 }
3492}
3493
3494fn inject_bo4e_metadata(mut value: serde_json::Value, bo4e_type: &str) -> serde_json::Value {
3499 match &mut value {
3500 serde_json::Value::Object(map) => {
3501 map.entry("boTyp")
3502 .or_insert_with(|| serde_json::Value::String(bo4e_type.to_uppercase()));
3503 map.entry("versionStruktur")
3504 .or_insert_with(|| serde_json::Value::String("1".to_string()));
3505 }
3506 serde_json::Value::Array(items) => {
3507 for item in items {
3508 if let serde_json::Value::Object(map) = item {
3509 map.entry("boTyp")
3510 .or_insert_with(|| serde_json::Value::String(bo4e_type.to_uppercase()));
3511 map.entry("versionStruktur")
3512 .or_insert_with(|| serde_json::Value::String("1".to_string()));
3513 }
3514 }
3515 }
3516 _ => {}
3517 }
3518 value
3519}
3520
3521fn deep_merge_insert(
3527 result: &mut serde_json::Map<String, serde_json::Value>,
3528 entity: &str,
3529 bo4e: serde_json::Value,
3530) {
3531 if let Some(existing) = result.get_mut(entity) {
3532 if let (Some(existing_arr), Some(new_arr)) =
3535 (existing.as_array().map(|a| a.len()), bo4e.as_array())
3536 {
3537 if existing_arr == new_arr.len() {
3538 let existing_arr = existing.as_array_mut().unwrap();
3539 for (existing_elem, new_elem) in existing_arr.iter_mut().zip(new_arr) {
3540 if let (Some(existing_map), Some(new_map)) =
3541 (existing_elem.as_object_mut(), new_elem.as_object())
3542 {
3543 for (k, v) in new_map {
3544 if let Some(existing_v) = existing_map.get_mut(k) {
3545 if let (Some(existing_inner), Some(new_inner)) =
3546 (existing_v.as_object_mut(), v.as_object())
3547 {
3548 for (ik, iv) in new_inner {
3549 existing_inner
3550 .entry(ik.clone())
3551 .or_insert_with(|| iv.clone());
3552 }
3553 }
3554 } else {
3555 existing_map.insert(k.clone(), v.clone());
3556 }
3557 }
3558 }
3559 }
3560 return;
3561 }
3562 }
3563 if let (Some(existing_map), serde_json::Value::Object(new_map)) =
3565 (existing.as_object_mut(), &bo4e)
3566 {
3567 for (k, v) in new_map {
3568 if let Some(existing_v) = existing_map.get_mut(k) {
3569 if let (Some(existing_inner), Some(new_inner)) =
3571 (existing_v.as_object_mut(), v.as_object())
3572 {
3573 for (ik, iv) in new_inner {
3574 existing_inner
3575 .entry(ik.clone())
3576 .or_insert_with(|| iv.clone());
3577 }
3578 }
3579 } else {
3581 existing_map.insert(k.clone(), v.clone());
3582 }
3583 }
3584 return;
3585 }
3586 }
3587 result.insert(entity.to_string(), bo4e);
3588}
3589
3590fn is_map_keyed_object(value: &serde_json::Value) -> bool {
3601 let Some(obj) = value.as_object() else {
3602 return false;
3603 };
3604 if obj.is_empty() {
3605 return false;
3606 }
3607 obj.iter().all(|(k, v)| {
3609 k.len() <= 5
3610 && k.chars()
3611 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
3612 && v.is_object()
3613 })
3614}
3615
3616fn find_qualifier_companion_field(
3625 definitions: &[crate::definition::MappingDefinition],
3626 entity: &str,
3627) -> Option<String> {
3628 for def in definitions {
3629 if def.meta.entity != *entity {
3630 continue;
3631 }
3632 let disc = def.meta.discriminator.as_deref()?;
3633 let (disc_path, _) = disc.split_once('=')?;
3634 let disc_path_lower = disc_path.to_lowercase();
3635
3636 let sections: Vec<&indexmap::IndexMap<String, FieldMapping>> =
3639 [def.companion_fields.as_ref(), Some(&def.fields)]
3640 .into_iter()
3641 .flatten()
3642 .collect();
3643
3644 for section in sections {
3645 for (path, mapping) in section {
3646 let cf_path = path.to_lowercase();
3647 let matches =
3648 cf_path == disc_path_lower || format!("{}.0", cf_path) == disc_path_lower;
3649 if matches {
3650 let target = match mapping {
3651 FieldMapping::Simple(t) => t.as_str(),
3652 FieldMapping::Structured(s) => s.target.as_str(),
3653 FieldMapping::Nested(_) => continue,
3654 };
3655 if !target.is_empty() {
3656 return Some(target.to_string());
3657 }
3658 }
3659 }
3660 }
3661 }
3662 None
3663}
3664
3665fn extract_child_from_parent(
3674 entities: &serde_json::Value,
3675 definitions: &[MappingDefinition],
3676 child_def: &MappingDefinition,
3677) -> Option<serde_json::Value> {
3678 extract_child_from_parent_with_indices(entities, definitions, child_def).map(|(v, _)| v)
3679}
3680
3681fn extract_child_from_parent_with_indices(
3686 entities: &serde_json::Value,
3687 definitions: &[MappingDefinition],
3688 child_def: &MappingDefinition,
3689) -> Option<(serde_json::Value, Vec<usize>)> {
3690 let parts: Vec<&str> = child_def.meta.source_group.split('.').collect();
3691 if parts.len() < 2 {
3692 return None;
3693 }
3694 let parent_group = parts[0];
3695 let parent_def = definitions
3696 .iter()
3697 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_def.meta.entity)?;
3698 let parent_key = to_camel_case(&parent_def.meta.entity);
3699 let child_key = to_camel_case(&child_def.meta.entity);
3700 let parent_value = entities.get(&parent_key)?;
3701
3702 if let Some(parent_map) = parent_value.as_object() {
3704 if is_map_keyed_value(parent_map) {
3705 let mut children: Vec<serde_json::Value> = Vec::new();
3706 let mut indices: Vec<usize> = Vec::new();
3707 for (i, (_key, inner)) in parent_map.iter().enumerate() {
3708 if let Some(child) = inner.get(&child_key) {
3709 if !child.is_null() {
3710 children.push(child.clone());
3711 indices.push(i);
3712 }
3713 }
3714 }
3715 return match children.len() {
3716 0 => None,
3717 1 => Some((children.into_iter().next().unwrap(), indices)),
3718 _ => Some((serde_json::Value::Array(children), indices)),
3719 };
3720 }
3721 }
3722
3723 if let Some(parent_arr) = parent_value.as_array() {
3725 let mut children: Vec<serde_json::Value> = Vec::new();
3726 let mut indices: Vec<usize> = Vec::new();
3727 for (i, item) in parent_arr.iter().enumerate() {
3728 if let Some(child) = item.get(&child_key) {
3729 if !child.is_null() {
3730 children.push(child.clone());
3731 indices.push(i);
3732 }
3733 }
3734 }
3735 return match children.len() {
3736 0 => None,
3737 1 => Some((children.into_iter().next().unwrap(), indices)),
3738 _ => Some((serde_json::Value::Array(children), indices)),
3739 };
3740 }
3741
3742 let child = parent_value.get(&child_key)?;
3744 if child.is_null() {
3745 return None;
3746 }
3747 Some((child.clone(), vec![0]))
3748}
3749
3750fn nest_child_entities_in_result(
3756 result: &mut serde_json::Map<String, serde_json::Value>,
3757 definitions: &[MappingDefinition],
3758 nesting_info: &std::collections::HashMap<String, Vec<usize>>,
3759 transaction_group: Option<&str>,
3760) {
3761 let mut nesting_pairs: Vec<(String, String, String, Option<String>)> = Vec::new();
3764 for def in definitions {
3765 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
3766 if parts.len() < 2 {
3767 continue;
3768 }
3769 let parent_group = parts[0];
3770 if transaction_group.is_some_and(|tx| tx == parent_group) {
3776 continue;
3777 }
3778 let child_entity = def.meta.entity.clone();
3779 let child_has_parent_level_def = definitions
3783 .iter()
3784 .any(|d| d.meta.source_group == parent_group && d.meta.entity == child_entity);
3785 if child_has_parent_level_def {
3786 continue;
3787 }
3788 let parent_entity = definitions
3790 .iter()
3791 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_entity)
3792 .map(|d| d.meta.entity.clone());
3793 if let Some(ref parent_entity) = parent_entity {
3794 let child_key_lc = to_camel_case(&child_entity);
3799 let parent_defs: Vec<_> = definitions
3800 .iter()
3801 .filter(|d| d.meta.entity == *parent_entity)
3802 .collect();
3803 let has_conflicting_field = parent_defs.iter().any(|pd| {
3804 pd.fields.values().any(|fm| {
3805 let target = match fm {
3806 crate::definition::FieldMapping::Simple(t) => t.as_str(),
3807 crate::definition::FieldMapping::Structured(s) => s.target.as_str(),
3808 crate::definition::FieldMapping::Nested(_) => "",
3809 };
3810 target.starts_with(&child_key_lc)
3811 && target.get(child_key_lc.len()..child_key_lc.len() + 1) == Some(".")
3812 })
3813 });
3814 if has_conflicting_field {
3815 continue;
3816 }
3817 if nesting_pairs
3819 .iter()
3820 .any(|(_, pe, ce, _)| *pe == *parent_entity && *ce == child_entity)
3821 {
3822 continue;
3823 }
3824 nesting_pairs.push((
3825 parent_group.to_string(),
3826 parent_entity.clone(),
3827 child_entity,
3828 def.meta.source_path.clone(),
3829 ));
3830 }
3831 }
3832
3833 for (_parent_group, parent_entity, child_entity, child_source_path) in nesting_pairs {
3834 let parent_key = to_camel_case(&parent_entity);
3835 let child_key = to_camel_case(&child_entity);
3836
3837 let child_value = match result.remove(&child_key) {
3839 Some(v) => v,
3840 None => continue,
3841 };
3842
3843 let Some(parent_value) = result.get_mut(&parent_key) else {
3848 result.insert(child_key, child_value);
3850 continue;
3851 };
3852 if parent_value.is_array() {
3853 result.insert(child_key, child_value);
3854 continue;
3855 }
3856
3857 let distribution = child_source_path
3859 .as_deref()
3860 .and_then(|sp| nesting_info.get(sp));
3861
3862 let child_items: Vec<(usize, &serde_json::Value)> = match &child_value {
3864 serde_json::Value::Array(arr) => arr.iter().enumerate().collect(),
3865 other => vec![(0, other)],
3866 };
3867
3868 let insert_or_append = |obj: &mut serde_json::Map<String, serde_json::Value>,
3871 key: &str,
3872 val: &serde_json::Value| {
3873 match obj.get_mut(key) {
3874 Some(existing) => {
3875 if !existing.is_array() {
3877 let prev = existing.take();
3878 *existing = serde_json::Value::Array(vec![prev]);
3879 }
3880 if let Some(arr) = existing.as_array_mut() {
3881 arr.push(val.clone());
3882 }
3883 }
3884 None => {
3885 obj.insert(key.to_string(), val.clone());
3886 }
3887 }
3888 };
3889
3890 if let Some(parent_map) = parent_value.as_object_mut() {
3892 if is_map_keyed_value(parent_map) {
3893 let keys: Vec<String> = parent_map.keys().cloned().collect();
3895 for (i, child_item) in &child_items {
3896 let target_idx = distribution
3897 .and_then(|dist| dist.get(*i))
3898 .copied()
3899 .unwrap_or(0);
3900 if let Some(key) = keys.get(target_idx) {
3901 if let Some(inner) = parent_map.get_mut(key).and_then(|v| v.as_object_mut())
3902 {
3903 insert_or_append(inner, &child_key, child_item);
3904 }
3905 }
3906 }
3907 continue;
3908 }
3909 }
3910
3911 if let Some(parent_arr) = parent_value.as_array_mut() {
3913 for (i, child_item) in &child_items {
3914 let target_idx = distribution
3915 .and_then(|dist| dist.get(*i))
3916 .copied()
3917 .unwrap_or(0);
3918 if let Some(parent_obj) = parent_arr
3919 .get_mut(target_idx)
3920 .and_then(|v| v.as_object_mut())
3921 {
3922 insert_or_append(parent_obj, &child_key, child_item);
3923 }
3924 }
3925 continue;
3926 }
3927
3928 if let Some(parent_obj) = parent_value.as_object_mut() {
3930 for (_i, child_item) in &child_items {
3931 insert_or_append(parent_obj, &child_key, child_item);
3932 }
3933 continue;
3934 }
3935
3936 result.insert(child_key, child_value);
3938 }
3939}
3940
3941fn is_map_keyed_value(map: &serde_json::Map<String, serde_json::Value>) -> bool {
3943 if map.is_empty() {
3944 return false;
3945 }
3946 map.values().all(|v| v.is_object())
3947 && map.keys().all(|k| {
3948 k.len() <= 5
3949 || k.chars()
3950 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
3951 })
3952}
3953
3954fn to_camel_case(name: &str) -> String {
3955 let mut chars = name.chars();
3956 match chars.next() {
3957 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
3958 None => String::new(),
3959 }
3960}
3961
3962fn set_nested_value(map: &mut serde_json::Map<String, serde_json::Value>, path: &str, val: String) {
3965 set_nested_value_json(map, path, serde_json::Value::String(val));
3966}
3967
3968fn set_nested_value_json(
3970 map: &mut serde_json::Map<String, serde_json::Value>,
3971 path: &str,
3972 val: serde_json::Value,
3973) {
3974 if let Some((prefix, leaf)) = path.rsplit_once('.') {
3975 let mut current = map;
3976 for part in prefix.split('.') {
3977 let entry = current
3978 .entry(part.to_string())
3979 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
3980 current = entry.as_object_mut().expect("expected object in path");
3981 }
3982 current.insert(leaf.to_string(), val);
3983 } else {
3984 map.insert(path.to_string(), val);
3985 }
3986}
3987
3988#[derive(serde::Serialize, serde::Deserialize)]
3993pub struct VariantCache {
3994 pub message_defs: Vec<MappingDefinition>,
3996 pub transaction_defs: HashMap<String, Vec<MappingDefinition>>,
3998 pub combined_defs: HashMap<String, Vec<MappingDefinition>>,
4000 #[serde(default)]
4002 pub code_lookups: HashMap<String, crate::code_lookup::CodeLookup>,
4003 #[serde(default)]
4005 pub mig_schema: Option<mig_types::schema::mig::MigSchema>,
4006 #[serde(default)]
4008 pub segment_structure: Option<crate::segment_structure::SegmentStructure>,
4009 #[serde(default)]
4012 pub pid_segment_numbers: HashMap<String, Vec<String>>,
4013 #[serde(default)]
4016 pub pid_requirements: HashMap<String, crate::pid_requirements::PidRequirements>,
4017 #[serde(default)]
4021 pub tx_groups: HashMap<String, String>,
4022}
4023
4024impl VariantCache {
4025 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
4027 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
4028 path: path.display().to_string(),
4029 message: e.to_string(),
4030 })?;
4031 if let Some(parent) = path.parent() {
4032 std::fs::create_dir_all(parent)?;
4033 }
4034 std::fs::write(path, encoded)?;
4035 Ok(())
4036 }
4037
4038 pub fn load(path: &Path) -> Result<Self, MappingError> {
4040 let bytes = std::fs::read(path)?;
4041 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
4042 path: path.display().to_string(),
4043 message: e.to_string(),
4044 })
4045 }
4046
4047 pub fn tx_group(&self, pid: &str) -> Option<&str> {
4051 self.tx_groups
4052 .get(&format!("pid_{pid}"))
4053 .map(|s| s.as_str())
4054 }
4055
4056 pub fn msg_engine(&self) -> MappingEngine {
4058 MappingEngine::from_definitions(self.message_defs.clone())
4059 }
4060
4061 pub fn tx_engine(&self, pid: &str) -> Option<MappingEngine> {
4064 self.transaction_defs
4065 .get(&format!("pid_{pid}"))
4066 .map(|defs| MappingEngine::from_definitions(defs.clone()))
4067 }
4068
4069 pub fn filtered_mig(&self, pid: &str) -> Option<mig_types::schema::mig::MigSchema> {
4072 let mig = self.mig_schema.as_ref()?;
4073 let numbers = self.pid_segment_numbers.get(&format!("pid_{pid}"))?;
4074 let number_set: std::collections::HashSet<String> = numbers.iter().cloned().collect();
4075 Some(mig_assembly::pid_filter::filter_mig_for_pid(
4076 mig,
4077 &number_set,
4078 ))
4079 }
4080}
4081
4082#[derive(serde::Serialize, serde::Deserialize)]
4087pub struct DataBundle {
4088 pub format_version: String,
4089 pub bundle_version: u32,
4090 pub variants: HashMap<String, VariantCache>,
4091}
4092
4093impl DataBundle {
4094 pub const CURRENT_VERSION: u32 = 2;
4095
4096 pub fn variant(&self, name: &str) -> Option<&VariantCache> {
4097 self.variants.get(name)
4098 }
4099
4100 pub fn write_to<W: std::io::Write>(&self, writer: &mut W) -> Result<(), MappingError> {
4101 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
4102 path: "<stream>".to_string(),
4103 message: e.to_string(),
4104 })?;
4105 writer.write_all(&encoded).map_err(MappingError::Io)
4106 }
4107
4108 pub fn read_from<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
4109 let mut bytes = Vec::new();
4110 reader.read_to_end(&mut bytes).map_err(MappingError::Io)?;
4111 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
4112 path: "<stream>".to_string(),
4113 message: e.to_string(),
4114 })
4115 }
4116
4117 pub fn read_from_checked<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
4118 let bundle = Self::read_from(reader)?;
4119 if bundle.bundle_version != Self::CURRENT_VERSION {
4120 return Err(MappingError::CacheRead {
4121 path: "<stream>".to_string(),
4122 message: format!(
4123 "Incompatible bundle version {}, expected version {}. \
4124 Run `edifact-data update` to fetch compatible bundles.",
4125 bundle.bundle_version,
4126 Self::CURRENT_VERSION
4127 ),
4128 });
4129 }
4130 Ok(bundle)
4131 }
4132
4133 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
4134 if let Some(parent) = path.parent() {
4135 std::fs::create_dir_all(parent)?;
4136 }
4137 let mut file = std::fs::File::create(path).map_err(MappingError::Io)?;
4138 self.write_to(&mut file)
4139 }
4140
4141 pub fn load(path: &Path) -> Result<Self, MappingError> {
4142 let mut file = std::fs::File::open(path).map_err(MappingError::Io)?;
4143 Self::read_from_checked(&mut file)
4144 }
4145}
4146
4147fn sort_variant_reps_by_mig(
4160 child_groups: &mut [AssembledGroup],
4161 mig: &MigSchema,
4162 transaction_group: &str,
4163) {
4164 let tx_def = match mig
4165 .segment_groups
4166 .iter()
4167 .find(|sg| sg.id == transaction_group)
4168 {
4169 Some(d) => d,
4170 None => return,
4171 };
4172
4173 for cg in child_groups.iter_mut() {
4174 if cg.repetitions.len() <= 1 {
4175 continue;
4176 }
4177
4178 let variant_defs: Vec<(usize, &mig_types::schema::mig::MigSegmentGroup)> = tx_def
4180 .nested_groups
4181 .iter()
4182 .enumerate()
4183 .filter(|(_, ng)| ng.id == cg.group_id && ng.variant_code.is_some())
4184 .collect();
4185
4186 if variant_defs.is_empty() {
4187 continue;
4188 }
4189
4190 cg.repetitions.sort_by_key(|rep| {
4193 let entry_seg = rep.segments.first();
4194 for &(mig_pos, variant_def) in &variant_defs {
4195 let (ei, ci) = variant_def.variant_qualifier_position.unwrap_or((0, 0));
4196 let actual_qual = entry_seg
4197 .and_then(|s| s.elements.get(ei))
4198 .and_then(|e| e.get(ci))
4199 .map(|s| s.as_str())
4200 .unwrap_or("");
4201 let matches = if !variant_def.variant_codes.is_empty() {
4202 variant_def
4203 .variant_codes
4204 .iter()
4205 .any(|c| actual_qual.eq_ignore_ascii_case(c))
4206 } else if let Some(ref expected_code) = variant_def.variant_code {
4207 actual_qual.eq_ignore_ascii_case(expected_code)
4208 } else {
4209 false
4210 };
4211 if matches {
4212 return mig_pos;
4213 }
4214 }
4215 usize::MAX });
4217 }
4218}
4219
4220#[cfg(test)]
4221mod variant_cache_helper_tests {
4222 use super::*;
4223
4224 fn make_test_cache() -> VariantCache {
4225 let mut tx_groups = HashMap::new();
4226 tx_groups.insert("pid_55001".to_string(), "SG4".to_string());
4227 tx_groups.insert("pid_21007".to_string(), "SG14".to_string());
4228
4229 let mut transaction_defs = HashMap::new();
4230 transaction_defs.insert("pid_55001".to_string(), vec![]);
4231 transaction_defs.insert("pid_21007".to_string(), vec![]);
4232
4233 VariantCache {
4234 message_defs: vec![],
4235 transaction_defs,
4236 combined_defs: HashMap::new(),
4237 code_lookups: HashMap::new(),
4238 mig_schema: None,
4239 segment_structure: None,
4240 pid_segment_numbers: HashMap::new(),
4241 pid_requirements: HashMap::new(),
4242 tx_groups,
4243 }
4244 }
4245
4246 #[test]
4247 fn test_tx_group_returns_correct_group() {
4248 let vc = make_test_cache();
4249 assert_eq!(vc.tx_group("55001").unwrap(), "SG4");
4250 assert_eq!(vc.tx_group("21007").unwrap(), "SG14");
4251 }
4252
4253 #[test]
4254 fn test_tx_group_unknown_pid_returns_none() {
4255 let vc = make_test_cache();
4256 assert!(vc.tx_group("99999").is_none());
4257 }
4258
4259 #[test]
4260 fn test_msg_engine_returns_engine() {
4261 let vc = make_test_cache();
4262 let engine = vc.msg_engine();
4263 assert_eq!(engine.definitions().len(), 0);
4264 }
4265
4266 #[test]
4267 fn test_tx_engine_returns_engine_for_known_pid() {
4268 let vc = make_test_cache();
4269 assert!(vc.tx_engine("55001").is_some());
4270 }
4271
4272 #[test]
4273 fn test_tx_engine_returns_none_for_unknown_pid() {
4274 let vc = make_test_cache();
4275 assert!(vc.tx_engine("99999").is_none());
4276 }
4277}
4278
4279#[cfg(test)]
4280mod tests {
4281 use super::*;
4282 use crate::definition::{MappingDefinition, MappingMeta, StructuredFieldMapping};
4283 use indexmap::IndexMap;
4284
4285 fn make_def(fields: IndexMap<String, FieldMapping>) -> MappingDefinition {
4286 MappingDefinition {
4287 meta: MappingMeta {
4288 entity: "Test".to_string(),
4289 bo4e_type: "Test".to_string(),
4290 companion_type: None,
4291 source_group: "SG4".to_string(),
4292 source_path: None,
4293 discriminator: None,
4294 repeat_on_tag: None,
4295 },
4296 fields,
4297 companion_fields: None,
4298 complex_handlers: None,
4299 }
4300 }
4301
4302 #[test]
4303 fn test_map_interchange_single_transaction_backward_compat() {
4304 use mig_assembly::assembler::*;
4305
4306 let tree = AssembledTree {
4308 segments: vec![
4309 AssembledSegment {
4310 tag: "UNH".to_string(),
4311 elements: vec![vec!["001".to_string()]],
4312 mig_number: None,
4313 },
4314 AssembledSegment {
4315 tag: "BGM".to_string(),
4316 elements: vec![vec!["E01".to_string()], vec!["DOC001".to_string()]],
4317 mig_number: None,
4318 },
4319 ],
4320 groups: vec![
4321 AssembledGroup {
4322 group_id: "SG2".to_string(),
4323 repetitions: vec![AssembledGroupInstance {
4324 segments: vec![AssembledSegment {
4325 tag: "NAD".to_string(),
4326 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4327 mig_number: None,
4328 }],
4329 child_groups: vec![],
4330 entry_mig_number: None,
4331 variant_mig_numbers: vec![],
4332 skipped_segments: vec![],
4333 }],
4334 },
4335 AssembledGroup {
4336 group_id: "SG4".to_string(),
4337 repetitions: vec![AssembledGroupInstance {
4338 segments: vec![AssembledSegment {
4339 tag: "IDE".to_string(),
4340 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4341 mig_number: None,
4342 }],
4343 child_groups: vec![AssembledGroup {
4344 group_id: "SG5".to_string(),
4345 repetitions: vec![AssembledGroupInstance {
4346 segments: vec![AssembledSegment {
4347 tag: "LOC".to_string(),
4348 elements: vec![
4349 vec!["Z16".to_string()],
4350 vec!["DE000111222333".to_string()],
4351 ],
4352 mig_number: None,
4353 }],
4354 child_groups: vec![],
4355 entry_mig_number: None,
4356 variant_mig_numbers: vec![],
4357 skipped_segments: vec![],
4358 }],
4359 }],
4360 entry_mig_number: None,
4361 variant_mig_numbers: vec![],
4362 skipped_segments: vec![],
4363 }],
4364 },
4365 ],
4366 post_group_start: 2,
4367 inter_group_segments: std::collections::BTreeMap::new(),
4368 };
4369
4370 let msg_engine = MappingEngine::from_definitions(vec![]);
4372
4373 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4375 tx_fields.insert(
4376 "ide.1".to_string(),
4377 FieldMapping::Simple("vorgangId".to_string()),
4378 );
4379 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4380 malo_fields.insert(
4381 "loc.1".to_string(),
4382 FieldMapping::Simple("marktlokationsId".to_string()),
4383 );
4384
4385 let tx_engine = MappingEngine::from_definitions(vec![
4386 MappingDefinition {
4387 meta: MappingMeta {
4388 entity: "Prozessdaten".to_string(),
4389 bo4e_type: "Prozessdaten".to_string(),
4390 companion_type: None,
4391 source_group: "SG4".to_string(),
4392 source_path: None,
4393 discriminator: None,
4394 repeat_on_tag: None,
4395 },
4396 fields: tx_fields,
4397 companion_fields: None,
4398 complex_handlers: None,
4399 },
4400 MappingDefinition {
4401 meta: MappingMeta {
4402 entity: "Marktlokation".to_string(),
4403 bo4e_type: "Marktlokation".to_string(),
4404 companion_type: None,
4405 source_group: "SG4.SG5".to_string(),
4406 source_path: None,
4407 discriminator: None,
4408 repeat_on_tag: None,
4409 },
4410 fields: malo_fields,
4411 companion_fields: None,
4412 complex_handlers: None,
4413 },
4414 ]);
4415
4416 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
4417
4418 assert_eq!(result.transaktionen.len(), 1);
4419 assert_eq!(
4420 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
4421 .as_str()
4422 .unwrap(),
4423 "TX001"
4424 );
4425 assert_eq!(
4428 result.transaktionen[0].stammdaten["marktlokation"]["marktlokationsId"]
4429 .as_str()
4430 .unwrap(),
4431 "DE000111222333"
4432 );
4433 }
4434
4435 #[test]
4436 fn test_map_reverse_pads_intermediate_empty_elements() {
4437 let mut fields = IndexMap::new();
4439 fields.insert(
4440 "nad.0".to_string(),
4441 FieldMapping::Structured(StructuredFieldMapping {
4442 target: String::new(),
4443 transform: None,
4444 when: None,
4445 default: Some("Z09".to_string()),
4446 enum_map: None,
4447 when_filled: None,
4448 also_target: None,
4449 also_enum_map: None,
4450 }),
4451 );
4452 fields.insert(
4453 "nad.3.0".to_string(),
4454 FieldMapping::Simple("name".to_string()),
4455 );
4456 fields.insert(
4457 "nad.3.1".to_string(),
4458 FieldMapping::Simple("vorname".to_string()),
4459 );
4460
4461 let def = make_def(fields);
4462 let engine = MappingEngine::from_definitions(vec![]);
4463
4464 let bo4e = serde_json::json!({
4465 "name": "Muster",
4466 "vorname": "Max"
4467 });
4468
4469 let instance = engine.map_reverse(&bo4e, &def);
4470 assert_eq!(instance.segments.len(), 1);
4471
4472 let nad = &instance.segments[0];
4473 assert_eq!(nad.tag, "NAD");
4474 assert_eq!(nad.elements.len(), 4);
4475 assert_eq!(nad.elements[0], vec!["Z09"]);
4476 assert_eq!(nad.elements[1], vec![""]);
4478 assert_eq!(nad.elements[2], vec![""]);
4479 assert_eq!(nad.elements[3][0], "Muster");
4480 assert_eq!(nad.elements[3][1], "Max");
4481 }
4482
4483 #[test]
4484 fn test_map_reverse_no_padding_when_contiguous() {
4485 let mut fields = IndexMap::new();
4487 fields.insert(
4488 "dtm.0.0".to_string(),
4489 FieldMapping::Structured(StructuredFieldMapping {
4490 target: String::new(),
4491 transform: None,
4492 when: None,
4493 default: Some("92".to_string()),
4494 enum_map: None,
4495 when_filled: None,
4496 also_target: None,
4497 also_enum_map: None,
4498 }),
4499 );
4500 fields.insert(
4501 "dtm.0.1".to_string(),
4502 FieldMapping::Simple("value".to_string()),
4503 );
4504 fields.insert(
4505 "dtm.0.2".to_string(),
4506 FieldMapping::Structured(StructuredFieldMapping {
4507 target: String::new(),
4508 transform: None,
4509 when: None,
4510 default: Some("303".to_string()),
4511 enum_map: None,
4512 when_filled: None,
4513 also_target: None,
4514 also_enum_map: None,
4515 }),
4516 );
4517
4518 let def = make_def(fields);
4519 let engine = MappingEngine::from_definitions(vec![]);
4520
4521 let bo4e = serde_json::json!({ "value": "20250531" });
4522
4523 let instance = engine.map_reverse(&bo4e, &def);
4524 let dtm = &instance.segments[0];
4525 assert_eq!(dtm.elements.len(), 1);
4527 assert_eq!(dtm.elements[0], vec!["92", "20250531", "303"]);
4528 }
4529
4530 #[test]
4531 fn test_map_message_level_extracts_sg2_only() {
4532 use mig_assembly::assembler::*;
4533
4534 let tree = AssembledTree {
4536 segments: vec![
4537 AssembledSegment {
4538 tag: "UNH".to_string(),
4539 elements: vec![vec!["001".to_string()]],
4540 mig_number: None,
4541 },
4542 AssembledSegment {
4543 tag: "BGM".to_string(),
4544 elements: vec![vec!["E01".to_string()]],
4545 mig_number: None,
4546 },
4547 ],
4548 groups: vec![
4549 AssembledGroup {
4550 group_id: "SG2".to_string(),
4551 repetitions: vec![AssembledGroupInstance {
4552 segments: vec![AssembledSegment {
4553 tag: "NAD".to_string(),
4554 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4555 mig_number: None,
4556 }],
4557 child_groups: vec![],
4558 entry_mig_number: None,
4559 variant_mig_numbers: vec![],
4560 skipped_segments: vec![],
4561 }],
4562 },
4563 AssembledGroup {
4564 group_id: "SG4".to_string(),
4565 repetitions: vec![AssembledGroupInstance {
4566 segments: vec![AssembledSegment {
4567 tag: "IDE".to_string(),
4568 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4569 mig_number: None,
4570 }],
4571 child_groups: vec![],
4572 entry_mig_number: None,
4573 variant_mig_numbers: vec![],
4574 skipped_segments: vec![],
4575 }],
4576 },
4577 ],
4578 post_group_start: 2,
4579 inter_group_segments: std::collections::BTreeMap::new(),
4580 };
4581
4582 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4584 msg_fields.insert(
4585 "nad.0".to_string(),
4586 FieldMapping::Simple("marktrolle".to_string()),
4587 );
4588 msg_fields.insert(
4589 "nad.1".to_string(),
4590 FieldMapping::Simple("rollencodenummer".to_string()),
4591 );
4592 let msg_def = MappingDefinition {
4593 meta: MappingMeta {
4594 entity: "Marktteilnehmer".to_string(),
4595 bo4e_type: "Marktteilnehmer".to_string(),
4596 companion_type: None,
4597 source_group: "SG2".to_string(),
4598 source_path: None,
4599 discriminator: None,
4600 repeat_on_tag: None,
4601 },
4602 fields: msg_fields,
4603 companion_fields: None,
4604 complex_handlers: None,
4605 };
4606
4607 let engine = MappingEngine::from_definitions(vec![msg_def.clone()]);
4608 let result = engine.map_all_forward(&tree);
4609
4610 assert!(result.get("marktteilnehmer").is_some());
4612 let mt = &result["marktteilnehmer"];
4613 assert_eq!(mt["marktrolle"].as_str().unwrap(), "MS");
4614 assert_eq!(mt["rollencodenummer"].as_str().unwrap(), "9900123");
4615 }
4616
4617 #[test]
4618 fn test_map_transaction_scoped_to_sg4_instance() {
4619 use mig_assembly::assembler::*;
4620
4621 let tree = AssembledTree {
4623 segments: vec![
4624 AssembledSegment {
4625 tag: "UNH".to_string(),
4626 elements: vec![vec!["001".to_string()]],
4627 mig_number: None,
4628 },
4629 AssembledSegment {
4630 tag: "BGM".to_string(),
4631 elements: vec![vec!["E01".to_string()]],
4632 mig_number: None,
4633 },
4634 ],
4635 groups: vec![AssembledGroup {
4636 group_id: "SG4".to_string(),
4637 repetitions: vec![AssembledGroupInstance {
4638 segments: vec![AssembledSegment {
4639 tag: "IDE".to_string(),
4640 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4641 mig_number: None,
4642 }],
4643 child_groups: vec![AssembledGroup {
4644 group_id: "SG5".to_string(),
4645 repetitions: vec![AssembledGroupInstance {
4646 segments: vec![AssembledSegment {
4647 tag: "LOC".to_string(),
4648 elements: vec![
4649 vec!["Z16".to_string()],
4650 vec!["DE000111222333".to_string()],
4651 ],
4652 mig_number: None,
4653 }],
4654 child_groups: vec![],
4655 entry_mig_number: None,
4656 variant_mig_numbers: vec![],
4657 skipped_segments: vec![],
4658 }],
4659 }],
4660 entry_mig_number: None,
4661 variant_mig_numbers: vec![],
4662 skipped_segments: vec![],
4663 }],
4664 }],
4665 post_group_start: 2,
4666 inter_group_segments: std::collections::BTreeMap::new(),
4667 };
4668
4669 let mut proz_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4671 proz_fields.insert(
4672 "ide.1".to_string(),
4673 FieldMapping::Simple("vorgangId".to_string()),
4674 );
4675 let proz_def = MappingDefinition {
4676 meta: MappingMeta {
4677 entity: "Prozessdaten".to_string(),
4678 bo4e_type: "Prozessdaten".to_string(),
4679 companion_type: None,
4680 source_group: "".to_string(), source_path: None,
4682 discriminator: None,
4683 repeat_on_tag: None,
4684 },
4685 fields: proz_fields,
4686 companion_fields: None,
4687 complex_handlers: None,
4688 };
4689
4690 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4691 malo_fields.insert(
4692 "loc.1".to_string(),
4693 FieldMapping::Simple("marktlokationsId".to_string()),
4694 );
4695 let malo_def = MappingDefinition {
4696 meta: MappingMeta {
4697 entity: "Marktlokation".to_string(),
4698 bo4e_type: "Marktlokation".to_string(),
4699 companion_type: None,
4700 source_group: "SG5".to_string(), source_path: None,
4702 discriminator: None,
4703 repeat_on_tag: None,
4704 },
4705 fields: malo_fields,
4706 companion_fields: None,
4707 complex_handlers: None,
4708 };
4709
4710 let tx_engine = MappingEngine::from_definitions(vec![proz_def, malo_def]);
4711
4712 let sg4 = &tree.groups[0]; let sg4_instance = &sg4.repetitions[0];
4715 let sub_tree = sg4_instance.as_assembled_tree();
4716
4717 let result = tx_engine.map_all_forward(&sub_tree);
4718
4719 assert_eq!(
4721 result["prozessdaten"]["vorgangId"].as_str().unwrap(),
4722 "TX001"
4723 );
4724
4725 assert_eq!(
4727 result["marktlokation"]["marktlokationsId"]
4728 .as_str()
4729 .unwrap(),
4730 "DE000111222333"
4731 );
4732 }
4733
4734 #[test]
4735 fn test_map_interchange_produces_full_hierarchy() {
4736 use mig_assembly::assembler::*;
4737
4738 let tree = AssembledTree {
4740 segments: vec![
4741 AssembledSegment {
4742 tag: "UNH".to_string(),
4743 elements: vec![vec!["001".to_string()]],
4744 mig_number: None,
4745 },
4746 AssembledSegment {
4747 tag: "BGM".to_string(),
4748 elements: vec![vec!["E01".to_string()]],
4749 mig_number: None,
4750 },
4751 ],
4752 groups: vec![
4753 AssembledGroup {
4754 group_id: "SG2".to_string(),
4755 repetitions: vec![AssembledGroupInstance {
4756 segments: vec![AssembledSegment {
4757 tag: "NAD".to_string(),
4758 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4759 mig_number: None,
4760 }],
4761 child_groups: vec![],
4762 entry_mig_number: None,
4763 variant_mig_numbers: vec![],
4764 skipped_segments: vec![],
4765 }],
4766 },
4767 AssembledGroup {
4768 group_id: "SG4".to_string(),
4769 repetitions: vec![
4770 AssembledGroupInstance {
4771 segments: vec![AssembledSegment {
4772 tag: "IDE".to_string(),
4773 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4774 mig_number: None,
4775 }],
4776 child_groups: vec![],
4777 entry_mig_number: None,
4778 variant_mig_numbers: vec![],
4779 skipped_segments: vec![],
4780 },
4781 AssembledGroupInstance {
4782 segments: vec![AssembledSegment {
4783 tag: "IDE".to_string(),
4784 elements: vec![vec!["24".to_string()], vec!["TX002".to_string()]],
4785 mig_number: None,
4786 }],
4787 child_groups: vec![],
4788 entry_mig_number: None,
4789 variant_mig_numbers: vec![],
4790 skipped_segments: vec![],
4791 },
4792 ],
4793 },
4794 ],
4795 post_group_start: 2,
4796 inter_group_segments: std::collections::BTreeMap::new(),
4797 };
4798
4799 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4801 msg_fields.insert(
4802 "nad.0".to_string(),
4803 FieldMapping::Simple("marktrolle".to_string()),
4804 );
4805 let msg_defs = vec![MappingDefinition {
4806 meta: MappingMeta {
4807 entity: "Marktteilnehmer".to_string(),
4808 bo4e_type: "Marktteilnehmer".to_string(),
4809 companion_type: None,
4810 source_group: "SG2".to_string(),
4811 source_path: None,
4812 discriminator: None,
4813 repeat_on_tag: None,
4814 },
4815 fields: msg_fields,
4816 companion_fields: None,
4817 complex_handlers: None,
4818 }];
4819
4820 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4822 tx_fields.insert(
4823 "ide.1".to_string(),
4824 FieldMapping::Simple("vorgangId".to_string()),
4825 );
4826 let tx_defs = vec![MappingDefinition {
4827 meta: MappingMeta {
4828 entity: "Prozessdaten".to_string(),
4829 bo4e_type: "Prozessdaten".to_string(),
4830 companion_type: None,
4831 source_group: "SG4".to_string(),
4832 source_path: None,
4833 discriminator: None,
4834 repeat_on_tag: None,
4835 },
4836 fields: tx_fields,
4837 companion_fields: None,
4838 complex_handlers: None,
4839 }];
4840
4841 let msg_engine = MappingEngine::from_definitions(msg_defs);
4842 let tx_engine = MappingEngine::from_definitions(tx_defs);
4843
4844 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
4845
4846 assert!(result.stammdaten["marktteilnehmer"].is_object());
4848 assert_eq!(
4849 result.stammdaten["marktteilnehmer"]["marktrolle"]
4850 .as_str()
4851 .unwrap(),
4852 "MS"
4853 );
4854
4855 assert_eq!(result.transaktionen.len(), 2);
4857 assert_eq!(
4858 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
4859 .as_str()
4860 .unwrap(),
4861 "TX001"
4862 );
4863 assert_eq!(
4864 result.transaktionen[1].stammdaten["prozessdaten"]["vorgangId"]
4865 .as_str()
4866 .unwrap(),
4867 "TX002"
4868 );
4869 }
4870
4871 #[test]
4872 fn test_map_reverse_with_segment_structure_pads_trailing() {
4873 let mut fields = IndexMap::new();
4875 fields.insert(
4876 "sts.0".to_string(),
4877 FieldMapping::Structured(StructuredFieldMapping {
4878 target: String::new(),
4879 transform: None,
4880 when: None,
4881 default: Some("7".to_string()),
4882 enum_map: None,
4883 when_filled: None,
4884 also_target: None,
4885 also_enum_map: None,
4886 }),
4887 );
4888 fields.insert(
4889 "sts.2".to_string(),
4890 FieldMapping::Simple("grund".to_string()),
4891 );
4892
4893 let def = make_def(fields);
4894
4895 let mut counts = std::collections::HashMap::new();
4897 counts.insert("STS".to_string(), 5usize);
4898 let ss = SegmentStructure {
4899 element_counts: counts,
4900 };
4901
4902 let engine = MappingEngine::from_definitions(vec![]).with_segment_structure(ss);
4903
4904 let bo4e = serde_json::json!({ "grund": "E01" });
4905
4906 let instance = engine.map_reverse(&bo4e, &def);
4907 let sts = &instance.segments[0];
4908 assert_eq!(sts.elements.len(), 5);
4911 assert_eq!(sts.elements[0], vec!["7"]);
4912 assert_eq!(sts.elements[1], vec![""]);
4913 assert_eq!(sts.elements[2], vec!["E01"]);
4914 assert_eq!(sts.elements[3], vec![""]);
4915 assert_eq!(sts.elements[4], vec![""]);
4916 }
4917
4918 #[test]
4919 fn test_extract_companion_fields_with_code_enrichment() {
4920 use crate::code_lookup::CodeLookup;
4921 use mig_assembly::assembler::*;
4922
4923 let schema = serde_json::json!({
4924 "fields": {
4925 "sg4": {
4926 "children": {
4927 "sg8_z01": {
4928 "children": {
4929 "sg10": {
4930 "segments": [{
4931 "id": "CCI",
4932 "elements": [{
4933 "index": 2,
4934 "components": [{
4935 "sub_index": 0,
4936 "type": "code",
4937 "codes": [
4938 {"value": "Z15", "name": "Haushaltskunde"},
4939 {"value": "Z18", "name": "Kein Haushaltskunde"}
4940 ]
4941 }]
4942 }]
4943 }],
4944 "source_group": "SG10"
4945 }
4946 },
4947 "segments": [],
4948 "source_group": "SG8"
4949 }
4950 },
4951 "segments": [],
4952 "source_group": "SG4"
4953 }
4954 }
4955 });
4956
4957 let code_lookup = CodeLookup::from_schema_value(&schema);
4958
4959 let tree = AssembledTree {
4960 segments: vec![],
4961 groups: vec![AssembledGroup {
4962 group_id: "SG4".to_string(),
4963 repetitions: vec![AssembledGroupInstance {
4964 segments: vec![],
4965 child_groups: vec![AssembledGroup {
4966 group_id: "SG8".to_string(),
4967 repetitions: vec![AssembledGroupInstance {
4968 segments: vec![],
4969 child_groups: vec![AssembledGroup {
4970 group_id: "SG10".to_string(),
4971 repetitions: vec![AssembledGroupInstance {
4972 segments: vec![AssembledSegment {
4973 tag: "CCI".to_string(),
4974 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
4975 mig_number: None,
4976 }],
4977 child_groups: vec![],
4978 entry_mig_number: None,
4979 variant_mig_numbers: vec![],
4980 skipped_segments: vec![],
4981 }],
4982 }],
4983 entry_mig_number: None,
4984 variant_mig_numbers: vec![],
4985 skipped_segments: vec![],
4986 }],
4987 }],
4988 entry_mig_number: None,
4989 variant_mig_numbers: vec![],
4990 skipped_segments: vec![],
4991 }],
4992 }],
4993 post_group_start: 0,
4994 inter_group_segments: std::collections::BTreeMap::new(),
4995 };
4996
4997 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4998 companion_fields.insert(
4999 "cci.2".to_string(),
5000 FieldMapping::Simple("haushaltskunde".to_string()),
5001 );
5002
5003 let def = MappingDefinition {
5004 meta: MappingMeta {
5005 entity: "Marktlokation".to_string(),
5006 bo4e_type: "Marktlokation".to_string(),
5007 companion_type: Some("MarktlokationEdifact".to_string()),
5008 source_group: "SG4.SG8.SG10".to_string(),
5009 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5010 discriminator: None,
5011 repeat_on_tag: None,
5012 },
5013 fields: IndexMap::new(),
5014 companion_fields: Some(companion_fields),
5015 complex_handlers: None,
5016 };
5017
5018 let engine_plain = MappingEngine::from_definitions(vec![]);
5020 let bo4e_plain = engine_plain.map_forward(&tree, &def, 0);
5021 assert_eq!(
5022 bo4e_plain["marktlokationEdifact"]["haushaltskunde"].as_str(),
5023 Some("Z15"),
5024 "Without code lookup, should be plain string"
5025 );
5026
5027 let engine_enriched = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
5029 let bo4e_enriched = engine_enriched.map_forward(&tree, &def, 0);
5030 let hk = &bo4e_enriched["marktlokationEdifact"]["haushaltskunde"];
5031 assert_eq!(hk["code"].as_str(), Some("Z15"));
5032 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
5033 assert!(hk.get("enum").is_none());
5035 }
5036
5037 #[test]
5038 fn test_extract_companion_fields_with_enum_enrichment() {
5039 use crate::code_lookup::CodeLookup;
5040 use mig_assembly::assembler::*;
5041
5042 let schema = serde_json::json!({
5044 "fields": {
5045 "sg4": {
5046 "children": {
5047 "sg8_z01": {
5048 "children": {
5049 "sg10": {
5050 "segments": [{
5051 "id": "CCI",
5052 "elements": [{
5053 "index": 2,
5054 "components": [{
5055 "sub_index": 0,
5056 "type": "code",
5057 "codes": [
5058 {"value": "Z15", "name": "Haushaltskunde", "enum": "HAUSHALTSKUNDE"},
5059 {"value": "Z18", "name": "Kein Haushaltskunde", "enum": "KEIN_HAUSHALTSKUNDE"}
5060 ]
5061 }]
5062 }]
5063 }],
5064 "source_group": "SG10"
5065 }
5066 },
5067 "segments": [],
5068 "source_group": "SG8"
5069 }
5070 },
5071 "segments": [],
5072 "source_group": "SG4"
5073 }
5074 }
5075 });
5076
5077 let code_lookup = CodeLookup::from_schema_value(&schema);
5078
5079 let tree = AssembledTree {
5080 segments: vec![],
5081 groups: vec![AssembledGroup {
5082 group_id: "SG4".to_string(),
5083 repetitions: vec![AssembledGroupInstance {
5084 segments: vec![],
5085 child_groups: vec![AssembledGroup {
5086 group_id: "SG8".to_string(),
5087 repetitions: vec![AssembledGroupInstance {
5088 segments: vec![],
5089 child_groups: vec![AssembledGroup {
5090 group_id: "SG10".to_string(),
5091 repetitions: vec![AssembledGroupInstance {
5092 segments: vec![AssembledSegment {
5093 tag: "CCI".to_string(),
5094 elements: vec![vec![], vec![], vec!["Z15".to_string()]],
5095 mig_number: None,
5096 }],
5097 child_groups: vec![],
5098 entry_mig_number: None,
5099 variant_mig_numbers: vec![],
5100 skipped_segments: vec![],
5101 }],
5102 }],
5103 entry_mig_number: None,
5104 variant_mig_numbers: vec![],
5105 skipped_segments: vec![],
5106 }],
5107 }],
5108 entry_mig_number: None,
5109 variant_mig_numbers: vec![],
5110 skipped_segments: vec![],
5111 }],
5112 }],
5113 post_group_start: 0,
5114 inter_group_segments: std::collections::BTreeMap::new(),
5115 };
5116
5117 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5118 companion_fields.insert(
5119 "cci.2".to_string(),
5120 FieldMapping::Simple("haushaltskunde".to_string()),
5121 );
5122
5123 let def = MappingDefinition {
5124 meta: MappingMeta {
5125 entity: "Marktlokation".to_string(),
5126 bo4e_type: "Marktlokation".to_string(),
5127 companion_type: Some("MarktlokationEdifact".to_string()),
5128 source_group: "SG4.SG8.SG10".to_string(),
5129 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5130 discriminator: None,
5131 repeat_on_tag: None,
5132 },
5133 fields: IndexMap::new(),
5134 companion_fields: Some(companion_fields),
5135 complex_handlers: None,
5136 };
5137
5138 let engine = MappingEngine::from_definitions(vec![]).with_code_lookup(code_lookup);
5139 let bo4e = engine.map_forward(&tree, &def, 0);
5140 let hk = &bo4e["marktlokationEdifact"]["haushaltskunde"];
5141 assert_eq!(hk["code"].as_str(), Some("Z15"));
5142 assert_eq!(hk["meaning"].as_str(), Some("Haushaltskunde"));
5143 assert_eq!(
5144 hk["enum"].as_str(),
5145 Some("HAUSHALTSKUNDE"),
5146 "enum field should be present"
5147 );
5148 }
5149
5150 #[test]
5151 fn test_reverse_mapping_accepts_enriched_with_enum() {
5152 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5154 companion_fields.insert(
5155 "cci.2".to_string(),
5156 FieldMapping::Simple("haushaltskunde".to_string()),
5157 );
5158
5159 let def = MappingDefinition {
5160 meta: MappingMeta {
5161 entity: "Test".to_string(),
5162 bo4e_type: "Test".to_string(),
5163 companion_type: Some("TestEdifact".to_string()),
5164 source_group: "SG4".to_string(),
5165 source_path: None,
5166 discriminator: None,
5167 repeat_on_tag: None,
5168 },
5169 fields: IndexMap::new(),
5170 companion_fields: Some(companion_fields),
5171 complex_handlers: None,
5172 };
5173
5174 let engine = MappingEngine::from_definitions(vec![]);
5175
5176 let bo4e = serde_json::json!({
5177 "testEdifact": {
5178 "haushaltskunde": {
5179 "code": "Z15",
5180 "meaning": "Haushaltskunde",
5181 "enum": "HAUSHALTSKUNDE"
5182 }
5183 }
5184 });
5185 let instance = engine.map_reverse(&bo4e, &def);
5186 assert_eq!(instance.segments[0].elements[2], vec!["Z15"]);
5187 }
5188
5189 #[test]
5190 fn test_reverse_mapping_accepts_enriched_companion() {
5191 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5193 companion_fields.insert(
5194 "cci.2".to_string(),
5195 FieldMapping::Simple("haushaltskunde".to_string()),
5196 );
5197
5198 let def = MappingDefinition {
5199 meta: MappingMeta {
5200 entity: "Test".to_string(),
5201 bo4e_type: "Test".to_string(),
5202 companion_type: Some("TestEdifact".to_string()),
5203 source_group: "SG4".to_string(),
5204 source_path: None,
5205 discriminator: None,
5206 repeat_on_tag: None,
5207 },
5208 fields: IndexMap::new(),
5209 companion_fields: Some(companion_fields),
5210 complex_handlers: None,
5211 };
5212
5213 let engine = MappingEngine::from_definitions(vec![]);
5214
5215 let bo4e_plain = serde_json::json!({
5217 "testEdifact": {
5218 "haushaltskunde": "Z15"
5219 }
5220 });
5221 let instance_plain = engine.map_reverse(&bo4e_plain, &def);
5222 assert_eq!(instance_plain.segments[0].elements[2], vec!["Z15"]);
5223
5224 let bo4e_enriched = serde_json::json!({
5226 "testEdifact": {
5227 "haushaltskunde": {
5228 "code": "Z15",
5229 "meaning": "Haushaltskunde gem. EnWG"
5230 }
5231 }
5232 });
5233 let instance_enriched = engine.map_reverse(&bo4e_enriched, &def);
5234 assert_eq!(instance_enriched.segments[0].elements[2], vec!["Z15"]);
5235 }
5236
5237 #[test]
5238 fn test_resolve_child_relative_with_source_path() {
5239 let mut map: std::collections::HashMap<String, Vec<usize>> =
5240 std::collections::HashMap::new();
5241 map.insert("sg4.sg8_ze1".to_string(), vec![6]);
5242 map.insert("sg4.sg8_z98".to_string(), vec![0]);
5243
5244 assert_eq!(
5246 resolve_child_relative("SG8.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5247 "SG8:6.SG10"
5248 );
5249
5250 assert_eq!(
5252 resolve_child_relative("SG8:3.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
5253 "SG8:3.SG10"
5254 );
5255
5256 assert_eq!(
5258 resolve_child_relative("SG8.SG10", Some("sg4.sg8_unknown.sg10"), &map, 0),
5259 "SG8.SG10"
5260 );
5261
5262 assert_eq!(
5264 resolve_child_relative("SG8.SG10", None, &map, 0),
5265 "SG8.SG10"
5266 );
5267
5268 assert_eq!(
5270 resolve_child_relative("SG8.SG9", Some("sg4.sg8_z98.sg9"), &map, 0),
5271 "SG8:0.SG9"
5272 );
5273
5274 map.insert("sg4.sg8_zf3".to_string(), vec![3, 4]);
5276 assert_eq!(
5277 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 0),
5278 "SG8:3.SG10"
5279 );
5280 assert_eq!(
5281 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 1),
5282 "SG8:4.SG10"
5283 );
5284 }
5285
5286 #[test]
5287 fn test_place_in_groups_returns_rep_index() {
5288 let mut groups: Vec<AssembledGroup> = Vec::new();
5289
5290 let instance = AssembledGroupInstance {
5292 segments: vec![],
5293 child_groups: vec![],
5294 entry_mig_number: None,
5295 variant_mig_numbers: vec![],
5296 skipped_segments: vec![],
5297 };
5298 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 0);
5299
5300 let instance = AssembledGroupInstance {
5302 segments: vec![],
5303 child_groups: vec![],
5304 entry_mig_number: None,
5305 variant_mig_numbers: vec![],
5306 skipped_segments: vec![],
5307 };
5308 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 1);
5309
5310 let instance = AssembledGroupInstance {
5312 segments: vec![],
5313 child_groups: vec![],
5314 entry_mig_number: None,
5315 variant_mig_numbers: vec![],
5316 skipped_segments: vec![],
5317 };
5318 assert_eq!(place_in_groups(&mut groups, "SG8:5", instance), 5);
5319 }
5320
5321 #[test]
5322 fn test_resolve_by_source_path() {
5323 use mig_assembly::assembler::*;
5324
5325 let tree = AssembledTree {
5327 segments: vec![],
5328 groups: vec![AssembledGroup {
5329 group_id: "SG4".to_string(),
5330 repetitions: vec![AssembledGroupInstance {
5331 segments: vec![],
5332 child_groups: vec![AssembledGroup {
5333 group_id: "SG8".to_string(),
5334 repetitions: vec![
5335 AssembledGroupInstance {
5336 segments: vec![AssembledSegment {
5337 tag: "SEQ".to_string(),
5338 elements: vec![vec!["Z98".to_string()]],
5339 mig_number: None,
5340 }],
5341 child_groups: vec![AssembledGroup {
5342 group_id: "SG10".to_string(),
5343 repetitions: vec![AssembledGroupInstance {
5344 segments: vec![AssembledSegment {
5345 tag: "CCI".to_string(),
5346 elements: vec![vec![], vec![], vec!["ZB3".to_string()]],
5347 mig_number: None,
5348 }],
5349 child_groups: vec![],
5350 entry_mig_number: None,
5351 variant_mig_numbers: vec![],
5352 skipped_segments: vec![],
5353 }],
5354 }],
5355 entry_mig_number: None,
5356 variant_mig_numbers: vec![],
5357 skipped_segments: vec![],
5358 },
5359 AssembledGroupInstance {
5360 segments: vec![AssembledSegment {
5361 tag: "SEQ".to_string(),
5362 elements: vec![vec!["ZD7".to_string()]],
5363 mig_number: None,
5364 }],
5365 child_groups: vec![AssembledGroup {
5366 group_id: "SG10".to_string(),
5367 repetitions: vec![AssembledGroupInstance {
5368 segments: vec![AssembledSegment {
5369 tag: "CCI".to_string(),
5370 elements: vec![vec![], vec![], vec!["ZE6".to_string()]],
5371 mig_number: None,
5372 }],
5373 child_groups: vec![],
5374 entry_mig_number: None,
5375 variant_mig_numbers: vec![],
5376 skipped_segments: vec![],
5377 }],
5378 }],
5379 entry_mig_number: None,
5380 variant_mig_numbers: vec![],
5381 skipped_segments: vec![],
5382 },
5383 ],
5384 }],
5385 entry_mig_number: None,
5386 variant_mig_numbers: vec![],
5387 skipped_segments: vec![],
5388 }],
5389 }],
5390 post_group_start: 0,
5391 inter_group_segments: std::collections::BTreeMap::new(),
5392 };
5393
5394 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_z98.sg10");
5396 assert!(inst.is_some());
5397 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
5398
5399 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zd7.sg10");
5401 assert!(inst.is_some());
5402 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZE6");
5403
5404 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zzz.sg10");
5406 assert!(inst.is_none());
5407
5408 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8.sg10");
5410 assert!(inst.is_some());
5411 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
5412 }
5413
5414 #[test]
5415 fn test_parse_source_path_part() {
5416 assert_eq!(parse_source_path_part("sg4"), ("sg4", None));
5417 assert_eq!(parse_source_path_part("sg8_z98"), ("sg8", Some("z98")));
5418 assert_eq!(parse_source_path_part("sg10"), ("sg10", None));
5419 assert_eq!(parse_source_path_part("sg12_z04"), ("sg12", Some("z04")));
5420 }
5421
5422 #[test]
5423 fn test_has_source_path_qualifiers() {
5424 assert!(has_source_path_qualifiers("sg4.sg8_z98.sg10"));
5425 assert!(has_source_path_qualifiers("sg4.sg8_ze1.sg9"));
5426 assert!(!has_source_path_qualifiers("sg4.sg6"));
5427 assert!(!has_source_path_qualifiers("sg4.sg8.sg10"));
5428 }
5429
5430 #[test]
5431 fn test_companion_dotted_path_forward() {
5432 use mig_assembly::assembler::*;
5433
5434 let tree = AssembledTree {
5436 segments: vec![],
5437 groups: vec![AssembledGroup {
5438 group_id: "SG4".to_string(),
5439 repetitions: vec![AssembledGroupInstance {
5440 segments: vec![],
5441 child_groups: vec![AssembledGroup {
5442 group_id: "SG8".to_string(),
5443 repetitions: vec![AssembledGroupInstance {
5444 segments: vec![],
5445 child_groups: vec![AssembledGroup {
5446 group_id: "SG10".to_string(),
5447 repetitions: vec![AssembledGroupInstance {
5448 segments: vec![AssembledSegment {
5449 tag: "CCI".to_string(),
5450 elements: vec![
5451 vec!["11XAB-1234".to_string()],
5452 vec!["305".to_string()],
5453 ],
5454 mig_number: None,
5455 }],
5456 child_groups: vec![],
5457 entry_mig_number: None,
5458 variant_mig_numbers: vec![],
5459 skipped_segments: vec![],
5460 }],
5461 }],
5462 entry_mig_number: None,
5463 variant_mig_numbers: vec![],
5464 skipped_segments: vec![],
5465 }],
5466 }],
5467 entry_mig_number: None,
5468 variant_mig_numbers: vec![],
5469 skipped_segments: vec![],
5470 }],
5471 }],
5472 post_group_start: 0,
5473 inter_group_segments: std::collections::BTreeMap::new(),
5474 };
5475
5476 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5478 companion_fields.insert(
5479 "cci.0".to_string(),
5480 FieldMapping::Simple("bilanzkreis.id".to_string()),
5481 );
5482 companion_fields.insert(
5483 "cci.1".to_string(),
5484 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
5485 );
5486
5487 let def = MappingDefinition {
5488 meta: MappingMeta {
5489 entity: "Test".to_string(),
5490 bo4e_type: "Test".to_string(),
5491 companion_type: Some("TestEdifact".to_string()),
5492 source_group: "SG4.SG8.SG10".to_string(),
5493 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5494 discriminator: None,
5495 repeat_on_tag: None,
5496 },
5497 fields: IndexMap::new(),
5498 companion_fields: Some(companion_fields),
5499 complex_handlers: None,
5500 };
5501
5502 let engine = MappingEngine::from_definitions(vec![]);
5503 let bo4e = engine.map_forward(&tree, &def, 0);
5504
5505 let companion = &bo4e["testEdifact"];
5507 assert!(
5508 companion.is_object(),
5509 "testEdifact should be an object, got: {companion}"
5510 );
5511 let bilanzkreis = &companion["bilanzkreis"];
5512 assert!(
5513 bilanzkreis.is_object(),
5514 "bilanzkreis should be a nested object, got: {bilanzkreis}"
5515 );
5516 assert_eq!(
5517 bilanzkreis["id"].as_str(),
5518 Some("11XAB-1234"),
5519 "bilanzkreis.id should be 11XAB-1234"
5520 );
5521 assert_eq!(
5522 bilanzkreis["codelist"].as_str(),
5523 Some("305"),
5524 "bilanzkreis.codelist should be 305"
5525 );
5526 }
5527
5528 #[test]
5529 fn test_companion_dotted_path_reverse() {
5530 let engine = MappingEngine::from_definitions(vec![]);
5532
5533 let companion_value = serde_json::json!({
5534 "bilanzkreis": {
5535 "id": "11XAB-1234",
5536 "codelist": "305"
5537 }
5538 });
5539
5540 assert_eq!(
5541 engine.populate_field(&companion_value, "bilanzkreis.id"),
5542 Some("11XAB-1234".to_string()),
5543 "dotted path bilanzkreis.id should resolve"
5544 );
5545 assert_eq!(
5546 engine.populate_field(&companion_value, "bilanzkreis.codelist"),
5547 Some("305".to_string()),
5548 "dotted path bilanzkreis.codelist should resolve"
5549 );
5550
5551 let mut companion_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5553 companion_fields.insert(
5554 "cci.0".to_string(),
5555 FieldMapping::Simple("bilanzkreis.id".to_string()),
5556 );
5557 companion_fields.insert(
5558 "cci.1".to_string(),
5559 FieldMapping::Simple("bilanzkreis.codelist".to_string()),
5560 );
5561
5562 let def = MappingDefinition {
5563 meta: MappingMeta {
5564 entity: "Test".to_string(),
5565 bo4e_type: "Test".to_string(),
5566 companion_type: Some("TestEdifact".to_string()),
5567 source_group: "SG4.SG8.SG10".to_string(),
5568 source_path: Some("sg4.sg8_z01.sg10".to_string()),
5569 discriminator: None,
5570 repeat_on_tag: None,
5571 },
5572 fields: IndexMap::new(),
5573 companion_fields: Some(companion_fields),
5574 complex_handlers: None,
5575 };
5576
5577 let bo4e = serde_json::json!({
5578 "testEdifact": {
5579 "bilanzkreis": {
5580 "id": "11XAB-1234",
5581 "codelist": "305"
5582 }
5583 }
5584 });
5585
5586 let instance = engine.map_reverse(&bo4e, &def);
5587 assert_eq!(instance.segments.len(), 1, "should produce one CCI segment");
5588 let cci = &instance.segments[0];
5589 assert_eq!(cci.tag, "CCI");
5590 assert_eq!(
5591 cci.elements[0],
5592 vec!["11XAB-1234"],
5593 "element 0 should contain bilanzkreis.id"
5594 );
5595 assert_eq!(
5596 cci.elements[1],
5597 vec!["305"],
5598 "element 1 should contain bilanzkreis.codelist"
5599 );
5600 }
5601
5602 #[test]
5603 fn test_when_filled_injects_when_field_present() {
5604 let toml_str = r#"
5605[meta]
5606entity = "Test"
5607bo4e_type = "Test"
5608companion_type = "TestEdifact"
5609source_group = "SG4.SG8.SG10"
5610
5611[fields]
5612
5613[companion_fields]
5614"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmalCode"] }
5615"cav.0.0" = "merkmalCode"
5616"#;
5617 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5618
5619 let bo4e_with = serde_json::json!({
5621 "testEdifact": { "merkmalCode": "ZA7" }
5622 });
5623 let engine = MappingEngine::new_empty();
5624 let instance = engine.map_reverse(&bo4e_with, &def);
5625 let cci = instance
5626 .segments
5627 .iter()
5628 .find(|s| s.tag == "CCI")
5629 .expect("CCI should exist");
5630 assert_eq!(cci.elements[0][0], "Z83");
5631
5632 let bo4e_without = serde_json::json!({
5634 "testEdifact": {}
5635 });
5636 let instance2 = engine.map_reverse(&bo4e_without, &def);
5637 let cci2 = instance2.segments.iter().find(|s| s.tag == "CCI");
5638 assert!(
5639 cci2.is_none(),
5640 "CCI should not be emitted when merkmalCode is absent"
5641 );
5642 }
5643
5644 #[test]
5645 fn test_when_filled_checks_core_and_companion() {
5646 let toml_str = r#"
5647[meta]
5648entity = "Test"
5649bo4e_type = "Test"
5650companion_type = "TestEdifact"
5651source_group = "SG4.SG5"
5652
5653[fields]
5654"loc.1.0" = "marktlokationsId"
5655
5656[companion_fields]
5657"loc.0.0" = { target = "", default = "Z16", when_filled = ["marktlokationsId"] }
5658"#;
5659 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5660
5661 let bo4e_with = serde_json::json!({
5663 "marktlokationsId": "51234567890"
5664 });
5665 let engine = MappingEngine::new_empty();
5666 let instance = engine.map_reverse(&bo4e_with, &def);
5667 let loc = instance
5668 .segments
5669 .iter()
5670 .find(|s| s.tag == "LOC")
5671 .expect("LOC should exist");
5672 assert_eq!(loc.elements[0][0], "Z16");
5673 assert_eq!(loc.elements[1][0], "51234567890");
5674
5675 let bo4e_without = serde_json::json!({});
5677 let instance2 = engine.map_reverse(&bo4e_without, &def);
5678 let loc2 = instance2.segments.iter().find(|s| s.tag == "LOC");
5679 assert!(loc2.is_none());
5680 }
5681
5682 #[test]
5683 fn test_extract_all_from_instance_collects_all_qualifier_matches() {
5684 use mig_assembly::assembler::*;
5685
5686 let instance = AssembledGroupInstance {
5688 segments: vec![
5689 AssembledSegment {
5690 tag: "SEQ".to_string(),
5691 elements: vec![vec!["ZD6".to_string()]],
5692 mig_number: None,
5693 },
5694 AssembledSegment {
5695 tag: "RFF".to_string(),
5696 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
5697 mig_number: None,
5698 },
5699 AssembledSegment {
5700 tag: "RFF".to_string(),
5701 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
5702 mig_number: None,
5703 },
5704 AssembledSegment {
5705 tag: "RFF".to_string(),
5706 elements: vec![vec!["Z34".to_string(), "REF_C".to_string()]],
5707 mig_number: None,
5708 },
5709 AssembledSegment {
5710 tag: "RFF".to_string(),
5711 elements: vec![vec!["Z35".to_string(), "OTHER".to_string()]],
5712 mig_number: None,
5713 },
5714 ],
5715 child_groups: vec![],
5716 entry_mig_number: None,
5717 variant_mig_numbers: vec![],
5718 skipped_segments: vec![],
5719 };
5720
5721 let all = MappingEngine::extract_all_from_instance(&instance, "rff[Z34,*].0.1");
5723 assert_eq!(all, vec!["REF_A", "REF_B", "REF_C"]);
5724
5725 let single = MappingEngine::extract_from_instance(&instance, "rff[Z34].0.1");
5727 assert_eq!(single, Some("REF_A".to_string()));
5728
5729 let second = MappingEngine::extract_from_instance(&instance, "rff[Z34,1].0.1");
5730 assert_eq!(second, Some("REF_B".to_string()));
5731 }
5732
5733 #[test]
5734 fn test_forward_wildcard_collect_produces_json_array() {
5735 use mig_assembly::assembler::*;
5736
5737 let instance = AssembledGroupInstance {
5738 segments: vec![
5739 AssembledSegment {
5740 tag: "SEQ".to_string(),
5741 elements: vec![vec!["ZD6".to_string()]],
5742 mig_number: None,
5743 },
5744 AssembledSegment {
5745 tag: "RFF".to_string(),
5746 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
5747 mig_number: None,
5748 },
5749 AssembledSegment {
5750 tag: "RFF".to_string(),
5751 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
5752 mig_number: None,
5753 },
5754 ],
5755 child_groups: vec![],
5756 entry_mig_number: None,
5757 variant_mig_numbers: vec![],
5758 skipped_segments: vec![],
5759 };
5760
5761 let toml_str = r#"
5762[meta]
5763entity = "Test"
5764bo4e_type = "Test"
5765companion_type = "TestEdifact"
5766source_group = "SG4.SG8"
5767
5768[fields]
5769
5770[companion_fields]
5771"rff[Z34,*].0.1" = "messlokationsIdRefs"
5772"#;
5773 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5774 let engine = MappingEngine::new_empty();
5775
5776 let mut result = serde_json::Map::new();
5777 engine.extract_companion_fields(&instance, &def, &mut result, false);
5778
5779 let companion = result.get("testEdifact").unwrap().as_object().unwrap();
5780 let refs = companion
5781 .get("messlokationsIdRefs")
5782 .unwrap()
5783 .as_array()
5784 .unwrap();
5785 assert_eq!(refs.len(), 2);
5786 assert_eq!(refs[0].as_str().unwrap(), "REF_A");
5787 assert_eq!(refs[1].as_str().unwrap(), "REF_B");
5788 }
5789
5790 #[test]
5791 fn test_reverse_json_array_produces_multiple_segments() {
5792 let toml_str = r#"
5793[meta]
5794entity = "Test"
5795bo4e_type = "Test"
5796companion_type = "TestEdifact"
5797source_group = "SG4.SG8"
5798
5799[fields]
5800
5801[companion_fields]
5802"seq.0.0" = { target = "", default = "ZD6" }
5803"rff[Z34,*].0.1" = "messlokationsIdRefs"
5804"#;
5805 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5806 let engine = MappingEngine::new_empty();
5807
5808 let bo4e = serde_json::json!({
5809 "testEdifact": {
5810 "messlokationsIdRefs": ["REF_A", "REF_B", "REF_C"]
5811 }
5812 });
5813
5814 let instance = engine.map_reverse(&bo4e, &def);
5815
5816 let rff_segs: Vec<_> = instance
5818 .segments
5819 .iter()
5820 .filter(|s| s.tag == "RFF")
5821 .collect();
5822 assert_eq!(rff_segs.len(), 3);
5823 assert_eq!(rff_segs[0].elements[0][0], "Z34");
5824 assert_eq!(rff_segs[0].elements[0][1], "REF_A");
5825 assert_eq!(rff_segs[1].elements[0][0], "Z34");
5826 assert_eq!(rff_segs[1].elements[0][1], "REF_B");
5827 assert_eq!(rff_segs[2].elements[0][0], "Z34");
5828 assert_eq!(rff_segs[2].elements[0][1], "REF_C");
5829 }
5830
5831 #[test]
5832 fn test_when_filled_dotted_path() {
5833 let toml_str = r#"
5834[meta]
5835entity = "Test"
5836bo4e_type = "Test"
5837companion_type = "TestEdifact"
5838source_group = "SG4.SG8.SG10"
5839
5840[fields]
5841
5842[companion_fields]
5843"cci.0.0" = { target = "", default = "Z83", when_filled = ["merkmal.code"] }
5844"cav.0.0" = "merkmal.code"
5845"#;
5846 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5847
5848 let bo4e = serde_json::json!({
5849 "testEdifact": { "merkmal": { "code": "ZA7" } }
5850 });
5851 let engine = MappingEngine::new_empty();
5852 let instance = engine.map_reverse(&bo4e, &def);
5853 let cci = instance
5854 .segments
5855 .iter()
5856 .find(|s| s.tag == "CCI")
5857 .expect("CCI should exist");
5858 assert_eq!(cci.elements[0][0], "Z83");
5859 }
5860
5861 #[test]
5862 fn test_also_target_forward_extracts_both_fields() {
5863 use mig_assembly::assembler::*;
5864
5865 let instance = AssembledGroupInstance {
5866 segments: vec![AssembledSegment {
5867 tag: "NAD".to_string(),
5868 elements: vec![vec!["Z47".to_string()], vec!["12345".to_string()]],
5869 mig_number: None,
5870 }],
5871 child_groups: vec![],
5872 entry_mig_number: None,
5873 variant_mig_numbers: vec![],
5874 skipped_segments: vec![],
5875 };
5876
5877 let toml_str = r#"
5878[meta]
5879entity = "Geschaeftspartner"
5880bo4e_type = "Geschaeftspartner"
5881companion_type = "GeschaeftspartnerEdifact"
5882source_group = "SG4.SG12"
5883
5884[fields]
5885"nad.1.0" = "identifikation"
5886
5887[companion_fields."nad.0.0"]
5888target = "partnerrolle"
5889enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
5890also_target = "datenqualitaet"
5891also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
5892"#;
5893 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5894 let engine = MappingEngine::new_empty();
5895
5896 let mut result = serde_json::Map::new();
5897 engine.extract_companion_fields(&instance, &def, &mut result, false);
5898
5899 let companion = result
5900 .get("geschaeftspartnerEdifact")
5901 .unwrap()
5902 .as_object()
5903 .unwrap();
5904 assert_eq!(
5905 companion.get("partnerrolle").unwrap().as_str().unwrap(),
5906 "kundeDesLf"
5907 );
5908 assert_eq!(
5909 companion.get("datenqualitaet").unwrap().as_str().unwrap(),
5910 "erwartet"
5911 );
5912 }
5913
5914 #[test]
5915 fn test_also_target_reverse_joint_lookup() {
5916 let toml_str = r#"
5917[meta]
5918entity = "Geschaeftspartner"
5919bo4e_type = "Geschaeftspartner"
5920companion_type = "GeschaeftspartnerEdifact"
5921source_group = "SG4.SG12"
5922
5923[fields]
5924
5925[companion_fields."nad.0.0"]
5926target = "partnerrolle"
5927enum_map = { "Z47" = "kundeDesLf", "Z48" = "kundeDesLf", "Z51" = "kundeDesNb", "Z52" = "kundeDesNb" }
5928also_target = "datenqualitaet"
5929also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden", "Z51" = "erwartet", "Z52" = "imSystemVorhanden" }
5930"#;
5931 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5932 let engine = MappingEngine::new_empty();
5933
5934 let bo4e = serde_json::json!({
5936 "geschaeftspartnerEdifact": {
5937 "partnerrolle": "kundeDesLf",
5938 "datenqualitaet": "erwartet"
5939 }
5940 });
5941 let instance = engine.map_reverse(&bo4e, &def);
5942 let nad = instance
5943 .segments
5944 .iter()
5945 .find(|s| s.tag == "NAD")
5946 .expect("NAD");
5947 assert_eq!(nad.elements[0][0], "Z47");
5948
5949 let bo4e2 = serde_json::json!({
5951 "geschaeftspartnerEdifact": {
5952 "partnerrolle": "kundeDesNb",
5953 "datenqualitaet": "imSystemVorhanden"
5954 }
5955 });
5956 let instance2 = engine.map_reverse(&bo4e2, &def);
5957 let nad2 = instance2
5958 .segments
5959 .iter()
5960 .find(|s| s.tag == "NAD")
5961 .expect("NAD");
5962 assert_eq!(nad2.elements[0][0], "Z52");
5963 }
5964
5965 #[test]
5966 fn test_also_target_mixed_codes_unpaired_skips_datenqualitaet() {
5967 use mig_assembly::assembler::*;
5968
5969 let toml_str = r#"
5971[meta]
5972entity = "Geschaeftspartner"
5973bo4e_type = "Geschaeftspartner"
5974companion_type = "GeschaeftspartnerEdifact"
5975source_group = "SG4.SG12"
5976
5977[fields]
5978
5979[companion_fields."nad.0.0"]
5980target = "partnerrolle"
5981enum_map = { "Z09" = "kundeDesLf", "Z47" = "kundeDesLf", "Z48" = "kundeDesLf" }
5982also_target = "datenqualitaet"
5983also_enum_map = { "Z47" = "erwartet", "Z48" = "imSystemVorhanden" }
5984"#;
5985 let def: MappingDefinition = toml::from_str(toml_str).unwrap();
5986 let engine = MappingEngine::new_empty();
5987
5988 let instance_z09 = AssembledGroupInstance {
5990 segments: vec![AssembledSegment {
5991 tag: "NAD".to_string(),
5992 elements: vec![vec!["Z09".to_string()]],
5993 mig_number: None,
5994 }],
5995 child_groups: vec![],
5996 entry_mig_number: None,
5997 variant_mig_numbers: vec![],
5998 skipped_segments: vec![],
5999 };
6000 let mut result = serde_json::Map::new();
6001 engine.extract_companion_fields(&instance_z09, &def, &mut result, false);
6002 let comp = result
6003 .get("geschaeftspartnerEdifact")
6004 .unwrap()
6005 .as_object()
6006 .unwrap();
6007 assert_eq!(
6008 comp.get("partnerrolle").unwrap().as_str().unwrap(),
6009 "kundeDesLf"
6010 );
6011 assert!(
6012 comp.get("datenqualitaet").is_none(),
6013 "Z09 should not set datenqualitaet"
6014 );
6015
6016 let bo4e = serde_json::json!({
6018 "geschaeftspartnerEdifact": { "partnerrolle": "kundeDesLf" }
6019 });
6020 let instance = engine.map_reverse(&bo4e, &def);
6021 let nad = instance
6022 .segments
6023 .iter()
6024 .find(|s| s.tag == "NAD")
6025 .expect("NAD");
6026 assert_eq!(nad.elements[0][0], "Z09");
6027
6028 let bo4e2 = serde_json::json!({
6030 "geschaeftspartnerEdifact": {
6031 "partnerrolle": "kundeDesLf",
6032 "datenqualitaet": "erwartet"
6033 }
6034 });
6035 let instance2 = engine.map_reverse(&bo4e2, &def);
6036 let nad2 = instance2
6037 .segments
6038 .iter()
6039 .find(|s| s.tag == "NAD")
6040 .expect("NAD");
6041 assert_eq!(nad2.elements[0][0], "Z47");
6042 }
6043}