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 list_pids(&self) -> Result<Vec<PidListEntry>, MapperError> {
193 let dir = self.data_dir.data_path();
194 let read_dir = std::fs::read_dir(dir).map_err(|_| MapperError::DataDirNotFound {
195 path: dir.display().to_string(),
196 })?;
197
198 let mut result = Vec::new();
199
200 for entry in read_dir.flatten() {
201 let path = entry.path();
202 if path.extension().is_some_and(|e| e == "bin") {
203 let stem = path
204 .file_stem()
205 .and_then(|s| s.to_str())
206 .unwrap_or("")
207 .to_string();
208 let fv = match stem.strip_prefix("edifact-data-") {
209 Some(v) => v.to_string(),
210 None => continue,
211 };
212 self.ensure_bundle_loaded(&fv)?;
213 let bundles = self.bundles.lock().unwrap();
214 if let Some(bundle) = bundles.get(&fv) {
215 for (variant, vc) in &bundle.variants {
216 for (pid_key, req) in &vc.pid_requirements {
217 let pid = pid_key
218 .strip_prefix("pid_")
219 .unwrap_or(pid_key)
220 .to_string();
221 result.push(PidListEntry {
222 fv: fv.clone(),
223 variant: variant.clone(),
224 pid,
225 beschreibung: req.beschreibung.clone(),
226 });
227 }
228 }
229 }
230 }
231 }
232
233 result.sort_by(|a, b| a.pid.cmp(&b.pid));
234 Ok(result)
235 }
236
237 pub fn validate_pid(
242 &self,
243 json: &serde_json::Value,
244 fv: &str,
245 variant: &str,
246 pid: &str,
247 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
248 self.ensure_bundle_loaded(fv)?;
249 let bundles = self.bundles.lock().unwrap();
250 let bundle = bundles.get(fv).unwrap();
251 let vc = bundle
252 .variant(variant)
253 .ok_or_else(|| MapperError::VariantNotFound {
254 fv: fv.to_string(),
255 variant: variant.to_string(),
256 })?;
257 let pid_key = format!("pid_{pid}");
258 let requirements =
259 vc.pid_requirements
260 .get(&pid_key)
261 .ok_or_else(|| MapperError::PidNotFound {
262 fv: fv.to_string(),
263 variant: variant.to_string(),
264 pid: pid.to_string(),
265 })?;
266
267 Ok(mig_bo4e::pid_validation::validate_pid_json(
268 json,
269 requirements,
270 ))
271 }
272
273 pub fn validate_pid_struct(
285 &self,
286 value: &impl serde::Serialize,
287 fv: &str,
288 variant: &str,
289 pid: &str,
290 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
291 let json = serde_json::to_value(value).map_err(|e| {
292 MapperError::Mapping(mig_bo4e::MappingError::TypeConversion(e.to_string()))
293 })?;
294 self.validate_pid(&json, fv, variant, pid)
295 }
296
297 pub fn validate_pid_with_conditions(
305 &self,
306 json: &serde_json::Value,
307 fv: &str,
308 variant: &str,
309 pid: &str,
310 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
311 self.ensure_bundle_loaded(fv)?;
312 let bundles = self.bundles.lock().unwrap();
313 let bundle = bundles.get(fv).unwrap();
314 let vc = bundle
315 .variant(variant)
316 .ok_or_else(|| MapperError::VariantNotFound {
317 fv: fv.to_string(),
318 variant: variant.to_string(),
319 })?;
320 let pid_key = format!("pid_{pid}");
321
322 let requirements =
323 vc.pid_requirements
324 .get(&pid_key)
325 .ok_or_else(|| MapperError::PidNotFound {
326 fv: fv.to_string(),
327 variant: variant.to_string(),
328 pid: pid.to_string(),
329 })?;
330
331 let evaluator = crate::evaluator_factory::create_evaluator(variant, fv);
333
334 if let Some(evaluator) = evaluator {
335 let defs = vc
337 .combined_defs
338 .get(&pid_key)
339 .ok_or_else(|| MapperError::PidNotFound {
340 fv: fv.to_string(),
341 variant: variant.to_string(),
342 pid: pid.to_string(),
343 })?;
344 let engine = MappingEngine::from_definitions(defs.clone());
345 let tree = engine.map_all_reverse(json, None);
346
347 let segments = crate::tree_to_segments::tree_to_owned_segments(&tree);
349
350 Ok(crate::evaluator_factory::validate_with_boxed_evaluator(
352 evaluator.as_ref(),
353 json,
354 requirements,
355 pid,
356 &segments,
357 ))
358 } else {
359 Ok(mig_bo4e::pid_validation::validate_pid_json_transaction(
361 json,
362 requirements,
363 ))
364 }
365 }
366
367 pub fn to_edifact(
393 &self,
394 msg_stammdaten: &serde_json::Value,
395 tx_stammdaten: &[serde_json::Value],
396 fv: &str,
397 variant: &str,
398 pid: &str,
399 ) -> Result<String, MapperError> {
400 self.ensure_bundle_loaded(fv)?;
401 let bundles = self.bundles.lock().unwrap();
402 let bundle = bundles.get(fv).unwrap();
403 let vc = bundle
404 .variant(variant)
405 .ok_or_else(|| MapperError::VariantNotFound {
406 fv: fv.to_string(),
407 variant: variant.to_string(),
408 })?;
409
410 let tx_group = vc
411 .tx_group(pid)
412 .ok_or_else(|| MapperError::PidNotFound {
413 fv: fv.to_string(),
414 variant: variant.to_string(),
415 pid: pid.to_string(),
416 })?;
417
418 let msg_engine = vc.msg_engine(pid);
419 let tx_engine =
420 vc.tx_engine(pid)
421 .ok_or_else(|| MapperError::PidNotFound {
422 fv: fv.to_string(),
423 variant: variant.to_string(),
424 pid: pid.to_string(),
425 })?;
426
427 let filtered_mig =
428 vc.filtered_mig(pid)
429 .ok_or_else(|| MapperError::NoMigSchema {
430 fv: fv.to_string(),
431 variant: variant.to_string(),
432 })?;
433
434 let transaktionen: Vec<mig_bo4e::model::MappedTransaktion> = tx_stammdaten
436 .iter()
437 .map(|tx| mig_bo4e::model::MappedTransaktion {
438 stammdaten: tx.clone(),
439 nesting_info: Default::default(),
440 dp_routing: Default::default(),
441 })
442 .collect();
443 let mapped = mig_bo4e::model::MappedMessage {
444 stammdaten: msg_stammdaten.clone(),
445 transaktionen,
446 nesting_info: Default::default(),
447 dp_routing: Default::default(),
448 inter_group_segments: Default::default(),
449 };
450
451 let tree = MappingEngine::map_interchange_reverse(
453 &msg_engine,
454 &tx_engine,
455 &mapped,
456 tx_group,
457 Some(&filtered_mig),
458 );
459
460 let disassembler =
462 mig_assembly::disassembler::Disassembler::new(&filtered_mig);
463 let segments = disassembler.disassemble(&tree);
464
465 let delimiters = edifact_primitives::EdifactDelimiters::default();
467 Ok(mig_assembly::renderer::render_edifact(
468 &segments,
469 &delimiters,
470 ))
471 }
472
473 pub fn to_edifact_struct(
479 &self,
480 nachricht: &impl serde::Serialize,
481 fv: &str,
482 variant: &str,
483 pid: &str,
484 ) -> Result<String, MapperError> {
485 let json = serde_json::to_value(nachricht)
486 .map_err(|e| MapperError::Serialization(e.to_string()))?;
487
488 let msg_stammdaten = json
489 .get("stammdaten")
490 .cloned()
491 .unwrap_or(serde_json::Value::Object(Default::default()));
492
493 let tx_stammdaten: Vec<serde_json::Value> = json
494 .get("transaktionen")
495 .and_then(|v| v.as_array())
496 .cloned()
497 .unwrap_or_default();
498
499 self.to_edifact(&msg_stammdaten, &tx_stammdaten, fv, variant, pid)
500 }
501
502 pub fn from_edifact<M, T>(
520 &self,
521 edifact: &str,
522 fv: &str,
523 variant: &str,
524 pid: &str,
525 ) -> Result<mig_bo4e::model::Interchange<M, T>, MapperError>
526 where
527 M: serde::de::DeserializeOwned,
528 T: serde::de::DeserializeOwned,
529 {
530 self.ensure_bundle_loaded(fv)?;
531 let bundles = self.bundles.lock().unwrap();
532 let bundle = bundles.get(fv).unwrap();
533 let vc = bundle
534 .variant(variant)
535 .ok_or_else(|| MapperError::VariantNotFound {
536 fv: fv.to_string(),
537 variant: variant.to_string(),
538 })?;
539
540 let tx_group = vc
541 .tx_group(pid)
542 .ok_or_else(|| MapperError::PidNotFound {
543 fv: fv.to_string(),
544 variant: variant.to_string(),
545 pid: pid.to_string(),
546 })?;
547
548 let msg_engine = vc.msg_engine(pid);
549 let tx_engine =
550 vc.tx_engine(pid)
551 .ok_or_else(|| MapperError::PidNotFound {
552 fv: fv.to_string(),
553 variant: variant.to_string(),
554 pid: pid.to_string(),
555 })?;
556
557 let filtered_mig =
558 vc.filtered_mig(pid)
559 .ok_or_else(|| MapperError::NoMigSchema {
560 fv: fv.to_string(),
561 variant: variant.to_string(),
562 })?;
563
564 let svc = ConversionService::from_mig(filtered_mig);
566 let (chunks, trees) = svc.convert_interchange_to_trees(edifact)?;
567
568 let tree = trees
569 .first()
570 .ok_or_else(|| MapperError::Assembly(
571 mig_assembly::AssemblyError::ParseError("No messages in interchange".to_string()),
572 ))?;
573
574 let interchangedaten =
576 mig_bo4e::model::extract_interchangedaten(&chunks.envelope);
577 let msg_chunk = chunks.messages.first().ok_or_else(|| {
578 MapperError::Assembly(mig_assembly::AssemblyError::ParseError(
579 "No message chunks".to_string(),
580 ))
581 })?;
582 let (unh_ref, nachrichten_typ) =
583 mig_bo4e::model::extract_unh_fields(&msg_chunk.unh);
584 let nachrichtendaten = mig_bo4e::model::Nachrichtendaten {
585 unh_referenz: unh_ref,
586 nachrichten_typ,
587 };
588
589 MappingEngine::map_interchange_typed::<M, T>(
591 &msg_engine,
592 &tx_engine,
593 tree,
594 tx_group,
595 true,
596 nachrichtendaten,
597 interchangedaten,
598 )
599 .map_err(|e| MapperError::Serialization(e.to_string()))
600 }
601
602 pub fn detect_pid(&self, edifact: &str) -> Result<String, MapperError> {
614 let segments = mig_assembly::tokenize::parse_to_segments(edifact.as_bytes())?;
615 let chunks = mig_assembly::split_messages(segments)?;
616 let msg_chunk =
617 chunks
618 .messages
619 .first()
620 .ok_or_else(|| MapperError::Assembly(
621 mig_assembly::AssemblyError::ParseError(
622 "No messages found in EDIFACT content".to_string(),
623 ),
624 ))?;
625 let msg_segments = msg_chunk.message_segments();
626 mig_assembly::pid_detect::detect_pid(&msg_segments).map_err(MapperError::Assembly)
627 }
628
629 pub fn association_code(&self, fv: &str, variant: &str) -> Result<String, MapperError> {
640 let meta = self.message_metadata(fv, variant)?;
641 Ok(meta.association_code)
642 }
643
644 pub fn message_metadata(
649 &self,
650 fv: &str,
651 variant: &str,
652 ) -> Result<MessageMetadata, MapperError> {
653 self.ensure_bundle_loaded(fv)?;
654 let bundles = self.bundles.lock().unwrap();
655 let bundle = bundles.get(fv).unwrap();
656 let vc = bundle
657 .variant(variant)
658 .ok_or_else(|| MapperError::VariantNotFound {
659 fv: fv.to_string(),
660 variant: variant.to_string(),
661 })?;
662 let mig = vc
663 .mig_schema
664 .as_ref()
665 .ok_or_else(|| MapperError::NoMigSchema {
666 fv: fv.to_string(),
667 variant: variant.to_string(),
668 })?;
669 Ok(MessageMetadata {
670 message_type: mig.message_type.clone(),
671 release: release_code_for_message_type(&mig.message_type),
672 association_code: mig.version.clone(),
673 })
674 }
675
676 pub fn to_edifact_interchange(
700 &self,
701 envelope: &InterchangeEnvelope,
702 messages: &[InterchangeMessage],
703 ) -> Result<String, MapperError> {
704 let delimiters = edifact_primitives::EdifactDelimiters::default();
705 let sep = delimiters.component as char;
706 let elem = delimiters.element as char;
707 let seg_term = delimiters.segment as char;
708
709 let mut output = String::new();
710
711 output.push_str(&format!(
713 "UNA{}{}{}{}{}{}",
714 sep, elem, delimiters.decimal as char, delimiters.release as char, ' ', seg_term, ));
721
722 let now = chrono::Utc::now();
724 let date_str = now.format("%y%m%d").to_string();
725 let time_str = now.format("%H%M").to_string();
726 let sender = &envelope.sender;
727 let receiver = &envelope.receiver;
728 let interchange_ref = &envelope.interchange_ref;
729 output.push_str(&format!(
730 "UNB{elem}UNOC{sep}3{elem}{sid}{sep}{sq}{elem}{rid}{sep}{rq}{elem}{date_str}{sep}{time_str}{elem}{interchange_ref}{seg_term}",
731 sid = sender.id,
732 sq = sender.qualifier,
733 rid = receiver.id,
734 rq = receiver.qualifier,
735 ));
736
737 let mut message_count = 0u32;
738
739 for msg in messages {
740 let meta = self.message_metadata(&msg.fv, &msg.variant)?;
741
742 let body = self.to_edifact(
744 &msg.msg_stammdaten,
745 &msg.tx_stammdaten,
746 &msg.fv,
747 &msg.variant,
748 &msg.pid,
749 )?;
750
751 let body_seg_count = body
753 .split(seg_term)
754 .filter(|s: &&str| !s.is_empty())
755 .count();
756 let segment_count = body_seg_count + 2;
758
759 output.push_str(&format!(
761 "UNH{elem}{ref}{elem}{msg_type}{sep}D{sep}{release}{sep}UN{sep}{assoc}{seg_term}",
762 ref = msg.message_ref,
763 msg_type = meta.message_type,
764 release = meta.release,
765 assoc = meta.association_code,
766 ));
767
768 output.push_str(&body);
770
771 output.push_str(&format!(
773 "UNT{elem}{segment_count}{elem}{ref}{seg_term}",
774 ref = msg.message_ref,
775 ));
776
777 message_count += 1;
778 }
779
780 output.push_str(&format!(
782 "UNZ{elem}{message_count}{elem}{interchange_ref}{seg_term}",
783 ));
784
785 Ok(output)
786 }
787
788 pub fn loaded_format_versions(&self) -> Vec<String> {
790 self.bundles.lock().unwrap().keys().cloned().collect()
791 }
792
793 pub fn variants(&self, fv: &str) -> Result<Vec<String>, MapperError> {
797 self.ensure_bundle_loaded(fv)?;
798 let bundles = self.bundles.lock().unwrap();
799 let bundle = bundles.get(fv).unwrap();
800 Ok(bundle.variants.keys().cloned().collect())
801 }
802}
803
804#[derive(Debug, Clone)]
806pub struct MessageMetadata {
807 pub message_type: String,
809 pub release: String,
811 pub association_code: String,
813}
814
815#[derive(Debug, Clone)]
817pub struct InterchangeEnvelope {
818 pub sender: EdifactParty,
820 pub receiver: EdifactParty,
822 pub interchange_ref: String,
824}
825
826#[derive(Debug, Clone)]
828pub struct EdifactParty {
829 pub id: String,
831 pub qualifier: String,
833}
834
835impl EdifactParty {
836 pub fn bdew(id: &str) -> Self {
838 Self {
839 id: id.to_string(),
840 qualifier: "500".to_string(),
841 }
842 }
843
844 pub fn gs1(id: &str) -> Self {
846 Self {
847 id: id.to_string(),
848 qualifier: "14".to_string(),
849 }
850 }
851}
852
853#[derive(Debug, Clone)]
856pub struct InterchangeMessage {
857 pub message_ref: String,
859 pub msg_stammdaten: serde_json::Value,
861 pub tx_stammdaten: Vec<serde_json::Value>,
863 pub fv: String,
865 pub variant: String,
867 pub pid: String,
869}
870
871fn release_code_for_message_type(msg_type: &str) -> String {
875 match msg_type {
876 "APERAK" => "07B",
877 "COMDIS" => "17A",
878 "CONTRL" => "04B",
879 "IFTSTA" => "18A",
880 "INSRPT" => "18A",
881 "INVOIC" => "06A",
882 "MSCONS" => "04B",
883 "ORDCHG" => "09B",
884 "ORDERS" => "09B",
885 "ORDRSP" => "10A",
886 "PARTIN" => "20B",
887 "PRICAT" => "20B",
888 "QUOTES" => "10A",
889 "REMADV" => "05A",
890 "REQOTE" => "10A",
891 "UTILMD" => "11A",
892 "UTILTS" => "18A",
893 _ => "04B", }
895 .to_string()
896}
897
898
899#[cfg(test)]
900mod tests {
901 use super::*;
902 use std::path::Path;
903
904 fn data_dir() -> Option<std::path::PathBuf> {
905 let dist = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../dist");
907 if dist.join("edifact-data-FV2504.bin").exists() {
908 return Some(dist);
909 }
910 let cache = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../cache/mappings");
911 if cache.join("FV2504").exists() {
912 return Some(cache);
913 }
914 eprintln!("Skipping test: no DataBundle files found");
915 None
916 }
917
918 #[test]
919 fn test_to_edifact_produces_edifact_output() {
920 let Some(data_dir) = data_dir() else {
921 return;
922 };
923 let mapper =
924 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
925
926 let msg_stammdaten = serde_json::json!({
927 "marktteilnehmer": [{
928 "marktrolle": "MS",
929 "rollencodenummer": "9900123456789",
930 "codepflegeCode": "293"
931 }]
932 });
933 let tx_stammdaten = serde_json::json!({
934 "prozessdaten": {
935 "pruefidentifikator": "55001",
936 "vorgangId": "ABC123",
937 "transaktionsgrund": "E01"
938 }
939 });
940
941 let result = mapper.to_edifact(
942 &msg_stammdaten,
943 &[tx_stammdaten],
944 "FV2504",
945 "UTILMD_Strom",
946 "55001",
947 );
948 assert!(result.is_ok(), "to_edifact failed: {:?}", result.err());
949 let edifact = result.unwrap();
950 assert!(!edifact.is_empty(), "EDIFACT output should not be empty");
951 assert!(edifact.contains("NAD"), "Should contain NAD segment");
953 assert!(edifact.contains("IDE"), "Should contain IDE segment");
955 }
956
957 #[test]
958 fn test_to_edifact_struct_produces_edifact_output() {
959 let Some(data_dir) = data_dir() else {
960 return;
961 };
962 let mapper =
963 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
964
965 let nachricht = serde_json::json!({
966 "stammdaten": {
967 "marktteilnehmer": [{
968 "marktrolle": "MS",
969 "rollencodenummer": "9900123456789",
970 "codepflegeCode": "293"
971 }]
972 },
973 "transaktionen": [{
974 "prozessdaten": {
975 "pruefidentifikator": "55001",
976 "vorgangId": "ABC123"
977 }
978 }]
979 });
980
981 let result = mapper.to_edifact_struct(&nachricht, "FV2504", "UTILMD_Strom", "55001");
982 assert!(
983 result.is_ok(),
984 "to_edifact_struct failed: {:?}",
985 result.err()
986 );
987 let edifact = result.unwrap();
988 assert!(!edifact.is_empty(), "EDIFACT output should not be empty");
989 }
990
991 #[test]
992 fn test_to_edifact_invalid_fv_returns_error() {
993 let Some(data_dir) = data_dir() else {
994 return;
995 };
996 let mapper =
997 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
998
999 let result = mapper.to_edifact(
1000 &serde_json::json!({}),
1001 &[serde_json::json!({})],
1002 "FV9999",
1003 "UTILMD_Strom",
1004 "55001",
1005 );
1006 assert!(result.is_err());
1007 }
1008
1009 #[test]
1010 fn test_to_edifact_invalid_variant_returns_error() {
1011 let Some(data_dir) = data_dir() else {
1012 return;
1013 };
1014 let mapper =
1015 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1016
1017 let result = mapper.to_edifact(
1018 &serde_json::json!({}),
1019 &[serde_json::json!({})],
1020 "FV2504",
1021 "NONEXISTENT",
1022 "55001",
1023 );
1024 assert!(result.is_err());
1025 }
1026
1027 #[test]
1028 fn test_to_edifact_invalid_pid_returns_error() {
1029 let Some(data_dir) = data_dir() else {
1030 return;
1031 };
1032 let mapper =
1033 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1034
1035 let result = mapper.to_edifact(
1036 &serde_json::json!({}),
1037 &[serde_json::json!({})],
1038 "FV2504",
1039 "UTILMD_Strom",
1040 "99999",
1041 );
1042 assert!(result.is_err());
1043 }
1044
1045 #[test]
1046 fn test_association_code() {
1047 let Some(data_dir) = data_dir() else {
1048 return;
1049 };
1050 let mapper =
1051 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1052
1053 let code = mapper.association_code("FV2504", "UTILMD_Strom").unwrap();
1054 assert_eq!(code, "S2.1");
1055
1056 let code = mapper.association_code("FV2504", "MSCONS").unwrap();
1057 assert_eq!(code, "2.4c");
1058 }
1059
1060 #[test]
1061 fn test_message_metadata() {
1062 let Some(data_dir) = data_dir() else {
1063 return;
1064 };
1065 let mapper =
1066 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1067
1068 let meta = mapper.message_metadata("FV2504", "UTILMD_Strom").unwrap();
1069 assert_eq!(meta.message_type, "UTILMD");
1070 assert_eq!(meta.release, "11A");
1071 assert_eq!(meta.association_code, "S2.1");
1072 }
1073
1074 #[test]
1075 fn test_to_edifact_interchange() {
1076 let Some(data_dir) = data_dir() else {
1077 return;
1078 };
1079 let mapper =
1080 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1081
1082 let result = mapper.to_edifact_interchange(
1083 &InterchangeEnvelope {
1084 sender: EdifactParty::bdew("9900000000003"),
1085 receiver: EdifactParty::bdew("9900000000001"),
1086 interchange_ref: "REF001".to_string(),
1087 },
1088 &[InterchangeMessage {
1089 message_ref: "MSG001".to_string(),
1090 msg_stammdaten: serde_json::json!({
1091 "marktteilnehmer": [{
1092 "marktrolle": "MS",
1093 "rollencodenummer": "9900123456789",
1094 "codepflegeCode": "293"
1095 }]
1096 }),
1097 tx_stammdaten: vec![serde_json::json!({
1098 "prozessdaten": {
1099 "pruefidentifikator": "55001",
1100 "vorgangId": "ABC123",
1101 "transaktionsgrund": "E01"
1102 }
1103 })],
1104 fv: "FV2504".to_string(),
1105 variant: "UTILMD_Strom".to_string(),
1106 pid: "55001".to_string(),
1107 }],
1108 );
1109 assert!(
1110 result.is_ok(),
1111 "to_edifact_interchange failed: {:?}",
1112 result.err()
1113 );
1114 let edifact = result.unwrap();
1115
1116 assert!(edifact.starts_with("UNA:+.? '"), "Should start with UNA");
1118 assert!(edifact.contains("UNB+UNOC:3+9900000000003:500+9900000000001:500+"),
1119 "Should contain UNB with sender/receiver");
1120 assert!(edifact.contains("UNH+MSG001+UTILMD:D:11A:UN:S2.1'"),
1121 "Should contain UNH with correct S009");
1122 assert!(edifact.contains("NAD"), "Should contain body NAD segment");
1123 assert!(edifact.contains("UNT+"), "Should contain UNT");
1124 assert!(edifact.contains("+MSG001'"), "UNT should reference message ref");
1125 assert!(edifact.contains("UNZ+1+REF001'"), "Should contain UNZ with count and ref");
1126 }
1127
1128 #[test]
1129 fn test_detect_pid_from_rff_z13() {
1130 let Some(data_dir) = data_dir() else {
1131 return;
1132 };
1133 let mapper =
1134 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1135
1136 let edifact = "\
1137 UNB+UNOC:3+9978842000002:500+9900269000000:500+250331:1329+REF001'\
1138 UNH+MSG001+UTILMD:D:11A:UN:S2.1'\
1139 BGM+E01+DOC001'\
1140 DTM+137:202503311329?+00:303'\
1141 NAD+MS+9978842000002::293'\
1142 NAD+MR+9900269000000::293'\
1143 IDE+24+TX001'\
1144 DTM+92:202505312200?+00:303'\
1145 DTM+93:202512312300?+00:303'\
1146 STS+7++E01+ZW4+E03'\
1147 LOC+Z16+12345678900'\
1148 RFF+Z13:55001'\
1149 UNT+12+MSG001'\
1150 UNZ+1+REF001'";
1151
1152 let pid = mapper.detect_pid(edifact).unwrap();
1153 assert_eq!(pid, "55001");
1154 }
1155
1156 #[test]
1157 fn test_detect_pid_no_messages_returns_error() {
1158 let Some(data_dir) = data_dir() else {
1159 return;
1160 };
1161 let mapper =
1162 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1163
1164 let edifact = "UNB+UNOC:3+SENDER:500+RECEIVER:500+250401:1200+REF'\
1165 UNZ+0+REF'";
1166 assert!(mapper.detect_pid(edifact).is_err());
1167 }
1168
1169 #[test]
1170 fn test_list_pids_returns_entries() {
1171 let Some(data_dir) = data_dir() else {
1172 return;
1173 };
1174 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir)).unwrap();
1175 let pids = mapper.list_pids().expect("list_pids should succeed");
1176 assert!(!pids.is_empty(), "should return at least one PID");
1177 assert!(
1178 pids.iter().any(|p| p.pid == "55001"),
1179 "should include PID 55001"
1180 );
1181 assert!(
1182 pids.iter().any(|p| p.fv == "FV2504"),
1183 "should include FV2504"
1184 );
1185 assert!(
1186 pids.iter().any(|p| p.variant == "UTILMD_Strom"),
1187 "should include UTILMD_Strom"
1188 );
1189 }
1190
1191 #[test]
1192 fn test_pid_requirements_returns_requirements() {
1193 let Some(data_dir) = data_dir() else {
1194 return;
1195 };
1196 let mapper =
1197 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1198
1199 let req = mapper
1200 .pid_requirements("FV2504", "UTILMD_Strom", "55001")
1201 .expect("pid_requirements should succeed");
1202
1203 assert_eq!(req.pid, "55001");
1204 assert!(
1205 !req.entities.is_empty(),
1206 "55001 should have at least one entity"
1207 );
1208 assert!(
1209 req.entities.iter().any(|e| e.entity == "Prozessdaten"),
1210 "55001 should have a Prozessdaten entity"
1211 );
1212 }
1213}