1use std::collections::HashMap;
11use std::path::Path;
12
13#[derive(Clone)]
24pub struct PathResolver {
25 composite_elements: HashMap<(String, String, String), (usize, usize)>,
27 simple_elements: HashMap<(String, String), usize>,
29}
30
31impl PathResolver {
32 pub fn from_schema(schema: &serde_json::Value) -> Self {
36 let mut simple_elements = HashMap::new();
37 let mut composite_elements = HashMap::new();
38
39 if let Some(fields) = schema.get("fields").and_then(|f| f.as_object()) {
40 for (_group_key, group_val) in fields {
41 collect_from_group(group_val, &mut simple_elements, &mut composite_elements);
42 }
43 }
44
45 if let Some(root_segs) = schema.get("root_segments") {
47 let wrapper = serde_json::json!({ "segments": root_segs });
48 collect_from_group(&wrapper, &mut simple_elements, &mut composite_elements);
49 }
50
51 Self {
52 simple_elements,
53 composite_elements,
54 }
55 }
56
57 pub fn from_schema_dir(dir: &Path) -> Self {
62 let mut resolver = Self {
63 simple_elements: HashMap::new(),
64 composite_elements: HashMap::new(),
65 };
66
67 let mut entries: Vec<_> = std::fs::read_dir(dir)
68 .into_iter()
69 .flatten()
70 .filter_map(|e| e.ok())
71 .collect();
72 entries.sort_by_key(|e| e.file_name());
73
74 for entry in entries {
75 let path = entry.path();
76 let is_schema = path
77 .file_name()
78 .and_then(|n| n.to_str())
79 .map(|n| n.starts_with("pid_") && n.ends_with("_schema.json"))
80 .unwrap_or(false);
81 if is_schema {
82 if let Ok(content) = std::fs::read_to_string(&path) {
83 if let Ok(schema) = serde_json::from_str::<serde_json::Value>(&content) {
84 resolver.merge_schema(&schema);
85 }
86 }
87 }
88 }
89
90 resolver
91 }
92
93 pub fn merge_schema(&mut self, schema: &serde_json::Value) {
95 if let Some(fields) = schema.get("fields").and_then(|f| f.as_object()) {
96 for (_group_key, group_val) in fields {
97 collect_from_group(
98 group_val,
99 &mut self.simple_elements,
100 &mut self.composite_elements,
101 );
102 }
103 }
104 if let Some(root_segs) = schema.get("root_segments") {
105 let wrapper = serde_json::json!({ "segments": root_segs });
106 collect_from_group(
107 &wrapper,
108 &mut self.simple_elements,
109 &mut self.composite_elements,
110 );
111 }
112 }
113
114 pub fn resolve_path(&self, path: &str) -> String {
125 let parts: Vec<&str> = path.split('.').collect();
126 if parts.len() < 2 {
127 return path.to_string();
128 }
129
130 let (seg_raw, qualifier_suffix) = split_qualifier(parts[0]);
132 let seg_upper = seg_raw.to_ascii_uppercase();
133
134 let rest = &parts[1..];
135
136 if rest[0]
138 .chars()
139 .next()
140 .map(|c| c.is_ascii_digit())
141 .unwrap_or(false)
142 {
143 return path.to_string();
144 }
145
146 if rest.len() == 2 && is_edifact_id(rest[0]) && is_edifact_id(rest[1]) {
148 let composite_id = rest[0].to_ascii_lowercase();
149 let data_element_id = rest[1].to_ascii_lowercase();
150
151 if let Some(&(elem_idx, sub_idx)) =
152 self.composite_elements
153 .get(&(seg_upper.clone(), composite_id, data_element_id))
154 {
155 return format!("{}{}.{}.{}", seg_raw, qualifier_suffix, elem_idx, sub_idx);
156 }
157 }
158
159 if rest.len() == 1 && is_edifact_id(rest[0]) {
161 let element_id = rest[0].to_ascii_lowercase();
162
163 if let Some(&elem_idx) = self.simple_elements.get(&(seg_upper, element_id)) {
164 return format!("{}{}.{}", seg_raw, qualifier_suffix, elem_idx);
165 }
166 }
167
168 path.to_string()
170 }
171
172 pub fn resolve_discriminator(&self, disc: &str) -> String {
182 let Some((path_part, value_part)) = disc.split_once('=') else {
183 return disc.to_string();
184 };
185
186 let parts: Vec<&str> = path_part.split('.').collect();
187
188 match parts.len() {
189 2 => {
190 let seg_upper = parts[0].to_ascii_uppercase();
191 let element_ref = parts[1];
192
193 if element_ref
195 .chars()
196 .next()
197 .map(|c| c.is_ascii_digit())
198 .unwrap_or(false)
199 {
200 return format!("{}.{}.0={}", parts[0], element_ref, value_part);
201 }
202
203 if is_edifact_id(element_ref) {
205 let element_id = element_ref.to_ascii_lowercase();
206 if let Some(&elem_idx) = self.simple_elements.get(&(seg_upper, element_id)) {
207 return format!("{}.{}.0={}", parts[0], elem_idx, value_part);
208 }
209 }
210
211 disc.to_string()
212 }
213 3 => {
214 let seg_upper = parts[0].to_ascii_uppercase();
215
216 if parts[1]
218 .chars()
219 .next()
220 .map(|c| c.is_ascii_digit())
221 .unwrap_or(false)
222 {
223 return disc.to_string();
224 }
225
226 if is_edifact_id(parts[1]) && is_edifact_id(parts[2]) {
228 let composite_id = parts[1].to_ascii_lowercase();
229 let data_element_id = parts[2].to_ascii_lowercase();
230
231 if let Some(&(elem_idx, sub_idx)) =
232 self.composite_elements
233 .get(&(seg_upper, composite_id, data_element_id))
234 {
235 return format!("{}.{}.{}={}", parts[0], elem_idx, sub_idx, value_part);
236 }
237 }
238
239 disc.to_string()
240 }
241 _ => disc.to_string(),
242 }
243 }
244}
245
246#[derive(Clone)]
253pub struct ReversePathResolver {
254 composite_reverse: HashMap<(String, usize, usize), String>,
256 simple_reverse: HashMap<(String, usize), String>,
258 is_composite: HashMap<(String, usize), bool>,
260}
261
262impl ReversePathResolver {
263 pub fn from_schema(schema: &serde_json::Value) -> Self {
265 let mut composite_reverse = HashMap::new();
266 let mut simple_reverse = HashMap::new();
267 let mut is_composite = HashMap::new();
268
269 if let Some(fields) = schema.get("fields").and_then(|f| f.as_object()) {
270 for (_group_key, group_val) in fields {
271 collect_reverse_from_group(
272 group_val,
273 &mut composite_reverse,
274 &mut simple_reverse,
275 &mut is_composite,
276 );
277 }
278 }
279
280 if let Some(root_segs) = schema.get("root_segments") {
281 let wrapper = serde_json::json!({ "segments": root_segs });
282 collect_reverse_from_group(
283 &wrapper,
284 &mut composite_reverse,
285 &mut simple_reverse,
286 &mut is_composite,
287 );
288 }
289
290 Self {
291 composite_reverse,
292 simple_reverse,
293 is_composite,
294 }
295 }
296
297 pub fn from_mig(mig: &mig_types::schema::mig::MigSchema) -> Self {
306 let mut composite_reverse = HashMap::new();
307 let mut simple_reverse = HashMap::new();
308 let mut is_composite = HashMap::new();
309 for seg in &mig.segments {
310 collect_reverse_from_mig_segment(
311 seg,
312 &mut composite_reverse,
313 &mut simple_reverse,
314 &mut is_composite,
315 );
316 }
317 for group in &mig.segment_groups {
318 collect_reverse_from_mig_group(
319 group,
320 &mut composite_reverse,
321 &mut simple_reverse,
322 &mut is_composite,
323 );
324 }
325 Self {
326 composite_reverse,
327 simple_reverse,
328 is_composite,
329 }
330 }
331
332 pub fn from_schema_dir(dir: &Path) -> Self {
334 let mut resolver = Self {
335 composite_reverse: HashMap::new(),
336 simple_reverse: HashMap::new(),
337 is_composite: HashMap::new(),
338 };
339
340 let mut entries: Vec<_> = std::fs::read_dir(dir)
341 .into_iter()
342 .flatten()
343 .filter_map(|e| e.ok())
344 .collect();
345 entries.sort_by_key(|e| e.file_name());
346
347 for entry in entries {
348 let path = entry.path();
349 let is_schema = path
350 .file_name()
351 .and_then(|n| n.to_str())
352 .map(|n| n.starts_with("pid_") && n.ends_with("_schema.json"))
353 .unwrap_or(false);
354 if is_schema {
355 if let Ok(content) = std::fs::read_to_string(&path) {
356 if let Ok(schema) = serde_json::from_str::<serde_json::Value>(&content) {
357 resolver.merge_schema(&schema);
358 }
359 }
360 }
361 }
362
363 resolver
364 }
365
366 pub fn merge_schema(&mut self, schema: &serde_json::Value) {
368 if let Some(fields) = schema.get("fields").and_then(|f| f.as_object()) {
369 for (_group_key, group_val) in fields {
370 collect_reverse_from_group(
371 group_val,
372 &mut self.composite_reverse,
373 &mut self.simple_reverse,
374 &mut self.is_composite,
375 );
376 }
377 }
378 if let Some(root_segs) = schema.get("root_segments") {
379 let wrapper = serde_json::json!({ "segments": root_segs });
380 collect_reverse_from_group(
381 &wrapper,
382 &mut self.composite_reverse,
383 &mut self.simple_reverse,
384 &mut self.is_composite,
385 );
386 }
387 }
388
389 pub fn reverse_path(&self, path: &str) -> String {
399 let parts: Vec<&str> = path.split('.').collect();
400 if parts.len() < 2 {
401 return path.to_string();
402 }
403
404 let (seg_raw, qualifier_suffix) = split_qualifier(parts[0]);
405 let seg_upper = seg_raw.to_ascii_uppercase();
406 let rest = &parts[1..];
407
408 if !rest[0]
410 .chars()
411 .next()
412 .map(|c| c.is_ascii_digit())
413 .unwrap_or(false)
414 {
415 return path.to_string();
416 }
417
418 match rest.len() {
419 1 => {
420 let Ok(elem_idx) = rest[0].parse::<usize>() else {
422 return path.to_string();
423 };
424
425 match self.is_composite.get(&(seg_upper.clone(), elem_idx)) {
426 Some(true) => {
427 if let Some(named) = self.composite_reverse.get(&(seg_upper, elem_idx, 0)) {
429 format!("{}{}.{}", seg_raw, qualifier_suffix, named)
430 } else {
431 path.to_string()
432 }
433 }
434 Some(false) => {
435 if let Some(named) = self.simple_reverse.get(&(seg_upper, elem_idx)) {
437 format!("{}{}.{}", seg_raw, qualifier_suffix, named)
438 } else {
439 path.to_string()
440 }
441 }
442 None => path.to_string(),
443 }
444 }
445 2 => {
446 let Ok(elem_idx) = rest[0].parse::<usize>() else {
448 return path.to_string();
449 };
450 let Ok(sub_idx) = rest[1].parse::<usize>() else {
451 return path.to_string();
452 };
453
454 if let Some(named) = self.composite_reverse.get(&(seg_upper, elem_idx, sub_idx)) {
455 format!("{}{}.{}", seg_raw, qualifier_suffix, named)
456 } else {
457 path.to_string()
458 }
459 }
460 _ => path.to_string(),
461 }
462 }
463
464 pub fn reverse_discriminator(&self, disc: &str) -> String {
471 let Some((path_part, value_part)) = disc.split_once('=') else {
472 return disc.to_string();
473 };
474
475 let parts: Vec<&str> = path_part.split('.').collect();
476 if parts.len() != 3 {
477 return disc.to_string();
478 }
479
480 let seg_raw = parts[0];
481 let seg_upper = seg_raw.to_ascii_uppercase();
482
483 let Ok(elem_idx) = parts[1].parse::<usize>() else {
485 return disc.to_string(); };
487 let Ok(sub_idx) = parts[2].parse::<usize>() else {
488 return disc.to_string();
489 };
490
491 if sub_idx == 0 {
493 if let Some(false) = self.is_composite.get(&(seg_upper.clone(), elem_idx)) {
494 if let Some(named) = self.simple_reverse.get(&(seg_upper.clone(), elem_idx)) {
495 return format!("{}.{}={}", seg_raw, named, value_part);
496 }
497 }
498 }
499
500 if let Some(named) = self
502 .composite_reverse
503 .get(&(seg_upper.clone(), elem_idx, sub_idx))
504 {
505 return format!("{}.{}={}", seg_raw, named, value_part);
506 }
507
508 disc.to_string()
509 }
510}
511
512fn is_edifact_id(s: &str) -> bool {
519 let mut chars = s.chars();
520 match chars.next() {
521 Some('c' | 'd' | 's' | 'C' | 'D' | 'S') => {
522 let rest: String = chars.collect();
523 if rest.is_empty() {
524 return false;
525 }
526 if let Some((base, suffix)) = rest.split_once('_') {
527 !base.is_empty()
528 && base.chars().all(|c| c.is_ascii_digit())
529 && !suffix.is_empty()
530 && suffix.chars().all(|c| c.is_ascii_digit())
531 } else {
532 rest.chars().all(|c| c.is_ascii_digit())
533 }
534 }
535 _ => false,
536 }
537}
538
539fn split_qualifier(tag: &str) -> (&str, &str) {
541 if let Some(bracket_pos) = tag.find('[') {
542 (&tag[..bracket_pos], &tag[bracket_pos..])
543 } else {
544 (tag, "")
545 }
546}
547
548fn collect_from_group(
553 group: &serde_json::Value,
554 simple: &mut HashMap<(String, String), usize>,
555 composite: &mut HashMap<(String, String, String), (usize, usize)>,
556) {
557 if let Some(segments) = group.get("segments").and_then(|s| s.as_array()) {
558 for seg in segments {
559 let seg_tag = seg
560 .get("id")
561 .and_then(|v| v.as_str())
562 .unwrap_or("")
563 .to_ascii_uppercase();
564
565 if let Some(elements) = seg.get("elements").and_then(|e| e.as_array()) {
566 let mut composite_id_count: HashMap<String, usize> = HashMap::new();
568
569 for elem in elements {
570 let elem_index =
571 elem.get("index").and_then(|v| v.as_u64()).unwrap_or(0) as usize;
572
573 if let Some(composite_id) = elem.get("composite").and_then(|v| v.as_str()) {
574 let base_composite = composite_id.to_ascii_lowercase();
575
576 let count = composite_id_count
578 .entry(base_composite.clone())
579 .or_insert(0);
580 *count += 1;
581
582 let composite_key = if *count == 1 {
583 base_composite
584 } else {
585 format!("{}_{}", base_composite, count)
586 };
587
588 if let Some(components) = elem.get("components").and_then(|c| c.as_array())
589 {
590 let mut data_elem_count: HashMap<String, usize> = HashMap::new();
592
593 for comp in components {
594 let comp_id = comp.get("id").and_then(|v| v.as_str()).unwrap_or("");
595 let sub_index =
596 comp.get("sub_index").and_then(|v| v.as_u64()).unwrap_or(0)
597 as usize;
598 let base_data = format!("d{}", comp_id).to_ascii_lowercase();
599
600 let dcount = data_elem_count.entry(base_data.clone()).or_insert(0);
601 *dcount += 1;
602
603 let data_key = if *dcount == 1 {
604 base_data
605 } else {
606 format!("{}_{}", base_data, dcount)
607 };
608
609 composite
610 .entry((seg_tag.clone(), composite_key.clone(), data_key))
611 .or_insert((elem_index, sub_index));
612 }
613 }
614 } else {
615 let elem_id = elem.get("id").and_then(|v| v.as_str()).unwrap_or("");
617 let elem_id_lower = format!("d{}", elem_id).to_ascii_lowercase();
618 simple
619 .entry((seg_tag.clone(), elem_id_lower))
620 .or_insert(elem_index);
621 }
622 }
623 }
624 }
625 }
626
627 if let Some(children) = group.get("children").and_then(|c| c.as_object()) {
629 for (_child_key, child_val) in children {
630 collect_from_group(child_val, simple, composite);
631 }
632 }
633}
634
635fn collect_reverse_from_group(
640 group: &serde_json::Value,
641 composite_reverse: &mut HashMap<(String, usize, usize), String>,
642 simple_reverse: &mut HashMap<(String, usize), String>,
643 is_composite: &mut HashMap<(String, usize), bool>,
644) {
645 if let Some(segments) = group.get("segments").and_then(|s| s.as_array()) {
646 for seg in segments {
647 let seg_tag = seg
648 .get("id")
649 .and_then(|v| v.as_str())
650 .unwrap_or("")
651 .to_ascii_uppercase();
652
653 if let Some(elements) = seg.get("elements").and_then(|e| e.as_array()) {
654 let mut composite_id_count: HashMap<String, usize> = HashMap::new();
655
656 for elem in elements {
657 let elem_index =
658 elem.get("index").and_then(|v| v.as_u64()).unwrap_or(0) as usize;
659
660 if let Some(composite_id) = elem.get("composite").and_then(|v| v.as_str()) {
661 let base = composite_id.to_ascii_lowercase();
662
663 let count = composite_id_count.entry(base.clone()).or_insert(0);
664 *count += 1;
665
666 let comp_key = if *count == 1 {
667 base
668 } else {
669 format!("{}_{}", base, count)
670 };
671
672 is_composite
673 .entry((seg_tag.clone(), elem_index))
674 .or_insert(true);
675
676 if let Some(components) = elem.get("components").and_then(|c| c.as_array())
677 {
678 let mut data_elem_count: HashMap<String, usize> = HashMap::new();
679
680 for comp in components {
681 let comp_id = comp.get("id").and_then(|v| v.as_str()).unwrap_or("");
682 let sub_index =
683 comp.get("sub_index").and_then(|v| v.as_u64()).unwrap_or(0)
684 as usize;
685 let base_data = format!("d{}", comp_id).to_ascii_lowercase();
686
687 let dcount = data_elem_count.entry(base_data.clone()).or_insert(0);
688 *dcount += 1;
689
690 let data_key = if *dcount == 1 {
691 base_data
692 } else {
693 format!("{}_{}", base_data, dcount)
694 };
695
696 composite_reverse
697 .entry((seg_tag.clone(), elem_index, sub_index))
698 .or_insert(format!("{}.{}", comp_key, data_key));
699 }
700 }
701 } else {
702 let elem_id = elem.get("id").and_then(|v| v.as_str()).unwrap_or("");
703 let elem_id_lower = format!("d{}", elem_id).to_ascii_lowercase();
704
705 is_composite
706 .entry((seg_tag.clone(), elem_index))
707 .or_insert(false);
708 simple_reverse
709 .entry((seg_tag.clone(), elem_index))
710 .or_insert(elem_id_lower);
711 }
712 }
713 }
714 }
715 }
716
717 if let Some(children) = group.get("children").and_then(|c| c.as_object()) {
718 for (_child_key, child_val) in children {
719 collect_reverse_from_group(child_val, composite_reverse, simple_reverse, is_composite);
720 }
721 }
722}
723
724fn collect_reverse_from_mig_group(
726 group: &mig_types::schema::mig::MigSegmentGroup,
727 composite_reverse: &mut HashMap<(String, usize, usize), String>,
728 simple_reverse: &mut HashMap<(String, usize), String>,
729 is_composite: &mut HashMap<(String, usize), bool>,
730) {
731 for seg in &group.segments {
732 collect_reverse_from_mig_segment(seg, composite_reverse, simple_reverse, is_composite);
733 }
734 for nested in &group.nested_groups {
735 collect_reverse_from_mig_group(nested, composite_reverse, simple_reverse, is_composite);
736 }
737}
738
739fn collect_reverse_from_mig_segment(
744 seg: &mig_types::schema::mig::MigSegment,
745 composite_reverse: &mut HashMap<(String, usize, usize), String>,
746 simple_reverse: &mut HashMap<(String, usize), String>,
747 is_composite: &mut HashMap<(String, usize), bool>,
748) {
749 let seg_tag = seg.id.to_ascii_uppercase();
750
751 enum El<'a> {
752 Simple(&'a mig_types::schema::mig::MigDataElement),
753 Composite(&'a mig_types::schema::mig::MigComposite),
754 }
755 let mut elems: Vec<(usize, El)> = Vec::new();
756 for de in &seg.data_elements {
757 elems.push((de.position, El::Simple(de)));
758 }
759 for c in &seg.composites {
760 elems.push((c.position, El::Composite(c)));
761 }
762 elems.sort_by_key(|(pos, _)| *pos);
763
764 let mut composite_id_count: HashMap<String, usize> = HashMap::new();
765 for (elem_index, el) in elems {
766 match el {
767 El::Composite(c) => {
768 let base = c.id.to_ascii_lowercase();
769 let count = composite_id_count.entry(base.clone()).or_insert(0);
770 *count += 1;
771 let comp_key = if *count == 1 {
772 base
773 } else {
774 format!("{}_{}", base, count)
775 };
776 is_composite
777 .entry((seg_tag.clone(), elem_index))
778 .or_insert(true);
779 let mut data_elem_count: HashMap<String, usize> = HashMap::new();
780 for comp in &c.data_elements {
781 let sub_index = comp.position;
782 let base_data = format!("d{}", comp.id).to_ascii_lowercase();
783 let dcount = data_elem_count.entry(base_data.clone()).or_insert(0);
784 *dcount += 1;
785 let data_key = if *dcount == 1 {
786 base_data
787 } else {
788 format!("{}_{}", base_data, dcount)
789 };
790 composite_reverse
791 .entry((seg_tag.clone(), elem_index, sub_index))
792 .or_insert(format!("{}.{}", comp_key, data_key));
793 }
794 }
795 El::Simple(de) => {
796 let elem_id_lower = format!("d{}", de.id).to_ascii_lowercase();
797 is_composite
798 .entry((seg_tag.clone(), elem_index))
799 .or_insert(false);
800 simple_reverse
801 .entry((seg_tag.clone(), elem_index))
802 .or_insert(elem_id_lower);
803 }
804 }
805 }
806}
807
808#[cfg(test)]
809mod tests {
810 use super::*;
811
812 fn test_schema() -> serde_json::Value {
813 serde_json::json!({
814 "beschreibung": "Test PID",
815 "fields": {
816 "sg4": {
817 "segments": [
818 {
819 "id": "LOC",
820 "name": "Lokation",
821 "elements": [
822 {
823 "id": "3227",
824 "index": 0,
825 "name": "Lokation, Qualifier",
826 "type": "code"
827 },
828 {
829 "composite": "C517",
830 "index": 1,
831 "name": "Lokationsidentifikation",
832 "components": [
833 {
834 "id": "3225",
835 "sub_index": 0,
836 "name": "MaLo-ID",
837 "type": "data"
838 },
839 {
840 "id": "1131",
841 "sub_index": 1,
842 "name": "Codeliste, Code",
843 "type": "data"
844 }
845 ]
846 }
847 ]
848 },
849 {
850 "id": "SEQ",
851 "name": "Reihenfolge",
852 "elements": [
853 {
854 "id": "1229",
855 "index": 0,
856 "name": "Handlung, Code",
857 "type": "code"
858 },
859 {
860 "composite": "C286",
861 "index": 1,
862 "name": "Information über eine Folge",
863 "components": [
864 {
865 "id": "1050",
866 "sub_index": 0,
867 "name": "Referenz auf Zeitraum-ID",
868 "type": "data"
869 }
870 ]
871 }
872 ]
873 }
874 ],
875 "source_group": "SG4",
876 "children": {
877 "sg8_zf0": {
878 "segments": [
879 {
880 "id": "CAV",
881 "name": "Merkmal",
882 "elements": [
883 {
884 "composite": "C889",
885 "index": 0,
886 "name": "Merkmalswert",
887 "components": [
888 {
889 "id": "7111",
890 "sub_index": 0,
891 "name": "Merkmalswert, Code",
892 "type": "code"
893 },
894 {
895 "id": "7110",
896 "sub_index": 1,
897 "name": "Merkmalswert",
898 "type": "data"
899 }
900 ]
901 }
902 ]
903 }
904 ],
905 "source_group": "SG8"
906 }
907 }
908 }
909 }
910 })
911 }
912
913 fn sts_schema() -> serde_json::Value {
914 serde_json::json!({
915 "beschreibung": "STS ordinal test",
916 "fields": {
917 "sg4": {
918 "segments": [{
919 "id": "STS",
920 "name": "Status",
921 "elements": [
922 {
923 "composite": "C601",
924 "index": 0,
925 "name": "Statuskategorie",
926 "components": [{
927 "id": "9015",
928 "sub_index": 0,
929 "type": "code"
930 }]
931 },
932 {
933 "composite": "C555",
934 "index": 1,
935 "name": "Status",
936 "components": [{
937 "id": "4405",
938 "sub_index": 0,
939 "type": "data"
940 }]
941 },
942 {
943 "composite": "C556",
944 "index": 2,
945 "name": "Statusanlaß",
946 "components": [{
947 "id": "9013",
948 "sub_index": 0,
949 "type": "code"
950 }]
951 },
952 {
953 "composite": "C556",
954 "index": 3,
955 "name": "Statusanlaß",
956 "components": [{
957 "id": "9013",
958 "sub_index": 0,
959 "type": "code"
960 }]
961 },
962 {
963 "composite": "C556",
964 "index": 4,
965 "name": "Statusanlaß",
966 "components": [{
967 "id": "9013",
968 "sub_index": 0,
969 "type": "code"
970 }]
971 }
972 ]
973 }],
974 "source_group": "SG4"
975 }
976 }
977 })
978 }
979
980 fn nad_schema() -> serde_json::Value {
981 serde_json::json!({
982 "beschreibung": "NAD ordinal test",
983 "fields": {
984 "sg12_z04": {
985 "segments": [{
986 "id": "NAD",
987 "name": "Geschäftspartner",
988 "elements": [
989 {
990 "id": "3229",
991 "index": 0,
992 "type": "code"
993 },
994 {
995 "composite": "C082",
996 "index": 1,
997 "name": "Identifikation",
998 "components": [
999 { "id": "3039", "sub_index": 0, "type": "data" },
1000 { "id": "1131", "sub_index": 1, "type": "data" },
1001 { "id": "3055", "sub_index": 2, "type": "code" }
1002 ]
1003 },
1004 {
1005 "composite": "C058",
1006 "index": 2,
1007 "name": "Zusatzinfo",
1008 "components": [
1009 { "id": "3124", "sub_index": 0, "type": "data" },
1010 { "id": "3124", "sub_index": 1, "type": "data" },
1011 { "id": "3124", "sub_index": 2, "type": "data" }
1012 ]
1013 },
1014 {
1015 "composite": "C080",
1016 "index": 3,
1017 "name": "Name",
1018 "components": [
1019 { "id": "3036", "sub_index": 0, "type": "data" },
1020 { "id": "3036", "sub_index": 1, "type": "data" },
1021 { "id": "3036", "sub_index": 2, "type": "data" },
1022 { "id": "3036", "sub_index": 3, "type": "data" },
1023 { "id": "3036", "sub_index": 4, "type": "data" },
1024 { "id": "3045", "sub_index": 5, "type": "code" }
1025 ]
1026 }
1027 ]
1028 }],
1029 "source_group": "SG12"
1030 }
1031 }
1032 })
1033 }
1034
1035 #[test]
1038 fn resolve_composite_path() {
1039 let resolver = PathResolver::from_schema(&test_schema());
1040 assert_eq!(resolver.resolve_path("loc.c517.d3225"), "loc.1.0");
1041 assert_eq!(resolver.resolve_path("loc.c517.d1131"), "loc.1.1");
1042 }
1043
1044 #[test]
1045 fn resolve_simple_element_path() {
1046 let resolver = PathResolver::from_schema(&test_schema());
1047 assert_eq!(resolver.resolve_path("loc.d3227"), "loc.0");
1048 assert_eq!(resolver.resolve_path("seq.d1229"), "seq.0");
1049 }
1050
1051 #[test]
1052 fn resolve_nested_group_paths() {
1053 let resolver = PathResolver::from_schema(&test_schema());
1054 assert_eq!(resolver.resolve_path("cav.c889.d7111"), "cav.0.0");
1055 assert_eq!(resolver.resolve_path("cav.c889.d7110"), "cav.0.1");
1056 }
1057
1058 #[test]
1059 fn numeric_paths_unchanged() {
1060 let resolver = PathResolver::from_schema(&test_schema());
1061 assert_eq!(resolver.resolve_path("loc.1.0"), "loc.1.0");
1062 assert_eq!(resolver.resolve_path("loc.0"), "loc.0");
1063 assert_eq!(resolver.resolve_path("seq.0"), "seq.0");
1064 }
1065
1066 #[test]
1067 fn qualifier_paths() {
1068 let resolver = PathResolver::from_schema(&test_schema());
1069 assert_eq!(resolver.resolve_path("cav[Z91].c889.d7111"), "cav[Z91].0.0");
1070 assert_eq!(resolver.resolve_path("cav[Z91].0.1"), "cav[Z91].0.1");
1071 }
1072
1073 #[test]
1074 fn resolve_discriminator_named() {
1075 let resolver = PathResolver::from_schema(&test_schema());
1076 assert_eq!(
1078 resolver.resolve_discriminator("SEQ.d1229=ZF0"),
1079 "SEQ.0.0=ZF0"
1080 );
1081 assert_eq!(
1082 resolver.resolve_discriminator("LOC.d3227=Z16"),
1083 "LOC.0.0=Z16"
1084 );
1085 }
1086
1087 #[test]
1088 fn resolve_discriminator_numeric() {
1089 let resolver = PathResolver::from_schema(&test_schema());
1090 assert_eq!(resolver.resolve_discriminator("SEQ.0.0=ZF0"), "SEQ.0.0=ZF0");
1092 assert_eq!(resolver.resolve_discriminator("SEQ.0=ZF0"), "SEQ.0.0=ZF0");
1094 }
1095
1096 #[test]
1097 fn resolve_discriminator_composite() {
1098 let resolver = PathResolver::from_schema(&sts_schema());
1099 assert_eq!(
1101 resolver.resolve_discriminator("STS.c556.d9013=E01"),
1102 "STS.2.0=E01"
1103 );
1104 assert_eq!(
1105 resolver.resolve_discriminator("STS.c556_2.d9013=ZW4"),
1106 "STS.3.0=ZW4"
1107 );
1108 }
1109
1110 #[test]
1111 fn unresolved_paths_unchanged() {
1112 let resolver = PathResolver::from_schema(&test_schema());
1113 assert_eq!(resolver.resolve_path("xyz.d9999"), "xyz.d9999");
1114 assert_eq!(resolver.resolve_path("loc"), "loc");
1115 }
1116
1117 #[test]
1118 fn composite_id_case_insensitive() {
1119 let resolver = PathResolver::from_schema(&test_schema());
1120 assert_eq!(resolver.resolve_path("loc.c517.d3225"), "loc.1.0");
1121 assert_eq!(resolver.resolve_path("LOC.c517.d3225"), "LOC.1.0");
1122 }
1123
1124 #[test]
1125 fn seq_composite_path() {
1126 let resolver = PathResolver::from_schema(&test_schema());
1127 assert_eq!(resolver.resolve_path("seq.c286.d1050"), "seq.1.0");
1128 }
1129
1130 #[test]
1133 fn ordinal_suffix_duplicate_composites() {
1134 let resolver = PathResolver::from_schema(&sts_schema());
1135 assert_eq!(resolver.resolve_path("sts.c556.d9013"), "sts.2.0");
1137 assert_eq!(resolver.resolve_path("sts.c556_2.d9013"), "sts.3.0");
1139 assert_eq!(resolver.resolve_path("sts.c556_3.d9013"), "sts.4.0");
1141 assert_eq!(resolver.resolve_path("sts.c601.d9015"), "sts.0.0");
1143 assert_eq!(resolver.resolve_path("sts.c555.d4405"), "sts.1.0");
1144 }
1145
1146 #[test]
1147 fn ordinal_suffix_duplicate_data_elements() {
1148 let resolver = PathResolver::from_schema(&nad_schema());
1149 assert_eq!(resolver.resolve_path("nad.c080.d3036"), "nad.3.0");
1151 assert_eq!(resolver.resolve_path("nad.c080.d3036_2"), "nad.3.1");
1152 assert_eq!(resolver.resolve_path("nad.c080.d3036_3"), "nad.3.2");
1153 assert_eq!(resolver.resolve_path("nad.c080.d3036_4"), "nad.3.3");
1154 assert_eq!(resolver.resolve_path("nad.c080.d3036_5"), "nad.3.4");
1155 assert_eq!(resolver.resolve_path("nad.c080.d3045"), "nad.3.5");
1156 assert_eq!(resolver.resolve_path("nad.c058.d3124"), "nad.2.0");
1158 assert_eq!(resolver.resolve_path("nad.c058.d3124_2"), "nad.2.1");
1159 assert_eq!(resolver.resolve_path("nad.c058.d3124_3"), "nad.2.2");
1160 }
1161
1162 #[test]
1163 fn is_edifact_id_with_suffix() {
1164 assert!(is_edifact_id("c556"));
1165 assert!(is_edifact_id("c556_2"));
1166 assert!(is_edifact_id("c556_3"));
1167 assert!(is_edifact_id("d3036"));
1168 assert!(is_edifact_id("d3036_2"));
1169 assert!(is_edifact_id("D3036_5"));
1170 assert!(is_edifact_id("s002"));
1171 assert!(is_edifact_id("S009"));
1172 assert!(is_edifact_id("s011"));
1173 assert!(!is_edifact_id("c"));
1174 assert!(!is_edifact_id("c_2"));
1175 assert!(!is_edifact_id("c556_"));
1176 assert!(!is_edifact_id("c556_a"));
1177 assert!(!is_edifact_id("abc"));
1178 assert!(!is_edifact_id("123"));
1179 }
1180
1181 #[test]
1184 fn reverse_path_composite() {
1185 let resolver = ReversePathResolver::from_schema(&test_schema());
1186 assert_eq!(resolver.reverse_path("loc.1.0"), "loc.c517.d3225");
1187 assert_eq!(resolver.reverse_path("loc.1.1"), "loc.c517.d1131");
1188 assert_eq!(resolver.reverse_path("seq.1.0"), "seq.c286.d1050");
1189 assert_eq!(resolver.reverse_path("cav.0.0"), "cav.c889.d7111");
1190 assert_eq!(resolver.reverse_path("cav.0.1"), "cav.c889.d7110");
1191 }
1192
1193 #[test]
1194 fn reverse_path_simple() {
1195 let resolver = ReversePathResolver::from_schema(&test_schema());
1196 assert_eq!(resolver.reverse_path("loc.0"), "loc.d3227");
1197 assert_eq!(resolver.reverse_path("seq.0"), "seq.d1229");
1198 }
1199
1200 #[test]
1201 fn reverse_path_two_part_composite() {
1202 let resolver = ReversePathResolver::from_schema(&sts_schema());
1203 assert_eq!(resolver.reverse_path("sts.0"), "sts.c601.d9015");
1205 assert_eq!(resolver.reverse_path("sts.1"), "sts.c555.d4405");
1206 assert_eq!(resolver.reverse_path("sts.2"), "sts.c556.d9013");
1207 }
1208
1209 #[test]
1210 fn reverse_path_ordinal_composites() {
1211 let resolver = ReversePathResolver::from_schema(&sts_schema());
1212 assert_eq!(resolver.reverse_path("sts.2.0"), "sts.c556.d9013");
1213 assert_eq!(resolver.reverse_path("sts.3.0"), "sts.c556_2.d9013");
1214 assert_eq!(resolver.reverse_path("sts.4.0"), "sts.c556_3.d9013");
1215 }
1216
1217 #[test]
1218 fn reverse_path_ordinal_data_elements() {
1219 let resolver = ReversePathResolver::from_schema(&nad_schema());
1220 assert_eq!(resolver.reverse_path("nad.3.0"), "nad.c080.d3036");
1221 assert_eq!(resolver.reverse_path("nad.3.1"), "nad.c080.d3036_2");
1222 assert_eq!(resolver.reverse_path("nad.3.2"), "nad.c080.d3036_3");
1223 assert_eq!(resolver.reverse_path("nad.3.3"), "nad.c080.d3036_4");
1224 assert_eq!(resolver.reverse_path("nad.3.4"), "nad.c080.d3036_5");
1225 assert_eq!(resolver.reverse_path("nad.3.5"), "nad.c080.d3045");
1226 }
1227
1228 #[test]
1229 fn reverse_path_qualifier() {
1230 let resolver = ReversePathResolver::from_schema(&test_schema());
1231 assert_eq!(resolver.reverse_path("cav[Z91].0.0"), "cav[Z91].c889.d7111");
1232 assert_eq!(resolver.reverse_path("cav[Z91].0.1"), "cav[Z91].c889.d7110");
1233 }
1234
1235 #[test]
1236 fn reverse_path_already_named() {
1237 let resolver = ReversePathResolver::from_schema(&test_schema());
1238 assert_eq!(resolver.reverse_path("loc.c517.d3225"), "loc.c517.d3225");
1239 assert_eq!(resolver.reverse_path("loc.d3227"), "loc.d3227");
1240 }
1241
1242 #[test]
1243 fn reverse_discriminator_simple() {
1244 let resolver = ReversePathResolver::from_schema(&test_schema());
1245 assert_eq!(
1246 resolver.reverse_discriminator("LOC.0.0=Z16"),
1247 "LOC.d3227=Z16"
1248 );
1249 assert_eq!(
1250 resolver.reverse_discriminator("SEQ.0.0=ZF0"),
1251 "SEQ.d1229=ZF0"
1252 );
1253 }
1254
1255 #[test]
1256 fn reverse_discriminator_composite() {
1257 let resolver = ReversePathResolver::from_schema(&sts_schema());
1258 assert_eq!(
1259 resolver.reverse_discriminator("STS.2.0=E01"),
1260 "STS.c556.d9013=E01"
1261 );
1262 assert_eq!(
1263 resolver.reverse_discriminator("STS.3.0=ZW4"),
1264 "STS.c556_2.d9013=ZW4"
1265 );
1266 }
1267
1268 #[test]
1269 fn reverse_discriminator_already_named() {
1270 let resolver = ReversePathResolver::from_schema(&test_schema());
1271 assert_eq!(
1273 resolver.reverse_discriminator("LOC.d3227=Z16"),
1274 "LOC.d3227=Z16"
1275 );
1276 }
1277
1278 fn s_prefix_schema() -> serde_json::Value {
1279 serde_json::json!({
1280 "beschreibung": "CONTRL S-prefix test",
1281 "fields": {},
1282 "root_segments": [{
1283 "id": "UCI",
1284 "name": "Übertragungsprüfung",
1285 "elements": [
1286 { "id": "0020", "index": 0, "type": "data" },
1287 {
1288 "composite": "S002",
1289 "index": 1,
1290 "name": "Absender",
1291 "components": [
1292 { "id": "0004", "sub_index": 0, "type": "data" },
1293 { "id": "0007", "sub_index": 1, "type": "code" }
1294 ]
1295 },
1296 {
1297 "composite": "S003",
1298 "index": 2,
1299 "name": "Empfänger",
1300 "components": [
1301 { "id": "0010", "sub_index": 0, "type": "data" },
1302 { "id": "0007", "sub_index": 1, "type": "code" }
1303 ]
1304 },
1305 { "id": "0083", "index": 3, "type": "code" },
1306 {
1307 "composite": "S011",
1308 "index": 6,
1309 "name": "Datenelement-Identifikation",
1310 "components": [
1311 { "id": "0098", "sub_index": 0, "type": "data" },
1312 { "id": "0104", "sub_index": 1, "type": "data" }
1313 ]
1314 }
1315 ]
1316 }]
1317 })
1318 }
1319
1320 #[test]
1321 fn resolve_s_prefix_composites() {
1322 let resolver = PathResolver::from_schema(&s_prefix_schema());
1323 assert_eq!(resolver.resolve_path("uci.s002.d0004"), "uci.1.0");
1324 assert_eq!(resolver.resolve_path("uci.s002.d0007"), "uci.1.1");
1325 assert_eq!(resolver.resolve_path("uci.s003.d0010"), "uci.2.0");
1326 assert_eq!(resolver.resolve_path("uci.s003.d0007"), "uci.2.1");
1327 assert_eq!(resolver.resolve_path("uci.s011.d0098"), "uci.6.0");
1328 assert_eq!(resolver.resolve_path("uci.s011.d0104"), "uci.6.1");
1329 assert_eq!(resolver.resolve_path("uci.d0020"), "uci.0");
1330 assert_eq!(resolver.resolve_path("uci.d0083"), "uci.3");
1331 }
1332
1333 #[test]
1334 fn reverse_s_prefix_composites() {
1335 let resolver = ReversePathResolver::from_schema(&s_prefix_schema());
1336 assert_eq!(resolver.reverse_path("uci.1.0"), "uci.s002.d0004");
1337 assert_eq!(resolver.reverse_path("uci.1.1"), "uci.s002.d0007");
1338 assert_eq!(resolver.reverse_path("uci.2.0"), "uci.s003.d0010");
1339 assert_eq!(resolver.reverse_path("uci.6.0"), "uci.s011.d0098");
1340 assert_eq!(resolver.reverse_path("uci.0"), "uci.d0020");
1341 }
1342
1343 #[test]
1344 fn forward_reverse_roundtrip() {
1345 let fwd = PathResolver::from_schema(&sts_schema());
1346 let rev = ReversePathResolver::from_schema(&sts_schema());
1347
1348 let named = "sts.c556_2.d9013";
1350 let numeric = fwd.resolve_path(named);
1351 assert_eq!(numeric, "sts.3.0");
1352 let back = rev.reverse_path(&numeric);
1353 assert_eq!(back, named);
1354
1355 let named_simple = "nad.d3229";
1357 let fwd_nad = PathResolver::from_schema(&nad_schema());
1358 let rev_nad = ReversePathResolver::from_schema(&nad_schema());
1359 let numeric_simple = fwd_nad.resolve_path(named_simple);
1360 assert_eq!(numeric_simple, "nad.0");
1361 let back_simple = rev_nad.reverse_path(&numeric_simple);
1362 assert_eq!(back_simple, named_simple);
1363 }
1364
1365 #[test]
1366 fn discriminator_forward_reverse_roundtrip() {
1367 let fwd = PathResolver::from_schema(&test_schema());
1368 let rev = ReversePathResolver::from_schema(&test_schema());
1369
1370 let named = "LOC.d3227=Z16";
1371 let numeric = fwd.resolve_discriminator(named);
1372 assert_eq!(numeric, "LOC.0.0=Z16");
1373 let back = rev.reverse_discriminator(&numeric);
1374 assert_eq!(back, named);
1375 }
1376}