1use std::collections::{BTreeMap, 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 code_lists: std::sync::Arc<crate::code_lists::CodeLists>,
45 raw_codes: bool,
48}
49
50impl MappingEngine {
51 pub fn new_empty() -> Self {
53 Self {
54 definitions: Vec::new(),
55 segment_structure: None,
56 code_lookup: None,
57 transaction_group: None,
58 current_pid: None,
59 raw_codes: false,
60 code_lists: std::sync::Arc::new(crate::code_lists::CodeLists::default()),
61 }
62 }
63
64 pub fn load(dir: &Path) -> Result<Self, MappingError> {
66 let mut definitions = Vec::new();
67
68 let mut entries: Vec<_> = std::fs::read_dir(dir)?.filter_map(|e| e.ok()).collect();
69 entries.sort_by_key(|e| e.file_name());
70
71 for entry in entries {
72 let path = entry.path();
73 if path.extension().map(|e| e == "toml").unwrap_or(false) {
74 let content = std::fs::read_to_string(&path)?;
75 let def = MappingDefinition::from_toml_str(&content).map_err(|message| {
76 MappingError::TomlParse {
77 file: path.display().to_string(),
78 message,
79 }
80 })?;
81 definitions.push(def);
82 }
83 }
84
85 definitions.sort_by_key(|d| d.meta.order.unwrap_or(u32::MAX));
91
92 Ok(Self {
93 definitions,
94 segment_structure: None,
95 code_lookup: None,
96 transaction_group: None,
97 current_pid: None,
98 raw_codes: false,
99 code_lists: crate::code_lists::CodeLists::discover(dir),
100 })
101 }
102
103 pub fn load_split(
109 message_dir: &Path,
110 transaction_dir: &Path,
111 ) -> Result<(Self, Self), MappingError> {
112 let msg_engine = Self::load(message_dir)?;
113 let tx_engine = Self::load(transaction_dir)?;
114 Ok((msg_engine, tx_engine))
115 }
116
117 pub fn load_merged(dirs: &[&Path]) -> Result<Self, MappingError> {
122 let mut definitions = Vec::new();
123 for dir in dirs {
124 let engine = Self::load(dir)?;
125 definitions.extend(engine.definitions);
126 }
127 Ok(Self {
128 definitions,
129 segment_structure: None,
130 code_lookup: None,
131 transaction_group: None,
132 current_pid: None,
133 raw_codes: false,
134 code_lists: crate::code_lists::CodeLists::discover(
135 dirs.first().copied().unwrap_or(Path::new("")),
136 ),
137 })
138 }
139
140 pub fn load_with_common(
149 common_dir: &Path,
150 pid_dir: &Path,
151 schema_index: &crate::pid_schema_index::PidSchemaIndex,
152 ) -> Result<Self, MappingError> {
153 let mut common_defs = Self::load(common_dir)?.definitions;
154
155 common_defs.retain(|d| {
157 d.meta
158 .source_path
159 .as_deref()
160 .map(|sp| schema_index.has_group(sp))
161 .unwrap_or(true)
162 });
163
164 let pid_defs = Self::load(pid_dir)?.definitions;
165
166 let normalize_sg = |sg: &str| -> String {
171 sg.split('.')
172 .map(|part| part.split(':').next().unwrap_or(part))
173 .collect::<Vec<_>>()
174 .join(".")
175 };
176 let pid_keys: HashSet<(String, Option<String>)> = pid_defs
177 .iter()
178 .flat_map(|d| {
179 let sg = normalize_sg(&d.meta.source_group);
180 let disc = d.meta.discriminator.clone();
181 let mut keys = vec![(sg.clone(), disc.clone())];
182 if let Some(ref disc_str) = disc {
184 if let Some(base) = disc_str.rsplit_once('#') {
185 if base.1.chars().all(|c| c.is_ascii_digit()) {
186 keys.push((sg, Some(base.0.to_string())));
187 }
188 }
189 }
190 keys
191 })
192 .collect();
193
194 common_defs.retain(|d| {
196 let key = (
197 normalize_sg(&d.meta.source_group),
198 d.meta.discriminator.clone(),
199 );
200 !pid_keys.contains(&key)
201 });
202
203 let mut definitions = common_defs;
205 definitions.extend(pid_defs);
206
207 Ok(Self {
208 definitions,
209 segment_structure: None,
210 code_lookup: None,
211 transaction_group: None,
212 current_pid: None,
213 raw_codes: false,
214 code_lists: crate::code_lists::CodeLists::discover(pid_dir),
215 })
216 }
217
218 pub fn load_common_only(
222 common_dir: &Path,
223 schema_index: &crate::pid_schema_index::PidSchemaIndex,
224 ) -> Result<Self, MappingError> {
225 let mut common_defs = Self::load(common_dir)?.definitions;
226
227 common_defs.retain(|d| {
229 d.meta
230 .source_path
231 .as_deref()
232 .map(|sp| schema_index.has_group(sp))
233 .unwrap_or(true)
234 });
235
236 Ok(Self {
237 definitions: common_defs,
238 segment_structure: None,
239 code_lookup: None,
240 transaction_group: None,
241 current_pid: None,
242 raw_codes: false,
243 code_lists: crate::code_lists::CodeLists::discover(common_dir),
244 })
245 }
246
247 pub fn load_split_with_common(
252 message_dir: &Path,
253 common_dir: &Path,
254 transaction_dir: &Path,
255 schema_index: &crate::pid_schema_index::PidSchemaIndex,
256 ) -> Result<(Self, Self), MappingError> {
257 let msg_engine = Self::load(message_dir)?;
258 let tx_engine = Self::load_with_common(common_dir, transaction_dir, schema_index)?;
259 Ok((msg_engine, tx_engine))
260 }
261
262 fn names_a_code_list(definitions: &[MappingDefinition]) -> bool {
265 definitions.iter().any(|d| {
266 d.fields.values().any(|f| {
267 matches!(f, FieldMapping::Structured(s)
268 if s.code_list.is_some() || s.also_code_list.is_some())
269 })
270 })
271 }
272
273 fn table<'a>(
280 &'a self,
281 inline: Option<&'a BTreeMap<String, String>>,
282 named: Option<&str>,
283 ) -> Option<&'a BTreeMap<String, String>> {
284 self.code_lists.resolve(inline, named)
285 }
286
287 pub fn with_code_lists(
289 mut self,
290 code_lists: std::sync::Arc<crate::code_lists::CodeLists>,
291 ) -> Self {
292 self.code_lists = code_lists;
293 self
294 }
295
296 pub fn code_lists(&self) -> &std::sync::Arc<crate::code_lists::CodeLists> {
298 &self.code_lists
299 }
300
301 pub fn from_definitions_with_code_lists(
304 code_lists: std::sync::Arc<crate::code_lists::CodeLists>,
305 definitions: Vec<MappingDefinition>,
306 ) -> Self {
307 debug_assert!(
312 !(code_lists.is_empty() && Self::names_a_code_list(&definitions)),
313 "definitions name a shared code list but the supplied tables are \
314 empty — whatever produced them (a bundle, a cache) is not carrying \
315 them, and every code they translate will reach the output raw"
316 );
317 Self {
321 definitions,
322 segment_structure: None,
323 code_lookup: None,
324 transaction_group: None,
325 current_pid: None,
326 raw_codes: false,
327 code_lists,
328 }
329 }
330
331 pub fn from_definitions(definitions: Vec<MappingDefinition>) -> Self {
332 debug_assert!(
339 !Self::names_a_code_list(&definitions),
340 "definitions name a shared code list but none were supplied — build \
341 this engine with `from_definitions_with_code_lists`, or the codes \
342 they translate will reach the output untranslated"
343 );
344 Self {
345 definitions,
346 segment_structure: None,
347 code_lookup: None,
348 transaction_group: None,
349 current_pid: None,
350 raw_codes: false,
351 code_lists: std::sync::Arc::new(crate::code_lists::CodeLists::default()),
352 }
353 }
354
355 pub fn save_cached(&self, path: &Path) -> Result<(), MappingError> {
361 let encoded =
362 serde_json::to_vec(&self.definitions).map_err(|e| MappingError::CacheWrite {
363 path: path.display().to_string(),
364 message: e.to_string(),
365 })?;
366 if let Some(parent) = path.parent() {
367 std::fs::create_dir_all(parent)?;
368 }
369 std::fs::write(path, encoded)?;
370 Ok(())
371 }
372
373 pub fn load_cached_or_toml(cache_path: &Path, toml_dir: &Path) -> Result<Self, MappingError> {
378 if cache_path.exists() {
379 Self::load_cached(cache_path)
380 } else {
381 Self::load(toml_dir)
382 }
383 }
384
385 pub fn load_cached(path: &Path) -> Result<Self, MappingError> {
390 let bytes = std::fs::read(path)?;
391 let definitions: Vec<MappingDefinition> =
392 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
393 path: path.display().to_string(),
394 message: e.to_string(),
395 })?;
396 Ok(Self {
397 definitions,
398 segment_structure: None,
399 code_lookup: None,
400 transaction_group: None,
401 current_pid: None,
402 raw_codes: false,
403 code_lists: crate::code_lists::CodeLists::discover(path),
404 })
405 }
406
407 pub fn with_segment_structure(mut self, ss: SegmentStructure) -> Self {
412 self.segment_structure = Some(ss);
413 self
414 }
415
416 pub fn with_code_lookup(mut self, cl: crate::code_lookup::CodeLookup) -> Self {
421 self.code_lookup = Some(cl);
422 self
423 }
424
425 pub fn with_pid(mut self, pid: impl Into<String>) -> Self {
433 self.current_pid = Some(pid.into());
434 self
435 }
436
437 pub fn with_raw_codes(mut self, raw: bool) -> Self {
447 self.raw_codes = raw;
448 self
449 }
450
451 pub fn with_path_resolver(mut self, resolver: crate::path_resolver::PathResolver) -> Self {
457 for def in &mut self.definitions {
458 def.normalize_paths(&resolver);
459 }
460 self
461 }
462
463 pub fn with_transaction_group(mut self, tx: impl Into<String>) -> Self {
470 self.transaction_group = Some(tx.into());
471 self
472 }
473
474 pub fn extend_definitions(mut self, defs: impl IntoIterator<Item = MappingDefinition>) -> Self {
488 fn key(d: &MappingDefinition) -> (String, String, String, String, String) {
489 (
490 d.meta.entity.clone(),
491 d.meta.source_group.clone(),
492 d.meta.source_path.clone().unwrap_or_default(),
493 d.meta.discriminator.clone().unwrap_or_default(),
494 d.meta.parent_field.clone().unwrap_or_default(),
495 )
496 }
497 let mut seen: std::collections::HashSet<_> = self.definitions.iter().map(key).collect();
498 for def in defs {
499 if seen.insert(key(&def)) {
500 self.definitions.push(def);
501 }
502 }
503 self
504 }
505
506 pub fn definitions(&self) -> &[MappingDefinition] {
508 &self.definitions
509 }
510
511 pub fn definition_for_entity(&self, entity: &str) -> Option<&MappingDefinition> {
516 self.definitions.iter().find(|d| {
517 d.meta.entity == entity
518 && d.meta.parent_field.is_none()
519 && !is_bound_child(&self.definitions, d)
520 })
521 }
522
523 pub fn extract_field(
532 &self,
533 tree: &AssembledTree,
534 group_path: &str,
535 path: &str,
536 repetition: usize,
537 ) -> Option<String> {
538 let instance = Self::resolve_group_instance(tree, group_path, repetition)?;
539 Self::extract_from_instance(instance, path)
540 }
541
542 pub fn resolve_group_instance<'a>(
551 tree: &'a AssembledTree,
552 group_path: &str,
553 repetition: usize,
554 ) -> Option<&'a AssembledGroupInstance> {
555 let parts: Vec<&str> = group_path.split('.').collect();
556
557 let (first_id, first_rep) = parse_group_spec(parts[0]);
558 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
559
560 if parts.len() == 1 {
561 let rep = first_rep.unwrap_or(repetition);
563 return first_group.repetitions.get(rep);
564 }
565
566 let mut current_instance = first_group.repetitions.get(first_rep.unwrap_or(0))?;
569
570 for (i, part) in parts[1..].iter().enumerate() {
571 let (group_id, explicit_rep) = parse_group_spec(part);
572 let child_group = current_instance
573 .child_groups
574 .iter()
575 .find(|g| g.group_id == group_id)?;
576
577 if i == parts.len() - 2 {
578 let rep = explicit_rep.unwrap_or(repetition);
580 return child_group.repetitions.get(rep);
581 }
582 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
584 }
585
586 None
587 }
588
589 pub fn resolve_by_source_path<'a>(
597 tree: &'a AssembledTree,
598 source_path: &str,
599 ) -> Option<&'a AssembledGroupInstance> {
600 let parts: Vec<&str> = source_path.split('.').collect();
601 if parts.is_empty() {
602 return None;
603 }
604
605 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
606 let first_group = tree
607 .groups
608 .iter()
609 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))?;
610
611 let mut current_instance = if let Some(q) = first_qualifier {
612 find_rep_by_entry_qualifier(&first_group.repetitions, q)?
613 } else {
614 first_group.repetitions.first()?
615 };
616
617 if parts.len() == 1 {
618 return Some(current_instance);
619 }
620
621 for part in &parts[1..] {
622 let (group_id, qualifier) = parse_source_path_part(part);
623 let child_group = current_instance
624 .child_groups
625 .iter()
626 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))?;
627
628 current_instance = if let Some(q) = qualifier {
629 find_rep_by_entry_qualifier(&child_group.repetitions, q)?
630 } else {
631 child_group.repetitions.first()?
632 };
633 }
634
635 Some(current_instance)
636 }
637
638 pub fn resolve_all_by_source_path<'a>(
646 tree: &'a AssembledTree,
647 source_path: &str,
648 ) -> Vec<&'a AssembledGroupInstance> {
649 let parts: Vec<&str> = source_path.split('.').collect();
650 if parts.is_empty() {
651 return vec![];
652 }
653
654 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
656 let first_group = match tree
657 .groups
658 .iter()
659 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
660 {
661 Some(g) => g,
662 None => return vec![],
663 };
664
665 let mut current_instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
666 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
667 } else {
668 first_group.repetitions.iter().collect()
669 };
670
671 for part in &parts[1..] {
674 let (group_id, qualifier) = parse_source_path_part(part);
675 let mut next_instances = Vec::new();
676
677 for instance in ¤t_instances {
678 if let Some(child_group) = instance
679 .child_groups
680 .iter()
681 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
682 {
683 if let Some(q) = qualifier {
684 next_instances.extend(find_all_reps_by_entry_qualifier(
685 &child_group.repetitions,
686 q,
687 ));
688 } else {
689 next_instances.extend(child_group.repetitions.iter());
690 }
691 }
692 }
693
694 current_instances = next_instances;
695 }
696
697 current_instances
698 }
699
700 fn compute_child_indices(
713 tree: &AssembledTree,
714 source_path: &str,
715 indexed: &[(usize, &AssembledGroupInstance)],
716 ) -> Vec<usize> {
717 let parts: Vec<&str> = source_path.split('.').collect();
718 if parts.len() < 2 {
719 return vec![];
720 }
721 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
723 let first_group = match tree
724 .groups
725 .iter()
726 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
727 {
728 Some(g) => g,
729 None => return vec![],
730 };
731 let parent_reps: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
732 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
733 } else {
734 first_group.repetitions.iter().collect()
735 };
736 let (child_id, _child_qualifier) = parse_source_path_part(parts[parts.len() - 1]);
738 let mut result = Vec::new();
739 for (_, inst) in indexed {
740 let mut found = false;
742 for parent in &parent_reps {
743 if let Some(child_group) = parent
744 .child_groups
745 .iter()
746 .find(|g| g.group_id.eq_ignore_ascii_case(child_id))
747 {
748 if let Some(pos) = child_group
749 .repetitions
750 .iter()
751 .position(|r| std::ptr::eq(r, *inst))
752 {
753 result.push(pos);
754 found = true;
755 break;
756 }
757 }
758 }
759 if !found {
760 result.push(usize::MAX); }
762 }
763 result
764 }
765
766 pub fn resolve_all_with_parent_indices<'a>(
768 tree: &'a AssembledTree,
769 source_path: &str,
770 ) -> Vec<(usize, &'a AssembledGroupInstance)> {
771 let parts: Vec<&str> = source_path.split('.').collect();
772 if parts.is_empty() {
773 return vec![];
774 }
775
776 let (first_id, first_qualifier) = parse_source_path_part(parts[0]);
778 let first_group = match tree
779 .groups
780 .iter()
781 .find(|g| g.group_id.eq_ignore_ascii_case(first_id))
782 {
783 Some(g) => g,
784 None => return vec![],
785 };
786
787 if parts.len() == 1 {
789 let instances: Vec<&AssembledGroupInstance> = if let Some(q) = first_qualifier {
790 find_all_reps_by_entry_qualifier(&first_group.repetitions, q)
791 } else {
792 first_group.repetitions.iter().collect()
793 };
794 return instances.into_iter().map(|i| (0, i)).collect();
795 }
796
797 let first_reps: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = first_qualifier {
802 let matching = find_all_reps_by_entry_qualifier(&first_group.repetitions, q);
803 let mut result = Vec::new();
804 for m in matching {
805 let idx = first_group
806 .repetitions
807 .iter()
808 .position(|r| std::ptr::eq(r, m))
809 .unwrap_or(0);
810 result.push((idx, m));
811 }
812 result
813 } else {
814 first_group.repetitions.iter().enumerate().collect()
815 };
816
817 let mut current: Vec<(usize, &AssembledGroupInstance)> = first_reps;
818 let remaining = &parts[1..];
819
820 for (level, part) in remaining.iter().enumerate() {
821 let is_leaf = level == remaining.len() - 1;
822 let (group_id, qualifier) = parse_source_path_part(part);
823 let mut next: Vec<(usize, &AssembledGroupInstance)> = Vec::new();
824
825 for (prev_parent_idx, instance) in ¤t {
826 if let Some(child_group) = instance
827 .child_groups
828 .iter()
829 .find(|g| g.group_id.eq_ignore_ascii_case(group_id))
830 {
831 let matching: Vec<(usize, &AssembledGroupInstance)> = if let Some(q) = qualifier
832 {
833 let filtered =
834 find_all_reps_by_entry_qualifier(&child_group.repetitions, q);
835 filtered
836 .into_iter()
837 .map(|m| {
838 let idx = child_group
839 .repetitions
840 .iter()
841 .position(|r| std::ptr::eq(r, m))
842 .unwrap_or(0);
843 (idx, m)
844 })
845 .collect()
846 } else {
847 child_group.repetitions.iter().enumerate().collect()
848 };
849
850 for (rep_idx, child_rep) in matching {
851 if is_leaf {
852 next.push((*prev_parent_idx, child_rep));
854 } else {
855 next.push((rep_idx, child_rep));
857 }
858 }
859 }
860 }
861
862 current = next;
863 }
864
865 current
866 }
867
868 pub fn extract_from_instance(instance: &AssembledGroupInstance, path: &str) -> Option<String> {
874 let parts: Vec<&str> = path.split('.').collect();
875 if parts.is_empty() {
876 return None;
877 }
878
879 let (segment_tag, qualifier, occurrence) = parse_tag_qualifier(parts[0]);
882
883 let segment = if let Some(q) = qualifier {
884 instance
885 .segments
886 .iter()
887 .filter(|s| {
888 s.tag.eq_ignore_ascii_case(&segment_tag)
889 && s.elements
890 .first()
891 .and_then(|e| e.first())
892 .map(|v| v.as_str())
893 == Some(q)
894 })
895 .nth(occurrence)?
896 } else {
897 instance
898 .segments
899 .iter()
900 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
901 .nth(occurrence)?
902 };
903
904 Self::resolve_field_path(segment, &parts[1..])
905 }
906
907 pub fn extract_all_from_instance(instance: &AssembledGroupInstance, path: &str) -> Vec<String> {
913 let parts: Vec<&str> = path.split('.').collect();
914 if parts.is_empty() {
915 return vec![];
916 }
917
918 let (segment_tag, qualifier, _) = parse_tag_qualifier(parts[0]);
919
920 let matching_segments: Vec<&AssembledSegment> = if let Some(q) = qualifier {
921 instance
922 .segments
923 .iter()
924 .filter(|s| {
925 s.tag.eq_ignore_ascii_case(&segment_tag)
926 && s.elements
927 .first()
928 .and_then(|e| e.first())
929 .map(|v| v.as_str())
930 == Some(q)
931 })
932 .collect()
933 } else {
934 instance
935 .segments
936 .iter()
937 .filter(|s| s.tag.eq_ignore_ascii_case(&segment_tag))
938 .collect()
939 };
940
941 matching_segments
942 .into_iter()
943 .filter_map(|seg| Self::resolve_field_path(seg, &parts[1..]))
944 .collect()
945 }
946
947 pub fn map_forward(
953 &self,
954 tree: &AssembledTree,
955 def: &MappingDefinition,
956 repetition: usize,
957 ) -> serde_json::Value {
958 self.map_forward_inner(tree, def, repetition, true)
959 }
960
961 fn map_forward_inner(
963 &self,
964 tree: &AssembledTree,
965 def: &MappingDefinition,
966 repetition: usize,
967 enrich_codes: bool,
968 ) -> serde_json::Value {
969 let mut result = serde_json::Map::new();
970
971 if def.meta.source_group.is_empty() {
976 let mut all_root_segs = tree.segments.clone();
977 for segs in tree.inter_group_segments.values() {
978 all_root_segs.extend(segs.iter().cloned());
979 }
980 let root_instance = AssembledGroupInstance {
981 segments: all_root_segs,
982 child_groups: vec![],
983 entry_mig_number: None,
984 variant_mig_numbers: vec![],
985 skipped_segments: Vec::new(),
986 skipped_positions: Vec::new(),
987 };
988 self.extract_fields_from_instance(&root_instance, def, &mut result, enrich_codes);
989 return serde_json::Value::Object(result);
990 }
991
992 let instance = if let Some(ref sp) = def.meta.source_path {
998 if has_source_path_qualifiers(sp) && !def.meta.source_group.contains(':') {
999 Self::resolve_by_source_path(tree, sp).or_else(|| {
1000 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
1001 })
1002 } else {
1003 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
1004 }
1005 } else {
1006 Self::resolve_group_instance(tree, &def.meta.source_group, repetition)
1007 };
1008
1009 if let Some(instance) = instance {
1010 if let Some(ref tag) = def.meta.repeat_on_tag {
1012 let matching: Vec<_> = instance
1013 .segments
1014 .iter()
1015 .filter(|s| s.tag.eq_ignore_ascii_case(tag))
1016 .collect();
1017
1018 if matching.len() > 1 {
1019 let mut arr = Vec::new();
1020 for seg in &matching {
1021 let sub_instance = AssembledGroupInstance {
1022 segments: vec![(*seg).clone()],
1023 child_groups: vec![],
1024 entry_mig_number: None,
1025 variant_mig_numbers: vec![],
1026 skipped_segments: Vec::new(),
1027 skipped_positions: Vec::new(),
1028 };
1029 let mut elem_result = serde_json::Map::new();
1030 self.extract_fields_from_instance(
1031 &sub_instance,
1032 def,
1033 &mut elem_result,
1034 enrich_codes,
1035 );
1036 if !elem_result.is_empty() {
1037 arr.push(serde_json::Value::Object(elem_result));
1038 }
1039 }
1040 if !arr.is_empty() {
1041 return serde_json::Value::Array(arr);
1042 }
1043 }
1044 }
1045
1046 self.extract_fields_from_instance(instance, def, &mut result, enrich_codes);
1047 }
1048
1049 serde_json::Value::Object(result)
1050 }
1051
1052 fn extract_fields_from_instance(
1057 &self,
1058 instance: &AssembledGroupInstance,
1059 def: &MappingDefinition,
1060 result: &mut serde_json::Map<String, serde_json::Value>,
1061 enrich_codes: bool,
1062 ) {
1063 for (path, field_mapping) in &def.fields {
1064 let (target, enum_map) = match field_mapping {
1065 FieldMapping::Simple(t) => (t.as_str(), None),
1066 FieldMapping::Structured(s) => (
1067 s.target.as_str(),
1068 self.table(s.enum_map.as_ref(), s.code_list.as_deref()),
1069 ),
1070 FieldMapping::Nested(_) => continue,
1071 };
1072 if target.is_empty() {
1073 continue;
1074 }
1075 if let Some((list, sub)) = list_target(target) {
1076 self.extract_list_field(
1077 instance,
1078 def,
1079 path,
1080 list,
1081 sub,
1082 enum_map,
1083 enrich_codes,
1084 result,
1085 );
1086 continue;
1087 }
1088 if let Some(val) = Self::extract_from_instance(instance, path) {
1089 if let FieldMapping::Structured(s) = field_mapping {
1094 if let (false, Some(also), Some(also_map)) = (
1095 self.raw_codes,
1096 s.also_target.as_deref(),
1097 self.table(s.also_enum_map.as_ref(), s.also_code_list.as_deref()),
1098 ) {
1099 if let Some(also_val) = also_map.get(&val) {
1100 set_nested_value(result, also, also_val.clone());
1101 }
1102 }
1103 }
1104
1105 let mapped_val = match enum_map {
1106 Some(map) if !self.raw_codes => {
1107 map.get(&val).cloned().unwrap_or_else(|| val.clone())
1108 }
1109 _ => val.clone(),
1110 };
1111
1112 if enrich_codes {
1114 if let (Some(ref code_lookup), Some(ref source_path)) =
1115 (&self.code_lookup, &def.meta.source_path)
1116 {
1117 let parts: Vec<&str> = path.split('.').collect();
1118 let (seg_tag, path_qualifier, _occ) = parse_tag_qualifier(parts[0]);
1119 let (element_idx, component_idx) =
1120 Self::parse_element_component(&parts[1..]);
1121 let disc_qualifier = Self::discriminator_qualifier_for_tag(def, &seg_tag);
1122 let q = disc_qualifier.as_deref();
1123
1124 if let Some(codes) = code_lookup.enrichment_codes(
1125 source_path,
1126 &seg_tag,
1127 path_qualifier,
1128 q,
1129 element_idx,
1130 component_idx,
1131 ) {
1132 if let Some(ref pid) = self.current_pid {
1137 if codes.len() == 1 && codes.contains_key(pid.as_str()) {
1138 set_nested_value(result, target, mapped_val);
1139 continue;
1140 }
1141 }
1142
1143 let enrichment = codes.get(&val);
1147 let meaning = enrichment
1148 .map(|e| serde_json::Value::String(e.meaning.clone()))
1149 .unwrap_or(serde_json::Value::Null);
1150
1151 let mut obj = serde_json::Map::new();
1152 obj.insert("code".into(), serde_json::json!(mapped_val));
1153 obj.insert("meaning".into(), meaning);
1154 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
1155 obj.insert("enum".into(), serde_json::json!(enum_key));
1156 }
1157 let enriched = serde_json::Value::Object(obj);
1158 set_nested_value_json(result, target, enriched);
1159 continue;
1160 }
1161 }
1162 }
1163
1164 set_nested_value(result, target, mapped_val);
1165 }
1166 }
1167
1168 if !instance.child_groups.is_empty() {
1171 self.extract_nested_children(instance, def, result, enrich_codes);
1172 }
1173 }
1174
1175 #[allow(clippy::too_many_arguments)]
1183 fn extract_list_field(
1184 &self,
1185 instance: &AssembledGroupInstance,
1186 def: &MappingDefinition,
1187 path: &str,
1188 list: &str,
1189 sub: &str,
1190 enum_map: Option<&std::collections::BTreeMap<String, String>>,
1191 enrich_codes: bool,
1192 result: &mut serde_json::Map<String, serde_json::Value>,
1193 ) {
1194 let parts: Vec<&str> = path.split('.').collect();
1195 if parts.len() < 2 {
1196 return;
1197 }
1198 let (seg_tag, qualifier, _) = parse_tag_qualifier(parts[0]);
1199 let segments: Vec<&AssembledSegment> = instance
1200 .segments
1201 .iter()
1202 .filter(|s| {
1203 s.tag.eq_ignore_ascii_case(&seg_tag)
1204 && qualifier.map_or(true, |q| {
1205 s.elements
1206 .first()
1207 .and_then(|e| e.first())
1208 .map(|v| v.as_str())
1209 == Some(q)
1210 })
1211 })
1212 .collect();
1213 if segments.is_empty() {
1214 return;
1215 }
1216 let items = result
1217 .entry(list.to_string())
1218 .or_insert_with(|| serde_json::Value::Array(Vec::new()));
1219 let Some(items) = items.as_array_mut() else {
1220 return;
1221 };
1222 while items.len() < segments.len() {
1223 items.push(serde_json::Value::Object(serde_json::Map::new()));
1224 }
1225 let codes = if enrich_codes {
1226 match (&self.code_lookup, &def.meta.source_path) {
1227 (Some(lookup), Some(source_path)) => {
1228 let (element_idx, component_idx) = Self::parse_element_component(&parts[1..]);
1229 let disc = Self::discriminator_qualifier_for_tag(def, &seg_tag);
1230 lookup
1231 .enrichment_codes(
1232 source_path,
1233 &seg_tag,
1234 qualifier,
1235 disc.as_deref(),
1236 element_idx,
1237 component_idx,
1238 )
1239 .cloned()
1240 }
1241 _ => None,
1242 }
1243 } else {
1244 None
1245 };
1246 for (item, segment) in items.iter_mut().zip(segments) {
1247 let Some(val) = Self::resolve_field_path(segment, &parts[1..]) else {
1248 continue;
1249 };
1250 let mapped = match enum_map {
1251 Some(map) if !self.raw_codes => {
1252 map.get(&val).cloned().unwrap_or_else(|| val.clone())
1253 }
1254 _ => val.clone(),
1255 };
1256 let value = match &codes {
1257 Some(codes) => {
1258 let enrichment = codes.get(&val);
1259 let mut obj = serde_json::Map::new();
1260 obj.insert("code".into(), serde_json::json!(mapped));
1261 obj.insert(
1262 "meaning".into(),
1263 enrichment
1264 .map(|e| serde_json::Value::String(e.meaning.clone()))
1265 .unwrap_or(serde_json::Value::Null),
1266 );
1267 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
1268 obj.insert("enum".into(), serde_json::json!(enum_key));
1269 }
1270 serde_json::Value::Object(obj)
1271 }
1272 None => serde_json::Value::String(mapped),
1273 };
1274 if let Some(obj) = item.as_object_mut() {
1275 set_nested_value_json(obj, sub, value);
1276 }
1277 }
1278 }
1279
1280 fn extract_nested_children(
1285 &self,
1286 instance: &AssembledGroupInstance,
1287 def: &MappingDefinition,
1288 result: &mut serde_json::Map<String, serde_json::Value>,
1289 enrich_codes: bool,
1290 ) {
1291 for child in self
1292 .definitions
1293 .iter()
1294 .filter(|c| is_nested_child_of(c, def))
1295 {
1296 if nested_parent_qualifier(child).is_some_and(|q| !entry_qualifier_matches(instance, q))
1297 {
1298 continue;
1299 }
1300 let (leaf_id, leaf_qualifier) = nested_child_leaf(child);
1301 let Some(group) = instance
1302 .child_groups
1303 .iter()
1304 .find(|g| g.group_id.eq_ignore_ascii_case(&leaf_id))
1305 else {
1306 continue;
1307 };
1308 let reps: Vec<&AssembledGroupInstance> = match leaf_qualifier {
1309 Some(q) => find_all_reps_by_entry_qualifier(&group.repetitions, q),
1310 None => group.repetitions.iter().collect(),
1311 };
1312
1313 let mut items: Vec<serde_json::Value> = Vec::new();
1314 let mut push_item = |sub: &AssembledGroupInstance| {
1315 let mut obj = serde_json::Map::new();
1316 self.extract_fields_from_instance(sub, child, &mut obj, enrich_codes);
1317 if !obj.is_empty() {
1318 items.push(serde_json::Value::Object(obj));
1319 }
1320 };
1321 for rep in reps {
1322 let repeat_tag = child
1323 .meta
1324 .repeat_on_tag
1325 .as_deref()
1326 .filter(|tag| rep.segments.iter().any(|s| s.tag.eq_ignore_ascii_case(tag)));
1327 let Some(tag) = repeat_tag else {
1328 push_item(rep);
1329 continue;
1330 };
1331 let shared: Vec<AssembledSegment> = rep
1334 .segments
1335 .iter()
1336 .filter(|s| !s.tag.eq_ignore_ascii_case(tag))
1337 .cloned()
1338 .collect();
1339 for seg in rep
1340 .segments
1341 .iter()
1342 .filter(|s| s.tag.eq_ignore_ascii_case(tag))
1343 {
1344 let mut segments = shared.clone();
1345 segments.push(seg.clone());
1346 push_item(&AssembledGroupInstance {
1347 segments,
1348 child_groups: vec![],
1349 entry_mig_number: None,
1350 variant_mig_numbers: vec![],
1351 skipped_segments: Vec::new(),
1352 skipped_positions: Vec::new(),
1353 });
1354 }
1355 }
1356 if items.is_empty() {
1357 continue;
1358 }
1359 let field = child.meta.parent_field.as_deref().unwrap_or_default();
1360 match result.get_mut(field) {
1361 Some(serde_json::Value::Array(existing)) => existing.extend(items),
1362 _ => {
1363 result.insert(field.to_string(), serde_json::Value::Array(items));
1364 }
1365 }
1366 }
1367
1368 for child in self
1370 .definitions
1371 .iter()
1372 .filter(|c| is_bound_child_of(c, def))
1373 {
1374 for rep in bound_child_reps(instance, child) {
1375 self.extract_fields_from_instance(rep, child, result, enrich_codes);
1376 }
1377 }
1378 }
1379
1380 fn reverse_nested_children(
1384 &self,
1385 bo4e_value: &serde_json::Value,
1386 def: &MappingDefinition,
1387 instance: &mut AssembledGroupInstance,
1388 ) {
1389 let mut handled_fields: Vec<&str> = Vec::new();
1390 for child in self
1391 .definitions
1392 .iter()
1393 .filter(|c| is_nested_child_of(c, def))
1394 {
1395 let field = child.meta.parent_field.as_deref().unwrap_or_default();
1396 if handled_fields.contains(&field) {
1397 continue;
1398 }
1399 if nested_parent_qualifier(child)
1400 .is_some_and(|q| !rebuilt_entry_qualifier_matches(instance, def, q))
1401 {
1402 continue;
1403 }
1404 let elements: Vec<&serde_json::Value> = match bo4e_value.get(field) {
1405 Some(serde_json::Value::Array(arr)) => arr.iter().collect(),
1406 Some(serde_json::Value::Null) | None => continue,
1407 Some(other) => vec![other],
1408 };
1409
1410 let mut reps: Vec<AssembledGroupInstance> = Vec::new();
1411 if let Some(tag) = child.meta.repeat_on_tag.as_deref() {
1412 let mut merged: Option<AssembledGroupInstance> = None;
1415 for element in elements {
1416 let sub = self.map_reverse_single(element, child);
1417 if sub.segments.is_empty() {
1418 continue;
1419 }
1420 match merged.as_mut() {
1421 None => merged = Some(sub),
1422 Some(m) => m.segments.extend(
1423 sub.segments
1424 .into_iter()
1425 .filter(|s| s.tag.eq_ignore_ascii_case(tag)),
1426 ),
1427 }
1428 }
1429 reps.extend(merged);
1430 } else {
1431 for element in elements {
1432 let mut sub = self.map_reverse_single(element, child);
1433 if sub.segments.is_empty() {
1434 continue;
1435 }
1436 self.reverse_nested_children(element, child, &mut sub);
1438 reps.push(sub);
1439 }
1440 }
1441 if reps.is_empty() {
1442 continue;
1443 }
1444 handled_fields.push(field);
1445
1446 let (leaf_id, _) = nested_child_leaf(child);
1447 push_child_reps(instance, leaf_id, reps);
1448 }
1449
1450 for child in self
1453 .definitions
1454 .iter()
1455 .filter(|c| is_bound_child_of(c, def))
1456 {
1457 let mut sub = self.map_reverse_single(bo4e_value, child);
1458 if sub.segments.is_empty() {
1459 continue;
1460 }
1461 self.reverse_nested_children(bo4e_value, child, &mut sub);
1462 let (leaf_id, _) = nested_child_leaf(child);
1463 push_child_reps(instance, leaf_id, vec![sub]);
1464 }
1465 }
1466
1467 pub(crate) fn discriminator_qualifier(def: &MappingDefinition) -> Option<String> {
1474 def.meta
1475 .discriminator
1476 .as_deref()
1477 .and_then(|d| d.split_once('=').map(|(_, v)| v.to_string()))
1478 }
1479
1480 pub(crate) fn discriminator_qualifier_for_tag(
1485 def: &MappingDefinition,
1486 segment_tag: &str,
1487 ) -> Option<String> {
1488 let (lhs, value) = def.meta.discriminator.as_deref()?.split_once('=')?;
1489 let disc_tag = lhs.split('.').next().unwrap_or(lhs);
1490 disc_tag
1491 .eq_ignore_ascii_case(segment_tag)
1492 .then(|| value.to_string())
1493 }
1494
1495 pub fn map_forward_from_segments(
1501 &self,
1502 segments: &[OwnedSegment],
1503 def: &MappingDefinition,
1504 ) -> serde_json::Value {
1505 let assembled_segments: Vec<AssembledSegment> = segments
1506 .iter()
1507 .map(|s| AssembledSegment {
1508 tag: s.id.clone(),
1509 elements: s.elements.clone(),
1510 mig_number: None,
1511 segment_number: Some(s.segment_number),
1512 })
1513 .collect();
1514
1515 let instance = AssembledGroupInstance {
1516 segments: assembled_segments,
1517 child_groups: vec![],
1518 entry_mig_number: None,
1519 variant_mig_numbers: vec![],
1520 skipped_segments: Vec::new(),
1521 skipped_positions: Vec::new(),
1522 };
1523
1524 let mut result = serde_json::Map::new();
1525 self.extract_fields_from_instance(&instance, def, &mut result, true);
1526 serde_json::Value::Object(result)
1527 }
1528
1529 pub fn map_reverse(
1542 &self,
1543 bo4e_value: &serde_json::Value,
1544 def: &MappingDefinition,
1545 ) -> AssembledGroupInstance {
1546 if def.meta.repeat_on_tag.is_some() {
1548 if let Some(arr) = bo4e_value.as_array() {
1549 let mut all_segments = Vec::new();
1550 for elem in arr {
1551 let sub = self.map_reverse_single(elem, def);
1552 all_segments.extend(sub.segments);
1553 }
1554 return AssembledGroupInstance {
1555 segments: all_segments,
1556 child_groups: vec![],
1557 entry_mig_number: None,
1558 variant_mig_numbers: vec![],
1559 skipped_segments: Vec::new(),
1560 skipped_positions: Vec::new(),
1561 };
1562 }
1563 }
1564 let mut instance = self.map_reverse_single(bo4e_value, def);
1565 if def.meta.parent_field.is_some() {
1566 return instance;
1567 }
1568 if !instance.segments.is_empty() {
1569 self.reverse_nested_children(bo4e_value, def, &mut instance);
1570 return instance;
1571 }
1572 let mut with_constants = self.map_reverse_single_inner(bo4e_value, def, true);
1580 if with_constants.segments.is_empty() {
1581 return instance;
1582 }
1583 self.reverse_nested_children(bo4e_value, def, &mut with_constants);
1584 if with_constants.child_groups.is_empty() && !self.stands_for_bare_group(bo4e_value, def) {
1585 return instance;
1586 }
1587 with_constants
1588 }
1589
1590 fn stands_for_bare_group(
1599 &self,
1600 bo4e_value: &serde_json::Value,
1601 def: &MappingDefinition,
1602 ) -> bool {
1603 bo4e_value.as_object().is_some_and(|o| o.is_empty())
1604 && self
1605 .definitions
1606 .iter()
1607 .filter(|d| d.meta.entity == def.meta.entity && d.meta.parent_field.is_none())
1608 .all(|d| d.meta.source_path == def.meta.source_path)
1609 }
1610
1611 fn map_reverse_single(
1612 &self,
1613 bo4e_value: &serde_json::Value,
1614 def: &MappingDefinition,
1615 ) -> AssembledGroupInstance {
1616 self.map_reverse_single_inner(bo4e_value, def, false)
1617 }
1618
1619 fn map_reverse_single_inner(
1622 &self,
1623 bo4e_value: &serde_json::Value,
1624 def: &MappingDefinition,
1625 keep_constants: bool,
1626 ) -> AssembledGroupInstance {
1627 let mut field_values: Vec<(String, String, usize, usize, String)> =
1630 Vec::with_capacity(def.fields.len());
1631
1632 let mut has_real_data = false;
1639 let mut has_data_fields = false;
1640 let mut seg_has_data_field: HashSet<String> = HashSet::new();
1643 let mut seg_has_real_data: HashSet<String> = HashSet::new();
1644 let mut injected_qualifiers: HashSet<String> = HashSet::new();
1645 type ListField<'a> = (
1648 &'a str,
1649 &'a str,
1650 String,
1651 Option<String>,
1652 usize,
1653 usize,
1654 Option<&'a std::collections::BTreeMap<String, String>>,
1655 );
1656 let mut list_fields: Vec<ListField<'_>> = Vec::new();
1657
1658 for (path, field_mapping) in &def.fields {
1659 let (target, default, enum_map, when_filled, also_target, also_enum_map) =
1660 match field_mapping {
1661 FieldMapping::Simple(t) => (t.as_str(), None, None, None, None, None),
1662 FieldMapping::Structured(s) => (
1663 s.target.as_str(),
1664 s.default.as_ref(),
1665 self.table(s.enum_map.as_ref(), s.code_list.as_deref()),
1666 s.when_filled.as_ref(),
1667 s.also_target.as_deref(),
1668 self.table(s.also_enum_map.as_ref(), s.also_code_list.as_deref()),
1669 ),
1670 FieldMapping::Nested(_) => continue,
1671 };
1672
1673 let parts: Vec<&str> = path.split('.').collect();
1674 if parts.len() < 2 {
1675 continue;
1676 }
1677
1678 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1679 let seg_key = parts[0].to_uppercase();
1682 let sub_path = &parts[1..];
1683
1684 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1686 let ci = if sub_path.len() > 1 {
1687 sub_path[1].parse::<usize>().unwrap_or(0)
1688 } else {
1689 0
1690 };
1691 (ei, ci)
1692 } else {
1693 match sub_path.len() {
1694 1 => (0, 0),
1695 2 => (1, 0),
1696 _ => continue,
1697 }
1698 };
1699
1700 if let Some((list, sub)) = list_target(target) {
1701 list_fields.push((
1702 list,
1703 sub,
1704 seg_tag.clone(),
1705 qualifier.map(str::to_string),
1706 element_idx,
1707 component_idx,
1708 enum_map,
1709 ));
1710 continue;
1711 }
1712
1713 let val = if target.is_empty() {
1715 match (default, when_filled) {
1716 (Some(d), Some(fields)) => {
1718 let any_filled = fields.iter().any(|f| field_is_filled(bo4e_value, f));
1719 if any_filled {
1720 has_real_data = true;
1723 Some(d.clone())
1724 } else {
1725 None
1726 }
1727 }
1728 (Some(d), None) => Some(d.clone()),
1730 (None, _) => None,
1731 }
1732 } else {
1733 has_data_fields = true;
1734 seg_has_data_field.insert(seg_key.clone());
1735 let bo4e_val = self.populate_field(bo4e_value, target);
1736 if bo4e_val.is_some() {
1737 has_real_data = true;
1738 seg_has_real_data.insert(seg_key.clone());
1739 }
1740 let mapped_val = match (bo4e_val, enum_map) {
1742 (Some(v), Some(map)) => {
1743 let joint = match (also_target, also_enum_map) {
1750 (Some(also), Some(also_map)) => {
1751 self.populate_field(bo4e_value, also).and_then(|also_v| {
1752 map.iter()
1753 .find(|(code, bo4e_v)| {
1754 *bo4e_v == &v && also_map.get(*code) == Some(&also_v)
1755 })
1756 .map(|(code, _)| code.clone())
1757 })
1758 }
1759 _ => None,
1760 };
1761 joint
1762 .or_else(|| {
1763 map.iter()
1765 .find(|(_, bo4e_v)| *bo4e_v == &v)
1766 .map(|(edifact_k, _)| edifact_k.clone())
1767 })
1768 .or(Some(v))
1769 }
1770 (v, _) => v,
1771 };
1772 mapped_val.or_else(|| default.cloned())
1773 };
1774
1775 if let Some(val) = val {
1776 field_values.push((
1777 seg_key.clone(),
1778 seg_tag.clone(),
1779 element_idx,
1780 component_idx,
1781 val,
1782 ));
1783 }
1784
1785 if let Some(q) = qualifier {
1787 if injected_qualifiers.insert(seg_key.clone()) {
1788 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1789 }
1790 }
1791 }
1792
1793 let longest = list_fields
1796 .iter()
1797 .filter_map(|(list, ..)| bo4e_value.get(*list).and_then(|v| v.as_array()))
1798 .map(|a| a.len())
1799 .max()
1800 .unwrap_or(0);
1801 if !list_fields.is_empty() {
1802 has_data_fields = true;
1803 }
1804 for i in 0..longest {
1805 for (list, sub, seg_tag, qualifier, element_idx, component_idx, enum_map) in
1806 &list_fields
1807 {
1808 let key = match qualifier {
1809 Some(q) => format!("{seg_tag}[{q},{i}]"),
1810 None => format!("{seg_tag}[*,{i}]"),
1811 };
1812 seg_has_data_field.insert(key.clone());
1813 let Some(item) = bo4e_value
1814 .get(*list)
1815 .and_then(|v| v.as_array())
1816 .and_then(|a| a.get(i))
1817 else {
1818 continue;
1819 };
1820 let Some(value) = self.populate_field(item, sub) else {
1821 continue;
1822 };
1823 let value = match enum_map {
1824 Some(map) => map
1825 .iter()
1826 .find(|(_, name)| **name == value)
1827 .map(|(code, _)| code.clone())
1828 .unwrap_or(value),
1829 None => value,
1830 };
1831 has_real_data = true;
1832 seg_has_real_data.insert(key.clone());
1833 field_values.push((
1834 key.clone(),
1835 seg_tag.clone(),
1836 *element_idx,
1837 *component_idx,
1838 value,
1839 ));
1840 if let Some(q) = qualifier {
1841 if injected_qualifiers.insert(key.clone()) {
1842 field_values.push((key, seg_tag.clone(), 0, 0, q.clone()));
1843 }
1844 }
1845 }
1846 }
1847
1848 field_values.retain(|(seg_key, _, _, _, _)| {
1856 if !seg_key.contains('[') {
1857 return true; }
1859 !seg_has_data_field.contains(seg_key) || seg_has_real_data.contains(seg_key)
1860 });
1861
1862 if has_data_fields && !has_real_data && !keep_constants {
1867 return AssembledGroupInstance {
1868 segments: vec![],
1869 child_groups: vec![],
1870 entry_mig_number: None,
1871 variant_mig_numbers: vec![],
1872 skipped_segments: Vec::new(),
1873 skipped_positions: Vec::new(),
1874 };
1875 }
1876
1877 let mut segments: Vec<AssembledSegment> = Vec::with_capacity(field_values.len());
1880 let mut seen_keys: HashMap<String, usize> = HashMap::new();
1881
1882 for (seg_key, seg_tag, element_idx, component_idx, val) in &field_values {
1883 let seg = if let Some(&pos) = seen_keys.get(seg_key) {
1884 &mut segments[pos]
1885 } else {
1886 let pos = segments.len();
1887 seen_keys.insert(seg_key.clone(), pos);
1888 segments.push(AssembledSegment {
1889 tag: seg_tag.clone(),
1890 elements: vec![],
1891 mig_number: None,
1892 segment_number: None,
1893 });
1894 &mut segments[pos]
1895 };
1896
1897 while seg.elements.len() <= *element_idx {
1898 seg.elements.push(vec![]);
1899 }
1900 while seg.elements[*element_idx].len() <= *component_idx {
1901 seg.elements[*element_idx].push(String::new());
1902 }
1903 seg.elements[*element_idx][*component_idx] = val.clone();
1904 }
1905
1906 for seg in &mut segments {
1909 let last_populated = seg.elements.iter().rposition(|e| !e.is_empty());
1910 if let Some(last_idx) = last_populated {
1911 for i in 0..last_idx {
1912 if seg.elements[i].is_empty() {
1913 seg.elements[i] = vec![String::new()];
1914 }
1915 }
1916 }
1917 }
1918
1919 if let Some(ref ss) = self.segment_structure {
1921 for seg in &mut segments {
1922 if let Some(expected) = ss.element_count(&seg.tag) {
1923 while seg.elements.len() < expected {
1924 seg.elements.push(vec![String::new()]);
1925 }
1926 }
1927 }
1928 }
1929
1930 AssembledGroupInstance {
1931 segments,
1932 child_groups: vec![],
1933 entry_mig_number: None,
1934 variant_mig_numbers: vec![],
1935 skipped_segments: Vec::new(),
1936 skipped_positions: Vec::new(),
1937 }
1938 }
1939
1940 fn resolve_field_path(segment: &AssembledSegment, path: &[&str]) -> Option<String> {
1953 if path.is_empty() {
1954 return None;
1955 }
1956
1957 if let Ok(element_idx) = path[0].parse::<usize>() {
1959 let component_idx = if path.len() > 1 {
1960 path[1].parse::<usize>().unwrap_or(0)
1961 } else {
1962 0
1963 };
1964 return segment
1965 .elements
1966 .get(element_idx)?
1967 .get(component_idx)
1968 .filter(|v| !v.is_empty())
1969 .cloned();
1970 }
1971
1972 None
1978 }
1979
1980 pub(crate) fn parse_element_component(parts: &[&str]) -> (usize, usize) {
1983 if parts.is_empty() {
1984 return (0, 0);
1985 }
1986 let element_idx = parts[0].parse::<usize>().unwrap_or(0);
1987 let component_idx = if parts.len() > 1 {
1988 parts[1].parse::<usize>().unwrap_or(0)
1989 } else {
1990 0
1991 };
1992 (element_idx, component_idx)
1993 }
1994
1995 pub fn populate_field(
1998 &self,
1999 bo4e_value: &serde_json::Value,
2000 target_field: &str,
2001 ) -> Option<String> {
2002 let mut current = bo4e_value;
2003 for part in target_field.split('.') {
2004 current = current.get(part)?;
2005 }
2006 if let Some(code) = current.get("code").and_then(|v| v.as_str()) {
2008 return Some(code.to_string());
2009 }
2010 current.as_str().map(|s| s.to_string())
2011 }
2012
2013 pub fn build_segment_from_bo4e(
2015 &self,
2016 bo4e_value: &serde_json::Value,
2017 segment_tag: &str,
2018 target_field: &str,
2019 ) -> AssembledSegment {
2020 let value = self.populate_field(bo4e_value, target_field);
2021 let elements = if let Some(val) = value {
2022 vec![vec![val]]
2023 } else {
2024 vec![]
2025 };
2026 AssembledSegment {
2027 tag: segment_tag.to_uppercase(),
2028 elements,
2029 mig_number: None,
2030 segment_number: None,
2031 }
2032 }
2033
2034 pub fn resolve_repetition(
2043 tree: &AssembledTree,
2044 group_path: &str,
2045 discriminator: &str,
2046 ) -> Option<usize> {
2047 let (spec, expected) = discriminator.split_once('=')?;
2048 let parts: Vec<&str> = spec.split('.').collect();
2049 if parts.len() != 3 {
2050 return None;
2051 }
2052 let tag = parts[0];
2053 let element_idx: usize = parts[1].parse().ok()?;
2054 let component_idx: usize = parts[2].parse().ok()?;
2055
2056 let path_parts: Vec<&str> = group_path.split('.').collect();
2058
2059 let leaf_group = if path_parts.len() == 1 {
2060 let (group_id, _) = parse_group_spec(path_parts[0]);
2061 tree.groups.iter().find(|g| g.group_id == group_id)?
2062 } else {
2063 let parent_parts = &path_parts[..path_parts.len() - 1];
2065 let mut current_instance = {
2066 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
2067 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
2068 first_group.repetitions.get(first_rep.unwrap_or(0))?
2069 };
2070 for part in &parent_parts[1..] {
2071 let (group_id, explicit_rep) = parse_group_spec(part);
2072 let child_group = current_instance
2073 .child_groups
2074 .iter()
2075 .find(|g| g.group_id == group_id)?;
2076 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
2077 }
2078 let (leaf_id, _) = parse_group_spec(path_parts.last()?);
2079 current_instance
2080 .child_groups
2081 .iter()
2082 .find(|g| g.group_id == leaf_id)?
2083 };
2084
2085 let expected_values: Vec<&str> = expected.split('|').collect();
2087 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
2088 let matches = instance.segments.iter().any(|s| {
2089 s.tag.eq_ignore_ascii_case(tag)
2090 && s.elements
2091 .get(element_idx)
2092 .and_then(|e| e.get(component_idx))
2093 .map(|v| expected_values.iter().any(|ev| v == ev))
2094 .unwrap_or(false)
2095 });
2096 if matches {
2097 return Some(rep_idx);
2098 }
2099 }
2100
2101 None
2102 }
2103
2104 pub fn resolve_all_repetitions(
2109 tree: &AssembledTree,
2110 group_path: &str,
2111 discriminator: &str,
2112 ) -> Vec<usize> {
2113 let Some((spec, expected)) = discriminator.split_once('=') else {
2114 return Vec::new();
2115 };
2116 let parts: Vec<&str> = spec.split('.').collect();
2117 if parts.len() != 3 {
2118 return Vec::new();
2119 }
2120 let tag = parts[0];
2121 let element_idx: usize = match parts[1].parse() {
2122 Ok(v) => v,
2123 Err(_) => return Vec::new(),
2124 };
2125 let component_idx: usize = match parts[2].parse() {
2126 Ok(v) => v,
2127 Err(_) => return Vec::new(),
2128 };
2129
2130 let path_parts: Vec<&str> = group_path.split('.').collect();
2132
2133 let leaf_group = if path_parts.len() == 1 {
2134 let (group_id, _) = parse_group_spec(path_parts[0]);
2135 match tree.groups.iter().find(|g| g.group_id == group_id) {
2136 Some(g) => g,
2137 None => return Vec::new(),
2138 }
2139 } else {
2140 let parent_parts = &path_parts[..path_parts.len() - 1];
2141 let mut current_instance = {
2142 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
2143 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2144 Some(g) => g,
2145 None => return Vec::new(),
2146 };
2147 match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2148 Some(i) => i,
2149 None => return Vec::new(),
2150 }
2151 };
2152 for part in &parent_parts[1..] {
2153 let (group_id, explicit_rep) = parse_group_spec(part);
2154 let child_group = match current_instance
2155 .child_groups
2156 .iter()
2157 .find(|g| g.group_id == group_id)
2158 {
2159 Some(g) => g,
2160 None => return Vec::new(),
2161 };
2162 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2163 Some(i) => i,
2164 None => return Vec::new(),
2165 };
2166 }
2167 let (leaf_id, _) = match path_parts.last() {
2168 Some(p) => parse_group_spec(p),
2169 None => return Vec::new(),
2170 };
2171 match current_instance
2172 .child_groups
2173 .iter()
2174 .find(|g| g.group_id == leaf_id)
2175 {
2176 Some(g) => g,
2177 None => return Vec::new(),
2178 }
2179 };
2180
2181 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
2183
2184 let expected_values: Vec<&str> = expected_raw.split('|').collect();
2186 let mut result = Vec::new();
2187 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
2188 let matches = instance.segments.iter().any(|s| {
2189 s.tag.eq_ignore_ascii_case(tag)
2190 && s.elements
2191 .get(element_idx)
2192 .and_then(|e| e.get(component_idx))
2193 .map(|v| expected_values.iter().any(|ev| v == ev))
2194 .unwrap_or(false)
2195 });
2196 if matches {
2197 result.push(rep_idx);
2198 }
2199 }
2200
2201 if let Some(occ) = occurrence {
2203 result.into_iter().nth(occ).into_iter().collect()
2204 } else {
2205 result
2206 }
2207 }
2208
2209 pub fn map_all_forward(&self, tree: &AssembledTree) -> serde_json::Value {
2231 self.map_all_forward_inner(tree, true).0
2232 }
2233
2234 pub fn map_all_forward_enriched(
2238 &self,
2239 tree: &AssembledTree,
2240 enrich_codes: bool,
2241 ) -> serde_json::Value {
2242 self.map_all_forward_inner(tree, enrich_codes).0
2243 }
2244
2245 fn map_all_forward_inner(
2251 &self,
2252 tree: &AssembledTree,
2253 enrich_codes: bool,
2254 ) -> (
2255 serde_json::Value,
2256 std::collections::HashMap<String, Vec<usize>>,
2257 ) {
2258 self.map_all_forward_inner_with_tx(tree, enrich_codes, self.transaction_group.as_deref())
2259 }
2260
2261 fn map_all_forward_inner_with_tx(
2265 &self,
2266 tree: &AssembledTree,
2267 enrich_codes: bool,
2268 tx_group_override: Option<&str>,
2269 ) -> (
2270 serde_json::Value,
2271 std::collections::HashMap<String, Vec<usize>>,
2272 ) {
2273 let mut result = serde_json::Map::new();
2274 let mut nesting_info: std::collections::HashMap<String, Vec<usize>> =
2275 std::collections::HashMap::new();
2276 let mut contributors: std::collections::HashMap<String, Vec<String>> =
2278 std::collections::HashMap::new();
2279
2280 for def in &self.definitions {
2281 if def.meta.parent_field.is_some() || is_bound_child(&self.definitions, def) {
2284 continue;
2285 }
2286 let entity = &def.meta.entity;
2287
2288 let bo4e = if let Some(ref disc) = def.meta.discriminator {
2289 let use_source_path = def
2294 .meta
2295 .source_path
2296 .as_ref()
2297 .is_some_and(|sp| has_source_path_qualifiers(sp));
2298 if use_source_path {
2299 let sp = def.meta.source_path.as_deref().unwrap();
2301 let all_instances = Self::resolve_all_by_source_path(tree, sp);
2302 let instances: Vec<_> = if let Some(matcher) = DiscriminatorMatcher::parse(disc)
2304 {
2305 matcher.filter_instances(all_instances)
2306 } else {
2307 all_instances
2308 };
2309 let extract = |instance: &AssembledGroupInstance| {
2310 let mut r = serde_json::Map::new();
2311 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
2312 serde_json::Value::Object(r)
2313 };
2314 match instances.len() {
2315 0 => None,
2316 1 => Some(extract(instances[0])),
2317 _ => Some(serde_json::Value::Array(
2318 instances.iter().map(|i| extract(i)).collect(),
2319 )),
2320 }
2321 } else {
2322 let reps = Self::resolve_all_repetitions(tree, &def.meta.source_group, disc);
2323 match reps.len() {
2324 0 => None,
2325 1 => Some(self.map_forward_inner(tree, def, reps[0], enrich_codes)),
2326 _ => Some(serde_json::Value::Array(
2327 reps.iter()
2328 .map(|&rep| self.map_forward_inner(tree, def, rep, enrich_codes))
2329 .collect(),
2330 )),
2331 }
2332 }
2333 } else if def.meta.source_group.is_empty() {
2334 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2336 } else if def.meta.source_path.as_ref().is_some_and(|sp| {
2337 has_source_path_qualifiers(sp) || def.meta.source_group.contains('.')
2338 }) {
2339 let sp = def.meta.source_path.as_deref().unwrap();
2344 let mut indexed = Self::resolve_all_with_parent_indices(tree, sp);
2345
2346 if let Some(last_part) = sp.rsplit('.').next() {
2351 if !last_part.contains('_') {
2352 let base_prefix = if let Some(parent) = sp.rsplit_once('.') {
2356 format!("{}.", parent.0)
2357 } else {
2358 String::new()
2359 };
2360 let sibling_qualifiers: Vec<String> = self
2361 .definitions
2362 .iter()
2363 .filter_map(|d| d.meta.source_path.as_deref())
2364 .filter(|other_sp| {
2365 *other_sp != sp
2366 && other_sp.starts_with(&base_prefix)
2367 && other_sp.split('.').count() == sp.split('.').count()
2368 })
2369 .filter_map(|other_sp| {
2370 let other_last = other_sp.rsplit('.').next()?;
2371 let (base, q) = other_last.split_once('_')?;
2374 if base == last_part {
2375 Some(q.to_string())
2376 } else {
2377 None
2378 }
2379 })
2380 .collect();
2381
2382 if !sibling_qualifiers.is_empty() {
2383 indexed.retain(|(_, inst)| {
2384 let entry_qual = inst
2385 .segments
2386 .first()
2387 .and_then(|seg| seg.elements.first())
2388 .and_then(|el| el.first())
2389 .map(|v| v.to_lowercase());
2390 !entry_qual.is_some_and(|q| {
2393 sibling_qualifiers.iter().any(|sq| {
2394 sq.split('_').any(|part| part.eq_ignore_ascii_case(&q))
2395 })
2396 })
2397 });
2398 }
2399 }
2400 }
2401 let extract = |instance: &AssembledGroupInstance| {
2402 let mut r = serde_json::Map::new();
2403 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
2404 serde_json::Value::Object(r)
2405 };
2406 if def.meta.source_group.contains('.') && !indexed.is_empty() {
2411 if let Some(sp) = &def.meta.source_path {
2412 let parent_indices: Vec<usize> =
2413 indexed.iter().map(|(idx, _)| *idx).collect();
2414 nesting_info.entry(sp.clone()).or_insert(parent_indices);
2415
2416 let child_key = format!("{sp}#child");
2419 if let std::collections::hash_map::Entry::Vacant(e) =
2420 nesting_info.entry(child_key)
2421 {
2422 let child_indices: Vec<usize> =
2423 Self::compute_child_indices(tree, sp, &indexed);
2424 if !child_indices.is_empty() {
2425 e.insert(child_indices);
2426 }
2427 }
2428 }
2429 }
2430 match indexed.len() {
2431 0 => None,
2432 1 => Some(extract(indexed[0].1)),
2433 _ => Some(serde_json::Value::Array(
2434 indexed.iter().map(|(_, i)| extract(i)).collect(),
2435 )),
2436 }
2437 } else {
2438 let num_reps = Self::count_repetitions(tree, &def.meta.source_group);
2439 if num_reps <= 1 {
2440 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2441 } else {
2442 let mut items = Vec::with_capacity(num_reps);
2444 for rep in 0..num_reps {
2445 items.push(self.map_forward_inner(tree, def, rep, enrich_codes));
2446 }
2447 Some(serde_json::Value::Array(items))
2448 }
2449 };
2450
2451 if let Some(bo4e) = bo4e {
2452 let key = to_camel_case(entity);
2453 match def.meta.target_list.as_deref() {
2454 Some(list_field) => append_to_list_field(&mut result, &key, list_field, bo4e),
2455 None => {
2456 let group = def
2460 .meta
2461 .source_path
2462 .clone()
2463 .unwrap_or_else(|| def.meta.source_group.to_lowercase());
2464 let seen = contributors.entry(key.clone()).or_default();
2465 let nested = seen.iter().any(|other: &String| {
2466 group.starts_with(&format!("{other}."))
2467 || other.starts_with(&format!("{group}."))
2468 });
2469 seen.push(group);
2470 merge_entity(&mut result, &key, bo4e, !nested);
2471 }
2472 }
2473 }
2474 }
2475
2476 nest_child_entities_in_result(
2481 &mut result,
2482 &self.definitions,
2483 &nesting_info,
2484 tx_group_override,
2485 );
2486
2487 (serde_json::Value::Object(result), nesting_info)
2488 }
2489
2490 pub fn map_all_reverse(
2499 &self,
2500 entities: &serde_json::Value,
2501 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2502 ) -> AssembledTree {
2503 self.map_all_reverse_with_mig(entities, nesting_info, None)
2504 }
2505
2506 pub fn map_all_reverse_with_mig(
2510 &self,
2511 entities: &serde_json::Value,
2512 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2513 mig: Option<&MigSchema>,
2514 ) -> AssembledTree {
2515 let mut root_segments: Vec<AssembledSegment> = Vec::new();
2516 let mut groups: Vec<AssembledGroup> = Vec::new();
2517 let mut inferred_nesting: std::collections::HashMap<String, Vec<usize>> =
2520 std::collections::HashMap::new();
2521
2522 for def in &self.definitions {
2523 if def.meta.parent_field.is_some() || is_bound_child(&self.definitions, def) {
2526 continue;
2527 }
2528 let entity_key = to_camel_case(&def.meta.entity);
2529
2530 let _extracted: Option<serde_json::Value>;
2533 let entity_value = if let Some(list_field) = def.meta.target_list.as_deref() {
2534 match entities.get(&entity_key).and_then(|e| e.get(list_field)) {
2540 Some(v) if v.is_array() => {
2541 _extracted = None;
2542 v
2543 }
2544 _ => continue,
2545 }
2546 } else if let Some(v) = entities.get(&entity_key) {
2547 _extracted = None;
2548 v
2549 } else if def.meta.source_group.contains('.') {
2550 match extract_child_from_parent_with_indices(entities, &self.definitions, def) {
2552 Some((v, parent_indices)) => {
2553 if let Some(sp) = def.meta.source_path.as_deref() {
2555 inferred_nesting
2556 .entry(sp.to_string())
2557 .or_insert(parent_indices);
2558 }
2559 _extracted = Some(v);
2560 _extracted.as_ref().unwrap()
2561 }
2562 None => continue,
2563 }
2564 } else {
2565 continue;
2566 };
2567
2568 let unwrapped: Option<serde_json::Value>;
2576 let entity_value = if entity_value.is_object() && !entity_value.is_array() {
2577 if let Some(disc_value) = def
2578 .meta
2579 .discriminator
2580 .as_deref()
2581 .and_then(|d| d.split_once('='))
2582 .map(|(_, v)| v)
2583 {
2584 if let Some(inner) = entity_value.get(disc_value) {
2586 let mut injected = inner.clone();
2587 if let Some(qualifier_field) =
2590 find_qualifier_companion_field(&self.definitions, &def.meta.entity)
2591 {
2592 if let Some(obj) = injected.as_object_mut() {
2593 let entry = obj
2594 .entry(qualifier_field)
2595 .or_insert(serde_json::Value::Null);
2596 if entry.is_null() {
2597 *entry = serde_json::Value::String(disc_value.to_string());
2598 }
2599 }
2600 }
2601 unwrapped = Some(injected);
2602 unwrapped.as_ref().unwrap()
2603 } else {
2604 entity_value
2605 }
2606 } else if is_map_keyed_object(entity_value) {
2607 let map = entity_value.as_object().unwrap();
2612 let arr: Vec<serde_json::Value> = map
2613 .iter()
2614 .map(|(key, val)| {
2615 let mut item = val.clone();
2616 if let Some(obj) = item.as_object_mut() {
2619 if let Some(qualifier_field) = find_qualifier_companion_field(
2620 &self.definitions,
2621 &def.meta.entity,
2622 ) {
2623 let entry = obj
2624 .entry(qualifier_field)
2625 .or_insert(serde_json::Value::Null);
2626 if entry.is_null() {
2627 *entry = serde_json::Value::String(key.clone());
2628 }
2629 }
2630 }
2631 item
2632 })
2633 .collect();
2634 unwrapped = Some(serde_json::Value::Array(arr));
2635 unwrapped.as_ref().unwrap()
2636 } else {
2637 entity_value
2638 }
2639 } else {
2640 entity_value
2641 };
2642
2643 let leaf_group = def
2645 .meta
2646 .source_group
2647 .rsplit('.')
2648 .next()
2649 .unwrap_or(&def.meta.source_group);
2650
2651 if def.meta.source_group.is_empty() {
2652 let instance = self.map_reverse(entity_value, def);
2654 root_segments.extend(instance.segments);
2655 } else if self.transaction_group.as_deref() == Some(def.meta.source_group.as_str()) {
2656 let items: Vec<&serde_json::Value> = match entity_value.as_array() {
2661 Some(arr) => arr.iter().collect(),
2662 None => vec![entity_value],
2663 };
2664 let group = match groups.iter().position(|g| g.group_id == leaf_group) {
2665 Some(i) => &mut groups[i],
2666 None => {
2667 groups.push(AssembledGroup {
2668 group_id: leaf_group.to_string(),
2669 repetitions: Vec::new(),
2670 });
2671 groups.last_mut().expect("pushed")
2672 }
2673 };
2674 if group.repetitions.is_empty() {
2675 group.repetitions.push(AssembledGroupInstance {
2676 segments: Vec::new(),
2677 child_groups: Vec::new(),
2678 entry_mig_number: None,
2679 variant_mig_numbers: Vec::new(),
2680 skipped_segments: Vec::new(),
2681 skipped_positions: Vec::new(),
2682 });
2683 }
2684 let transaction = &mut group.repetitions[0];
2685 for item in items {
2686 let instance = self.map_reverse(item, def);
2687 transaction.segments.extend(instance.segments);
2688 for child in instance.child_groups {
2689 match transaction
2690 .child_groups
2691 .iter_mut()
2692 .find(|g| g.group_id == child.group_id)
2693 {
2694 Some(existing) => existing.repetitions.extend(child.repetitions),
2695 None => transaction.child_groups.push(child),
2696 }
2697 }
2698 }
2699 } else if entity_value.is_array() {
2700 let arr = entity_value.as_array().unwrap();
2702 let reps: Vec<_> = arr.iter().map(|item| self.map_reverse(item, def)).collect();
2703
2704 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2706 existing.repetitions.extend(reps);
2707 } else {
2708 groups.push(AssembledGroup {
2709 group_id: leaf_group.to_string(),
2710 repetitions: reps,
2711 });
2712 }
2713 } else {
2714 let instance = self.map_reverse(entity_value, def);
2716
2717 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2718 existing.repetitions.push(instance);
2719 } else {
2720 groups.push(AssembledGroup {
2721 group_id: leaf_group.to_string(),
2722 repetitions: vec![instance],
2723 });
2724 }
2725 }
2726 }
2727
2728 let nested_specs: Vec<(String, String)> = self
2734 .definitions
2735 .iter()
2736 .filter(|def| def.meta.parent_field.is_none())
2737 .filter_map(|def| {
2738 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
2739 if parts.len() > 1 {
2740 Some((parts[0].to_string(), parts[parts.len() - 1].to_string()))
2741 } else {
2742 None
2743 }
2744 })
2745 .collect();
2746 for (parent_id, child_id) in &nested_specs {
2747 let has_parent = groups.iter().any(|g| g.group_id == *parent_id);
2749 let has_child = groups.iter().any(|g| g.group_id == *child_id);
2750 if has_parent && has_child {
2751 let child_idx = groups.iter().position(|g| g.group_id == *child_id).unwrap();
2752 let child_group = groups.remove(child_idx);
2753 let parent = groups
2754 .iter_mut()
2755 .find(|g| g.group_id == *parent_id)
2756 .unwrap();
2757 let child_source_path = self
2761 .definitions
2762 .iter()
2763 .find(|d| {
2764 let parts: Vec<&str> = d.meta.source_group.split('.').collect();
2765 d.meta.parent_field.is_none()
2766 && parts.len() > 1
2767 && parts[parts.len() - 1] == *child_id
2768 })
2769 .and_then(|d| d.meta.source_path.as_deref());
2770 let distribution = child_source_path.and_then(|key| {
2771 nesting_info
2772 .and_then(|ni| ni.get(key))
2773 .or_else(|| inferred_nesting.get(key))
2774 });
2775 let unlinked_target = mig
2781 .and_then(|m| {
2782 mig_assembly::repetition_order::preferred_parent_repetition(
2783 parent,
2784 &m.segment_groups,
2785 child_id,
2786 )
2787 })
2788 .unwrap_or(0);
2789 for (i, child_rep) in child_group.repetitions.into_iter().enumerate() {
2790 let target_idx = distribution
2791 .and_then(|dist| dist.get(i))
2792 .copied()
2793 .unwrap_or(unlinked_target);
2794
2795 if let Some(target_rep) = parent.repetitions.get_mut(target_idx) {
2796 if let Some(existing) = target_rep
2797 .child_groups
2798 .iter_mut()
2799 .find(|g| g.group_id == *child_id)
2800 {
2801 existing.repetitions.push(child_rep);
2802 } else {
2803 target_rep.child_groups.push(AssembledGroup {
2804 group_id: child_id.clone(),
2805 repetitions: vec![child_rep],
2806 });
2807 }
2808 }
2809 }
2810 }
2811 }
2812
2813 let post_group_start = root_segments.len();
2814 AssembledTree {
2815 segments: root_segments,
2816 groups,
2817 post_group_start,
2818 inter_group_segments: std::collections::BTreeMap::new(),
2819 }
2820 }
2821
2822 fn count_repetitions(tree: &AssembledTree, group_path: &str) -> usize {
2824 let parts: Vec<&str> = group_path.split('.').collect();
2825
2826 let (first_id, first_rep) = parse_group_spec(parts[0]);
2827 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2828 Some(g) => g,
2829 None => return 0,
2830 };
2831
2832 if parts.len() == 1 {
2833 return first_group.repetitions.len();
2834 }
2835
2836 let mut current_instance = match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2838 Some(i) => i,
2839 None => return 0,
2840 };
2841
2842 for (i, part) in parts[1..].iter().enumerate() {
2843 let (group_id, explicit_rep) = parse_group_spec(part);
2844 let child_group = match current_instance
2845 .child_groups
2846 .iter()
2847 .find(|g| g.group_id == group_id)
2848 {
2849 Some(g) => g,
2850 None => return 0,
2851 };
2852
2853 if i == parts.len() - 2 {
2854 return child_group.repetitions.len();
2856 }
2857 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2858 Some(i) => i,
2859 None => return 0,
2860 };
2861 }
2862
2863 0
2864 }
2865
2866 pub fn translate_edifact_to_bo4e(
2874 msg_engine: &MappingEngine,
2875 tx_engine: &MappingEngine,
2876 tree: &AssembledTree,
2877 transaction_group: &str,
2878 ) -> crate::model::MappedMessage {
2879 Self::map_interchange_inner(msg_engine, tx_engine, tree, transaction_group, false)
2880 }
2881
2882 pub fn enrich_bo4e_types(
2896 msg_engine: &MappingEngine,
2897 tx_engine: &MappingEngine,
2898 mapped: &mut crate::model::MappedMessage,
2899 ) {
2900 msg_engine.enrich_entities(&mut mapped.stammdaten);
2901 for tx in &mut mapped.transaktionen {
2902 tx_engine.enrich_entities(&mut tx.stammdaten);
2903 }
2904
2905 msg_engine.enrich_named_entity(
2909 &mut mapped.nachricht_meta,
2910 crate::model::MSG_METADATA_ENTITY,
2911 );
2912 for tx in &mut mapped.transaktionen {
2913 tx_engine
2914 .enrich_named_entity(&mut tx.transaktionsdaten, crate::model::TX_METADATA_ENTITY);
2915 }
2916 }
2917
2918 fn enrich_named_entity(&self, value: &mut serde_json::Value, entity_key: &str) {
2923 if self.code_lookup.is_none() || value.is_null() {
2924 return;
2925 }
2926 let sites = self.code_sites();
2927 if let Some(entity_sites) = sites.get(entity_key) {
2928 Self::apply_sites(self, value, entity_sites);
2929 }
2930 }
2931
2932 fn enrich_entities(&self, value: &mut serde_json::Value) {
2938 if self.code_lookup.is_none() {
2939 return;
2940 }
2941 let sites: HashMap<String, Vec<CodeSite<'_>>> = self.code_sites();
2942 if sites.is_empty() {
2943 return;
2944 }
2945 Self::walk_and_enrich(self, value, &sites);
2946 }
2947
2948 fn code_sites(&self) -> HashMap<String, Vec<CodeSite<'_>>> {
2951 let Some(ref code_lookup) = self.code_lookup else {
2952 return HashMap::new();
2953 };
2954 let mut sites: HashMap<String, Vec<CodeSite<'_>>> = HashMap::new();
2955
2956 for def in &self.definitions {
2957 let Some(ref source_path) = def.meta.source_path else {
2958 continue;
2959 };
2960 let entity_key = to_camel_case(&def.meta.entity);
2961
2962 for (path, field_mapping) in &def.fields {
2963 let (target, enum_map, also_target, also_enum_map) = match field_mapping {
2964 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
2965 FieldMapping::Structured(s) => (
2966 s.target.as_str(),
2967 self.table(s.enum_map.as_ref(), s.code_list.as_deref()),
2968 s.also_target.as_deref(),
2969 self.table(s.also_enum_map.as_ref(), s.also_code_list.as_deref()),
2970 ),
2971 FieldMapping::Nested(_) => continue,
2972 };
2973 if target.is_empty() {
2974 continue;
2975 }
2976
2977 let parts: Vec<&str> = path.split('.').collect();
2978 let (seg_tag, path_qualifier, _occ) = parse_tag_qualifier(parts[0]);
2979 let (element_idx, component_idx) = Self::parse_element_component(&parts[1..]);
2980 let disc_qualifier = Self::discriminator_qualifier_for_tag(def, &seg_tag);
2987 if code_lookup
2988 .enrichment_codes(
2989 source_path,
2990 &seg_tag,
2991 path_qualifier,
2992 disc_qualifier.as_deref(),
2993 element_idx,
2994 component_idx,
2995 )
2996 .is_none()
2997 {
2998 continue;
2999 }
3000
3001 sites.entry(entity_key.clone()).or_default().push(CodeSite {
3002 target,
3003 parent_field: def.meta.parent_field.as_deref(),
3004 source_path,
3005 seg_tag,
3006 path_qualifier: path_qualifier.map(str::to_string),
3007 disc_qualifier,
3008 element_idx,
3009 component_idx,
3010 enum_map,
3011 also_target,
3012 also_enum_map,
3013 });
3014 }
3015 }
3016 sites
3017 }
3018
3019 fn walk_and_enrich(
3022 engine: &MappingEngine,
3023 value: &mut serde_json::Value,
3024 sites: &HashMap<String, Vec<CodeSite<'_>>>,
3025 ) {
3026 match value {
3027 serde_json::Value::Object(map) => {
3028 for (key, child) in map.iter_mut() {
3029 if let Some(entity_sites) = sites.get(key.as_str()) {
3030 Self::apply_sites(engine, child, entity_sites);
3031 }
3032 Self::walk_and_enrich(engine, child, sites);
3033 }
3034 }
3035 serde_json::Value::Array(items) => {
3036 for item in items.iter_mut() {
3037 Self::walk_and_enrich(engine, item, sites);
3038 }
3039 }
3040 _ => {}
3041 }
3042 }
3043
3044 fn apply_sites(engine: &MappingEngine, value: &mut serde_json::Value, sites: &[CodeSite<'_>]) {
3047 match value {
3048 serde_json::Value::Array(items) => {
3049 for item in items.iter_mut() {
3050 Self::apply_sites(engine, item, sites);
3051 }
3052 }
3053 serde_json::Value::Object(_) => {
3054 for site in sites {
3055 match site.parent_field {
3056 None => engine.enrich_one(value, site),
3057 Some(field) => {
3058 if let Some(nested) = value.get_mut(field) {
3059 Self::apply_nested_site(engine, nested, site);
3060 }
3061 }
3062 }
3063 }
3064 }
3065 _ => {}
3066 }
3067 }
3068
3069 fn apply_nested_site(
3071 engine: &MappingEngine,
3072 value: &mut serde_json::Value,
3073 site: &CodeSite<'_>,
3074 ) {
3075 match value {
3076 serde_json::Value::Array(items) => {
3077 for item in items.iter_mut() {
3078 Self::apply_nested_site(engine, item, site);
3079 }
3080 }
3081 serde_json::Value::Object(_) => engine.enrich_one(value, site),
3082 _ => {}
3083 }
3084 }
3085
3086 fn enrich_one(&self, entity: &mut serde_json::Value, site: &CodeSite<'_>) {
3088 if let Some((list, sub)) = list_target(site.target) {
3090 if let Some(items) = entity.get_mut(list).and_then(|v| v.as_array_mut()) {
3091 let element_site = CodeSite {
3092 target: sub,
3093 ..site.clone()
3094 };
3095 for item in items {
3096 self.enrich_one(item, &element_site);
3097 }
3098 }
3099 return;
3100 }
3101 let Some(ref code_lookup) = self.code_lookup else {
3102 return;
3103 };
3104 let Some(mapped_val) = Self::read_plain_string(entity, site.target) else {
3106 return;
3107 };
3108
3109 let raw = match site.enum_map {
3112 None => mapped_val.clone(),
3113 Some(map) => {
3114 let joint = match (site.also_target, site.also_enum_map) {
3115 (Some(also), Some(also_map)) => {
3116 Self::read_plain_string(entity, also).and_then(|also_v| {
3117 map.iter()
3118 .find(|(code, bo4e_v)| {
3119 *bo4e_v == &mapped_val && also_map.get(*code) == Some(&also_v)
3120 })
3121 .map(|(code, _)| code.clone())
3122 })
3123 }
3124 _ => None,
3125 };
3126 joint
3127 .or_else(|| {
3128 map.iter()
3129 .find(|(_, bo4e_v)| *bo4e_v == &mapped_val)
3130 .map(|(code, _)| code.clone())
3131 })
3132 .unwrap_or_else(|| mapped_val.clone())
3133 }
3134 };
3135
3136 let Some(codes) = code_lookup.enrichment_codes(
3137 site.source_path,
3138 &site.seg_tag,
3139 site.path_qualifier.as_deref(),
3140 site.disc_qualifier.as_deref(),
3141 site.element_idx,
3142 site.component_idx,
3143 ) else {
3144 return;
3145 };
3146
3147 if let Some(ref pid) = self.current_pid {
3149 if codes.len() == 1 && codes.contains_key(pid.as_str()) {
3150 return;
3151 }
3152 }
3153
3154 let enrichment = codes.get(&raw);
3155 let meaning = enrichment
3156 .map(|e| serde_json::Value::String(e.meaning.clone()))
3157 .unwrap_or(serde_json::Value::Null);
3158
3159 let mut obj = serde_json::Map::new();
3160 obj.insert("code".into(), serde_json::json!(mapped_val));
3161 obj.insert("meaning".into(), meaning);
3162 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
3163 obj.insert("enum".into(), serde_json::json!(enum_key));
3164 }
3165
3166 if let serde_json::Value::Object(map) = entity {
3167 set_nested_value_json(map, site.target, serde_json::Value::Object(obj));
3168 }
3169 }
3170
3171 fn read_plain_string(entity: &serde_json::Value, target: &str) -> Option<String> {
3174 let mut current = entity;
3175 for part in target.split('.') {
3176 current = current.get(part)?;
3177 }
3178 current.as_str().map(str::to_string)
3179 }
3180
3181 pub fn map_interchange(
3190 msg_engine: &MappingEngine,
3191 tx_engine: &MappingEngine,
3192 tree: &AssembledTree,
3193 transaction_group: &str,
3194 enrich_codes: bool,
3195 ) -> crate::model::MappedMessage {
3196 let mut mapped =
3197 Self::translate_edifact_to_bo4e(msg_engine, tx_engine, tree, transaction_group);
3198 if enrich_codes {
3199 Self::enrich_bo4e_types(msg_engine, tx_engine, &mut mapped);
3200 }
3201 mapped
3202 }
3203
3204 #[doc(hidden)]
3213 pub fn map_interchange_inner_for_test(
3214 msg_engine: &MappingEngine,
3215 tx_engine: &MappingEngine,
3216 tree: &AssembledTree,
3217 transaction_group: &str,
3218 enrich_codes: bool,
3219 ) -> crate::model::MappedMessage {
3220 Self::map_interchange_inner(msg_engine, tx_engine, tree, transaction_group, enrich_codes)
3221 }
3222
3223 pub(crate) fn map_interchange_inner(
3224 msg_engine: &MappingEngine,
3225 tx_engine: &MappingEngine,
3226 tree: &AssembledTree,
3227 transaction_group: &str,
3228 enrich_codes: bool,
3229 ) -> crate::model::MappedMessage {
3230 let (stammdaten, nesting_info) = msg_engine.map_all_forward_inner(tree, enrich_codes);
3232
3233 let transaktionen = tree
3235 .groups
3236 .iter()
3237 .find(|g| g.group_id == transaction_group)
3238 .map(|sg| {
3239 sg.repetitions
3240 .iter()
3241 .map(|instance| {
3242 let wrapped_tree = AssembledTree {
3245 segments: vec![],
3246 groups: vec![AssembledGroup {
3247 group_id: transaction_group.to_string(),
3248 repetitions: vec![instance.clone()],
3249 }],
3250 post_group_start: 0,
3251 inter_group_segments: std::collections::BTreeMap::new(),
3252 };
3253
3254 let (tx_result, tx_nesting) = tx_engine.map_all_forward_inner_with_tx(
3258 &wrapped_tree,
3259 enrich_codes,
3260 Some(transaction_group),
3261 );
3262
3263 let mut tx_result = tx_result;
3268 let transaktionsdaten = crate::model::take_entity(
3269 &mut tx_result,
3270 crate::model::TX_METADATA_ENTITY,
3271 );
3272
3273 crate::model::MappedTransaktion {
3274 stammdaten: tx_result,
3275 transaktionsdaten,
3276 nesting_info: tx_nesting,
3277 }
3278 })
3279 .collect()
3280 })
3281 .unwrap_or_default();
3282
3283 let mut stammdaten = stammdaten;
3285 let nachricht_meta =
3286 crate::model::take_entity(&mut stammdaten, crate::model::MSG_METADATA_ENTITY);
3287
3288 crate::model::MappedMessage {
3289 stammdaten,
3290 nachricht_meta,
3291 transaktionen,
3292 nesting_info,
3293 inter_group_segments: tree.inter_group_segments.clone(),
3294 }
3295 }
3296
3297 pub fn map_interchange_reverse(
3307 msg_engine: &MappingEngine,
3308 tx_engine: &MappingEngine,
3309 mapped: &crate::model::MappedMessage,
3310 transaction_group: &str,
3311 filtered_mig: Option<&MigSchema>,
3312 ) -> AssembledTree {
3313 let _owned_msg: Option<serde_json::Value>;
3321 let msg_stammdaten = if !mapped.nachricht_meta.is_null() {
3322 let mut merged = mapped.stammdaten.clone();
3323 crate::model::restore_entity(
3324 &mut merged,
3325 crate::model::MSG_METADATA_ENTITY,
3326 &mapped.nachricht_meta,
3327 );
3328 _owned_msg = Some(merged);
3329 _owned_msg.as_ref().unwrap()
3330 } else {
3331 _owned_msg = None;
3332 &mapped.stammdaten
3333 };
3334
3335 let msg_tree = msg_engine.map_all_reverse_with_mig(
3336 msg_stammdaten,
3337 if mapped.nesting_info.is_empty() {
3338 None
3339 } else {
3340 Some(&mapped.nesting_info)
3341 },
3342 filtered_mig,
3343 );
3344
3345 let mut sg4_reps: Vec<AssembledGroupInstance> = Vec::new();
3347
3348 struct DefWithMeta<'a> {
3352 def: &'a MappingDefinition,
3353 relative: String,
3354 depth: usize,
3355 }
3356
3357 let mut sorted_defs: Vec<DefWithMeta> = tx_engine
3358 .definitions
3359 .iter()
3360 .filter(|def| {
3363 def.meta.parent_field.is_none() && !is_bound_child(&tx_engine.definitions, def)
3364 })
3365 .map(|def| {
3366 let relative = strip_tx_group_prefix(&def.meta.source_group, transaction_group);
3367 let depth = if relative.is_empty() {
3368 0
3369 } else {
3370 relative.chars().filter(|c| *c == '.').count() + 1
3371 };
3372 DefWithMeta {
3373 def,
3374 relative,
3375 depth,
3376 }
3377 })
3378 .collect();
3379
3380 let mut parent_rep_map: std::collections::HashMap<String, usize> =
3384 std::collections::HashMap::new();
3385 for dm in &sorted_defs {
3386 if dm.depth >= 2 {
3387 let parts: Vec<&str> = dm.relative.split('.').collect();
3388 let (_, parent_rep) = parse_group_spec(parts[0]);
3389 if let Some(rep_idx) = parent_rep {
3390 if let Some(sp) = &dm.def.meta.source_path {
3391 if let Some((parent_path, _)) = sp.rsplit_once('.') {
3392 parent_rep_map
3393 .entry(parent_path.to_string())
3394 .or_insert(rep_idx);
3395 }
3396 }
3397 }
3398 }
3399 }
3400
3401 for dm in &mut sorted_defs {
3404 if dm.depth == 1 && !dm.relative.contains(':') {
3405 if let Some(sp) = &dm.def.meta.source_path {
3406 if let Some(rep_idx) = parent_rep_map.get(sp.as_str()) {
3407 dm.relative = format!("{}:{}", dm.relative, rep_idx);
3408 }
3409 }
3410 }
3411 }
3412
3413 if let Some(mig) = filtered_mig {
3420 let mig_order = build_reverse_mig_group_order(mig, transaction_group);
3421 sorted_defs.sort_by(|a, b| {
3422 a.depth.cmp(&b.depth).then_with(|| {
3423 let a_id = a.relative.split(':').next().unwrap_or(&a.relative);
3424 let b_id = b.relative.split(':').next().unwrap_or(&b.relative);
3425 let a_pos = variant_mig_position(a.def, a_id, &mig_order);
3427 let b_pos = variant_mig_position(b.def, b_id, &mig_order);
3428 a_pos.cmp(&b_pos).then(a.relative.cmp(&b.relative))
3429 })
3430 });
3431 } else {
3432 sorted_defs.sort_by(|a, b| a.depth.cmp(&b.depth).then(a.relative.cmp(&b.relative)));
3433 }
3434
3435 for tx in &mapped.transaktionen {
3436 let mut root_segs: Vec<AssembledSegment> = Vec::new();
3437 let mut child_groups: Vec<AssembledGroup> = Vec::new();
3438
3439 let _owned_tx: Option<serde_json::Value>;
3442 let tx_stammdaten: &serde_json::Value = if !tx.transaktionsdaten.is_null() {
3443 let mut merged = tx.stammdaten.clone();
3444 crate::model::restore_entity(
3445 &mut merged,
3446 crate::model::TX_METADATA_ENTITY,
3447 &tx.transaktionsdaten,
3448 );
3449 _owned_tx = Some(merged);
3450 _owned_tx.as_ref().unwrap()
3451 } else {
3452 _owned_tx = None;
3453 &tx.stammdaten
3454 };
3455
3456 let mut source_path_to_rep: std::collections::HashMap<String, Vec<usize>> =
3461 std::collections::HashMap::new();
3462
3463 for dm in &sorted_defs {
3464 let entity_key = to_camel_case(&dm.def.meta.entity);
3467 let _tx_extracted: Option<serde_json::Value>;
3468 let bo4e_value = if let Some(v) = tx_stammdaten.get(&entity_key) {
3469 _tx_extracted = None;
3470 v
3471 } else if dm.def.meta.source_group.contains('.') {
3472 match extract_child_from_parent(tx_stammdaten, &tx_engine.definitions, dm.def) {
3473 Some(v) => {
3474 _tx_extracted = Some(v);
3475 _tx_extracted.as_ref().unwrap()
3476 }
3477 None => continue,
3478 }
3479 } else {
3480 continue;
3481 };
3482
3483 let unwrapped_value: Option<serde_json::Value>;
3485 let bo4e_value = if bo4e_value.is_object() && !bo4e_value.is_array() {
3486 if let Some(disc_value) = dm
3487 .def
3488 .meta
3489 .discriminator
3490 .as_deref()
3491 .and_then(|d| d.split_once('='))
3492 .map(|(_, v)| v)
3493 {
3494 if let Some(inner) = bo4e_value.get(disc_value) {
3495 let mut injected = inner.clone();
3496 if let Some(qualifier_field) = find_qualifier_companion_field(
3497 &tx_engine.definitions,
3498 &dm.def.meta.entity,
3499 ) {
3500 if let Some(obj) = injected.as_object_mut() {
3501 obj.entry(qualifier_field).or_insert_with(|| {
3502 serde_json::Value::String(disc_value.to_string())
3503 });
3504 }
3505 }
3506 unwrapped_value = Some(injected);
3507 unwrapped_value.as_ref().unwrap()
3508 } else {
3509 bo4e_value
3510 }
3511 } else if is_map_keyed_object(bo4e_value) {
3512 let map = bo4e_value.as_object().unwrap();
3513 let arr: Vec<serde_json::Value> = map
3514 .iter()
3515 .map(|(key, val)| {
3516 let mut item = val.clone();
3517 if let Some(obj) = item.as_object_mut() {
3518 if let Some(qualifier_field) = find_qualifier_companion_field(
3519 &tx_engine.definitions,
3520 &dm.def.meta.entity,
3521 ) {
3522 let entry = obj
3523 .entry(qualifier_field)
3524 .or_insert(serde_json::Value::Null);
3525 if entry.is_null() {
3526 *entry = serde_json::Value::String(key.clone());
3527 }
3528 }
3529 }
3530 item
3531 })
3532 .collect();
3533 unwrapped_value = Some(serde_json::Value::Array(arr));
3534 unwrapped_value.as_ref().unwrap()
3535 } else {
3536 bo4e_value
3537 }
3538 } else {
3539 bo4e_value
3540 };
3541
3542 let items: Vec<&serde_json::Value> = if bo4e_value.is_array() {
3546 bo4e_value.as_array().unwrap().iter().collect()
3547 } else {
3548 vec![bo4e_value]
3549 };
3550
3551 for (item_idx, item) in items.iter().enumerate() {
3552 let instance = tx_engine.map_reverse(item, dm.def);
3553
3554 if instance.segments.is_empty() && instance.child_groups.is_empty() {
3556 continue;
3557 }
3558
3559 if dm.relative.is_empty() {
3560 root_segs.extend(instance.segments);
3567 for child in instance.child_groups {
3568 match child_groups
3569 .iter_mut()
3570 .find(|g| g.group_id == child.group_id)
3571 {
3572 Some(existing) => existing.repetitions.extend(child.repetitions),
3573 None => child_groups.push(child),
3574 }
3575 }
3576 } else {
3577 let effective_relative = if dm.depth >= 2 {
3581 let rel = if items.len() > 1 {
3584 strip_all_rep_indices(&dm.relative)
3585 } else {
3586 dm.relative.clone()
3587 };
3588 let skip_nesting = dm
3595 .def
3596 .meta
3597 .source_path
3598 .as_ref()
3599 .and_then(|sp| sp.rsplit_once('.'))
3600 .and_then(|(parent_path, _)| source_path_to_rep.get(parent_path))
3601 .is_some_and(|reps| reps.len() == 1);
3602 let nesting_idx = if items.len() > 1 && !skip_nesting {
3603 dm.def
3604 .meta
3605 .source_path
3606 .as_ref()
3607 .and_then(|sp| tx.nesting_info.get(sp))
3608 .and_then(|dist| dist.get(item_idx))
3609 .copied()
3610 } else {
3611 None
3612 };
3613 if let Some(parent_rep) = nesting_idx {
3614 let parts: Vec<&str> = rel.split('.').collect();
3616 let parent_id = parts[0].split(':').next().unwrap_or(parts[0]);
3617 let rest = parts[1..].join(".");
3618 format!("{}:{}.{}", parent_id, parent_rep, rest)
3619 } else {
3620 resolve_child_relative(
3621 &rel,
3622 dm.def.meta.source_path.as_deref(),
3623 &source_path_to_rep,
3624 item_idx,
3625 )
3626 }
3627 } else if dm.depth == 1 {
3628 let child_key = dm
3631 .def
3632 .meta
3633 .source_path
3634 .as_ref()
3635 .map(|sp| format!("{sp}#child"));
3636 if let Some(child_indices) =
3637 child_key.as_ref().and_then(|ck| tx.nesting_info.get(ck))
3638 {
3639 if let Some(&target) = child_indices.get(item_idx) {
3640 if target != usize::MAX {
3641 let base =
3642 dm.relative.split(':').next().unwrap_or(&dm.relative);
3643 format!("{}:{}", base, target)
3644 } else {
3645 dm.relative.clone()
3646 }
3647 } else if items.len() > 1 && item_idx > 0 {
3648 strip_rep_index(&dm.relative)
3649 } else {
3650 dm.relative.clone()
3651 }
3652 } else if items.len() > 1 && item_idx > 0 {
3653 strip_rep_index(&dm.relative)
3654 } else {
3655 dm.relative.clone()
3656 }
3657 } else if items.len() > 1 && item_idx > 0 {
3658 strip_rep_index(&dm.relative)
3661 } else {
3662 dm.relative.clone()
3663 };
3664
3665 let rep_used =
3666 place_in_groups(&mut child_groups, &effective_relative, instance);
3667
3668 if dm.depth == 1 {
3670 if let Some(sp) = &dm.def.meta.source_path {
3671 source_path_to_rep
3672 .entry(sp.clone())
3673 .or_default()
3674 .push(rep_used);
3675 }
3676 }
3677 }
3678 }
3679 }
3680
3681 sg4_reps.push(AssembledGroupInstance {
3682 segments: root_segs,
3683 child_groups,
3684 entry_mig_number: None,
3685 variant_mig_numbers: vec![],
3686 skipped_segments: Vec::new(),
3687 skipped_positions: Vec::new(),
3688 });
3689 }
3690
3691 let mut root_segments = Vec::new();
3698 let mut uns_segments = Vec::new();
3699 let mut uns_is_summary = false;
3700 let mut found_uns = false;
3701 for seg in msg_tree.segments {
3702 if seg.tag == "UNS" {
3703 uns_is_summary = seg
3705 .elements
3706 .first()
3707 .and_then(|el| el.first())
3708 .map(|v| v == "S")
3709 .unwrap_or(false);
3710 uns_segments.push(seg);
3711 found_uns = true;
3712 } else if found_uns {
3713 uns_segments.push(seg);
3715 } else {
3716 root_segments.push(seg);
3717 }
3718 }
3719
3720 let pre_group_count = root_segments.len();
3721 let mut all_groups = msg_tree.groups;
3722 let mut inter_group = msg_tree.inter_group_segments;
3723
3724 let sg_num = |id: &str| -> usize {
3726 id.strip_prefix("SG")
3727 .and_then(|n| n.parse::<usize>().ok())
3728 .unwrap_or(0)
3729 };
3730
3731 if !sg4_reps.is_empty() {
3732 if uns_is_summary {
3733 all_groups.push(AssembledGroup {
3735 group_id: transaction_group.to_string(),
3736 repetitions: sg4_reps,
3737 });
3738 if !uns_segments.is_empty() {
3739 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3744 let tx_num = sg_num(transaction_group);
3745 let uns_pos = all_groups
3746 .iter()
3747 .rposition(|g| sg_num(&g.group_id) <= tx_num)
3748 .map(|i| i + 1)
3749 .unwrap_or(all_groups.len());
3750 inter_group.insert(uns_pos, uns_segments);
3751 }
3752 } else {
3753 if !uns_segments.is_empty() {
3755 inter_group.insert(all_groups.len(), uns_segments);
3756 }
3757 all_groups.push(AssembledGroup {
3758 group_id: transaction_group.to_string(),
3759 repetitions: sg4_reps,
3760 });
3761 }
3762 } else if !uns_segments.is_empty() {
3763 if transaction_group.is_empty() {
3764 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3769 if uns_is_summary {
3770 inter_group.insert(all_groups.len(), uns_segments);
3771 } else {
3772 inter_group.insert(0, uns_segments);
3773 }
3774 } else {
3775 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3779 let tx_num = sg_num(transaction_group);
3780 let uns_pos = all_groups
3781 .iter()
3782 .rposition(|g| sg_num(&g.group_id) <= tx_num)
3783 .map(|i| i + 1)
3784 .unwrap_or(all_groups.len());
3785 inter_group.insert(uns_pos, uns_segments);
3786 }
3787 }
3788
3789 for (k, segs) in &mapped.inter_group_segments {
3797 if segs.is_empty() {
3798 continue;
3799 }
3800 let existing_tags: std::collections::HashSet<String> = inter_group
3801 .get(k)
3802 .map(|v| v.iter().map(|s| s.tag.clone()).collect())
3803 .unwrap_or_default();
3804 for seg in segs {
3805 if existing_tags.contains(&seg.tag) {
3806 continue;
3807 }
3808 inter_group.entry(*k).or_default().push(seg.clone());
3809 }
3810 }
3811
3812 let mut tree = AssembledTree {
3813 segments: root_segments,
3814 groups: all_groups,
3815 post_group_start: pre_group_count,
3816 inter_group_segments: inter_group,
3817 };
3818
3819 if let Some(mig) = filtered_mig {
3826 mig_assembly::repetition_order::sort_repetitions_by_mig_variant(
3827 &mut tree,
3828 mig,
3829 (!transaction_group.is_empty()).then_some(transaction_group),
3830 );
3831 }
3832 tree
3833 }
3834
3835 pub fn build_group_from_bo4e(
3837 &self,
3838 bo4e_value: &serde_json::Value,
3839 def: &MappingDefinition,
3840 ) -> AssembledGroup {
3841 let instance = self.map_reverse(bo4e_value, def);
3842 let leaf_group = def
3843 .meta
3844 .source_group
3845 .rsplit('.')
3846 .next()
3847 .unwrap_or(&def.meta.source_group);
3848
3849 AssembledGroup {
3850 group_id: leaf_group.to_string(),
3851 repetitions: vec![instance],
3852 }
3853 }
3854
3855 pub fn map_interchange_typed<M, T>(
3863 msg_engine: &MappingEngine,
3864 tx_engine: &MappingEngine,
3865 tree: &AssembledTree,
3866 tx_group: &str,
3867 enrich_codes: bool,
3868 nachrichtendaten: crate::model::Nachrichtendaten,
3869 interchangedaten: crate::model::Interchangedaten,
3870 ) -> Result<crate::model::Interchange<M, T>, serde_json::Error>
3871 where
3872 M: serde::de::DeserializeOwned,
3873 T: serde::de::DeserializeOwned,
3874 {
3875 let mapped = Self::map_interchange(msg_engine, tx_engine, tree, tx_group, enrich_codes);
3876 let nachricht = mapped.into_dynamic_nachricht(nachrichtendaten);
3877 let dynamic = crate::model::DynamicInterchange {
3878 interchangedaten,
3879 nachrichten: vec![nachricht],
3880 };
3881 let value = serde_json::to_value(&dynamic)?;
3882 serde_json::from_value(value)
3883 }
3884
3885 pub fn map_interchange_reverse_typed<M, T>(
3892 msg_engine: &MappingEngine,
3893 tx_engine: &MappingEngine,
3894 nachricht: &crate::model::Nachricht<M, T>,
3895 tx_group: &str,
3896 ) -> Result<AssembledTree, serde_json::Error>
3897 where
3898 M: serde::Serialize,
3899 T: serde::Serialize,
3900 {
3901 let mut stammdaten = serde_json::to_value(&nachricht.stammdaten)?;
3904 crate::model::restore_message_metadata(&mut stammdaten, &nachricht.nachrichtendaten);
3905 let transaktionen: Vec<crate::model::MappedTransaktion> = nachricht
3906 .transaktionen
3907 .iter()
3908 .map(|t| {
3909 Ok(crate::model::MappedTransaktion {
3910 stammdaten: serde_json::to_value(t)?,
3911 transaktionsdaten: serde_json::Value::Null,
3912 nesting_info: Default::default(),
3913 })
3914 })
3915 .collect::<Result<Vec<_>, serde_json::Error>>()?;
3916 let mapped = crate::model::MappedMessage {
3917 stammdaten,
3918 nachricht_meta: serde_json::Value::Null,
3919 transaktionen,
3920 nesting_info: Default::default(),
3921 inter_group_segments: Default::default(),
3922 };
3923 Ok(Self::map_interchange_reverse(
3924 msg_engine, tx_engine, &mapped, tx_group, None,
3925 ))
3926 }
3927}
3928
3929fn parse_source_path_part(part: &str) -> (&str, Option<&str>) {
3936 if let Some(pos) = part.find('_') {
3940 let group = &part[..pos];
3941 let qualifier = &part[pos + 1..];
3942 if !qualifier.is_empty() {
3943 return (group, Some(qualifier));
3944 }
3945 }
3946 (part, None)
3947}
3948
3949fn build_reverse_mig_group_order(mig: &MigSchema, tx_group_id: &str) -> HashMap<String, usize> {
3957 let mut order = HashMap::new();
3958 if let Some(tg) = mig.segment_groups.iter().find(|g| g.id == tx_group_id) {
3959 for (i, nested) in tg.nested_groups.iter().enumerate() {
3960 if let Some(ref vc) = nested.variant_code {
3962 let variant_key = format!("{}_{}", nested.id, vc.to_uppercase());
3963 order.insert(variant_key, i);
3964 }
3965 order.entry(nested.id.clone()).or_insert(i);
3967 }
3968 }
3969 order
3970}
3971
3972fn variant_mig_position(
3978 def: &MappingDefinition,
3979 base_group_id: &str,
3980 mig_order: &HashMap<String, usize>,
3981) -> usize {
3982 if let Some(ref sp) = def.meta.source_path {
3985 let base_lower = base_group_id.to_lowercase();
3987 for part in sp.split('.') {
3988 if part.starts_with(&base_lower)
3989 || part.starts_with(base_group_id.to_lowercase().as_str())
3990 {
3991 if let Some(underscore_pos) = part.find('_') {
3993 let qualifier = &part[underscore_pos + 1..];
3994 let variant_key = format!("{}_{}", base_group_id, qualifier.to_uppercase());
3995 if let Some(&pos) = mig_order.get(&variant_key) {
3996 return pos;
3997 }
3998 }
3999 }
4000 }
4001 }
4002 mig_order.get(base_group_id).copied().unwrap_or(usize::MAX)
4004}
4005
4006fn find_rep_by_entry_qualifier<'a>(
4011 reps: &'a [AssembledGroupInstance],
4012 qualifier: &str,
4013) -> Option<&'a AssembledGroupInstance> {
4014 let parts: Vec<&str> = qualifier.split('_').collect();
4016 reps.iter().find(|inst| {
4017 inst.segments.first().is_some_and(|seg| {
4018 seg.elements
4019 .first()
4020 .and_then(|e| e.first())
4021 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
4022 })
4023 })
4024}
4025
4026fn find_all_reps_by_entry_qualifier<'a>(
4028 reps: &'a [AssembledGroupInstance],
4029 qualifier: &str,
4030) -> Vec<&'a AssembledGroupInstance> {
4031 let parts: Vec<&str> = qualifier.split('_').collect();
4033 reps.iter()
4034 .filter(|inst| {
4035 inst.segments.first().is_some_and(|seg| {
4036 seg.elements
4037 .first()
4038 .and_then(|e| e.first())
4039 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
4040 })
4041 })
4042 .collect()
4043}
4044
4045fn has_source_path_qualifiers(source_path: &str) -> bool {
4047 source_path.split('.').any(|part| {
4048 if let Some(pos) = part.find('_') {
4049 pos < part.len() - 1
4050 } else {
4051 false
4052 }
4053 })
4054}
4055
4056fn parse_group_spec(part: &str) -> (&str, Option<usize>) {
4057 if let Some(colon_pos) = part.find(':') {
4058 let id = &part[..colon_pos];
4059 let rep = part[colon_pos + 1..].parse::<usize>().ok();
4060 (id, rep)
4061 } else {
4062 (part, None)
4063 }
4064}
4065
4066fn strip_tx_group_prefix(source_group: &str, tx_group: &str) -> String {
4072 if source_group == tx_group || source_group.is_empty() {
4073 String::new()
4074 } else if let Some(rest) = source_group.strip_prefix(tx_group) {
4075 rest.strip_prefix('.').unwrap_or(rest).to_string()
4076 } else {
4077 source_group.to_string()
4078 }
4079}
4080
4081fn place_in_groups(
4089 groups: &mut Vec<AssembledGroup>,
4090 relative_path: &str,
4091 instance: AssembledGroupInstance,
4092) -> usize {
4093 let parts: Vec<&str> = relative_path.split('.').collect();
4094
4095 if parts.len() == 1 {
4096 let (id, rep) = parse_group_spec(parts[0]);
4098
4099 let group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == id) {
4101 g
4102 } else {
4103 groups.push(AssembledGroup {
4104 group_id: id.to_string(),
4105 repetitions: vec![],
4106 });
4107 groups.last_mut().unwrap()
4108 };
4109
4110 if let Some(rep_idx) = rep {
4111 while group.repetitions.len() <= rep_idx {
4113 group.repetitions.push(AssembledGroupInstance {
4114 segments: vec![],
4115 child_groups: vec![],
4116 entry_mig_number: None,
4117 variant_mig_numbers: vec![],
4118 skipped_segments: Vec::new(),
4119 skipped_positions: Vec::new(),
4120 });
4121 }
4122 group.repetitions[rep_idx]
4123 .segments
4124 .extend(instance.segments);
4125 group.repetitions[rep_idx]
4126 .child_groups
4127 .extend(instance.child_groups);
4128 rep_idx
4129 } else {
4130 let pos = group.repetitions.len();
4132 group.repetitions.push(instance);
4133 pos
4134 }
4135 } else {
4136 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
4138 let rep_idx = parent_rep.unwrap_or(0);
4139
4140 let parent_group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == parent_id) {
4142 g
4143 } else {
4144 groups.push(AssembledGroup {
4145 group_id: parent_id.to_string(),
4146 repetitions: vec![],
4147 });
4148 groups.last_mut().unwrap()
4149 };
4150
4151 while parent_group.repetitions.len() <= rep_idx {
4153 parent_group.repetitions.push(AssembledGroupInstance {
4154 segments: vec![],
4155 child_groups: vec![],
4156 entry_mig_number: None,
4157 variant_mig_numbers: vec![],
4158 skipped_segments: Vec::new(),
4159 skipped_positions: Vec::new(),
4160 });
4161 }
4162
4163 let remaining = parts[1..].join(".");
4164 place_in_groups(
4165 &mut parent_group.repetitions[rep_idx].child_groups,
4166 &remaining,
4167 instance,
4168 );
4169 rep_idx
4170 }
4171}
4172
4173fn resolve_child_relative(
4185 relative: &str,
4186 source_path: Option<&str>,
4187 source_path_to_rep: &std::collections::HashMap<String, Vec<usize>>,
4188 item_idx: usize,
4189) -> String {
4190 let parts: Vec<&str> = relative.split('.').collect();
4191 if parts.is_empty() {
4192 return relative.to_string();
4193 }
4194
4195 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
4197 if parent_rep.is_some() {
4198 return relative.to_string();
4199 }
4200
4201 if let Some(sp) = source_path {
4203 if let Some((parent_path, _child)) = sp.rsplit_once('.') {
4204 if let Some(rep_indices) = source_path_to_rep.get(parent_path) {
4206 let rep_idx = rep_indices
4207 .get(item_idx)
4208 .or_else(|| rep_indices.last())
4209 .copied()
4210 .unwrap_or(0);
4211 let rest = parts[1..].join(".");
4212 return format!("{}:{}.{}", parent_id, rep_idx, rest);
4213 }
4214 let prefix = format!("{}_", parent_path);
4222 let mut unioned: Vec<usize> = source_path_to_rep
4223 .iter()
4224 .filter(|(k, _)| k.starts_with(&prefix))
4225 .flat_map(|(_, v)| v.iter().copied())
4226 .collect();
4227 if !unioned.is_empty() {
4228 unioned.sort_unstable();
4229 unioned.dedup();
4230 let rep_idx = unioned
4231 .get(item_idx)
4232 .or_else(|| unioned.last())
4233 .copied()
4234 .unwrap_or(0);
4235 let rest = parts[1..].join(".");
4236 return format!("{}:{}.{}", parent_id, rep_idx, rest);
4237 }
4238 }
4239 }
4240
4241 relative.to_string()
4243}
4244
4245struct DiscriminatorMatcher<'a> {
4252 tag: &'a str,
4253 element_idx: usize,
4254 component_idx: usize,
4255 expected_values: Vec<&'a str>,
4256 occurrence: Option<usize>,
4258}
4259
4260impl<'a> DiscriminatorMatcher<'a> {
4261 fn parse(disc: &'a str) -> Option<Self> {
4262 let (spec, expected) = disc.split_once('=')?;
4263 let parts: Vec<&str> = spec.split('.').collect();
4264 if parts.len() != 3 {
4265 return None;
4266 }
4267 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
4268 Some(Self {
4269 tag: parts[0],
4270 element_idx: parts[1].parse().ok()?,
4271 component_idx: parts[2].parse().ok()?,
4272 expected_values: expected_raw.split('|').collect(),
4273 occurrence,
4274 })
4275 }
4276
4277 fn matches(&self, instance: &AssembledGroupInstance) -> bool {
4278 instance.segments.iter().any(|s| {
4279 s.tag.eq_ignore_ascii_case(self.tag)
4280 && s.elements
4281 .get(self.element_idx)
4282 .and_then(|e| e.get(self.component_idx))
4283 .map(|v| self.expected_values.iter().any(|ev| v == ev))
4284 .unwrap_or(false)
4285 })
4286 }
4287
4288 fn filter_instances<'b>(
4290 &self,
4291 instances: Vec<&'b AssembledGroupInstance>,
4292 ) -> Vec<&'b AssembledGroupInstance> {
4293 let matching: Vec<_> = instances
4294 .into_iter()
4295 .filter(|inst| self.matches(inst))
4296 .collect();
4297 if let Some(occ) = self.occurrence {
4298 matching.into_iter().nth(occ).into_iter().collect()
4299 } else {
4300 matching
4301 }
4302 }
4303}
4304
4305fn parse_discriminator_occurrence(expected: &str) -> (&str, Option<usize>) {
4311 if let Some(hash_pos) = expected.rfind('#') {
4312 if let Ok(occ) = expected[hash_pos + 1..].parse::<usize>() {
4313 return (&expected[..hash_pos], Some(occ));
4314 }
4315 }
4316 (expected, None)
4317}
4318
4319fn strip_rep_index(relative: &str) -> String {
4323 let (id, _) = parse_group_spec(relative);
4324 id.to_string()
4325}
4326
4327pub(crate) fn strip_all_rep_indices(relative: &str) -> String {
4332 relative
4333 .split('.')
4334 .map(|part| {
4335 let (id, _) = parse_group_spec(part);
4336 id
4337 })
4338 .collect::<Vec<_>>()
4339 .join(".")
4340}
4341
4342pub fn is_nested_child_of(child: &MappingDefinition, parent: &MappingDefinition) -> bool {
4352 if child.meta.parent_field.is_none() || child.meta.entity != parent.meta.entity {
4353 return false;
4354 }
4355 let child_sg = strip_all_rep_indices(&child.meta.source_group);
4356 let parent_sg = strip_all_rep_indices(&parent.meta.source_group);
4357 match child_sg.rsplit_once('.') {
4358 Some((head, _)) if head.eq_ignore_ascii_case(&parent_sg) => {}
4359 _ => return false,
4360 }
4361 let (Some(child_sp), Some(parent_sp)) = (
4362 child.meta.source_path.as_deref(),
4363 parent.meta.source_path.as_deref(),
4364 ) else {
4365 return true;
4366 };
4367 let Some((child_parent_sp, _)) = child_sp.rsplit_once('.') else {
4368 return false;
4369 };
4370 let child_parts: Vec<&str> = child_parent_sp.split('.').collect();
4371 let parent_parts: Vec<&str> = parent_sp.split('.').collect();
4372 child_parts.len() == parent_parts.len()
4373 && child_parts.iter().zip(&parent_parts).all(|(c, p)| {
4374 let (c_id, c_q) = parse_source_path_part(c);
4375 let (p_id, p_q) = parse_source_path_part(p);
4376 c_id.eq_ignore_ascii_case(p_id)
4377 && match (c_q, p_q) {
4378 (Some(cq), Some(pq)) => cq.eq_ignore_ascii_case(pq),
4379 _ => true,
4380 }
4381 })
4382}
4383
4384pub fn is_bound_child_of(child: &MappingDefinition, parent: &MappingDefinition) -> bool {
4399 if std::ptr::eq(child, parent)
4400 || child.meta.parent_field.is_some()
4401 || parent.meta.parent_field.is_some()
4402 || child.meta.target_list.is_some()
4403 || parent.meta.target_list.is_some()
4404 || child.meta.entity != parent.meta.entity
4405 {
4406 return false;
4407 }
4408 let (Some(child_sp), Some(parent_sp)) = (
4409 child.meta.source_path.as_deref(),
4410 parent.meta.source_path.as_deref(),
4411 ) else {
4412 return false;
4413 };
4414 parent_sp.contains('.')
4415 && child_sp
4416 .rsplit_once('.')
4417 .is_some_and(|(head, _)| head.eq_ignore_ascii_case(parent_sp))
4418}
4419
4420pub fn is_bound_child(definitions: &[MappingDefinition], def: &MappingDefinition) -> bool {
4423 definitions.iter().any(|p| is_bound_child_of(def, p))
4424}
4425
4426fn nested_parent_qualifier(child: &MappingDefinition) -> Option<&str> {
4429 let (parent_path, _) = child.meta.source_path.as_deref()?.rsplit_once('.')?;
4430 let last = parent_path.rsplit('.').next()?;
4431 parse_source_path_part(last).1
4432}
4433
4434fn nested_child_leaf(child: &MappingDefinition) -> (String, Option<&str>) {
4437 let leaf_group = strip_all_rep_indices(
4438 child
4439 .meta
4440 .source_group
4441 .rsplit('.')
4442 .next()
4443 .unwrap_or(&child.meta.source_group),
4444 );
4445 let leaf_qualifier = child
4446 .meta
4447 .source_path
4448 .as_deref()
4449 .and_then(|sp| sp.rsplit('.').next())
4450 .and_then(|part| parse_source_path_part(part).1);
4451 (leaf_group, leaf_qualifier)
4452}
4453
4454fn bound_child_reps<'i>(
4458 instance: &'i AssembledGroupInstance,
4459 child: &MappingDefinition,
4460) -> Vec<&'i AssembledGroupInstance> {
4461 let (leaf_id, leaf_qualifier) = nested_child_leaf(child);
4462 let Some(group) = instance
4463 .child_groups
4464 .iter()
4465 .find(|g| g.group_id.eq_ignore_ascii_case(&leaf_id))
4466 else {
4467 return Vec::new();
4468 };
4469 let reps: Vec<&AssembledGroupInstance> = match leaf_qualifier {
4470 Some(q) => find_all_reps_by_entry_qualifier(&group.repetitions, q),
4471 None => group.repetitions.iter().collect(),
4472 };
4473 match child
4474 .meta
4475 .discriminator
4476 .as_deref()
4477 .and_then(DiscriminatorMatcher::parse)
4478 {
4479 Some(matcher) => matcher.filter_instances(reps),
4480 None => reps,
4481 }
4482}
4483
4484fn push_child_reps(
4486 instance: &mut AssembledGroupInstance,
4487 leaf_id: String,
4488 reps: Vec<AssembledGroupInstance>,
4489) {
4490 match instance
4491 .child_groups
4492 .iter_mut()
4493 .find(|g| g.group_id.eq_ignore_ascii_case(&leaf_id))
4494 {
4495 Some(group) => group.repetitions.extend(reps),
4496 None => instance.child_groups.push(AssembledGroup {
4497 group_id: leaf_id,
4498 repetitions: reps,
4499 }),
4500 }
4501}
4502
4503fn entry_qualifier_matches(instance: &AssembledGroupInstance, qualifier: &str) -> bool {
4506 segment_qualifier_matches(instance.segments.first(), qualifier)
4507}
4508
4509fn rebuilt_entry_qualifier_matches(
4514 instance: &AssembledGroupInstance,
4515 def: &MappingDefinition,
4516 qualifier: &str,
4517) -> bool {
4518 let entry_tag = def
4519 .meta
4520 .discriminator
4521 .as_deref()
4522 .and_then(|d| d.split('.').next())
4523 .filter(|tag| !tag.is_empty());
4524 let entry = match entry_tag {
4525 Some(tag) => instance
4526 .segments
4527 .iter()
4528 .find(|s| s.tag.eq_ignore_ascii_case(tag)),
4529 None => instance.segments.first(),
4530 };
4531 segment_qualifier_matches(entry, qualifier)
4532}
4533
4534fn segment_qualifier_matches(segment: Option<&AssembledSegment>, qualifier: &str) -> bool {
4535 segment
4536 .and_then(|seg| seg.elements.first())
4537 .and_then(|e| e.first())
4538 .is_some_and(|v| qualifier.split('_').any(|q| v.eq_ignore_ascii_case(q)))
4539}
4540
4541fn field_is_filled(bo4e_value: &serde_json::Value, field: &str) -> bool {
4547 let mut current = bo4e_value;
4548 for part in field.split('.') {
4549 match current.get(part) {
4550 Some(v) => current = v,
4551 None => return false,
4552 }
4553 }
4554 match current {
4555 serde_json::Value::String(s) => !s.is_empty(),
4556 serde_json::Value::Array(a) => !a.is_empty(),
4557 serde_json::Value::Object(o) => !o.is_empty(),
4558 serde_json::Value::Number(_) | serde_json::Value::Bool(_) => true,
4559 serde_json::Value::Null => false,
4560 }
4561}
4562
4563pub(crate) fn list_target(target: &str) -> Option<(&str, &str)> {
4566 let (list, sub) = target.split_once("[].")?;
4567 (!list.is_empty() && !sub.is_empty()).then_some((list, sub))
4568}
4569
4570pub(crate) fn parse_tag_qualifier(tag_part: &str) -> (String, Option<&str>, usize) {
4577 if let Some(bracket_start) = tag_part.find('[') {
4578 let tag = tag_part[..bracket_start].to_uppercase();
4579 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
4580 if let Some(comma_pos) = inner.find(',') {
4581 let qualifier = &inner[..comma_pos];
4582 let index = inner[comma_pos + 1..].parse::<usize>().unwrap_or(0);
4583 if qualifier == "*" {
4585 (tag, None, index)
4586 } else {
4587 (tag, Some(qualifier), index)
4588 }
4589 } else {
4590 (tag, Some(inner), 0)
4591 }
4592 } else {
4593 (tag_part.to_uppercase(), None, 0)
4594 }
4595}
4596
4597pub fn deep_merge_insert(
4603 result: &mut serde_json::Map<String, serde_json::Value>,
4604 entity: &str,
4605 bo4e: serde_json::Value,
4606) {
4607 merge_entity(result, entity, bo4e, false);
4608}
4609
4610fn merge_entity(
4622 result: &mut serde_json::Map<String, serde_json::Value>,
4623 entity: &str,
4624 bo4e: serde_json::Value,
4625 keep_both: bool,
4626) {
4627 if let Some(existing) = result.get_mut(entity) {
4628 if let (Some(existing_arr), Some(new_arr)) =
4631 (existing.as_array().map(|a| a.len()), bo4e.as_array())
4632 {
4633 if existing_arr == new_arr.len() {
4634 let existing_arr = existing.as_array_mut().unwrap();
4635 for (existing_elem, new_elem) in existing_arr.iter_mut().zip(new_arr) {
4636 if let (Some(existing_map), Some(new_map)) =
4637 (existing_elem.as_object_mut(), new_elem.as_object())
4638 {
4639 for (k, v) in new_map {
4640 if let Some(existing_v) = existing_map.get_mut(k) {
4641 if let (Some(existing_inner), Some(new_inner)) =
4642 (existing_v.as_object_mut(), v.as_object())
4643 {
4644 for (ik, iv) in new_inner {
4645 existing_inner
4646 .entry(ik.clone())
4647 .or_insert_with(|| iv.clone());
4648 }
4649 }
4650 } else {
4651 existing_map.insert(k.clone(), v.clone());
4652 }
4653 }
4654 }
4655 }
4656 return;
4657 }
4658 }
4659 if let (Some(existing_map), serde_json::Value::Object(new_map)) =
4661 (existing.as_object_mut(), &bo4e)
4662 {
4663 for (k, v) in new_map {
4664 if let Some(existing_v) = existing_map.get_mut(k) {
4665 if let (Some(existing_inner), Some(new_inner)) =
4667 (existing_v.as_object_mut(), v.as_object())
4668 {
4669 for (ik, iv) in new_inner {
4670 existing_inner
4671 .entry(ik.clone())
4672 .or_insert_with(|| iv.clone());
4673 }
4674 }
4675 } else {
4677 existing_map.insert(k.clone(), v.clone());
4678 }
4679 }
4680 return;
4681 }
4682 if !keep_both {
4683 result.insert(entity.to_string(), bo4e);
4684 return;
4685 }
4686 let existing_items = match std::mem::take(existing) {
4688 serde_json::Value::Array(items) => items,
4689 other => vec![other],
4690 };
4691 let new_items = match bo4e {
4692 serde_json::Value::Array(items) => items,
4693 other => vec![other],
4694 };
4695 *existing = serde_json::Value::Array(existing_items.into_iter().chain(new_items).collect());
4696 return;
4697 }
4698 result.insert(entity.to_string(), bo4e);
4699}
4700
4701fn append_to_list_field(
4714 result: &mut serde_json::Map<String, serde_json::Value>,
4715 entity: &str,
4716 list_field: &str,
4717 bo4e: serde_json::Value,
4718) {
4719 let mut items = match bo4e {
4720 serde_json::Value::Array(a) => a,
4721 other => vec![other],
4722 };
4723 items.retain(|v| !v.as_object().is_some_and(|o| o.is_empty()));
4724 if items.is_empty() {
4725 return;
4726 }
4727 let entry = result
4728 .entry(entity.to_string())
4729 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
4730 let Some(obj) = entry.as_object_mut() else {
4734 return;
4735 };
4736 match obj.get_mut(list_field).and_then(|v| v.as_array_mut()) {
4737 Some(existing) => existing.extend(items),
4738 None => {
4739 obj.insert(list_field.to_string(), serde_json::Value::Array(items));
4740 }
4741 }
4742}
4743
4744fn is_map_keyed_object(value: &serde_json::Value) -> bool {
4755 let Some(obj) = value.as_object() else {
4756 return false;
4757 };
4758 if obj.is_empty() {
4759 return false;
4760 }
4761 obj.iter().all(|(k, v)| {
4763 k.len() <= 5
4764 && k.chars()
4765 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
4766 && v.is_object()
4767 })
4768}
4769
4770fn find_qualifier_companion_field(
4779 definitions: &[crate::definition::MappingDefinition],
4780 entity: &str,
4781) -> Option<String> {
4782 for def in definitions {
4783 if def.meta.entity != *entity || def.meta.parent_field.is_some() {
4784 continue;
4785 }
4786 let disc = def.meta.discriminator.as_deref()?;
4787 let (disc_path, _) = disc.split_once('=')?;
4788 let disc_path_lower = disc_path.to_lowercase();
4789
4790 for (path, mapping) in &def.fields {
4793 let cf_path = path.to_lowercase();
4794 let matches = cf_path == disc_path_lower || format!("{}.0", cf_path) == disc_path_lower;
4795 if matches {
4796 let target = match mapping {
4797 FieldMapping::Simple(t) => t.as_str(),
4798 FieldMapping::Structured(s) => s.target.as_str(),
4799 FieldMapping::Nested(_) => continue,
4800 };
4801 if !target.is_empty() {
4802 return Some(target.to_string());
4803 }
4804 }
4805 }
4806 }
4807 None
4808}
4809
4810fn extract_child_from_parent(
4819 entities: &serde_json::Value,
4820 definitions: &[MappingDefinition],
4821 child_def: &MappingDefinition,
4822) -> Option<serde_json::Value> {
4823 extract_child_from_parent_with_indices(entities, definitions, child_def).map(|(v, _)| v)
4824}
4825
4826fn extract_child_from_parent_with_indices(
4831 entities: &serde_json::Value,
4832 definitions: &[MappingDefinition],
4833 child_def: &MappingDefinition,
4834) -> Option<(serde_json::Value, Vec<usize>)> {
4835 let parts: Vec<&str> = child_def.meta.source_group.split('.').collect();
4836 if parts.len() < 2 {
4837 return None;
4838 }
4839 let parent_group = parts[0];
4840 let parent_def = definitions
4841 .iter()
4842 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_def.meta.entity)?;
4843 let parent_key = to_camel_case(&parent_def.meta.entity);
4844 let child_key = to_camel_case(&child_def.meta.entity);
4845 let parent_value = entities.get(&parent_key)?;
4846
4847 if let Some(parent_map) = parent_value.as_object() {
4849 if is_map_keyed_value(parent_map) {
4850 let mut children: Vec<serde_json::Value> = Vec::new();
4851 let mut indices: Vec<usize> = Vec::new();
4852 for (i, (_key, inner)) in parent_map.iter().enumerate() {
4853 if let Some(child) = inner.get(&child_key) {
4854 if !child.is_null() {
4855 children.push(child.clone());
4856 indices.push(i);
4857 }
4858 }
4859 }
4860 return match children.len() {
4861 0 => None,
4862 1 => Some((children.into_iter().next().unwrap(), indices)),
4863 _ => Some((serde_json::Value::Array(children), indices)),
4864 };
4865 }
4866 }
4867
4868 if let Some(parent_arr) = parent_value.as_array() {
4870 let mut children: Vec<serde_json::Value> = Vec::new();
4871 let mut indices: Vec<usize> = Vec::new();
4872 for (i, item) in parent_arr.iter().enumerate() {
4873 if let Some(child) = item.get(&child_key) {
4874 if !child.is_null() {
4875 children.push(child.clone());
4876 indices.push(i);
4877 }
4878 }
4879 }
4880 return match children.len() {
4881 0 => None,
4882 1 => Some((children.into_iter().next().unwrap(), indices)),
4883 _ => Some((serde_json::Value::Array(children), indices)),
4884 };
4885 }
4886
4887 let child = parent_value.get(&child_key)?;
4889 if child.is_null() {
4890 return None;
4891 }
4892 Some((child.clone(), vec![0]))
4893}
4894
4895fn nest_child_entities_in_result(
4901 result: &mut serde_json::Map<String, serde_json::Value>,
4902 definitions: &[MappingDefinition],
4903 nesting_info: &std::collections::HashMap<String, Vec<usize>>,
4904 transaction_group: Option<&str>,
4905) {
4906 let nesting_pairs = child_entity_nesting_pairs(definitions, transaction_group);
4907
4908 for (_parent_group, parent_entity, child_entity, child_source_path) in nesting_pairs {
4909 let parent_key = to_camel_case(&parent_entity);
4910 let child_key = to_camel_case(&child_entity);
4911
4912 let child_value = match result.remove(&child_key) {
4914 Some(v) => v,
4915 None => continue,
4916 };
4917
4918 let Some(parent_value) = result.get_mut(&parent_key) else {
4923 result.insert(child_key, child_value);
4925 continue;
4926 };
4927 if parent_value.is_array() {
4928 result.insert(child_key, child_value);
4929 continue;
4930 }
4931
4932 let distribution = child_source_path
4934 .as_deref()
4935 .and_then(|sp| nesting_info.get(sp));
4936
4937 let child_items: Vec<(usize, &serde_json::Value)> = match &child_value {
4939 serde_json::Value::Array(arr) => arr.iter().enumerate().collect(),
4940 other => vec![(0, other)],
4941 };
4942
4943 let insert_or_append = |obj: &mut serde_json::Map<String, serde_json::Value>,
4946 key: &str,
4947 val: &serde_json::Value| {
4948 match obj.get_mut(key) {
4949 Some(existing) => {
4950 if !existing.is_array() {
4952 let prev = existing.take();
4953 *existing = serde_json::Value::Array(vec![prev]);
4954 }
4955 if let Some(arr) = existing.as_array_mut() {
4956 arr.push(val.clone());
4957 }
4958 }
4959 None => {
4960 obj.insert(key.to_string(), val.clone());
4961 }
4962 }
4963 };
4964
4965 if let Some(parent_map) = parent_value.as_object_mut() {
4967 if is_map_keyed_value(parent_map) {
4968 let keys: Vec<String> = parent_map.keys().cloned().collect();
4970 for (i, child_item) in &child_items {
4971 let target_idx = distribution
4972 .and_then(|dist| dist.get(*i))
4973 .copied()
4974 .unwrap_or(0);
4975 if let Some(key) = keys.get(target_idx) {
4976 if let Some(inner) = parent_map.get_mut(key).and_then(|v| v.as_object_mut())
4977 {
4978 insert_or_append(inner, &child_key, child_item);
4979 }
4980 }
4981 }
4982 continue;
4983 }
4984 }
4985
4986 if let Some(parent_arr) = parent_value.as_array_mut() {
4988 for (i, child_item) in &child_items {
4989 let target_idx = distribution
4990 .and_then(|dist| dist.get(*i))
4991 .copied()
4992 .unwrap_or(0);
4993 if let Some(parent_obj) = parent_arr
4994 .get_mut(target_idx)
4995 .and_then(|v| v.as_object_mut())
4996 {
4997 insert_or_append(parent_obj, &child_key, child_item);
4998 }
4999 }
5000 continue;
5001 }
5002
5003 if let Some(parent_obj) = parent_value.as_object_mut() {
5005 for (_i, child_item) in &child_items {
5006 insert_or_append(parent_obj, &child_key, child_item);
5007 }
5008 continue;
5009 }
5010
5011 result.insert(child_key, child_value);
5013 }
5014}
5015
5016pub(crate) fn child_entity_nesting_pairs(
5026 definitions: &[MappingDefinition],
5027 transaction_group: Option<&str>,
5028) -> Vec<(String, String, String, Option<String>)> {
5029 let mut nesting_pairs: Vec<(String, String, String, Option<String>)> = Vec::new();
5032 for def in definitions {
5033 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
5034 if parts.len() < 2 || def.meta.parent_field.is_some() {
5035 continue;
5036 }
5037 let parent_group = parts[0];
5038 if transaction_group.is_some_and(|tx| tx == parent_group) {
5044 continue;
5045 }
5046 let child_entity = def.meta.entity.clone();
5047 let child_has_parent_level_def = definitions
5051 .iter()
5052 .any(|d| d.meta.source_group == parent_group && d.meta.entity == child_entity);
5053 if child_has_parent_level_def {
5054 continue;
5055 }
5056 let parent_entity = definitions
5058 .iter()
5059 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_entity)
5060 .map(|d| d.meta.entity.clone());
5061 if let Some(ref parent_entity) = parent_entity {
5062 let child_key_lc = to_camel_case(&child_entity);
5067 let parent_defs: Vec<_> = definitions
5068 .iter()
5069 .filter(|d| d.meta.entity == *parent_entity)
5070 .collect();
5071 let has_conflicting_field = parent_defs.iter().any(|pd| {
5072 pd.fields.values().any(|fm| {
5073 let target = match fm {
5074 crate::definition::FieldMapping::Simple(t) => t.as_str(),
5075 crate::definition::FieldMapping::Structured(s) => s.target.as_str(),
5076 crate::definition::FieldMapping::Nested(_) => "",
5077 };
5078 target.starts_with(&child_key_lc)
5079 && target.get(child_key_lc.len()..child_key_lc.len() + 1) == Some(".")
5080 })
5081 });
5082 if has_conflicting_field {
5083 continue;
5084 }
5085 if nesting_pairs
5087 .iter()
5088 .any(|(_, pe, ce, _)| *pe == *parent_entity && *ce == child_entity)
5089 {
5090 continue;
5091 }
5092 nesting_pairs.push((
5093 parent_group.to_string(),
5094 parent_entity.clone(),
5095 child_entity,
5096 def.meta.source_path.clone(),
5097 ));
5098 }
5099 }
5100
5101 nesting_pairs
5102}
5103
5104fn is_map_keyed_value(map: &serde_json::Map<String, serde_json::Value>) -> bool {
5106 if map.is_empty() {
5107 return false;
5108 }
5109 map.values().all(|v| v.is_object())
5110 && map.keys().all(|k| {
5111 k.len() <= 5
5112 || k.chars()
5113 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
5114 })
5115}
5116
5117#[derive(Clone)]
5120struct CodeSite<'a> {
5121 target: &'a str,
5122 parent_field: Option<&'a str>,
5128 source_path: &'a str,
5129 seg_tag: String,
5130 path_qualifier: Option<String>,
5132 disc_qualifier: Option<String>,
5134 element_idx: usize,
5135 component_idx: usize,
5136 enum_map: Option<&'a std::collections::BTreeMap<String, String>>,
5137 also_target: Option<&'a str>,
5138 also_enum_map: Option<&'a std::collections::BTreeMap<String, String>>,
5139}
5140
5141pub(crate) fn to_camel_case(name: &str) -> String {
5142 let mut chars = name.chars();
5143 match chars.next() {
5144 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
5145 None => String::new(),
5146 }
5147}
5148
5149fn set_nested_value(map: &mut serde_json::Map<String, serde_json::Value>, path: &str, val: String) {
5152 set_nested_value_json(map, path, serde_json::Value::String(val));
5153}
5154
5155fn set_nested_value_json(
5157 map: &mut serde_json::Map<String, serde_json::Value>,
5158 path: &str,
5159 val: serde_json::Value,
5160) {
5161 if let Some((prefix, leaf)) = path.rsplit_once('.') {
5162 let mut current = map;
5163 for part in prefix.split('.') {
5164 let entry = current
5165 .entry(part.to_string())
5166 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
5167 current = entry.as_object_mut().expect("expected object in path");
5168 }
5169 current.insert(leaf.to_string(), val);
5170 } else {
5171 map.insert(path.to_string(), val);
5172 }
5173}
5174
5175#[derive(serde::Serialize, serde::Deserialize)]
5180pub struct VariantCache {
5181 pub message_defs: Vec<MappingDefinition>,
5183 pub transaction_defs: BTreeMap<String, Vec<MappingDefinition>>,
5185 pub combined_defs: BTreeMap<String, Vec<MappingDefinition>>,
5187 #[serde(default)]
5189 pub code_lookups: BTreeMap<String, crate::code_lookup::CodeLookup>,
5190 #[serde(default)]
5192 pub mig_schema: Option<mig_types::schema::mig::MigSchema>,
5193 #[serde(default)]
5195 pub segment_structure: Option<crate::segment_structure::SegmentStructure>,
5196 #[serde(skip)]
5201 pub code_lists: std::sync::Arc<crate::code_lists::CodeLists>,
5202 #[serde(default)]
5205 pub pid_segment_numbers: BTreeMap<String, Vec<String>>,
5206 #[serde(default)]
5209 pub pid_requirements: BTreeMap<String, crate::pid_requirements::PidRequirements>,
5210 #[serde(default)]
5215 pub pid_ahb_workflows: BTreeMap<String, ahb_types::AhbWorkflow>,
5216 #[serde(default)]
5220 pub tx_groups: BTreeMap<String, String>,
5221}
5222
5223impl VariantCache {
5224 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
5226 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
5227 path: path.display().to_string(),
5228 message: e.to_string(),
5229 })?;
5230 if let Some(parent) = path.parent() {
5231 std::fs::create_dir_all(parent)?;
5232 }
5233 std::fs::write(path, encoded)?;
5234 Ok(())
5235 }
5236
5237 pub fn load(path: &Path) -> Result<Self, MappingError> {
5239 let bytes = std::fs::read(path)?;
5240 let mut cache: Self =
5241 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
5242 path: path.display().to_string(),
5243 message: e.to_string(),
5244 })?;
5245 cache.code_lists = crate::code_lists::CodeLists::discover(path);
5246 Ok(cache)
5247 }
5248
5249 pub fn tx_group(&self, pid: &str) -> Option<&str> {
5253 self.tx_groups
5254 .get(&format!("pid_{pid}"))
5255 .map(|s| s.as_str())
5256 }
5257
5258 pub fn msg_engine(&self, pid: &str) -> MappingEngine {
5262 let mut eng = MappingEngine::from_definitions_with_code_lists(
5263 std::sync::Arc::clone(&self.code_lists),
5264 self.message_defs.clone(),
5265 )
5266 .with_pid(pid);
5267 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
5268 eng = eng.with_code_lookup(cl.clone());
5269 }
5270 eng
5271 }
5272
5273 pub fn tx_engine(&self, pid: &str) -> Option<MappingEngine> {
5277 self.transaction_defs
5278 .get(&format!("pid_{pid}"))
5279 .map(|defs| {
5280 let mut eng = MappingEngine::from_definitions_with_code_lists(
5281 std::sync::Arc::clone(&self.code_lists),
5282 defs.clone(),
5283 )
5284 .with_pid(pid);
5285 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
5286 eng = eng.with_code_lookup(cl.clone());
5287 }
5288 eng
5289 })
5290 }
5291
5292 pub fn filtered_mig(&self, pid: &str) -> Option<mig_types::schema::mig::MigSchema> {
5300 let mig = self.mig_schema.as_ref()?;
5301 let numbers = self
5302 .pid_segment_numbers
5303 .get(&format!("pid_{pid}"))
5304 .or_else(|| self.pid_segment_numbers.get("pid_"))?;
5305 let number_set: std::collections::HashSet<String> = numbers.iter().cloned().collect();
5306 Some(mig_assembly::pid_filter::filter_mig_for_pid(
5307 mig,
5308 &number_set,
5309 ))
5310 }
5311
5312 pub fn pid_mig_unmerged(&self, pid: &str) -> Option<mig_types::schema::mig::MigSchema> {
5315 let mig = self.mig_schema.as_ref()?;
5316 let numbers = self
5317 .pid_segment_numbers
5318 .get(&format!("pid_{pid}"))
5319 .or_else(|| self.pid_segment_numbers.get("pid_"))?;
5320 let number_set: std::collections::HashSet<String> = numbers.iter().cloned().collect();
5321 Some(mig_assembly::pid_filter::filter_mig_for_pid_unmerged(
5322 mig,
5323 &number_set,
5324 ))
5325 }
5326}
5327
5328#[derive(serde::Serialize, serde::Deserialize)]
5333pub struct DataBundle {
5334 pub format_version: String,
5335 pub bundle_version: u32,
5336 pub variants: BTreeMap<String, VariantCache>,
5337 #[serde(default)]
5342 pub bo4e_catalog: crate::bo4e_catalog::Bo4eCatalog,
5343
5344 #[serde(default, skip_serializing_if = "Option::is_none")]
5356 pub built_by: Option<String>,
5357 #[serde(default)]
5369 pub code_lists: crate::code_lists::CodeLists,
5370}
5371
5372impl DataBundle {
5373 pub const CURRENT_VERSION: u32 = 2;
5374
5375 pub const PRODUCING_VERSION: &'static str = env!("CARGO_PKG_VERSION");
5385
5386 pub fn variant(&self, name: &str) -> Option<&VariantCache> {
5387 self.variants.get(name)
5388 }
5389
5390 pub fn write_to<W: std::io::Write>(&self, writer: &mut W) -> Result<(), MappingError> {
5391 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
5392 path: "<stream>".to_string(),
5393 message: e.to_string(),
5394 })?;
5395 writer.write_all(&encoded).map_err(MappingError::Io)
5396 }
5397
5398 pub fn read_from<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
5399 let mut bytes = Vec::new();
5400 reader.read_to_end(&mut bytes).map_err(MappingError::Io)?;
5401 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
5402 path: "<stream>".to_string(),
5403 message: e.to_string(),
5404 })
5405 }
5406
5407 pub fn read_from_checked<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
5408 let mut bundle = Self::read_from(reader)?;
5409 let shared = std::sync::Arc::new(std::mem::take(&mut bundle.code_lists));
5412 for variant in bundle.variants.values_mut() {
5413 variant.code_lists = std::sync::Arc::clone(&shared);
5414 }
5415 bundle.code_lists = (*shared).clone();
5416 if bundle.bundle_version != Self::CURRENT_VERSION {
5417 return Err(MappingError::CacheRead {
5418 path: "<stream>".to_string(),
5419 message: format!(
5420 "Incompatible bundle version {}, expected version {}. \
5421 Run `edifact-data update` to fetch compatible bundles.",
5422 bundle.bundle_version,
5423 Self::CURRENT_VERSION
5424 ),
5425 });
5426 }
5427 Ok(bundle)
5428 }
5429
5430 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
5431 if let Some(parent) = path.parent() {
5432 std::fs::create_dir_all(parent)?;
5433 }
5434 let mut file = std::fs::File::create(path).map_err(MappingError::Io)?;
5435 self.write_to(&mut file)
5436 }
5437
5438 pub fn load(path: &Path) -> Result<Self, MappingError> {
5439 let mut file = std::fs::File::open(path).map_err(MappingError::Io)?;
5440 Self::read_from_checked(&mut file)
5441 }
5442}
5443
5444#[cfg(test)]
5445mod variant_cache_helper_tests {
5446 use super::*;
5447
5448 fn make_test_cache() -> VariantCache {
5449 let mut tx_groups = BTreeMap::new();
5450 tx_groups.insert("pid_55001".to_string(), "SG4".to_string());
5451 tx_groups.insert("pid_21007".to_string(), "SG14".to_string());
5452
5453 let mut transaction_defs = BTreeMap::new();
5454 transaction_defs.insert("pid_55001".to_string(), vec![]);
5455 transaction_defs.insert("pid_21007".to_string(), vec![]);
5456
5457 VariantCache {
5458 code_lists: Default::default(),
5459 message_defs: vec![],
5460 transaction_defs,
5461 combined_defs: BTreeMap::new(),
5462 code_lookups: BTreeMap::new(),
5463 mig_schema: None,
5464 segment_structure: None,
5465 pid_segment_numbers: BTreeMap::new(),
5466 pid_requirements: BTreeMap::new(),
5467 pid_ahb_workflows: BTreeMap::new(),
5468 tx_groups,
5469 }
5470 }
5471
5472 #[test]
5473 fn test_tx_group_returns_correct_group() {
5474 let vc = make_test_cache();
5475 assert_eq!(vc.tx_group("55001").unwrap(), "SG4");
5476 assert_eq!(vc.tx_group("21007").unwrap(), "SG14");
5477 }
5478
5479 #[test]
5480 fn test_tx_group_unknown_pid_returns_none() {
5481 let vc = make_test_cache();
5482 assert!(vc.tx_group("99999").is_none());
5483 }
5484
5485 #[test]
5486 fn test_msg_engine_returns_engine() {
5487 let vc = make_test_cache();
5488 let engine = vc.msg_engine("55001");
5489 assert_eq!(engine.definitions().len(), 0);
5490 }
5491
5492 #[test]
5493 fn test_tx_engine_returns_engine_for_known_pid() {
5494 let vc = make_test_cache();
5495 assert!(vc.tx_engine("55001").is_some());
5496 }
5497
5498 #[test]
5499 fn test_tx_engine_returns_none_for_unknown_pid() {
5500 let vc = make_test_cache();
5501 assert!(vc.tx_engine("99999").is_none());
5502 }
5503
5504 fn make_populated_cache() -> VariantCache {
5508 let pids: Vec<String> = (0..40).map(|i| format!("pid_{}", 55000 + i * 7)).collect();
5509 let schema: serde_json::Value = serde_json::from_str(include_str!(
5510 "../../mig-types/src/generated/fv2504/utilmd/pids/pid_55001_schema.json"
5511 ))
5512 .unwrap();
5513 let code_lookup = crate::code_lookup::CodeLookup::from_schema_value(&schema);
5514 let element_counts: serde_json::Map<String, serde_json::Value> = (0..40)
5515 .map(|i| (format!("T{i:02}"), serde_json::json!(i)))
5516 .collect();
5517 let segment_structure: SegmentStructure =
5518 serde_json::from_value(serde_json::json!({ "element_counts": element_counts }))
5519 .unwrap();
5520 let ubs: serde_json::Map<String, serde_json::Value> = (0..40)
5521 .map(|i| (format!("UB{i}"), serde_json::json!({ "Ref": i })))
5522 .collect();
5523 let workflow: ahb_types::AhbWorkflow = serde_json::from_value(serde_json::json!({
5524 "pruefidentifikator": "55001",
5525 "description": "",
5526 "communication_direction": null,
5527 "fields": [],
5528 "ub_definitions": ubs,
5529 }))
5530 .unwrap();
5531
5532 let mut vc = make_test_cache();
5533 vc.segment_structure = Some(segment_structure);
5534 for pid in &pids {
5535 vc.transaction_defs.insert(pid.clone(), vec![]);
5536 vc.combined_defs.insert(pid.clone(), vec![]);
5537 vc.code_lookups.insert(pid.clone(), code_lookup.clone());
5538 vc.pid_segment_numbers
5539 .insert(pid.clone(), vec!["00001".to_string()]);
5540 vc.pid_ahb_workflows.insert(pid.clone(), workflow.clone());
5541 vc.tx_groups.insert(pid.clone(), "SG4".to_string());
5542 }
5543 vc
5544 }
5545
5546 #[test]
5549 fn test_enrichment_uses_codes_of_the_path_qualifier_variant() {
5550 let comp = |sub: u64, id: &str, codes: Option<serde_json::Value>| match codes {
5551 Some(c) => serde_json::json!({"sub_index": sub, "id": id, "type": "code", "codes": c}),
5552 None => serde_json::json!({"sub_index": sub, "id": id, "type": "data"}),
5553 };
5554 let code = |v: &str, n: &str| serde_json::json!([{"value": v, "name": n}]);
5555 let seg = |tag: &str, composite: &str, comps: Vec<serde_json::Value>| serde_json::json!({"id": tag, "elements": [{"index": 0, "composite": composite, "components": comps}]});
5556 let schema = serde_json::json!({"fields": {"sg15": {"segments": [
5557 seg("RFF", "C506", vec![comp(0, "1153", Some(code("Z13", "PID"))), comp(1, "1154", Some(code("21037", "RD / NB-Bewertung")))]),
5558 seg("RFF", "C506", vec![comp(0, "1153", Some(code("ACW", "Referenz"))), comp(1, "1154", None)]),
5559 seg("CAV", "C889", vec![comp(0, "7111", Some(code("Z91", "Z91"))), comp(1, "7110", Some(code("A", "Alpha")))]),
5560 seg("CAV", "C889", vec![comp(0, "7111", Some(code("ZF0", "ZF0"))), comp(1, "7110", Some(code("C", "Gamma")))]),
5561 ]}}});
5562 let engine = MappingEngine::new_empty()
5563 .with_code_lookup(crate::code_lookup::CodeLookup::from_schema_value(&schema));
5564 let def = MappingDefinition::from_toml_str(
5565 r#"
5566[meta]
5567entity = "Status"
5568bo4e_type = "Status"
5569source_group = "SG15"
5570source_path = "sg15"
5571discriminator = "RFF.0.0=Z13"
5572
5573[fields]
5574"rff.0.1" = "pruefidentifikator"
5575"rff[ACW].0.1" = "referenz"
5576"cav[Z91].0.1" = "z91Wert"
5577"cav[ZF0].0.1" = "zf0Wert"
5578"#,
5579 )
5580 .unwrap();
5581 let segment = |tag: &str, elements: &[&[&str]]| OwnedSegment {
5582 id: tag.to_string(),
5583 elements: elements
5584 .iter()
5585 .map(|e| e.iter().map(|c| c.to_string()).collect())
5586 .collect(),
5587 segment_number: 1,
5588 };
5589 let json = engine.map_forward_from_segments(
5590 &[
5591 segment("RFF", &[&["Z13", "21037"]]),
5592 segment("RFF", &[&["ACW", "REF-1"]]),
5593 segment("CAV", &[&["Z91", "C"]]),
5594 segment("CAV", &[&["ZF0", "C"]]),
5595 ],
5596 &def,
5597 );
5598 assert_eq!(
5599 json["referenz"],
5600 serde_json::json!("REF-1"),
5601 "RFF+ACW d1154 is data; RFF+Z13's codes must not apply: {json}"
5602 );
5603 assert_eq!(json["pruefidentifikator"]["meaning"], "RD / NB-Bewertung");
5604 assert_eq!(
5605 json["z91Wert"]["meaning"],
5606 serde_json::Value::Null,
5607 "'C' is a CAV+ZF0 code, unknown to CAV+Z91: {json}"
5608 );
5609 assert_eq!(json["zf0Wert"]["meaning"], "Gamma");
5610 }
5611
5612 #[test]
5616 fn list_target_reads_and_writes_every_repetition_in_order() {
5617 let engine = MappingEngine::new_empty();
5618 let def = MappingDefinition::from_toml_str(
5619 r#"
5620[meta]
5621entity = "Zuordnung"
5622bo4e_type = "Zuordnung"
5623source_group = "SG10"
5624source_path = "sg10"
5625
5626[fields]
5627"cci.2.0" = "merkmal.code"
5628"cav[*,*].0.0" = "werte[].code"
5629"cav[*,*].0.3" = "werte[].text"
5630"cav[Z30,*].0.3" = "geraetenummern[].nummer"
5631"#,
5632 )
5633 .unwrap();
5634 let segment = |tag: &str, elements: &[&[&str]]| OwnedSegment {
5635 id: tag.to_string(),
5636 elements: elements
5637 .iter()
5638 .map(|e| e.iter().map(|c| c.to_string()).collect())
5639 .collect(),
5640 segment_number: 1,
5641 };
5642 let json = engine.map_forward_from_segments(
5643 &[
5644 segment("CCI", &[&[""], &[""], &["ZB3"]]),
5645 segment("CAV", &[&["Z90", "", "", "UENB"]]),
5646 segment("CAV", &[&["Z91", "", "", "MSB"]]),
5647 segment("CAV", &[&["Z30", "", "", "W1"]]),
5648 segment("CAV", &[&["Z30", "", "", "W2"]]),
5649 ],
5650 &def,
5651 );
5652 assert_eq!(
5653 json["werte"],
5654 serde_json::json!([
5655 {"code": "Z90", "text": "UENB"},
5656 {"code": "Z91", "text": "MSB"},
5657 {"code": "Z30", "text": "W1"},
5658 {"code": "Z30", "text": "W2"},
5659 ]),
5660 "{json}"
5661 );
5662 assert_eq!(
5663 json["geraetenummern"],
5664 serde_json::json!([{"nummer": "W1"}, {"nummer": "W2"}])
5665 );
5666
5667 let only_werte = serde_json::json!({
5669 "merkmal": {"code": "ZB3"},
5670 "werte": [{"text": "UENB", "code": "Z90"}, {"code": "Z91", "text": "MSB"}],
5671 });
5672 let instance = engine.map_reverse(&only_werte, &def);
5673 let cavs: Vec<Vec<String>> = instance
5674 .segments
5675 .iter()
5676 .filter(|s| s.tag == "CAV")
5677 .map(|s| s.elements[0].clone())
5678 .collect();
5679 assert_eq!(
5680 cavs,
5681 vec![
5682 vec![
5683 "Z90".to_string(),
5684 String::new(),
5685 String::new(),
5686 "UENB".to_string()
5687 ],
5688 vec![
5689 "Z91".to_string(),
5690 String::new(),
5691 String::new(),
5692 "MSB".to_string()
5693 ],
5694 ]
5695 );
5696 }
5697
5698 #[test]
5699 fn test_variant_cache_serialization_is_deterministic() {
5700 let reference = serde_json::to_vec(&make_populated_cache()).unwrap();
5701 for _ in 0..5 {
5702 let again = serde_json::to_vec(&make_populated_cache()).unwrap();
5703 assert!(
5704 reference == again,
5705 "VariantCache serialization must not depend on HashMap iteration order"
5706 );
5707 }
5708 }
5709
5710 #[test]
5711 fn test_variant_cache_serializes_map_keys_sorted() {
5712 use indexmap::IndexMap;
5713 use serde::de::IgnoredAny;
5714
5715 #[derive(serde::Deserialize)]
5716 struct ProbeWorkflow {
5717 ub_definitions: IndexMap<String, IgnoredAny>,
5718 }
5719 #[derive(serde::Deserialize)]
5720 struct ProbeStructure {
5721 element_counts: IndexMap<String, usize>,
5722 }
5723 #[derive(serde::Deserialize)]
5724 struct Probe {
5725 transaction_defs: IndexMap<String, IgnoredAny>,
5726 combined_defs: IndexMap<String, IgnoredAny>,
5727 code_lookups: IndexMap<String, IndexMap<String, IgnoredAny>>,
5728 segment_structure: ProbeStructure,
5729 pid_segment_numbers: IndexMap<String, IgnoredAny>,
5730 pid_requirements: IndexMap<String, IgnoredAny>,
5731 pid_ahb_workflows: IndexMap<String, ProbeWorkflow>,
5732 tx_groups: IndexMap<String, String>,
5733 }
5734 fn assert_sorted<'a>(what: &str, keys: impl Iterator<Item = &'a String>) {
5735 let keys: Vec<&String> = keys.collect();
5736 let mut sorted = keys.clone();
5737 sorted.sort();
5738 assert_eq!(keys, sorted, "{what} keys must serialize in sorted order");
5739 }
5740
5741 let json = serde_json::to_string(&make_populated_cache()).unwrap();
5742 let probe: Probe = serde_json::from_str(&json).unwrap();
5743 assert_sorted("transaction_defs", probe.transaction_defs.keys());
5744 assert_sorted("combined_defs", probe.combined_defs.keys());
5745 assert_sorted("code_lookups", probe.code_lookups.keys());
5746 let lookup = probe.code_lookups.values().next().unwrap();
5747 assert!(lookup.len() > 10, "fixture lookup should have many entries");
5748 assert_sorted("code_lookup entries", lookup.keys());
5749 assert_sorted(
5750 "segment_structure",
5751 probe.segment_structure.element_counts.keys(),
5752 );
5753 assert_sorted("pid_segment_numbers", probe.pid_segment_numbers.keys());
5754 assert_sorted("pid_requirements", probe.pid_requirements.keys());
5755 assert_sorted("pid_ahb_workflows", probe.pid_ahb_workflows.keys());
5756 let wf = probe.pid_ahb_workflows.values().next().unwrap();
5757 assert_sorted("ub_definitions", wf.ub_definitions.keys());
5758 assert_sorted("tx_groups", probe.tx_groups.keys());
5759 }
5760
5761 #[test]
5762 fn test_data_bundle_serializes_variants_sorted() {
5763 use indexmap::IndexMap;
5764 use serde::de::IgnoredAny;
5765
5766 #[derive(serde::Deserialize)]
5767 struct Probe {
5768 variants: IndexMap<String, IgnoredAny>,
5769 }
5770 let variants: BTreeMap<String, VariantCache> = (0..20)
5771 .map(|i| (format!("VARIANT_{i:02}"), make_test_cache()))
5772 .collect();
5773 let bundle = DataBundle {
5774 format_version: "FV2504".to_string(),
5775 bundle_version: DataBundle::CURRENT_VERSION,
5776 built_by: Some(DataBundle::PRODUCING_VERSION.to_string()),
5777 variants,
5778 bo4e_catalog: Default::default(),
5779 code_lists: Default::default(),
5780 };
5781 let mut bytes = Vec::new();
5782 bundle.write_to(&mut bytes).unwrap();
5783 let probe: Probe = serde_json::from_slice(&bytes).unwrap();
5784 let keys: Vec<&String> = probe.variants.keys().collect();
5785 let mut sorted = keys.clone();
5786 sorted.sort();
5787 assert_eq!(keys, sorted);
5788 }
5789}
5790
5791#[cfg(test)]
5792mod tests {
5793 use super::*;
5794 use crate::definition::{MappingDefinition, MappingMeta, StructuredFieldMapping};
5795 use indexmap::IndexMap;
5796
5797 fn make_def(fields: IndexMap<String, FieldMapping>) -> MappingDefinition {
5798 MappingDefinition {
5799 meta: MappingMeta {
5800 entity: "Test".to_string(),
5801 bo4e_type: "Test".to_string(),
5802 source_group: "SG4".to_string(),
5803 source_path: None,
5804 discriminator: None,
5805 repeat_on_tag: None,
5806 parent_field: None,
5807 target_list: None,
5808 order: None,
5809 },
5810 fields,
5811 complex_handlers: None,
5812 }
5813 }
5814
5815 #[test]
5816 fn test_map_interchange_single_transaction_backward_compat() {
5817 use mig_assembly::assembler::*;
5818
5819 let tree = AssembledTree {
5821 segments: vec![
5822 AssembledSegment {
5823 tag: "UNH".to_string(),
5824 elements: vec![vec!["001".to_string()]],
5825 mig_number: None,
5826 segment_number: None,
5827 },
5828 AssembledSegment {
5829 tag: "BGM".to_string(),
5830 elements: vec![vec!["E01".to_string()], vec!["DOC001".to_string()]],
5831 mig_number: None,
5832 segment_number: None,
5833 },
5834 ],
5835 groups: vec![
5836 AssembledGroup {
5837 group_id: "SG2".to_string(),
5838 repetitions: vec![AssembledGroupInstance {
5839 segments: vec![AssembledSegment {
5840 tag: "NAD".to_string(),
5841 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
5842 mig_number: None,
5843 segment_number: None,
5844 }],
5845 child_groups: vec![],
5846 entry_mig_number: None,
5847 variant_mig_numbers: vec![],
5848 skipped_segments: vec![],
5849 skipped_positions: Vec::new(),
5850 }],
5851 },
5852 AssembledGroup {
5853 group_id: "SG4".to_string(),
5854 repetitions: vec![AssembledGroupInstance {
5855 segments: vec![AssembledSegment {
5856 tag: "IDE".to_string(),
5857 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5858 mig_number: None,
5859 segment_number: None,
5860 }],
5861 child_groups: vec![AssembledGroup {
5862 group_id: "SG5".to_string(),
5863 repetitions: vec![AssembledGroupInstance {
5864 segments: vec![AssembledSegment {
5865 tag: "LOC".to_string(),
5866 elements: vec![
5867 vec!["Z16".to_string()],
5868 vec!["DE000111222333".to_string()],
5869 ],
5870 mig_number: None,
5871 segment_number: None,
5872 }],
5873 child_groups: vec![],
5874 entry_mig_number: None,
5875 variant_mig_numbers: vec![],
5876 skipped_segments: vec![],
5877 skipped_positions: Vec::new(),
5878 }],
5879 }],
5880 entry_mig_number: None,
5881 variant_mig_numbers: vec![],
5882 skipped_segments: vec![],
5883 skipped_positions: Vec::new(),
5884 }],
5885 },
5886 ],
5887 post_group_start: 2,
5888 inter_group_segments: std::collections::BTreeMap::new(),
5889 };
5890
5891 let msg_engine = MappingEngine::from_definitions(vec![]);
5893
5894 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5896 tx_fields.insert(
5897 "ide.1".to_string(),
5898 FieldMapping::Simple("vorgangId".to_string()),
5899 );
5900 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5901 malo_fields.insert(
5902 "loc.1".to_string(),
5903 FieldMapping::Simple("marktlokationsId".to_string()),
5904 );
5905
5906 let tx_engine = MappingEngine::from_definitions(vec![
5907 MappingDefinition {
5908 meta: MappingMeta {
5909 entity: "Prozessdaten".to_string(),
5910 bo4e_type: "Prozessdaten".to_string(),
5911 source_group: "SG4".to_string(),
5912 source_path: None,
5913 discriminator: None,
5914 repeat_on_tag: None,
5915 parent_field: None,
5916 target_list: None,
5917 order: None,
5918 },
5919 fields: tx_fields,
5920 complex_handlers: None,
5921 },
5922 MappingDefinition {
5923 meta: MappingMeta {
5924 entity: "Marktlokation".to_string(),
5925 bo4e_type: "Marktlokation".to_string(),
5926 source_group: "SG4.SG5".to_string(),
5927 source_path: None,
5928 discriminator: None,
5929 repeat_on_tag: None,
5930 parent_field: None,
5931 target_list: None,
5932 order: None,
5933 },
5934 fields: malo_fields,
5935 complex_handlers: None,
5936 },
5937 ]);
5938
5939 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
5940
5941 assert_eq!(result.transaktionen.len(), 1);
5942 assert_eq!(
5943 result.transaktionen[0].transaktionsdaten["vorgangId"]
5944 .as_str()
5945 .unwrap(),
5946 "TX001"
5947 );
5948 assert_eq!(
5951 result.transaktionen[0].stammdaten["marktlokation"]["marktlokationsId"]
5952 .as_str()
5953 .unwrap(),
5954 "DE000111222333"
5955 );
5956 }
5957
5958 #[test]
5959 fn test_map_reverse_pads_intermediate_empty_elements() {
5960 let mut fields = IndexMap::new();
5962 fields.insert(
5963 "nad.0".to_string(),
5964 FieldMapping::Structured(StructuredFieldMapping {
5965 target: String::new(),
5966 transform: None,
5967 when: None,
5968 default: Some("Z09".to_string()),
5969 enum_map: None,
5970 code_list: None,
5971 also_code_list: None,
5972 when_filled: None,
5973 also_target: None,
5974 also_enum_map: None,
5975 }),
5976 );
5977 fields.insert(
5978 "nad.3.0".to_string(),
5979 FieldMapping::Simple("name".to_string()),
5980 );
5981 fields.insert(
5982 "nad.3.1".to_string(),
5983 FieldMapping::Simple("vorname".to_string()),
5984 );
5985
5986 let def = make_def(fields);
5987 let engine = MappingEngine::from_definitions(vec![]);
5988
5989 let bo4e = serde_json::json!({
5990 "name": "Muster",
5991 "vorname": "Max"
5992 });
5993
5994 let instance = engine.map_reverse(&bo4e, &def);
5995 assert_eq!(instance.segments.len(), 1);
5996
5997 let nad = &instance.segments[0];
5998 assert_eq!(nad.tag, "NAD");
5999 assert_eq!(nad.elements.len(), 4);
6000 assert_eq!(nad.elements[0], vec!["Z09"]);
6001 assert_eq!(nad.elements[1], vec![""]);
6003 assert_eq!(nad.elements[2], vec![""]);
6004 assert_eq!(nad.elements[3][0], "Muster");
6005 assert_eq!(nad.elements[3][1], "Max");
6006 }
6007
6008 #[test]
6009 fn test_map_reverse_no_padding_when_contiguous() {
6010 let mut fields = IndexMap::new();
6012 fields.insert(
6013 "dtm.0.0".to_string(),
6014 FieldMapping::Structured(StructuredFieldMapping {
6015 target: String::new(),
6016 transform: None,
6017 when: None,
6018 default: Some("92".to_string()),
6019 enum_map: None,
6020 code_list: None,
6021 also_code_list: None,
6022 when_filled: None,
6023 also_target: None,
6024 also_enum_map: None,
6025 }),
6026 );
6027 fields.insert(
6028 "dtm.0.1".to_string(),
6029 FieldMapping::Simple("value".to_string()),
6030 );
6031 fields.insert(
6032 "dtm.0.2".to_string(),
6033 FieldMapping::Structured(StructuredFieldMapping {
6034 target: String::new(),
6035 transform: None,
6036 when: None,
6037 default: Some("303".to_string()),
6038 enum_map: None,
6039 code_list: None,
6040 also_code_list: None,
6041 when_filled: None,
6042 also_target: None,
6043 also_enum_map: None,
6044 }),
6045 );
6046
6047 let def = make_def(fields);
6048 let engine = MappingEngine::from_definitions(vec![]);
6049
6050 let bo4e = serde_json::json!({ "value": "20250531" });
6051
6052 let instance = engine.map_reverse(&bo4e, &def);
6053 let dtm = &instance.segments[0];
6054 assert_eq!(dtm.elements.len(), 1);
6056 assert_eq!(dtm.elements[0], vec!["92", "20250531", "303"]);
6057 }
6058
6059 #[test]
6060 fn test_map_message_level_extracts_sg2_only() {
6061 use mig_assembly::assembler::*;
6062
6063 let tree = AssembledTree {
6065 segments: vec![
6066 AssembledSegment {
6067 tag: "UNH".to_string(),
6068 elements: vec![vec!["001".to_string()]],
6069 mig_number: None,
6070 segment_number: None,
6071 },
6072 AssembledSegment {
6073 tag: "BGM".to_string(),
6074 elements: vec![vec!["E01".to_string()]],
6075 mig_number: None,
6076 segment_number: None,
6077 },
6078 ],
6079 groups: vec![
6080 AssembledGroup {
6081 group_id: "SG2".to_string(),
6082 repetitions: vec![AssembledGroupInstance {
6083 segments: vec![AssembledSegment {
6084 tag: "NAD".to_string(),
6085 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
6086 mig_number: None,
6087 segment_number: None,
6088 }],
6089 child_groups: vec![],
6090 entry_mig_number: None,
6091 variant_mig_numbers: vec![],
6092 skipped_segments: vec![],
6093 skipped_positions: Vec::new(),
6094 }],
6095 },
6096 AssembledGroup {
6097 group_id: "SG4".to_string(),
6098 repetitions: vec![AssembledGroupInstance {
6099 segments: vec![AssembledSegment {
6100 tag: "IDE".to_string(),
6101 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
6102 mig_number: None,
6103 segment_number: None,
6104 }],
6105 child_groups: vec![],
6106 entry_mig_number: None,
6107 variant_mig_numbers: vec![],
6108 skipped_segments: vec![],
6109 skipped_positions: Vec::new(),
6110 }],
6111 },
6112 ],
6113 post_group_start: 2,
6114 inter_group_segments: std::collections::BTreeMap::new(),
6115 };
6116
6117 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
6119 msg_fields.insert(
6120 "nad.0".to_string(),
6121 FieldMapping::Simple("marktrolle".to_string()),
6122 );
6123 msg_fields.insert(
6124 "nad.1".to_string(),
6125 FieldMapping::Simple("rollencodenummer".to_string()),
6126 );
6127 let msg_def = MappingDefinition {
6128 meta: MappingMeta {
6129 entity: "Marktteilnehmer".to_string(),
6130 bo4e_type: "Marktteilnehmer".to_string(),
6131 source_group: "SG2".to_string(),
6132 source_path: None,
6133 discriminator: None,
6134 repeat_on_tag: None,
6135 parent_field: None,
6136 target_list: None,
6137 order: None,
6138 },
6139 fields: msg_fields,
6140 complex_handlers: None,
6141 };
6142
6143 let engine = MappingEngine::from_definitions(vec![msg_def.clone()]);
6144 let result = engine.map_all_forward(&tree);
6145
6146 assert!(result.get("marktteilnehmer").is_some());
6148 let mt = &result["marktteilnehmer"];
6149 assert_eq!(mt["marktrolle"].as_str().unwrap(), "MS");
6150 assert_eq!(mt["rollencodenummer"].as_str().unwrap(), "9900123");
6151 }
6152
6153 #[test]
6154 fn test_map_transaction_scoped_to_sg4_instance() {
6155 use mig_assembly::assembler::*;
6156
6157 let tree = AssembledTree {
6159 segments: vec![
6160 AssembledSegment {
6161 tag: "UNH".to_string(),
6162 elements: vec![vec!["001".to_string()]],
6163 mig_number: None,
6164 segment_number: None,
6165 },
6166 AssembledSegment {
6167 tag: "BGM".to_string(),
6168 elements: vec![vec!["E01".to_string()]],
6169 mig_number: None,
6170 segment_number: None,
6171 },
6172 ],
6173 groups: vec![AssembledGroup {
6174 group_id: "SG4".to_string(),
6175 repetitions: vec![AssembledGroupInstance {
6176 segments: vec![AssembledSegment {
6177 tag: "IDE".to_string(),
6178 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
6179 mig_number: None,
6180 segment_number: None,
6181 }],
6182 child_groups: vec![AssembledGroup {
6183 group_id: "SG5".to_string(),
6184 repetitions: vec![AssembledGroupInstance {
6185 segments: vec![AssembledSegment {
6186 tag: "LOC".to_string(),
6187 elements: vec![
6188 vec!["Z16".to_string()],
6189 vec!["DE000111222333".to_string()],
6190 ],
6191 mig_number: None,
6192 segment_number: None,
6193 }],
6194 child_groups: vec![],
6195 entry_mig_number: None,
6196 variant_mig_numbers: vec![],
6197 skipped_segments: vec![],
6198 skipped_positions: Vec::new(),
6199 }],
6200 }],
6201 entry_mig_number: None,
6202 variant_mig_numbers: vec![],
6203 skipped_segments: vec![],
6204 skipped_positions: Vec::new(),
6205 }],
6206 }],
6207 post_group_start: 2,
6208 inter_group_segments: std::collections::BTreeMap::new(),
6209 };
6210
6211 let mut proz_fields: IndexMap<String, FieldMapping> = IndexMap::new();
6213 proz_fields.insert(
6214 "ide.1".to_string(),
6215 FieldMapping::Simple("vorgangId".to_string()),
6216 );
6217 let proz_def = MappingDefinition {
6218 meta: MappingMeta {
6219 entity: "Prozessdaten".to_string(),
6220 bo4e_type: "Prozessdaten".to_string(),
6221 source_group: "".to_string(), source_path: None,
6223 discriminator: None,
6224 repeat_on_tag: None,
6225 parent_field: None,
6226 target_list: None,
6227 order: None,
6228 },
6229 fields: proz_fields,
6230 complex_handlers: None,
6231 };
6232
6233 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
6234 malo_fields.insert(
6235 "loc.1".to_string(),
6236 FieldMapping::Simple("marktlokationsId".to_string()),
6237 );
6238 let malo_def = MappingDefinition {
6239 meta: MappingMeta {
6240 entity: "Marktlokation".to_string(),
6241 bo4e_type: "Marktlokation".to_string(),
6242 source_group: "SG5".to_string(), source_path: None,
6244 discriminator: None,
6245 repeat_on_tag: None,
6246 parent_field: None,
6247 target_list: None,
6248 order: None,
6249 },
6250 fields: malo_fields,
6251 complex_handlers: None,
6252 };
6253
6254 let tx_engine = MappingEngine::from_definitions(vec![proz_def, malo_def]);
6255
6256 let sg4 = &tree.groups[0]; let sg4_instance = &sg4.repetitions[0];
6259 let sub_tree = sg4_instance.as_assembled_tree();
6260
6261 let result = tx_engine.map_all_forward(&sub_tree);
6262
6263 assert_eq!(
6265 result["prozessdaten"]["vorgangId"].as_str().unwrap(),
6266 "TX001"
6267 );
6268
6269 assert_eq!(
6271 result["marktlokation"]["marktlokationsId"]
6272 .as_str()
6273 .unwrap(),
6274 "DE000111222333"
6275 );
6276 }
6277
6278 #[test]
6279 fn test_map_interchange_produces_full_hierarchy() {
6280 use mig_assembly::assembler::*;
6281
6282 let tree = AssembledTree {
6284 segments: vec![
6285 AssembledSegment {
6286 tag: "UNH".to_string(),
6287 elements: vec![vec!["001".to_string()]],
6288 mig_number: None,
6289 segment_number: None,
6290 },
6291 AssembledSegment {
6292 tag: "BGM".to_string(),
6293 elements: vec![vec!["E01".to_string()]],
6294 mig_number: None,
6295 segment_number: None,
6296 },
6297 ],
6298 groups: vec![
6299 AssembledGroup {
6300 group_id: "SG2".to_string(),
6301 repetitions: vec![AssembledGroupInstance {
6302 segments: vec![AssembledSegment {
6303 tag: "NAD".to_string(),
6304 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
6305 mig_number: None,
6306 segment_number: None,
6307 }],
6308 child_groups: vec![],
6309 entry_mig_number: None,
6310 variant_mig_numbers: vec![],
6311 skipped_segments: vec![],
6312 skipped_positions: Vec::new(),
6313 }],
6314 },
6315 AssembledGroup {
6316 group_id: "SG4".to_string(),
6317 repetitions: vec![
6318 AssembledGroupInstance {
6319 segments: vec![AssembledSegment {
6320 tag: "IDE".to_string(),
6321 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
6322 mig_number: None,
6323 segment_number: None,
6324 }],
6325 child_groups: vec![],
6326 entry_mig_number: None,
6327 variant_mig_numbers: vec![],
6328 skipped_segments: vec![],
6329 skipped_positions: Vec::new(),
6330 },
6331 AssembledGroupInstance {
6332 segments: vec![AssembledSegment {
6333 tag: "IDE".to_string(),
6334 elements: vec![vec!["24".to_string()], vec!["TX002".to_string()]],
6335 mig_number: None,
6336 segment_number: None,
6337 }],
6338 child_groups: vec![],
6339 entry_mig_number: None,
6340 variant_mig_numbers: vec![],
6341 skipped_segments: vec![],
6342 skipped_positions: Vec::new(),
6343 },
6344 ],
6345 },
6346 ],
6347 post_group_start: 2,
6348 inter_group_segments: std::collections::BTreeMap::new(),
6349 };
6350
6351 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
6353 msg_fields.insert(
6354 "nad.0".to_string(),
6355 FieldMapping::Simple("marktrolle".to_string()),
6356 );
6357 let msg_defs = vec![MappingDefinition {
6358 meta: MappingMeta {
6359 entity: "Marktteilnehmer".to_string(),
6360 bo4e_type: "Marktteilnehmer".to_string(),
6361 source_group: "SG2".to_string(),
6362 source_path: None,
6363 discriminator: None,
6364 repeat_on_tag: None,
6365 parent_field: None,
6366 target_list: None,
6367 order: None,
6368 },
6369 fields: msg_fields,
6370 complex_handlers: None,
6371 }];
6372
6373 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
6375 tx_fields.insert(
6376 "ide.1".to_string(),
6377 FieldMapping::Simple("vorgangId".to_string()),
6378 );
6379 let tx_defs = vec![MappingDefinition {
6380 meta: MappingMeta {
6381 entity: "Prozessdaten".to_string(),
6382 bo4e_type: "Prozessdaten".to_string(),
6383 source_group: "SG4".to_string(),
6384 source_path: None,
6385 discriminator: None,
6386 repeat_on_tag: None,
6387 parent_field: None,
6388 target_list: None,
6389 order: None,
6390 },
6391 fields: tx_fields,
6392 complex_handlers: None,
6393 }];
6394
6395 let msg_engine = MappingEngine::from_definitions(msg_defs);
6396 let tx_engine = MappingEngine::from_definitions(tx_defs);
6397
6398 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
6399
6400 assert!(result.stammdaten["marktteilnehmer"].is_object());
6402 assert_eq!(
6403 result.stammdaten["marktteilnehmer"]["marktrolle"]
6404 .as_str()
6405 .unwrap(),
6406 "MS"
6407 );
6408
6409 assert_eq!(result.transaktionen.len(), 2);
6411 assert_eq!(
6412 result.transaktionen[0].transaktionsdaten["vorgangId"]
6413 .as_str()
6414 .unwrap(),
6415 "TX001"
6416 );
6417 assert_eq!(
6418 result.transaktionen[1].transaktionsdaten["vorgangId"]
6419 .as_str()
6420 .unwrap(),
6421 "TX002"
6422 );
6423 }
6424
6425 #[test]
6426 fn test_map_reverse_with_segment_structure_pads_trailing() {
6427 let mut fields = IndexMap::new();
6429 fields.insert(
6430 "sts.0".to_string(),
6431 FieldMapping::Structured(StructuredFieldMapping {
6432 target: String::new(),
6433 transform: None,
6434 when: None,
6435 default: Some("7".to_string()),
6436 enum_map: None,
6437 code_list: None,
6438 also_code_list: None,
6439 when_filled: None,
6440 also_target: None,
6441 also_enum_map: None,
6442 }),
6443 );
6444 fields.insert(
6445 "sts.2".to_string(),
6446 FieldMapping::Simple("grund".to_string()),
6447 );
6448
6449 let def = make_def(fields);
6450
6451 let mut counts = std::collections::BTreeMap::new();
6453 counts.insert("STS".to_string(), 5usize);
6454 let ss = SegmentStructure {
6455 element_counts: counts,
6456 };
6457
6458 let engine = MappingEngine::from_definitions(vec![]).with_segment_structure(ss);
6459
6460 let bo4e = serde_json::json!({ "grund": "E01" });
6461
6462 let instance = engine.map_reverse(&bo4e, &def);
6463 let sts = &instance.segments[0];
6464 assert_eq!(sts.elements.len(), 5);
6467 assert_eq!(sts.elements[0], vec!["7"]);
6468 assert_eq!(sts.elements[1], vec![""]);
6469 assert_eq!(sts.elements[2], vec!["E01"]);
6470 assert_eq!(sts.elements[3], vec![""]);
6471 assert_eq!(sts.elements[4], vec![""]);
6472 }
6473
6474 #[test]
6475 fn test_resolve_child_relative_with_source_path() {
6476 let mut map: std::collections::HashMap<String, Vec<usize>> =
6477 std::collections::HashMap::new();
6478 map.insert("sg4.sg8_ze1".to_string(), vec![6]);
6479 map.insert("sg4.sg8_z98".to_string(), vec![0]);
6480
6481 assert_eq!(
6483 resolve_child_relative("SG8.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
6484 "SG8:6.SG10"
6485 );
6486
6487 assert_eq!(
6489 resolve_child_relative("SG8:3.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
6490 "SG8:3.SG10"
6491 );
6492
6493 assert_eq!(
6495 resolve_child_relative("SG8.SG10", Some("sg4.sg8_unknown.sg10"), &map, 0),
6496 "SG8.SG10"
6497 );
6498
6499 assert_eq!(
6501 resolve_child_relative("SG8.SG10", None, &map, 0),
6502 "SG8.SG10"
6503 );
6504
6505 assert_eq!(
6507 resolve_child_relative("SG8.SG9", Some("sg4.sg8_z98.sg9"), &map, 0),
6508 "SG8:0.SG9"
6509 );
6510
6511 map.insert("sg4.sg8_zf3".to_string(), vec![3, 4]);
6513 assert_eq!(
6514 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 0),
6515 "SG8:3.SG10"
6516 );
6517 assert_eq!(
6518 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 1),
6519 "SG8:4.SG10"
6520 );
6521 }
6522
6523 #[test]
6524 fn test_place_in_groups_returns_rep_index() {
6525 let mut groups: Vec<AssembledGroup> = Vec::new();
6526
6527 let instance = AssembledGroupInstance {
6529 segments: vec![],
6530 child_groups: vec![],
6531 entry_mig_number: None,
6532 variant_mig_numbers: vec![],
6533 skipped_segments: vec![],
6534 skipped_positions: Vec::new(),
6535 };
6536 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 0);
6537
6538 let instance = AssembledGroupInstance {
6540 segments: vec![],
6541 child_groups: vec![],
6542 entry_mig_number: None,
6543 variant_mig_numbers: vec![],
6544 skipped_segments: vec![],
6545 skipped_positions: Vec::new(),
6546 };
6547 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 1);
6548
6549 let instance = AssembledGroupInstance {
6551 segments: vec![],
6552 child_groups: vec![],
6553 entry_mig_number: None,
6554 variant_mig_numbers: vec![],
6555 skipped_segments: vec![],
6556 skipped_positions: Vec::new(),
6557 };
6558 assert_eq!(place_in_groups(&mut groups, "SG8:5", instance), 5);
6559 }
6560
6561 #[test]
6562 fn test_resolve_by_source_path() {
6563 use mig_assembly::assembler::*;
6564
6565 let tree = AssembledTree {
6567 segments: vec![],
6568 groups: vec![AssembledGroup {
6569 group_id: "SG4".to_string(),
6570 repetitions: vec![AssembledGroupInstance {
6571 segments: vec![],
6572 child_groups: vec![AssembledGroup {
6573 group_id: "SG8".to_string(),
6574 repetitions: vec![
6575 AssembledGroupInstance {
6576 segments: vec![AssembledSegment {
6577 tag: "SEQ".to_string(),
6578 elements: vec![vec!["Z98".to_string()]],
6579 mig_number: None,
6580 segment_number: None,
6581 }],
6582 child_groups: vec![AssembledGroup {
6583 group_id: "SG10".to_string(),
6584 repetitions: vec![AssembledGroupInstance {
6585 segments: vec![AssembledSegment {
6586 tag: "CCI".to_string(),
6587 elements: vec![vec![], vec![], vec!["ZB3".to_string()]],
6588 mig_number: None,
6589 segment_number: None,
6590 }],
6591 child_groups: vec![],
6592 entry_mig_number: None,
6593 variant_mig_numbers: vec![],
6594 skipped_segments: vec![],
6595 skipped_positions: Vec::new(),
6596 }],
6597 }],
6598 entry_mig_number: None,
6599 variant_mig_numbers: vec![],
6600 skipped_segments: vec![],
6601 skipped_positions: Vec::new(),
6602 },
6603 AssembledGroupInstance {
6604 segments: vec![AssembledSegment {
6605 tag: "SEQ".to_string(),
6606 elements: vec![vec!["ZD7".to_string()]],
6607 mig_number: None,
6608 segment_number: None,
6609 }],
6610 child_groups: vec![AssembledGroup {
6611 group_id: "SG10".to_string(),
6612 repetitions: vec![AssembledGroupInstance {
6613 segments: vec![AssembledSegment {
6614 tag: "CCI".to_string(),
6615 elements: vec![vec![], vec![], vec!["ZE6".to_string()]],
6616 mig_number: None,
6617 segment_number: None,
6618 }],
6619 child_groups: vec![],
6620 entry_mig_number: None,
6621 variant_mig_numbers: vec![],
6622 skipped_segments: vec![],
6623 skipped_positions: Vec::new(),
6624 }],
6625 }],
6626 entry_mig_number: None,
6627 variant_mig_numbers: vec![],
6628 skipped_segments: vec![],
6629 skipped_positions: Vec::new(),
6630 },
6631 ],
6632 }],
6633 entry_mig_number: None,
6634 variant_mig_numbers: vec![],
6635 skipped_segments: vec![],
6636 skipped_positions: Vec::new(),
6637 }],
6638 }],
6639 post_group_start: 0,
6640 inter_group_segments: std::collections::BTreeMap::new(),
6641 };
6642
6643 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_z98.sg10");
6645 assert!(inst.is_some());
6646 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
6647
6648 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zd7.sg10");
6650 assert!(inst.is_some());
6651 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZE6");
6652
6653 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zzz.sg10");
6655 assert!(inst.is_none());
6656
6657 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8.sg10");
6659 assert!(inst.is_some());
6660 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
6661 }
6662
6663 #[test]
6664 fn test_parse_source_path_part() {
6665 assert_eq!(parse_source_path_part("sg4"), ("sg4", None));
6666 assert_eq!(parse_source_path_part("sg8_z98"), ("sg8", Some("z98")));
6667 assert_eq!(parse_source_path_part("sg10"), ("sg10", None));
6668 assert_eq!(parse_source_path_part("sg12_z04"), ("sg12", Some("z04")));
6669 }
6670
6671 #[test]
6672 fn test_has_source_path_qualifiers() {
6673 assert!(has_source_path_qualifiers("sg4.sg8_z98.sg10"));
6674 assert!(has_source_path_qualifiers("sg4.sg8_ze1.sg9"));
6675 assert!(!has_source_path_qualifiers("sg4.sg6"));
6676 assert!(!has_source_path_qualifiers("sg4.sg8.sg10"));
6677 }
6678
6679 #[test]
6680 fn test_extract_all_from_instance_collects_all_qualifier_matches() {
6681 use mig_assembly::assembler::*;
6682
6683 let instance = AssembledGroupInstance {
6685 segments: vec![
6686 AssembledSegment {
6687 tag: "SEQ".to_string(),
6688 elements: vec![vec!["ZD6".to_string()]],
6689 mig_number: None,
6690 segment_number: None,
6691 },
6692 AssembledSegment {
6693 tag: "RFF".to_string(),
6694 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
6695 mig_number: None,
6696 segment_number: None,
6697 },
6698 AssembledSegment {
6699 tag: "RFF".to_string(),
6700 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
6701 mig_number: None,
6702 segment_number: None,
6703 },
6704 AssembledSegment {
6705 tag: "RFF".to_string(),
6706 elements: vec![vec!["Z34".to_string(), "REF_C".to_string()]],
6707 mig_number: None,
6708 segment_number: None,
6709 },
6710 AssembledSegment {
6711 tag: "RFF".to_string(),
6712 elements: vec![vec!["Z35".to_string(), "OTHER".to_string()]],
6713 mig_number: None,
6714 segment_number: None,
6715 },
6716 ],
6717 child_groups: vec![],
6718 entry_mig_number: None,
6719 variant_mig_numbers: vec![],
6720 skipped_segments: vec![],
6721 skipped_positions: Vec::new(),
6722 };
6723
6724 let all = MappingEngine::extract_all_from_instance(&instance, "rff[Z34,*].0.1");
6726 assert_eq!(all, vec!["REF_A", "REF_B", "REF_C"]);
6727
6728 let single = MappingEngine::extract_from_instance(&instance, "rff[Z34].0.1");
6730 assert_eq!(single, Some("REF_A".to_string()));
6731
6732 let second = MappingEngine::extract_from_instance(&instance, "rff[Z34,1].0.1");
6733 assert_eq!(second, Some("REF_B".to_string()));
6734 }
6735}