1use std::collections::HashMap;
4use std::sync::Mutex;
5
6use mig_assembly::ConversionService;
7use mig_bo4e::engine::DataBundle;
8use mig_bo4e::MappingEngine;
9
10use crate::data_dir::DataDir;
11use crate::error::MapperError;
12
13pub struct Bo4eResult {
15 pub pid: String,
17 pub message_type: String,
19 pub variant: String,
21 pub bo4e: serde_json::Value,
23}
24
25pub struct Mapper {
43 data_dir: DataDir,
44 bundles: Mutex<HashMap<String, DataBundle>>,
45}
46
47impl Mapper {
48 pub fn from_data_dir(data_dir: DataDir) -> Result<Self, MapperError> {
53 let mapper = Self {
54 data_dir,
55 bundles: Mutex::new(HashMap::new()),
56 };
57 let eager_fvs: Vec<String> = mapper.data_dir.eager_fvs().to_vec();
58 for fv in &eager_fvs {
59 mapper.ensure_bundle_loaded(fv)?;
60 }
61 Ok(mapper)
62 }
63
64 fn ensure_bundle_loaded(&self, fv: &str) -> Result<(), MapperError> {
66 let mut bundles = self.bundles.lock().unwrap();
67 if bundles.contains_key(fv) {
68 return Ok(());
69 }
70 let path = self.data_dir.bundle_path(fv);
71 if !path.exists() {
72 return Err(MapperError::BundleNotFound { fv: fv.to_string() });
73 }
74 let bundle = DataBundle::load(&path)?;
75 bundles.insert(fv.to_string(), bundle);
76 Ok(())
77 }
78
79 pub fn conversion_service(
83 &self,
84 fv: &str,
85 variant: &str,
86 ) -> Result<ConversionService, MapperError> {
87 self.ensure_bundle_loaded(fv)?;
88 let bundles = self.bundles.lock().unwrap();
89 let bundle = bundles.get(fv).unwrap();
90 let vc = bundle
91 .variant(variant)
92 .ok_or_else(|| MapperError::VariantNotFound {
93 fv: fv.to_string(),
94 variant: variant.to_string(),
95 })?;
96 let mig = vc
97 .mig_schema
98 .as_ref()
99 .ok_or_else(|| MapperError::VariantNotFound {
100 fv: fv.to_string(),
101 variant: format!("{variant} (no MIG schema in bundle)"),
102 })?;
103 Ok(ConversionService::from_mig(mig.clone()))
104 }
105
106 pub fn engine(&self, fv: &str, variant: &str, pid: &str) -> Result<MappingEngine, MapperError> {
110 self.ensure_bundle_loaded(fv)?;
111 let bundles = self.bundles.lock().unwrap();
112 let bundle = bundles.get(fv).unwrap();
113 let vc = bundle
114 .variant(variant)
115 .ok_or_else(|| MapperError::VariantNotFound {
116 fv: fv.to_string(),
117 variant: variant.to_string(),
118 })?;
119 let pid_key = format!("pid_{pid}");
120 let defs = vc
121 .combined_defs
122 .get(&pid_key)
123 .ok_or_else(|| MapperError::PidNotFound {
124 fv: fv.to_string(),
125 variant: variant.to_string(),
126 pid: pid.to_string(),
127 })?;
128 Ok(MappingEngine::from_definitions(defs.clone()))
129 }
130
131 pub fn validate_pid(
136 &self,
137 json: &serde_json::Value,
138 fv: &str,
139 variant: &str,
140 pid: &str,
141 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
142 self.ensure_bundle_loaded(fv)?;
143 let bundles = self.bundles.lock().unwrap();
144 let bundle = bundles.get(fv).unwrap();
145 let vc = bundle
146 .variant(variant)
147 .ok_or_else(|| MapperError::VariantNotFound {
148 fv: fv.to_string(),
149 variant: variant.to_string(),
150 })?;
151 let pid_key = format!("pid_{pid}");
152 let requirements =
153 vc.pid_requirements
154 .get(&pid_key)
155 .ok_or_else(|| MapperError::PidNotFound {
156 fv: fv.to_string(),
157 variant: variant.to_string(),
158 pid: pid.to_string(),
159 })?;
160
161 Ok(mig_bo4e::pid_validation::validate_pid_json(
162 json,
163 requirements,
164 ))
165 }
166
167 pub fn validate_pid_struct(
179 &self,
180 value: &impl serde::Serialize,
181 fv: &str,
182 variant: &str,
183 pid: &str,
184 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
185 let json = serde_json::to_value(value).map_err(|e| {
186 MapperError::Mapping(mig_bo4e::MappingError::TypeConversion(e.to_string()))
187 })?;
188 self.validate_pid(&json, fv, variant, pid)
189 }
190
191 pub fn validate_pid_with_conditions(
199 &self,
200 json: &serde_json::Value,
201 fv: &str,
202 variant: &str,
203 pid: &str,
204 ) -> Result<Vec<mig_bo4e::PidValidationError>, MapperError> {
205 self.ensure_bundle_loaded(fv)?;
206 let bundles = self.bundles.lock().unwrap();
207 let bundle = bundles.get(fv).unwrap();
208 let vc = bundle
209 .variant(variant)
210 .ok_or_else(|| MapperError::VariantNotFound {
211 fv: fv.to_string(),
212 variant: variant.to_string(),
213 })?;
214 let pid_key = format!("pid_{pid}");
215
216 let requirements =
217 vc.pid_requirements
218 .get(&pid_key)
219 .ok_or_else(|| MapperError::PidNotFound {
220 fv: fv.to_string(),
221 variant: variant.to_string(),
222 pid: pid.to_string(),
223 })?;
224
225 let evaluator = crate::evaluator_factory::create_evaluator(variant, fv);
227
228 if let Some(evaluator) = evaluator {
229 let defs = vc
231 .combined_defs
232 .get(&pid_key)
233 .ok_or_else(|| MapperError::PidNotFound {
234 fv: fv.to_string(),
235 variant: variant.to_string(),
236 pid: pid.to_string(),
237 })?;
238 let engine = MappingEngine::from_definitions(defs.clone());
239 let tree = engine.map_all_reverse(json, None);
240
241 let segments = crate::tree_to_segments::tree_to_owned_segments(&tree);
243
244 Ok(crate::evaluator_factory::validate_with_boxed_evaluator(
246 evaluator.as_ref(),
247 json,
248 requirements,
249 pid,
250 &segments,
251 ))
252 } else {
253 Ok(mig_bo4e::pid_validation::validate_pid_json(
255 json,
256 requirements,
257 ))
258 }
259 }
260
261 pub fn to_edifact(
287 &self,
288 msg_stammdaten: &serde_json::Value,
289 tx_stammdaten: &[serde_json::Value],
290 fv: &str,
291 variant: &str,
292 pid: &str,
293 ) -> Result<String, MapperError> {
294 self.ensure_bundle_loaded(fv)?;
295 let bundles = self.bundles.lock().unwrap();
296 let bundle = bundles.get(fv).unwrap();
297 let vc = bundle
298 .variant(variant)
299 .ok_or_else(|| MapperError::VariantNotFound {
300 fv: fv.to_string(),
301 variant: variant.to_string(),
302 })?;
303
304 let tx_group = vc
305 .tx_group(pid)
306 .ok_or_else(|| MapperError::PidNotFound {
307 fv: fv.to_string(),
308 variant: variant.to_string(),
309 pid: pid.to_string(),
310 })?;
311
312 let msg_engine = vc.msg_engine();
313 let tx_engine =
314 vc.tx_engine(pid)
315 .ok_or_else(|| MapperError::PidNotFound {
316 fv: fv.to_string(),
317 variant: variant.to_string(),
318 pid: pid.to_string(),
319 })?;
320
321 let filtered_mig =
322 vc.filtered_mig(pid)
323 .ok_or_else(|| MapperError::NoMigSchema {
324 fv: fv.to_string(),
325 variant: variant.to_string(),
326 })?;
327
328 let transaktionen: Vec<mig_bo4e::model::MappedTransaktion> = tx_stammdaten
330 .iter()
331 .map(|tx| mig_bo4e::model::MappedTransaktion {
332 stammdaten: tx.clone(),
333 nesting_info: Default::default(),
334 })
335 .collect();
336 let mapped = mig_bo4e::model::MappedMessage {
337 stammdaten: msg_stammdaten.clone(),
338 transaktionen,
339 nesting_info: Default::default(),
340 };
341
342 let tree = MappingEngine::map_interchange_reverse(
344 &msg_engine,
345 &tx_engine,
346 &mapped,
347 &tx_group,
348 Some(&filtered_mig),
349 );
350
351 let disassembler =
353 mig_assembly::disassembler::Disassembler::new(&filtered_mig);
354 let segments = disassembler.disassemble(&tree);
355
356 let delimiters = edifact_primitives::EdifactDelimiters::default();
358 Ok(mig_assembly::renderer::render_edifact(
359 &segments,
360 &delimiters,
361 ))
362 }
363
364 pub fn to_edifact_struct(
370 &self,
371 nachricht: &impl serde::Serialize,
372 fv: &str,
373 variant: &str,
374 pid: &str,
375 ) -> Result<String, MapperError> {
376 let json = serde_json::to_value(nachricht)
377 .map_err(|e| MapperError::Serialization(e.to_string()))?;
378
379 let msg_stammdaten = json
380 .get("stammdaten")
381 .cloned()
382 .unwrap_or(serde_json::Value::Object(Default::default()));
383
384 let tx_stammdaten: Vec<serde_json::Value> = json
385 .get("transaktionen")
386 .and_then(|v| v.as_array())
387 .cloned()
388 .unwrap_or_default();
389
390 self.to_edifact(&msg_stammdaten, &tx_stammdaten, fv, variant, pid)
391 }
392
393 pub fn loaded_format_versions(&self) -> Vec<String> {
395 self.bundles.lock().unwrap().keys().cloned().collect()
396 }
397
398 pub fn variants(&self, fv: &str) -> Result<Vec<String>, MapperError> {
402 self.ensure_bundle_loaded(fv)?;
403 let bundles = self.bundles.lock().unwrap();
404 let bundle = bundles.get(fv).unwrap();
405 Ok(bundle.variants.keys().cloned().collect())
406 }
407}
408
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413 use std::path::Path;
414
415 fn data_dir() -> Option<std::path::PathBuf> {
416 let dist = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../dist");
418 if dist.join("edifact-data-FV2504.bin").exists() {
419 return Some(dist);
420 }
421 let cache = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../cache/mappings");
422 if cache.join("FV2504").exists() {
423 return Some(cache);
424 }
425 eprintln!("Skipping test: no DataBundle files found");
426 None
427 }
428
429 #[test]
430 fn test_to_edifact_produces_edifact_output() {
431 let Some(data_dir) = data_dir() else {
432 return;
433 };
434 let mapper =
435 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
436
437 let msg_stammdaten = serde_json::json!({
438 "marktteilnehmer": [{
439 "marktrolle": "MS",
440 "rollencodenummer": "9900123456789",
441 "codepflegeCode": "293"
442 }]
443 });
444 let tx_stammdaten = serde_json::json!({
445 "prozessdaten": {
446 "pruefidentifikator": "55001",
447 "vorgangId": "ABC123",
448 "transaktionsgrund": "E01"
449 }
450 });
451
452 let result = mapper.to_edifact(
453 &msg_stammdaten,
454 &[tx_stammdaten],
455 "FV2504",
456 "UTILMD_Strom",
457 "55001",
458 );
459 assert!(result.is_ok(), "to_edifact failed: {:?}", result.err());
460 let edifact = result.unwrap();
461 assert!(!edifact.is_empty(), "EDIFACT output should not be empty");
462 assert!(edifact.contains("NAD"), "Should contain NAD segment");
464 assert!(edifact.contains("IDE"), "Should contain IDE segment");
466 }
467
468 #[test]
469 fn test_to_edifact_struct_produces_edifact_output() {
470 let Some(data_dir) = data_dir() else {
471 return;
472 };
473 let mapper =
474 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
475
476 let nachricht = serde_json::json!({
477 "stammdaten": {
478 "marktteilnehmer": [{
479 "marktrolle": "MS",
480 "rollencodenummer": "9900123456789",
481 "codepflegeCode": "293"
482 }]
483 },
484 "transaktionen": [{
485 "prozessdaten": {
486 "pruefidentifikator": "55001",
487 "vorgangId": "ABC123"
488 }
489 }]
490 });
491
492 let result = mapper.to_edifact_struct(&nachricht, "FV2504", "UTILMD_Strom", "55001");
493 assert!(
494 result.is_ok(),
495 "to_edifact_struct failed: {:?}",
496 result.err()
497 );
498 let edifact = result.unwrap();
499 assert!(!edifact.is_empty(), "EDIFACT output should not be empty");
500 }
501
502 #[test]
503 fn test_to_edifact_invalid_fv_returns_error() {
504 let Some(data_dir) = data_dir() else {
505 return;
506 };
507 let mapper =
508 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
509
510 let result = mapper.to_edifact(
511 &serde_json::json!({}),
512 &[serde_json::json!({})],
513 "FV9999",
514 "UTILMD_Strom",
515 "55001",
516 );
517 assert!(result.is_err());
518 }
519
520 #[test]
521 fn test_to_edifact_invalid_variant_returns_error() {
522 let Some(data_dir) = data_dir() else {
523 return;
524 };
525 let mapper =
526 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
527
528 let result = mapper.to_edifact(
529 &serde_json::json!({}),
530 &[serde_json::json!({})],
531 "FV2504",
532 "NONEXISTENT",
533 "55001",
534 );
535 assert!(result.is_err());
536 }
537
538 #[test]
539 fn test_to_edifact_invalid_pid_returns_error() {
540 let Some(data_dir) = data_dir() else {
541 return;
542 };
543 let mapper =
544 Mapper::from_data_dir(DataDir::path(&data_dir).eager(&["FV2504"])).unwrap();
545
546 let result = mapper.to_edifact(
547 &serde_json::json!({}),
548 &[serde_json::json!({})],
549 "FV2504",
550 "UTILMD_Strom",
551 "99999",
552 );
553 assert!(result.is_err());
554 }
555}