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>(
527 &self,
528 edifact: &str,
529 fv: &str,
530 variant: &str,
531 pid: &str,
532 ) -> Result<mig_bo4e::model::Interchange<M, T>, MapperError>
533 where
534 M: serde::de::DeserializeOwned,
535 T: serde::de::DeserializeOwned,
536 {
537 self.ensure_bundle_loaded(fv)?;
538 let bundles = self.bundles.lock().unwrap();
539 let bundle = bundles.get(fv).unwrap();
540 let vc = bundle
541 .variant(variant)
542 .ok_or_else(|| MapperError::VariantNotFound {
543 fv: fv.to_string(),
544 variant: variant.to_string(),
545 })?;
546
547 let tx_group = vc.tx_group(pid).ok_or_else(|| MapperError::PidNotFound {
548 fv: fv.to_string(),
549 variant: variant.to_string(),
550 pid: pid.to_string(),
551 })?;
552
553 let msg_engine = vc.msg_engine(pid);
554 let tx_engine = vc.tx_engine(pid).ok_or_else(|| MapperError::PidNotFound {
555 fv: fv.to_string(),
556 variant: variant.to_string(),
557 pid: pid.to_string(),
558 })?;
559
560 let filtered_mig = vc
561 .filtered_mig(pid)
562 .ok_or_else(|| MapperError::NoMigSchema {
563 fv: fv.to_string(),
564 variant: variant.to_string(),
565 })?;
566
567 let svc = ConversionService::from_mig(filtered_mig);
569 let (chunks, trees) = svc.convert_interchange_to_trees(edifact)?;
570
571 let tree = trees.first().ok_or_else(|| {
572 MapperError::Assembly(mig_assembly::AssemblyError::ParseError(
573 "No messages in interchange".to_string(),
574 ))
575 })?;
576
577 let interchangedaten = mig_bo4e::model::extract_interchangedaten(&chunks.envelope);
579 let msg_chunk = chunks.messages.first().ok_or_else(|| {
580 MapperError::Assembly(mig_assembly::AssemblyError::ParseError(
581 "No message chunks".to_string(),
582 ))
583 })?;
584 let (unh_ref, nachrichten_typ) = mig_bo4e::model::extract_unh_fields(&msg_chunk.unh);
585 let nachrichtendaten = mig_bo4e::model::Nachrichtendaten {
586 unh_referenz: unh_ref,
587 nachrichten_typ,
588 };
589
590 MappingEngine::map_interchange_typed::<M, T>(
592 &msg_engine,
593 &tx_engine,
594 tree,
595 tx_group,
596 true,
597 nachrichtendaten,
598 interchangedaten,
599 )
600 .map_err(|e| MapperError::Serialization(e.to_string()))
601 }
602
603 pub fn detect_pid(&self, edifact: &str) -> Result<String, MapperError> {
615 let segments = mig_assembly::tokenize::parse_to_segments(edifact.as_bytes())?;
616 let chunks = mig_assembly::split_messages(segments)?;
617 let msg_chunk = chunks.messages.first().ok_or_else(|| {
618 MapperError::Assembly(mig_assembly::AssemblyError::ParseError(
619 "No messages found in EDIFACT content".to_string(),
620 ))
621 })?;
622 let msg_segments = msg_chunk.message_segments();
623 mig_assembly::pid_detect::detect_pid(&msg_segments).map_err(MapperError::Assembly)
624 }
625
626 pub fn validate_edifact(
642 &self,
643 edifact: &str,
644 fv: &str,
645 level: automapper_validation::ValidationLevel,
646 ) -> Result<automapper_validation::ValidationReport, MapperError> {
647 self.ensure_bundle_loaded(fv)?;
648 let bundles = self.bundles.lock().unwrap();
649 let bundle = bundles.get(fv).unwrap();
650
651 let segments = mig_assembly::tokenize::parse_to_segments(edifact.as_bytes())?;
653 let chunks = mig_assembly::split_messages(segments)?;
654 let msg_chunk = chunks.messages.first().ok_or_else(|| {
655 MapperError::Assembly(mig_assembly::AssemblyError::ParseError(
656 "No messages found in EDIFACT content".to_string(),
657 ))
658 })?;
659
660 let pid = mig_assembly::pid_detect::detect_pid(&msg_chunk.message_segments())
661 .map_err(MapperError::Assembly)?;
662
663 let pid_key = format!("pid_{pid}");
665 let (variant, vc) = bundle
666 .variants
667 .iter()
668 .find(|(_, vc)| vc.pid_ahb_workflows.contains_key(&pid_key))
669 .ok_or_else(|| MapperError::PidNotFound {
670 fv: fv.to_string(),
671 variant: "?".to_string(),
672 pid: pid.clone(),
673 })?;
674
675 let workflow = vc
676 .pid_ahb_workflows
677 .get(&pid_key)
678 .expect("presence checked by find() above");
679 let filtered_mig = vc
680 .filtered_mig(&pid)
681 .ok_or_else(|| MapperError::NoMigSchema {
682 fv: fv.to_string(),
683 variant: variant.clone(),
684 })?;
685
686 let mut all_segments = msg_chunk.segments_for_mig(&filtered_mig);
689 if filtered_mig.segments.iter().any(|s| s.id == "UNZ") {
690 if let Some(unz) = &chunks.unz {
691 all_segments.push(unz.clone());
692 }
693 }
694
695 let evaluator: std::sync::Arc<dyn automapper_validation::ConditionEvaluator> =
699 match crate::evaluator_factory::create_evaluator(variant, fv) {
700 Some(boxed) => std::sync::Arc::from(boxed),
701 None => std::sync::Arc::new(
702 automapper_validation::UtilmdStromConditionEvaluatorFV2504::default(),
703 ),
704 };
705 let external = automapper_validation::eval::NoOpExternalProvider;
706
707 Ok(automapper_validation::validate_edifact_message(
708 &all_segments,
709 &filtered_mig,
710 workflow,
711 evaluator,
712 &external,
713 level,
714 ))
715 }
716
717 pub fn association_code(&self, fv: &str, variant: &str) -> Result<String, MapperError> {
728 let meta = self.message_metadata(fv, variant)?;
729 Ok(meta.association_code)
730 }
731
732 pub fn message_metadata(
737 &self,
738 fv: &str,
739 variant: &str,
740 ) -> Result<MessageMetadata, MapperError> {
741 self.ensure_bundle_loaded(fv)?;
742 let bundles = self.bundles.lock().unwrap();
743 let bundle = bundles.get(fv).unwrap();
744 let vc = bundle
745 .variant(variant)
746 .ok_or_else(|| MapperError::VariantNotFound {
747 fv: fv.to_string(),
748 variant: variant.to_string(),
749 })?;
750 let mig = vc
751 .mig_schema
752 .as_ref()
753 .ok_or_else(|| MapperError::NoMigSchema {
754 fv: fv.to_string(),
755 variant: variant.to_string(),
756 })?;
757 Ok(MessageMetadata {
758 message_type: mig.message_type.clone(),
759 release: release_code_for_message_type(&mig.message_type),
760 association_code: mig.version.clone(),
761 })
762 }
763
764 pub fn to_edifact_interchange(
788 &self,
789 envelope: &InterchangeEnvelope,
790 messages: &[InterchangeMessage],
791 ) -> Result<String, MapperError> {
792 let delimiters = edifact_primitives::EdifactDelimiters::default();
793 let sep = delimiters.component as char;
794 let elem = delimiters.element as char;
795 let seg_term = delimiters.segment as char;
796
797 let mut output = String::new();
798
799 output.push_str(&format!(
801 "UNA{}{}{}{}{}{}",
802 sep, elem, delimiters.decimal as char, delimiters.release as char, ' ', seg_term, ));
809
810 let now = chrono::Utc::now();
812 let date_str = now.format("%y%m%d").to_string();
813 let time_str = now.format("%H%M").to_string();
814 let sender = &envelope.sender;
815 let receiver = &envelope.receiver;
816 let interchange_ref = &envelope.interchange_ref;
817 output.push_str(&format!(
818 "UNB{elem}UNOC{sep}3{elem}{sid}{sep}{sq}{elem}{rid}{sep}{rq}{elem}{date_str}{sep}{time_str}{elem}{interchange_ref}{seg_term}",
819 sid = sender.id,
820 sq = sender.qualifier,
821 rid = receiver.id,
822 rq = receiver.qualifier,
823 ));
824
825 let mut message_count = 0u32;
826
827 for msg in messages {
828 let meta = self.message_metadata(&msg.fv, &msg.variant)?;
829
830 let body = self.to_edifact(
832 &msg.msg_stammdaten,
833 &msg.tx_stammdaten,
834 &msg.fv,
835 &msg.variant,
836 &msg.pid,
837 )?;
838
839 let body_seg_count = body
841 .split(seg_term)
842 .filter(|s: &&str| !s.is_empty())
843 .count();
844 let segment_count = body_seg_count + 2;
846
847 output.push_str(&format!(
849 "UNH{elem}{ref}{elem}{msg_type}{sep}D{sep}{release}{sep}UN{sep}{assoc}{seg_term}",
850 ref = msg.message_ref,
851 msg_type = meta.message_type,
852 release = meta.release,
853 assoc = meta.association_code,
854 ));
855
856 output.push_str(&body);
858
859 output.push_str(&format!(
861 "UNT{elem}{segment_count}{elem}{ref}{seg_term}",
862 ref = msg.message_ref,
863 ));
864
865 message_count += 1;
866 }
867
868 output.push_str(&format!(
870 "UNZ{elem}{message_count}{elem}{interchange_ref}{seg_term}",
871 ));
872
873 Ok(output)
874 }
875
876 pub fn loaded_format_versions(&self) -> Vec<String> {
878 self.bundles.lock().unwrap().keys().cloned().collect()
879 }
880
881 pub fn variants(&self, fv: &str) -> Result<Vec<String>, MapperError> {
885 self.ensure_bundle_loaded(fv)?;
886 let bundles = self.bundles.lock().unwrap();
887 let bundle = bundles.get(fv).unwrap();
888 Ok(bundle.variants.keys().cloned().collect())
889 }
890}
891
892#[derive(Debug, Clone)]
894pub struct MessageMetadata {
895 pub message_type: String,
897 pub release: String,
899 pub association_code: String,
901}
902
903#[derive(Debug, Clone)]
905pub struct InterchangeEnvelope {
906 pub sender: EdifactParty,
908 pub receiver: EdifactParty,
910 pub interchange_ref: String,
912}
913
914#[derive(Debug, Clone)]
916pub struct EdifactParty {
917 pub id: String,
919 pub qualifier: String,
921}
922
923impl EdifactParty {
924 pub fn bdew(id: &str) -> Self {
926 Self {
927 id: id.to_string(),
928 qualifier: "500".to_string(),
929 }
930 }
931
932 pub fn gs1(id: &str) -> Self {
934 Self {
935 id: id.to_string(),
936 qualifier: "14".to_string(),
937 }
938 }
939}
940
941#[derive(Debug, Clone)]
944pub struct InterchangeMessage {
945 pub message_ref: String,
947 pub msg_stammdaten: serde_json::Value,
949 pub tx_stammdaten: Vec<serde_json::Value>,
951 pub fv: String,
953 pub variant: String,
955 pub pid: String,
957}
958
959fn release_code_for_message_type(msg_type: &str) -> String {
963 match msg_type {
964 "APERAK" => "07B",
965 "COMDIS" => "17A",
966 "CONTRL" => "04B",
967 "IFTSTA" => "18A",
968 "INSRPT" => "18A",
969 "INVOIC" => "06A",
970 "MSCONS" => "04B",
971 "ORDCHG" => "09B",
972 "ORDERS" => "09B",
973 "ORDRSP" => "10A",
974 "PARTIN" => "20B",
975 "PRICAT" => "20B",
976 "QUOTES" => "10A",
977 "REMADV" => "05A",
978 "REQOTE" => "10A",
979 "UTILMD" => "11A",
980 "UTILTS" => "18A",
981 _ => "04B", }
983 .to_string()
984}
985
986#[cfg(test)]
987mod tests {
988 use super::*;
989 use std::path::Path;
990
991 fn data_dir() -> Option<std::path::PathBuf> {
992 let dist = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../dist");
994 if dist.join("edifact-data-FV2504.bin").exists() {
995 return Some(dist);
996 }
997 let cache = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../cache/mappings");
998 if cache.join("FV2504").exists() {
999 return Some(cache);
1000 }
1001 eprintln!("Skipping test: no DataBundle files found");
1002 None
1003 }
1004
1005 #[test]
1006 fn test_to_edifact_produces_edifact_output() {
1007 let Some(data_dir) = data_dir() else {
1008 return;
1009 };
1010 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1011
1012 let msg_stammdaten = serde_json::json!({
1013 "marktteilnehmer": [{
1014 "marktrolle": "MS",
1015 "rollencodenummer": "9900123456789",
1016 "codepflegeCode": "293"
1017 }]
1018 });
1019 let tx_stammdaten = serde_json::json!({
1020 "prozessdaten": {
1021 "pruefidentifikator": "55001",
1022 "vorgangId": "ABC123",
1023 "transaktionsgrund": "E01"
1024 }
1025 });
1026
1027 let result = mapper.to_edifact(
1028 &msg_stammdaten,
1029 &[tx_stammdaten],
1030 "FV2504",
1031 "UTILMD_Strom",
1032 "55001",
1033 );
1034 assert!(result.is_ok(), "to_edifact failed: {:?}", result.err());
1035 let edifact = result.unwrap();
1036 assert!(!edifact.is_empty(), "EDIFACT output should not be empty");
1037 assert!(edifact.contains("NAD"), "Should contain NAD segment");
1039 assert!(edifact.contains("IDE"), "Should contain IDE segment");
1041 }
1042
1043 #[test]
1044 fn test_to_edifact_struct_produces_edifact_output() {
1045 let Some(data_dir) = data_dir() else {
1046 return;
1047 };
1048 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1049
1050 let nachricht = serde_json::json!({
1051 "stammdaten": {
1052 "marktteilnehmer": [{
1053 "marktrolle": "MS",
1054 "rollencodenummer": "9900123456789",
1055 "codepflegeCode": "293"
1056 }]
1057 },
1058 "transaktionen": [{
1059 "prozessdaten": {
1060 "pruefidentifikator": "55001",
1061 "vorgangId": "ABC123"
1062 }
1063 }]
1064 });
1065
1066 let result = mapper.to_edifact_struct(&nachricht, "FV2504", "UTILMD_Strom", "55001");
1067 assert!(
1068 result.is_ok(),
1069 "to_edifact_struct failed: {:?}",
1070 result.err()
1071 );
1072 let edifact = result.unwrap();
1073 assert!(!edifact.is_empty(), "EDIFACT output should not be empty");
1074 }
1075
1076 #[test]
1077 fn test_to_edifact_invalid_fv_returns_error() {
1078 let Some(data_dir) = data_dir() else {
1079 return;
1080 };
1081 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1082
1083 let result = mapper.to_edifact(
1084 &serde_json::json!({}),
1085 &[serde_json::json!({})],
1086 "FV9999",
1087 "UTILMD_Strom",
1088 "55001",
1089 );
1090 assert!(result.is_err());
1091 }
1092
1093 #[test]
1094 fn test_to_edifact_invalid_variant_returns_error() {
1095 let Some(data_dir) = data_dir() else {
1096 return;
1097 };
1098 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1099
1100 let result = mapper.to_edifact(
1101 &serde_json::json!({}),
1102 &[serde_json::json!({})],
1103 "FV2504",
1104 "NONEXISTENT",
1105 "55001",
1106 );
1107 assert!(result.is_err());
1108 }
1109
1110 #[test]
1111 fn test_to_edifact_invalid_pid_returns_error() {
1112 let Some(data_dir) = data_dir() else {
1113 return;
1114 };
1115 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1116
1117 let result = mapper.to_edifact(
1118 &serde_json::json!({}),
1119 &[serde_json::json!({})],
1120 "FV2504",
1121 "UTILMD_Strom",
1122 "99999",
1123 );
1124 assert!(result.is_err());
1125 }
1126
1127 #[test]
1128 fn test_association_code() {
1129 let Some(data_dir) = data_dir() else {
1130 return;
1131 };
1132 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1133
1134 let code = mapper.association_code("FV2504", "UTILMD_Strom").unwrap();
1135 assert_eq!(code, "S2.1");
1136
1137 let code = mapper.association_code("FV2504", "MSCONS").unwrap();
1138 assert_eq!(code, "2.4c");
1139 }
1140
1141 #[test]
1142 fn test_message_metadata() {
1143 let Some(data_dir) = data_dir() else {
1144 return;
1145 };
1146 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1147
1148 let meta = mapper.message_metadata("FV2504", "UTILMD_Strom").unwrap();
1149 assert_eq!(meta.message_type, "UTILMD");
1150 assert_eq!(meta.release, "11A");
1151 assert_eq!(meta.association_code, "S2.1");
1152 }
1153
1154 #[test]
1155 fn test_to_edifact_interchange() {
1156 let Some(data_dir) = data_dir() else {
1157 return;
1158 };
1159 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1160
1161 let result = mapper.to_edifact_interchange(
1162 &InterchangeEnvelope {
1163 sender: EdifactParty::bdew("9900000000003"),
1164 receiver: EdifactParty::bdew("9900000000001"),
1165 interchange_ref: "REF001".to_string(),
1166 },
1167 &[InterchangeMessage {
1168 message_ref: "MSG001".to_string(),
1169 msg_stammdaten: serde_json::json!({
1170 "marktteilnehmer": [{
1171 "marktrolle": "MS",
1172 "rollencodenummer": "9900123456789",
1173 "codepflegeCode": "293"
1174 }]
1175 }),
1176 tx_stammdaten: vec![serde_json::json!({
1177 "prozessdaten": {
1178 "pruefidentifikator": "55001",
1179 "vorgangId": "ABC123",
1180 "transaktionsgrund": "E01"
1181 }
1182 })],
1183 fv: "FV2504".to_string(),
1184 variant: "UTILMD_Strom".to_string(),
1185 pid: "55001".to_string(),
1186 }],
1187 );
1188 assert!(
1189 result.is_ok(),
1190 "to_edifact_interchange failed: {:?}",
1191 result.err()
1192 );
1193 let edifact = result.unwrap();
1194
1195 assert!(edifact.starts_with("UNA:+.? '"), "Should start with UNA");
1197 assert!(
1198 edifact.contains("UNB+UNOC:3+9900000000003:500+9900000000001:500+"),
1199 "Should contain UNB with sender/receiver"
1200 );
1201 assert!(
1202 edifact.contains("UNH+MSG001+UTILMD:D:11A:UN:S2.1'"),
1203 "Should contain UNH with correct S009"
1204 );
1205 assert!(edifact.contains("NAD"), "Should contain body NAD segment");
1206 assert!(edifact.contains("UNT+"), "Should contain UNT");
1207 assert!(
1208 edifact.contains("+MSG001'"),
1209 "UNT should reference message ref"
1210 );
1211 assert!(
1212 edifact.contains("UNZ+1+REF001'"),
1213 "Should contain UNZ with count and ref"
1214 );
1215 }
1216
1217 #[test]
1218 fn test_detect_pid_from_rff_z13() {
1219 let Some(data_dir) = data_dir() else {
1220 return;
1221 };
1222 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1223
1224 let edifact = "\
1225 UNB+UNOC:3+9978842000002:500+9900269000000:500+250331:1329+REF001'\
1226 UNH+MSG001+UTILMD:D:11A:UN:S2.1'\
1227 BGM+E01+DOC001'\
1228 DTM+137:202503311329?+00:303'\
1229 NAD+MS+9978842000002::293'\
1230 NAD+MR+9900269000000::293'\
1231 IDE+24+TX001'\
1232 DTM+92:202505312200?+00:303'\
1233 DTM+93:202512312300?+00:303'\
1234 STS+7++E01+ZW4+E03'\
1235 LOC+Z16+12345678900'\
1236 RFF+Z13:55001'\
1237 UNT+12+MSG001'\
1238 UNZ+1+REF001'";
1239
1240 let pid = mapper.detect_pid(edifact).unwrap();
1241 assert_eq!(pid, "55001");
1242 }
1243
1244 #[test]
1245 fn test_detect_pid_no_messages_returns_error() {
1246 let Some(data_dir) = data_dir() else {
1247 return;
1248 };
1249 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1250
1251 let edifact = "UNB+UNOC:3+SENDER:500+RECEIVER:500+250401:1200+REF'\
1252 UNZ+0+REF'";
1253 assert!(mapper.detect_pid(edifact).is_err());
1254 }
1255
1256 #[test]
1257 fn test_list_pids_returns_entries() {
1258 let Some(data_dir) = data_dir() else {
1259 return;
1260 };
1261 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir)).unwrap();
1262 let pids = mapper.list_pids().expect("list_pids should succeed");
1263 assert!(!pids.is_empty(), "should return at least one PID");
1264 assert!(
1265 pids.iter().any(|p| p.pid == "55001"),
1266 "should include PID 55001"
1267 );
1268 assert!(
1269 pids.iter().any(|p| p.fv == "FV2504"),
1270 "should include FV2504"
1271 );
1272 assert!(
1273 pids.iter().any(|p| p.variant == "UTILMD_Strom"),
1274 "should include UTILMD_Strom"
1275 );
1276 }
1277
1278 #[test]
1279 fn test_pid_requirements_returns_requirements() {
1280 let Some(data_dir) = data_dir() else {
1281 return;
1282 };
1283 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1284
1285 let req = mapper
1286 .pid_requirements("FV2504", "UTILMD_Strom", "55001")
1287 .expect("pid_requirements should succeed");
1288
1289 assert_eq!(req.pid, "55001");
1290 assert!(
1291 !req.entities.is_empty(),
1292 "55001 should have at least one entity"
1293 );
1294 assert!(
1295 req.entities.iter().any(|e| e.entity == "Prozessdaten"),
1296 "55001 should have a Prozessdaten entity"
1297 );
1298 }
1299}