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 current_pid: Option<String>,
40}
41
42impl MappingEngine {
43 pub fn new_empty() -> Self {
45 Self {
46 definitions: Vec::new(),
47 segment_structure: None,
48 code_lookup: None,
49 transaction_group: None,
50 current_pid: None,
51 }
52 }
53
54 pub fn load(dir: &Path) -> Result<Self, MappingError> {
56 let mut definitions = Vec::new();
57
58 let mut entries: Vec<_> = std::fs::read_dir(dir)?.filter_map(|e| e.ok()).collect();
59 entries.sort_by_key(|e| e.file_name());
60
61 for entry in entries {
62 let path = entry.path();
63 if path.extension().map(|e| e == "toml").unwrap_or(false) {
64 let content = std::fs::read_to_string(&path)?;
65 let def: MappingDefinition =
66 toml::from_str(&content).map_err(|e| MappingError::TomlParse {
67 file: path.display().to_string(),
68 message: e.to_string(),
69 })?;
70 definitions.push(def);
71 }
72 }
73
74 Ok(Self {
75 definitions,
76 segment_structure: None,
77 code_lookup: None,
78 transaction_group: None,
79 current_pid: None,
80 })
81 }
82
83 pub fn load_split(
89 message_dir: &Path,
90 transaction_dir: &Path,
91 ) -> Result<(Self, Self), MappingError> {
92 let msg_engine = Self::load(message_dir)?;
93 let tx_engine = Self::load(transaction_dir)?;
94 Ok((msg_engine, tx_engine))
95 }
96
97 pub fn load_merged(dirs: &[&Path]) -> Result<Self, MappingError> {
102 let mut definitions = Vec::new();
103 for dir in dirs {
104 let engine = Self::load(dir)?;
105 definitions.extend(engine.definitions);
106 }
107 Ok(Self {
108 definitions,
109 segment_structure: None,
110 code_lookup: None,
111 transaction_group: None,
112 current_pid: None,
113 })
114 }
115
116 pub fn load_with_common(
125 common_dir: &Path,
126 pid_dir: &Path,
127 schema_index: &crate::pid_schema_index::PidSchemaIndex,
128 ) -> Result<Self, MappingError> {
129 let mut common_defs = Self::load(common_dir)?.definitions;
130
131 common_defs.retain(|d| {
133 d.meta
134 .source_path
135 .as_deref()
136 .map(|sp| schema_index.has_group(sp))
137 .unwrap_or(true)
138 });
139
140 let pid_defs = Self::load(pid_dir)?.definitions;
141
142 let normalize_sg = |sg: &str| -> String {
147 sg.split('.')
148 .map(|part| part.split(':').next().unwrap_or(part))
149 .collect::<Vec<_>>()
150 .join(".")
151 };
152 let pid_keys: HashSet<(String, Option<String>)> = pid_defs
153 .iter()
154 .flat_map(|d| {
155 let sg = normalize_sg(&d.meta.source_group);
156 let disc = d.meta.discriminator.clone();
157 let mut keys = vec![(sg.clone(), disc.clone())];
158 if let Some(ref disc_str) = disc {
160 if let Some(base) = disc_str.rsplit_once('#') {
161 if base.1.chars().all(|c| c.is_ascii_digit()) {
162 keys.push((sg, Some(base.0.to_string())));
163 }
164 }
165 }
166 keys
167 })
168 .collect();
169
170 common_defs.retain(|d| {
172 let key = (
173 normalize_sg(&d.meta.source_group),
174 d.meta.discriminator.clone(),
175 );
176 !pid_keys.contains(&key)
177 });
178
179 let mut definitions = common_defs;
181 definitions.extend(pid_defs);
182
183 Ok(Self {
184 definitions,
185 segment_structure: None,
186 code_lookup: None,
187 transaction_group: None,
188 current_pid: None,
189 })
190 }
191
192 pub fn load_common_only(
196 common_dir: &Path,
197 schema_index: &crate::pid_schema_index::PidSchemaIndex,
198 ) -> Result<Self, MappingError> {
199 let mut common_defs = Self::load(common_dir)?.definitions;
200
201 common_defs.retain(|d| {
203 d.meta
204 .source_path
205 .as_deref()
206 .map(|sp| schema_index.has_group(sp))
207 .unwrap_or(true)
208 });
209
210 Ok(Self {
211 definitions: common_defs,
212 segment_structure: None,
213 code_lookup: None,
214 transaction_group: None,
215 current_pid: None,
216 })
217 }
218
219 pub fn load_split_with_common(
224 message_dir: &Path,
225 common_dir: &Path,
226 transaction_dir: &Path,
227 schema_index: &crate::pid_schema_index::PidSchemaIndex,
228 ) -> Result<(Self, Self), MappingError> {
229 let msg_engine = Self::load(message_dir)?;
230 let tx_engine = Self::load_with_common(common_dir, transaction_dir, schema_index)?;
231 Ok((msg_engine, tx_engine))
232 }
233
234 pub fn from_definitions(definitions: Vec<MappingDefinition>) -> Self {
236 Self {
237 definitions,
238 segment_structure: None,
239 code_lookup: None,
240 transaction_group: None,
241 current_pid: None,
242 }
243 }
244
245 pub fn save_cached(&self, path: &Path) -> Result<(), MappingError> {
251 let encoded =
252 serde_json::to_vec(&self.definitions).map_err(|e| MappingError::CacheWrite {
253 path: path.display().to_string(),
254 message: e.to_string(),
255 })?;
256 if let Some(parent) = path.parent() {
257 std::fs::create_dir_all(parent)?;
258 }
259 std::fs::write(path, encoded)?;
260 Ok(())
261 }
262
263 pub fn load_cached_or_toml(cache_path: &Path, toml_dir: &Path) -> Result<Self, MappingError> {
268 if cache_path.exists() {
269 Self::load_cached(cache_path)
270 } else {
271 Self::load(toml_dir)
272 }
273 }
274
275 pub fn load_cached(path: &Path) -> Result<Self, MappingError> {
280 let bytes = std::fs::read(path)?;
281 let definitions: Vec<MappingDefinition> =
282 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
283 path: path.display().to_string(),
284 message: e.to_string(),
285 })?;
286 Ok(Self {
287 definitions,
288 segment_structure: None,
289 code_lookup: None,
290 transaction_group: None,
291 current_pid: None,
292 })
293 }
294
295 pub fn with_segment_structure(mut self, ss: SegmentStructure) -> Self {
300 self.segment_structure = Some(ss);
301 self
302 }
303
304 pub fn with_code_lookup(mut self, cl: crate::code_lookup::CodeLookup) -> Self {
309 self.code_lookup = Some(cl);
310 self
311 }
312
313 pub fn with_pid(mut self, pid: impl Into<String>) -> Self {
321 self.current_pid = Some(pid.into());
322 self
323 }
324
325 pub fn with_path_resolver(mut self, resolver: crate::path_resolver::PathResolver) -> Self {
331 for def in &mut self.definitions {
332 def.normalize_paths(&resolver);
333 }
334 self
335 }
336
337 pub fn with_transaction_group(mut self, tx: impl Into<String>) -> Self {
344 self.transaction_group = Some(tx.into());
345 self
346 }
347
348 pub fn definitions(&self) -> &[MappingDefinition] {
350 &self.definitions
351 }
352
353 pub fn definition_for_entity(&self, entity: &str) -> Option<&MappingDefinition> {
355 self.definitions.iter().find(|d| d.meta.entity == entity)
356 }
357
358 pub fn extract_field(
367 &self,
368 tree: &AssembledTree,
369 group_path: &str,
370 path: &str,
371 repetition: usize,
372 ) -> Option<String> {
373 let instance = Self::resolve_group_instance(tree, group_path, repetition)?;
374 Self::extract_from_instance(instance, path)
375 }
376
377 pub fn resolve_group_instance<'a>(
386 tree: &'a AssembledTree,
387 group_path: &str,
388 repetition: usize,
389 ) -> Option<&'a AssembledGroupInstance> {
390 let parts: Vec<&str> = group_path.split('.').collect();
391
392 let (first_id, first_rep) = parse_group_spec(parts[0]);
393 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
394
395 if parts.len() == 1 {
396 let rep = first_rep.unwrap_or(repetition);
398 return first_group.repetitions.get(rep);
399 }
400
401 let mut current_instance = first_group.repetitions.get(first_rep.unwrap_or(0))?;
404
405 for (i, part) in parts[1..].iter().enumerate() {
406 let (group_id, explicit_rep) = parse_group_spec(part);
407 let child_group = current_instance
408 .child_groups
409 .iter()
410 .find(|g| g.group_id == group_id)?;
411
412 if i == parts.len() - 2 {
413 let rep = explicit_rep.unwrap_or(repetition);
415 return child_group.repetitions.get(rep);
416 }
417 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
419 }
420
421 None
422 }
423
424 pub fn resolve_by_source_path<'a>(
432 tree: &'a AssembledTree,
433 source_path: &str,
434 ) -> Option<&'a AssembledGroupInstance> {
435 let parts: Vec<&str> = source_path.split('.').collect();
436 if parts.is_empty() {
437 return None;
438 }
439
440 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
441 let first_group = tree
442 .groups
443 .iter()
444 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))?;
445
446 let mut current_instance = if let Some(q) = first_qualifier {
447 find_rep_by_entry_qualifier(&first_group.repetitions, q)?
448 } else {
449 first_group.repetitions.first()?
450 };
451
452 if parts.len() == 1 {
453 return Some(current_instance);
454 }
455
456 for part in &parts[1..] {
457 let (group_id, qualifier) = parse_source_path_part(part);
458 let child_group = current_instance
459 .child_groups
460 .iter()
461 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))?;
462
463 current_instance = if let Some(q) = qualifier {
464 find_rep_by_entry_qualifier(&child_group.repetitions, q)?
465 } else {
466 child_group.repetitions.first()?
467 };
468 }
469
470 Some(current_instance)
471 }
472
473 pub fn resolve_all_by_source_path<'a>(
481 tree: &'a AssembledTree,
482 source_path: &str,
483 ) -> Vec<&'a AssembledGroupInstance> {
484 let parts: Vec<&str> = source_path.split('.').collect();
485 if parts.is_empty() {
486 return vec![];
487 }
488
489 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
491 let first_group = match tree
492 .groups
493 .iter()
494 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
495 {
496 Some(g) => g,
497 None => return vec![],
498 };
499
500 let mut current_instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
501 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
502 } else {
503 first_group.repetitions.iter().collect()
504 };
505
506 for part in &parts[1..] {
509 let (group_id, qualifier) = parse_source_path_part(part);
510 let mut next_instances = Vec::new();
511
512 for instance in ¤t_instances {
513 if let Some(child_group) = instance
514 .child_groups
515 .iter()
516 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
517 {
518 if let Some(q) = qualifier {
519 next_instances.extend(find_all_reps_by_entry_qualifier(
520 &child_group.repetitions,
521 q,
522 ));
523 } else {
524 next_instances.extend(child_group.repetitions.iter());
525 }
526 }
527 }
528
529 current_instances = next_instances;
530 }
531
532 current_instances
533 }
534
535 fn compute_child_indices(
548 tree: &AssembledTree,
549 source_path: &str,
550 indexed: &[(usize, &AssembledGroupInstance)],
551 ) -> Vec<usize> {
552 let parts: Vec<&str> = source_path.split('.').collect();
553 if parts.len() < 2 {
554 return vec![];
555 }
556 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
558 let first_group = match tree
559 .groups
560 .iter()
561 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
562 {
563 Some(g) => g,
564 None => return vec![],
565 };
566 let parent_reps: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
567 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
568 } else {
569 first_group.repetitions.iter().collect()
570 };
571 let (child_id, _child_qualifier) = parse_source_path_part(parts[parts.len() - 1]);
573 let mut result = Vec::new();
574 for (_, inst) in indexed {
575 let mut found = false;
577 for parent in &parent_reps {
578 if let Some(child_group) = parent
579 .child_groups
580 .iter()
581 .find(|g| g.group_id.eq_ignore_ascii_case(child_id))
582 {
583 if let Some(pos) = child_group
584 .repetitions
585 .iter()
586 .position(|r| std::ptr::eq(r, *inst))
587 {
588 result.push(pos);
589 found = true;
590 break;
591 }
592 }
593 }
594 if !found {
595 result.push(usize::MAX); }
597 }
598 result
599 }
600
601 pub fn resolve_all_with_parent_indices<'a>(
603 tree: &'a AssembledTree,
604 source_path: &str,
605 ) -> Vec<(usize, &'a AssembledGroupInstance)> {
606 let parts: Vec<&str> = source_path.split('.').collect();
607 if parts.is_empty() {
608 return vec![];
609 }
610
611 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
613 let first_group = match tree
614 .groups
615 .iter()
616 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
617 {
618 Some(g) => g,
619 None => return vec![],
620 };
621
622 if parts.len() == 1 {
624 let instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
625 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
626 } else {
627 first_group.repetitions.iter().collect()
628 };
629 return instances.into_iter().map(|i| (0, i)).collect();
630 }
631
632 let first_reps: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = first_qualifier {
637 let matching = find_all_reps_by_entry_qualifier(&first_group.repetitions, q);
638 let mut result = Vec::new();
639 for m in matching {
640 let idx = first_group
641 .repetitions
642 .iter()
643 .position(|r| std::ptr::eq(r, m))
644 .unwrap_or(0);
645 result.push((idx, m));
646 }
647 result
648 } else {
649 first_group.repetitions.iter().enumerate().collect()
650 };
651
652 let mut current: Vec<(usize, &AssembledGroupInstance)> = first_reps;
653 let remaining = &parts[1..];
654
655 for (level, part) in remaining.iter().enumerate() {
656 let is_leaf = level == remaining.len() - 1;
657 let (group_id, qualifier) = parse_source_path_part(part);
658 let mut next: Vec<(usize, &AssembledGroupInstance)> = Vec::new();
659
660 for (prev_parent_idx, instance) in ¤t {
661 if let Some(child_group) = instance
662 .child_groups
663 .iter()
664 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
665 {
666 let matching: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = qualifier
667 {
668 let filtered =
669 find_all_reps_by_entry_qualifier(&child_group.repetitions, q);
670 filtered
671 .into_iter()
672 .map(|m| {
673 let idx = child_group
674 .repetitions
675 .iter()
676 .position(|r| std::ptr::eq(r, m))
677 .unwrap_or(0);
678 (idx, m)
679 })
680 .collect()
681 } else {
682 child_group.repetitions.iter().enumerate().collect()
683 };
684
685 for (rep_idx, child_rep) in matching {
686 if is_leaf {
687 next.push((*prev_parent_idx, child_rep));
689 } else {
690 next.push((rep_idx, child_rep));
692 }
693 }
694 }
695 }
696
697 current = next;
698 }
699
700 current
701 }
702
703 pub fn extract_from_instance(instance: &AssembledGroupInstance, path: &str) -> Option<String> {
709 let parts: Vec<&str> = path.split('.').collect();
710 if parts.is_empty() {
711 return None;
712 }
713
714 let (segment_tag, qualifier, occurrence) = parse_tag_qualifier(parts[0]);
717
718 let segment = if let Some(q) = qualifier {
719 instance
720 .segments
721 .iter()
722 .filter(|s| {
723 s.tag.eq_ignore_ascii_case(&segment_tag)
724 && s.elements
725 .first()
726 .and_then(|e| e.first())
727 .map(|v| v.as_str())
728 == Some(q)
729 })
730 .nth(occurrence)?
731 } else {
732 instance
733 .segments
734 .iter()
735 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
736 .nth(occurrence)?
737 };
738
739 Self::resolve_field_path(segment, &parts[1..])
740 }
741
742 pub fn extract_all_from_instance(instance: &AssembledGroupInstance, path: &str) -> Vec<String> {
748 let parts: Vec<&str> = path.split('.').collect();
749 if parts.is_empty() {
750 return vec![];
751 }
752
753 let (segment_tag, qualifier, _) = parse_tag_qualifier(parts[0]);
754
755 let matching_segments: Vec<&AssembledSegment> = if let Some(q) = qualifier {
756 instance
757 .segments
758 .iter()
759 .filter(|s| {
760 s.tag.eq_ignore_ascii_case(&segment_tag)
761 && s.elements
762 .first()
763 .and_then(|e| e.first())
764 .map(|v| v.as_str())
765 == Some(q)
766 })
767 .collect()
768 } else {
769 instance
770 .segments
771 .iter()
772 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
773 .collect()
774 };
775
776 matching_segments
777 .into_iter()
778 .filter_map(|seg| Self::resolve_field_path(seg, &parts[1..]))
779 .collect()
780 }
781
782 pub fn map_forward(
788 &self,
789 tree: &AssembledTree,
790 def: &MappingDefinition,
791 repetition: usize,
792 ) -> serde_json::Value {
793 self.map_forward_inner(tree, def, repetition, true)
794 }
795
796 fn map_forward_inner(
798 &self,
799 tree: &AssembledTree,
800 def: &MappingDefinition,
801 repetition: usize,
802 enrich_codes: bool,
803 ) -> serde_json::Value {
804 let mut result = serde_json::Map::new();
805
806 if def.meta.source_group.is_empty() {
811 let mut all_root_segs = tree.segments.clone();
812 for segs in tree.inter_group_segments.values() {
813 all_root_segs.extend(segs.iter().cloned());
814 }
815 let root_instance = AssembledGroupInstance {
816 segments: all_root_segs,
817 child_groups: vec![],
818 entry_mig_number: None,
819 variant_mig_numbers: vec![],
820 skipped_segments: Vec::new(),
821 skipped_positions: Vec::new(),
822 };
823 self.extract_fields_from_instance(&root_instance, def, &mut result, enrich_codes);
824 return serde_json::Value::Object(result);
825 }
826
827 let instance = if let Some(ref sp) = def.meta.source_path {
833 if has_source_path_qualifiers(sp) && !def.meta.source_group.contains(':') {
834 Self::resolve_by_source_path(tree, sp).or_else(|| {
835 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
836 })
837 } else {
838 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
839 }
840 } else {
841 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
842 };
843
844 if let Some(instance) = instance {
845 if let Some(ref tag) = def.meta.repeat_on_tag {
847 let matching: Vec<_> = instance
848 .segments
849 .iter()
850 .filter(|s| s.tag.eq_ignore_ascii_case(tag))
851 .collect();
852
853 if matching.len() > 1 {
854 let mut arr = Vec::new();
855 for seg in &matching {
856 let sub_instance = AssembledGroupInstance {
857 segments: vec![(*seg).clone()],
858 child_groups: vec![],
859 entry_mig_number: None,
860 variant_mig_numbers: vec![],
861 skipped_segments: Vec::new(),
862 skipped_positions: Vec::new(),
863 };
864 let mut elem_result = serde_json::Map::new();
865 self.extract_fields_from_instance(
866 &sub_instance,
867 def,
868 &mut elem_result,
869 enrich_codes,
870 );
871 if !elem_result.is_empty() {
872 arr.push(serde_json::Value::Object(elem_result));
873 }
874 }
875 if !arr.is_empty() {
876 return serde_json::Value::Array(arr);
877 }
878 }
879 }
880
881 self.extract_fields_from_instance(instance, def, &mut result, enrich_codes);
882 }
883
884 serde_json::Value::Object(result)
885 }
886
887 fn extract_fields_from_instance(
892 &self,
893 instance: &AssembledGroupInstance,
894 def: &MappingDefinition,
895 result: &mut serde_json::Map<String, serde_json::Value>,
896 enrich_codes: bool,
897 ) {
898 for (path, field_mapping) in &def.fields {
899 let (target, enum_map) = match field_mapping {
900 FieldMapping::Simple(t) => (t.as_str(), None),
901 FieldMapping::Structured(s) => (s.target.as_str(), s.enum_map.as_ref()),
902 FieldMapping::Nested(_) => continue,
903 };
904 if target.is_empty() {
905 continue;
906 }
907 if let Some(val) = Self::extract_from_instance(instance, path) {
908 let mapped_val = if let Some(map) = enum_map {
909 map.get(&val).cloned().unwrap_or_else(|| val.clone())
910 } else {
911 val.clone()
912 };
913
914 if enrich_codes {
916 if let (Some(ref code_lookup), Some(ref source_path)) =
917 (&self.code_lookup, &def.meta.source_path)
918 {
919 let parts: Vec<&str> = path.split('.').collect();
920 let (seg_tag, _qualifier, _occ) = parse_tag_qualifier(parts[0]);
921 let (element_idx, component_idx) =
922 Self::parse_element_component(&parts[1..]);
923 let disc_qualifier = Self::discriminator_qualifier(def);
924 let q = disc_qualifier.as_deref();
925
926 if code_lookup.is_code_field_q(
927 source_path,
928 &seg_tag,
929 q,
930 element_idx,
931 component_idx,
932 ) {
933 if let Some(ref pid) = self.current_pid {
938 if code_lookup.is_pid_self_reference(
939 source_path,
940 &seg_tag,
941 q,
942 element_idx,
943 component_idx,
944 pid,
945 ) {
946 set_nested_value(result, target, mapped_val);
947 continue;
948 }
949 }
950
951 let enrichment = code_lookup.enrichment_for_q(
955 source_path,
956 &seg_tag,
957 q,
958 element_idx,
959 component_idx,
960 &val,
961 );
962 let meaning = enrichment
963 .map(|e| serde_json::Value::String(e.meaning.clone()))
964 .unwrap_or(serde_json::Value::Null);
965
966 let mut obj = serde_json::Map::new();
967 obj.insert("code".into(), serde_json::json!(mapped_val));
968 obj.insert("meaning".into(), meaning);
969 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
970 obj.insert("enum".into(), serde_json::json!(enum_key));
971 }
972 let enriched = serde_json::Value::Object(obj);
973 set_nested_value_json(result, target, enriched);
974 continue;
975 }
976 }
977 }
978
979 set_nested_value(result, target, mapped_val);
980 }
981 }
982 }
983
984 fn discriminator_qualifier(def: &MappingDefinition) -> Option<String> {
991 def.meta
992 .discriminator
993 .as_deref()
994 .and_then(|d| d.split_once('=').map(|(_, v)| v.to_string()))
995 }
996
997 pub fn map_forward_from_segments(
1003 &self,
1004 segments: &[OwnedSegment],
1005 def: &MappingDefinition,
1006 ) -> serde_json::Value {
1007 let assembled_segments: Vec<AssembledSegment> = segments
1008 .iter()
1009 .map(|s| AssembledSegment {
1010 tag: s.id.clone(),
1011 elements: s.elements.clone(),
1012 mig_number: None,
1013 segment_number: Some(s.segment_number),
1014 })
1015 .collect();
1016
1017 let instance = AssembledGroupInstance {
1018 segments: assembled_segments,
1019 child_groups: vec![],
1020 entry_mig_number: None,
1021 variant_mig_numbers: vec![],
1022 skipped_segments: Vec::new(),
1023 skipped_positions: Vec::new(),
1024 };
1025
1026 let mut result = serde_json::Map::new();
1027 self.extract_fields_from_instance(&instance, def, &mut result, true);
1028 serde_json::Value::Object(result)
1029 }
1030
1031 pub fn map_reverse(
1044 &self,
1045 bo4e_value: &serde_json::Value,
1046 def: &MappingDefinition,
1047 ) -> AssembledGroupInstance {
1048 if def.meta.repeat_on_tag.is_some() {
1050 if let Some(arr) = bo4e_value.as_array() {
1051 let mut all_segments = Vec::new();
1052 for elem in arr {
1053 let sub = self.map_reverse_single(elem, def);
1054 all_segments.extend(sub.segments);
1055 }
1056 return AssembledGroupInstance {
1057 segments: all_segments,
1058 child_groups: vec![],
1059 entry_mig_number: None,
1060 variant_mig_numbers: vec![],
1061 skipped_segments: Vec::new(),
1062 skipped_positions: Vec::new(),
1063 };
1064 }
1065 }
1066 self.map_reverse_single(bo4e_value, def)
1067 }
1068
1069 fn map_reverse_single(
1070 &self,
1071 bo4e_value: &serde_json::Value,
1072 def: &MappingDefinition,
1073 ) -> AssembledGroupInstance {
1074 let mut field_values: Vec<(String, String, usize, usize, String)> =
1077 Vec::with_capacity(def.fields.len());
1078
1079 let mut has_real_data = false;
1086 let mut has_data_fields = false;
1087 let mut seg_has_data_field: HashSet<String> = HashSet::new();
1090 let mut seg_has_real_data: HashSet<String> = HashSet::new();
1091 let mut injected_qualifiers: HashSet<String> = HashSet::new();
1092
1093 for (path, field_mapping) in &def.fields {
1094 let (target, default, enum_map, when_filled) = match field_mapping {
1095 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
1096 FieldMapping::Structured(s) => (
1097 s.target.as_str(),
1098 s.default.as_ref(),
1099 s.enum_map.as_ref(),
1100 s.when_filled.as_ref(),
1101 ),
1102 FieldMapping::Nested(_) => continue,
1103 };
1104
1105 let parts: Vec<&str> = path.split('.').collect();
1106 if parts.len() < 2 {
1107 continue;
1108 }
1109
1110 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1111 let seg_key = parts[0].to_uppercase();
1114 let sub_path = &parts[1..];
1115
1116 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1118 let ci = if sub_path.len() > 1 {
1119 sub_path[1].parse::<usize>().unwrap_or(0)
1120 } else {
1121 0
1122 };
1123 (ei, ci)
1124 } else {
1125 match sub_path.len() {
1126 1 => (0, 0),
1127 2 => (1, 0),
1128 _ => continue,
1129 }
1130 };
1131
1132 let val = if target.is_empty() {
1134 match (default, when_filled) {
1135 (Some(d), Some(fields)) => {
1137 let any_filled = fields
1138 .iter()
1139 .any(|f| self.populate_field(bo4e_value, f).is_some());
1140 if any_filled {
1141 has_real_data = true;
1144 Some(d.clone())
1145 } else {
1146 None
1147 }
1148 }
1149 (Some(d), None) => Some(d.clone()),
1151 (None, _) => None,
1152 }
1153 } else {
1154 has_data_fields = true;
1155 seg_has_data_field.insert(seg_key.clone());
1156 let bo4e_val = self.populate_field(bo4e_value, target);
1157 if bo4e_val.is_some() {
1158 has_real_data = true;
1159 seg_has_real_data.insert(seg_key.clone());
1160 }
1161 let mapped_val = match (bo4e_val, enum_map) {
1163 (Some(v), Some(map)) => {
1164 map.iter()
1166 .find(|(_, bo4e_v)| *bo4e_v == &v)
1167 .map(|(edifact_k, _)| edifact_k.clone())
1168 .or(Some(v))
1169 }
1170 (v, _) => v,
1171 };
1172 mapped_val.or_else(|| default.cloned())
1173 };
1174
1175 if let Some(val) = val {
1176 field_values.push((
1177 seg_key.clone(),
1178 seg_tag.clone(),
1179 element_idx,
1180 component_idx,
1181 val,
1182 ));
1183 }
1184
1185 if let Some(q) = qualifier {
1187 if injected_qualifiers.insert(seg_key.clone()) {
1188 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1189 }
1190 }
1191 }
1192
1193 field_values.retain(|(seg_key, _, _, _, _)| {
1201 if !seg_key.contains('[') {
1202 return true; }
1204 !seg_has_data_field.contains(seg_key) || seg_has_real_data.contains(seg_key)
1205 });
1206
1207 if has_data_fields && !has_real_data {
1212 return AssembledGroupInstance {
1213 segments: vec![],
1214 child_groups: vec![],
1215 entry_mig_number: None,
1216 variant_mig_numbers: vec![],
1217 skipped_segments: Vec::new(),
1218 skipped_positions: Vec::new(),
1219 };
1220 }
1221
1222 let mut segments: Vec<AssembledSegment> = Vec::with_capacity(field_values.len());
1225 let mut seen_keys: HashMap<String, usize> = HashMap::new();
1226
1227 for (seg_key, seg_tag, element_idx, component_idx, val) in &field_values {
1228 let seg = if let Some(&pos) = seen_keys.get(seg_key) {
1229 &mut segments[pos]
1230 } else {
1231 let pos = segments.len();
1232 seen_keys.insert(seg_key.clone(), pos);
1233 segments.push(AssembledSegment {
1234 tag: seg_tag.clone(),
1235 elements: vec![],
1236 mig_number: None,
1237 segment_number: None,
1238 });
1239 &mut segments[pos]
1240 };
1241
1242 while seg.elements.len() <= *element_idx {
1243 seg.elements.push(vec![]);
1244 }
1245 while seg.elements[*element_idx].len() <= *component_idx {
1246 seg.elements[*element_idx].push(String::new());
1247 }
1248 seg.elements[*element_idx][*component_idx] = val.clone();
1249 }
1250
1251 for seg in &mut segments {
1254 let last_populated = seg.elements.iter().rposition(|e| !e.is_empty());
1255 if let Some(last_idx) = last_populated {
1256 for i in 0..last_idx {
1257 if seg.elements[i].is_empty() {
1258 seg.elements[i] = vec![String::new()];
1259 }
1260 }
1261 }
1262 }
1263
1264 if let Some(ref ss) = self.segment_structure {
1266 for seg in &mut segments {
1267 if let Some(expected) = ss.element_count(&seg.tag) {
1268 while seg.elements.len() < expected {
1269 seg.elements.push(vec![String::new()]);
1270 }
1271 }
1272 }
1273 }
1274
1275 AssembledGroupInstance {
1276 segments,
1277 child_groups: vec![],
1278 entry_mig_number: None,
1279 variant_mig_numbers: vec![],
1280 skipped_segments: Vec::new(),
1281 skipped_positions: Vec::new(),
1282 }
1283 }
1284
1285 fn resolve_field_path(segment: &AssembledSegment, path: &[&str]) -> Option<String> {
1298 if path.is_empty() {
1299 return None;
1300 }
1301
1302 if let Ok(element_idx) = path[0].parse::<usize>() {
1304 let component_idx = if path.len() > 1 {
1305 path[1].parse::<usize>().unwrap_or(0)
1306 } else {
1307 0
1308 };
1309 return segment
1310 .elements
1311 .get(element_idx)?
1312 .get(component_idx)
1313 .filter(|v| !v.is_empty())
1314 .cloned();
1315 }
1316
1317 None
1323 }
1324
1325 fn parse_element_component(parts: &[&str]) -> (usize, usize) {
1328 if parts.is_empty() {
1329 return (0, 0);
1330 }
1331 let element_idx = parts[0].parse::<usize>().unwrap_or(0);
1332 let component_idx = if parts.len() > 1 {
1333 parts[1].parse::<usize>().unwrap_or(0)
1334 } else {
1335 0
1336 };
1337 (element_idx, component_idx)
1338 }
1339
1340 pub fn populate_field(
1343 &self,
1344 bo4e_value: &serde_json::Value,
1345 target_field: &str,
1346 ) -> Option<String> {
1347 let mut current = bo4e_value;
1348 for part in target_field.split('.') {
1349 current = current.get(part)?;
1350 }
1351 if let Some(code) = current.get("code").and_then(|v| v.as_str()) {
1353 return Some(code.to_string());
1354 }
1355 current.as_str().map(|s| s.to_string())
1356 }
1357
1358 pub fn build_segment_from_bo4e(
1360 &self,
1361 bo4e_value: &serde_json::Value,
1362 segment_tag: &str,
1363 target_field: &str,
1364 ) -> AssembledSegment {
1365 let value = self.populate_field(bo4e_value, target_field);
1366 let elements = if let Some(val) = value {
1367 vec![vec![val]]
1368 } else {
1369 vec![]
1370 };
1371 AssembledSegment {
1372 tag: segment_tag.to_uppercase(),
1373 elements,
1374 mig_number: None,
1375 segment_number: None,
1376 }
1377 }
1378
1379 pub fn resolve_repetition(
1388 tree: &AssembledTree,
1389 group_path: &str,
1390 discriminator: &str,
1391 ) -> Option<usize> {
1392 let (spec, expected) = discriminator.split_once('=')?;
1393 let parts: Vec<&str> = spec.split('.').collect();
1394 if parts.len() != 3 {
1395 return None;
1396 }
1397 let tag = parts[0];
1398 let element_idx: usize = parts[1].parse().ok()?;
1399 let component_idx: usize = parts[2].parse().ok()?;
1400
1401 let path_parts: Vec<&str> = group_path.split('.').collect();
1403
1404 let leaf_group = if path_parts.len() == 1 {
1405 let (group_id, _) = parse_group_spec(path_parts[0]);
1406 tree.groups.iter().find(|g| g.group_id == group_id)?
1407 } else {
1408 let parent_parts = &path_parts[..path_parts.len() - 1];
1410 let mut current_instance = {
1411 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1412 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
1413 first_group.repetitions.get(first_rep.unwrap_or(0))?
1414 };
1415 for part in &parent_parts[1..] {
1416 let (group_id, explicit_rep) = parse_group_spec(part);
1417 let child_group = current_instance
1418 .child_groups
1419 .iter()
1420 .find(|g| g.group_id == group_id)?;
1421 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
1422 }
1423 let (leaf_id, _) = parse_group_spec(path_parts.last()?);
1424 current_instance
1425 .child_groups
1426 .iter()
1427 .find(|g| g.group_id == leaf_id)?
1428 };
1429
1430 let expected_values: Vec<&str> = expected.split('|').collect();
1432 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1433 let matches = instance.segments.iter().any(|s| {
1434 s.tag.eq_ignore_ascii_case(tag)
1435 && s.elements
1436 .get(element_idx)
1437 .and_then(|e| e.get(component_idx))
1438 .map(|v| expected_values.iter().any(|ev| v == ev))
1439 .unwrap_or(false)
1440 });
1441 if matches {
1442 return Some(rep_idx);
1443 }
1444 }
1445
1446 None
1447 }
1448
1449 pub fn resolve_all_repetitions(
1454 tree: &AssembledTree,
1455 group_path: &str,
1456 discriminator: &str,
1457 ) -> Vec<usize> {
1458 let Some((spec, expected)) = discriminator.split_once('=') else {
1459 return Vec::new();
1460 };
1461 let parts: Vec<&str> = spec.split('.').collect();
1462 if parts.len() != 3 {
1463 return Vec::new();
1464 }
1465 let tag = parts[0];
1466 let element_idx: usize = match parts[1].parse() {
1467 Ok(v) => v,
1468 Err(_) => return Vec::new(),
1469 };
1470 let component_idx: usize = match parts[2].parse() {
1471 Ok(v) => v,
1472 Err(_) => return Vec::new(),
1473 };
1474
1475 let path_parts: Vec<&str> = group_path.split('.').collect();
1477
1478 let leaf_group = if path_parts.len() == 1 {
1479 let (group_id, _) = parse_group_spec(path_parts[0]);
1480 match tree.groups.iter().find(|g| g.group_id == group_id) {
1481 Some(g) => g,
1482 None => return Vec::new(),
1483 }
1484 } else {
1485 let parent_parts = &path_parts[..path_parts.len() - 1];
1486 let mut current_instance = {
1487 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
1488 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
1489 Some(g) => g,
1490 None => return Vec::new(),
1491 };
1492 match first_group.repetitions.get(first_rep.unwrap_or(0)) {
1493 Some(i) => i,
1494 None => return Vec::new(),
1495 }
1496 };
1497 for part in &parent_parts[1..] {
1498 let (group_id, explicit_rep) = parse_group_spec(part);
1499 let child_group = match current_instance
1500 .child_groups
1501 .iter()
1502 .find(|g| g.group_id == group_id)
1503 {
1504 Some(g) => g,
1505 None => return Vec::new(),
1506 };
1507 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
1508 Some(i) => i,
1509 None => return Vec::new(),
1510 };
1511 }
1512 let (leaf_id, _) = match path_parts.last() {
1513 Some(p) => parse_group_spec(p),
1514 None => return Vec::new(),
1515 };
1516 match current_instance
1517 .child_groups
1518 .iter()
1519 .find(|g| g.group_id == leaf_id)
1520 {
1521 Some(g) => g,
1522 None => return Vec::new(),
1523 }
1524 };
1525
1526 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
1528
1529 let expected_values: Vec<&str> = expected_raw.split('|').collect();
1531 let mut result = Vec::new();
1532 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
1533 let matches = instance.segments.iter().any(|s| {
1534 s.tag.eq_ignore_ascii_case(tag)
1535 && s.elements
1536 .get(element_idx)
1537 .and_then(|e| e.get(component_idx))
1538 .map(|v| expected_values.iter().any(|ev| v == ev))
1539 .unwrap_or(false)
1540 });
1541 if matches {
1542 result.push(rep_idx);
1543 }
1544 }
1545
1546 if let Some(occ) = occurrence {
1548 result.into_iter().nth(occ).into_iter().collect()
1549 } else {
1550 result
1551 }
1552 }
1553
1554 pub fn map_all_forward(&self, tree: &AssembledTree) -> serde_json::Value {
1576 self.map_all_forward_inner(tree, true).0
1577 }
1578
1579 pub fn map_all_forward_enriched(
1583 &self,
1584 tree: &AssembledTree,
1585 enrich_codes: bool,
1586 ) -> serde_json::Value {
1587 self.map_all_forward_inner(tree, enrich_codes).0
1588 }
1589
1590 fn map_all_forward_inner(
1600 &self,
1601 tree: &AssembledTree,
1602 enrich_codes: bool,
1603 ) -> (
1604 serde_json::Value,
1605 std::collections::HashMap<String, Vec<usize>>,
1606 DpRouting,
1607 ) {
1608 self.map_all_forward_inner_with_tx(tree, enrich_codes, self.transaction_group.as_deref())
1609 }
1610
1611 fn map_all_forward_inner_with_tx(
1615 &self,
1616 tree: &AssembledTree,
1617 enrich_codes: bool,
1618 tx_group_override: Option<&str>,
1619 ) -> (
1620 serde_json::Value,
1621 std::collections::HashMap<String, Vec<usize>>,
1622 DpRouting,
1623 ) {
1624 let mut result = serde_json::Map::new();
1625 let mut nesting_info: std::collections::HashMap<String, Vec<usize>> =
1626 std::collections::HashMap::new();
1627
1628 for def in &self.definitions {
1629 let entity = &def.meta.entity;
1630
1631 let bo4e = if let Some(ref disc) = def.meta.discriminator {
1632 let use_source_path = def
1637 .meta
1638 .source_path
1639 .as_ref()
1640 .is_some_and(|sp| has_source_path_qualifiers(sp));
1641 if use_source_path {
1642 let sp = def.meta.source_path.as_deref().unwrap();
1644 let all_instances = Self::resolve_all_by_source_path(tree, sp);
1645 let instances: Vec<_> = if let Some(matcher) = DiscriminatorMatcher::parse(disc)
1647 {
1648 matcher.filter_instances(all_instances)
1649 } else {
1650 all_instances
1651 };
1652 let extract = |instance: &AssembledGroupInstance| {
1653 let mut r = serde_json::Map::new();
1654 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
1655 serde_json::Value::Object(r)
1656 };
1657 match instances.len() {
1658 0 => None,
1659 1 => Some(extract(instances[0])),
1660 _ => Some(serde_json::Value::Array(
1661 instances.iter().map(|i| extract(i)).collect(),
1662 )),
1663 }
1664 } else {
1665 let reps = Self::resolve_all_repetitions(tree, &def.meta.source_group, disc);
1666 match reps.len() {
1667 0 => None,
1668 1 => Some(self.map_forward_inner(tree, def, reps[0], enrich_codes)),
1669 _ => Some(serde_json::Value::Array(
1670 reps.iter()
1671 .map(|&rep| self.map_forward_inner(tree, def, rep, enrich_codes))
1672 .collect(),
1673 )),
1674 }
1675 }
1676 } else if def.meta.source_group.is_empty() {
1677 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
1679 } else if def.meta.source_path.as_ref().is_some_and(|sp| {
1680 has_source_path_qualifiers(sp) || def.meta.source_group.contains('.')
1681 }) {
1682 let sp = def.meta.source_path.as_deref().unwrap();
1687 let mut indexed = Self::resolve_all_with_parent_indices(tree, sp);
1688
1689 if let Some(last_part) = sp.rsplit('.').next() {
1694 if !last_part.contains('_') {
1695 let base_prefix = if let Some(parent) = sp.rsplit_once('.') {
1699 format!("{}.", parent.0)
1700 } else {
1701 String::new()
1702 };
1703 let sibling_qualifiers: Vec<String> = self
1704 .definitions
1705 .iter()
1706 .filter_map(|d| d.meta.source_path.as_deref())
1707 .filter(|other_sp| {
1708 *other_sp != sp
1709 && other_sp.starts_with(&base_prefix)
1710 && other_sp.split('.').count() == sp.split('.').count()
1711 })
1712 .filter_map(|other_sp| {
1713 let other_last = other_sp.rsplit('.').next()?;
1714 let (base, q) = other_last.split_once('_')?;
1717 if base == last_part {
1718 Some(q.to_string())
1719 } else {
1720 None
1721 }
1722 })
1723 .collect();
1724
1725 if !sibling_qualifiers.is_empty() {
1726 indexed.retain(|(_, inst)| {
1727 let entry_qual = inst
1728 .segments
1729 .first()
1730 .and_then(|seg| seg.elements.first())
1731 .and_then(|el| el.first())
1732 .map(|v| v.to_lowercase());
1733 !entry_qual.is_some_and(|q| {
1736 sibling_qualifiers.iter().any(|sq| {
1737 sq.split('_').any(|part| part.eq_ignore_ascii_case(&q))
1738 })
1739 })
1740 });
1741 }
1742 }
1743 }
1744 let extract = |instance: &AssembledGroupInstance| {
1745 let mut r = serde_json::Map::new();
1746 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
1747 serde_json::Value::Object(r)
1748 };
1749 if def.meta.source_group.contains('.') && !indexed.is_empty() {
1754 if let Some(sp) = &def.meta.source_path {
1755 let parent_indices: Vec<usize> =
1756 indexed.iter().map(|(idx, _)| *idx).collect();
1757 nesting_info.entry(sp.clone()).or_insert(parent_indices);
1758
1759 let child_key = format!("{sp}#child");
1762 if let std::collections::hash_map::Entry::Vacant(e) =
1763 nesting_info.entry(child_key)
1764 {
1765 let child_indices: Vec<usize> =
1766 Self::compute_child_indices(tree, sp, &indexed);
1767 if !child_indices.is_empty() {
1768 e.insert(child_indices);
1769 }
1770 }
1771 }
1772 }
1773 match indexed.len() {
1774 0 => None,
1775 1 => Some(extract(indexed[0].1)),
1776 _ => Some(serde_json::Value::Array(
1777 indexed.iter().map(|(_, i)| extract(i)).collect(),
1778 )),
1779 }
1780 } else {
1781 let num_reps = Self::count_repetitions(tree, &def.meta.source_group);
1782 if num_reps <= 1 {
1783 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
1784 } else {
1785 let mut items = Vec::with_capacity(num_reps);
1787 for rep in 0..num_reps {
1788 items.push(self.map_forward_inner(tree, def, rep, enrich_codes));
1789 }
1790 Some(serde_json::Value::Array(items))
1791 }
1792 };
1793
1794 if let Some(bo4e) = bo4e {
1795 let key = to_camel_case(entity);
1796 deep_merge_insert(&mut result, &key, bo4e);
1797 }
1798 }
1799
1800 nest_child_entities_in_result(
1805 &mut result,
1806 &self.definitions,
1807 &nesting_info,
1808 tx_group_override,
1809 );
1810
1811 let dp_routing = route_nad_dp_to_lokation(&mut result);
1817
1818 (serde_json::Value::Object(result), nesting_info, dp_routing)
1819 }
1820
1821 pub fn map_all_reverse(
1830 &self,
1831 entities: &serde_json::Value,
1832 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
1833 ) -> AssembledTree {
1834 let mut root_segments: Vec<AssembledSegment> = Vec::new();
1835 let mut groups: Vec<AssembledGroup> = Vec::new();
1836 let mut inferred_nesting: std::collections::HashMap<String, Vec<usize>> =
1839 std::collections::HashMap::new();
1840
1841 for def in &self.definitions {
1842 let entity_key = to_camel_case(&def.meta.entity);
1843
1844 let _extracted: Option<serde_json::Value>;
1847 let entity_value = if let Some(v) = entities.get(&entity_key) {
1848 _extracted = None;
1849 v
1850 } else if def.meta.source_group.contains('.') {
1851 match extract_child_from_parent_with_indices(entities, &self.definitions, def) {
1853 Some((v, parent_indices)) => {
1854 if let Some(sp) = def.meta.source_path.as_deref() {
1856 inferred_nesting
1857 .entry(sp.to_string())
1858 .or_insert(parent_indices);
1859 }
1860 _extracted = Some(v);
1861 _extracted.as_ref().unwrap()
1862 }
1863 None => continue,
1864 }
1865 } else {
1866 continue;
1867 };
1868
1869 let unwrapped: Option<serde_json::Value>;
1877 let entity_value = if entity_value.is_object() && !entity_value.is_array() {
1878 if let Some(disc_value) = def
1879 .meta
1880 .discriminator
1881 .as_deref()
1882 .and_then(|d| d.split_once('='))
1883 .map(|(_, v)| v)
1884 {
1885 if let Some(inner) = entity_value.get(disc_value) {
1887 let mut injected = inner.clone();
1888 if let Some(qualifier_field) =
1891 find_qualifier_companion_field(&self.definitions, &def.meta.entity)
1892 {
1893 if let Some(obj) = injected.as_object_mut() {
1894 let entry = obj
1895 .entry(qualifier_field)
1896 .or_insert(serde_json::Value::Null);
1897 if entry.is_null() {
1898 *entry = serde_json::Value::String(disc_value.to_string());
1899 }
1900 }
1901 }
1902 unwrapped = Some(injected);
1903 unwrapped.as_ref().unwrap()
1904 } else {
1905 entity_value
1906 }
1907 } else if is_map_keyed_object(entity_value) {
1908 let map = entity_value.as_object().unwrap();
1913 let arr: Vec<serde_json::Value> = map
1914 .iter()
1915 .map(|(key, val)| {
1916 let mut item = val.clone();
1917 if let Some(obj) = item.as_object_mut() {
1920 if let Some(qualifier_field) = find_qualifier_companion_field(
1921 &self.definitions,
1922 &def.meta.entity,
1923 ) {
1924 let entry = obj
1925 .entry(qualifier_field)
1926 .or_insert(serde_json::Value::Null);
1927 if entry.is_null() {
1928 *entry = serde_json::Value::String(key.clone());
1929 }
1930 }
1931 }
1932 item
1933 })
1934 .collect();
1935 unwrapped = Some(serde_json::Value::Array(arr));
1936 unwrapped.as_ref().unwrap()
1937 } else {
1938 entity_value
1939 }
1940 } else {
1941 entity_value
1942 };
1943
1944 let leaf_group = def
1946 .meta
1947 .source_group
1948 .rsplit('.')
1949 .next()
1950 .unwrap_or(&def.meta.source_group);
1951
1952 if def.meta.source_group.is_empty() {
1953 let instance = self.map_reverse(entity_value, def);
1955 root_segments.extend(instance.segments);
1956 } else if entity_value.is_array() {
1957 let arr = entity_value.as_array().unwrap();
1959 let reps: Vec<_> = arr.iter().map(|item| self.map_reverse(item, def)).collect();
1960
1961 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
1963 existing.repetitions.extend(reps);
1964 } else {
1965 groups.push(AssembledGroup {
1966 group_id: leaf_group.to_string(),
1967 repetitions: reps,
1968 });
1969 }
1970 } else {
1971 let instance = self.map_reverse(entity_value, def);
1973
1974 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
1975 existing.repetitions.push(instance);
1976 } else {
1977 groups.push(AssembledGroup {
1978 group_id: leaf_group.to_string(),
1979 repetitions: vec![instance],
1980 });
1981 }
1982 }
1983 }
1984
1985 let nested_specs: Vec<(String, String)> = self
1991 .definitions
1992 .iter()
1993 .filter_map(|def| {
1994 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
1995 if parts.len() > 1 {
1996 Some((parts[0].to_string(), parts[parts.len() - 1].to_string()))
1997 } else {
1998 None
1999 }
2000 })
2001 .collect();
2002 for (parent_id, child_id) in &nested_specs {
2003 let has_parent = groups.iter().any(|g| g.group_id == *parent_id);
2005 let has_child = groups.iter().any(|g| g.group_id == *child_id);
2006 if has_parent && has_child {
2007 let child_idx = groups.iter().position(|g| g.group_id == *child_id).unwrap();
2008 let child_group = groups.remove(child_idx);
2009 let parent = groups
2010 .iter_mut()
2011 .find(|g| g.group_id == *parent_id)
2012 .unwrap();
2013 let child_source_path = self
2017 .definitions
2018 .iter()
2019 .find(|d| {
2020 let parts: Vec<&str> = d.meta.source_group.split('.').collect();
2021 parts.len() > 1 && parts[parts.len() - 1] == *child_id
2022 })
2023 .and_then(|d| d.meta.source_path.as_deref());
2024 let distribution = child_source_path.and_then(|key| {
2025 nesting_info
2026 .and_then(|ni| ni.get(key))
2027 .or_else(|| inferred_nesting.get(key))
2028 });
2029 for (i, child_rep) in child_group.repetitions.into_iter().enumerate() {
2030 let target_idx = distribution
2031 .and_then(|dist| dist.get(i))
2032 .copied()
2033 .unwrap_or(0);
2034
2035 if let Some(target_rep) = parent.repetitions.get_mut(target_idx) {
2036 if let Some(existing) = target_rep
2037 .child_groups
2038 .iter_mut()
2039 .find(|g| g.group_id == *child_id)
2040 {
2041 existing.repetitions.push(child_rep);
2042 } else {
2043 target_rep.child_groups.push(AssembledGroup {
2044 group_id: child_id.clone(),
2045 repetitions: vec![child_rep],
2046 });
2047 }
2048 }
2049 }
2050 }
2051 }
2052
2053 let post_group_start = root_segments.len();
2054 AssembledTree {
2055 segments: root_segments,
2056 groups,
2057 post_group_start,
2058 inter_group_segments: std::collections::BTreeMap::new(),
2059 }
2060 }
2061
2062 fn count_repetitions(tree: &AssembledTree, group_path: &str) -> usize {
2064 let parts: Vec<&str> = group_path.split('.').collect();
2065
2066 let (first_id, first_rep) = parse_group_spec(parts[0]);
2067 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2068 Some(g) => g,
2069 None => return 0,
2070 };
2071
2072 if parts.len() == 1 {
2073 return first_group.repetitions.len();
2074 }
2075
2076 let mut current_instance = match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2078 Some(i) => i,
2079 None => return 0,
2080 };
2081
2082 for (i, part) in parts[1..].iter().enumerate() {
2083 let (group_id, explicit_rep) = parse_group_spec(part);
2084 let child_group = match current_instance
2085 .child_groups
2086 .iter()
2087 .find(|g| g.group_id == group_id)
2088 {
2089 Some(g) => g,
2090 None => return 0,
2091 };
2092
2093 if i == parts.len() - 2 {
2094 return child_group.repetitions.len();
2096 }
2097 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2098 Some(i) => i,
2099 None => return 0,
2100 };
2101 }
2102
2103 0
2104 }
2105
2106 pub fn map_interchange(
2115 msg_engine: &MappingEngine,
2116 tx_engine: &MappingEngine,
2117 tree: &AssembledTree,
2118 transaction_group: &str,
2119 enrich_codes: bool,
2120 ) -> crate::model::MappedMessage {
2121 let (stammdaten, nesting_info, dp_routing) =
2123 msg_engine.map_all_forward_inner(tree, enrich_codes);
2124
2125 let transaktionen = tree
2127 .groups
2128 .iter()
2129 .find(|g| g.group_id == transaction_group)
2130 .map(|sg| {
2131 sg.repetitions
2132 .iter()
2133 .map(|instance| {
2134 let wrapped_tree = AssembledTree {
2137 segments: vec![],
2138 groups: vec![AssembledGroup {
2139 group_id: transaction_group.to_string(),
2140 repetitions: vec![instance.clone()],
2141 }],
2142 post_group_start: 0,
2143 inter_group_segments: std::collections::BTreeMap::new(),
2144 };
2145
2146 let (tx_result, tx_nesting, tx_dp_routing) = tx_engine
2150 .map_all_forward_inner_with_tx(
2151 &wrapped_tree,
2152 enrich_codes,
2153 Some(transaction_group),
2154 );
2155
2156 crate::model::MappedTransaktion {
2157 stammdaten: tx_result,
2158 nesting_info: tx_nesting,
2159 dp_routing: tx_dp_routing,
2160 }
2161 })
2162 .collect()
2163 })
2164 .unwrap_or_default();
2165
2166 crate::model::MappedMessage {
2167 stammdaten,
2168 transaktionen,
2169 nesting_info,
2170 dp_routing,
2171 inter_group_segments: tree.inter_group_segments.clone(),
2172 }
2173 }
2174
2175 pub fn map_interchange_reverse(
2185 msg_engine: &MappingEngine,
2186 tx_engine: &MappingEngine,
2187 mapped: &crate::model::MappedMessage,
2188 transaction_group: &str,
2189 filtered_mig: Option<&MigSchema>,
2190 ) -> AssembledTree {
2191 let _owned_msg: Option<serde_json::Value>;
2197 let msg_stammdaten = if !mapped.dp_routing.is_empty() {
2198 if let serde_json::Value::Object(map) = &mapped.stammdaten {
2199 let mut cloned = map.clone();
2200 unroute_lokation_to_nad_dp(&mut cloned, &mapped.dp_routing);
2201 _owned_msg = Some(serde_json::Value::Object(cloned));
2202 _owned_msg.as_ref().unwrap()
2203 } else {
2204 _owned_msg = None;
2205 &mapped.stammdaten
2206 }
2207 } else {
2208 _owned_msg = None;
2209 &mapped.stammdaten
2210 };
2211
2212 let msg_tree = msg_engine.map_all_reverse(
2213 msg_stammdaten,
2214 if mapped.nesting_info.is_empty() {
2215 None
2216 } else {
2217 Some(&mapped.nesting_info)
2218 },
2219 );
2220
2221 let mut sg4_reps: Vec<AssembledGroupInstance> = Vec::new();
2223
2224 struct DefWithMeta<'a> {
2228 def: &'a MappingDefinition,
2229 relative: String,
2230 depth: usize,
2231 }
2232
2233 let mut sorted_defs: Vec<DefWithMeta> = tx_engine
2234 .definitions
2235 .iter()
2236 .map(|def| {
2237 let relative = strip_tx_group_prefix(&def.meta.source_group, transaction_group);
2238 let depth = if relative.is_empty() {
2239 0
2240 } else {
2241 relative.chars().filter(|c| *c == '.').count() + 1
2242 };
2243 DefWithMeta {
2244 def,
2245 relative,
2246 depth,
2247 }
2248 })
2249 .collect();
2250
2251 let mut parent_rep_map: std::collections::HashMap<String, usize> =
2255 std::collections::HashMap::new();
2256 for dm in &sorted_defs {
2257 if dm.depth >= 2 {
2258 let parts: Vec<&str> = dm.relative.split('.').collect();
2259 let (_, parent_rep) = parse_group_spec(parts[0]);
2260 if let Some(rep_idx) = parent_rep {
2261 if let Some(sp) = &dm.def.meta.source_path {
2262 if let Some((parent_path, _)) = sp.rsplit_once('.') {
2263 parent_rep_map
2264 .entry(parent_path.to_string())
2265 .or_insert(rep_idx);
2266 }
2267 }
2268 }
2269 }
2270 }
2271
2272 for dm in &mut sorted_defs {
2275 if dm.depth == 1 && !dm.relative.contains(':') {
2276 if let Some(sp) = &dm.def.meta.source_path {
2277 if let Some(rep_idx) = parent_rep_map.get(sp.as_str()) {
2278 dm.relative = format!("{}:{}", dm.relative, rep_idx);
2279 }
2280 }
2281 }
2282 }
2283
2284 if let Some(mig) = filtered_mig {
2291 let mig_order = build_reverse_mig_group_order(mig, transaction_group);
2292 sorted_defs.sort_by(|a, b| {
2293 a.depth.cmp(&b.depth).then_with(|| {
2294 let a_id = a.relative.split(':').next().unwrap_or(&a.relative);
2295 let b_id = b.relative.split(':').next().unwrap_or(&b.relative);
2296 let a_pos = variant_mig_position(a.def, a_id, &mig_order);
2298 let b_pos = variant_mig_position(b.def, b_id, &mig_order);
2299 a_pos.cmp(&b_pos).then(a.relative.cmp(&b.relative))
2300 })
2301 });
2302 } else {
2303 sorted_defs.sort_by(|a, b| a.depth.cmp(&b.depth).then(a.relative.cmp(&b.relative)));
2304 }
2305
2306 for tx in &mapped.transaktionen {
2307 let mut root_segs: Vec<AssembledSegment> = Vec::new();
2308 let mut child_groups: Vec<AssembledGroup> = Vec::new();
2309
2310 let _owned_tx: Option<serde_json::Value>;
2315 let tx_stammdaten: &serde_json::Value = if !tx.dp_routing.is_empty() {
2316 if let serde_json::Value::Object(map) = &tx.stammdaten {
2317 let mut cloned = map.clone();
2318 unroute_lokation_to_nad_dp(&mut cloned, &tx.dp_routing);
2319 _owned_tx = Some(serde_json::Value::Object(cloned));
2320 _owned_tx.as_ref().unwrap()
2321 } else {
2322 _owned_tx = None;
2323 &tx.stammdaten
2324 }
2325 } else {
2326 _owned_tx = None;
2327 &tx.stammdaten
2328 };
2329
2330 let mut source_path_to_rep: std::collections::HashMap<String, Vec<usize>> =
2335 std::collections::HashMap::new();
2336
2337 for dm in &sorted_defs {
2338 let entity_key = to_camel_case(&dm.def.meta.entity);
2341 let _tx_extracted: Option<serde_json::Value>;
2342 let bo4e_value = if let Some(v) = tx_stammdaten.get(&entity_key) {
2343 _tx_extracted = None;
2344 v
2345 } else if dm.def.meta.source_group.contains('.') {
2346 match extract_child_from_parent(tx_stammdaten, &tx_engine.definitions, dm.def)
2347 {
2348 Some(v) => {
2349 _tx_extracted = Some(v);
2350 _tx_extracted.as_ref().unwrap()
2351 }
2352 None => continue,
2353 }
2354 } else {
2355 continue;
2356 };
2357
2358 let unwrapped_value: Option<serde_json::Value>;
2360 let bo4e_value = if bo4e_value.is_object() && !bo4e_value.is_array() {
2361 if let Some(disc_value) = dm
2362 .def
2363 .meta
2364 .discriminator
2365 .as_deref()
2366 .and_then(|d| d.split_once('='))
2367 .map(|(_, v)| v)
2368 {
2369 if let Some(inner) = bo4e_value.get(disc_value) {
2370 let mut injected = inner.clone();
2371 if let Some(qualifier_field) = find_qualifier_companion_field(
2372 &tx_engine.definitions,
2373 &dm.def.meta.entity,
2374 ) {
2375 if let Some(obj) = injected.as_object_mut() {
2376 obj.entry(qualifier_field).or_insert_with(|| {
2377 serde_json::Value::String(disc_value.to_string())
2378 });
2379 }
2380 }
2381 unwrapped_value = Some(injected);
2382 unwrapped_value.as_ref().unwrap()
2383 } else {
2384 bo4e_value
2385 }
2386 } else if is_map_keyed_object(bo4e_value) {
2387 let map = bo4e_value.as_object().unwrap();
2388 let arr: Vec<serde_json::Value> = map
2389 .iter()
2390 .map(|(key, val)| {
2391 let mut item = val.clone();
2392 if let Some(obj) = item.as_object_mut() {
2393 if let Some(qualifier_field) = find_qualifier_companion_field(
2394 &tx_engine.definitions,
2395 &dm.def.meta.entity,
2396 ) {
2397 let entry = obj
2398 .entry(qualifier_field)
2399 .or_insert(serde_json::Value::Null);
2400 if entry.is_null() {
2401 *entry = serde_json::Value::String(key.clone());
2402 }
2403 }
2404 }
2405 item
2406 })
2407 .collect();
2408 unwrapped_value = Some(serde_json::Value::Array(arr));
2409 unwrapped_value.as_ref().unwrap()
2410 } else {
2411 bo4e_value
2412 }
2413 } else {
2414 bo4e_value
2415 };
2416
2417 let items: Vec<&serde_json::Value> = if bo4e_value.is_array() {
2421 bo4e_value.as_array().unwrap().iter().collect()
2422 } else {
2423 vec![bo4e_value]
2424 };
2425
2426 for (item_idx, item) in items.iter().enumerate() {
2427 let instance = tx_engine.map_reverse(item, dm.def);
2428
2429 if instance.segments.is_empty() && instance.child_groups.is_empty() {
2431 continue;
2432 }
2433
2434 if dm.relative.is_empty() {
2435 root_segs.extend(instance.segments);
2436 } else {
2437 let effective_relative = if dm.depth >= 2 {
2441 let rel = if items.len() > 1 {
2444 strip_all_rep_indices(&dm.relative)
2445 } else {
2446 dm.relative.clone()
2447 };
2448 let skip_nesting = dm
2455 .def
2456 .meta
2457 .source_path
2458 .as_ref()
2459 .and_then(|sp| sp.rsplit_once('.'))
2460 .and_then(|(parent_path, _)| source_path_to_rep.get(parent_path))
2461 .is_some_and(|reps| reps.len() == 1);
2462 let nesting_idx = if items.len() > 1 && !skip_nesting {
2463 dm.def
2464 .meta
2465 .source_path
2466 .as_ref()
2467 .and_then(|sp| tx.nesting_info.get(sp))
2468 .and_then(|dist| dist.get(item_idx))
2469 .copied()
2470 } else {
2471 None
2472 };
2473 if let Some(parent_rep) = nesting_idx {
2474 let parts: Vec<&str> = rel.split('.').collect();
2476 let parent_id = parts[0].split(':').next().unwrap_or(parts[0]);
2477 let rest = parts[1..].join(".");
2478 format!("{}:{}.{}", parent_id, parent_rep, rest)
2479 } else {
2480 resolve_child_relative(
2481 &rel,
2482 dm.def.meta.source_path.as_deref(),
2483 &source_path_to_rep,
2484 item_idx,
2485 )
2486 }
2487 } else if dm.depth == 1 {
2488 let child_key = dm
2491 .def
2492 .meta
2493 .source_path
2494 .as_ref()
2495 .map(|sp| format!("{sp}#child"));
2496 if let Some(child_indices) =
2497 child_key.as_ref().and_then(|ck| tx.nesting_info.get(ck))
2498 {
2499 if let Some(&target) = child_indices.get(item_idx) {
2500 if target != usize::MAX {
2501 let base =
2502 dm.relative.split(':').next().unwrap_or(&dm.relative);
2503 format!("{}:{}", base, target)
2504 } else {
2505 dm.relative.clone()
2506 }
2507 } else if items.len() > 1 && item_idx > 0 {
2508 strip_rep_index(&dm.relative)
2509 } else {
2510 dm.relative.clone()
2511 }
2512 } else if items.len() > 1 && item_idx > 0 {
2513 strip_rep_index(&dm.relative)
2514 } else {
2515 dm.relative.clone()
2516 }
2517 } else if items.len() > 1 && item_idx > 0 {
2518 strip_rep_index(&dm.relative)
2521 } else {
2522 dm.relative.clone()
2523 };
2524
2525 let rep_used =
2526 place_in_groups(&mut child_groups, &effective_relative, instance);
2527
2528 if dm.depth == 1 {
2530 if let Some(sp) = &dm.def.meta.source_path {
2531 source_path_to_rep
2532 .entry(sp.clone())
2533 .or_default()
2534 .push(rep_used);
2535 }
2536 }
2537 }
2538 }
2539 }
2540
2541 if let Some(mig) = filtered_mig {
2546 sort_variant_reps_by_mig(&mut child_groups, mig, transaction_group);
2547 }
2548
2549 sg4_reps.push(AssembledGroupInstance {
2550 segments: root_segs,
2551 child_groups,
2552 entry_mig_number: None,
2553 variant_mig_numbers: vec![],
2554 skipped_segments: Vec::new(),
2555 skipped_positions: Vec::new(),
2556 });
2557 }
2558
2559 let mut root_segments = Vec::new();
2566 let mut uns_segments = Vec::new();
2567 let mut uns_is_summary = false;
2568 let mut found_uns = false;
2569 for seg in msg_tree.segments {
2570 if seg.tag == "UNS" {
2571 uns_is_summary = seg
2573 .elements
2574 .first()
2575 .and_then(|el| el.first())
2576 .map(|v| v == "S")
2577 .unwrap_or(false);
2578 uns_segments.push(seg);
2579 found_uns = true;
2580 } else if found_uns {
2581 uns_segments.push(seg);
2583 } else {
2584 root_segments.push(seg);
2585 }
2586 }
2587
2588 let pre_group_count = root_segments.len();
2589 let mut all_groups = msg_tree.groups;
2590 let mut inter_group = msg_tree.inter_group_segments;
2591
2592 let sg_num = |id: &str| -> usize {
2594 id.strip_prefix("SG")
2595 .and_then(|n| n.parse::<usize>().ok())
2596 .unwrap_or(0)
2597 };
2598
2599 if !sg4_reps.is_empty() {
2600 if uns_is_summary {
2601 all_groups.push(AssembledGroup {
2603 group_id: transaction_group.to_string(),
2604 repetitions: sg4_reps,
2605 });
2606 if !uns_segments.is_empty() {
2607 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2612 let tx_num = sg_num(transaction_group);
2613 let uns_pos = all_groups
2614 .iter()
2615 .rposition(|g| sg_num(&g.group_id) <= tx_num)
2616 .map(|i| i + 1)
2617 .unwrap_or(all_groups.len());
2618 inter_group.insert(uns_pos, uns_segments);
2619 }
2620 } else {
2621 if !uns_segments.is_empty() {
2623 inter_group.insert(all_groups.len(), uns_segments);
2624 }
2625 all_groups.push(AssembledGroup {
2626 group_id: transaction_group.to_string(),
2627 repetitions: sg4_reps,
2628 });
2629 }
2630 } else if !uns_segments.is_empty() {
2631 if transaction_group.is_empty() {
2632 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2637 if uns_is_summary {
2638 inter_group.insert(all_groups.len(), uns_segments);
2639 } else {
2640 inter_group.insert(0, uns_segments);
2641 }
2642 } else {
2643 all_groups.sort_by_key(|g| sg_num(&g.group_id));
2647 let tx_num = sg_num(transaction_group);
2648 let uns_pos = all_groups
2649 .iter()
2650 .rposition(|g| sg_num(&g.group_id) <= tx_num)
2651 .map(|i| i + 1)
2652 .unwrap_or(all_groups.len());
2653 inter_group.insert(uns_pos, uns_segments);
2654 }
2655 }
2656
2657 for (k, segs) in &mapped.inter_group_segments {
2665 if segs.is_empty() {
2666 continue;
2667 }
2668 let existing_tags: std::collections::HashSet<String> = inter_group
2669 .get(k)
2670 .map(|v| v.iter().map(|s| s.tag.clone()).collect())
2671 .unwrap_or_default();
2672 for seg in segs {
2673 if existing_tags.contains(&seg.tag) {
2674 continue;
2675 }
2676 inter_group.entry(*k).or_default().push(seg.clone());
2677 }
2678 }
2679
2680 AssembledTree {
2681 segments: root_segments,
2682 groups: all_groups,
2683 post_group_start: pre_group_count,
2684 inter_group_segments: inter_group,
2685 }
2686 }
2687
2688 pub fn build_group_from_bo4e(
2690 &self,
2691 bo4e_value: &serde_json::Value,
2692 def: &MappingDefinition,
2693 ) -> AssembledGroup {
2694 let instance = self.map_reverse(bo4e_value, def);
2695 let leaf_group = def
2696 .meta
2697 .source_group
2698 .rsplit('.')
2699 .next()
2700 .unwrap_or(&def.meta.source_group);
2701
2702 AssembledGroup {
2703 group_id: leaf_group.to_string(),
2704 repetitions: vec![instance],
2705 }
2706 }
2707
2708 pub fn map_interchange_typed<M, T>(
2716 msg_engine: &MappingEngine,
2717 tx_engine: &MappingEngine,
2718 tree: &AssembledTree,
2719 tx_group: &str,
2720 enrich_codes: bool,
2721 nachrichtendaten: crate::model::Nachrichtendaten,
2722 interchangedaten: crate::model::Interchangedaten,
2723 ) -> Result<crate::model::Interchange<M, T>, serde_json::Error>
2724 where
2725 M: serde::de::DeserializeOwned,
2726 T: serde::de::DeserializeOwned,
2727 {
2728 let mapped = Self::map_interchange(msg_engine, tx_engine, tree, tx_group, enrich_codes);
2729 let nachricht = mapped.into_dynamic_nachricht(nachrichtendaten);
2730 let dynamic = crate::model::DynamicInterchange {
2731 interchangedaten,
2732 nachrichten: vec![nachricht],
2733 };
2734 let value = serde_json::to_value(&dynamic)?;
2735 serde_json::from_value(value)
2736 }
2737
2738 pub fn map_interchange_reverse_typed<M, T>(
2745 msg_engine: &MappingEngine,
2746 tx_engine: &MappingEngine,
2747 nachricht: &crate::model::Nachricht<M, T>,
2748 tx_group: &str,
2749 ) -> Result<AssembledTree, serde_json::Error>
2750 where
2751 M: serde::Serialize,
2752 T: serde::Serialize,
2753 {
2754 let stammdaten = serde_json::to_value(&nachricht.stammdaten)?;
2755 let transaktionen: Vec<crate::model::MappedTransaktion> = nachricht
2756 .transaktionen
2757 .iter()
2758 .map(|t| {
2759 Ok(crate::model::MappedTransaktion {
2760 stammdaten: serde_json::to_value(t)?,
2761 nesting_info: Default::default(),
2762 dp_routing: Default::default(),
2763 })
2764 })
2765 .collect::<Result<Vec<_>, serde_json::Error>>()?;
2766 let mapped = crate::model::MappedMessage {
2767 stammdaten,
2768 transaktionen,
2769 nesting_info: Default::default(),
2770 dp_routing: Default::default(),
2771 inter_group_segments: Default::default(),
2772 };
2773 Ok(Self::map_interchange_reverse(
2774 msg_engine, tx_engine, &mapped, tx_group, None,
2775 ))
2776 }
2777}
2778
2779fn parse_source_path_part(part: &str) -> (&str, Option<&str>) {
2786 if let Some(pos) = part.find('_') {
2790 let group = &part[..pos];
2791 let qualifier = &part[pos + 1..];
2792 if !qualifier.is_empty() {
2793 return (group, Some(qualifier));
2794 }
2795 }
2796 (part, None)
2797}
2798
2799fn build_reverse_mig_group_order(mig: &MigSchema, tx_group_id: &str) -> HashMap<String, usize> {
2807 let mut order = HashMap::new();
2808 if let Some(tg) = mig.segment_groups.iter().find(|g| g.id == tx_group_id) {
2809 for (i, nested) in tg.nested_groups.iter().enumerate() {
2810 if let Some(ref vc) = nested.variant_code {
2812 let variant_key = format!("{}_{}", nested.id, vc.to_uppercase());
2813 order.insert(variant_key, i);
2814 }
2815 order.entry(nested.id.clone()).or_insert(i);
2817 }
2818 }
2819 order
2820}
2821
2822fn variant_mig_position(
2828 def: &MappingDefinition,
2829 base_group_id: &str,
2830 mig_order: &HashMap<String, usize>,
2831) -> usize {
2832 if let Some(ref sp) = def.meta.source_path {
2835 let base_lower = base_group_id.to_lowercase();
2837 for part in sp.split('.') {
2838 if part.starts_with(&base_lower)
2839 || part.starts_with(base_group_id.to_lowercase().as_str())
2840 {
2841 if let Some(underscore_pos) = part.find('_') {
2843 let qualifier = &part[underscore_pos + 1..];
2844 let variant_key = format!("{}_{}", base_group_id, qualifier.to_uppercase());
2845 if let Some(&pos) = mig_order.get(&variant_key) {
2846 return pos;
2847 }
2848 }
2849 }
2850 }
2851 }
2852 mig_order.get(base_group_id).copied().unwrap_or(usize::MAX)
2854}
2855
2856fn find_rep_by_entry_qualifier<'a>(
2861 reps: &'a [AssembledGroupInstance],
2862 qualifier: &str,
2863) -> Option<&'a AssembledGroupInstance> {
2864 let parts: Vec<&str> = qualifier.split('_').collect();
2866 reps.iter().find(|inst| {
2867 inst.segments.first().is_some_and(|seg| {
2868 seg.elements
2869 .first()
2870 .and_then(|e| e.first())
2871 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
2872 })
2873 })
2874}
2875
2876fn find_all_reps_by_entry_qualifier<'a>(
2878 reps: &'a [AssembledGroupInstance],
2879 qualifier: &str,
2880) -> Vec<&'a AssembledGroupInstance> {
2881 let parts: Vec<&str> = qualifier.split('_').collect();
2883 reps.iter()
2884 .filter(|inst| {
2885 inst.segments.first().is_some_and(|seg| {
2886 seg.elements
2887 .first()
2888 .and_then(|e| e.first())
2889 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
2890 })
2891 })
2892 .collect()
2893}
2894
2895fn has_source_path_qualifiers(source_path: &str) -> bool {
2897 source_path.split('.').any(|part| {
2898 if let Some(pos) = part.find('_') {
2899 pos < part.len() - 1
2900 } else {
2901 false
2902 }
2903 })
2904}
2905
2906fn parse_group_spec(part: &str) -> (&str, Option<usize>) {
2907 if let Some(colon_pos) = part.find(':') {
2908 let id = &part[..colon_pos];
2909 let rep = part[colon_pos + 1..].parse::<usize>().ok();
2910 (id, rep)
2911 } else {
2912 (part, None)
2913 }
2914}
2915
2916fn strip_tx_group_prefix(source_group: &str, tx_group: &str) -> String {
2922 if source_group == tx_group || source_group.is_empty() {
2923 String::new()
2924 } else if let Some(rest) = source_group.strip_prefix(tx_group) {
2925 rest.strip_prefix('.').unwrap_or(rest).to_string()
2926 } else {
2927 source_group.to_string()
2928 }
2929}
2930
2931fn place_in_groups(
2939 groups: &mut Vec<AssembledGroup>,
2940 relative_path: &str,
2941 instance: AssembledGroupInstance,
2942) -> usize {
2943 let parts: Vec<&str> = relative_path.split('.').collect();
2944
2945 if parts.len() == 1 {
2946 let (id, rep) = parse_group_spec(parts[0]);
2948
2949 let group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == id) {
2951 g
2952 } else {
2953 groups.push(AssembledGroup {
2954 group_id: id.to_string(),
2955 repetitions: vec![],
2956 });
2957 groups.last_mut().unwrap()
2958 };
2959
2960 if let Some(rep_idx) = rep {
2961 while group.repetitions.len() <= rep_idx {
2963 group.repetitions.push(AssembledGroupInstance {
2964 segments: vec![],
2965 child_groups: vec![],
2966 entry_mig_number: None,
2967 variant_mig_numbers: vec![],
2968 skipped_segments: Vec::new(),
2969 skipped_positions: Vec::new(),
2970 });
2971 }
2972 group.repetitions[rep_idx]
2973 .segments
2974 .extend(instance.segments);
2975 group.repetitions[rep_idx]
2976 .child_groups
2977 .extend(instance.child_groups);
2978 rep_idx
2979 } else {
2980 let pos = group.repetitions.len();
2982 group.repetitions.push(instance);
2983 pos
2984 }
2985 } else {
2986 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
2988 let rep_idx = parent_rep.unwrap_or(0);
2989
2990 let parent_group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == parent_id) {
2992 g
2993 } else {
2994 groups.push(AssembledGroup {
2995 group_id: parent_id.to_string(),
2996 repetitions: vec![],
2997 });
2998 groups.last_mut().unwrap()
2999 };
3000
3001 while parent_group.repetitions.len() <= rep_idx {
3003 parent_group.repetitions.push(AssembledGroupInstance {
3004 segments: vec![],
3005 child_groups: vec![],
3006 entry_mig_number: None,
3007 variant_mig_numbers: vec![],
3008 skipped_segments: Vec::new(),
3009 skipped_positions: Vec::new(),
3010 });
3011 }
3012
3013 let remaining = parts[1..].join(".");
3014 place_in_groups(
3015 &mut parent_group.repetitions[rep_idx].child_groups,
3016 &remaining,
3017 instance,
3018 );
3019 rep_idx
3020 }
3021}
3022
3023fn resolve_child_relative(
3035 relative: &str,
3036 source_path: Option<&str>,
3037 source_path_to_rep: &std::collections::HashMap<String, Vec<usize>>,
3038 item_idx: usize,
3039) -> String {
3040 let parts: Vec<&str> = relative.split('.').collect();
3041 if parts.is_empty() {
3042 return relative.to_string();
3043 }
3044
3045 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
3047 if parent_rep.is_some() {
3048 return relative.to_string();
3049 }
3050
3051 if let Some(sp) = source_path {
3053 if let Some((parent_path, _child)) = sp.rsplit_once('.') {
3054 if let Some(rep_indices) = source_path_to_rep.get(parent_path) {
3056 let rep_idx = rep_indices
3057 .get(item_idx)
3058 .or_else(|| rep_indices.last())
3059 .copied()
3060 .unwrap_or(0);
3061 let rest = parts[1..].join(".");
3062 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3063 }
3064 let prefix = format!("{}_", parent_path);
3072 let mut unioned: Vec<usize> = source_path_to_rep
3073 .iter()
3074 .filter(|(k, _)| k.starts_with(&prefix))
3075 .flat_map(|(_, v)| v.iter().copied())
3076 .collect();
3077 if !unioned.is_empty() {
3078 unioned.sort_unstable();
3079 unioned.dedup();
3080 let rep_idx = unioned
3081 .get(item_idx)
3082 .or_else(|| unioned.last())
3083 .copied()
3084 .unwrap_or(0);
3085 let rest = parts[1..].join(".");
3086 return format!("{}:{}.{}", parent_id, rep_idx, rest);
3087 }
3088 }
3089 }
3090
3091 relative.to_string()
3093}
3094
3095struct DiscriminatorMatcher<'a> {
3102 tag: &'a str,
3103 element_idx: usize,
3104 component_idx: usize,
3105 expected_values: Vec<&'a str>,
3106 occurrence: Option<usize>,
3108}
3109
3110impl<'a> DiscriminatorMatcher<'a> {
3111 fn parse(disc: &'a str) -> Option<Self> {
3112 let (spec, expected) = disc.split_once('=')?;
3113 let parts: Vec<&str> = spec.split('.').collect();
3114 if parts.len() != 3 {
3115 return None;
3116 }
3117 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
3118 Some(Self {
3119 tag: parts[0],
3120 element_idx: parts[1].parse().ok()?,
3121 component_idx: parts[2].parse().ok()?,
3122 expected_values: expected_raw.split('|').collect(),
3123 occurrence,
3124 })
3125 }
3126
3127 fn matches(&self, instance: &AssembledGroupInstance) -> bool {
3128 instance.segments.iter().any(|s| {
3129 s.tag.eq_ignore_ascii_case(self.tag)
3130 && s.elements
3131 .get(self.element_idx)
3132 .and_then(|e| e.get(self.component_idx))
3133 .map(|v| self.expected_values.iter().any(|ev| v == ev))
3134 .unwrap_or(false)
3135 })
3136 }
3137
3138 fn filter_instances<'b>(
3140 &self,
3141 instances: Vec<&'b AssembledGroupInstance>,
3142 ) -> Vec<&'b AssembledGroupInstance> {
3143 let matching: Vec<_> = instances
3144 .into_iter()
3145 .filter(|inst| self.matches(inst))
3146 .collect();
3147 if let Some(occ) = self.occurrence {
3148 matching.into_iter().nth(occ).into_iter().collect()
3149 } else {
3150 matching
3151 }
3152 }
3153}
3154
3155fn parse_discriminator_occurrence(expected: &str) -> (&str, Option<usize>) {
3161 if let Some(hash_pos) = expected.rfind('#') {
3162 if let Ok(occ) = expected[hash_pos + 1..].parse::<usize>() {
3163 return (&expected[..hash_pos], Some(occ));
3164 }
3165 }
3166 (expected, None)
3167}
3168
3169fn strip_rep_index(relative: &str) -> String {
3173 let (id, _) = parse_group_spec(relative);
3174 id.to_string()
3175}
3176
3177fn strip_all_rep_indices(relative: &str) -> String {
3182 relative
3183 .split('.')
3184 .map(|part| {
3185 let (id, _) = parse_group_spec(part);
3186 id
3187 })
3188 .collect::<Vec<_>>()
3189 .join(".")
3190}
3191
3192fn parse_tag_qualifier(tag_part: &str) -> (String, Option<&str>, usize) {
3199 if let Some(bracket_start) = tag_part.find('[') {
3200 let tag = tag_part[..bracket_start].to_uppercase();
3201 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
3202 if let Some(comma_pos) = inner.find(',') {
3203 let qualifier = &inner[..comma_pos];
3204 let index = inner[comma_pos + 1..].parse::<usize>().unwrap_or(0);
3205 if qualifier == "*" {
3207 (tag, None, index)
3208 } else {
3209 (tag, Some(qualifier), index)
3210 }
3211 } else {
3212 (tag, Some(inner), 0)
3213 }
3214 } else {
3215 (tag_part.to_uppercase(), None, 0)
3216 }
3217}
3218
3219pub fn deep_merge_insert(
3225 result: &mut serde_json::Map<String, serde_json::Value>,
3226 entity: &str,
3227 bo4e: serde_json::Value,
3228) {
3229 if let Some(existing) = result.get_mut(entity) {
3230 if let (Some(existing_arr), Some(new_arr)) =
3233 (existing.as_array().map(|a| a.len()), bo4e.as_array())
3234 {
3235 if existing_arr == new_arr.len() {
3236 let existing_arr = existing.as_array_mut().unwrap();
3237 for (existing_elem, new_elem) in existing_arr.iter_mut().zip(new_arr) {
3238 if let (Some(existing_map), Some(new_map)) =
3239 (existing_elem.as_object_mut(), new_elem.as_object())
3240 {
3241 for (k, v) in new_map {
3242 if let Some(existing_v) = existing_map.get_mut(k) {
3243 if let (Some(existing_inner), Some(new_inner)) =
3244 (existing_v.as_object_mut(), v.as_object())
3245 {
3246 for (ik, iv) in new_inner {
3247 existing_inner
3248 .entry(ik.clone())
3249 .or_insert_with(|| iv.clone());
3250 }
3251 }
3252 } else {
3253 existing_map.insert(k.clone(), v.clone());
3254 }
3255 }
3256 }
3257 }
3258 return;
3259 }
3260 }
3261 if let (Some(existing_map), serde_json::Value::Object(new_map)) =
3263 (existing.as_object_mut(), &bo4e)
3264 {
3265 for (k, v) in new_map {
3266 if let Some(existing_v) = existing_map.get_mut(k) {
3267 if let (Some(existing_inner), Some(new_inner)) =
3269 (existing_v.as_object_mut(), v.as_object())
3270 {
3271 for (ik, iv) in new_inner {
3272 existing_inner
3273 .entry(ik.clone())
3274 .or_insert_with(|| iv.clone());
3275 }
3276 }
3277 } else {
3279 existing_map.insert(k.clone(), v.clone());
3280 }
3281 }
3282 return;
3283 }
3284 }
3285 result.insert(entity.to_string(), bo4e);
3286}
3287
3288fn is_map_keyed_object(value: &serde_json::Value) -> bool {
3299 let Some(obj) = value.as_object() else {
3300 return false;
3301 };
3302 if obj.is_empty() {
3303 return false;
3304 }
3305 obj.iter().all(|(k, v)| {
3307 k.len() <= 5
3308 && k.chars()
3309 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
3310 && v.is_object()
3311 })
3312}
3313
3314fn find_qualifier_companion_field(
3323 definitions: &[crate::definition::MappingDefinition],
3324 entity: &str,
3325) -> Option<String> {
3326 for def in definitions {
3327 if def.meta.entity != *entity {
3328 continue;
3329 }
3330 let disc = def.meta.discriminator.as_deref()?;
3331 let (disc_path, _) = disc.split_once('=')?;
3332 let disc_path_lower = disc_path.to_lowercase();
3333
3334 for (path, mapping) in &def.fields {
3337 let cf_path = path.to_lowercase();
3338 let matches = cf_path == disc_path_lower || format!("{}.0", cf_path) == disc_path_lower;
3339 if matches {
3340 let target = match mapping {
3341 FieldMapping::Simple(t) => t.as_str(),
3342 FieldMapping::Structured(s) => s.target.as_str(),
3343 FieldMapping::Nested(_) => continue,
3344 };
3345 if !target.is_empty() {
3346 return Some(target.to_string());
3347 }
3348 }
3349 }
3350 }
3351 None
3352}
3353
3354fn extract_child_from_parent(
3363 entities: &serde_json::Value,
3364 definitions: &[MappingDefinition],
3365 child_def: &MappingDefinition,
3366) -> Option<serde_json::Value> {
3367 extract_child_from_parent_with_indices(entities, definitions, child_def).map(|(v, _)| v)
3368}
3369
3370fn extract_child_from_parent_with_indices(
3375 entities: &serde_json::Value,
3376 definitions: &[MappingDefinition],
3377 child_def: &MappingDefinition,
3378) -> Option<(serde_json::Value, Vec<usize>)> {
3379 let parts: Vec<&str> = child_def.meta.source_group.split('.').collect();
3380 if parts.len() < 2 {
3381 return None;
3382 }
3383 let parent_group = parts[0];
3384 let parent_def = definitions
3385 .iter()
3386 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_def.meta.entity)?;
3387 let parent_key = to_camel_case(&parent_def.meta.entity);
3388 let child_key = to_camel_case(&child_def.meta.entity);
3389 let parent_value = entities.get(&parent_key)?;
3390
3391 if let Some(parent_map) = parent_value.as_object() {
3393 if is_map_keyed_value(parent_map) {
3394 let mut children: Vec<serde_json::Value> = Vec::new();
3395 let mut indices: Vec<usize> = Vec::new();
3396 for (i, (_key, inner)) in parent_map.iter().enumerate() {
3397 if let Some(child) = inner.get(&child_key) {
3398 if !child.is_null() {
3399 children.push(child.clone());
3400 indices.push(i);
3401 }
3402 }
3403 }
3404 return match children.len() {
3405 0 => None,
3406 1 => Some((children.into_iter().next().unwrap(), indices)),
3407 _ => Some((serde_json::Value::Array(children), indices)),
3408 };
3409 }
3410 }
3411
3412 if let Some(parent_arr) = parent_value.as_array() {
3414 let mut children: Vec<serde_json::Value> = Vec::new();
3415 let mut indices: Vec<usize> = Vec::new();
3416 for (i, item) in parent_arr.iter().enumerate() {
3417 if let Some(child) = item.get(&child_key) {
3418 if !child.is_null() {
3419 children.push(child.clone());
3420 indices.push(i);
3421 }
3422 }
3423 }
3424 return match children.len() {
3425 0 => None,
3426 1 => Some((children.into_iter().next().unwrap(), indices)),
3427 _ => Some((serde_json::Value::Array(children), indices)),
3428 };
3429 }
3430
3431 let child = parent_value.get(&child_key)?;
3433 if child.is_null() {
3434 return None;
3435 }
3436 Some((child.clone(), vec![0]))
3437}
3438
3439fn nest_child_entities_in_result(
3445 result: &mut serde_json::Map<String, serde_json::Value>,
3446 definitions: &[MappingDefinition],
3447 nesting_info: &std::collections::HashMap<String, Vec<usize>>,
3448 transaction_group: Option<&str>,
3449) {
3450 let mut nesting_pairs: Vec<(String, String, String, Option<String>)> = Vec::new();
3453 for def in definitions {
3454 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
3455 if parts.len() < 2 {
3456 continue;
3457 }
3458 let parent_group = parts[0];
3459 if transaction_group.is_some_and(|tx| tx == parent_group) {
3465 continue;
3466 }
3467 let child_entity = def.meta.entity.clone();
3468 let child_has_parent_level_def = definitions
3472 .iter()
3473 .any(|d| d.meta.source_group == parent_group && d.meta.entity == child_entity);
3474 if child_has_parent_level_def {
3475 continue;
3476 }
3477 let parent_entity = definitions
3479 .iter()
3480 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_entity)
3481 .map(|d| d.meta.entity.clone());
3482 if let Some(ref parent_entity) = parent_entity {
3483 let child_key_lc = to_camel_case(&child_entity);
3488 let parent_defs: Vec<_> = definitions
3489 .iter()
3490 .filter(|d| d.meta.entity == *parent_entity)
3491 .collect();
3492 let has_conflicting_field = parent_defs.iter().any(|pd| {
3493 pd.fields.values().any(|fm| {
3494 let target = match fm {
3495 crate::definition::FieldMapping::Simple(t) => t.as_str(),
3496 crate::definition::FieldMapping::Structured(s) => s.target.as_str(),
3497 crate::definition::FieldMapping::Nested(_) => "",
3498 };
3499 target.starts_with(&child_key_lc)
3500 && target.get(child_key_lc.len()..child_key_lc.len() + 1) == Some(".")
3501 })
3502 });
3503 if has_conflicting_field {
3504 continue;
3505 }
3506 if nesting_pairs
3508 .iter()
3509 .any(|(_, pe, ce, _)| *pe == *parent_entity && *ce == child_entity)
3510 {
3511 continue;
3512 }
3513 nesting_pairs.push((
3514 parent_group.to_string(),
3515 parent_entity.clone(),
3516 child_entity,
3517 def.meta.source_path.clone(),
3518 ));
3519 }
3520 }
3521
3522 for (_parent_group, parent_entity, child_entity, child_source_path) in nesting_pairs {
3523 let parent_key = to_camel_case(&parent_entity);
3524 let child_key = to_camel_case(&child_entity);
3525
3526 let child_value = match result.remove(&child_key) {
3528 Some(v) => v,
3529 None => continue,
3530 };
3531
3532 let Some(parent_value) = result.get_mut(&parent_key) else {
3537 result.insert(child_key, child_value);
3539 continue;
3540 };
3541 if parent_value.is_array() {
3542 result.insert(child_key, child_value);
3543 continue;
3544 }
3545
3546 let distribution = child_source_path
3548 .as_deref()
3549 .and_then(|sp| nesting_info.get(sp));
3550
3551 let child_items: Vec<(usize, &serde_json::Value)> = match &child_value {
3553 serde_json::Value::Array(arr) => arr.iter().enumerate().collect(),
3554 other => vec![(0, other)],
3555 };
3556
3557 let insert_or_append = |obj: &mut serde_json::Map<String, serde_json::Value>,
3560 key: &str,
3561 val: &serde_json::Value| {
3562 match obj.get_mut(key) {
3563 Some(existing) => {
3564 if !existing.is_array() {
3566 let prev = existing.take();
3567 *existing = serde_json::Value::Array(vec![prev]);
3568 }
3569 if let Some(arr) = existing.as_array_mut() {
3570 arr.push(val.clone());
3571 }
3572 }
3573 None => {
3574 obj.insert(key.to_string(), val.clone());
3575 }
3576 }
3577 };
3578
3579 if let Some(parent_map) = parent_value.as_object_mut() {
3581 if is_map_keyed_value(parent_map) {
3582 let keys: Vec<String> = parent_map.keys().cloned().collect();
3584 for (i, child_item) in &child_items {
3585 let target_idx = distribution
3586 .and_then(|dist| dist.get(*i))
3587 .copied()
3588 .unwrap_or(0);
3589 if let Some(key) = keys.get(target_idx) {
3590 if let Some(inner) = parent_map.get_mut(key).and_then(|v| v.as_object_mut())
3591 {
3592 insert_or_append(inner, &child_key, child_item);
3593 }
3594 }
3595 }
3596 continue;
3597 }
3598 }
3599
3600 if let Some(parent_arr) = parent_value.as_array_mut() {
3602 for (i, child_item) in &child_items {
3603 let target_idx = distribution
3604 .and_then(|dist| dist.get(*i))
3605 .copied()
3606 .unwrap_or(0);
3607 if let Some(parent_obj) = parent_arr
3608 .get_mut(target_idx)
3609 .and_then(|v| v.as_object_mut())
3610 {
3611 insert_or_append(parent_obj, &child_key, child_item);
3612 }
3613 }
3614 continue;
3615 }
3616
3617 if let Some(parent_obj) = parent_value.as_object_mut() {
3619 for (_i, child_item) in &child_items {
3620 insert_or_append(parent_obj, &child_key, child_item);
3621 }
3622 continue;
3623 }
3624
3625 result.insert(child_key, child_value);
3627 }
3628}
3629
3630pub use crate::dp_routing::{route_nad_dp_to_lokation, unroute_lokation_to_nad_dp, DpRouting};
3632
3633fn is_map_keyed_value(map: &serde_json::Map<String, serde_json::Value>) -> bool {
3635 if map.is_empty() {
3636 return false;
3637 }
3638 map.values().all(|v| v.is_object())
3639 && map.keys().all(|k| {
3640 k.len() <= 5
3641 || k.chars()
3642 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
3643 })
3644}
3645
3646fn to_camel_case(name: &str) -> String {
3647 let mut chars = name.chars();
3648 match chars.next() {
3649 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
3650 None => String::new(),
3651 }
3652}
3653
3654fn set_nested_value(map: &mut serde_json::Map<String, serde_json::Value>, path: &str, val: String) {
3657 set_nested_value_json(map, path, serde_json::Value::String(val));
3658}
3659
3660fn set_nested_value_json(
3662 map: &mut serde_json::Map<String, serde_json::Value>,
3663 path: &str,
3664 val: serde_json::Value,
3665) {
3666 if let Some((prefix, leaf)) = path.rsplit_once('.') {
3667 let mut current = map;
3668 for part in prefix.split('.') {
3669 let entry = current
3670 .entry(part.to_string())
3671 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
3672 current = entry.as_object_mut().expect("expected object in path");
3673 }
3674 current.insert(leaf.to_string(), val);
3675 } else {
3676 map.insert(path.to_string(), val);
3677 }
3678}
3679
3680#[derive(serde::Serialize, serde::Deserialize)]
3685pub struct VariantCache {
3686 pub message_defs: Vec<MappingDefinition>,
3688 pub transaction_defs: HashMap<String, Vec<MappingDefinition>>,
3690 pub combined_defs: HashMap<String, Vec<MappingDefinition>>,
3692 #[serde(default)]
3694 pub code_lookups: HashMap<String, crate::code_lookup::CodeLookup>,
3695 #[serde(default)]
3697 pub mig_schema: Option<mig_types::schema::mig::MigSchema>,
3698 #[serde(default)]
3700 pub segment_structure: Option<crate::segment_structure::SegmentStructure>,
3701 #[serde(default)]
3704 pub pid_segment_numbers: HashMap<String, Vec<String>>,
3705 #[serde(default)]
3708 pub pid_requirements: HashMap<String, crate::pid_requirements::PidRequirements>,
3709 #[serde(default)]
3713 pub tx_groups: HashMap<String, String>,
3714}
3715
3716impl VariantCache {
3717 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
3719 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
3720 path: path.display().to_string(),
3721 message: e.to_string(),
3722 })?;
3723 if let Some(parent) = path.parent() {
3724 std::fs::create_dir_all(parent)?;
3725 }
3726 std::fs::write(path, encoded)?;
3727 Ok(())
3728 }
3729
3730 pub fn load(path: &Path) -> Result<Self, MappingError> {
3732 let bytes = std::fs::read(path)?;
3733 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
3734 path: path.display().to_string(),
3735 message: e.to_string(),
3736 })
3737 }
3738
3739 pub fn tx_group(&self, pid: &str) -> Option<&str> {
3743 self.tx_groups
3744 .get(&format!("pid_{pid}"))
3745 .map(|s| s.as_str())
3746 }
3747
3748 pub fn msg_engine(&self, pid: &str) -> MappingEngine {
3752 let mut eng = MappingEngine::from_definitions(self.message_defs.clone()).with_pid(pid);
3753 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
3754 eng = eng.with_code_lookup(cl.clone());
3755 }
3756 eng
3757 }
3758
3759 pub fn tx_engine(&self, pid: &str) -> Option<MappingEngine> {
3763 self.transaction_defs
3764 .get(&format!("pid_{pid}"))
3765 .map(|defs| {
3766 let mut eng = MappingEngine::from_definitions(defs.clone()).with_pid(pid);
3767 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
3768 eng = eng.with_code_lookup(cl.clone());
3769 }
3770 eng
3771 })
3772 }
3773
3774 pub fn filtered_mig(&self, pid: &str) -> Option<mig_types::schema::mig::MigSchema> {
3782 let mig = self.mig_schema.as_ref()?;
3783 let numbers = self
3784 .pid_segment_numbers
3785 .get(&format!("pid_{pid}"))
3786 .or_else(|| self.pid_segment_numbers.get("pid_"))?;
3787 let number_set: std::collections::HashSet<String> = numbers.iter().cloned().collect();
3788 Some(mig_assembly::pid_filter::filter_mig_for_pid(
3789 mig,
3790 &number_set,
3791 ))
3792 }
3793}
3794
3795#[derive(serde::Serialize, serde::Deserialize)]
3800pub struct DataBundle {
3801 pub format_version: String,
3802 pub bundle_version: u32,
3803 pub variants: HashMap<String, VariantCache>,
3804 #[serde(default)]
3809 pub bo4e_catalog: crate::bo4e_catalog::Bo4eCatalog,
3810}
3811
3812impl DataBundle {
3813 pub const CURRENT_VERSION: u32 = 2;
3814
3815 pub fn variant(&self, name: &str) -> Option<&VariantCache> {
3816 self.variants.get(name)
3817 }
3818
3819 pub fn write_to<W: std::io::Write>(&self, writer: &mut W) -> Result<(), MappingError> {
3820 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
3821 path: "<stream>".to_string(),
3822 message: e.to_string(),
3823 })?;
3824 writer.write_all(&encoded).map_err(MappingError::Io)
3825 }
3826
3827 pub fn read_from<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
3828 let mut bytes = Vec::new();
3829 reader.read_to_end(&mut bytes).map_err(MappingError::Io)?;
3830 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
3831 path: "<stream>".to_string(),
3832 message: e.to_string(),
3833 })
3834 }
3835
3836 pub fn read_from_checked<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
3837 let bundle = Self::read_from(reader)?;
3838 if bundle.bundle_version != Self::CURRENT_VERSION {
3839 return Err(MappingError::CacheRead {
3840 path: "<stream>".to_string(),
3841 message: format!(
3842 "Incompatible bundle version {}, expected version {}. \
3843 Run `edifact-data update` to fetch compatible bundles.",
3844 bundle.bundle_version,
3845 Self::CURRENT_VERSION
3846 ),
3847 });
3848 }
3849 Ok(bundle)
3850 }
3851
3852 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
3853 if let Some(parent) = path.parent() {
3854 std::fs::create_dir_all(parent)?;
3855 }
3856 let mut file = std::fs::File::create(path).map_err(MappingError::Io)?;
3857 self.write_to(&mut file)
3858 }
3859
3860 pub fn load(path: &Path) -> Result<Self, MappingError> {
3861 let mut file = std::fs::File::open(path).map_err(MappingError::Io)?;
3862 Self::read_from_checked(&mut file)
3863 }
3864}
3865
3866fn sort_variant_reps_by_mig(
3879 child_groups: &mut [AssembledGroup],
3880 mig: &MigSchema,
3881 transaction_group: &str,
3882) {
3883 let tx_def = match mig
3884 .segment_groups
3885 .iter()
3886 .find(|sg| sg.id == transaction_group)
3887 {
3888 Some(d) => d,
3889 None => return,
3890 };
3891
3892 for cg in child_groups.iter_mut() {
3893 if cg.repetitions.len() <= 1 {
3894 continue;
3895 }
3896
3897 let variant_defs: Vec<(usize, &mig_types::schema::mig::MigSegmentGroup)> = tx_def
3899 .nested_groups
3900 .iter()
3901 .enumerate()
3902 .filter(|(_, ng)| ng.id == cg.group_id && ng.variant_code.is_some())
3903 .collect();
3904
3905 if variant_defs.is_empty() {
3906 continue;
3907 }
3908
3909 cg.repetitions.sort_by_key(|rep| {
3912 let entry_seg = rep.segments.first();
3913 for &(mig_pos, variant_def) in &variant_defs {
3914 let (ei, ci) = variant_def.variant_qualifier_position.unwrap_or((0, 0));
3915 let actual_qual = entry_seg
3916 .and_then(|s| s.elements.get(ei))
3917 .and_then(|e| e.get(ci))
3918 .map(|s| s.as_str())
3919 .unwrap_or("");
3920 let matches = if !variant_def.variant_codes.is_empty() {
3921 variant_def
3922 .variant_codes
3923 .iter()
3924 .any(|c| actual_qual.eq_ignore_ascii_case(c))
3925 } else if let Some(ref expected_code) = variant_def.variant_code {
3926 actual_qual.eq_ignore_ascii_case(expected_code)
3927 } else {
3928 false
3929 };
3930 if matches {
3931 return mig_pos;
3932 }
3933 }
3934 usize::MAX });
3936 }
3937}
3938
3939#[cfg(test)]
3940mod variant_cache_helper_tests {
3941 use super::*;
3942
3943 fn make_test_cache() -> VariantCache {
3944 let mut tx_groups = HashMap::new();
3945 tx_groups.insert("pid_55001".to_string(), "SG4".to_string());
3946 tx_groups.insert("pid_21007".to_string(), "SG14".to_string());
3947
3948 let mut transaction_defs = HashMap::new();
3949 transaction_defs.insert("pid_55001".to_string(), vec![]);
3950 transaction_defs.insert("pid_21007".to_string(), vec![]);
3951
3952 VariantCache {
3953 message_defs: vec![],
3954 transaction_defs,
3955 combined_defs: HashMap::new(),
3956 code_lookups: HashMap::new(),
3957 mig_schema: None,
3958 segment_structure: None,
3959 pid_segment_numbers: HashMap::new(),
3960 pid_requirements: HashMap::new(),
3961 tx_groups,
3962 }
3963 }
3964
3965 #[test]
3966 fn test_tx_group_returns_correct_group() {
3967 let vc = make_test_cache();
3968 assert_eq!(vc.tx_group("55001").unwrap(), "SG4");
3969 assert_eq!(vc.tx_group("21007").unwrap(), "SG14");
3970 }
3971
3972 #[test]
3973 fn test_tx_group_unknown_pid_returns_none() {
3974 let vc = make_test_cache();
3975 assert!(vc.tx_group("99999").is_none());
3976 }
3977
3978 #[test]
3979 fn test_msg_engine_returns_engine() {
3980 let vc = make_test_cache();
3981 let engine = vc.msg_engine("55001");
3982 assert_eq!(engine.definitions().len(), 0);
3983 }
3984
3985 #[test]
3986 fn test_tx_engine_returns_engine_for_known_pid() {
3987 let vc = make_test_cache();
3988 assert!(vc.tx_engine("55001").is_some());
3989 }
3990
3991 #[test]
3992 fn test_tx_engine_returns_none_for_unknown_pid() {
3993 let vc = make_test_cache();
3994 assert!(vc.tx_engine("99999").is_none());
3995 }
3996}
3997
3998#[cfg(test)]
3999mod tests {
4000 use super::*;
4001 use crate::definition::{MappingDefinition, MappingMeta, StructuredFieldMapping};
4002 use indexmap::IndexMap;
4003
4004 fn make_def(fields: IndexMap<String, FieldMapping>) -> MappingDefinition {
4005 MappingDefinition {
4006 meta: MappingMeta {
4007 entity: "Test".to_string(),
4008 bo4e_type: "Test".to_string(),
4009 source_group: "SG4".to_string(),
4010 source_path: None,
4011 discriminator: None,
4012 repeat_on_tag: None,
4013 },
4014 fields,
4015 complex_handlers: None,
4016 }
4017 }
4018
4019 #[test]
4020 fn test_map_interchange_single_transaction_backward_compat() {
4021 use mig_assembly::assembler::*;
4022
4023 let tree = AssembledTree {
4025 segments: vec![
4026 AssembledSegment {
4027 tag: "UNH".to_string(),
4028 elements: vec![vec!["001".to_string()]],
4029 mig_number: None,
4030 segment_number: None,
4031 },
4032 AssembledSegment {
4033 tag: "BGM".to_string(),
4034 elements: vec![vec!["E01".to_string()], vec!["DOC001".to_string()]],
4035 mig_number: None,
4036 segment_number: None,
4037 },
4038 ],
4039 groups: vec![
4040 AssembledGroup {
4041 group_id: "SG2".to_string(),
4042 repetitions: vec![AssembledGroupInstance {
4043 segments: vec![AssembledSegment {
4044 tag: "NAD".to_string(),
4045 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4046 mig_number: None,
4047 segment_number: None,
4048 }],
4049 child_groups: vec![],
4050 entry_mig_number: None,
4051 variant_mig_numbers: vec![],
4052 skipped_segments: vec![],
4053 skipped_positions: Vec::new(),
4054 }],
4055 },
4056 AssembledGroup {
4057 group_id: "SG4".to_string(),
4058 repetitions: vec![AssembledGroupInstance {
4059 segments: vec![AssembledSegment {
4060 tag: "IDE".to_string(),
4061 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4062 mig_number: None,
4063 segment_number: None,
4064 }],
4065 child_groups: vec![AssembledGroup {
4066 group_id: "SG5".to_string(),
4067 repetitions: vec![AssembledGroupInstance {
4068 segments: vec![AssembledSegment {
4069 tag: "LOC".to_string(),
4070 elements: vec![
4071 vec!["Z16".to_string()],
4072 vec!["DE000111222333".to_string()],
4073 ],
4074 mig_number: None,
4075 segment_number: None,
4076 }],
4077 child_groups: vec![],
4078 entry_mig_number: None,
4079 variant_mig_numbers: vec![],
4080 skipped_segments: vec![],
4081 skipped_positions: Vec::new(),
4082 }],
4083 }],
4084 entry_mig_number: None,
4085 variant_mig_numbers: vec![],
4086 skipped_segments: vec![],
4087 skipped_positions: Vec::new(),
4088 }],
4089 },
4090 ],
4091 post_group_start: 2,
4092 inter_group_segments: std::collections::BTreeMap::new(),
4093 };
4094
4095 let msg_engine = MappingEngine::from_definitions(vec![]);
4097
4098 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4100 tx_fields.insert(
4101 "ide.1".to_string(),
4102 FieldMapping::Simple("vorgangId".to_string()),
4103 );
4104 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4105 malo_fields.insert(
4106 "loc.1".to_string(),
4107 FieldMapping::Simple("marktlokationsId".to_string()),
4108 );
4109
4110 let tx_engine = MappingEngine::from_definitions(vec![
4111 MappingDefinition {
4112 meta: MappingMeta {
4113 entity: "Prozessdaten".to_string(),
4114 bo4e_type: "Prozessdaten".to_string(),
4115 source_group: "SG4".to_string(),
4116 source_path: None,
4117 discriminator: None,
4118 repeat_on_tag: None,
4119 },
4120 fields: tx_fields,
4121 complex_handlers: None,
4122 },
4123 MappingDefinition {
4124 meta: MappingMeta {
4125 entity: "Marktlokation".to_string(),
4126 bo4e_type: "Marktlokation".to_string(),
4127 source_group: "SG4.SG5".to_string(),
4128 source_path: None,
4129 discriminator: None,
4130 repeat_on_tag: None,
4131 },
4132 fields: malo_fields,
4133 complex_handlers: None,
4134 },
4135 ]);
4136
4137 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
4138
4139 assert_eq!(result.transaktionen.len(), 1);
4140 assert_eq!(
4141 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
4142 .as_str()
4143 .unwrap(),
4144 "TX001"
4145 );
4146 assert_eq!(
4149 result.transaktionen[0].stammdaten["marktlokation"]["marktlokationsId"]
4150 .as_str()
4151 .unwrap(),
4152 "DE000111222333"
4153 );
4154 }
4155
4156 #[test]
4157 fn test_map_reverse_pads_intermediate_empty_elements() {
4158 let mut fields = IndexMap::new();
4160 fields.insert(
4161 "nad.0".to_string(),
4162 FieldMapping::Structured(StructuredFieldMapping {
4163 target: String::new(),
4164 transform: None,
4165 when: None,
4166 default: Some("Z09".to_string()),
4167 enum_map: None,
4168 when_filled: None,
4169 also_target: None,
4170 also_enum_map: None,
4171 }),
4172 );
4173 fields.insert(
4174 "nad.3.0".to_string(),
4175 FieldMapping::Simple("name".to_string()),
4176 );
4177 fields.insert(
4178 "nad.3.1".to_string(),
4179 FieldMapping::Simple("vorname".to_string()),
4180 );
4181
4182 let def = make_def(fields);
4183 let engine = MappingEngine::from_definitions(vec![]);
4184
4185 let bo4e = serde_json::json!({
4186 "name": "Muster",
4187 "vorname": "Max"
4188 });
4189
4190 let instance = engine.map_reverse(&bo4e, &def);
4191 assert_eq!(instance.segments.len(), 1);
4192
4193 let nad = &instance.segments[0];
4194 assert_eq!(nad.tag, "NAD");
4195 assert_eq!(nad.elements.len(), 4);
4196 assert_eq!(nad.elements[0], vec!["Z09"]);
4197 assert_eq!(nad.elements[1], vec![""]);
4199 assert_eq!(nad.elements[2], vec![""]);
4200 assert_eq!(nad.elements[3][0], "Muster");
4201 assert_eq!(nad.elements[3][1], "Max");
4202 }
4203
4204 #[test]
4205 fn test_map_reverse_no_padding_when_contiguous() {
4206 let mut fields = IndexMap::new();
4208 fields.insert(
4209 "dtm.0.0".to_string(),
4210 FieldMapping::Structured(StructuredFieldMapping {
4211 target: String::new(),
4212 transform: None,
4213 when: None,
4214 default: Some("92".to_string()),
4215 enum_map: None,
4216 when_filled: None,
4217 also_target: None,
4218 also_enum_map: None,
4219 }),
4220 );
4221 fields.insert(
4222 "dtm.0.1".to_string(),
4223 FieldMapping::Simple("value".to_string()),
4224 );
4225 fields.insert(
4226 "dtm.0.2".to_string(),
4227 FieldMapping::Structured(StructuredFieldMapping {
4228 target: String::new(),
4229 transform: None,
4230 when: None,
4231 default: Some("303".to_string()),
4232 enum_map: None,
4233 when_filled: None,
4234 also_target: None,
4235 also_enum_map: None,
4236 }),
4237 );
4238
4239 let def = make_def(fields);
4240 let engine = MappingEngine::from_definitions(vec![]);
4241
4242 let bo4e = serde_json::json!({ "value": "20250531" });
4243
4244 let instance = engine.map_reverse(&bo4e, &def);
4245 let dtm = &instance.segments[0];
4246 assert_eq!(dtm.elements.len(), 1);
4248 assert_eq!(dtm.elements[0], vec!["92", "20250531", "303"]);
4249 }
4250
4251 #[test]
4252 fn test_map_message_level_extracts_sg2_only() {
4253 use mig_assembly::assembler::*;
4254
4255 let tree = AssembledTree {
4257 segments: vec![
4258 AssembledSegment {
4259 tag: "UNH".to_string(),
4260 elements: vec![vec!["001".to_string()]],
4261 mig_number: None,
4262 segment_number: None,
4263 },
4264 AssembledSegment {
4265 tag: "BGM".to_string(),
4266 elements: vec![vec!["E01".to_string()]],
4267 mig_number: None,
4268 segment_number: None,
4269 },
4270 ],
4271 groups: vec![
4272 AssembledGroup {
4273 group_id: "SG2".to_string(),
4274 repetitions: vec![AssembledGroupInstance {
4275 segments: vec![AssembledSegment {
4276 tag: "NAD".to_string(),
4277 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4278 mig_number: None,
4279 segment_number: None,
4280 }],
4281 child_groups: vec![],
4282 entry_mig_number: None,
4283 variant_mig_numbers: vec![],
4284 skipped_segments: vec![],
4285 skipped_positions: Vec::new(),
4286 }],
4287 },
4288 AssembledGroup {
4289 group_id: "SG4".to_string(),
4290 repetitions: vec![AssembledGroupInstance {
4291 segments: vec![AssembledSegment {
4292 tag: "IDE".to_string(),
4293 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4294 mig_number: None,
4295 segment_number: None,
4296 }],
4297 child_groups: vec![],
4298 entry_mig_number: None,
4299 variant_mig_numbers: vec![],
4300 skipped_segments: vec![],
4301 skipped_positions: Vec::new(),
4302 }],
4303 },
4304 ],
4305 post_group_start: 2,
4306 inter_group_segments: std::collections::BTreeMap::new(),
4307 };
4308
4309 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4311 msg_fields.insert(
4312 "nad.0".to_string(),
4313 FieldMapping::Simple("marktrolle".to_string()),
4314 );
4315 msg_fields.insert(
4316 "nad.1".to_string(),
4317 FieldMapping::Simple("rollencodenummer".to_string()),
4318 );
4319 let msg_def = MappingDefinition {
4320 meta: MappingMeta {
4321 entity: "Marktteilnehmer".to_string(),
4322 bo4e_type: "Marktteilnehmer".to_string(),
4323 source_group: "SG2".to_string(),
4324 source_path: None,
4325 discriminator: None,
4326 repeat_on_tag: None,
4327 },
4328 fields: msg_fields,
4329 complex_handlers: None,
4330 };
4331
4332 let engine = MappingEngine::from_definitions(vec![msg_def.clone()]);
4333 let result = engine.map_all_forward(&tree);
4334
4335 assert!(result.get("marktteilnehmer").is_some());
4337 let mt = &result["marktteilnehmer"];
4338 assert_eq!(mt["marktrolle"].as_str().unwrap(), "MS");
4339 assert_eq!(mt["rollencodenummer"].as_str().unwrap(), "9900123");
4340 }
4341
4342 #[test]
4343 fn test_map_transaction_scoped_to_sg4_instance() {
4344 use mig_assembly::assembler::*;
4345
4346 let tree = AssembledTree {
4348 segments: vec![
4349 AssembledSegment {
4350 tag: "UNH".to_string(),
4351 elements: vec![vec!["001".to_string()]],
4352 mig_number: None,
4353 segment_number: None,
4354 },
4355 AssembledSegment {
4356 tag: "BGM".to_string(),
4357 elements: vec![vec!["E01".to_string()]],
4358 mig_number: None,
4359 segment_number: None,
4360 },
4361 ],
4362 groups: vec![AssembledGroup {
4363 group_id: "SG4".to_string(),
4364 repetitions: vec![AssembledGroupInstance {
4365 segments: vec![AssembledSegment {
4366 tag: "IDE".to_string(),
4367 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4368 mig_number: None,
4369 segment_number: None,
4370 }],
4371 child_groups: vec![AssembledGroup {
4372 group_id: "SG5".to_string(),
4373 repetitions: vec![AssembledGroupInstance {
4374 segments: vec![AssembledSegment {
4375 tag: "LOC".to_string(),
4376 elements: vec![
4377 vec!["Z16".to_string()],
4378 vec!["DE000111222333".to_string()],
4379 ],
4380 mig_number: None,
4381 segment_number: None,
4382 }],
4383 child_groups: vec![],
4384 entry_mig_number: None,
4385 variant_mig_numbers: vec![],
4386 skipped_segments: vec![],
4387 skipped_positions: Vec::new(),
4388 }],
4389 }],
4390 entry_mig_number: None,
4391 variant_mig_numbers: vec![],
4392 skipped_segments: vec![],
4393 skipped_positions: Vec::new(),
4394 }],
4395 }],
4396 post_group_start: 2,
4397 inter_group_segments: std::collections::BTreeMap::new(),
4398 };
4399
4400 let mut proz_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4402 proz_fields.insert(
4403 "ide.1".to_string(),
4404 FieldMapping::Simple("vorgangId".to_string()),
4405 );
4406 let proz_def = MappingDefinition {
4407 meta: MappingMeta {
4408 entity: "Prozessdaten".to_string(),
4409 bo4e_type: "Prozessdaten".to_string(),
4410 source_group: "".to_string(), source_path: None,
4412 discriminator: None,
4413 repeat_on_tag: None,
4414 },
4415 fields: proz_fields,
4416 complex_handlers: None,
4417 };
4418
4419 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4420 malo_fields.insert(
4421 "loc.1".to_string(),
4422 FieldMapping::Simple("marktlokationsId".to_string()),
4423 );
4424 let malo_def = MappingDefinition {
4425 meta: MappingMeta {
4426 entity: "Marktlokation".to_string(),
4427 bo4e_type: "Marktlokation".to_string(),
4428 source_group: "SG5".to_string(), source_path: None,
4430 discriminator: None,
4431 repeat_on_tag: None,
4432 },
4433 fields: malo_fields,
4434 complex_handlers: None,
4435 };
4436
4437 let tx_engine = MappingEngine::from_definitions(vec![proz_def, malo_def]);
4438
4439 let sg4 = &tree.groups[0]; let sg4_instance = &sg4.repetitions[0];
4442 let sub_tree = sg4_instance.as_assembled_tree();
4443
4444 let result = tx_engine.map_all_forward(&sub_tree);
4445
4446 assert_eq!(
4448 result["prozessdaten"]["vorgangId"].as_str().unwrap(),
4449 "TX001"
4450 );
4451
4452 assert_eq!(
4454 result["marktlokation"]["marktlokationsId"]
4455 .as_str()
4456 .unwrap(),
4457 "DE000111222333"
4458 );
4459 }
4460
4461 #[test]
4462 fn test_map_interchange_produces_full_hierarchy() {
4463 use mig_assembly::assembler::*;
4464
4465 let tree = AssembledTree {
4467 segments: vec![
4468 AssembledSegment {
4469 tag: "UNH".to_string(),
4470 elements: vec![vec!["001".to_string()]],
4471 mig_number: None,
4472 segment_number: None,
4473 },
4474 AssembledSegment {
4475 tag: "BGM".to_string(),
4476 elements: vec![vec!["E01".to_string()]],
4477 mig_number: None,
4478 segment_number: None,
4479 },
4480 ],
4481 groups: vec![
4482 AssembledGroup {
4483 group_id: "SG2".to_string(),
4484 repetitions: vec![AssembledGroupInstance {
4485 segments: vec![AssembledSegment {
4486 tag: "NAD".to_string(),
4487 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
4488 mig_number: None,
4489 segment_number: None,
4490 }],
4491 child_groups: vec![],
4492 entry_mig_number: None,
4493 variant_mig_numbers: vec![],
4494 skipped_segments: vec![],
4495 skipped_positions: Vec::new(),
4496 }],
4497 },
4498 AssembledGroup {
4499 group_id: "SG4".to_string(),
4500 repetitions: vec![
4501 AssembledGroupInstance {
4502 segments: vec![AssembledSegment {
4503 tag: "IDE".to_string(),
4504 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
4505 mig_number: None,
4506 segment_number: None,
4507 }],
4508 child_groups: vec![],
4509 entry_mig_number: None,
4510 variant_mig_numbers: vec![],
4511 skipped_segments: vec![],
4512 skipped_positions: Vec::new(),
4513 },
4514 AssembledGroupInstance {
4515 segments: vec![AssembledSegment {
4516 tag: "IDE".to_string(),
4517 elements: vec![vec!["24".to_string()], vec!["TX002".to_string()]],
4518 mig_number: None,
4519 segment_number: None,
4520 }],
4521 child_groups: vec![],
4522 entry_mig_number: None,
4523 variant_mig_numbers: vec![],
4524 skipped_segments: vec![],
4525 skipped_positions: Vec::new(),
4526 },
4527 ],
4528 },
4529 ],
4530 post_group_start: 2,
4531 inter_group_segments: std::collections::BTreeMap::new(),
4532 };
4533
4534 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4536 msg_fields.insert(
4537 "nad.0".to_string(),
4538 FieldMapping::Simple("marktrolle".to_string()),
4539 );
4540 let msg_defs = vec![MappingDefinition {
4541 meta: MappingMeta {
4542 entity: "Marktteilnehmer".to_string(),
4543 bo4e_type: "Marktteilnehmer".to_string(),
4544 source_group: "SG2".to_string(),
4545 source_path: None,
4546 discriminator: None,
4547 repeat_on_tag: None,
4548 },
4549 fields: msg_fields,
4550 complex_handlers: None,
4551 }];
4552
4553 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
4555 tx_fields.insert(
4556 "ide.1".to_string(),
4557 FieldMapping::Simple("vorgangId".to_string()),
4558 );
4559 let tx_defs = vec![MappingDefinition {
4560 meta: MappingMeta {
4561 entity: "Prozessdaten".to_string(),
4562 bo4e_type: "Prozessdaten".to_string(),
4563 source_group: "SG4".to_string(),
4564 source_path: None,
4565 discriminator: None,
4566 repeat_on_tag: None,
4567 },
4568 fields: tx_fields,
4569 complex_handlers: None,
4570 }];
4571
4572 let msg_engine = MappingEngine::from_definitions(msg_defs);
4573 let tx_engine = MappingEngine::from_definitions(tx_defs);
4574
4575 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
4576
4577 assert!(result.stammdaten["marktteilnehmer"].is_object());
4579 assert_eq!(
4580 result.stammdaten["marktteilnehmer"]["marktrolle"]
4581 .as_str()
4582 .unwrap(),
4583 "MS"
4584 );
4585
4586 assert_eq!(result.transaktionen.len(), 2);
4588 assert_eq!(
4589 result.transaktionen[0].stammdaten["prozessdaten"]["vorgangId"]
4590 .as_str()
4591 .unwrap(),
4592 "TX001"
4593 );
4594 assert_eq!(
4595 result.transaktionen[1].stammdaten["prozessdaten"]["vorgangId"]
4596 .as_str()
4597 .unwrap(),
4598 "TX002"
4599 );
4600 }
4601
4602 #[test]
4603 fn test_map_reverse_with_segment_structure_pads_trailing() {
4604 let mut fields = IndexMap::new();
4606 fields.insert(
4607 "sts.0".to_string(),
4608 FieldMapping::Structured(StructuredFieldMapping {
4609 target: String::new(),
4610 transform: None,
4611 when: None,
4612 default: Some("7".to_string()),
4613 enum_map: None,
4614 when_filled: None,
4615 also_target: None,
4616 also_enum_map: None,
4617 }),
4618 );
4619 fields.insert(
4620 "sts.2".to_string(),
4621 FieldMapping::Simple("grund".to_string()),
4622 );
4623
4624 let def = make_def(fields);
4625
4626 let mut counts = std::collections::HashMap::new();
4628 counts.insert("STS".to_string(), 5usize);
4629 let ss = SegmentStructure {
4630 element_counts: counts,
4631 };
4632
4633 let engine = MappingEngine::from_definitions(vec![]).with_segment_structure(ss);
4634
4635 let bo4e = serde_json::json!({ "grund": "E01" });
4636
4637 let instance = engine.map_reverse(&bo4e, &def);
4638 let sts = &instance.segments[0];
4639 assert_eq!(sts.elements.len(), 5);
4642 assert_eq!(sts.elements[0], vec!["7"]);
4643 assert_eq!(sts.elements[1], vec![""]);
4644 assert_eq!(sts.elements[2], vec!["E01"]);
4645 assert_eq!(sts.elements[3], vec![""]);
4646 assert_eq!(sts.elements[4], vec![""]);
4647 }
4648
4649 #[test]
4650 fn test_resolve_child_relative_with_source_path() {
4651 let mut map: std::collections::HashMap<String, Vec<usize>> =
4652 std::collections::HashMap::new();
4653 map.insert("sg4.sg8_ze1".to_string(), vec![6]);
4654 map.insert("sg4.sg8_z98".to_string(), vec![0]);
4655
4656 assert_eq!(
4658 resolve_child_relative("SG8.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
4659 "SG8:6.SG10"
4660 );
4661
4662 assert_eq!(
4664 resolve_child_relative("SG8:3.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
4665 "SG8:3.SG10"
4666 );
4667
4668 assert_eq!(
4670 resolve_child_relative("SG8.SG10", Some("sg4.sg8_unknown.sg10"), &map, 0),
4671 "SG8.SG10"
4672 );
4673
4674 assert_eq!(
4676 resolve_child_relative("SG8.SG10", None, &map, 0),
4677 "SG8.SG10"
4678 );
4679
4680 assert_eq!(
4682 resolve_child_relative("SG8.SG9", Some("sg4.sg8_z98.sg9"), &map, 0),
4683 "SG8:0.SG9"
4684 );
4685
4686 map.insert("sg4.sg8_zf3".to_string(), vec![3, 4]);
4688 assert_eq!(
4689 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 0),
4690 "SG8:3.SG10"
4691 );
4692 assert_eq!(
4693 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 1),
4694 "SG8:4.SG10"
4695 );
4696 }
4697
4698 #[test]
4699 fn test_place_in_groups_returns_rep_index() {
4700 let mut groups: Vec<AssembledGroup> = Vec::new();
4701
4702 let instance = AssembledGroupInstance {
4704 segments: vec![],
4705 child_groups: vec![],
4706 entry_mig_number: None,
4707 variant_mig_numbers: vec![],
4708 skipped_segments: vec![],
4709 skipped_positions: Vec::new(),
4710 };
4711 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 0);
4712
4713 let instance = AssembledGroupInstance {
4715 segments: vec![],
4716 child_groups: vec![],
4717 entry_mig_number: None,
4718 variant_mig_numbers: vec![],
4719 skipped_segments: vec![],
4720 skipped_positions: Vec::new(),
4721 };
4722 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 1);
4723
4724 let instance = AssembledGroupInstance {
4726 segments: vec![],
4727 child_groups: vec![],
4728 entry_mig_number: None,
4729 variant_mig_numbers: vec![],
4730 skipped_segments: vec![],
4731 skipped_positions: Vec::new(),
4732 };
4733 assert_eq!(place_in_groups(&mut groups, "SG8:5", instance), 5);
4734 }
4735
4736 #[test]
4737 fn test_resolve_by_source_path() {
4738 use mig_assembly::assembler::*;
4739
4740 let tree = AssembledTree {
4742 segments: vec![],
4743 groups: vec![AssembledGroup {
4744 group_id: "SG4".to_string(),
4745 repetitions: vec![AssembledGroupInstance {
4746 segments: vec![],
4747 child_groups: vec![AssembledGroup {
4748 group_id: "SG8".to_string(),
4749 repetitions: vec![
4750 AssembledGroupInstance {
4751 segments: vec![AssembledSegment {
4752 tag: "SEQ".to_string(),
4753 elements: vec![vec!["Z98".to_string()]],
4754 mig_number: None,
4755 segment_number: None,
4756 }],
4757 child_groups: vec![AssembledGroup {
4758 group_id: "SG10".to_string(),
4759 repetitions: vec![AssembledGroupInstance {
4760 segments: vec![AssembledSegment {
4761 tag: "CCI".to_string(),
4762 elements: vec![vec![], vec![], vec!["ZB3".to_string()]],
4763 mig_number: None,
4764 segment_number: None,
4765 }],
4766 child_groups: vec![],
4767 entry_mig_number: None,
4768 variant_mig_numbers: vec![],
4769 skipped_segments: vec![],
4770 skipped_positions: Vec::new(),
4771 }],
4772 }],
4773 entry_mig_number: None,
4774 variant_mig_numbers: vec![],
4775 skipped_segments: vec![],
4776 skipped_positions: Vec::new(),
4777 },
4778 AssembledGroupInstance {
4779 segments: vec![AssembledSegment {
4780 tag: "SEQ".to_string(),
4781 elements: vec![vec!["ZD7".to_string()]],
4782 mig_number: None,
4783 segment_number: None,
4784 }],
4785 child_groups: vec![AssembledGroup {
4786 group_id: "SG10".to_string(),
4787 repetitions: vec![AssembledGroupInstance {
4788 segments: vec![AssembledSegment {
4789 tag: "CCI".to_string(),
4790 elements: vec![vec![], vec![], vec!["ZE6".to_string()]],
4791 mig_number: None,
4792 segment_number: None,
4793 }],
4794 child_groups: vec![],
4795 entry_mig_number: None,
4796 variant_mig_numbers: vec![],
4797 skipped_segments: vec![],
4798 skipped_positions: Vec::new(),
4799 }],
4800 }],
4801 entry_mig_number: None,
4802 variant_mig_numbers: vec![],
4803 skipped_segments: vec![],
4804 skipped_positions: Vec::new(),
4805 },
4806 ],
4807 }],
4808 entry_mig_number: None,
4809 variant_mig_numbers: vec![],
4810 skipped_segments: vec![],
4811 skipped_positions: Vec::new(),
4812 }],
4813 }],
4814 post_group_start: 0,
4815 inter_group_segments: std::collections::BTreeMap::new(),
4816 };
4817
4818 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_z98.sg10");
4820 assert!(inst.is_some());
4821 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
4822
4823 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zd7.sg10");
4825 assert!(inst.is_some());
4826 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZE6");
4827
4828 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zzz.sg10");
4830 assert!(inst.is_none());
4831
4832 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8.sg10");
4834 assert!(inst.is_some());
4835 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
4836 }
4837
4838 #[test]
4839 fn test_parse_source_path_part() {
4840 assert_eq!(parse_source_path_part("sg4"), ("sg4", None));
4841 assert_eq!(parse_source_path_part("sg8_z98"), ("sg8", Some("z98")));
4842 assert_eq!(parse_source_path_part("sg10"), ("sg10", None));
4843 assert_eq!(parse_source_path_part("sg12_z04"), ("sg12", Some("z04")));
4844 }
4845
4846 #[test]
4847 fn test_has_source_path_qualifiers() {
4848 assert!(has_source_path_qualifiers("sg4.sg8_z98.sg10"));
4849 assert!(has_source_path_qualifiers("sg4.sg8_ze1.sg9"));
4850 assert!(!has_source_path_qualifiers("sg4.sg6"));
4851 assert!(!has_source_path_qualifiers("sg4.sg8.sg10"));
4852 }
4853
4854 #[test]
4855 fn test_extract_all_from_instance_collects_all_qualifier_matches() {
4856 use mig_assembly::assembler::*;
4857
4858 let instance = AssembledGroupInstance {
4860 segments: vec![
4861 AssembledSegment {
4862 tag: "SEQ".to_string(),
4863 elements: vec![vec!["ZD6".to_string()]],
4864 mig_number: None,
4865 segment_number: None,
4866 },
4867 AssembledSegment {
4868 tag: "RFF".to_string(),
4869 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
4870 mig_number: None,
4871 segment_number: None,
4872 },
4873 AssembledSegment {
4874 tag: "RFF".to_string(),
4875 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
4876 mig_number: None,
4877 segment_number: None,
4878 },
4879 AssembledSegment {
4880 tag: "RFF".to_string(),
4881 elements: vec![vec!["Z34".to_string(), "REF_C".to_string()]],
4882 mig_number: None,
4883 segment_number: None,
4884 },
4885 AssembledSegment {
4886 tag: "RFF".to_string(),
4887 elements: vec![vec!["Z35".to_string(), "OTHER".to_string()]],
4888 mig_number: None,
4889 segment_number: None,
4890 },
4891 ],
4892 child_groups: vec![],
4893 entry_mig_number: None,
4894 variant_mig_numbers: vec![],
4895 skipped_segments: vec![],
4896 skipped_positions: Vec::new(),
4897 };
4898
4899 let all = MappingEngine::extract_all_from_instance(&instance, "rff[Z34,*].0.1");
4901 assert_eq!(all, vec!["REF_A", "REF_B", "REF_C"]);
4902
4903 let single = MappingEngine::extract_from_instance(&instance, "rff[Z34].0.1");
4905 assert_eq!(single, Some("REF_A".to_string()));
4906
4907 let second = MappingEngine::extract_from_instance(&instance, "rff[Z34,1].0.1");
4908 assert_eq!(second, Some("REF_B".to_string()));
4909 }
4910}