1use std::collections::HashMap;
4use std::sync::Mutex;
5
6use mig_assembly::ConversionService;
7use mig_bo4e::engine::DataBundle;
8use mig_bo4e::MappingEngine;
9
10use crate::data_dir::DataDir;
11use crate::error::MapperError;
12
13pub struct Bo4eResult {
15 pub pid: String,
17 pub message_type: String,
19 pub variant: String,
21 pub bo4e: serde_json::Value,
23}
24
25pub struct Mapper {
43 data_dir: DataDir,
44 bundles: Mutex<HashMap<String, DataBundle>>,
45}
46
47impl Mapper {
48 pub fn from_data_dir(data_dir: DataDir) -> Result<Self, MapperError> {
53 let mapper = Self {
54 data_dir,
55 bundles: Mutex::new(HashMap::new()),
56 };
57 let eager_fvs: Vec<String> = mapper.data_dir.eager_fvs().to_vec();
58 for fv in &eager_fvs {
59 mapper.ensure_bundle_loaded(fv)?;
60 }
61 Ok(mapper)
62 }
63
64 fn ensure_bundle_loaded(&self, fv: &str) -> Result<(), MapperError> {
66 let mut bundles = self.bundles.lock().unwrap();
67 if bundles.contains_key(fv) {
68 return Ok(());
69 }
70 let path = self.data_dir.bundle_path(fv);
71 if !path.exists() {
72 return Err(MapperError::BundleNotFound { fv: fv.to_string() });
73 }
74 let bundle = DataBundle::load(&path)?;
75 bundles.insert(fv.to_string(), bundle);
76 Ok(())
77 }
78
79 pub fn conversion_service(
83 &self,
84 fv: &str,
85 variant: &str,
86 ) -> Result<ConversionService, MapperError> {
87 self.ensure_bundle_loaded(fv)?;
88 let bundles = self.bundles.lock().unwrap();
89 let bundle = bundles.get(fv).unwrap();
90 let vc = bundle
91 .variant(variant)
92 .ok_or_else(|| MapperError::VariantNotFound {
93 fv: fv.to_string(),
94 variant: variant.to_string(),
95 })?;
96 let mig = vc
97 .mig_schema
98 .as_ref()
99 .ok_or_else(|| MapperError::VariantNotFound {
100 fv: fv.to_string(),
101 variant: format!("{variant} (no MIG schema in bundle)"),
102 })?;
103 Ok(ConversionService::from_mig(mig.clone()))
104 }
105
106 pub fn engine(&self, fv: &str, variant: &str, pid: &str) -> Result<MappingEngine, MapperError> {
110 self.ensure_bundle_loaded(fv)?;
111 let bundles = self.bundles.lock().unwrap();
112 let bundle = bundles.get(fv).unwrap();
113 let vc = bundle
114 .variant(variant)
115 .ok_or_else(|| MapperError::VariantNotFound {
116 fv: fv.to_string(),
117 variant: variant.to_string(),
118 })?;
119 let pid_key = format!("pid_{pid}");
120 let defs = vc
121 .combined_defs
122 .get(&pid_key)
123 .ok_or_else(|| MapperError::PidNotFound {
124 fv: fv.to_string(),
125 variant: variant.to_string(),
126 pid: pid.to_string(),
127 })?;
128 Ok(MappingEngine::from_definitions(defs.clone()))
129 }
130
131 pub fn validate_pid(
136 &self,
137 json: &serde_json::Value,
138 fv: &str,
139 variant: &str,
140 pid: &str,
141 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
142 self.ensure_bundle_loaded(fv)?;
143 let bundles = self.bundles.lock().unwrap();
144 let bundle = bundles.get(fv).unwrap();
145 let vc = bundle
146 .variant(variant)
147 .ok_or_else(|| MapperError::VariantNotFound {
148 fv: fv.to_string(),
149 variant: variant.to_string(),
150 })?;
151 let pid_key = format!("pid_{pid}");
152 let requirements =
153 vc.pid_requirements
154 .get(&pid_key)
155 .ok_or_else(|| MapperError::PidNotFound {
156 fv: fv.to_string(),
157 variant: variant.to_string(),
158 pid: pid.to_string(),
159 })?;
160
161 Ok(mig_bo4e::pid_validation::validate_pid_json(
162 json,
163 requirements,
164 ))
165 }
166
167 pub fn validate_pid_struct(
179 &self,
180 value: &impl serde::Serialize,
181 fv: &str,
182 variant: &str,
183 pid: &str,
184 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
185 let json = serde_json::to_value(value).map_err(|e| {
186 MapperError::Mapping(mig_bo4e::MappingError::TypeConversion(e.to_string()))
187 })?;
188 self.validate_pid(&json, fv, variant, pid)
189 }
190
191 pub fn validate_pid_with_conditions(
199 &self,
200 json: &serde_json::Value,
201 fv: &str,
202 variant: &str,
203 pid: &str,
204 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
205 self.ensure_bundle_loaded(fv)?;
206 let bundles = self.bundles.lock().unwrap();
207 let bundle = bundles.get(fv).unwrap();
208 let vc = bundle
209 .variant(variant)
210 .ok_or_else(|| MapperError::VariantNotFound {
211 fv: fv.to_string(),
212 variant: variant.to_string(),
213 })?;
214 let pid_key = format!("pid_{pid}");
215
216 let requirements =
217 vc.pid_requirements
218 .get(&pid_key)
219 .ok_or_else(|| MapperError::PidNotFound {
220 fv: fv.to_string(),
221 variant: variant.to_string(),
222 pid: pid.to_string(),
223 })?;
224
225 let evaluator = crate::evaluator_factory::create_evaluator(variant, fv);
227
228 if let Some(evaluator) = evaluator {
229 let defs = vc
231 .combined_defs
232 .get(&pid_key)
233 .ok_or_else(|| MapperError::PidNotFound {
234 fv: fv.to_string(),
235 variant: variant.to_string(),
236 pid: pid.to_string(),
237 })?;
238 let engine = MappingEngine::from_definitions(defs.clone());
239 let tree = engine.map_all_reverse(json, None);
240
241 let segments = crate::tree_to_segments::tree_to_owned_segments(&tree);
243
244 Ok(crate::evaluator_factory::validate_with_boxed_evaluator(
246 evaluator.as_ref(),
247 json,
248 requirements,
249 pid,
250 &segments,
251 ))
252 } else {
253 Ok(mig_bo4e::pid_validation::validate_pid_json(
255 json,
256 requirements,
257 ))
258 }
259 }
260
261 pub fn to_edifact(
287 &self,
288 msg_stammdaten: &serde_json::Value,
289 tx_stammdaten: &[serde_json::Value],
290 fv: &str,
291 variant: &str,
292 pid: &str,
293 ) -> Result<String, MapperError> {
294 self.ensure_bundle_loaded(fv)?;
295 let bundles = self.bundles.lock().unwrap();
296 let bundle = bundles.get(fv).unwrap();
297 let vc = bundle
298 .variant(variant)
299 .ok_or_else(|| MapperError::VariantNotFound {
300 fv: fv.to_string(),
301 variant: variant.to_string(),
302 })?;
303
304 let tx_group = vc
305 .tx_group(pid)
306 .ok_or_else(|| MapperError::PidNotFound {
307 fv: fv.to_string(),
308 variant: variant.to_string(),
309 pid: pid.to_string(),
310 })?;
311
312 let msg_engine = vc.msg_engine();
313 let tx_engine =
314 vc.tx_engine(pid)
315 .ok_or_else(|| MapperError::PidNotFound {
316 fv: fv.to_string(),
317 variant: variant.to_string(),
318 pid: pid.to_string(),
319 })?;
320
321 let filtered_mig =
322 vc.filtered_mig(pid)
323 .ok_or_else(|| MapperError::NoMigSchema {
324 fv: fv.to_string(),
325 variant: variant.to_string(),
326 })?;
327
328 let transaktionen: Vec<mig_bo4e::model::MappedTransaktion> = tx_stammdaten
330 .iter()
331 .map(|tx| mig_bo4e::model::MappedTransaktion {
332 stammdaten: tx.clone(),
333 nesting_info: Default::default(),
334 })
335 .collect();
336 let mapped = mig_bo4e::model::MappedMessage {
337 stammdaten: msg_stammdaten.clone(),
338 transaktionen,
339 nesting_info: Default::default(),
340 };
341
342 let tree = MappingEngine::map_interchange_reverse(
344 &msg_engine,
345 &tx_engine,
346 &mapped,
347 tx_group,
348 Some(&filtered_mig),
349 );
350
351 let disassembler =
353 mig_assembly::disassembler::Disassembler::new(&filtered_mig);
354 let segments = disassembler.disassemble(&tree);
355
356 let delimiters = edifact_primitives::EdifactDelimiters::default();
358 Ok(mig_assembly::renderer::render_edifact(
359 &segments,
360 &delimiters,
361 ))
362 }
363
364 pub fn to_edifact_struct(
370 &self,
371 nachricht: &impl serde::Serialize,
372 fv: &str,
373 variant: &str,
374 pid: &str,
375 ) -> Result<String, MapperError> {
376 let json = serde_json::to_value(nachricht)
377 .map_err(|e| MapperError::Serialization(e.to_string()))?;
378
379 let msg_stammdaten = json
380 .get("stammdaten")
381 .cloned()
382 .unwrap_or(serde_json::Value::Object(Default::default()));
383
384 let tx_stammdaten: Vec<serde_json::Value> = json
385 .get("transaktionen")
386 .and_then(|v| v.as_array())
387 .cloned()
388 .unwrap_or_default();
389
390 self.to_edifact(&msg_stammdaten, &tx_stammdaten, fv, variant, pid)
391 }
392
393 pub fn association_code(&self, fv: &str, variant: &str) -> Result<String, MapperError> {
404 let meta = self.message_metadata(fv, variant)?;
405 Ok(meta.association_code)
406 }
407
408 pub fn message_metadata(
413 &self,
414 fv: &str,
415 variant: &str,
416 ) -> Result<MessageMetadata, MapperError> {
417 self.ensure_bundle_loaded(fv)?;
418 let bundles = self.bundles.lock().unwrap();
419 let bundle = bundles.get(fv).unwrap();
420 let vc = bundle
421 .variant(variant)
422 .ok_or_else(|| MapperError::VariantNotFound {
423 fv: fv.to_string(),
424 variant: variant.to_string(),
425 })?;
426 let mig = vc
427 .mig_schema
428 .as_ref()
429 .ok_or_else(|| MapperError::NoMigSchema {
430 fv: fv.to_string(),
431 variant: variant.to_string(),
432 })?;
433 Ok(MessageMetadata {
434 message_type: mig.message_type.clone(),
435 release: release_code_for_message_type(&mig.message_type),
436 association_code: mig.version.clone(),
437 })
438 }
439
440 pub fn to_edifact_interchange(
464 &self,
465 envelope: &InterchangeEnvelope,
466 messages: &[InterchangeMessage],
467 ) -> Result<String, MapperError> {
468 let delimiters = edifact_primitives::EdifactDelimiters::default();
469 let sep = delimiters.component as char;
470 let elem = delimiters.element as char;
471 let seg_term = delimiters.segment as char;
472
473 let mut output = String::new();
474
475 output.push_str(&format!(
477 "UNA{}{}{}{}{}{}",
478 sep, elem, delimiters.decimal as char, delimiters.release as char, ' ', seg_term, ));
485
486 let now = chrono::Utc::now();
488 let date_str = now.format("%y%m%d").to_string();
489 let time_str = now.format("%H%M").to_string();
490 let sender = &envelope.sender;
491 let receiver = &envelope.receiver;
492 let interchange_ref = &envelope.interchange_ref;
493 output.push_str(&format!(
494 "UNB{elem}UNOC{sep}3{elem}{sid}{sep}{sq}{elem}{rid}{sep}{rq}{elem}{date_str}{sep}{time_str}{elem}{interchange_ref}{seg_term}",
495 sid = sender.id,
496 sq = sender.qualifier,
497 rid = receiver.id,
498 rq = receiver.qualifier,
499 ));
500
501 let mut message_count = 0u32;
502
503 for msg in messages {
504 let meta = self.message_metadata(&msg.fv, &msg.variant)?;
505
506 let body = self.to_edifact(
508 &msg.msg_stammdaten,
509 &msg.tx_stammdaten,
510 &msg.fv,
511 &msg.variant,
512 &msg.pid,
513 )?;
514
515 let body_seg_count = body
517 .split(seg_term)
518 .filter(|s: &&str| !s.is_empty())
519 .count();
520 let segment_count = body_seg_count + 2;
522
523 output.push_str(&format!(
525 "UNH{elem}{ref}{elem}{msg_type}{sep}D{sep}{release}{sep}UN{sep}{assoc}{seg_term}",
526 ref = msg.message_ref,
527 msg_type = meta.message_type,
528 release = meta.release,
529 assoc = meta.association_code,
530 ));
531
532 output.push_str(&body);
534
535 output.push_str(&format!(
537 "UNT{elem}{segment_count}{elem}{ref}{seg_term}",
538 ref = msg.message_ref,
539 ));
540
541 message_count += 1;
542 }
543
544 output.push_str(&format!(
546 "UNZ{elem}{message_count}{elem}{interchange_ref}{seg_term}",
547 ));
548
549 Ok(output)
550 }
551
552 pub fn loaded_format_versions(&self) -> Vec<String> {
554 self.bundles.lock().unwrap().keys().cloned().collect()
555 }
556
557 pub fn variants(&self, fv: &str) -> Result<Vec<String>, MapperError> {
561 self.ensure_bundle_loaded(fv)?;
562 let bundles = self.bundles.lock().unwrap();
563 let bundle = bundles.get(fv).unwrap();
564 Ok(bundle.variants.keys().cloned().collect())
565 }
566}
567
568#[derive(Debug, Clone)]
570pub struct MessageMetadata {
571 pub message_type: String,
573 pub release: String,
575 pub association_code: String,
577}
578
579#[derive(Debug, Clone)]
581pub struct InterchangeEnvelope {
582 pub sender: EdifactParty,
584 pub receiver: EdifactParty,
586 pub interchange_ref: String,
588}
589
590#[derive(Debug, Clone)]
592pub struct EdifactParty {
593 pub id: String,
595 pub qualifier: String,
597}
598
599impl EdifactParty {
600 pub fn bdew(id: &str) -> Self {
602 Self {
603 id: id.to_string(),
604 qualifier: "500".to_string(),
605 }
606 }
607
608 pub fn gs1(id: &str) -> Self {
610 Self {
611 id: id.to_string(),
612 qualifier: "14".to_string(),
613 }
614 }
615}
616
617#[derive(Debug, Clone)]
620pub struct InterchangeMessage {
621 pub message_ref: String,
623 pub msg_stammdaten: serde_json::Value,
625 pub tx_stammdaten: Vec<serde_json::Value>,
627 pub fv: String,
629 pub variant: String,
631 pub pid: String,
633}
634
635fn release_code_for_message_type(msg_type: &str) -> String {
639 match msg_type {
640 "APERAK" => "07B",
641 "COMDIS" => "17A",
642 "CONTRL" => "04B",
643 "IFTSTA" => "18A",
644 "INSRPT" => "18A",
645 "INVOIC" => "06A",
646 "MSCONS" => "04B",
647 "ORDCHG" => "09B",
648 "ORDERS" => "09B",
649 "ORDRSP" => "10A",
650 "PARTIN" => "20B",
651 "PRICAT" => "20B",
652 "QUOTES" => "10A",
653 "REMADV" => "05A",
654 "REQOTE" => "10A",
655 "UTILMD" => "11A",
656 "UTILTS" => "18A",
657 _ => "04B", }
659 .to_string()
660}
661
662
663#[cfg(test)]
664mod tests {
665 use super::*;
666 use std::path::Path;
667
668 fn data_dir() -> Option<std::path::PathBuf> {
669 let dist = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../dist");
671 if dist.join("edifact-data-FV2504.bin").exists() {
672 return Some(dist);
673 }
674 let cache = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../cache/mappings");
675 if cache.join("FV2504").exists() {
676 return Some(cache);
677 }
678 eprintln!("Skipping test: no DataBundle files found");
679 None
680 }
681
682 #[test]
683 fn test_to_edifact_produces_edifact_output() {
684 let Some(data_dir) = data_dir() else {
685 return;
686 };
687 let mapper =
688 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
689
690 let msg_stammdaten = serde_json::json!({
691 "marktteilnehmer": [{
692 "marktrolle": "MS",
693 "rollencodenummer": "9900123456789",
694 "codepflegeCode": "293"
695 }]
696 });
697 let tx_stammdaten = serde_json::json!({
698 "prozessdaten": {
699 "pruefidentifikator": "55001",
700 "vorgangId": "ABC123",
701 "transaktionsgrund": "E01"
702 }
703 });
704
705 let result = mapper.to_edifact(
706 &msg_stammdaten,
707 &[tx_stammdaten],
708 "FV2504",
709 "UTILMD_Strom",
710 "55001",
711 );
712 assert!(result.is_ok(), "to_edifact failed: {:?}", result.err());
713 let edifact = result.unwrap();
714 assert!(!edifact.is_empty(), "EDIFACT output should not be empty");
715 assert!(edifact.contains("NAD"), "Should contain NAD segment");
717 assert!(edifact.contains("IDE"), "Should contain IDE segment");
719 }
720
721 #[test]
722 fn test_to_edifact_struct_produces_edifact_output() {
723 let Some(data_dir) = data_dir() else {
724 return;
725 };
726 let mapper =
727 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
728
729 let nachricht = serde_json::json!({
730 "stammdaten": {
731 "marktteilnehmer": [{
732 "marktrolle": "MS",
733 "rollencodenummer": "9900123456789",
734 "codepflegeCode": "293"
735 }]
736 },
737 "transaktionen": [{
738 "prozessdaten": {
739 "pruefidentifikator": "55001",
740 "vorgangId": "ABC123"
741 }
742 }]
743 });
744
745 let result = mapper.to_edifact_struct(&nachricht, "FV2504", "UTILMD_Strom", "55001");
746 assert!(
747 result.is_ok(),
748 "to_edifact_struct failed: {:?}",
749 result.err()
750 );
751 let edifact = result.unwrap();
752 assert!(!edifact.is_empty(), "EDIFACT output should not be empty");
753 }
754
755 #[test]
756 fn test_to_edifact_invalid_fv_returns_error() {
757 let Some(data_dir) = data_dir() else {
758 return;
759 };
760 let mapper =
761 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
762
763 let result = mapper.to_edifact(
764 &serde_json::json!({}),
765 &[serde_json::json!({})],
766 "FV9999",
767 "UTILMD_Strom",
768 "55001",
769 );
770 assert!(result.is_err());
771 }
772
773 #[test]
774 fn test_to_edifact_invalid_variant_returns_error() {
775 let Some(data_dir) = data_dir() else {
776 return;
777 };
778 let mapper =
779 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
780
781 let result = mapper.to_edifact(
782 &serde_json::json!({}),
783 &[serde_json::json!({})],
784 "FV2504",
785 "NONEXISTENT",
786 "55001",
787 );
788 assert!(result.is_err());
789 }
790
791 #[test]
792 fn test_to_edifact_invalid_pid_returns_error() {
793 let Some(data_dir) = data_dir() else {
794 return;
795 };
796 let mapper =
797 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
798
799 let result = mapper.to_edifact(
800 &serde_json::json!({}),
801 &[serde_json::json!({})],
802 "FV2504",
803 "UTILMD_Strom",
804 "99999",
805 );
806 assert!(result.is_err());
807 }
808
809 #[test]
810 fn test_association_code() {
811 let Some(data_dir) = data_dir() else {
812 return;
813 };
814 let mapper =
815 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
816
817 let code = mapper.association_code("FV2504", "UTILMD_Strom").unwrap();
818 assert_eq!(code, "S2.1");
819
820 let code = mapper.association_code("FV2504", "MSCONS").unwrap();
821 assert_eq!(code, "2.4c");
822 }
823
824 #[test]
825 fn test_message_metadata() {
826 let Some(data_dir) = data_dir() else {
827 return;
828 };
829 let mapper =
830 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
831
832 let meta = mapper.message_metadata("FV2504", "UTILMD_Strom").unwrap();
833 assert_eq!(meta.message_type, "UTILMD");
834 assert_eq!(meta.release, "11A");
835 assert_eq!(meta.association_code, "S2.1");
836 }
837
838 #[test]
839 fn test_to_edifact_interchange() {
840 let Some(data_dir) = data_dir() else {
841 return;
842 };
843 let mapper =
844 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
845
846 let result = mapper.to_edifact_interchange(
847 &InterchangeEnvelope {
848 sender: EdifactParty::bdew("9900000000003"),
849 receiver: EdifactParty::bdew("9900000000001"),
850 interchange_ref: "REF001".to_string(),
851 },
852 &[InterchangeMessage {
853 message_ref: "MSG001".to_string(),
854 msg_stammdaten: serde_json::json!({
855 "marktteilnehmer": [{
856 "marktrolle": "MS",
857 "rollencodenummer": "9900123456789",
858 "codepflegeCode": "293"
859 }]
860 }),
861 tx_stammdaten: vec![serde_json::json!({
862 "prozessdaten": {
863 "pruefidentifikator": "55001",
864 "vorgangId": "ABC123",
865 "transaktionsgrund": "E01"
866 }
867 })],
868 fv: "FV2504".to_string(),
869 variant: "UTILMD_Strom".to_string(),
870 pid: "55001".to_string(),
871 }],
872 );
873 assert!(
874 result.is_ok(),
875 "to_edifact_interchange failed: {:?}",
876 result.err()
877 );
878 let edifact = result.unwrap();
879
880 assert!(edifact.starts_with("UNA:+.? '"), "Should start with UNA");
882 assert!(edifact.contains("UNB+UNOC:3+9900000000003:500+9900000000001:500+"),
883 "Should contain UNB with sender/receiver");
884 assert!(edifact.contains("UNH+MSG001+UTILMD:D:11A:UN:S2.1'"),
885 "Should contain UNH with correct S009");
886 assert!(edifact.contains("NAD"), "Should contain body NAD segment");
887 assert!(edifact.contains("UNT+"), "Should contain UNT");
888 assert!(edifact.contains("+MSG001'"), "UNT should reference message ref");
889 assert!(edifact.contains("UNZ+1+REF001'"), "Should contain UNZ with count and ref");
890 }
891}