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 children = AssembledGroupInstance {
1577 segments: vec![],
1578 child_groups: vec![],
1579 entry_mig_number: None,
1580 variant_mig_numbers: vec![],
1581 skipped_segments: Vec::new(),
1582 skipped_positions: Vec::new(),
1583 };
1584 self.reverse_nested_children(bo4e_value, def, &mut children);
1585 if children.child_groups.is_empty() {
1586 return instance;
1587 }
1588 let mut instance = self.map_reverse_single_inner(bo4e_value, def, true);
1589 if !instance.segments.is_empty() {
1590 instance.child_groups = children.child_groups;
1591 }
1592 instance
1593 }
1594
1595 fn map_reverse_single(
1596 &self,
1597 bo4e_value: &serde_json::Value,
1598 def: &MappingDefinition,
1599 ) -> AssembledGroupInstance {
1600 self.map_reverse_single_inner(bo4e_value, def, false)
1601 }
1602
1603 fn map_reverse_single_inner(
1606 &self,
1607 bo4e_value: &serde_json::Value,
1608 def: &MappingDefinition,
1609 keep_constants: bool,
1610 ) -> AssembledGroupInstance {
1611 let mut field_values: Vec<(String, String, usize, usize, String)> =
1614 Vec::with_capacity(def.fields.len());
1615
1616 let mut has_real_data = false;
1623 let mut has_data_fields = false;
1624 let mut seg_has_data_field: HashSet<String> = HashSet::new();
1627 let mut seg_has_real_data: HashSet<String> = HashSet::new();
1628 let mut injected_qualifiers: HashSet<String> = HashSet::new();
1629 type ListField<'a> = (
1632 &'a str,
1633 &'a str,
1634 String,
1635 Option<String>,
1636 usize,
1637 usize,
1638 Option<&'a std::collections::BTreeMap<String, String>>,
1639 );
1640 let mut list_fields: Vec<ListField<'_>> = Vec::new();
1641
1642 for (path, field_mapping) in &def.fields {
1643 let (target, default, enum_map, when_filled, also_target, also_enum_map) =
1644 match field_mapping {
1645 FieldMapping::Simple(t) => (t.as_str(), None, None, None, None, None),
1646 FieldMapping::Structured(s) => (
1647 s.target.as_str(),
1648 s.default.as_ref(),
1649 self.table(s.enum_map.as_ref(), s.code_list.as_deref()),
1650 s.when_filled.as_ref(),
1651 s.also_target.as_deref(),
1652 self.table(s.also_enum_map.as_ref(), s.also_code_list.as_deref()),
1653 ),
1654 FieldMapping::Nested(_) => continue,
1655 };
1656
1657 let parts: Vec<&str> = path.split('.').collect();
1658 if parts.len() < 2 {
1659 continue;
1660 }
1661
1662 let (seg_tag, qualifier, _occ) = parse_tag_qualifier(parts[0]);
1663 let seg_key = parts[0].to_uppercase();
1666 let sub_path = &parts[1..];
1667
1668 let (element_idx, component_idx) = if let Ok(ei) = sub_path[0].parse::<usize>() {
1670 let ci = if sub_path.len() > 1 {
1671 sub_path[1].parse::<usize>().unwrap_or(0)
1672 } else {
1673 0
1674 };
1675 (ei, ci)
1676 } else {
1677 match sub_path.len() {
1678 1 => (0, 0),
1679 2 => (1, 0),
1680 _ => continue,
1681 }
1682 };
1683
1684 if let Some((list, sub)) = list_target(target) {
1685 list_fields.push((
1686 list,
1687 sub,
1688 seg_tag.clone(),
1689 qualifier.map(str::to_string),
1690 element_idx,
1691 component_idx,
1692 enum_map,
1693 ));
1694 continue;
1695 }
1696
1697 let val = if target.is_empty() {
1699 match (default, when_filled) {
1700 (Some(d), Some(fields)) => {
1702 let any_filled = fields.iter().any(|f| field_is_filled(bo4e_value, f));
1703 if any_filled {
1704 has_real_data = true;
1707 Some(d.clone())
1708 } else {
1709 None
1710 }
1711 }
1712 (Some(d), None) => Some(d.clone()),
1714 (None, _) => None,
1715 }
1716 } else {
1717 has_data_fields = true;
1718 seg_has_data_field.insert(seg_key.clone());
1719 let bo4e_val = self.populate_field(bo4e_value, target);
1720 if bo4e_val.is_some() {
1721 has_real_data = true;
1722 seg_has_real_data.insert(seg_key.clone());
1723 }
1724 let mapped_val = match (bo4e_val, enum_map) {
1726 (Some(v), Some(map)) => {
1727 let joint = match (also_target, also_enum_map) {
1734 (Some(also), Some(also_map)) => {
1735 self.populate_field(bo4e_value, also).and_then(|also_v| {
1736 map.iter()
1737 .find(|(code, bo4e_v)| {
1738 *bo4e_v == &v && also_map.get(*code) == Some(&also_v)
1739 })
1740 .map(|(code, _)| code.clone())
1741 })
1742 }
1743 _ => None,
1744 };
1745 joint
1746 .or_else(|| {
1747 map.iter()
1749 .find(|(_, bo4e_v)| *bo4e_v == &v)
1750 .map(|(edifact_k, _)| edifact_k.clone())
1751 })
1752 .or(Some(v))
1753 }
1754 (v, _) => v,
1755 };
1756 mapped_val.or_else(|| default.cloned())
1757 };
1758
1759 if let Some(val) = val {
1760 field_values.push((
1761 seg_key.clone(),
1762 seg_tag.clone(),
1763 element_idx,
1764 component_idx,
1765 val,
1766 ));
1767 }
1768
1769 if let Some(q) = qualifier {
1771 if injected_qualifiers.insert(seg_key.clone()) {
1772 field_values.push((seg_key, seg_tag, 0, 0, q.to_string()));
1773 }
1774 }
1775 }
1776
1777 let longest = list_fields
1780 .iter()
1781 .filter_map(|(list, ..)| bo4e_value.get(*list).and_then(|v| v.as_array()))
1782 .map(|a| a.len())
1783 .max()
1784 .unwrap_or(0);
1785 if !list_fields.is_empty() {
1786 has_data_fields = true;
1787 }
1788 for i in 0..longest {
1789 for (list, sub, seg_tag, qualifier, element_idx, component_idx, enum_map) in
1790 &list_fields
1791 {
1792 let key = match qualifier {
1793 Some(q) => format!("{seg_tag}[{q},{i}]"),
1794 None => format!("{seg_tag}[*,{i}]"),
1795 };
1796 seg_has_data_field.insert(key.clone());
1797 let Some(item) = bo4e_value
1798 .get(*list)
1799 .and_then(|v| v.as_array())
1800 .and_then(|a| a.get(i))
1801 else {
1802 continue;
1803 };
1804 let Some(value) = self.populate_field(item, sub) else {
1805 continue;
1806 };
1807 let value = match enum_map {
1808 Some(map) => map
1809 .iter()
1810 .find(|(_, name)| **name == value)
1811 .map(|(code, _)| code.clone())
1812 .unwrap_or(value),
1813 None => value,
1814 };
1815 has_real_data = true;
1816 seg_has_real_data.insert(key.clone());
1817 field_values.push((
1818 key.clone(),
1819 seg_tag.clone(),
1820 *element_idx,
1821 *component_idx,
1822 value,
1823 ));
1824 if let Some(q) = qualifier {
1825 if injected_qualifiers.insert(key.clone()) {
1826 field_values.push((key, seg_tag.clone(), 0, 0, q.clone()));
1827 }
1828 }
1829 }
1830 }
1831
1832 field_values.retain(|(seg_key, _, _, _, _)| {
1840 if !seg_key.contains('[') {
1841 return true; }
1843 !seg_has_data_field.contains(seg_key) || seg_has_real_data.contains(seg_key)
1844 });
1845
1846 if has_data_fields && !has_real_data && !keep_constants {
1851 return AssembledGroupInstance {
1852 segments: vec![],
1853 child_groups: vec![],
1854 entry_mig_number: None,
1855 variant_mig_numbers: vec![],
1856 skipped_segments: Vec::new(),
1857 skipped_positions: Vec::new(),
1858 };
1859 }
1860
1861 let mut segments: Vec<AssembledSegment> = Vec::with_capacity(field_values.len());
1864 let mut seen_keys: HashMap<String, usize> = HashMap::new();
1865
1866 for (seg_key, seg_tag, element_idx, component_idx, val) in &field_values {
1867 let seg = if let Some(&pos) = seen_keys.get(seg_key) {
1868 &mut segments[pos]
1869 } else {
1870 let pos = segments.len();
1871 seen_keys.insert(seg_key.clone(), pos);
1872 segments.push(AssembledSegment {
1873 tag: seg_tag.clone(),
1874 elements: vec![],
1875 mig_number: None,
1876 segment_number: None,
1877 });
1878 &mut segments[pos]
1879 };
1880
1881 while seg.elements.len() <= *element_idx {
1882 seg.elements.push(vec![]);
1883 }
1884 while seg.elements[*element_idx].len() <= *component_idx {
1885 seg.elements[*element_idx].push(String::new());
1886 }
1887 seg.elements[*element_idx][*component_idx] = val.clone();
1888 }
1889
1890 for seg in &mut segments {
1893 let last_populated = seg.elements.iter().rposition(|e| !e.is_empty());
1894 if let Some(last_idx) = last_populated {
1895 for i in 0..last_idx {
1896 if seg.elements[i].is_empty() {
1897 seg.elements[i] = vec![String::new()];
1898 }
1899 }
1900 }
1901 }
1902
1903 if let Some(ref ss) = self.segment_structure {
1905 for seg in &mut segments {
1906 if let Some(expected) = ss.element_count(&seg.tag) {
1907 while seg.elements.len() < expected {
1908 seg.elements.push(vec![String::new()]);
1909 }
1910 }
1911 }
1912 }
1913
1914 AssembledGroupInstance {
1915 segments,
1916 child_groups: vec![],
1917 entry_mig_number: None,
1918 variant_mig_numbers: vec![],
1919 skipped_segments: Vec::new(),
1920 skipped_positions: Vec::new(),
1921 }
1922 }
1923
1924 fn resolve_field_path(segment: &AssembledSegment, path: &[&str]) -> Option<String> {
1937 if path.is_empty() {
1938 return None;
1939 }
1940
1941 if let Ok(element_idx) = path[0].parse::<usize>() {
1943 let component_idx = if path.len() > 1 {
1944 path[1].parse::<usize>().unwrap_or(0)
1945 } else {
1946 0
1947 };
1948 return segment
1949 .elements
1950 .get(element_idx)?
1951 .get(component_idx)
1952 .filter(|v| !v.is_empty())
1953 .cloned();
1954 }
1955
1956 None
1962 }
1963
1964 pub(crate) fn parse_element_component(parts: &[&str]) -> (usize, usize) {
1967 if parts.is_empty() {
1968 return (0, 0);
1969 }
1970 let element_idx = parts[0].parse::<usize>().unwrap_or(0);
1971 let component_idx = if parts.len() > 1 {
1972 parts[1].parse::<usize>().unwrap_or(0)
1973 } else {
1974 0
1975 };
1976 (element_idx, component_idx)
1977 }
1978
1979 pub fn populate_field(
1982 &self,
1983 bo4e_value: &serde_json::Value,
1984 target_field: &str,
1985 ) -> Option<String> {
1986 let mut current = bo4e_value;
1987 for part in target_field.split('.') {
1988 current = current.get(part)?;
1989 }
1990 if let Some(code) = current.get("code").and_then(|v| v.as_str()) {
1992 return Some(code.to_string());
1993 }
1994 current.as_str().map(|s| s.to_string())
1995 }
1996
1997 pub fn build_segment_from_bo4e(
1999 &self,
2000 bo4e_value: &serde_json::Value,
2001 segment_tag: &str,
2002 target_field: &str,
2003 ) -> AssembledSegment {
2004 let value = self.populate_field(bo4e_value, target_field);
2005 let elements = if let Some(val) = value {
2006 vec![vec![val]]
2007 } else {
2008 vec![]
2009 };
2010 AssembledSegment {
2011 tag: segment_tag.to_uppercase(),
2012 elements,
2013 mig_number: None,
2014 segment_number: None,
2015 }
2016 }
2017
2018 pub fn resolve_repetition(
2027 tree: &AssembledTree,
2028 group_path: &str,
2029 discriminator: &str,
2030 ) -> Option<usize> {
2031 let (spec, expected) = discriminator.split_once('=')?;
2032 let parts: Vec<&str> = spec.split('.').collect();
2033 if parts.len() != 3 {
2034 return None;
2035 }
2036 let tag = parts[0];
2037 let element_idx: usize = parts[1].parse().ok()?;
2038 let component_idx: usize = parts[2].parse().ok()?;
2039
2040 let path_parts: Vec<&str> = group_path.split('.').collect();
2042
2043 let leaf_group = if path_parts.len() == 1 {
2044 let (group_id, _) = parse_group_spec(path_parts[0]);
2045 tree.groups.iter().find(|g| g.group_id == group_id)?
2046 } else {
2047 let parent_parts = &path_parts[..path_parts.len() - 1];
2049 let mut current_instance = {
2050 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
2051 let first_group = tree.groups.iter().find(|g| g.group_id == first_id)?;
2052 first_group.repetitions.get(first_rep.unwrap_or(0))?
2053 };
2054 for part in &parent_parts[1..] {
2055 let (group_id, explicit_rep) = parse_group_spec(part);
2056 let child_group = current_instance
2057 .child_groups
2058 .iter()
2059 .find(|g| g.group_id == group_id)?;
2060 current_instance = child_group.repetitions.get(explicit_rep.unwrap_or(0))?;
2061 }
2062 let (leaf_id, _) = parse_group_spec(path_parts.last()?);
2063 current_instance
2064 .child_groups
2065 .iter()
2066 .find(|g| g.group_id == leaf_id)?
2067 };
2068
2069 let expected_values: Vec<&str> = expected.split('|').collect();
2071 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
2072 let matches = instance.segments.iter().any(|s| {
2073 s.tag.eq_ignore_ascii_case(tag)
2074 && s.elements
2075 .get(element_idx)
2076 .and_then(|e| e.get(component_idx))
2077 .map(|v| expected_values.iter().any(|ev| v == ev))
2078 .unwrap_or(false)
2079 });
2080 if matches {
2081 return Some(rep_idx);
2082 }
2083 }
2084
2085 None
2086 }
2087
2088 pub fn resolve_all_repetitions(
2093 tree: &AssembledTree,
2094 group_path: &str,
2095 discriminator: &str,
2096 ) -> Vec<usize> {
2097 let Some((spec, expected)) = discriminator.split_once('=') else {
2098 return Vec::new();
2099 };
2100 let parts: Vec<&str> = spec.split('.').collect();
2101 if parts.len() != 3 {
2102 return Vec::new();
2103 }
2104 let tag = parts[0];
2105 let element_idx: usize = match parts[1].parse() {
2106 Ok(v) => v,
2107 Err(_) => return Vec::new(),
2108 };
2109 let component_idx: usize = match parts[2].parse() {
2110 Ok(v) => v,
2111 Err(_) => return Vec::new(),
2112 };
2113
2114 let path_parts: Vec<&str> = group_path.split('.').collect();
2116
2117 let leaf_group = if path_parts.len() == 1 {
2118 let (group_id, _) = parse_group_spec(path_parts[0]);
2119 match tree.groups.iter().find(|g| g.group_id == group_id) {
2120 Some(g) => g,
2121 None => return Vec::new(),
2122 }
2123 } else {
2124 let parent_parts = &path_parts[..path_parts.len() - 1];
2125 let mut current_instance = {
2126 let (first_id, first_rep) = parse_group_spec(parent_parts[0]);
2127 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2128 Some(g) => g,
2129 None => return Vec::new(),
2130 };
2131 match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2132 Some(i) => i,
2133 None => return Vec::new(),
2134 }
2135 };
2136 for part in &parent_parts[1..] {
2137 let (group_id, explicit_rep) = parse_group_spec(part);
2138 let child_group = match current_instance
2139 .child_groups
2140 .iter()
2141 .find(|g| g.group_id == group_id)
2142 {
2143 Some(g) => g,
2144 None => return Vec::new(),
2145 };
2146 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2147 Some(i) => i,
2148 None => return Vec::new(),
2149 };
2150 }
2151 let (leaf_id, _) = match path_parts.last() {
2152 Some(p) => parse_group_spec(p),
2153 None => return Vec::new(),
2154 };
2155 match current_instance
2156 .child_groups
2157 .iter()
2158 .find(|g| g.group_id == leaf_id)
2159 {
2160 Some(g) => g,
2161 None => return Vec::new(),
2162 }
2163 };
2164
2165 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
2167
2168 let expected_values: Vec<&str> = expected_raw.split('|').collect();
2170 let mut result = Vec::new();
2171 for (rep_idx, instance) in leaf_group.repetitions.iter().enumerate() {
2172 let matches = instance.segments.iter().any(|s| {
2173 s.tag.eq_ignore_ascii_case(tag)
2174 && s.elements
2175 .get(element_idx)
2176 .and_then(|e| e.get(component_idx))
2177 .map(|v| expected_values.iter().any(|ev| v == ev))
2178 .unwrap_or(false)
2179 });
2180 if matches {
2181 result.push(rep_idx);
2182 }
2183 }
2184
2185 if let Some(occ) = occurrence {
2187 result.into_iter().nth(occ).into_iter().collect()
2188 } else {
2189 result
2190 }
2191 }
2192
2193 pub fn map_all_forward(&self, tree: &AssembledTree) -> serde_json::Value {
2215 self.map_all_forward_inner(tree, true).0
2216 }
2217
2218 pub fn map_all_forward_enriched(
2222 &self,
2223 tree: &AssembledTree,
2224 enrich_codes: bool,
2225 ) -> serde_json::Value {
2226 self.map_all_forward_inner(tree, enrich_codes).0
2227 }
2228
2229 fn map_all_forward_inner(
2235 &self,
2236 tree: &AssembledTree,
2237 enrich_codes: bool,
2238 ) -> (
2239 serde_json::Value,
2240 std::collections::HashMap<String, Vec<usize>>,
2241 ) {
2242 self.map_all_forward_inner_with_tx(tree, enrich_codes, self.transaction_group.as_deref())
2243 }
2244
2245 fn map_all_forward_inner_with_tx(
2249 &self,
2250 tree: &AssembledTree,
2251 enrich_codes: bool,
2252 tx_group_override: Option<&str>,
2253 ) -> (
2254 serde_json::Value,
2255 std::collections::HashMap<String, Vec<usize>>,
2256 ) {
2257 let mut result = serde_json::Map::new();
2258 let mut nesting_info: std::collections::HashMap<String, Vec<usize>> =
2259 std::collections::HashMap::new();
2260 let mut contributors: std::collections::HashMap<String, Vec<String>> =
2262 std::collections::HashMap::new();
2263
2264 for def in &self.definitions {
2265 if def.meta.parent_field.is_some() || is_bound_child(&self.definitions, def) {
2268 continue;
2269 }
2270 let entity = &def.meta.entity;
2271
2272 let bo4e = if let Some(ref disc) = def.meta.discriminator {
2273 let use_source_path = def
2278 .meta
2279 .source_path
2280 .as_ref()
2281 .is_some_and(|sp| has_source_path_qualifiers(sp));
2282 if use_source_path {
2283 let sp = def.meta.source_path.as_deref().unwrap();
2285 let all_instances = Self::resolve_all_by_source_path(tree, sp);
2286 let instances: Vec<_> = if let Some(matcher) = DiscriminatorMatcher::parse(disc)
2288 {
2289 matcher.filter_instances(all_instances)
2290 } else {
2291 all_instances
2292 };
2293 let extract = |instance: &AssembledGroupInstance| {
2294 let mut r = serde_json::Map::new();
2295 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
2296 serde_json::Value::Object(r)
2297 };
2298 match instances.len() {
2299 0 => None,
2300 1 => Some(extract(instances[0])),
2301 _ => Some(serde_json::Value::Array(
2302 instances.iter().map(|i| extract(i)).collect(),
2303 )),
2304 }
2305 } else {
2306 let reps = Self::resolve_all_repetitions(tree, &def.meta.source_group, disc);
2307 match reps.len() {
2308 0 => None,
2309 1 => Some(self.map_forward_inner(tree, def, reps[0], enrich_codes)),
2310 _ => Some(serde_json::Value::Array(
2311 reps.iter()
2312 .map(|&rep| self.map_forward_inner(tree, def, rep, enrich_codes))
2313 .collect(),
2314 )),
2315 }
2316 }
2317 } else if def.meta.source_group.is_empty() {
2318 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2320 } else if def.meta.source_path.as_ref().is_some_and(|sp| {
2321 has_source_path_qualifiers(sp) || def.meta.source_group.contains('.')
2322 }) {
2323 let sp = def.meta.source_path.as_deref().unwrap();
2328 let mut indexed = Self::resolve_all_with_parent_indices(tree, sp);
2329
2330 if let Some(last_part) = sp.rsplit('.').next() {
2335 if !last_part.contains('_') {
2336 let base_prefix = if let Some(parent) = sp.rsplit_once('.') {
2340 format!("{}.", parent.0)
2341 } else {
2342 String::new()
2343 };
2344 let sibling_qualifiers: Vec<String> = self
2345 .definitions
2346 .iter()
2347 .filter_map(|d| d.meta.source_path.as_deref())
2348 .filter(|other_sp| {
2349 *other_sp != sp
2350 && other_sp.starts_with(&base_prefix)
2351 && other_sp.split('.').count() == sp.split('.').count()
2352 })
2353 .filter_map(|other_sp| {
2354 let other_last = other_sp.rsplit('.').next()?;
2355 let (base, q) = other_last.split_once('_')?;
2358 if base == last_part {
2359 Some(q.to_string())
2360 } else {
2361 None
2362 }
2363 })
2364 .collect();
2365
2366 if !sibling_qualifiers.is_empty() {
2367 indexed.retain(|(_, inst)| {
2368 let entry_qual = inst
2369 .segments
2370 .first()
2371 .and_then(|seg| seg.elements.first())
2372 .and_then(|el| el.first())
2373 .map(|v| v.to_lowercase());
2374 !entry_qual.is_some_and(|q| {
2377 sibling_qualifiers.iter().any(|sq| {
2378 sq.split('_').any(|part| part.eq_ignore_ascii_case(&q))
2379 })
2380 })
2381 });
2382 }
2383 }
2384 }
2385 let extract = |instance: &AssembledGroupInstance| {
2386 let mut r = serde_json::Map::new();
2387 self.extract_fields_from_instance(instance, def, &mut r, enrich_codes);
2388 serde_json::Value::Object(r)
2389 };
2390 if def.meta.source_group.contains('.') && !indexed.is_empty() {
2395 if let Some(sp) = &def.meta.source_path {
2396 let parent_indices: Vec<usize> =
2397 indexed.iter().map(|(idx, _)| *idx).collect();
2398 nesting_info.entry(sp.clone()).or_insert(parent_indices);
2399
2400 let child_key = format!("{sp}#child");
2403 if let std::collections::hash_map::Entry::Vacant(e) =
2404 nesting_info.entry(child_key)
2405 {
2406 let child_indices: Vec<usize> =
2407 Self::compute_child_indices(tree, sp, &indexed);
2408 if !child_indices.is_empty() {
2409 e.insert(child_indices);
2410 }
2411 }
2412 }
2413 }
2414 match indexed.len() {
2415 0 => None,
2416 1 => Some(extract(indexed[0].1)),
2417 _ => Some(serde_json::Value::Array(
2418 indexed.iter().map(|(_, i)| extract(i)).collect(),
2419 )),
2420 }
2421 } else {
2422 let num_reps = Self::count_repetitions(tree, &def.meta.source_group);
2423 if num_reps <= 1 {
2424 Some(self.map_forward_inner(tree, def, 0, enrich_codes))
2425 } else {
2426 let mut items = Vec::with_capacity(num_reps);
2428 for rep in 0..num_reps {
2429 items.push(self.map_forward_inner(tree, def, rep, enrich_codes));
2430 }
2431 Some(serde_json::Value::Array(items))
2432 }
2433 };
2434
2435 if let Some(bo4e) = bo4e {
2436 let key = to_camel_case(entity);
2437 match def.meta.target_list.as_deref() {
2438 Some(list_field) => append_to_list_field(&mut result, &key, list_field, bo4e),
2439 None => {
2440 let group = def
2444 .meta
2445 .source_path
2446 .clone()
2447 .unwrap_or_else(|| def.meta.source_group.to_lowercase());
2448 let seen = contributors.entry(key.clone()).or_default();
2449 let nested = seen.iter().any(|other: &String| {
2450 group.starts_with(&format!("{other}."))
2451 || other.starts_with(&format!("{group}."))
2452 });
2453 seen.push(group);
2454 merge_entity(&mut result, &key, bo4e, !nested);
2455 }
2456 }
2457 }
2458 }
2459
2460 nest_child_entities_in_result(
2465 &mut result,
2466 &self.definitions,
2467 &nesting_info,
2468 tx_group_override,
2469 );
2470
2471 (serde_json::Value::Object(result), nesting_info)
2472 }
2473
2474 pub fn map_all_reverse(
2483 &self,
2484 entities: &serde_json::Value,
2485 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2486 ) -> AssembledTree {
2487 self.map_all_reverse_with_mig(entities, nesting_info, None)
2488 }
2489
2490 pub fn map_all_reverse_with_mig(
2494 &self,
2495 entities: &serde_json::Value,
2496 nesting_info: Option<&std::collections::HashMap<String, Vec<usize>>>,
2497 mig: Option<&MigSchema>,
2498 ) -> AssembledTree {
2499 let mut root_segments: Vec<AssembledSegment> = Vec::new();
2500 let mut groups: Vec<AssembledGroup> = Vec::new();
2501 let mut inferred_nesting: std::collections::HashMap<String, Vec<usize>> =
2504 std::collections::HashMap::new();
2505
2506 for def in &self.definitions {
2507 if def.meta.parent_field.is_some() || is_bound_child(&self.definitions, def) {
2510 continue;
2511 }
2512 let entity_key = to_camel_case(&def.meta.entity);
2513
2514 let _extracted: Option<serde_json::Value>;
2517 let entity_value = if let Some(list_field) = def.meta.target_list.as_deref() {
2518 match entities.get(&entity_key).and_then(|e| e.get(list_field)) {
2524 Some(v) if v.is_array() => {
2525 _extracted = None;
2526 v
2527 }
2528 _ => continue,
2529 }
2530 } else if let Some(v) = entities.get(&entity_key) {
2531 _extracted = None;
2532 v
2533 } else if def.meta.source_group.contains('.') {
2534 match extract_child_from_parent_with_indices(entities, &self.definitions, def) {
2536 Some((v, parent_indices)) => {
2537 if let Some(sp) = def.meta.source_path.as_deref() {
2539 inferred_nesting
2540 .entry(sp.to_string())
2541 .or_insert(parent_indices);
2542 }
2543 _extracted = Some(v);
2544 _extracted.as_ref().unwrap()
2545 }
2546 None => continue,
2547 }
2548 } else {
2549 continue;
2550 };
2551
2552 let unwrapped: Option<serde_json::Value>;
2560 let entity_value = if entity_value.is_object() && !entity_value.is_array() {
2561 if let Some(disc_value) = def
2562 .meta
2563 .discriminator
2564 .as_deref()
2565 .and_then(|d| d.split_once('='))
2566 .map(|(_, v)| v)
2567 {
2568 if let Some(inner) = entity_value.get(disc_value) {
2570 let mut injected = inner.clone();
2571 if let Some(qualifier_field) =
2574 find_qualifier_companion_field(&self.definitions, &def.meta.entity)
2575 {
2576 if let Some(obj) = injected.as_object_mut() {
2577 let entry = obj
2578 .entry(qualifier_field)
2579 .or_insert(serde_json::Value::Null);
2580 if entry.is_null() {
2581 *entry = serde_json::Value::String(disc_value.to_string());
2582 }
2583 }
2584 }
2585 unwrapped = Some(injected);
2586 unwrapped.as_ref().unwrap()
2587 } else {
2588 entity_value
2589 }
2590 } else if is_map_keyed_object(entity_value) {
2591 let map = entity_value.as_object().unwrap();
2596 let arr: Vec<serde_json::Value> = map
2597 .iter()
2598 .map(|(key, val)| {
2599 let mut item = val.clone();
2600 if let Some(obj) = item.as_object_mut() {
2603 if let Some(qualifier_field) = find_qualifier_companion_field(
2604 &self.definitions,
2605 &def.meta.entity,
2606 ) {
2607 let entry = obj
2608 .entry(qualifier_field)
2609 .or_insert(serde_json::Value::Null);
2610 if entry.is_null() {
2611 *entry = serde_json::Value::String(key.clone());
2612 }
2613 }
2614 }
2615 item
2616 })
2617 .collect();
2618 unwrapped = Some(serde_json::Value::Array(arr));
2619 unwrapped.as_ref().unwrap()
2620 } else {
2621 entity_value
2622 }
2623 } else {
2624 entity_value
2625 };
2626
2627 let leaf_group = def
2629 .meta
2630 .source_group
2631 .rsplit('.')
2632 .next()
2633 .unwrap_or(&def.meta.source_group);
2634
2635 if def.meta.source_group.is_empty() {
2636 let instance = self.map_reverse(entity_value, def);
2638 root_segments.extend(instance.segments);
2639 } else if entity_value.is_array() {
2640 let arr = entity_value.as_array().unwrap();
2642 let reps: Vec<_> = arr.iter().map(|item| self.map_reverse(item, def)).collect();
2643
2644 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2646 existing.repetitions.extend(reps);
2647 } else {
2648 groups.push(AssembledGroup {
2649 group_id: leaf_group.to_string(),
2650 repetitions: reps,
2651 });
2652 }
2653 } else {
2654 let instance = self.map_reverse(entity_value, def);
2656
2657 if let Some(existing) = groups.iter_mut().find(|g| g.group_id == leaf_group) {
2658 existing.repetitions.push(instance);
2659 } else {
2660 groups.push(AssembledGroup {
2661 group_id: leaf_group.to_string(),
2662 repetitions: vec![instance],
2663 });
2664 }
2665 }
2666 }
2667
2668 let nested_specs: Vec<(String, String)> = self
2674 .definitions
2675 .iter()
2676 .filter(|def| def.meta.parent_field.is_none())
2677 .filter_map(|def| {
2678 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
2679 if parts.len() > 1 {
2680 Some((parts[0].to_string(), parts[parts.len() - 1].to_string()))
2681 } else {
2682 None
2683 }
2684 })
2685 .collect();
2686 for (parent_id, child_id) in &nested_specs {
2687 let has_parent = groups.iter().any(|g| g.group_id == *parent_id);
2689 let has_child = groups.iter().any(|g| g.group_id == *child_id);
2690 if has_parent && has_child {
2691 let child_idx = groups.iter().position(|g| g.group_id == *child_id).unwrap();
2692 let child_group = groups.remove(child_idx);
2693 let parent = groups
2694 .iter_mut()
2695 .find(|g| g.group_id == *parent_id)
2696 .unwrap();
2697 let child_source_path = self
2701 .definitions
2702 .iter()
2703 .find(|d| {
2704 let parts: Vec<&str> = d.meta.source_group.split('.').collect();
2705 d.meta.parent_field.is_none()
2706 && parts.len() > 1
2707 && parts[parts.len() - 1] == *child_id
2708 })
2709 .and_then(|d| d.meta.source_path.as_deref());
2710 let distribution = child_source_path.and_then(|key| {
2711 nesting_info
2712 .and_then(|ni| ni.get(key))
2713 .or_else(|| inferred_nesting.get(key))
2714 });
2715 let unlinked_target = mig
2721 .and_then(|m| {
2722 mig_assembly::repetition_order::preferred_parent_repetition(
2723 parent,
2724 &m.segment_groups,
2725 child_id,
2726 )
2727 })
2728 .unwrap_or(0);
2729 for (i, child_rep) in child_group.repetitions.into_iter().enumerate() {
2730 let target_idx = distribution
2731 .and_then(|dist| dist.get(i))
2732 .copied()
2733 .unwrap_or(unlinked_target);
2734
2735 if let Some(target_rep) = parent.repetitions.get_mut(target_idx) {
2736 if let Some(existing) = target_rep
2737 .child_groups
2738 .iter_mut()
2739 .find(|g| g.group_id == *child_id)
2740 {
2741 existing.repetitions.push(child_rep);
2742 } else {
2743 target_rep.child_groups.push(AssembledGroup {
2744 group_id: child_id.clone(),
2745 repetitions: vec![child_rep],
2746 });
2747 }
2748 }
2749 }
2750 }
2751 }
2752
2753 let post_group_start = root_segments.len();
2754 AssembledTree {
2755 segments: root_segments,
2756 groups,
2757 post_group_start,
2758 inter_group_segments: std::collections::BTreeMap::new(),
2759 }
2760 }
2761
2762 fn count_repetitions(tree: &AssembledTree, group_path: &str) -> usize {
2764 let parts: Vec<&str> = group_path.split('.').collect();
2765
2766 let (first_id, first_rep) = parse_group_spec(parts[0]);
2767 let first_group = match tree.groups.iter().find(|g| g.group_id == first_id) {
2768 Some(g) => g,
2769 None => return 0,
2770 };
2771
2772 if parts.len() == 1 {
2773 return first_group.repetitions.len();
2774 }
2775
2776 let mut current_instance = match first_group.repetitions.get(first_rep.unwrap_or(0)) {
2778 Some(i) => i,
2779 None => return 0,
2780 };
2781
2782 for (i, part) in parts[1..].iter().enumerate() {
2783 let (group_id, explicit_rep) = parse_group_spec(part);
2784 let child_group = match current_instance
2785 .child_groups
2786 .iter()
2787 .find(|g| g.group_id == group_id)
2788 {
2789 Some(g) => g,
2790 None => return 0,
2791 };
2792
2793 if i == parts.len() - 2 {
2794 return child_group.repetitions.len();
2796 }
2797 current_instance = match child_group.repetitions.get(explicit_rep.unwrap_or(0)) {
2798 Some(i) => i,
2799 None => return 0,
2800 };
2801 }
2802
2803 0
2804 }
2805
2806 pub fn translate_edifact_to_bo4e(
2814 msg_engine: &MappingEngine,
2815 tx_engine: &MappingEngine,
2816 tree: &AssembledTree,
2817 transaction_group: &str,
2818 ) -> crate::model::MappedMessage {
2819 Self::map_interchange_inner(msg_engine, tx_engine, tree, transaction_group, false)
2820 }
2821
2822 pub fn enrich_bo4e_types(
2836 msg_engine: &MappingEngine,
2837 tx_engine: &MappingEngine,
2838 mapped: &mut crate::model::MappedMessage,
2839 ) {
2840 msg_engine.enrich_entities(&mut mapped.stammdaten);
2841 for tx in &mut mapped.transaktionen {
2842 tx_engine.enrich_entities(&mut tx.stammdaten);
2843 }
2844
2845 msg_engine.enrich_named_entity(
2849 &mut mapped.nachricht_meta,
2850 crate::model::MSG_METADATA_ENTITY,
2851 );
2852 for tx in &mut mapped.transaktionen {
2853 tx_engine
2854 .enrich_named_entity(&mut tx.transaktionsdaten, crate::model::TX_METADATA_ENTITY);
2855 }
2856 }
2857
2858 fn enrich_named_entity(&self, value: &mut serde_json::Value, entity_key: &str) {
2863 if self.code_lookup.is_none() || value.is_null() {
2864 return;
2865 }
2866 let sites = self.code_sites();
2867 if let Some(entity_sites) = sites.get(entity_key) {
2868 Self::apply_sites(self, value, entity_sites);
2869 }
2870 }
2871
2872 fn enrich_entities(&self, value: &mut serde_json::Value) {
2878 if self.code_lookup.is_none() {
2879 return;
2880 }
2881 let sites: HashMap<String, Vec<CodeSite<'_>>> = self.code_sites();
2882 if sites.is_empty() {
2883 return;
2884 }
2885 Self::walk_and_enrich(self, value, &sites);
2886 }
2887
2888 fn code_sites(&self) -> HashMap<String, Vec<CodeSite<'_>>> {
2891 let Some(ref code_lookup) = self.code_lookup else {
2892 return HashMap::new();
2893 };
2894 let mut sites: HashMap<String, Vec<CodeSite<'_>>> = HashMap::new();
2895
2896 for def in &self.definitions {
2897 let Some(ref source_path) = def.meta.source_path else {
2898 continue;
2899 };
2900 let entity_key = to_camel_case(&def.meta.entity);
2901
2902 for (path, field_mapping) in &def.fields {
2903 let (target, enum_map, also_target, also_enum_map) = match field_mapping {
2904 FieldMapping::Simple(t) => (t.as_str(), None, None, None),
2905 FieldMapping::Structured(s) => (
2906 s.target.as_str(),
2907 self.table(s.enum_map.as_ref(), s.code_list.as_deref()),
2908 s.also_target.as_deref(),
2909 self.table(s.also_enum_map.as_ref(), s.also_code_list.as_deref()),
2910 ),
2911 FieldMapping::Nested(_) => continue,
2912 };
2913 if target.is_empty() {
2914 continue;
2915 }
2916
2917 let parts: Vec<&str> = path.split('.').collect();
2918 let (seg_tag, path_qualifier, _occ) = parse_tag_qualifier(parts[0]);
2919 let (element_idx, component_idx) = Self::parse_element_component(&parts[1..]);
2920 let disc_qualifier = Self::discriminator_qualifier_for_tag(def, &seg_tag);
2927 if code_lookup
2928 .enrichment_codes(
2929 source_path,
2930 &seg_tag,
2931 path_qualifier,
2932 disc_qualifier.as_deref(),
2933 element_idx,
2934 component_idx,
2935 )
2936 .is_none()
2937 {
2938 continue;
2939 }
2940
2941 sites.entry(entity_key.clone()).or_default().push(CodeSite {
2942 target,
2943 parent_field: def.meta.parent_field.as_deref(),
2944 source_path,
2945 seg_tag,
2946 path_qualifier: path_qualifier.map(str::to_string),
2947 disc_qualifier,
2948 element_idx,
2949 component_idx,
2950 enum_map,
2951 also_target,
2952 also_enum_map,
2953 });
2954 }
2955 }
2956 sites
2957 }
2958
2959 fn walk_and_enrich(
2962 engine: &MappingEngine,
2963 value: &mut serde_json::Value,
2964 sites: &HashMap<String, Vec<CodeSite<'_>>>,
2965 ) {
2966 match value {
2967 serde_json::Value::Object(map) => {
2968 for (key, child) in map.iter_mut() {
2969 if let Some(entity_sites) = sites.get(key.as_str()) {
2970 Self::apply_sites(engine, child, entity_sites);
2971 }
2972 Self::walk_and_enrich(engine, child, sites);
2973 }
2974 }
2975 serde_json::Value::Array(items) => {
2976 for item in items.iter_mut() {
2977 Self::walk_and_enrich(engine, item, sites);
2978 }
2979 }
2980 _ => {}
2981 }
2982 }
2983
2984 fn apply_sites(engine: &MappingEngine, value: &mut serde_json::Value, sites: &[CodeSite<'_>]) {
2987 match value {
2988 serde_json::Value::Array(items) => {
2989 for item in items.iter_mut() {
2990 Self::apply_sites(engine, item, sites);
2991 }
2992 }
2993 serde_json::Value::Object(_) => {
2994 for site in sites {
2995 match site.parent_field {
2996 None => engine.enrich_one(value, site),
2997 Some(field) => {
2998 if let Some(nested) = value.get_mut(field) {
2999 Self::apply_nested_site(engine, nested, site);
3000 }
3001 }
3002 }
3003 }
3004 }
3005 _ => {}
3006 }
3007 }
3008
3009 fn apply_nested_site(
3011 engine: &MappingEngine,
3012 value: &mut serde_json::Value,
3013 site: &CodeSite<'_>,
3014 ) {
3015 match value {
3016 serde_json::Value::Array(items) => {
3017 for item in items.iter_mut() {
3018 Self::apply_nested_site(engine, item, site);
3019 }
3020 }
3021 serde_json::Value::Object(_) => engine.enrich_one(value, site),
3022 _ => {}
3023 }
3024 }
3025
3026 fn enrich_one(&self, entity: &mut serde_json::Value, site: &CodeSite<'_>) {
3028 if let Some((list, sub)) = list_target(site.target) {
3030 if let Some(items) = entity.get_mut(list).and_then(|v| v.as_array_mut()) {
3031 let element_site = CodeSite {
3032 target: sub,
3033 ..site.clone()
3034 };
3035 for item in items {
3036 self.enrich_one(item, &element_site);
3037 }
3038 }
3039 return;
3040 }
3041 let Some(ref code_lookup) = self.code_lookup else {
3042 return;
3043 };
3044 let Some(mapped_val) = Self::read_plain_string(entity, site.target) else {
3046 return;
3047 };
3048
3049 let raw = match site.enum_map {
3052 None => mapped_val.clone(),
3053 Some(map) => {
3054 let joint = match (site.also_target, site.also_enum_map) {
3055 (Some(also), Some(also_map)) => {
3056 Self::read_plain_string(entity, also).and_then(|also_v| {
3057 map.iter()
3058 .find(|(code, bo4e_v)| {
3059 *bo4e_v == &mapped_val && also_map.get(*code) == Some(&also_v)
3060 })
3061 .map(|(code, _)| code.clone())
3062 })
3063 }
3064 _ => None,
3065 };
3066 joint
3067 .or_else(|| {
3068 map.iter()
3069 .find(|(_, bo4e_v)| *bo4e_v == &mapped_val)
3070 .map(|(code, _)| code.clone())
3071 })
3072 .unwrap_or_else(|| mapped_val.clone())
3073 }
3074 };
3075
3076 let Some(codes) = code_lookup.enrichment_codes(
3077 site.source_path,
3078 &site.seg_tag,
3079 site.path_qualifier.as_deref(),
3080 site.disc_qualifier.as_deref(),
3081 site.element_idx,
3082 site.component_idx,
3083 ) else {
3084 return;
3085 };
3086
3087 if let Some(ref pid) = self.current_pid {
3089 if codes.len() == 1 && codes.contains_key(pid.as_str()) {
3090 return;
3091 }
3092 }
3093
3094 let enrichment = codes.get(&raw);
3095 let meaning = enrichment
3096 .map(|e| serde_json::Value::String(e.meaning.clone()))
3097 .unwrap_or(serde_json::Value::Null);
3098
3099 let mut obj = serde_json::Map::new();
3100 obj.insert("code".into(), serde_json::json!(mapped_val));
3101 obj.insert("meaning".into(), meaning);
3102 if let Some(enum_key) = enrichment.and_then(|e| e.enum_key.as_ref()) {
3103 obj.insert("enum".into(), serde_json::json!(enum_key));
3104 }
3105
3106 if let serde_json::Value::Object(map) = entity {
3107 set_nested_value_json(map, site.target, serde_json::Value::Object(obj));
3108 }
3109 }
3110
3111 fn read_plain_string(entity: &serde_json::Value, target: &str) -> Option<String> {
3114 let mut current = entity;
3115 for part in target.split('.') {
3116 current = current.get(part)?;
3117 }
3118 current.as_str().map(str::to_string)
3119 }
3120
3121 pub fn map_interchange(
3130 msg_engine: &MappingEngine,
3131 tx_engine: &MappingEngine,
3132 tree: &AssembledTree,
3133 transaction_group: &str,
3134 enrich_codes: bool,
3135 ) -> crate::model::MappedMessage {
3136 let mut mapped =
3137 Self::translate_edifact_to_bo4e(msg_engine, tx_engine, tree, transaction_group);
3138 if enrich_codes {
3139 Self::enrich_bo4e_types(msg_engine, tx_engine, &mut mapped);
3140 }
3141 mapped
3142 }
3143
3144 #[doc(hidden)]
3153 pub fn map_interchange_inner_for_test(
3154 msg_engine: &MappingEngine,
3155 tx_engine: &MappingEngine,
3156 tree: &AssembledTree,
3157 transaction_group: &str,
3158 enrich_codes: bool,
3159 ) -> crate::model::MappedMessage {
3160 Self::map_interchange_inner(msg_engine, tx_engine, tree, transaction_group, enrich_codes)
3161 }
3162
3163 pub(crate) fn map_interchange_inner(
3164 msg_engine: &MappingEngine,
3165 tx_engine: &MappingEngine,
3166 tree: &AssembledTree,
3167 transaction_group: &str,
3168 enrich_codes: bool,
3169 ) -> crate::model::MappedMessage {
3170 let (stammdaten, nesting_info) = msg_engine.map_all_forward_inner(tree, enrich_codes);
3172
3173 let transaktionen = tree
3175 .groups
3176 .iter()
3177 .find(|g| g.group_id == transaction_group)
3178 .map(|sg| {
3179 sg.repetitions
3180 .iter()
3181 .map(|instance| {
3182 let wrapped_tree = AssembledTree {
3185 segments: vec![],
3186 groups: vec![AssembledGroup {
3187 group_id: transaction_group.to_string(),
3188 repetitions: vec![instance.clone()],
3189 }],
3190 post_group_start: 0,
3191 inter_group_segments: std::collections::BTreeMap::new(),
3192 };
3193
3194 let (tx_result, tx_nesting) = tx_engine.map_all_forward_inner_with_tx(
3198 &wrapped_tree,
3199 enrich_codes,
3200 Some(transaction_group),
3201 );
3202
3203 let mut tx_result = tx_result;
3208 let transaktionsdaten = crate::model::take_entity(
3209 &mut tx_result,
3210 crate::model::TX_METADATA_ENTITY,
3211 );
3212
3213 crate::model::MappedTransaktion {
3214 stammdaten: tx_result,
3215 transaktionsdaten,
3216 nesting_info: tx_nesting,
3217 }
3218 })
3219 .collect()
3220 })
3221 .unwrap_or_default();
3222
3223 let mut stammdaten = stammdaten;
3225 let nachricht_meta =
3226 crate::model::take_entity(&mut stammdaten, crate::model::MSG_METADATA_ENTITY);
3227
3228 crate::model::MappedMessage {
3229 stammdaten,
3230 nachricht_meta,
3231 transaktionen,
3232 nesting_info,
3233 inter_group_segments: tree.inter_group_segments.clone(),
3234 }
3235 }
3236
3237 pub fn map_interchange_reverse(
3247 msg_engine: &MappingEngine,
3248 tx_engine: &MappingEngine,
3249 mapped: &crate::model::MappedMessage,
3250 transaction_group: &str,
3251 filtered_mig: Option<&MigSchema>,
3252 ) -> AssembledTree {
3253 let _owned_msg: Option<serde_json::Value>;
3261 let msg_stammdaten = if !mapped.nachricht_meta.is_null() {
3262 let mut merged = mapped.stammdaten.clone();
3263 crate::model::restore_entity(
3264 &mut merged,
3265 crate::model::MSG_METADATA_ENTITY,
3266 &mapped.nachricht_meta,
3267 );
3268 _owned_msg = Some(merged);
3269 _owned_msg.as_ref().unwrap()
3270 } else {
3271 _owned_msg = None;
3272 &mapped.stammdaten
3273 };
3274
3275 let msg_tree = msg_engine.map_all_reverse_with_mig(
3276 msg_stammdaten,
3277 if mapped.nesting_info.is_empty() {
3278 None
3279 } else {
3280 Some(&mapped.nesting_info)
3281 },
3282 filtered_mig,
3283 );
3284
3285 let mut sg4_reps: Vec<AssembledGroupInstance> = Vec::new();
3287
3288 struct DefWithMeta<'a> {
3292 def: &'a MappingDefinition,
3293 relative: String,
3294 depth: usize,
3295 }
3296
3297 let mut sorted_defs: Vec<DefWithMeta> = tx_engine
3298 .definitions
3299 .iter()
3300 .filter(|def| {
3303 def.meta.parent_field.is_none() && !is_bound_child(&tx_engine.definitions, def)
3304 })
3305 .map(|def| {
3306 let relative = strip_tx_group_prefix(&def.meta.source_group, transaction_group);
3307 let depth = if relative.is_empty() {
3308 0
3309 } else {
3310 relative.chars().filter(|c| *c == '.').count() + 1
3311 };
3312 DefWithMeta {
3313 def,
3314 relative,
3315 depth,
3316 }
3317 })
3318 .collect();
3319
3320 let mut parent_rep_map: std::collections::HashMap<String, usize> =
3324 std::collections::HashMap::new();
3325 for dm in &sorted_defs {
3326 if dm.depth >= 2 {
3327 let parts: Vec<&str> = dm.relative.split('.').collect();
3328 let (_, parent_rep) = parse_group_spec(parts[0]);
3329 if let Some(rep_idx) = parent_rep {
3330 if let Some(sp) = &dm.def.meta.source_path {
3331 if let Some((parent_path, _)) = sp.rsplit_once('.') {
3332 parent_rep_map
3333 .entry(parent_path.to_string())
3334 .or_insert(rep_idx);
3335 }
3336 }
3337 }
3338 }
3339 }
3340
3341 for dm in &mut sorted_defs {
3344 if dm.depth == 1 && !dm.relative.contains(':') {
3345 if let Some(sp) = &dm.def.meta.source_path {
3346 if let Some(rep_idx) = parent_rep_map.get(sp.as_str()) {
3347 dm.relative = format!("{}:{}", dm.relative, rep_idx);
3348 }
3349 }
3350 }
3351 }
3352
3353 if let Some(mig) = filtered_mig {
3360 let mig_order = build_reverse_mig_group_order(mig, transaction_group);
3361 sorted_defs.sort_by(|a, b| {
3362 a.depth.cmp(&b.depth).then_with(|| {
3363 let a_id = a.relative.split(':').next().unwrap_or(&a.relative);
3364 let b_id = b.relative.split(':').next().unwrap_or(&b.relative);
3365 let a_pos = variant_mig_position(a.def, a_id, &mig_order);
3367 let b_pos = variant_mig_position(b.def, b_id, &mig_order);
3368 a_pos.cmp(&b_pos).then(a.relative.cmp(&b.relative))
3369 })
3370 });
3371 } else {
3372 sorted_defs.sort_by(|a, b| a.depth.cmp(&b.depth).then(a.relative.cmp(&b.relative)));
3373 }
3374
3375 for tx in &mapped.transaktionen {
3376 let mut root_segs: Vec<AssembledSegment> = Vec::new();
3377 let mut child_groups: Vec<AssembledGroup> = Vec::new();
3378
3379 let _owned_tx: Option<serde_json::Value>;
3382 let tx_stammdaten: &serde_json::Value = if !tx.transaktionsdaten.is_null() {
3383 let mut merged = tx.stammdaten.clone();
3384 crate::model::restore_entity(
3385 &mut merged,
3386 crate::model::TX_METADATA_ENTITY,
3387 &tx.transaktionsdaten,
3388 );
3389 _owned_tx = Some(merged);
3390 _owned_tx.as_ref().unwrap()
3391 } else {
3392 _owned_tx = None;
3393 &tx.stammdaten
3394 };
3395
3396 let mut source_path_to_rep: std::collections::HashMap<String, Vec<usize>> =
3401 std::collections::HashMap::new();
3402
3403 for dm in &sorted_defs {
3404 let entity_key = to_camel_case(&dm.def.meta.entity);
3407 let _tx_extracted: Option<serde_json::Value>;
3408 let bo4e_value = if let Some(v) = tx_stammdaten.get(&entity_key) {
3409 _tx_extracted = None;
3410 v
3411 } else if dm.def.meta.source_group.contains('.') {
3412 match extract_child_from_parent(tx_stammdaten, &tx_engine.definitions, dm.def) {
3413 Some(v) => {
3414 _tx_extracted = Some(v);
3415 _tx_extracted.as_ref().unwrap()
3416 }
3417 None => continue,
3418 }
3419 } else {
3420 continue;
3421 };
3422
3423 let unwrapped_value: Option<serde_json::Value>;
3425 let bo4e_value = if bo4e_value.is_object() && !bo4e_value.is_array() {
3426 if let Some(disc_value) = dm
3427 .def
3428 .meta
3429 .discriminator
3430 .as_deref()
3431 .and_then(|d| d.split_once('='))
3432 .map(|(_, v)| v)
3433 {
3434 if let Some(inner) = bo4e_value.get(disc_value) {
3435 let mut injected = inner.clone();
3436 if let Some(qualifier_field) = find_qualifier_companion_field(
3437 &tx_engine.definitions,
3438 &dm.def.meta.entity,
3439 ) {
3440 if let Some(obj) = injected.as_object_mut() {
3441 obj.entry(qualifier_field).or_insert_with(|| {
3442 serde_json::Value::String(disc_value.to_string())
3443 });
3444 }
3445 }
3446 unwrapped_value = Some(injected);
3447 unwrapped_value.as_ref().unwrap()
3448 } else {
3449 bo4e_value
3450 }
3451 } else if is_map_keyed_object(bo4e_value) {
3452 let map = bo4e_value.as_object().unwrap();
3453 let arr: Vec<serde_json::Value> = map
3454 .iter()
3455 .map(|(key, val)| {
3456 let mut item = val.clone();
3457 if let Some(obj) = item.as_object_mut() {
3458 if let Some(qualifier_field) = find_qualifier_companion_field(
3459 &tx_engine.definitions,
3460 &dm.def.meta.entity,
3461 ) {
3462 let entry = obj
3463 .entry(qualifier_field)
3464 .or_insert(serde_json::Value::Null);
3465 if entry.is_null() {
3466 *entry = serde_json::Value::String(key.clone());
3467 }
3468 }
3469 }
3470 item
3471 })
3472 .collect();
3473 unwrapped_value = Some(serde_json::Value::Array(arr));
3474 unwrapped_value.as_ref().unwrap()
3475 } else {
3476 bo4e_value
3477 }
3478 } else {
3479 bo4e_value
3480 };
3481
3482 let items: Vec<&serde_json::Value> = if bo4e_value.is_array() {
3486 bo4e_value.as_array().unwrap().iter().collect()
3487 } else {
3488 vec![bo4e_value]
3489 };
3490
3491 for (item_idx, item) in items.iter().enumerate() {
3492 let instance = tx_engine.map_reverse(item, dm.def);
3493
3494 if instance.segments.is_empty() && instance.child_groups.is_empty() {
3496 continue;
3497 }
3498
3499 if dm.relative.is_empty() {
3500 root_segs.extend(instance.segments);
3507 for child in instance.child_groups {
3508 match child_groups
3509 .iter_mut()
3510 .find(|g| g.group_id == child.group_id)
3511 {
3512 Some(existing) => existing.repetitions.extend(child.repetitions),
3513 None => child_groups.push(child),
3514 }
3515 }
3516 } else {
3517 let effective_relative = if dm.depth >= 2 {
3521 let rel = if items.len() > 1 {
3524 strip_all_rep_indices(&dm.relative)
3525 } else {
3526 dm.relative.clone()
3527 };
3528 let skip_nesting = dm
3535 .def
3536 .meta
3537 .source_path
3538 .as_ref()
3539 .and_then(|sp| sp.rsplit_once('.'))
3540 .and_then(|(parent_path, _)| source_path_to_rep.get(parent_path))
3541 .is_some_and(|reps| reps.len() == 1);
3542 let nesting_idx = if items.len() > 1 && !skip_nesting {
3543 dm.def
3544 .meta
3545 .source_path
3546 .as_ref()
3547 .and_then(|sp| tx.nesting_info.get(sp))
3548 .and_then(|dist| dist.get(item_idx))
3549 .copied()
3550 } else {
3551 None
3552 };
3553 if let Some(parent_rep) = nesting_idx {
3554 let parts: Vec<&str> = rel.split('.').collect();
3556 let parent_id = parts[0].split(':').next().unwrap_or(parts[0]);
3557 let rest = parts[1..].join(".");
3558 format!("{}:{}.{}", parent_id, parent_rep, rest)
3559 } else {
3560 resolve_child_relative(
3561 &rel,
3562 dm.def.meta.source_path.as_deref(),
3563 &source_path_to_rep,
3564 item_idx,
3565 )
3566 }
3567 } else if dm.depth == 1 {
3568 let child_key = dm
3571 .def
3572 .meta
3573 .source_path
3574 .as_ref()
3575 .map(|sp| format!("{sp}#child"));
3576 if let Some(child_indices) =
3577 child_key.as_ref().and_then(|ck| tx.nesting_info.get(ck))
3578 {
3579 if let Some(&target) = child_indices.get(item_idx) {
3580 if target != usize::MAX {
3581 let base =
3582 dm.relative.split(':').next().unwrap_or(&dm.relative);
3583 format!("{}:{}", base, target)
3584 } else {
3585 dm.relative.clone()
3586 }
3587 } else if items.len() > 1 && item_idx > 0 {
3588 strip_rep_index(&dm.relative)
3589 } else {
3590 dm.relative.clone()
3591 }
3592 } else if items.len() > 1 && item_idx > 0 {
3593 strip_rep_index(&dm.relative)
3594 } else {
3595 dm.relative.clone()
3596 }
3597 } else if items.len() > 1 && item_idx > 0 {
3598 strip_rep_index(&dm.relative)
3601 } else {
3602 dm.relative.clone()
3603 };
3604
3605 let rep_used =
3606 place_in_groups(&mut child_groups, &effective_relative, instance);
3607
3608 if dm.depth == 1 {
3610 if let Some(sp) = &dm.def.meta.source_path {
3611 source_path_to_rep
3612 .entry(sp.clone())
3613 .or_default()
3614 .push(rep_used);
3615 }
3616 }
3617 }
3618 }
3619 }
3620
3621 sg4_reps.push(AssembledGroupInstance {
3622 segments: root_segs,
3623 child_groups,
3624 entry_mig_number: None,
3625 variant_mig_numbers: vec![],
3626 skipped_segments: Vec::new(),
3627 skipped_positions: Vec::new(),
3628 });
3629 }
3630
3631 let mut root_segments = Vec::new();
3638 let mut uns_segments = Vec::new();
3639 let mut uns_is_summary = false;
3640 let mut found_uns = false;
3641 for seg in msg_tree.segments {
3642 if seg.tag == "UNS" {
3643 uns_is_summary = seg
3645 .elements
3646 .first()
3647 .and_then(|el| el.first())
3648 .map(|v| v == "S")
3649 .unwrap_or(false);
3650 uns_segments.push(seg);
3651 found_uns = true;
3652 } else if found_uns {
3653 uns_segments.push(seg);
3655 } else {
3656 root_segments.push(seg);
3657 }
3658 }
3659
3660 let pre_group_count = root_segments.len();
3661 let mut all_groups = msg_tree.groups;
3662 let mut inter_group = msg_tree.inter_group_segments;
3663
3664 let sg_num = |id: &str| -> usize {
3666 id.strip_prefix("SG")
3667 .and_then(|n| n.parse::<usize>().ok())
3668 .unwrap_or(0)
3669 };
3670
3671 if !sg4_reps.is_empty() {
3672 if uns_is_summary {
3673 all_groups.push(AssembledGroup {
3675 group_id: transaction_group.to_string(),
3676 repetitions: sg4_reps,
3677 });
3678 if !uns_segments.is_empty() {
3679 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3684 let tx_num = sg_num(transaction_group);
3685 let uns_pos = all_groups
3686 .iter()
3687 .rposition(|g| sg_num(&g.group_id) <= tx_num)
3688 .map(|i| i + 1)
3689 .unwrap_or(all_groups.len());
3690 inter_group.insert(uns_pos, uns_segments);
3691 }
3692 } else {
3693 if !uns_segments.is_empty() {
3695 inter_group.insert(all_groups.len(), uns_segments);
3696 }
3697 all_groups.push(AssembledGroup {
3698 group_id: transaction_group.to_string(),
3699 repetitions: sg4_reps,
3700 });
3701 }
3702 } else if !uns_segments.is_empty() {
3703 if transaction_group.is_empty() {
3704 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3709 if uns_is_summary {
3710 inter_group.insert(all_groups.len(), uns_segments);
3711 } else {
3712 inter_group.insert(0, uns_segments);
3713 }
3714 } else {
3715 all_groups.sort_by_key(|g| sg_num(&g.group_id));
3719 let tx_num = sg_num(transaction_group);
3720 let uns_pos = all_groups
3721 .iter()
3722 .rposition(|g| sg_num(&g.group_id) <= tx_num)
3723 .map(|i| i + 1)
3724 .unwrap_or(all_groups.len());
3725 inter_group.insert(uns_pos, uns_segments);
3726 }
3727 }
3728
3729 for (k, segs) in &mapped.inter_group_segments {
3737 if segs.is_empty() {
3738 continue;
3739 }
3740 let existing_tags: std::collections::HashSet<String> = inter_group
3741 .get(k)
3742 .map(|v| v.iter().map(|s| s.tag.clone()).collect())
3743 .unwrap_or_default();
3744 for seg in segs {
3745 if existing_tags.contains(&seg.tag) {
3746 continue;
3747 }
3748 inter_group.entry(*k).or_default().push(seg.clone());
3749 }
3750 }
3751
3752 let mut tree = AssembledTree {
3753 segments: root_segments,
3754 groups: all_groups,
3755 post_group_start: pre_group_count,
3756 inter_group_segments: inter_group,
3757 };
3758
3759 if let Some(mig) = filtered_mig {
3766 mig_assembly::repetition_order::sort_repetitions_by_mig_variant(
3767 &mut tree,
3768 mig,
3769 (!transaction_group.is_empty()).then_some(transaction_group),
3770 );
3771 }
3772 tree
3773 }
3774
3775 pub fn build_group_from_bo4e(
3777 &self,
3778 bo4e_value: &serde_json::Value,
3779 def: &MappingDefinition,
3780 ) -> AssembledGroup {
3781 let instance = self.map_reverse(bo4e_value, def);
3782 let leaf_group = def
3783 .meta
3784 .source_group
3785 .rsplit('.')
3786 .next()
3787 .unwrap_or(&def.meta.source_group);
3788
3789 AssembledGroup {
3790 group_id: leaf_group.to_string(),
3791 repetitions: vec![instance],
3792 }
3793 }
3794
3795 pub fn map_interchange_typed<M, T>(
3803 msg_engine: &MappingEngine,
3804 tx_engine: &MappingEngine,
3805 tree: &AssembledTree,
3806 tx_group: &str,
3807 enrich_codes: bool,
3808 nachrichtendaten: crate::model::Nachrichtendaten,
3809 interchangedaten: crate::model::Interchangedaten,
3810 ) -> Result<crate::model::Interchange<M, T>, serde_json::Error>
3811 where
3812 M: serde::de::DeserializeOwned,
3813 T: serde::de::DeserializeOwned,
3814 {
3815 let mapped = Self::map_interchange(msg_engine, tx_engine, tree, tx_group, enrich_codes);
3816 let nachricht = mapped.into_dynamic_nachricht(nachrichtendaten);
3817 let dynamic = crate::model::DynamicInterchange {
3818 interchangedaten,
3819 nachrichten: vec![nachricht],
3820 };
3821 let value = serde_json::to_value(&dynamic)?;
3822 serde_json::from_value(value)
3823 }
3824
3825 pub fn map_interchange_reverse_typed<M, T>(
3832 msg_engine: &MappingEngine,
3833 tx_engine: &MappingEngine,
3834 nachricht: &crate::model::Nachricht<M, T>,
3835 tx_group: &str,
3836 ) -> Result<AssembledTree, serde_json::Error>
3837 where
3838 M: serde::Serialize,
3839 T: serde::Serialize,
3840 {
3841 let mut stammdaten = serde_json::to_value(&nachricht.stammdaten)?;
3844 crate::model::restore_message_metadata(&mut stammdaten, &nachricht.nachrichtendaten);
3845 let transaktionen: Vec<crate::model::MappedTransaktion> = nachricht
3846 .transaktionen
3847 .iter()
3848 .map(|t| {
3849 Ok(crate::model::MappedTransaktion {
3850 stammdaten: serde_json::to_value(t)?,
3851 transaktionsdaten: serde_json::Value::Null,
3852 nesting_info: Default::default(),
3853 })
3854 })
3855 .collect::<Result<Vec<_>, serde_json::Error>>()?;
3856 let mapped = crate::model::MappedMessage {
3857 stammdaten,
3858 nachricht_meta: serde_json::Value::Null,
3859 transaktionen,
3860 nesting_info: Default::default(),
3861 inter_group_segments: Default::default(),
3862 };
3863 Ok(Self::map_interchange_reverse(
3864 msg_engine, tx_engine, &mapped, tx_group, None,
3865 ))
3866 }
3867}
3868
3869fn parse_source_path_part(part: &str) -> (&str, Option<&str>) {
3876 if let Some(pos) = part.find('_') {
3880 let group = &part[..pos];
3881 let qualifier = &part[pos + 1..];
3882 if !qualifier.is_empty() {
3883 return (group, Some(qualifier));
3884 }
3885 }
3886 (part, None)
3887}
3888
3889fn build_reverse_mig_group_order(mig: &MigSchema, tx_group_id: &str) -> HashMap<String, usize> {
3897 let mut order = HashMap::new();
3898 if let Some(tg) = mig.segment_groups.iter().find(|g| g.id == tx_group_id) {
3899 for (i, nested) in tg.nested_groups.iter().enumerate() {
3900 if let Some(ref vc) = nested.variant_code {
3902 let variant_key = format!("{}_{}", nested.id, vc.to_uppercase());
3903 order.insert(variant_key, i);
3904 }
3905 order.entry(nested.id.clone()).or_insert(i);
3907 }
3908 }
3909 order
3910}
3911
3912fn variant_mig_position(
3918 def: &MappingDefinition,
3919 base_group_id: &str,
3920 mig_order: &HashMap<String, usize>,
3921) -> usize {
3922 if let Some(ref sp) = def.meta.source_path {
3925 let base_lower = base_group_id.to_lowercase();
3927 for part in sp.split('.') {
3928 if part.starts_with(&base_lower)
3929 || part.starts_with(base_group_id.to_lowercase().as_str())
3930 {
3931 if let Some(underscore_pos) = part.find('_') {
3933 let qualifier = &part[underscore_pos + 1..];
3934 let variant_key = format!("{}_{}", base_group_id, qualifier.to_uppercase());
3935 if let Some(&pos) = mig_order.get(&variant_key) {
3936 return pos;
3937 }
3938 }
3939 }
3940 }
3941 }
3942 mig_order.get(base_group_id).copied().unwrap_or(usize::MAX)
3944}
3945
3946fn find_rep_by_entry_qualifier<'a>(
3951 reps: &'a [AssembledGroupInstance],
3952 qualifier: &str,
3953) -> Option<&'a AssembledGroupInstance> {
3954 let parts: Vec<&str> = qualifier.split('_').collect();
3956 reps.iter().find(|inst| {
3957 inst.segments.first().is_some_and(|seg| {
3958 seg.elements
3959 .first()
3960 .and_then(|e| e.first())
3961 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3962 })
3963 })
3964}
3965
3966fn find_all_reps_by_entry_qualifier<'a>(
3968 reps: &'a [AssembledGroupInstance],
3969 qualifier: &str,
3970) -> Vec<&'a AssembledGroupInstance> {
3971 let parts: Vec<&str> = qualifier.split('_').collect();
3973 reps.iter()
3974 .filter(|inst| {
3975 inst.segments.first().is_some_and(|seg| {
3976 seg.elements
3977 .first()
3978 .and_then(|e| e.first())
3979 .is_some_and(|v| parts.iter().any(|part| v.eq_ignore_ascii_case(part)))
3980 })
3981 })
3982 .collect()
3983}
3984
3985fn has_source_path_qualifiers(source_path: &str) -> bool {
3987 source_path.split('.').any(|part| {
3988 if let Some(pos) = part.find('_') {
3989 pos < part.len() - 1
3990 } else {
3991 false
3992 }
3993 })
3994}
3995
3996fn parse_group_spec(part: &str) -> (&str, Option<usize>) {
3997 if let Some(colon_pos) = part.find(':') {
3998 let id = &part[..colon_pos];
3999 let rep = part[colon_pos + 1..].parse::<usize>().ok();
4000 (id, rep)
4001 } else {
4002 (part, None)
4003 }
4004}
4005
4006fn strip_tx_group_prefix(source_group: &str, tx_group: &str) -> String {
4012 if source_group == tx_group || source_group.is_empty() {
4013 String::new()
4014 } else if let Some(rest) = source_group.strip_prefix(tx_group) {
4015 rest.strip_prefix('.').unwrap_or(rest).to_string()
4016 } else {
4017 source_group.to_string()
4018 }
4019}
4020
4021fn place_in_groups(
4029 groups: &mut Vec<AssembledGroup>,
4030 relative_path: &str,
4031 instance: AssembledGroupInstance,
4032) -> usize {
4033 let parts: Vec<&str> = relative_path.split('.').collect();
4034
4035 if parts.len() == 1 {
4036 let (id, rep) = parse_group_spec(parts[0]);
4038
4039 let group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == id) {
4041 g
4042 } else {
4043 groups.push(AssembledGroup {
4044 group_id: id.to_string(),
4045 repetitions: vec![],
4046 });
4047 groups.last_mut().unwrap()
4048 };
4049
4050 if let Some(rep_idx) = rep {
4051 while group.repetitions.len() <= rep_idx {
4053 group.repetitions.push(AssembledGroupInstance {
4054 segments: vec![],
4055 child_groups: vec![],
4056 entry_mig_number: None,
4057 variant_mig_numbers: vec![],
4058 skipped_segments: Vec::new(),
4059 skipped_positions: Vec::new(),
4060 });
4061 }
4062 group.repetitions[rep_idx]
4063 .segments
4064 .extend(instance.segments);
4065 group.repetitions[rep_idx]
4066 .child_groups
4067 .extend(instance.child_groups);
4068 rep_idx
4069 } else {
4070 let pos = group.repetitions.len();
4072 group.repetitions.push(instance);
4073 pos
4074 }
4075 } else {
4076 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
4078 let rep_idx = parent_rep.unwrap_or(0);
4079
4080 let parent_group = if let Some(g) = groups.iter_mut().find(|g| g.group_id == parent_id) {
4082 g
4083 } else {
4084 groups.push(AssembledGroup {
4085 group_id: parent_id.to_string(),
4086 repetitions: vec![],
4087 });
4088 groups.last_mut().unwrap()
4089 };
4090
4091 while parent_group.repetitions.len() <= rep_idx {
4093 parent_group.repetitions.push(AssembledGroupInstance {
4094 segments: vec![],
4095 child_groups: vec![],
4096 entry_mig_number: None,
4097 variant_mig_numbers: vec![],
4098 skipped_segments: Vec::new(),
4099 skipped_positions: Vec::new(),
4100 });
4101 }
4102
4103 let remaining = parts[1..].join(".");
4104 place_in_groups(
4105 &mut parent_group.repetitions[rep_idx].child_groups,
4106 &remaining,
4107 instance,
4108 );
4109 rep_idx
4110 }
4111}
4112
4113fn resolve_child_relative(
4125 relative: &str,
4126 source_path: Option<&str>,
4127 source_path_to_rep: &std::collections::HashMap<String, Vec<usize>>,
4128 item_idx: usize,
4129) -> String {
4130 let parts: Vec<&str> = relative.split('.').collect();
4131 if parts.is_empty() {
4132 return relative.to_string();
4133 }
4134
4135 let (parent_id, parent_rep) = parse_group_spec(parts[0]);
4137 if parent_rep.is_some() {
4138 return relative.to_string();
4139 }
4140
4141 if let Some(sp) = source_path {
4143 if let Some((parent_path, _child)) = sp.rsplit_once('.') {
4144 if let Some(rep_indices) = source_path_to_rep.get(parent_path) {
4146 let rep_idx = rep_indices
4147 .get(item_idx)
4148 .or_else(|| rep_indices.last())
4149 .copied()
4150 .unwrap_or(0);
4151 let rest = parts[1..].join(".");
4152 return format!("{}:{}.{}", parent_id, rep_idx, rest);
4153 }
4154 let prefix = format!("{}_", parent_path);
4162 let mut unioned: Vec<usize> = source_path_to_rep
4163 .iter()
4164 .filter(|(k, _)| k.starts_with(&prefix))
4165 .flat_map(|(_, v)| v.iter().copied())
4166 .collect();
4167 if !unioned.is_empty() {
4168 unioned.sort_unstable();
4169 unioned.dedup();
4170 let rep_idx = unioned
4171 .get(item_idx)
4172 .or_else(|| unioned.last())
4173 .copied()
4174 .unwrap_or(0);
4175 let rest = parts[1..].join(".");
4176 return format!("{}:{}.{}", parent_id, rep_idx, rest);
4177 }
4178 }
4179 }
4180
4181 relative.to_string()
4183}
4184
4185struct DiscriminatorMatcher<'a> {
4192 tag: &'a str,
4193 element_idx: usize,
4194 component_idx: usize,
4195 expected_values: Vec<&'a str>,
4196 occurrence: Option<usize>,
4198}
4199
4200impl<'a> DiscriminatorMatcher<'a> {
4201 fn parse(disc: &'a str) -> Option<Self> {
4202 let (spec, expected) = disc.split_once('=')?;
4203 let parts: Vec<&str> = spec.split('.').collect();
4204 if parts.len() != 3 {
4205 return None;
4206 }
4207 let (expected_raw, occurrence) = parse_discriminator_occurrence(expected);
4208 Some(Self {
4209 tag: parts[0],
4210 element_idx: parts[1].parse().ok()?,
4211 component_idx: parts[2].parse().ok()?,
4212 expected_values: expected_raw.split('|').collect(),
4213 occurrence,
4214 })
4215 }
4216
4217 fn matches(&self, instance: &AssembledGroupInstance) -> bool {
4218 instance.segments.iter().any(|s| {
4219 s.tag.eq_ignore_ascii_case(self.tag)
4220 && s.elements
4221 .get(self.element_idx)
4222 .and_then(|e| e.get(self.component_idx))
4223 .map(|v| self.expected_values.iter().any(|ev| v == ev))
4224 .unwrap_or(false)
4225 })
4226 }
4227
4228 fn filter_instances<'b>(
4230 &self,
4231 instances: Vec<&'b AssembledGroupInstance>,
4232 ) -> Vec<&'b AssembledGroupInstance> {
4233 let matching: Vec<_> = instances
4234 .into_iter()
4235 .filter(|inst| self.matches(inst))
4236 .collect();
4237 if let Some(occ) = self.occurrence {
4238 matching.into_iter().nth(occ).into_iter().collect()
4239 } else {
4240 matching
4241 }
4242 }
4243}
4244
4245fn parse_discriminator_occurrence(expected: &str) -> (&str, Option<usize>) {
4251 if let Some(hash_pos) = expected.rfind('#') {
4252 if let Ok(occ) = expected[hash_pos + 1..].parse::<usize>() {
4253 return (&expected[..hash_pos], Some(occ));
4254 }
4255 }
4256 (expected, None)
4257}
4258
4259fn strip_rep_index(relative: &str) -> String {
4263 let (id, _) = parse_group_spec(relative);
4264 id.to_string()
4265}
4266
4267pub(crate) fn strip_all_rep_indices(relative: &str) -> String {
4272 relative
4273 .split('.')
4274 .map(|part| {
4275 let (id, _) = parse_group_spec(part);
4276 id
4277 })
4278 .collect::<Vec<_>>()
4279 .join(".")
4280}
4281
4282pub fn is_nested_child_of(child: &MappingDefinition, parent: &MappingDefinition) -> bool {
4292 if child.meta.parent_field.is_none() || child.meta.entity != parent.meta.entity {
4293 return false;
4294 }
4295 let child_sg = strip_all_rep_indices(&child.meta.source_group);
4296 let parent_sg = strip_all_rep_indices(&parent.meta.source_group);
4297 match child_sg.rsplit_once('.') {
4298 Some((head, _)) if head.eq_ignore_ascii_case(&parent_sg) => {}
4299 _ => return false,
4300 }
4301 let (Some(child_sp), Some(parent_sp)) = (
4302 child.meta.source_path.as_deref(),
4303 parent.meta.source_path.as_deref(),
4304 ) else {
4305 return true;
4306 };
4307 let Some((child_parent_sp, _)) = child_sp.rsplit_once('.') else {
4308 return false;
4309 };
4310 let child_parts: Vec<&str> = child_parent_sp.split('.').collect();
4311 let parent_parts: Vec<&str> = parent_sp.split('.').collect();
4312 child_parts.len() == parent_parts.len()
4313 && child_parts.iter().zip(&parent_parts).all(|(c, p)| {
4314 let (c_id, c_q) = parse_source_path_part(c);
4315 let (p_id, p_q) = parse_source_path_part(p);
4316 c_id.eq_ignore_ascii_case(p_id)
4317 && match (c_q, p_q) {
4318 (Some(cq), Some(pq)) => cq.eq_ignore_ascii_case(pq),
4319 _ => true,
4320 }
4321 })
4322}
4323
4324pub fn is_bound_child_of(child: &MappingDefinition, parent: &MappingDefinition) -> bool {
4339 if std::ptr::eq(child, parent)
4340 || child.meta.parent_field.is_some()
4341 || parent.meta.parent_field.is_some()
4342 || child.meta.target_list.is_some()
4343 || parent.meta.target_list.is_some()
4344 || child.meta.entity != parent.meta.entity
4345 {
4346 return false;
4347 }
4348 let (Some(child_sp), Some(parent_sp)) = (
4349 child.meta.source_path.as_deref(),
4350 parent.meta.source_path.as_deref(),
4351 ) else {
4352 return false;
4353 };
4354 parent_sp.contains('.')
4355 && child_sp
4356 .rsplit_once('.')
4357 .is_some_and(|(head, _)| head.eq_ignore_ascii_case(parent_sp))
4358}
4359
4360pub fn is_bound_child(definitions: &[MappingDefinition], def: &MappingDefinition) -> bool {
4363 definitions.iter().any(|p| is_bound_child_of(def, p))
4364}
4365
4366fn nested_parent_qualifier(child: &MappingDefinition) -> Option<&str> {
4369 let (parent_path, _) = child.meta.source_path.as_deref()?.rsplit_once('.')?;
4370 let last = parent_path.rsplit('.').next()?;
4371 parse_source_path_part(last).1
4372}
4373
4374fn nested_child_leaf(child: &MappingDefinition) -> (String, Option<&str>) {
4377 let leaf_group = strip_all_rep_indices(
4378 child
4379 .meta
4380 .source_group
4381 .rsplit('.')
4382 .next()
4383 .unwrap_or(&child.meta.source_group),
4384 );
4385 let leaf_qualifier = child
4386 .meta
4387 .source_path
4388 .as_deref()
4389 .and_then(|sp| sp.rsplit('.').next())
4390 .and_then(|part| parse_source_path_part(part).1);
4391 (leaf_group, leaf_qualifier)
4392}
4393
4394fn bound_child_reps<'i>(
4398 instance: &'i AssembledGroupInstance,
4399 child: &MappingDefinition,
4400) -> Vec<&'i AssembledGroupInstance> {
4401 let (leaf_id, leaf_qualifier) = nested_child_leaf(child);
4402 let Some(group) = instance
4403 .child_groups
4404 .iter()
4405 .find(|g| g.group_id.eq_ignore_ascii_case(&leaf_id))
4406 else {
4407 return Vec::new();
4408 };
4409 let reps: Vec<&AssembledGroupInstance> = match leaf_qualifier {
4410 Some(q) => find_all_reps_by_entry_qualifier(&group.repetitions, q),
4411 None => group.repetitions.iter().collect(),
4412 };
4413 match child
4414 .meta
4415 .discriminator
4416 .as_deref()
4417 .and_then(DiscriminatorMatcher::parse)
4418 {
4419 Some(matcher) => matcher.filter_instances(reps),
4420 None => reps,
4421 }
4422}
4423
4424fn push_child_reps(
4426 instance: &mut AssembledGroupInstance,
4427 leaf_id: String,
4428 reps: Vec<AssembledGroupInstance>,
4429) {
4430 match instance
4431 .child_groups
4432 .iter_mut()
4433 .find(|g| g.group_id.eq_ignore_ascii_case(&leaf_id))
4434 {
4435 Some(group) => group.repetitions.extend(reps),
4436 None => instance.child_groups.push(AssembledGroup {
4437 group_id: leaf_id,
4438 repetitions: reps,
4439 }),
4440 }
4441}
4442
4443fn entry_qualifier_matches(instance: &AssembledGroupInstance, qualifier: &str) -> bool {
4446 segment_qualifier_matches(instance.segments.first(), qualifier)
4447}
4448
4449fn rebuilt_entry_qualifier_matches(
4454 instance: &AssembledGroupInstance,
4455 def: &MappingDefinition,
4456 qualifier: &str,
4457) -> bool {
4458 let entry_tag = def
4459 .meta
4460 .discriminator
4461 .as_deref()
4462 .and_then(|d| d.split('.').next())
4463 .filter(|tag| !tag.is_empty());
4464 let entry = match entry_tag {
4465 Some(tag) => instance
4466 .segments
4467 .iter()
4468 .find(|s| s.tag.eq_ignore_ascii_case(tag)),
4469 None => instance.segments.first(),
4470 };
4471 segment_qualifier_matches(entry, qualifier)
4472}
4473
4474fn segment_qualifier_matches(segment: Option<&AssembledSegment>, qualifier: &str) -> bool {
4475 segment
4476 .and_then(|seg| seg.elements.first())
4477 .and_then(|e| e.first())
4478 .is_some_and(|v| qualifier.split('_').any(|q| v.eq_ignore_ascii_case(q)))
4479}
4480
4481fn field_is_filled(bo4e_value: &serde_json::Value, field: &str) -> bool {
4487 let mut current = bo4e_value;
4488 for part in field.split('.') {
4489 match current.get(part) {
4490 Some(v) => current = v,
4491 None => return false,
4492 }
4493 }
4494 match current {
4495 serde_json::Value::String(s) => !s.is_empty(),
4496 serde_json::Value::Array(a) => !a.is_empty(),
4497 serde_json::Value::Object(o) => !o.is_empty(),
4498 serde_json::Value::Number(_) | serde_json::Value::Bool(_) => true,
4499 serde_json::Value::Null => false,
4500 }
4501}
4502
4503pub(crate) fn list_target(target: &str) -> Option<(&str, &str)> {
4506 let (list, sub) = target.split_once("[].")?;
4507 (!list.is_empty() && !sub.is_empty()).then_some((list, sub))
4508}
4509
4510pub(crate) fn parse_tag_qualifier(tag_part: &str) -> (String, Option<&str>, usize) {
4517 if let Some(bracket_start) = tag_part.find('[') {
4518 let tag = tag_part[..bracket_start].to_uppercase();
4519 let inner = tag_part[bracket_start + 1..].trim_end_matches(']');
4520 if let Some(comma_pos) = inner.find(',') {
4521 let qualifier = &inner[..comma_pos];
4522 let index = inner[comma_pos + 1..].parse::<usize>().unwrap_or(0);
4523 if qualifier == "*" {
4525 (tag, None, index)
4526 } else {
4527 (tag, Some(qualifier), index)
4528 }
4529 } else {
4530 (tag, Some(inner), 0)
4531 }
4532 } else {
4533 (tag_part.to_uppercase(), None, 0)
4534 }
4535}
4536
4537pub fn deep_merge_insert(
4543 result: &mut serde_json::Map<String, serde_json::Value>,
4544 entity: &str,
4545 bo4e: serde_json::Value,
4546) {
4547 merge_entity(result, entity, bo4e, false);
4548}
4549
4550fn merge_entity(
4562 result: &mut serde_json::Map<String, serde_json::Value>,
4563 entity: &str,
4564 bo4e: serde_json::Value,
4565 keep_both: bool,
4566) {
4567 if let Some(existing) = result.get_mut(entity) {
4568 if let (Some(existing_arr), Some(new_arr)) =
4571 (existing.as_array().map(|a| a.len()), bo4e.as_array())
4572 {
4573 if existing_arr == new_arr.len() {
4574 let existing_arr = existing.as_array_mut().unwrap();
4575 for (existing_elem, new_elem) in existing_arr.iter_mut().zip(new_arr) {
4576 if let (Some(existing_map), Some(new_map)) =
4577 (existing_elem.as_object_mut(), new_elem.as_object())
4578 {
4579 for (k, v) in new_map {
4580 if let Some(existing_v) = existing_map.get_mut(k) {
4581 if let (Some(existing_inner), Some(new_inner)) =
4582 (existing_v.as_object_mut(), v.as_object())
4583 {
4584 for (ik, iv) in new_inner {
4585 existing_inner
4586 .entry(ik.clone())
4587 .or_insert_with(|| iv.clone());
4588 }
4589 }
4590 } else {
4591 existing_map.insert(k.clone(), v.clone());
4592 }
4593 }
4594 }
4595 }
4596 return;
4597 }
4598 }
4599 if let (Some(existing_map), serde_json::Value::Object(new_map)) =
4601 (existing.as_object_mut(), &bo4e)
4602 {
4603 for (k, v) in new_map {
4604 if let Some(existing_v) = existing_map.get_mut(k) {
4605 if let (Some(existing_inner), Some(new_inner)) =
4607 (existing_v.as_object_mut(), v.as_object())
4608 {
4609 for (ik, iv) in new_inner {
4610 existing_inner
4611 .entry(ik.clone())
4612 .or_insert_with(|| iv.clone());
4613 }
4614 }
4615 } else {
4617 existing_map.insert(k.clone(), v.clone());
4618 }
4619 }
4620 return;
4621 }
4622 if !keep_both {
4623 result.insert(entity.to_string(), bo4e);
4624 return;
4625 }
4626 let existing_items = match std::mem::take(existing) {
4628 serde_json::Value::Array(items) => items,
4629 other => vec![other],
4630 };
4631 let new_items = match bo4e {
4632 serde_json::Value::Array(items) => items,
4633 other => vec![other],
4634 };
4635 *existing = serde_json::Value::Array(existing_items.into_iter().chain(new_items).collect());
4636 return;
4637 }
4638 result.insert(entity.to_string(), bo4e);
4639}
4640
4641fn append_to_list_field(
4654 result: &mut serde_json::Map<String, serde_json::Value>,
4655 entity: &str,
4656 list_field: &str,
4657 bo4e: serde_json::Value,
4658) {
4659 let mut items = match bo4e {
4660 serde_json::Value::Array(a) => a,
4661 other => vec![other],
4662 };
4663 items.retain(|v| !v.as_object().is_some_and(|o| o.is_empty()));
4664 if items.is_empty() {
4665 return;
4666 }
4667 let entry = result
4668 .entry(entity.to_string())
4669 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
4670 let Some(obj) = entry.as_object_mut() else {
4674 return;
4675 };
4676 match obj.get_mut(list_field).and_then(|v| v.as_array_mut()) {
4677 Some(existing) => existing.extend(items),
4678 None => {
4679 obj.insert(list_field.to_string(), serde_json::Value::Array(items));
4680 }
4681 }
4682}
4683
4684fn is_map_keyed_object(value: &serde_json::Value) -> bool {
4695 let Some(obj) = value.as_object() else {
4696 return false;
4697 };
4698 if obj.is_empty() {
4699 return false;
4700 }
4701 obj.iter().all(|(k, v)| {
4703 k.len() <= 5
4704 && k.chars()
4705 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
4706 && v.is_object()
4707 })
4708}
4709
4710fn find_qualifier_companion_field(
4719 definitions: &[crate::definition::MappingDefinition],
4720 entity: &str,
4721) -> Option<String> {
4722 for def in definitions {
4723 if def.meta.entity != *entity || def.meta.parent_field.is_some() {
4724 continue;
4725 }
4726 let disc = def.meta.discriminator.as_deref()?;
4727 let (disc_path, _) = disc.split_once('=')?;
4728 let disc_path_lower = disc_path.to_lowercase();
4729
4730 for (path, mapping) in &def.fields {
4733 let cf_path = path.to_lowercase();
4734 let matches = cf_path == disc_path_lower || format!("{}.0", cf_path) == disc_path_lower;
4735 if matches {
4736 let target = match mapping {
4737 FieldMapping::Simple(t) => t.as_str(),
4738 FieldMapping::Structured(s) => s.target.as_str(),
4739 FieldMapping::Nested(_) => continue,
4740 };
4741 if !target.is_empty() {
4742 return Some(target.to_string());
4743 }
4744 }
4745 }
4746 }
4747 None
4748}
4749
4750fn extract_child_from_parent(
4759 entities: &serde_json::Value,
4760 definitions: &[MappingDefinition],
4761 child_def: &MappingDefinition,
4762) -> Option<serde_json::Value> {
4763 extract_child_from_parent_with_indices(entities, definitions, child_def).map(|(v, _)| v)
4764}
4765
4766fn extract_child_from_parent_with_indices(
4771 entities: &serde_json::Value,
4772 definitions: &[MappingDefinition],
4773 child_def: &MappingDefinition,
4774) -> Option<(serde_json::Value, Vec<usize>)> {
4775 let parts: Vec<&str> = child_def.meta.source_group.split('.').collect();
4776 if parts.len() < 2 {
4777 return None;
4778 }
4779 let parent_group = parts[0];
4780 let parent_def = definitions
4781 .iter()
4782 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_def.meta.entity)?;
4783 let parent_key = to_camel_case(&parent_def.meta.entity);
4784 let child_key = to_camel_case(&child_def.meta.entity);
4785 let parent_value = entities.get(&parent_key)?;
4786
4787 if let Some(parent_map) = parent_value.as_object() {
4789 if is_map_keyed_value(parent_map) {
4790 let mut children: Vec<serde_json::Value> = Vec::new();
4791 let mut indices: Vec<usize> = Vec::new();
4792 for (i, (_key, inner)) in parent_map.iter().enumerate() {
4793 if let Some(child) = inner.get(&child_key) {
4794 if !child.is_null() {
4795 children.push(child.clone());
4796 indices.push(i);
4797 }
4798 }
4799 }
4800 return match children.len() {
4801 0 => None,
4802 1 => Some((children.into_iter().next().unwrap(), indices)),
4803 _ => Some((serde_json::Value::Array(children), indices)),
4804 };
4805 }
4806 }
4807
4808 if let Some(parent_arr) = parent_value.as_array() {
4810 let mut children: Vec<serde_json::Value> = Vec::new();
4811 let mut indices: Vec<usize> = Vec::new();
4812 for (i, item) in parent_arr.iter().enumerate() {
4813 if let Some(child) = item.get(&child_key) {
4814 if !child.is_null() {
4815 children.push(child.clone());
4816 indices.push(i);
4817 }
4818 }
4819 }
4820 return match children.len() {
4821 0 => None,
4822 1 => Some((children.into_iter().next().unwrap(), indices)),
4823 _ => Some((serde_json::Value::Array(children), indices)),
4824 };
4825 }
4826
4827 let child = parent_value.get(&child_key)?;
4829 if child.is_null() {
4830 return None;
4831 }
4832 Some((child.clone(), vec![0]))
4833}
4834
4835fn nest_child_entities_in_result(
4841 result: &mut serde_json::Map<String, serde_json::Value>,
4842 definitions: &[MappingDefinition],
4843 nesting_info: &std::collections::HashMap<String, Vec<usize>>,
4844 transaction_group: Option<&str>,
4845) {
4846 let nesting_pairs = child_entity_nesting_pairs(definitions, transaction_group);
4847
4848 for (_parent_group, parent_entity, child_entity, child_source_path) in nesting_pairs {
4849 let parent_key = to_camel_case(&parent_entity);
4850 let child_key = to_camel_case(&child_entity);
4851
4852 let child_value = match result.remove(&child_key) {
4854 Some(v) => v,
4855 None => continue,
4856 };
4857
4858 let Some(parent_value) = result.get_mut(&parent_key) else {
4863 result.insert(child_key, child_value);
4865 continue;
4866 };
4867 if parent_value.is_array() {
4868 result.insert(child_key, child_value);
4869 continue;
4870 }
4871
4872 let distribution = child_source_path
4874 .as_deref()
4875 .and_then(|sp| nesting_info.get(sp));
4876
4877 let child_items: Vec<(usize, &serde_json::Value)> = match &child_value {
4879 serde_json::Value::Array(arr) => arr.iter().enumerate().collect(),
4880 other => vec![(0, other)],
4881 };
4882
4883 let insert_or_append = |obj: &mut serde_json::Map<String, serde_json::Value>,
4886 key: &str,
4887 val: &serde_json::Value| {
4888 match obj.get_mut(key) {
4889 Some(existing) => {
4890 if !existing.is_array() {
4892 let prev = existing.take();
4893 *existing = serde_json::Value::Array(vec![prev]);
4894 }
4895 if let Some(arr) = existing.as_array_mut() {
4896 arr.push(val.clone());
4897 }
4898 }
4899 None => {
4900 obj.insert(key.to_string(), val.clone());
4901 }
4902 }
4903 };
4904
4905 if let Some(parent_map) = parent_value.as_object_mut() {
4907 if is_map_keyed_value(parent_map) {
4908 let keys: Vec<String> = parent_map.keys().cloned().collect();
4910 for (i, child_item) in &child_items {
4911 let target_idx = distribution
4912 .and_then(|dist| dist.get(*i))
4913 .copied()
4914 .unwrap_or(0);
4915 if let Some(key) = keys.get(target_idx) {
4916 if let Some(inner) = parent_map.get_mut(key).and_then(|v| v.as_object_mut())
4917 {
4918 insert_or_append(inner, &child_key, child_item);
4919 }
4920 }
4921 }
4922 continue;
4923 }
4924 }
4925
4926 if let Some(parent_arr) = parent_value.as_array_mut() {
4928 for (i, child_item) in &child_items {
4929 let target_idx = distribution
4930 .and_then(|dist| dist.get(*i))
4931 .copied()
4932 .unwrap_or(0);
4933 if let Some(parent_obj) = parent_arr
4934 .get_mut(target_idx)
4935 .and_then(|v| v.as_object_mut())
4936 {
4937 insert_or_append(parent_obj, &child_key, child_item);
4938 }
4939 }
4940 continue;
4941 }
4942
4943 if let Some(parent_obj) = parent_value.as_object_mut() {
4945 for (_i, child_item) in &child_items {
4946 insert_or_append(parent_obj, &child_key, child_item);
4947 }
4948 continue;
4949 }
4950
4951 result.insert(child_key, child_value);
4953 }
4954}
4955
4956pub(crate) fn child_entity_nesting_pairs(
4966 definitions: &[MappingDefinition],
4967 transaction_group: Option<&str>,
4968) -> Vec<(String, String, String, Option<String>)> {
4969 let mut nesting_pairs: Vec<(String, String, String, Option<String>)> = Vec::new();
4972 for def in definitions {
4973 let parts: Vec<&str> = def.meta.source_group.split('.').collect();
4974 if parts.len() < 2 || def.meta.parent_field.is_some() {
4975 continue;
4976 }
4977 let parent_group = parts[0];
4978 if transaction_group.is_some_and(|tx| tx == parent_group) {
4984 continue;
4985 }
4986 let child_entity = def.meta.entity.clone();
4987 let child_has_parent_level_def = definitions
4991 .iter()
4992 .any(|d| d.meta.source_group == parent_group && d.meta.entity == child_entity);
4993 if child_has_parent_level_def {
4994 continue;
4995 }
4996 let parent_entity = definitions
4998 .iter()
4999 .find(|d| d.meta.source_group == parent_group && d.meta.entity != child_entity)
5000 .map(|d| d.meta.entity.clone());
5001 if let Some(ref parent_entity) = parent_entity {
5002 let child_key_lc = to_camel_case(&child_entity);
5007 let parent_defs: Vec<_> = definitions
5008 .iter()
5009 .filter(|d| d.meta.entity == *parent_entity)
5010 .collect();
5011 let has_conflicting_field = parent_defs.iter().any(|pd| {
5012 pd.fields.values().any(|fm| {
5013 let target = match fm {
5014 crate::definition::FieldMapping::Simple(t) => t.as_str(),
5015 crate::definition::FieldMapping::Structured(s) => s.target.as_str(),
5016 crate::definition::FieldMapping::Nested(_) => "",
5017 };
5018 target.starts_with(&child_key_lc)
5019 && target.get(child_key_lc.len()..child_key_lc.len() + 1) == Some(".")
5020 })
5021 });
5022 if has_conflicting_field {
5023 continue;
5024 }
5025 if nesting_pairs
5027 .iter()
5028 .any(|(_, pe, ce, _)| *pe == *parent_entity && *ce == child_entity)
5029 {
5030 continue;
5031 }
5032 nesting_pairs.push((
5033 parent_group.to_string(),
5034 parent_entity.clone(),
5035 child_entity,
5036 def.meta.source_path.clone(),
5037 ));
5038 }
5039 }
5040
5041 nesting_pairs
5042}
5043
5044fn is_map_keyed_value(map: &serde_json::Map<String, serde_json::Value>) -> bool {
5046 if map.is_empty() {
5047 return false;
5048 }
5049 map.values().all(|v| v.is_object())
5050 && map.keys().all(|k| {
5051 k.len() <= 5
5052 || k.chars()
5053 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
5054 })
5055}
5056
5057#[derive(Clone)]
5060struct CodeSite<'a> {
5061 target: &'a str,
5062 parent_field: Option<&'a str>,
5068 source_path: &'a str,
5069 seg_tag: String,
5070 path_qualifier: Option<String>,
5072 disc_qualifier: Option<String>,
5074 element_idx: usize,
5075 component_idx: usize,
5076 enum_map: Option<&'a std::collections::BTreeMap<String, String>>,
5077 also_target: Option<&'a str>,
5078 also_enum_map: Option<&'a std::collections::BTreeMap<String, String>>,
5079}
5080
5081pub(crate) fn to_camel_case(name: &str) -> String {
5082 let mut chars = name.chars();
5083 match chars.next() {
5084 Some(c) => c.to_lowercase().to_string() + chars.as_str(),
5085 None => String::new(),
5086 }
5087}
5088
5089fn set_nested_value(map: &mut serde_json::Map<String, serde_json::Value>, path: &str, val: String) {
5092 set_nested_value_json(map, path, serde_json::Value::String(val));
5093}
5094
5095fn set_nested_value_json(
5097 map: &mut serde_json::Map<String, serde_json::Value>,
5098 path: &str,
5099 val: serde_json::Value,
5100) {
5101 if let Some((prefix, leaf)) = path.rsplit_once('.') {
5102 let mut current = map;
5103 for part in prefix.split('.') {
5104 let entry = current
5105 .entry(part.to_string())
5106 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
5107 current = entry.as_object_mut().expect("expected object in path");
5108 }
5109 current.insert(leaf.to_string(), val);
5110 } else {
5111 map.insert(path.to_string(), val);
5112 }
5113}
5114
5115#[derive(serde::Serialize, serde::Deserialize)]
5120pub struct VariantCache {
5121 pub message_defs: Vec<MappingDefinition>,
5123 pub transaction_defs: BTreeMap<String, Vec<MappingDefinition>>,
5125 pub combined_defs: BTreeMap<String, Vec<MappingDefinition>>,
5127 #[serde(default)]
5129 pub code_lookups: BTreeMap<String, crate::code_lookup::CodeLookup>,
5130 #[serde(default)]
5132 pub mig_schema: Option<mig_types::schema::mig::MigSchema>,
5133 #[serde(default)]
5135 pub segment_structure: Option<crate::segment_structure::SegmentStructure>,
5136 #[serde(skip)]
5141 pub code_lists: std::sync::Arc<crate::code_lists::CodeLists>,
5142 #[serde(default)]
5145 pub pid_segment_numbers: BTreeMap<String, Vec<String>>,
5146 #[serde(default)]
5149 pub pid_requirements: BTreeMap<String, crate::pid_requirements::PidRequirements>,
5150 #[serde(default)]
5155 pub pid_ahb_workflows: BTreeMap<String, ahb_types::AhbWorkflow>,
5156 #[serde(default)]
5160 pub tx_groups: BTreeMap<String, String>,
5161}
5162
5163impl VariantCache {
5164 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
5166 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
5167 path: path.display().to_string(),
5168 message: e.to_string(),
5169 })?;
5170 if let Some(parent) = path.parent() {
5171 std::fs::create_dir_all(parent)?;
5172 }
5173 std::fs::write(path, encoded)?;
5174 Ok(())
5175 }
5176
5177 pub fn load(path: &Path) -> Result<Self, MappingError> {
5179 let bytes = std::fs::read(path)?;
5180 let mut cache: Self =
5181 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
5182 path: path.display().to_string(),
5183 message: e.to_string(),
5184 })?;
5185 cache.code_lists = crate::code_lists::CodeLists::discover(path);
5186 Ok(cache)
5187 }
5188
5189 pub fn tx_group(&self, pid: &str) -> Option<&str> {
5193 self.tx_groups
5194 .get(&format!("pid_{pid}"))
5195 .map(|s| s.as_str())
5196 }
5197
5198 pub fn msg_engine(&self, pid: &str) -> MappingEngine {
5202 let mut eng = MappingEngine::from_definitions_with_code_lists(
5203 std::sync::Arc::clone(&self.code_lists),
5204 self.message_defs.clone(),
5205 )
5206 .with_pid(pid);
5207 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
5208 eng = eng.with_code_lookup(cl.clone());
5209 }
5210 eng
5211 }
5212
5213 pub fn tx_engine(&self, pid: &str) -> Option<MappingEngine> {
5217 self.transaction_defs
5218 .get(&format!("pid_{pid}"))
5219 .map(|defs| {
5220 let mut eng = MappingEngine::from_definitions_with_code_lists(
5221 std::sync::Arc::clone(&self.code_lists),
5222 defs.clone(),
5223 )
5224 .with_pid(pid);
5225 if let Some(cl) = self.code_lookups.get(&format!("pid_{pid}")) {
5226 eng = eng.with_code_lookup(cl.clone());
5227 }
5228 eng
5229 })
5230 }
5231
5232 pub fn filtered_mig(&self, pid: &str) -> Option<mig_types::schema::mig::MigSchema> {
5240 let mig = self.mig_schema.as_ref()?;
5241 let numbers = self
5242 .pid_segment_numbers
5243 .get(&format!("pid_{pid}"))
5244 .or_else(|| self.pid_segment_numbers.get("pid_"))?;
5245 let number_set: std::collections::HashSet<String> = numbers.iter().cloned().collect();
5246 Some(mig_assembly::pid_filter::filter_mig_for_pid(
5247 mig,
5248 &number_set,
5249 ))
5250 }
5251
5252 pub fn pid_mig_unmerged(&self, pid: &str) -> Option<mig_types::schema::mig::MigSchema> {
5255 let mig = self.mig_schema.as_ref()?;
5256 let numbers = self
5257 .pid_segment_numbers
5258 .get(&format!("pid_{pid}"))
5259 .or_else(|| self.pid_segment_numbers.get("pid_"))?;
5260 let number_set: std::collections::HashSet<String> = numbers.iter().cloned().collect();
5261 Some(mig_assembly::pid_filter::filter_mig_for_pid_unmerged(
5262 mig,
5263 &number_set,
5264 ))
5265 }
5266}
5267
5268#[derive(serde::Serialize, serde::Deserialize)]
5273pub struct DataBundle {
5274 pub format_version: String,
5275 pub bundle_version: u32,
5276 pub variants: BTreeMap<String, VariantCache>,
5277 #[serde(default)]
5282 pub bo4e_catalog: crate::bo4e_catalog::Bo4eCatalog,
5283
5284 #[serde(default, skip_serializing_if = "Option::is_none")]
5296 pub built_by: Option<String>,
5297 #[serde(default)]
5309 pub code_lists: crate::code_lists::CodeLists,
5310}
5311
5312impl DataBundle {
5313 pub const CURRENT_VERSION: u32 = 2;
5314
5315 pub const PRODUCING_VERSION: &'static str = env!("CARGO_PKG_VERSION");
5325
5326 pub fn variant(&self, name: &str) -> Option<&VariantCache> {
5327 self.variants.get(name)
5328 }
5329
5330 pub fn write_to<W: std::io::Write>(&self, writer: &mut W) -> Result<(), MappingError> {
5331 let encoded = serde_json::to_vec(self).map_err(|e| MappingError::CacheWrite {
5332 path: "<stream>".to_string(),
5333 message: e.to_string(),
5334 })?;
5335 writer.write_all(&encoded).map_err(MappingError::Io)
5336 }
5337
5338 pub fn read_from<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
5339 let mut bytes = Vec::new();
5340 reader.read_to_end(&mut bytes).map_err(MappingError::Io)?;
5341 serde_json::from_slice(&bytes).map_err(|e| MappingError::CacheRead {
5342 path: "<stream>".to_string(),
5343 message: e.to_string(),
5344 })
5345 }
5346
5347 pub fn read_from_checked<R: std::io::Read>(reader: &mut R) -> Result<Self, MappingError> {
5348 let mut bundle = Self::read_from(reader)?;
5349 let shared = std::sync::Arc::new(std::mem::take(&mut bundle.code_lists));
5352 for variant in bundle.variants.values_mut() {
5353 variant.code_lists = std::sync::Arc::clone(&shared);
5354 }
5355 bundle.code_lists = (*shared).clone();
5356 if bundle.bundle_version != Self::CURRENT_VERSION {
5357 return Err(MappingError::CacheRead {
5358 path: "<stream>".to_string(),
5359 message: format!(
5360 "Incompatible bundle version {}, expected version {}. \
5361 Run `edifact-data update` to fetch compatible bundles.",
5362 bundle.bundle_version,
5363 Self::CURRENT_VERSION
5364 ),
5365 });
5366 }
5367 Ok(bundle)
5368 }
5369
5370 pub fn save(&self, path: &Path) -> Result<(), MappingError> {
5371 if let Some(parent) = path.parent() {
5372 std::fs::create_dir_all(parent)?;
5373 }
5374 let mut file = std::fs::File::create(path).map_err(MappingError::Io)?;
5375 self.write_to(&mut file)
5376 }
5377
5378 pub fn load(path: &Path) -> Result<Self, MappingError> {
5379 let mut file = std::fs::File::open(path).map_err(MappingError::Io)?;
5380 Self::read_from_checked(&mut file)
5381 }
5382}
5383
5384#[cfg(test)]
5385mod variant_cache_helper_tests {
5386 use super::*;
5387
5388 fn make_test_cache() -> VariantCache {
5389 let mut tx_groups = BTreeMap::new();
5390 tx_groups.insert("pid_55001".to_string(), "SG4".to_string());
5391 tx_groups.insert("pid_21007".to_string(), "SG14".to_string());
5392
5393 let mut transaction_defs = BTreeMap::new();
5394 transaction_defs.insert("pid_55001".to_string(), vec![]);
5395 transaction_defs.insert("pid_21007".to_string(), vec![]);
5396
5397 VariantCache {
5398 code_lists: Default::default(),
5399 message_defs: vec![],
5400 transaction_defs,
5401 combined_defs: BTreeMap::new(),
5402 code_lookups: BTreeMap::new(),
5403 mig_schema: None,
5404 segment_structure: None,
5405 pid_segment_numbers: BTreeMap::new(),
5406 pid_requirements: BTreeMap::new(),
5407 pid_ahb_workflows: BTreeMap::new(),
5408 tx_groups,
5409 }
5410 }
5411
5412 #[test]
5413 fn test_tx_group_returns_correct_group() {
5414 let vc = make_test_cache();
5415 assert_eq!(vc.tx_group("55001").unwrap(), "SG4");
5416 assert_eq!(vc.tx_group("21007").unwrap(), "SG14");
5417 }
5418
5419 #[test]
5420 fn test_tx_group_unknown_pid_returns_none() {
5421 let vc = make_test_cache();
5422 assert!(vc.tx_group("99999").is_none());
5423 }
5424
5425 #[test]
5426 fn test_msg_engine_returns_engine() {
5427 let vc = make_test_cache();
5428 let engine = vc.msg_engine("55001");
5429 assert_eq!(engine.definitions().len(), 0);
5430 }
5431
5432 #[test]
5433 fn test_tx_engine_returns_engine_for_known_pid() {
5434 let vc = make_test_cache();
5435 assert!(vc.tx_engine("55001").is_some());
5436 }
5437
5438 #[test]
5439 fn test_tx_engine_returns_none_for_unknown_pid() {
5440 let vc = make_test_cache();
5441 assert!(vc.tx_engine("99999").is_none());
5442 }
5443
5444 fn make_populated_cache() -> VariantCache {
5448 let pids: Vec<String> = (0..40).map(|i| format!("pid_{}", 55000 + i * 7)).collect();
5449 let schema: serde_json::Value = serde_json::from_str(include_str!(
5450 "../../mig-types/src/generated/fv2504/utilmd/pids/pid_55001_schema.json"
5451 ))
5452 .unwrap();
5453 let code_lookup = crate::code_lookup::CodeLookup::from_schema_value(&schema);
5454 let element_counts: serde_json::Map<String, serde_json::Value> = (0..40)
5455 .map(|i| (format!("T{i:02}"), serde_json::json!(i)))
5456 .collect();
5457 let segment_structure: SegmentStructure =
5458 serde_json::from_value(serde_json::json!({ "element_counts": element_counts }))
5459 .unwrap();
5460 let ubs: serde_json::Map<String, serde_json::Value> = (0..40)
5461 .map(|i| (format!("UB{i}"), serde_json::json!({ "Ref": i })))
5462 .collect();
5463 let workflow: ahb_types::AhbWorkflow = serde_json::from_value(serde_json::json!({
5464 "pruefidentifikator": "55001",
5465 "description": "",
5466 "communication_direction": null,
5467 "fields": [],
5468 "ub_definitions": ubs,
5469 }))
5470 .unwrap();
5471
5472 let mut vc = make_test_cache();
5473 vc.segment_structure = Some(segment_structure);
5474 for pid in &pids {
5475 vc.transaction_defs.insert(pid.clone(), vec![]);
5476 vc.combined_defs.insert(pid.clone(), vec![]);
5477 vc.code_lookups.insert(pid.clone(), code_lookup.clone());
5478 vc.pid_segment_numbers
5479 .insert(pid.clone(), vec!["00001".to_string()]);
5480 vc.pid_ahb_workflows.insert(pid.clone(), workflow.clone());
5481 vc.tx_groups.insert(pid.clone(), "SG4".to_string());
5482 }
5483 vc
5484 }
5485
5486 #[test]
5489 fn test_enrichment_uses_codes_of_the_path_qualifier_variant() {
5490 let comp = |sub: u64, id: &str, codes: Option<serde_json::Value>| match codes {
5491 Some(c) => serde_json::json!({"sub_index": sub, "id": id, "type": "code", "codes": c}),
5492 None => serde_json::json!({"sub_index": sub, "id": id, "type": "data"}),
5493 };
5494 let code = |v: &str, n: &str| serde_json::json!([{"value": v, "name": n}]);
5495 let seg = |tag: &str, composite: &str, comps: Vec<serde_json::Value>| serde_json::json!({"id": tag, "elements": [{"index": 0, "composite": composite, "components": comps}]});
5496 let schema = serde_json::json!({"fields": {"sg15": {"segments": [
5497 seg("RFF", "C506", vec![comp(0, "1153", Some(code("Z13", "PID"))), comp(1, "1154", Some(code("21037", "RD / NB-Bewertung")))]),
5498 seg("RFF", "C506", vec![comp(0, "1153", Some(code("ACW", "Referenz"))), comp(1, "1154", None)]),
5499 seg("CAV", "C889", vec![comp(0, "7111", Some(code("Z91", "Z91"))), comp(1, "7110", Some(code("A", "Alpha")))]),
5500 seg("CAV", "C889", vec![comp(0, "7111", Some(code("ZF0", "ZF0"))), comp(1, "7110", Some(code("C", "Gamma")))]),
5501 ]}}});
5502 let engine = MappingEngine::new_empty()
5503 .with_code_lookup(crate::code_lookup::CodeLookup::from_schema_value(&schema));
5504 let def = MappingDefinition::from_toml_str(
5505 r#"
5506[meta]
5507entity = "Status"
5508bo4e_type = "Status"
5509source_group = "SG15"
5510source_path = "sg15"
5511discriminator = "RFF.0.0=Z13"
5512
5513[fields]
5514"rff.0.1" = "pruefidentifikator"
5515"rff[ACW].0.1" = "referenz"
5516"cav[Z91].0.1" = "z91Wert"
5517"cav[ZF0].0.1" = "zf0Wert"
5518"#,
5519 )
5520 .unwrap();
5521 let segment = |tag: &str, elements: &[&[&str]]| OwnedSegment {
5522 id: tag.to_string(),
5523 elements: elements
5524 .iter()
5525 .map(|e| e.iter().map(|c| c.to_string()).collect())
5526 .collect(),
5527 segment_number: 1,
5528 };
5529 let json = engine.map_forward_from_segments(
5530 &[
5531 segment("RFF", &[&["Z13", "21037"]]),
5532 segment("RFF", &[&["ACW", "REF-1"]]),
5533 segment("CAV", &[&["Z91", "C"]]),
5534 segment("CAV", &[&["ZF0", "C"]]),
5535 ],
5536 &def,
5537 );
5538 assert_eq!(
5539 json["referenz"],
5540 serde_json::json!("REF-1"),
5541 "RFF+ACW d1154 is data; RFF+Z13's codes must not apply: {json}"
5542 );
5543 assert_eq!(json["pruefidentifikator"]["meaning"], "RD / NB-Bewertung");
5544 assert_eq!(
5545 json["z91Wert"]["meaning"],
5546 serde_json::Value::Null,
5547 "'C' is a CAV+ZF0 code, unknown to CAV+Z91: {json}"
5548 );
5549 assert_eq!(json["zf0Wert"]["meaning"], "Gamma");
5550 }
5551
5552 #[test]
5556 fn list_target_reads_and_writes_every_repetition_in_order() {
5557 let engine = MappingEngine::new_empty();
5558 let def = MappingDefinition::from_toml_str(
5559 r#"
5560[meta]
5561entity = "Zuordnung"
5562bo4e_type = "Zuordnung"
5563source_group = "SG10"
5564source_path = "sg10"
5565
5566[fields]
5567"cci.2.0" = "merkmal.code"
5568"cav[*,*].0.0" = "werte[].code"
5569"cav[*,*].0.3" = "werte[].text"
5570"cav[Z30,*].0.3" = "geraetenummern[].nummer"
5571"#,
5572 )
5573 .unwrap();
5574 let segment = |tag: &str, elements: &[&[&str]]| OwnedSegment {
5575 id: tag.to_string(),
5576 elements: elements
5577 .iter()
5578 .map(|e| e.iter().map(|c| c.to_string()).collect())
5579 .collect(),
5580 segment_number: 1,
5581 };
5582 let json = engine.map_forward_from_segments(
5583 &[
5584 segment("CCI", &[&[""], &[""], &["ZB3"]]),
5585 segment("CAV", &[&["Z90", "", "", "UENB"]]),
5586 segment("CAV", &[&["Z91", "", "", "MSB"]]),
5587 segment("CAV", &[&["Z30", "", "", "W1"]]),
5588 segment("CAV", &[&["Z30", "", "", "W2"]]),
5589 ],
5590 &def,
5591 );
5592 assert_eq!(
5593 json["werte"],
5594 serde_json::json!([
5595 {"code": "Z90", "text": "UENB"},
5596 {"code": "Z91", "text": "MSB"},
5597 {"code": "Z30", "text": "W1"},
5598 {"code": "Z30", "text": "W2"},
5599 ]),
5600 "{json}"
5601 );
5602 assert_eq!(
5603 json["geraetenummern"],
5604 serde_json::json!([{"nummer": "W1"}, {"nummer": "W2"}])
5605 );
5606
5607 let only_werte = serde_json::json!({
5609 "merkmal": {"code": "ZB3"},
5610 "werte": [{"text": "UENB", "code": "Z90"}, {"code": "Z91", "text": "MSB"}],
5611 });
5612 let instance = engine.map_reverse(&only_werte, &def);
5613 let cavs: Vec<Vec<String>> = instance
5614 .segments
5615 .iter()
5616 .filter(|s| s.tag == "CAV")
5617 .map(|s| s.elements[0].clone())
5618 .collect();
5619 assert_eq!(
5620 cavs,
5621 vec![
5622 vec![
5623 "Z90".to_string(),
5624 String::new(),
5625 String::new(),
5626 "UENB".to_string()
5627 ],
5628 vec![
5629 "Z91".to_string(),
5630 String::new(),
5631 String::new(),
5632 "MSB".to_string()
5633 ],
5634 ]
5635 );
5636 }
5637
5638 #[test]
5639 fn test_variant_cache_serialization_is_deterministic() {
5640 let reference = serde_json::to_vec(&make_populated_cache()).unwrap();
5641 for _ in 0..5 {
5642 let again = serde_json::to_vec(&make_populated_cache()).unwrap();
5643 assert!(
5644 reference == again,
5645 "VariantCache serialization must not depend on HashMap iteration order"
5646 );
5647 }
5648 }
5649
5650 #[test]
5651 fn test_variant_cache_serializes_map_keys_sorted() {
5652 use indexmap::IndexMap;
5653 use serde::de::IgnoredAny;
5654
5655 #[derive(serde::Deserialize)]
5656 struct ProbeWorkflow {
5657 ub_definitions: IndexMap<String, IgnoredAny>,
5658 }
5659 #[derive(serde::Deserialize)]
5660 struct ProbeStructure {
5661 element_counts: IndexMap<String, usize>,
5662 }
5663 #[derive(serde::Deserialize)]
5664 struct Probe {
5665 transaction_defs: IndexMap<String, IgnoredAny>,
5666 combined_defs: IndexMap<String, IgnoredAny>,
5667 code_lookups: IndexMap<String, IndexMap<String, IgnoredAny>>,
5668 segment_structure: ProbeStructure,
5669 pid_segment_numbers: IndexMap<String, IgnoredAny>,
5670 pid_requirements: IndexMap<String, IgnoredAny>,
5671 pid_ahb_workflows: IndexMap<String, ProbeWorkflow>,
5672 tx_groups: IndexMap<String, String>,
5673 }
5674 fn assert_sorted<'a>(what: &str, keys: impl Iterator<Item = &'a String>) {
5675 let keys: Vec<&String> = keys.collect();
5676 let mut sorted = keys.clone();
5677 sorted.sort();
5678 assert_eq!(keys, sorted, "{what} keys must serialize in sorted order");
5679 }
5680
5681 let json = serde_json::to_string(&make_populated_cache()).unwrap();
5682 let probe: Probe = serde_json::from_str(&json).unwrap();
5683 assert_sorted("transaction_defs", probe.transaction_defs.keys());
5684 assert_sorted("combined_defs", probe.combined_defs.keys());
5685 assert_sorted("code_lookups", probe.code_lookups.keys());
5686 let lookup = probe.code_lookups.values().next().unwrap();
5687 assert!(lookup.len() > 10, "fixture lookup should have many entries");
5688 assert_sorted("code_lookup entries", lookup.keys());
5689 assert_sorted(
5690 "segment_structure",
5691 probe.segment_structure.element_counts.keys(),
5692 );
5693 assert_sorted("pid_segment_numbers", probe.pid_segment_numbers.keys());
5694 assert_sorted("pid_requirements", probe.pid_requirements.keys());
5695 assert_sorted("pid_ahb_workflows", probe.pid_ahb_workflows.keys());
5696 let wf = probe.pid_ahb_workflows.values().next().unwrap();
5697 assert_sorted("ub_definitions", wf.ub_definitions.keys());
5698 assert_sorted("tx_groups", probe.tx_groups.keys());
5699 }
5700
5701 #[test]
5702 fn test_data_bundle_serializes_variants_sorted() {
5703 use indexmap::IndexMap;
5704 use serde::de::IgnoredAny;
5705
5706 #[derive(serde::Deserialize)]
5707 struct Probe {
5708 variants: IndexMap<String, IgnoredAny>,
5709 }
5710 let variants: BTreeMap<String, VariantCache> = (0..20)
5711 .map(|i| (format!("VARIANT_{i:02}"), make_test_cache()))
5712 .collect();
5713 let bundle = DataBundle {
5714 format_version: "FV2504".to_string(),
5715 bundle_version: DataBundle::CURRENT_VERSION,
5716 built_by: Some(DataBundle::PRODUCING_VERSION.to_string()),
5717 variants,
5718 bo4e_catalog: Default::default(),
5719 code_lists: Default::default(),
5720 };
5721 let mut bytes = Vec::new();
5722 bundle.write_to(&mut bytes).unwrap();
5723 let probe: Probe = serde_json::from_slice(&bytes).unwrap();
5724 let keys: Vec<&String> = probe.variants.keys().collect();
5725 let mut sorted = keys.clone();
5726 sorted.sort();
5727 assert_eq!(keys, sorted);
5728 }
5729}
5730
5731#[cfg(test)]
5732mod tests {
5733 use super::*;
5734 use crate::definition::{MappingDefinition, MappingMeta, StructuredFieldMapping};
5735 use indexmap::IndexMap;
5736
5737 fn make_def(fields: IndexMap<String, FieldMapping>) -> MappingDefinition {
5738 MappingDefinition {
5739 meta: MappingMeta {
5740 entity: "Test".to_string(),
5741 bo4e_type: "Test".to_string(),
5742 source_group: "SG4".to_string(),
5743 source_path: None,
5744 discriminator: None,
5745 repeat_on_tag: None,
5746 parent_field: None,
5747 target_list: None,
5748 order: None,
5749 },
5750 fields,
5751 complex_handlers: None,
5752 }
5753 }
5754
5755 #[test]
5756 fn test_map_interchange_single_transaction_backward_compat() {
5757 use mig_assembly::assembler::*;
5758
5759 let tree = AssembledTree {
5761 segments: vec![
5762 AssembledSegment {
5763 tag: "UNH".to_string(),
5764 elements: vec![vec!["001".to_string()]],
5765 mig_number: None,
5766 segment_number: None,
5767 },
5768 AssembledSegment {
5769 tag: "BGM".to_string(),
5770 elements: vec![vec!["E01".to_string()], vec!["DOC001".to_string()]],
5771 mig_number: None,
5772 segment_number: None,
5773 },
5774 ],
5775 groups: vec![
5776 AssembledGroup {
5777 group_id: "SG2".to_string(),
5778 repetitions: vec![AssembledGroupInstance {
5779 segments: vec![AssembledSegment {
5780 tag: "NAD".to_string(),
5781 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
5782 mig_number: None,
5783 segment_number: None,
5784 }],
5785 child_groups: vec![],
5786 entry_mig_number: None,
5787 variant_mig_numbers: vec![],
5788 skipped_segments: vec![],
5789 skipped_positions: Vec::new(),
5790 }],
5791 },
5792 AssembledGroup {
5793 group_id: "SG4".to_string(),
5794 repetitions: vec![AssembledGroupInstance {
5795 segments: vec![AssembledSegment {
5796 tag: "IDE".to_string(),
5797 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
5798 mig_number: None,
5799 segment_number: None,
5800 }],
5801 child_groups: vec![AssembledGroup {
5802 group_id: "SG5".to_string(),
5803 repetitions: vec![AssembledGroupInstance {
5804 segments: vec![AssembledSegment {
5805 tag: "LOC".to_string(),
5806 elements: vec![
5807 vec!["Z16".to_string()],
5808 vec!["DE000111222333".to_string()],
5809 ],
5810 mig_number: None,
5811 segment_number: None,
5812 }],
5813 child_groups: vec![],
5814 entry_mig_number: None,
5815 variant_mig_numbers: vec![],
5816 skipped_segments: vec![],
5817 skipped_positions: Vec::new(),
5818 }],
5819 }],
5820 entry_mig_number: None,
5821 variant_mig_numbers: vec![],
5822 skipped_segments: vec![],
5823 skipped_positions: Vec::new(),
5824 }],
5825 },
5826 ],
5827 post_group_start: 2,
5828 inter_group_segments: std::collections::BTreeMap::new(),
5829 };
5830
5831 let msg_engine = MappingEngine::from_definitions(vec![]);
5833
5834 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5836 tx_fields.insert(
5837 "ide.1".to_string(),
5838 FieldMapping::Simple("vorgangId".to_string()),
5839 );
5840 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
5841 malo_fields.insert(
5842 "loc.1".to_string(),
5843 FieldMapping::Simple("marktlokationsId".to_string()),
5844 );
5845
5846 let tx_engine = MappingEngine::from_definitions(vec![
5847 MappingDefinition {
5848 meta: MappingMeta {
5849 entity: "Prozessdaten".to_string(),
5850 bo4e_type: "Prozessdaten".to_string(),
5851 source_group: "SG4".to_string(),
5852 source_path: None,
5853 discriminator: None,
5854 repeat_on_tag: None,
5855 parent_field: None,
5856 target_list: None,
5857 order: None,
5858 },
5859 fields: tx_fields,
5860 complex_handlers: None,
5861 },
5862 MappingDefinition {
5863 meta: MappingMeta {
5864 entity: "Marktlokation".to_string(),
5865 bo4e_type: "Marktlokation".to_string(),
5866 source_group: "SG4.SG5".to_string(),
5867 source_path: None,
5868 discriminator: None,
5869 repeat_on_tag: None,
5870 parent_field: None,
5871 target_list: None,
5872 order: None,
5873 },
5874 fields: malo_fields,
5875 complex_handlers: None,
5876 },
5877 ]);
5878
5879 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
5880
5881 assert_eq!(result.transaktionen.len(), 1);
5882 assert_eq!(
5883 result.transaktionen[0].transaktionsdaten["vorgangId"]
5884 .as_str()
5885 .unwrap(),
5886 "TX001"
5887 );
5888 assert_eq!(
5891 result.transaktionen[0].stammdaten["marktlokation"]["marktlokationsId"]
5892 .as_str()
5893 .unwrap(),
5894 "DE000111222333"
5895 );
5896 }
5897
5898 #[test]
5899 fn test_map_reverse_pads_intermediate_empty_elements() {
5900 let mut fields = IndexMap::new();
5902 fields.insert(
5903 "nad.0".to_string(),
5904 FieldMapping::Structured(StructuredFieldMapping {
5905 target: String::new(),
5906 transform: None,
5907 when: None,
5908 default: Some("Z09".to_string()),
5909 enum_map: None,
5910 code_list: None,
5911 also_code_list: None,
5912 when_filled: None,
5913 also_target: None,
5914 also_enum_map: None,
5915 }),
5916 );
5917 fields.insert(
5918 "nad.3.0".to_string(),
5919 FieldMapping::Simple("name".to_string()),
5920 );
5921 fields.insert(
5922 "nad.3.1".to_string(),
5923 FieldMapping::Simple("vorname".to_string()),
5924 );
5925
5926 let def = make_def(fields);
5927 let engine = MappingEngine::from_definitions(vec![]);
5928
5929 let bo4e = serde_json::json!({
5930 "name": "Muster",
5931 "vorname": "Max"
5932 });
5933
5934 let instance = engine.map_reverse(&bo4e, &def);
5935 assert_eq!(instance.segments.len(), 1);
5936
5937 let nad = &instance.segments[0];
5938 assert_eq!(nad.tag, "NAD");
5939 assert_eq!(nad.elements.len(), 4);
5940 assert_eq!(nad.elements[0], vec!["Z09"]);
5941 assert_eq!(nad.elements[1], vec![""]);
5943 assert_eq!(nad.elements[2], vec![""]);
5944 assert_eq!(nad.elements[3][0], "Muster");
5945 assert_eq!(nad.elements[3][1], "Max");
5946 }
5947
5948 #[test]
5949 fn test_map_reverse_no_padding_when_contiguous() {
5950 let mut fields = IndexMap::new();
5952 fields.insert(
5953 "dtm.0.0".to_string(),
5954 FieldMapping::Structured(StructuredFieldMapping {
5955 target: String::new(),
5956 transform: None,
5957 when: None,
5958 default: Some("92".to_string()),
5959 enum_map: None,
5960 code_list: None,
5961 also_code_list: None,
5962 when_filled: None,
5963 also_target: None,
5964 also_enum_map: None,
5965 }),
5966 );
5967 fields.insert(
5968 "dtm.0.1".to_string(),
5969 FieldMapping::Simple("value".to_string()),
5970 );
5971 fields.insert(
5972 "dtm.0.2".to_string(),
5973 FieldMapping::Structured(StructuredFieldMapping {
5974 target: String::new(),
5975 transform: None,
5976 when: None,
5977 default: Some("303".to_string()),
5978 enum_map: None,
5979 code_list: None,
5980 also_code_list: None,
5981 when_filled: None,
5982 also_target: None,
5983 also_enum_map: None,
5984 }),
5985 );
5986
5987 let def = make_def(fields);
5988 let engine = MappingEngine::from_definitions(vec![]);
5989
5990 let bo4e = serde_json::json!({ "value": "20250531" });
5991
5992 let instance = engine.map_reverse(&bo4e, &def);
5993 let dtm = &instance.segments[0];
5994 assert_eq!(dtm.elements.len(), 1);
5996 assert_eq!(dtm.elements[0], vec!["92", "20250531", "303"]);
5997 }
5998
5999 #[test]
6000 fn test_map_message_level_extracts_sg2_only() {
6001 use mig_assembly::assembler::*;
6002
6003 let tree = AssembledTree {
6005 segments: vec![
6006 AssembledSegment {
6007 tag: "UNH".to_string(),
6008 elements: vec![vec!["001".to_string()]],
6009 mig_number: None,
6010 segment_number: None,
6011 },
6012 AssembledSegment {
6013 tag: "BGM".to_string(),
6014 elements: vec![vec!["E01".to_string()]],
6015 mig_number: None,
6016 segment_number: None,
6017 },
6018 ],
6019 groups: vec![
6020 AssembledGroup {
6021 group_id: "SG2".to_string(),
6022 repetitions: vec![AssembledGroupInstance {
6023 segments: vec![AssembledSegment {
6024 tag: "NAD".to_string(),
6025 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
6026 mig_number: None,
6027 segment_number: None,
6028 }],
6029 child_groups: vec![],
6030 entry_mig_number: None,
6031 variant_mig_numbers: vec![],
6032 skipped_segments: vec![],
6033 skipped_positions: Vec::new(),
6034 }],
6035 },
6036 AssembledGroup {
6037 group_id: "SG4".to_string(),
6038 repetitions: vec![AssembledGroupInstance {
6039 segments: vec![AssembledSegment {
6040 tag: "IDE".to_string(),
6041 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
6042 mig_number: None,
6043 segment_number: None,
6044 }],
6045 child_groups: vec![],
6046 entry_mig_number: None,
6047 variant_mig_numbers: vec![],
6048 skipped_segments: vec![],
6049 skipped_positions: Vec::new(),
6050 }],
6051 },
6052 ],
6053 post_group_start: 2,
6054 inter_group_segments: std::collections::BTreeMap::new(),
6055 };
6056
6057 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
6059 msg_fields.insert(
6060 "nad.0".to_string(),
6061 FieldMapping::Simple("marktrolle".to_string()),
6062 );
6063 msg_fields.insert(
6064 "nad.1".to_string(),
6065 FieldMapping::Simple("rollencodenummer".to_string()),
6066 );
6067 let msg_def = MappingDefinition {
6068 meta: MappingMeta {
6069 entity: "Marktteilnehmer".to_string(),
6070 bo4e_type: "Marktteilnehmer".to_string(),
6071 source_group: "SG2".to_string(),
6072 source_path: None,
6073 discriminator: None,
6074 repeat_on_tag: None,
6075 parent_field: None,
6076 target_list: None,
6077 order: None,
6078 },
6079 fields: msg_fields,
6080 complex_handlers: None,
6081 };
6082
6083 let engine = MappingEngine::from_definitions(vec![msg_def.clone()]);
6084 let result = engine.map_all_forward(&tree);
6085
6086 assert!(result.get("marktteilnehmer").is_some());
6088 let mt = &result["marktteilnehmer"];
6089 assert_eq!(mt["marktrolle"].as_str().unwrap(), "MS");
6090 assert_eq!(mt["rollencodenummer"].as_str().unwrap(), "9900123");
6091 }
6092
6093 #[test]
6094 fn test_map_transaction_scoped_to_sg4_instance() {
6095 use mig_assembly::assembler::*;
6096
6097 let tree = AssembledTree {
6099 segments: vec![
6100 AssembledSegment {
6101 tag: "UNH".to_string(),
6102 elements: vec![vec!["001".to_string()]],
6103 mig_number: None,
6104 segment_number: None,
6105 },
6106 AssembledSegment {
6107 tag: "BGM".to_string(),
6108 elements: vec![vec!["E01".to_string()]],
6109 mig_number: None,
6110 segment_number: None,
6111 },
6112 ],
6113 groups: vec![AssembledGroup {
6114 group_id: "SG4".to_string(),
6115 repetitions: vec![AssembledGroupInstance {
6116 segments: vec![AssembledSegment {
6117 tag: "IDE".to_string(),
6118 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
6119 mig_number: None,
6120 segment_number: None,
6121 }],
6122 child_groups: vec![AssembledGroup {
6123 group_id: "SG5".to_string(),
6124 repetitions: vec![AssembledGroupInstance {
6125 segments: vec![AssembledSegment {
6126 tag: "LOC".to_string(),
6127 elements: vec![
6128 vec!["Z16".to_string()],
6129 vec!["DE000111222333".to_string()],
6130 ],
6131 mig_number: None,
6132 segment_number: None,
6133 }],
6134 child_groups: vec![],
6135 entry_mig_number: None,
6136 variant_mig_numbers: vec![],
6137 skipped_segments: vec![],
6138 skipped_positions: Vec::new(),
6139 }],
6140 }],
6141 entry_mig_number: None,
6142 variant_mig_numbers: vec![],
6143 skipped_segments: vec![],
6144 skipped_positions: Vec::new(),
6145 }],
6146 }],
6147 post_group_start: 2,
6148 inter_group_segments: std::collections::BTreeMap::new(),
6149 };
6150
6151 let mut proz_fields: IndexMap<String, FieldMapping> = IndexMap::new();
6153 proz_fields.insert(
6154 "ide.1".to_string(),
6155 FieldMapping::Simple("vorgangId".to_string()),
6156 );
6157 let proz_def = MappingDefinition {
6158 meta: MappingMeta {
6159 entity: "Prozessdaten".to_string(),
6160 bo4e_type: "Prozessdaten".to_string(),
6161 source_group: "".to_string(), source_path: None,
6163 discriminator: None,
6164 repeat_on_tag: None,
6165 parent_field: None,
6166 target_list: None,
6167 order: None,
6168 },
6169 fields: proz_fields,
6170 complex_handlers: None,
6171 };
6172
6173 let mut malo_fields: IndexMap<String, FieldMapping> = IndexMap::new();
6174 malo_fields.insert(
6175 "loc.1".to_string(),
6176 FieldMapping::Simple("marktlokationsId".to_string()),
6177 );
6178 let malo_def = MappingDefinition {
6179 meta: MappingMeta {
6180 entity: "Marktlokation".to_string(),
6181 bo4e_type: "Marktlokation".to_string(),
6182 source_group: "SG5".to_string(), source_path: None,
6184 discriminator: None,
6185 repeat_on_tag: None,
6186 parent_field: None,
6187 target_list: None,
6188 order: None,
6189 },
6190 fields: malo_fields,
6191 complex_handlers: None,
6192 };
6193
6194 let tx_engine = MappingEngine::from_definitions(vec![proz_def, malo_def]);
6195
6196 let sg4 = &tree.groups[0]; let sg4_instance = &sg4.repetitions[0];
6199 let sub_tree = sg4_instance.as_assembled_tree();
6200
6201 let result = tx_engine.map_all_forward(&sub_tree);
6202
6203 assert_eq!(
6205 result["prozessdaten"]["vorgangId"].as_str().unwrap(),
6206 "TX001"
6207 );
6208
6209 assert_eq!(
6211 result["marktlokation"]["marktlokationsId"]
6212 .as_str()
6213 .unwrap(),
6214 "DE000111222333"
6215 );
6216 }
6217
6218 #[test]
6219 fn test_map_interchange_produces_full_hierarchy() {
6220 use mig_assembly::assembler::*;
6221
6222 let tree = AssembledTree {
6224 segments: vec![
6225 AssembledSegment {
6226 tag: "UNH".to_string(),
6227 elements: vec![vec!["001".to_string()]],
6228 mig_number: None,
6229 segment_number: None,
6230 },
6231 AssembledSegment {
6232 tag: "BGM".to_string(),
6233 elements: vec![vec!["E01".to_string()]],
6234 mig_number: None,
6235 segment_number: None,
6236 },
6237 ],
6238 groups: vec![
6239 AssembledGroup {
6240 group_id: "SG2".to_string(),
6241 repetitions: vec![AssembledGroupInstance {
6242 segments: vec![AssembledSegment {
6243 tag: "NAD".to_string(),
6244 elements: vec![vec!["MS".to_string()], vec!["9900123".to_string()]],
6245 mig_number: None,
6246 segment_number: None,
6247 }],
6248 child_groups: vec![],
6249 entry_mig_number: None,
6250 variant_mig_numbers: vec![],
6251 skipped_segments: vec![],
6252 skipped_positions: Vec::new(),
6253 }],
6254 },
6255 AssembledGroup {
6256 group_id: "SG4".to_string(),
6257 repetitions: vec![
6258 AssembledGroupInstance {
6259 segments: vec![AssembledSegment {
6260 tag: "IDE".to_string(),
6261 elements: vec![vec!["24".to_string()], vec!["TX001".to_string()]],
6262 mig_number: None,
6263 segment_number: None,
6264 }],
6265 child_groups: vec![],
6266 entry_mig_number: None,
6267 variant_mig_numbers: vec![],
6268 skipped_segments: vec![],
6269 skipped_positions: Vec::new(),
6270 },
6271 AssembledGroupInstance {
6272 segments: vec![AssembledSegment {
6273 tag: "IDE".to_string(),
6274 elements: vec![vec!["24".to_string()], vec!["TX002".to_string()]],
6275 mig_number: None,
6276 segment_number: None,
6277 }],
6278 child_groups: vec![],
6279 entry_mig_number: None,
6280 variant_mig_numbers: vec![],
6281 skipped_segments: vec![],
6282 skipped_positions: Vec::new(),
6283 },
6284 ],
6285 },
6286 ],
6287 post_group_start: 2,
6288 inter_group_segments: std::collections::BTreeMap::new(),
6289 };
6290
6291 let mut msg_fields: IndexMap<String, FieldMapping> = IndexMap::new();
6293 msg_fields.insert(
6294 "nad.0".to_string(),
6295 FieldMapping::Simple("marktrolle".to_string()),
6296 );
6297 let msg_defs = vec![MappingDefinition {
6298 meta: MappingMeta {
6299 entity: "Marktteilnehmer".to_string(),
6300 bo4e_type: "Marktteilnehmer".to_string(),
6301 source_group: "SG2".to_string(),
6302 source_path: None,
6303 discriminator: None,
6304 repeat_on_tag: None,
6305 parent_field: None,
6306 target_list: None,
6307 order: None,
6308 },
6309 fields: msg_fields,
6310 complex_handlers: None,
6311 }];
6312
6313 let mut tx_fields: IndexMap<String, FieldMapping> = IndexMap::new();
6315 tx_fields.insert(
6316 "ide.1".to_string(),
6317 FieldMapping::Simple("vorgangId".to_string()),
6318 );
6319 let tx_defs = vec![MappingDefinition {
6320 meta: MappingMeta {
6321 entity: "Prozessdaten".to_string(),
6322 bo4e_type: "Prozessdaten".to_string(),
6323 source_group: "SG4".to_string(),
6324 source_path: None,
6325 discriminator: None,
6326 repeat_on_tag: None,
6327 parent_field: None,
6328 target_list: None,
6329 order: None,
6330 },
6331 fields: tx_fields,
6332 complex_handlers: None,
6333 }];
6334
6335 let msg_engine = MappingEngine::from_definitions(msg_defs);
6336 let tx_engine = MappingEngine::from_definitions(tx_defs);
6337
6338 let result = MappingEngine::map_interchange(&msg_engine, &tx_engine, &tree, "SG4", true);
6339
6340 assert!(result.stammdaten["marktteilnehmer"].is_object());
6342 assert_eq!(
6343 result.stammdaten["marktteilnehmer"]["marktrolle"]
6344 .as_str()
6345 .unwrap(),
6346 "MS"
6347 );
6348
6349 assert_eq!(result.transaktionen.len(), 2);
6351 assert_eq!(
6352 result.transaktionen[0].transaktionsdaten["vorgangId"]
6353 .as_str()
6354 .unwrap(),
6355 "TX001"
6356 );
6357 assert_eq!(
6358 result.transaktionen[1].transaktionsdaten["vorgangId"]
6359 .as_str()
6360 .unwrap(),
6361 "TX002"
6362 );
6363 }
6364
6365 #[test]
6366 fn test_map_reverse_with_segment_structure_pads_trailing() {
6367 let mut fields = IndexMap::new();
6369 fields.insert(
6370 "sts.0".to_string(),
6371 FieldMapping::Structured(StructuredFieldMapping {
6372 target: String::new(),
6373 transform: None,
6374 when: None,
6375 default: Some("7".to_string()),
6376 enum_map: None,
6377 code_list: None,
6378 also_code_list: None,
6379 when_filled: None,
6380 also_target: None,
6381 also_enum_map: None,
6382 }),
6383 );
6384 fields.insert(
6385 "sts.2".to_string(),
6386 FieldMapping::Simple("grund".to_string()),
6387 );
6388
6389 let def = make_def(fields);
6390
6391 let mut counts = std::collections::BTreeMap::new();
6393 counts.insert("STS".to_string(), 5usize);
6394 let ss = SegmentStructure {
6395 element_counts: counts,
6396 };
6397
6398 let engine = MappingEngine::from_definitions(vec![]).with_segment_structure(ss);
6399
6400 let bo4e = serde_json::json!({ "grund": "E01" });
6401
6402 let instance = engine.map_reverse(&bo4e, &def);
6403 let sts = &instance.segments[0];
6404 assert_eq!(sts.elements.len(), 5);
6407 assert_eq!(sts.elements[0], vec!["7"]);
6408 assert_eq!(sts.elements[1], vec![""]);
6409 assert_eq!(sts.elements[2], vec!["E01"]);
6410 assert_eq!(sts.elements[3], vec![""]);
6411 assert_eq!(sts.elements[4], vec![""]);
6412 }
6413
6414 #[test]
6415 fn test_resolve_child_relative_with_source_path() {
6416 let mut map: std::collections::HashMap<String, Vec<usize>> =
6417 std::collections::HashMap::new();
6418 map.insert("sg4.sg8_ze1".to_string(), vec![6]);
6419 map.insert("sg4.sg8_z98".to_string(), vec![0]);
6420
6421 assert_eq!(
6423 resolve_child_relative("SG8.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
6424 "SG8:6.SG10"
6425 );
6426
6427 assert_eq!(
6429 resolve_child_relative("SG8:3.SG10", Some("sg4.sg8_ze1.sg10"), &map, 0),
6430 "SG8:3.SG10"
6431 );
6432
6433 assert_eq!(
6435 resolve_child_relative("SG8.SG10", Some("sg4.sg8_unknown.sg10"), &map, 0),
6436 "SG8.SG10"
6437 );
6438
6439 assert_eq!(
6441 resolve_child_relative("SG8.SG10", None, &map, 0),
6442 "SG8.SG10"
6443 );
6444
6445 assert_eq!(
6447 resolve_child_relative("SG8.SG9", Some("sg4.sg8_z98.sg9"), &map, 0),
6448 "SG8:0.SG9"
6449 );
6450
6451 map.insert("sg4.sg8_zf3".to_string(), vec![3, 4]);
6453 assert_eq!(
6454 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 0),
6455 "SG8:3.SG10"
6456 );
6457 assert_eq!(
6458 resolve_child_relative("SG8.SG10", Some("sg4.sg8_zf3.sg10"), &map, 1),
6459 "SG8:4.SG10"
6460 );
6461 }
6462
6463 #[test]
6464 fn test_place_in_groups_returns_rep_index() {
6465 let mut groups: Vec<AssembledGroup> = Vec::new();
6466
6467 let instance = AssembledGroupInstance {
6469 segments: vec![],
6470 child_groups: vec![],
6471 entry_mig_number: None,
6472 variant_mig_numbers: vec![],
6473 skipped_segments: vec![],
6474 skipped_positions: Vec::new(),
6475 };
6476 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 0);
6477
6478 let instance = AssembledGroupInstance {
6480 segments: vec![],
6481 child_groups: vec![],
6482 entry_mig_number: None,
6483 variant_mig_numbers: vec![],
6484 skipped_segments: vec![],
6485 skipped_positions: Vec::new(),
6486 };
6487 assert_eq!(place_in_groups(&mut groups, "SG8", instance), 1);
6488
6489 let instance = AssembledGroupInstance {
6491 segments: vec![],
6492 child_groups: vec![],
6493 entry_mig_number: None,
6494 variant_mig_numbers: vec![],
6495 skipped_segments: vec![],
6496 skipped_positions: Vec::new(),
6497 };
6498 assert_eq!(place_in_groups(&mut groups, "SG8:5", instance), 5);
6499 }
6500
6501 #[test]
6502 fn test_resolve_by_source_path() {
6503 use mig_assembly::assembler::*;
6504
6505 let tree = AssembledTree {
6507 segments: vec![],
6508 groups: vec![AssembledGroup {
6509 group_id: "SG4".to_string(),
6510 repetitions: vec![AssembledGroupInstance {
6511 segments: vec![],
6512 child_groups: vec![AssembledGroup {
6513 group_id: "SG8".to_string(),
6514 repetitions: vec![
6515 AssembledGroupInstance {
6516 segments: vec![AssembledSegment {
6517 tag: "SEQ".to_string(),
6518 elements: vec![vec!["Z98".to_string()]],
6519 mig_number: None,
6520 segment_number: None,
6521 }],
6522 child_groups: vec![AssembledGroup {
6523 group_id: "SG10".to_string(),
6524 repetitions: vec![AssembledGroupInstance {
6525 segments: vec![AssembledSegment {
6526 tag: "CCI".to_string(),
6527 elements: vec![vec![], vec![], vec!["ZB3".to_string()]],
6528 mig_number: None,
6529 segment_number: None,
6530 }],
6531 child_groups: vec![],
6532 entry_mig_number: None,
6533 variant_mig_numbers: vec![],
6534 skipped_segments: vec![],
6535 skipped_positions: Vec::new(),
6536 }],
6537 }],
6538 entry_mig_number: None,
6539 variant_mig_numbers: vec![],
6540 skipped_segments: vec![],
6541 skipped_positions: Vec::new(),
6542 },
6543 AssembledGroupInstance {
6544 segments: vec![AssembledSegment {
6545 tag: "SEQ".to_string(),
6546 elements: vec![vec!["ZD7".to_string()]],
6547 mig_number: None,
6548 segment_number: None,
6549 }],
6550 child_groups: vec![AssembledGroup {
6551 group_id: "SG10".to_string(),
6552 repetitions: vec![AssembledGroupInstance {
6553 segments: vec![AssembledSegment {
6554 tag: "CCI".to_string(),
6555 elements: vec![vec![], vec![], vec!["ZE6".to_string()]],
6556 mig_number: None,
6557 segment_number: None,
6558 }],
6559 child_groups: vec![],
6560 entry_mig_number: None,
6561 variant_mig_numbers: vec![],
6562 skipped_segments: vec![],
6563 skipped_positions: Vec::new(),
6564 }],
6565 }],
6566 entry_mig_number: None,
6567 variant_mig_numbers: vec![],
6568 skipped_segments: vec![],
6569 skipped_positions: Vec::new(),
6570 },
6571 ],
6572 }],
6573 entry_mig_number: None,
6574 variant_mig_numbers: vec![],
6575 skipped_segments: vec![],
6576 skipped_positions: Vec::new(),
6577 }],
6578 }],
6579 post_group_start: 0,
6580 inter_group_segments: std::collections::BTreeMap::new(),
6581 };
6582
6583 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_z98.sg10");
6585 assert!(inst.is_some());
6586 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
6587
6588 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zd7.sg10");
6590 assert!(inst.is_some());
6591 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZE6");
6592
6593 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8_zzz.sg10");
6595 assert!(inst.is_none());
6596
6597 let inst = MappingEngine::resolve_by_source_path(&tree, "sg4.sg8.sg10");
6599 assert!(inst.is_some());
6600 assert_eq!(inst.unwrap().segments[0].elements[2][0], "ZB3");
6601 }
6602
6603 #[test]
6604 fn test_parse_source_path_part() {
6605 assert_eq!(parse_source_path_part("sg4"), ("sg4", None));
6606 assert_eq!(parse_source_path_part("sg8_z98"), ("sg8", Some("z98")));
6607 assert_eq!(parse_source_path_part("sg10"), ("sg10", None));
6608 assert_eq!(parse_source_path_part("sg12_z04"), ("sg12", Some("z04")));
6609 }
6610
6611 #[test]
6612 fn test_has_source_path_qualifiers() {
6613 assert!(has_source_path_qualifiers("sg4.sg8_z98.sg10"));
6614 assert!(has_source_path_qualifiers("sg4.sg8_ze1.sg9"));
6615 assert!(!has_source_path_qualifiers("sg4.sg6"));
6616 assert!(!has_source_path_qualifiers("sg4.sg8.sg10"));
6617 }
6618
6619 #[test]
6620 fn test_extract_all_from_instance_collects_all_qualifier_matches() {
6621 use mig_assembly::assembler::*;
6622
6623 let instance = AssembledGroupInstance {
6625 segments: vec![
6626 AssembledSegment {
6627 tag: "SEQ".to_string(),
6628 elements: vec![vec!["ZD6".to_string()]],
6629 mig_number: None,
6630 segment_number: None,
6631 },
6632 AssembledSegment {
6633 tag: "RFF".to_string(),
6634 elements: vec![vec!["Z34".to_string(), "REF_A".to_string()]],
6635 mig_number: None,
6636 segment_number: None,
6637 },
6638 AssembledSegment {
6639 tag: "RFF".to_string(),
6640 elements: vec![vec!["Z34".to_string(), "REF_B".to_string()]],
6641 mig_number: None,
6642 segment_number: None,
6643 },
6644 AssembledSegment {
6645 tag: "RFF".to_string(),
6646 elements: vec![vec!["Z34".to_string(), "REF_C".to_string()]],
6647 mig_number: None,
6648 segment_number: None,
6649 },
6650 AssembledSegment {
6651 tag: "RFF".to_string(),
6652 elements: vec![vec!["Z35".to_string(), "OTHER".to_string()]],
6653 mig_number: None,
6654 segment_number: None,
6655 },
6656 ],
6657 child_groups: vec![],
6658 entry_mig_number: None,
6659 variant_mig_numbers: vec![],
6660 skipped_segments: vec![],
6661 skipped_positions: Vec::new(),
6662 };
6663
6664 let all = MappingEngine::extract_all_from_instance(&instance, "rff[Z34,*].0.1");
6666 assert_eq!(all, vec!["REF_A", "REF_B", "REF_C"]);
6667
6668 let single = MappingEngine::extract_from_instance(&instance, "rff[Z34].0.1");
6670 assert_eq!(single, Some("REF_A".to_string()));
6671
6672 let second = MappingEngine::extract_from_instance(&instance, "rff[Z34,1].0.1");
6673 assert_eq!(second, Some("REF_B".to_string()));
6674 }
6675}