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
25#[derive(Debug, Clone)]
62pub struct PidListEntry {
63 pub fv: String,
64 pub variant: String,
65 pub pid: String,
66 pub beschreibung: String,
67}
68
69pub struct Mapper {
70 data_dir: DataDir,
71 bundles: Mutex<HashMap<String, DataBundle>>,
72}
73
74impl Mapper {
75 pub fn from_data_dir(data_dir: DataDir) -> Result<Self, MapperError> {
80 let mapper = Self {
81 data_dir,
82 bundles: Mutex::new(HashMap::new()),
83 };
84 let eager_fvs: Vec<String> = mapper.data_dir.eager_fvs().to_vec();
85 for fv in &eager_fvs {
86 mapper.ensure_bundle_loaded(fv)?;
87 }
88 Ok(mapper)
89 }
90
91 fn ensure_bundle_loaded(&self, fv: &str) -> Result<(), MapperError> {
93 let mut bundles = self.bundles.lock().unwrap();
94 if bundles.contains_key(fv) {
95 return Ok(());
96 }
97 let path = self.data_dir.bundle_path(fv);
98 if !path.exists() {
99 return Err(MapperError::BundleNotFound { fv: fv.to_string() });
100 }
101 let bundle = DataBundle::load(&path)?;
102 bundles.insert(fv.to_string(), bundle);
103 Ok(())
104 }
105
106 pub fn conversion_service(
110 &self,
111 fv: &str,
112 variant: &str,
113 ) -> Result<ConversionService, MapperError> {
114 self.ensure_bundle_loaded(fv)?;
115 let bundles = self.bundles.lock().unwrap();
116 let bundle = bundles.get(fv).unwrap();
117 let vc = bundle
118 .variant(variant)
119 .ok_or_else(|| MapperError::VariantNotFound {
120 fv: fv.to_string(),
121 variant: variant.to_string(),
122 })?;
123 let mig = vc
124 .mig_schema
125 .as_ref()
126 .ok_or_else(|| MapperError::VariantNotFound {
127 fv: fv.to_string(),
128 variant: format!("{variant} (no MIG schema in bundle)"),
129 })?;
130 Ok(ConversionService::from_mig(mig.clone()))
131 }
132
133 pub fn engine(&self, fv: &str, variant: &str, pid: &str) -> Result<MappingEngine, MapperError> {
137 self.ensure_bundle_loaded(fv)?;
138 let bundles = self.bundles.lock().unwrap();
139 let bundle = bundles.get(fv).unwrap();
140 let vc = bundle
141 .variant(variant)
142 .ok_or_else(|| MapperError::VariantNotFound {
143 fv: fv.to_string(),
144 variant: variant.to_string(),
145 })?;
146 let pid_key = format!("pid_{pid}");
147 let defs = vc
148 .combined_defs
149 .get(&pid_key)
150 .ok_or_else(|| MapperError::PidNotFound {
151 fv: fv.to_string(),
152 variant: variant.to_string(),
153 pid: pid.to_string(),
154 })?;
155 Ok(MappingEngine::from_definitions(defs.clone()))
156 }
157
158 pub fn pid_requirements(
163 &self,
164 fv: &str,
165 variant: &str,
166 pid: &str,
167 ) -> Result<mig_bo4e::pid_requirements::PidRequirements, MapperError> {
168 self.ensure_bundle_loaded(fv)?;
169 let bundles = self.bundles.lock().unwrap();
170 let bundle = bundles.get(fv).unwrap();
171 let vc = bundle
172 .variant(variant)
173 .ok_or_else(|| MapperError::VariantNotFound {
174 fv: fv.to_string(),
175 variant: variant.to_string(),
176 })?;
177 let pid_key = format!("pid_{pid}");
178 vc.pid_requirements
179 .get(&pid_key)
180 .cloned()
181 .ok_or_else(|| MapperError::PidNotFound {
182 fv: fv.to_string(),
183 variant: variant.to_string(),
184 pid: pid.to_string(),
185 })
186 }
187
188 pub fn bo4e_catalog(
194 &self,
195 fv: &str,
196 ) -> Result<mig_bo4e::bo4e_catalog::Bo4eCatalog, MapperError> {
197 self.ensure_bundle_loaded(fv)?;
198 let bundles = self.bundles.lock().unwrap();
199 let bundle = bundles.get(fv).unwrap();
200 Ok(bundle.bo4e_catalog.clone())
201 }
202
203 pub fn list_pids(&self) -> Result<Vec<PidListEntry>, MapperError> {
208 let dir = self.data_dir.data_path();
209 let read_dir = std::fs::read_dir(dir).map_err(|_| MapperError::DataDirNotFound {
210 path: dir.display().to_string(),
211 })?;
212
213 let mut result = Vec::new();
214
215 for entry in read_dir.flatten() {
216 let path = entry.path();
217 if path.extension().is_some_and(|e| e == "bin") {
218 let stem = path
219 .file_stem()
220 .and_then(|s| s.to_str())
221 .unwrap_or("")
222 .to_string();
223 let fv = match stem.strip_prefix("edifact-data-") {
224 Some(v) => v.to_string(),
225 None => continue,
226 };
227 self.ensure_bundle_loaded(&fv)?;
228 let bundles = self.bundles.lock().unwrap();
229 if let Some(bundle) = bundles.get(&fv) {
230 for (variant, vc) in &bundle.variants {
231 for (pid_key, req) in &vc.pid_requirements {
232 let pid = pid_key.strip_prefix("pid_").unwrap_or(pid_key).to_string();
233 result.push(PidListEntry {
234 fv: fv.clone(),
235 variant: variant.clone(),
236 pid,
237 beschreibung: req.beschreibung.clone(),
238 });
239 }
240 }
241 }
242 }
243 }
244
245 result.sort_by(|a, b| a.pid.cmp(&b.pid));
246 Ok(result)
247 }
248
249 pub fn validate_pid(
254 &self,
255 json: &serde_json::Value,
256 fv: &str,
257 variant: &str,
258 pid: &str,
259 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
260 self.ensure_bundle_loaded(fv)?;
261 let bundles = self.bundles.lock().unwrap();
262 let bundle = bundles.get(fv).unwrap();
263 let vc = bundle
264 .variant(variant)
265 .ok_or_else(|| MapperError::VariantNotFound {
266 fv: fv.to_string(),
267 variant: variant.to_string(),
268 })?;
269 let pid_key = format!("pid_{pid}");
270 let requirements =
271 vc.pid_requirements
272 .get(&pid_key)
273 .ok_or_else(|| MapperError::PidNotFound {
274 fv: fv.to_string(),
275 variant: variant.to_string(),
276 pid: pid.to_string(),
277 })?;
278
279 Ok(mig_bo4e::pid_validation::validate_pid_json(
280 json,
281 requirements,
282 ))
283 }
284
285 pub fn validate_pid_struct(
297 &self,
298 value: &impl serde::Serialize,
299 fv: &str,
300 variant: &str,
301 pid: &str,
302 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
303 let json = serde_json::to_value(value).map_err(|e| {
304 MapperError::Mapping(mig_bo4e::MappingError::TypeConversion(e.to_string()))
305 })?;
306 self.validate_pid(&json, fv, variant, pid)
307 }
308
309 pub fn validate_pid_with_conditions(
317 &self,
318 json: &serde_json::Value,
319 fv: &str,
320 variant: &str,
321 pid: &str,
322 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
323 self.ensure_bundle_loaded(fv)?;
324 let bundles = self.bundles.lock().unwrap();
325 let bundle = bundles.get(fv).unwrap();
326 let vc = bundle
327 .variant(variant)
328 .ok_or_else(|| MapperError::VariantNotFound {
329 fv: fv.to_string(),
330 variant: variant.to_string(),
331 })?;
332 let pid_key = format!("pid_{pid}");
333
334 let requirements =
335 vc.pid_requirements
336 .get(&pid_key)
337 .ok_or_else(|| MapperError::PidNotFound {
338 fv: fv.to_string(),
339 variant: variant.to_string(),
340 pid: pid.to_string(),
341 })?;
342
343 let evaluator = crate::evaluator_factory::create_evaluator(variant, fv);
345
346 if let Some(evaluator) = evaluator {
347 let defs = vc
349 .combined_defs
350 .get(&pid_key)
351 .ok_or_else(|| MapperError::PidNotFound {
352 fv: fv.to_string(),
353 variant: variant.to_string(),
354 pid: pid.to_string(),
355 })?;
356 let engine = MappingEngine::from_definitions(defs.clone());
357 let tree = engine.map_all_reverse(json, None);
358
359 let segments = crate::tree_to_segments::tree_to_owned_segments(&tree);
361
362 Ok(crate::evaluator_factory::validate_with_boxed_evaluator(
364 evaluator.as_ref(),
365 json,
366 requirements,
367 pid,
368 &segments,
369 ))
370 } else {
371 Ok(mig_bo4e::pid_validation::validate_pid_json_transaction(
373 json,
374 requirements,
375 ))
376 }
377 }
378
379 pub fn to_edifact(
405 &self,
406 msg_stammdaten: &serde_json::Value,
407 tx_stammdaten: &[serde_json::Value],
408 fv: &str,
409 variant: &str,
410 pid: &str,
411 ) -> Result<String, MapperError> {
412 self.ensure_bundle_loaded(fv)?;
413 let bundles = self.bundles.lock().unwrap();
414 let bundle = bundles.get(fv).unwrap();
415 let vc = bundle
416 .variant(variant)
417 .ok_or_else(|| MapperError::VariantNotFound {
418 fv: fv.to_string(),
419 variant: variant.to_string(),
420 })?;
421
422 let tx_group = vc.tx_group(pid).ok_or_else(|| MapperError::PidNotFound {
423 fv: fv.to_string(),
424 variant: variant.to_string(),
425 pid: pid.to_string(),
426 })?;
427
428 let msg_engine = vc.msg_engine(pid);
429 let tx_engine = vc.tx_engine(pid).ok_or_else(|| MapperError::PidNotFound {
430 fv: fv.to_string(),
431 variant: variant.to_string(),
432 pid: pid.to_string(),
433 })?;
434
435 let filtered_mig = vc
436 .filtered_mig(pid)
437 .ok_or_else(|| MapperError::NoMigSchema {
438 fv: fv.to_string(),
439 variant: variant.to_string(),
440 })?;
441
442 let transaktionen: Vec<mig_bo4e::model::MappedTransaktion> = tx_stammdaten
444 .iter()
445 .map(|tx| mig_bo4e::model::MappedTransaktion {
446 stammdaten: tx.clone(),
447 nesting_info: Default::default(),
448 dp_routing: Default::default(),
449 })
450 .collect();
451 let mapped = mig_bo4e::model::MappedMessage {
452 stammdaten: msg_stammdaten.clone(),
453 transaktionen,
454 nesting_info: Default::default(),
455 dp_routing: Default::default(),
456 inter_group_segments: Default::default(),
457 };
458
459 let tree = MappingEngine::map_interchange_reverse(
461 &msg_engine,
462 &tx_engine,
463 &mapped,
464 tx_group,
465 Some(&filtered_mig),
466 );
467
468 let disassembler = mig_assembly::disassembler::Disassembler::new(&filtered_mig);
470 let segments = disassembler.disassemble(&tree);
471
472 let delimiters = edifact_primitives::EdifactDelimiters::default();
474 Ok(mig_assembly::renderer::render_edifact(
475 &segments,
476 &delimiters,
477 ))
478 }
479
480 pub fn to_edifact_struct(
486 &self,
487 nachricht: &impl serde::Serialize,
488 fv: &str,
489 variant: &str,
490 pid: &str,
491 ) -> Result<String, MapperError> {
492 let json = serde_json::to_value(nachricht)
493 .map_err(|e| MapperError::Serialization(e.to_string()))?;
494
495 let msg_stammdaten = json
496 .get("stammdaten")
497 .cloned()
498 .unwrap_or(serde_json::Value::Object(Default::default()));
499
500 let tx_stammdaten: Vec<serde_json::Value> = json
501 .get("transaktionen")
502 .and_then(|v| v.as_array())
503 .cloned()
504 .unwrap_or_default();
505
506 self.to_edifact(&msg_stammdaten, &tx_stammdaten, fv, variant, pid)
507 }
508
509 pub fn from_edifact<M, T>(
533 &self,
534 edifact: &str,
535 fv: &str,
536 variant: &str,
537 pid: &str,
538 ) -> Result<mig_bo4e::model::Interchange<M, T>, MapperError>
539 where
540 M: serde::de::DeserializeOwned,
541 T: serde::de::DeserializeOwned,
542 {
543 self.from_edifact_with_diagnostics(edifact, fv, variant, pid)
544 .map(|(interchange, _)| interchange)
545 }
546
547 pub fn from_edifact_with_diagnostics<M, T>(
557 &self,
558 edifact: &str,
559 fv: &str,
560 variant: &str,
561 pid: &str,
562 ) -> Result<
563 (
564 mig_bo4e::model::Interchange<M, T>,
565 Vec<mig_assembly::StructureDiagnostic>,
566 ),
567 MapperError,
568 >
569 where
570 M: serde::de::DeserializeOwned,
571 T: serde::de::DeserializeOwned,
572 {
573 self.ensure_bundle_loaded(fv)?;
574 let bundles = self.bundles.lock().unwrap();
575 let bundle = bundles.get(fv).unwrap();
576 let vc = bundle
577 .variant(variant)
578 .ok_or_else(|| MapperError::VariantNotFound {
579 fv: fv.to_string(),
580 variant: variant.to_string(),
581 })?;
582
583 let tx_group = vc.tx_group(pid).ok_or_else(|| MapperError::PidNotFound {
584 fv: fv.to_string(),
585 variant: variant.to_string(),
586 pid: pid.to_string(),
587 })?;
588
589 let msg_engine = vc.msg_engine(pid);
590 let tx_engine = vc.tx_engine(pid).ok_or_else(|| MapperError::PidNotFound {
591 fv: fv.to_string(),
592 variant: variant.to_string(),
593 pid: pid.to_string(),
594 })?;
595
596 let filtered_mig = vc
597 .filtered_mig(pid)
598 .ok_or_else(|| MapperError::NoMigSchema {
599 fv: fv.to_string(),
600 variant: variant.to_string(),
601 })?;
602
603 let svc = ConversionService::from_mig(filtered_mig);
609 let (chunks, trees, assembly_diagnostics) = svc
610 .convert_interchange_to_trees_with_diagnostics(
611 edifact,
612 mig_assembly::assembler::AssemblerConfig {
613 strict_code_matching: true,
614 skip_unknown_segments: true,
615 ..Default::default()
616 },
617 )?;
618
619 let tree = trees.first().ok_or_else(|| {
620 MapperError::Assembly(mig_assembly::AssemblyError::ParseError(
621 "No messages in interchange".to_string(),
622 ))
623 })?;
624
625 let interchangedaten = mig_bo4e::model::extract_interchangedaten(&chunks.envelope);
627 let msg_chunk = chunks.messages.first().ok_or_else(|| {
628 MapperError::Assembly(mig_assembly::AssemblyError::ParseError(
629 "No message chunks".to_string(),
630 ))
631 })?;
632 let (unh_ref, nachrichten_typ) = mig_bo4e::model::extract_unh_fields(&msg_chunk.unh);
633 let nachrichtendaten = mig_bo4e::model::Nachrichtendaten {
634 unh_referenz: unh_ref,
635 nachrichten_typ,
636 };
637
638 let interchange = MappingEngine::map_interchange_typed::<M, T>(
640 &msg_engine,
641 &tx_engine,
642 tree,
643 tx_group,
644 true,
645 nachrichtendaten,
646 interchangedaten,
647 )
648 .map_err(|e| MapperError::Serialization(e.to_string()))?;
649
650 Ok((interchange, assembly_diagnostics))
651 }
652
653 pub fn detect_pid(&self, edifact: &str) -> Result<String, MapperError> {
665 let segments = mig_assembly::tokenize::parse_to_segments(edifact.as_bytes())?;
666 let chunks = mig_assembly::split_messages(segments)?;
667 let msg_chunk = chunks.messages.first().ok_or_else(|| {
668 MapperError::Assembly(mig_assembly::AssemblyError::ParseError(
669 "No messages found in EDIFACT content".to_string(),
670 ))
671 })?;
672 let msg_segments = msg_chunk.message_segments();
673 mig_assembly::pid_detect::detect_pid(&msg_segments).map_err(MapperError::Assembly)
674 }
675
676 pub fn validate_edifact(
692 &self,
693 edifact: &str,
694 fv: &str,
695 level: automapper_validation::ValidationLevel,
696 ) -> Result<automapper_validation::ValidationReport, MapperError> {
697 self.validate_edifact_inner(edifact, fv, None, level)
698 }
699
700 pub fn validate_edifact_for_pid(
710 &self,
711 edifact: &str,
712 fv: &str,
713 variant: &str,
714 pid: &str,
715 level: automapper_validation::ValidationLevel,
716 ) -> Result<automapper_validation::ValidationReport, MapperError> {
717 self.validate_edifact_inner(edifact, fv, Some((variant, pid)), level)
718 }
719
720 fn validate_edifact_inner(
721 &self,
722 edifact: &str,
723 fv: &str,
724 known: Option<(&str, &str)>,
725 level: automapper_validation::ValidationLevel,
726 ) -> Result<automapper_validation::ValidationReport, MapperError> {
727 self.ensure_bundle_loaded(fv)?;
728 let bundles = self.bundles.lock().unwrap();
729 let bundle = bundles.get(fv).unwrap();
730
731 let segments = mig_assembly::tokenize::parse_to_segments(edifact.as_bytes())?;
733 let chunks = mig_assembly::split_messages(segments)?;
734 let msg_chunk = chunks.messages.first().ok_or_else(|| {
735 MapperError::Assembly(mig_assembly::AssemblyError::ParseError(
736 "No messages found in EDIFACT content".to_string(),
737 ))
738 })?;
739
740 let (pid, variant, vc) = match known {
746 Some((variant, pid)) => {
747 let vc = bundle
748 .variant(variant)
749 .ok_or_else(|| MapperError::VariantNotFound {
750 fv: fv.to_string(),
751 variant: variant.to_string(),
752 })?;
753 (pid.to_string(), variant.to_string(), vc)
754 }
755 None => {
756 let pid = mig_assembly::pid_detect::detect_pid(&msg_chunk.message_segments())
757 .map_err(MapperError::Assembly)?;
758 let pid_key = format!("pid_{pid}");
759 let (variant, vc) = bundle
760 .variants
761 .iter()
762 .find(|(_, vc)| vc.pid_ahb_workflows.contains_key(&pid_key))
763 .ok_or_else(|| MapperError::PidNotFound {
764 fv: fv.to_string(),
765 variant: "?".to_string(),
766 pid: pid.clone(),
767 })?;
768 (pid, variant.clone(), vc)
769 }
770 };
771 let pid_key = format!("pid_{pid}");
772
773 let workflow =
774 vc.pid_ahb_workflows
775 .get(&pid_key)
776 .ok_or_else(|| MapperError::PidNotFound {
777 fv: fv.to_string(),
778 variant: variant.clone(),
779 pid: pid.clone(),
780 })?;
781 let filtered_mig = vc
782 .filtered_mig(&pid)
783 .ok_or_else(|| MapperError::NoMigSchema {
784 fv: fv.to_string(),
785 variant: variant.clone(),
786 })?;
787
788 let mut all_segments = msg_chunk.segments_for_mig(&filtered_mig);
791 if filtered_mig.segments.iter().any(|s| s.id == "UNZ") {
792 if let Some(unz) = &chunks.unz {
793 all_segments.push(unz.clone());
794 }
795 }
796
797 let evaluator: std::sync::Arc<dyn automapper_validation::ConditionEvaluator> =
801 match crate::evaluator_factory::create_evaluator(&variant, fv) {
802 Some(boxed) => std::sync::Arc::from(boxed),
803 None => std::sync::Arc::new(
804 automapper_validation::UtilmdStromConditionEvaluatorFV2504::default(),
805 ),
806 };
807 let external = automapper_validation::eval::NoOpExternalProvider;
808
809 let mut report = automapper_validation::validate_edifact_message(
810 &all_segments,
811 &filtered_mig,
812 workflow,
813 evaluator,
814 &external,
815 level,
816 );
817
818 if let (Some(mig), Some(defs)) = (vc.mig_schema.as_ref(), vc.combined_defs.get(&pid_key)) {
824 let reverse = mig_bo4e::path_resolver::ReversePathResolver::from_mig(mig);
825 let field_index =
826 mig_bo4e::Bo4eFieldIndex::build_with_resolver(defs, &filtered_mig, &reverse);
827 report.enrich_bo4e_paths(|path, hint| field_index.resolve(path, hint));
828 }
829
830 Ok(report)
831 }
832
833 pub fn validate_bo4e(
856 &self,
857 msg_stammdaten: &serde_json::Value,
858 tx_stammdaten: &[serde_json::Value],
859 fv: &str,
860 variant: &str,
861 pid: &str,
862 envelope: Option<&InterchangeEnvelope>,
863 level: automapper_validation::ValidationLevel,
864 ) -> Result<automapper_validation::ValidationReport, MapperError> {
865 let placeholder;
866 let envelope = match envelope {
867 Some(e) => e,
868 None => {
869 placeholder = InterchangeEnvelope {
870 sender: EdifactParty::bdew("9900000000001"),
871 receiver: EdifactParty::bdew("9900000000002"),
872 interchange_ref: "1".to_string(),
873 };
874 &placeholder
875 }
876 };
877
878 let edifact = self.to_edifact_interchange(
879 envelope,
880 &[InterchangeMessage {
881 message_ref: "1".to_string(),
882 msg_stammdaten: msg_stammdaten.clone(),
883 tx_stammdaten: tx_stammdaten.to_vec(),
884 fv: fv.to_string(),
885 variant: variant.to_string(),
886 pid: pid.to_string(),
887 }],
888 )?;
889
890 self.validate_edifact_for_pid(&edifact, fv, variant, pid, level)
893 }
894
895 pub fn association_code(&self, fv: &str, variant: &str) -> Result<String, MapperError> {
906 let meta = self.message_metadata(fv, variant)?;
907 Ok(meta.association_code)
908 }
909
910 pub fn message_metadata(
915 &self,
916 fv: &str,
917 variant: &str,
918 ) -> Result<MessageMetadata, MapperError> {
919 self.ensure_bundle_loaded(fv)?;
920 let bundles = self.bundles.lock().unwrap();
921 let bundle = bundles.get(fv).unwrap();
922 let vc = bundle
923 .variant(variant)
924 .ok_or_else(|| MapperError::VariantNotFound {
925 fv: fv.to_string(),
926 variant: variant.to_string(),
927 })?;
928 let mig = vc
929 .mig_schema
930 .as_ref()
931 .ok_or_else(|| MapperError::NoMigSchema {
932 fv: fv.to_string(),
933 variant: variant.to_string(),
934 })?;
935 Ok(MessageMetadata {
936 message_type: mig.message_type.clone(),
937 release: release_code_for_message_type(&mig.message_type),
938 association_code: mig.version.clone(),
939 })
940 }
941
942 pub fn to_edifact_interchange(
966 &self,
967 envelope: &InterchangeEnvelope,
968 messages: &[InterchangeMessage],
969 ) -> Result<String, MapperError> {
970 let delimiters = edifact_primitives::EdifactDelimiters::default();
971 let sep = delimiters.component as char;
972 let elem = delimiters.element as char;
973 let seg_term = delimiters.segment as char;
974
975 let mut output = String::new();
976
977 output.push_str(&format!(
979 "UNA{}{}{}{}{}{}",
980 sep, elem, delimiters.decimal as char, delimiters.release as char, ' ', seg_term, ));
987
988 let now = chrono::Utc::now();
990 let date_str = now.format("%y%m%d").to_string();
991 let time_str = now.format("%H%M").to_string();
992 let sender = &envelope.sender;
993 let receiver = &envelope.receiver;
994 let interchange_ref = &envelope.interchange_ref;
995 output.push_str(&format!(
996 "UNB{elem}UNOC{sep}3{elem}{sid}{sep}{sq}{elem}{rid}{sep}{rq}{elem}{date_str}{sep}{time_str}{elem}{interchange_ref}{seg_term}",
997 sid = sender.id,
998 sq = sender.qualifier,
999 rid = receiver.id,
1000 rq = receiver.qualifier,
1001 ));
1002
1003 let mut message_count = 0u32;
1004
1005 for msg in messages {
1006 let meta = self.message_metadata(&msg.fv, &msg.variant)?;
1007
1008 let body = self.to_edifact(
1010 &msg.msg_stammdaten,
1011 &msg.tx_stammdaten,
1012 &msg.fv,
1013 &msg.variant,
1014 &msg.pid,
1015 )?;
1016
1017 let body_seg_count = body
1019 .split(seg_term)
1020 .filter(|s: &&str| !s.is_empty())
1021 .count();
1022 let segment_count = body_seg_count + 2;
1024
1025 output.push_str(&format!(
1027 "UNH{elem}{ref}{elem}{msg_type}{sep}D{sep}{release}{sep}UN{sep}{assoc}{seg_term}",
1028 ref = msg.message_ref,
1029 msg_type = meta.message_type,
1030 release = meta.release,
1031 assoc = meta.association_code,
1032 ));
1033
1034 output.push_str(&body);
1036
1037 output.push_str(&format!(
1039 "UNT{elem}{segment_count}{elem}{ref}{seg_term}",
1040 ref = msg.message_ref,
1041 ));
1042
1043 message_count += 1;
1044 }
1045
1046 output.push_str(&format!(
1048 "UNZ{elem}{message_count}{elem}{interchange_ref}{seg_term}",
1049 ));
1050
1051 Ok(output)
1052 }
1053
1054 pub fn loaded_format_versions(&self) -> Vec<String> {
1056 self.bundles.lock().unwrap().keys().cloned().collect()
1057 }
1058
1059 pub fn variants(&self, fv: &str) -> Result<Vec<String>, MapperError> {
1063 self.ensure_bundle_loaded(fv)?;
1064 let bundles = self.bundles.lock().unwrap();
1065 let bundle = bundles.get(fv).unwrap();
1066 Ok(bundle.variants.keys().cloned().collect())
1067 }
1068}
1069
1070#[derive(Debug, Clone)]
1072pub struct MessageMetadata {
1073 pub message_type: String,
1075 pub release: String,
1077 pub association_code: String,
1079}
1080
1081#[derive(Debug, Clone)]
1083pub struct InterchangeEnvelope {
1084 pub sender: EdifactParty,
1086 pub receiver: EdifactParty,
1088 pub interchange_ref: String,
1090}
1091
1092#[derive(Debug, Clone)]
1094pub struct EdifactParty {
1095 pub id: String,
1097 pub qualifier: String,
1099}
1100
1101impl EdifactParty {
1102 pub fn bdew(id: &str) -> Self {
1104 Self {
1105 id: id.to_string(),
1106 qualifier: "500".to_string(),
1107 }
1108 }
1109
1110 pub fn gs1(id: &str) -> Self {
1112 Self {
1113 id: id.to_string(),
1114 qualifier: "14".to_string(),
1115 }
1116 }
1117}
1118
1119#[derive(Debug, Clone)]
1122pub struct InterchangeMessage {
1123 pub message_ref: String,
1125 pub msg_stammdaten: serde_json::Value,
1127 pub tx_stammdaten: Vec<serde_json::Value>,
1129 pub fv: String,
1131 pub variant: String,
1133 pub pid: String,
1135}
1136
1137fn release_code_for_message_type(msg_type: &str) -> String {
1141 mig_bo4e::model::release_code_for_message_type(msg_type).to_string()
1142}
1143
1144#[cfg(test)]
1145mod tests {
1146 use super::*;
1147 use std::path::Path;
1148
1149 fn data_dir() -> Option<std::path::PathBuf> {
1150 let dist = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../dist");
1152 if dist.join("edifact-data-FV2504.bin").exists() {
1153 return Some(dist);
1154 }
1155 let cache = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../cache/mappings");
1156 if cache.join("FV2504").exists() {
1157 return Some(cache);
1158 }
1159 eprintln!("Skipping test: no DataBundle files found");
1160 None
1161 }
1162
1163 #[test]
1164 fn test_to_edifact_produces_edifact_output() {
1165 let Some(data_dir) = data_dir() else {
1166 return;
1167 };
1168 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1169
1170 let msg_stammdaten = serde_json::json!({
1171 "marktteilnehmer": [{
1172 "marktrolle": "MS",
1173 "rollencodenummer": "9900123456789",
1174 "codepflegeCode": "293"
1175 }]
1176 });
1177 let tx_stammdaten = serde_json::json!({
1178 "prozessdaten": {
1179 "pruefidentifikator": "55001",
1180 "vorgangId": "ABC123",
1181 "transaktionsgrund": "E01"
1182 }
1183 });
1184
1185 let result = mapper.to_edifact(
1186 &msg_stammdaten,
1187 &[tx_stammdaten],
1188 "FV2504",
1189 "UTILMD_Strom",
1190 "55001",
1191 );
1192 assert!(result.is_ok(), "to_edifact failed: {:?}", result.err());
1193 let edifact = result.unwrap();
1194 assert!(!edifact.is_empty(), "EDIFACT output should not be empty");
1195 assert!(edifact.contains("NAD"), "Should contain NAD segment");
1197 assert!(edifact.contains("IDE"), "Should contain IDE segment");
1199 }
1200
1201 #[test]
1202 fn test_to_edifact_struct_produces_edifact_output() {
1203 let Some(data_dir) = data_dir() else {
1204 return;
1205 };
1206 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1207
1208 let nachricht = serde_json::json!({
1209 "stammdaten": {
1210 "marktteilnehmer": [{
1211 "marktrolle": "MS",
1212 "rollencodenummer": "9900123456789",
1213 "codepflegeCode": "293"
1214 }]
1215 },
1216 "transaktionen": [{
1217 "prozessdaten": {
1218 "pruefidentifikator": "55001",
1219 "vorgangId": "ABC123"
1220 }
1221 }]
1222 });
1223
1224 let result = mapper.to_edifact_struct(&nachricht, "FV2504", "UTILMD_Strom", "55001");
1225 assert!(
1226 result.is_ok(),
1227 "to_edifact_struct failed: {:?}",
1228 result.err()
1229 );
1230 let edifact = result.unwrap();
1231 assert!(!edifact.is_empty(), "EDIFACT output should not be empty");
1232 }
1233
1234 #[test]
1235 fn test_to_edifact_invalid_fv_returns_error() {
1236 let Some(data_dir) = data_dir() else {
1237 return;
1238 };
1239 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1240
1241 let result = mapper.to_edifact(
1242 &serde_json::json!({}),
1243 &[serde_json::json!({})],
1244 "FV9999",
1245 "UTILMD_Strom",
1246 "55001",
1247 );
1248 assert!(result.is_err());
1249 }
1250
1251 #[test]
1252 fn test_to_edifact_invalid_variant_returns_error() {
1253 let Some(data_dir) = data_dir() else {
1254 return;
1255 };
1256 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1257
1258 let result = mapper.to_edifact(
1259 &serde_json::json!({}),
1260 &[serde_json::json!({})],
1261 "FV2504",
1262 "NONEXISTENT",
1263 "55001",
1264 );
1265 assert!(result.is_err());
1266 }
1267
1268 #[test]
1269 fn test_to_edifact_invalid_pid_returns_error() {
1270 let Some(data_dir) = data_dir() else {
1271 return;
1272 };
1273 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1274
1275 let result = mapper.to_edifact(
1276 &serde_json::json!({}),
1277 &[serde_json::json!({})],
1278 "FV2504",
1279 "UTILMD_Strom",
1280 "99999",
1281 );
1282 assert!(result.is_err());
1283 }
1284
1285 #[test]
1286 fn test_association_code() {
1287 let Some(data_dir) = data_dir() else {
1288 return;
1289 };
1290 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1291
1292 let code = mapper.association_code("FV2504", "UTILMD_Strom").unwrap();
1293 assert_eq!(code, "S2.1");
1294
1295 let code = mapper.association_code("FV2504", "MSCONS").unwrap();
1296 assert_eq!(code, "2.4c");
1297 }
1298
1299 #[test]
1300 fn test_message_metadata() {
1301 let Some(data_dir) = data_dir() else {
1302 return;
1303 };
1304 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1305
1306 let meta = mapper.message_metadata("FV2504", "UTILMD_Strom").unwrap();
1307 assert_eq!(meta.message_type, "UTILMD");
1308 assert_eq!(meta.release, "11A");
1309 assert_eq!(meta.association_code, "S2.1");
1310 }
1311
1312 #[test]
1313 fn test_to_edifact_interchange() {
1314 let Some(data_dir) = data_dir() else {
1315 return;
1316 };
1317 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1318
1319 let result = mapper.to_edifact_interchange(
1320 &InterchangeEnvelope {
1321 sender: EdifactParty::bdew("9900000000003"),
1322 receiver: EdifactParty::bdew("9900000000001"),
1323 interchange_ref: "REF001".to_string(),
1324 },
1325 &[InterchangeMessage {
1326 message_ref: "MSG001".to_string(),
1327 msg_stammdaten: serde_json::json!({
1328 "marktteilnehmer": [{
1329 "marktrolle": "MS",
1330 "rollencodenummer": "9900123456789",
1331 "codepflegeCode": "293"
1332 }]
1333 }),
1334 tx_stammdaten: vec![serde_json::json!({
1335 "prozessdaten": {
1336 "pruefidentifikator": "55001",
1337 "vorgangId": "ABC123",
1338 "transaktionsgrund": "E01"
1339 }
1340 })],
1341 fv: "FV2504".to_string(),
1342 variant: "UTILMD_Strom".to_string(),
1343 pid: "55001".to_string(),
1344 }],
1345 );
1346 assert!(
1347 result.is_ok(),
1348 "to_edifact_interchange failed: {:?}",
1349 result.err()
1350 );
1351 let edifact = result.unwrap();
1352
1353 assert!(edifact.starts_with("UNA:+.? '"), "Should start with UNA");
1355 assert!(
1356 edifact.contains("UNB+UNOC:3+9900000000003:500+9900000000001:500+"),
1357 "Should contain UNB with sender/receiver"
1358 );
1359 assert!(
1360 edifact.contains("UNH+MSG001+UTILMD:D:11A:UN:S2.1'"),
1361 "Should contain UNH with correct S009"
1362 );
1363 assert!(edifact.contains("NAD"), "Should contain body NAD segment");
1364 assert!(edifact.contains("UNT+"), "Should contain UNT");
1365 assert!(
1366 edifact.contains("+MSG001'"),
1367 "UNT should reference message ref"
1368 );
1369 assert!(
1370 edifact.contains("UNZ+1+REF001'"),
1371 "Should contain UNZ with count and ref"
1372 );
1373 }
1374
1375 #[test]
1376 fn test_detect_pid_from_rff_z13() {
1377 let Some(data_dir) = data_dir() else {
1378 return;
1379 };
1380 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1381
1382 let edifact = "\
1383 UNB+UNOC:3+9978842000002:500+9900269000000:500+250331:1329+REF001'\
1384 UNH+MSG001+UTILMD:D:11A:UN:S2.1'\
1385 BGM+E01+DOC001'\
1386 DTM+137:202503311329?+00:303'\
1387 NAD+MS+9978842000002::293'\
1388 NAD+MR+9900269000000::293'\
1389 IDE+24+TX001'\
1390 DTM+92:202505312200?+00:303'\
1391 DTM+93:202512312300?+00:303'\
1392 STS+7++E01+ZW4+E03'\
1393 LOC+Z16+12345678900'\
1394 RFF+Z13:55001'\
1395 UNT+12+MSG001'\
1396 UNZ+1+REF001'";
1397
1398 let pid = mapper.detect_pid(edifact).unwrap();
1399 assert_eq!(pid, "55001");
1400 }
1401
1402 #[test]
1403 fn test_detect_pid_no_messages_returns_error() {
1404 let Some(data_dir) = data_dir() else {
1405 return;
1406 };
1407 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1408
1409 let edifact = "UNB+UNOC:3+SENDER:500+RECEIVER:500+250401:1200+REF'\
1410 UNZ+0+REF'";
1411 assert!(mapper.detect_pid(edifact).is_err());
1412 }
1413
1414 #[test]
1415 fn test_list_pids_returns_entries() {
1416 let Some(data_dir) = data_dir() else {
1417 return;
1418 };
1419 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir)).unwrap();
1420 let pids = mapper.list_pids().expect("list_pids should succeed");
1421 assert!(!pids.is_empty(), "should return at least one PID");
1422 assert!(
1423 pids.iter().any(|p| p.pid == "55001"),
1424 "should include PID 55001"
1425 );
1426 assert!(
1427 pids.iter().any(|p| p.fv == "FV2504"),
1428 "should include FV2504"
1429 );
1430 assert!(
1431 pids.iter().any(|p| p.variant == "UTILMD_Strom"),
1432 "should include UTILMD_Strom"
1433 );
1434 }
1435
1436 #[test]
1437 fn test_pid_requirements_returns_requirements() {
1438 let Some(data_dir) = data_dir() else {
1439 return;
1440 };
1441 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1442
1443 let req = mapper
1444 .pid_requirements("FV2504", "UTILMD_Strom", "55001")
1445 .expect("pid_requirements should succeed");
1446
1447 assert_eq!(req.pid, "55001");
1448 assert!(
1449 !req.entities.is_empty(),
1450 "55001 should have at least one entity"
1451 );
1452 assert!(
1453 req.entities.iter().any(|e| e.entity == "Prozessdaten"),
1454 "55001 should have a Prozessdaten entity"
1455 );
1456 }
1457}