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 let mut report = automapper_validation::validate_edifact_message(
708 &all_segments,
709 &filtered_mig,
710 workflow,
711 evaluator,
712 &external,
713 level,
714 );
715
716 if let (Some(mig), Some(defs)) = (vc.mig_schema.as_ref(), vc.combined_defs.get(&pid_key)) {
722 let reverse = mig_bo4e::path_resolver::ReversePathResolver::from_mig(mig);
723 let field_index =
724 mig_bo4e::Bo4eFieldIndex::build_with_resolver(defs, &filtered_mig, &reverse);
725 report.enrich_bo4e_paths(|path, hint| field_index.resolve(path, hint));
726 }
727
728 Ok(report)
729 }
730
731 pub fn association_code(&self, fv: &str, variant: &str) -> Result<String, MapperError> {
742 let meta = self.message_metadata(fv, variant)?;
743 Ok(meta.association_code)
744 }
745
746 pub fn message_metadata(
751 &self,
752 fv: &str,
753 variant: &str,
754 ) -> Result<MessageMetadata, MapperError> {
755 self.ensure_bundle_loaded(fv)?;
756 let bundles = self.bundles.lock().unwrap();
757 let bundle = bundles.get(fv).unwrap();
758 let vc = bundle
759 .variant(variant)
760 .ok_or_else(|| MapperError::VariantNotFound {
761 fv: fv.to_string(),
762 variant: variant.to_string(),
763 })?;
764 let mig = vc
765 .mig_schema
766 .as_ref()
767 .ok_or_else(|| MapperError::NoMigSchema {
768 fv: fv.to_string(),
769 variant: variant.to_string(),
770 })?;
771 Ok(MessageMetadata {
772 message_type: mig.message_type.clone(),
773 release: release_code_for_message_type(&mig.message_type),
774 association_code: mig.version.clone(),
775 })
776 }
777
778 pub fn to_edifact_interchange(
802 &self,
803 envelope: &InterchangeEnvelope,
804 messages: &[InterchangeMessage],
805 ) -> Result<String, MapperError> {
806 let delimiters = edifact_primitives::EdifactDelimiters::default();
807 let sep = delimiters.component as char;
808 let elem = delimiters.element as char;
809 let seg_term = delimiters.segment as char;
810
811 let mut output = String::new();
812
813 output.push_str(&format!(
815 "UNA{}{}{}{}{}{}",
816 sep, elem, delimiters.decimal as char, delimiters.release as char, ' ', seg_term, ));
823
824 let now = chrono::Utc::now();
826 let date_str = now.format("%y%m%d").to_string();
827 let time_str = now.format("%H%M").to_string();
828 let sender = &envelope.sender;
829 let receiver = &envelope.receiver;
830 let interchange_ref = &envelope.interchange_ref;
831 output.push_str(&format!(
832 "UNB{elem}UNOC{sep}3{elem}{sid}{sep}{sq}{elem}{rid}{sep}{rq}{elem}{date_str}{sep}{time_str}{elem}{interchange_ref}{seg_term}",
833 sid = sender.id,
834 sq = sender.qualifier,
835 rid = receiver.id,
836 rq = receiver.qualifier,
837 ));
838
839 let mut message_count = 0u32;
840
841 for msg in messages {
842 let meta = self.message_metadata(&msg.fv, &msg.variant)?;
843
844 let body = self.to_edifact(
846 &msg.msg_stammdaten,
847 &msg.tx_stammdaten,
848 &msg.fv,
849 &msg.variant,
850 &msg.pid,
851 )?;
852
853 let body_seg_count = body
855 .split(seg_term)
856 .filter(|s: &&str| !s.is_empty())
857 .count();
858 let segment_count = body_seg_count + 2;
860
861 output.push_str(&format!(
863 "UNH{elem}{ref}{elem}{msg_type}{sep}D{sep}{release}{sep}UN{sep}{assoc}{seg_term}",
864 ref = msg.message_ref,
865 msg_type = meta.message_type,
866 release = meta.release,
867 assoc = meta.association_code,
868 ));
869
870 output.push_str(&body);
872
873 output.push_str(&format!(
875 "UNT{elem}{segment_count}{elem}{ref}{seg_term}",
876 ref = msg.message_ref,
877 ));
878
879 message_count += 1;
880 }
881
882 output.push_str(&format!(
884 "UNZ{elem}{message_count}{elem}{interchange_ref}{seg_term}",
885 ));
886
887 Ok(output)
888 }
889
890 pub fn loaded_format_versions(&self) -> Vec<String> {
892 self.bundles.lock().unwrap().keys().cloned().collect()
893 }
894
895 pub fn variants(&self, fv: &str) -> Result<Vec<String>, MapperError> {
899 self.ensure_bundle_loaded(fv)?;
900 let bundles = self.bundles.lock().unwrap();
901 let bundle = bundles.get(fv).unwrap();
902 Ok(bundle.variants.keys().cloned().collect())
903 }
904}
905
906#[derive(Debug, Clone)]
908pub struct MessageMetadata {
909 pub message_type: String,
911 pub release: String,
913 pub association_code: String,
915}
916
917#[derive(Debug, Clone)]
919pub struct InterchangeEnvelope {
920 pub sender: EdifactParty,
922 pub receiver: EdifactParty,
924 pub interchange_ref: String,
926}
927
928#[derive(Debug, Clone)]
930pub struct EdifactParty {
931 pub id: String,
933 pub qualifier: String,
935}
936
937impl EdifactParty {
938 pub fn bdew(id: &str) -> Self {
940 Self {
941 id: id.to_string(),
942 qualifier: "500".to_string(),
943 }
944 }
945
946 pub fn gs1(id: &str) -> Self {
948 Self {
949 id: id.to_string(),
950 qualifier: "14".to_string(),
951 }
952 }
953}
954
955#[derive(Debug, Clone)]
958pub struct InterchangeMessage {
959 pub message_ref: String,
961 pub msg_stammdaten: serde_json::Value,
963 pub tx_stammdaten: Vec<serde_json::Value>,
965 pub fv: String,
967 pub variant: String,
969 pub pid: String,
971}
972
973fn release_code_for_message_type(msg_type: &str) -> String {
977 match msg_type {
978 "APERAK" => "07B",
979 "COMDIS" => "17A",
980 "CONTRL" => "04B",
981 "IFTSTA" => "18A",
982 "INSRPT" => "18A",
983 "INVOIC" => "06A",
984 "MSCONS" => "04B",
985 "ORDCHG" => "09B",
986 "ORDERS" => "09B",
987 "ORDRSP" => "10A",
988 "PARTIN" => "20B",
989 "PRICAT" => "20B",
990 "QUOTES" => "10A",
991 "REMADV" => "05A",
992 "REQOTE" => "10A",
993 "UTILMD" => "11A",
994 "UTILTS" => "18A",
995 _ => "04B", }
997 .to_string()
998}
999
1000#[cfg(test)]
1001mod tests {
1002 use super::*;
1003 use std::path::Path;
1004
1005 fn data_dir() -> Option<std::path::PathBuf> {
1006 let dist = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../dist");
1008 if dist.join("edifact-data-FV2504.bin").exists() {
1009 return Some(dist);
1010 }
1011 let cache = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../cache/mappings");
1012 if cache.join("FV2504").exists() {
1013 return Some(cache);
1014 }
1015 eprintln!("Skipping test: no DataBundle files found");
1016 None
1017 }
1018
1019 #[test]
1020 fn test_to_edifact_produces_edifact_output() {
1021 let Some(data_dir) = data_dir() else {
1022 return;
1023 };
1024 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1025
1026 let msg_stammdaten = serde_json::json!({
1027 "marktteilnehmer": [{
1028 "marktrolle": "MS",
1029 "rollencodenummer": "9900123456789",
1030 "codepflegeCode": "293"
1031 }]
1032 });
1033 let tx_stammdaten = serde_json::json!({
1034 "prozessdaten": {
1035 "pruefidentifikator": "55001",
1036 "vorgangId": "ABC123",
1037 "transaktionsgrund": "E01"
1038 }
1039 });
1040
1041 let result = mapper.to_edifact(
1042 &msg_stammdaten,
1043 &[tx_stammdaten],
1044 "FV2504",
1045 "UTILMD_Strom",
1046 "55001",
1047 );
1048 assert!(result.is_ok(), "to_edifact failed: {:?}", result.err());
1049 let edifact = result.unwrap();
1050 assert!(!edifact.is_empty(), "EDIFACT output should not be empty");
1051 assert!(edifact.contains("NAD"), "Should contain NAD segment");
1053 assert!(edifact.contains("IDE"), "Should contain IDE segment");
1055 }
1056
1057 #[test]
1058 fn test_to_edifact_struct_produces_edifact_output() {
1059 let Some(data_dir) = data_dir() else {
1060 return;
1061 };
1062 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1063
1064 let nachricht = serde_json::json!({
1065 "stammdaten": {
1066 "marktteilnehmer": [{
1067 "marktrolle": "MS",
1068 "rollencodenummer": "9900123456789",
1069 "codepflegeCode": "293"
1070 }]
1071 },
1072 "transaktionen": [{
1073 "prozessdaten": {
1074 "pruefidentifikator": "55001",
1075 "vorgangId": "ABC123"
1076 }
1077 }]
1078 });
1079
1080 let result = mapper.to_edifact_struct(&nachricht, "FV2504", "UTILMD_Strom", "55001");
1081 assert!(
1082 result.is_ok(),
1083 "to_edifact_struct failed: {:?}",
1084 result.err()
1085 );
1086 let edifact = result.unwrap();
1087 assert!(!edifact.is_empty(), "EDIFACT output should not be empty");
1088 }
1089
1090 #[test]
1091 fn test_to_edifact_invalid_fv_returns_error() {
1092 let Some(data_dir) = data_dir() else {
1093 return;
1094 };
1095 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1096
1097 let result = mapper.to_edifact(
1098 &serde_json::json!({}),
1099 &[serde_json::json!({})],
1100 "FV9999",
1101 "UTILMD_Strom",
1102 "55001",
1103 );
1104 assert!(result.is_err());
1105 }
1106
1107 #[test]
1108 fn test_to_edifact_invalid_variant_returns_error() {
1109 let Some(data_dir) = data_dir() else {
1110 return;
1111 };
1112 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1113
1114 let result = mapper.to_edifact(
1115 &serde_json::json!({}),
1116 &[serde_json::json!({})],
1117 "FV2504",
1118 "NONEXISTENT",
1119 "55001",
1120 );
1121 assert!(result.is_err());
1122 }
1123
1124 #[test]
1125 fn test_to_edifact_invalid_pid_returns_error() {
1126 let Some(data_dir) = data_dir() else {
1127 return;
1128 };
1129 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1130
1131 let result = mapper.to_edifact(
1132 &serde_json::json!({}),
1133 &[serde_json::json!({})],
1134 "FV2504",
1135 "UTILMD_Strom",
1136 "99999",
1137 );
1138 assert!(result.is_err());
1139 }
1140
1141 #[test]
1142 fn test_association_code() {
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 code = mapper.association_code("FV2504", "UTILMD_Strom").unwrap();
1149 assert_eq!(code, "S2.1");
1150
1151 let code = mapper.association_code("FV2504", "MSCONS").unwrap();
1152 assert_eq!(code, "2.4c");
1153 }
1154
1155 #[test]
1156 fn test_message_metadata() {
1157 let Some(data_dir) = data_dir() else {
1158 return;
1159 };
1160 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1161
1162 let meta = mapper.message_metadata("FV2504", "UTILMD_Strom").unwrap();
1163 assert_eq!(meta.message_type, "UTILMD");
1164 assert_eq!(meta.release, "11A");
1165 assert_eq!(meta.association_code, "S2.1");
1166 }
1167
1168 #[test]
1169 fn test_to_edifact_interchange() {
1170 let Some(data_dir) = data_dir() else {
1171 return;
1172 };
1173 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1174
1175 let result = mapper.to_edifact_interchange(
1176 &InterchangeEnvelope {
1177 sender: EdifactParty::bdew("9900000000003"),
1178 receiver: EdifactParty::bdew("9900000000001"),
1179 interchange_ref: "REF001".to_string(),
1180 },
1181 &[InterchangeMessage {
1182 message_ref: "MSG001".to_string(),
1183 msg_stammdaten: serde_json::json!({
1184 "marktteilnehmer": [{
1185 "marktrolle": "MS",
1186 "rollencodenummer": "9900123456789",
1187 "codepflegeCode": "293"
1188 }]
1189 }),
1190 tx_stammdaten: vec![serde_json::json!({
1191 "prozessdaten": {
1192 "pruefidentifikator": "55001",
1193 "vorgangId": "ABC123",
1194 "transaktionsgrund": "E01"
1195 }
1196 })],
1197 fv: "FV2504".to_string(),
1198 variant: "UTILMD_Strom".to_string(),
1199 pid: "55001".to_string(),
1200 }],
1201 );
1202 assert!(
1203 result.is_ok(),
1204 "to_edifact_interchange failed: {:?}",
1205 result.err()
1206 );
1207 let edifact = result.unwrap();
1208
1209 assert!(edifact.starts_with("UNA:+.? '"), "Should start with UNA");
1211 assert!(
1212 edifact.contains("UNB+UNOC:3+9900000000003:500+9900000000001:500+"),
1213 "Should contain UNB with sender/receiver"
1214 );
1215 assert!(
1216 edifact.contains("UNH+MSG001+UTILMD:D:11A:UN:S2.1'"),
1217 "Should contain UNH with correct S009"
1218 );
1219 assert!(edifact.contains("NAD"), "Should contain body NAD segment");
1220 assert!(edifact.contains("UNT+"), "Should contain UNT");
1221 assert!(
1222 edifact.contains("+MSG001'"),
1223 "UNT should reference message ref"
1224 );
1225 assert!(
1226 edifact.contains("UNZ+1+REF001'"),
1227 "Should contain UNZ with count and ref"
1228 );
1229 }
1230
1231 #[test]
1232 fn test_detect_pid_from_rff_z13() {
1233 let Some(data_dir) = data_dir() else {
1234 return;
1235 };
1236 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1237
1238 let edifact = "\
1239 UNB+UNOC:3+9978842000002:500+9900269000000:500+250331:1329+REF001'\
1240 UNH+MSG001+UTILMD:D:11A:UN:S2.1'\
1241 BGM+E01+DOC001'\
1242 DTM+137:202503311329?+00:303'\
1243 NAD+MS+9978842000002::293'\
1244 NAD+MR+9900269000000::293'\
1245 IDE+24+TX001'\
1246 DTM+92:202505312200?+00:303'\
1247 DTM+93:202512312300?+00:303'\
1248 STS+7++E01+ZW4+E03'\
1249 LOC+Z16+12345678900'\
1250 RFF+Z13:55001'\
1251 UNT+12+MSG001'\
1252 UNZ+1+REF001'";
1253
1254 let pid = mapper.detect_pid(edifact).unwrap();
1255 assert_eq!(pid, "55001");
1256 }
1257
1258 #[test]
1259 fn test_detect_pid_no_messages_returns_error() {
1260 let Some(data_dir) = data_dir() else {
1261 return;
1262 };
1263 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1264
1265 let edifact = "UNB+UNOC:3+SENDER:500+RECEIVER:500+250401:1200+REF'\
1266 UNZ+0+REF'";
1267 assert!(mapper.detect_pid(edifact).is_err());
1268 }
1269
1270 #[test]
1271 fn test_list_pids_returns_entries() {
1272 let Some(data_dir) = data_dir() else {
1273 return;
1274 };
1275 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir)).unwrap();
1276 let pids = mapper.list_pids().expect("list_pids should succeed");
1277 assert!(!pids.is_empty(), "should return at least one PID");
1278 assert!(
1279 pids.iter().any(|p| p.pid == "55001"),
1280 "should include PID 55001"
1281 );
1282 assert!(
1283 pids.iter().any(|p| p.fv == "FV2504"),
1284 "should include FV2504"
1285 );
1286 assert!(
1287 pids.iter().any(|p| p.variant == "UTILMD_Strom"),
1288 "should include UTILMD_Strom"
1289 );
1290 }
1291
1292 #[test]
1293 fn test_pid_requirements_returns_requirements() {
1294 let Some(data_dir) = data_dir() else {
1295 return;
1296 };
1297 let mapper = Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
1298
1299 let req = mapper
1300 .pid_requirements("FV2504", "UTILMD_Strom", "55001")
1301 .expect("pid_requirements should succeed");
1302
1303 assert_eq!(req.pid, "55001");
1304 assert!(
1305 !req.entities.is_empty(),
1306 "55001 should have at least one entity"
1307 );
1308 assert!(
1309 req.entities.iter().any(|e| e.entity == "Prozessdaten"),
1310 "55001 should have a Prozessdaten entity"
1311 );
1312 }
1313}