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