1use crate::error::ValidationError;
21use crate::scenario_config::{
22 ScenarioConfig, find_scenario_by_name_with_config, find_scenario_for_message_type_with_config,
23};
24use crate::xml::to_mx_xml;
25use datafake_rs::DataGenerator;
26use serde_json::{Value, json};
27
28type Result<T> = std::result::Result<T, ValidationError>;
29
30pub fn generate_sample_xml(
57 message_type: &str,
58 scenario_name: Option<&str>,
59 config: &ScenarioConfig,
60) -> Result<String> {
61 let scenario_json = if let Some(name) = scenario_name {
63 find_scenario_by_name_with_config(message_type, name, config)?
64 } else {
65 find_scenario_for_message_type_with_config(message_type, config)?
66 };
67
68 let envelope_data = generate_envelope_from_scenario(&scenario_json)?;
70
71 envelope_to_xml(envelope_data, message_type)
73}
74
75pub fn generate_sample_object<T>(
100 message_type: &str,
101 scenario_name: Option<&str>,
102 config: &ScenarioConfig,
103) -> Result<T>
104where
105 T: serde::de::DeserializeOwned,
106{
107 let scenario_json = if let Some(name) = scenario_name {
109 find_scenario_by_name_with_config(message_type, name, config)?
110 } else {
111 find_scenario_for_message_type_with_config(message_type, config)?
112 };
113
114 let envelope_data = generate_envelope_from_scenario(&scenario_json)?;
116
117 let document_data = extract_document_from_envelope(&envelope_data, message_type)?;
119
120 let generated_json = serde_json::to_string_pretty(&document_data).map_err(|e| {
122 ValidationError::new(9997, format!("Failed to serialize generated data: {e}"))
123 })?;
124
125 serde_json::from_str(&generated_json).map_err(|e| {
127 ValidationError::new(
128 9997,
129 format!("Failed to parse generated JSON into {message_type}: {e}"),
130 )
131 })
132}
133
134fn generate_envelope_from_scenario(scenario: &Value) -> Result<Value> {
140 let datafake_config = convert_to_datafake_config(scenario)?;
142
143 let generator = DataGenerator::from_value(datafake_config)
145 .map_err(|e| ValidationError::new(9997, format!("Failed to create generator: {e}")))?;
146
147 generator
149 .generate()
150 .map_err(|e| ValidationError::new(9997, format!("Failed to generate data: {e}")))
151}
152
153fn convert_to_datafake_config(scenario: &Value) -> Result<Value> {
155 let variables = scenario.get("variables").ok_or_else(|| {
157 ValidationError::new(9997, "Scenario missing 'variables' section".to_string())
158 })?;
159
160 let schema = scenario.get("schema").ok_or_else(|| {
161 ValidationError::new(9997, "Scenario missing 'schema' section".to_string())
162 })?;
163
164 let converted_variables = convert_variables(variables)?;
167
168 let converted_schema = convert_schema(schema)?;
170
171 Ok(json!({
172 "variables": converted_variables,
173 "schema": converted_schema
174 }))
175}
176
177fn convert_variables(variables: &Value) -> Result<Value> {
179 match variables {
180 Value::Object(vars) => {
181 let mut converted = serde_json::Map::new();
182
183 for (key, spec) in vars {
184 let converted_spec = convert_variable_spec(spec)?;
185 converted.insert(key.clone(), converted_spec);
186 }
187
188 Ok(Value::Object(converted))
189 }
190 _ => Ok(variables.clone()),
191 }
192}
193
194fn convert_variable_spec(spec: &Value) -> Result<Value> {
196 match spec {
197 Value::Object(obj) => {
198 if obj.contains_key("cat") {
200 return Ok(spec.clone());
201 }
202
203 if let Some(fake_spec) = obj.get("fake") {
205 if let Value::Array(fake_arr) = fake_spec {
206 if let Some(Value::String(fake_type)) = fake_arr.first() {
208 let mapped_spec = map_fake_type(fake_type, &fake_arr[1..])?;
209 return Ok(mapped_spec);
210 }
211 }
212 return Ok(spec.clone());
213 }
214
215 if let Some(Value::Array(options)) = obj.get("pick") {
217 let mut fake_array = vec![json!("enum")];
219 for option in options {
220 fake_array.push(option.clone());
221 }
222 return Ok(json!({"fake": fake_array}));
223 }
224
225 Ok(spec.clone())
226 }
227 Value::String(_) | Value::Number(_) | Value::Bool(_) | Value::Null => {
228 Ok(spec.clone())
230 }
231 _ => Ok(spec.clone()),
232 }
233}
234
235fn map_fake_type(fake_type: &str, args: &[Value]) -> Result<Value> {
237 match fake_type {
239 "iso8601_datetime" => {
241 let now = chrono::Utc::now();
243 Ok(json!(now.to_rfc3339()))
244 }
245 "currency_code" => {
247 Ok(json!({"fake": ["enum", "EUR", "USD", "GBP", "CHF", "JPY", "CAD", "AUD"]}))
249 }
250 "iban" => {
252 let country = args.first().and_then(|v| v.as_str()).unwrap_or("DE");
253 Ok(json!({"fake": ["iban", country]}))
254 }
255 "bic" => Ok(json!({"fake": ["bic"]})),
257 "lei" => Ok(json!({"fake": ["lei"]})),
259 "alphanumeric" => {
261 let min_len = args.first().and_then(|v| v.as_u64()).unwrap_or(10);
262 let max_len = args.get(1).and_then(|v| v.as_u64()).unwrap_or(min_len);
263 Ok(json!({"fake": ["alphanumeric", min_len, max_len]}))
264 }
265 "words" => {
267 let min = args.first().and_then(|v| v.as_u64()).unwrap_or(1);
268 let max = args.get(1).and_then(|v| v.as_u64()).unwrap_or(3);
269 Ok(json!({"fake": ["words", min, max]}))
270 }
271 "regex" => {
273 let mut fake_array = vec![json!("regex")];
274 for arg in args {
275 fake_array.push(arg.clone());
276 }
277 Ok(json!({"fake": fake_array}))
278 }
279 _ => {
281 let mut fake_array = vec![json!(fake_type)];
282 for arg in args {
283 fake_array.push(arg.clone());
284 }
285 Ok(json!({"fake": fake_array}))
286 }
287 }
288}
289
290fn convert_schema(schema: &Value) -> Result<Value> {
292 process_schema_value(schema)
293}
294
295fn process_schema_value(value: &Value) -> Result<Value> {
297 match value {
298 Value::Object(obj) => {
299 let mut result = serde_json::Map::new();
300
301 for (key, val) in obj {
302 if key == "@Ccy" {
304 result.insert(key.clone(), process_schema_value(val)?);
306 } else if key == "$value" {
307 result.insert(key.clone(), process_schema_value(val)?);
309 } else if key == "var" {
310 return Ok(value.clone());
312 } else if key == "fake" || key == "cat" || key == "pick" {
313 return convert_variable_spec(value);
315 } else {
316 result.insert(key.clone(), process_schema_value(val)?);
318 }
319 }
320
321 Ok(Value::Object(result))
322 }
323 Value::Array(arr) => {
324 let mut result = Vec::new();
325 for item in arr {
326 result.push(process_schema_value(item)?);
327 }
328 Ok(Value::Array(result))
329 }
330 _ => Ok(value.clone()),
331 }
332}
333
334fn extract_document_from_envelope(envelope: &Value, message_type: &str) -> Result<Value> {
336 let document = envelope.get("Document").ok_or_else(|| {
337 ValidationError::new(9997, "Envelope missing 'Document' section".to_string())
338 })?;
339
340 let doc_root = get_document_root_element(message_type);
342
343 document
344 .get(&doc_root)
345 .cloned()
346 .ok_or_else(|| ValidationError::new(9997, format!("Document missing '{doc_root}' element")))
347}
348
349fn get_document_root_element(message_type: &str) -> String {
351 match message_type {
352 "pacs008" => "FIToFICstmrCdtTrf",
353 "pacs009" => "FinInstnCdtTrf",
354 "pacs003" => "FIToFICstmrDrctDbt",
355 "pacs004" => "PmtRtr",
356 "pacs002" => "FIToFIPmtStsRpt",
357 "pain001" => "CstmrCdtTrfInitn",
358 "pain008" => "CstmrDrctDbtInitn",
359 "camt025" => "Rcpt",
360 "camt029" => "RsltnOfInvstgtn",
361 "camt052" => "BkToCstmrAcctRpt",
362 "camt053" => "BkToCstmrStmt",
363 "camt054" => "BkToCstmrDbtCdtNtfctn",
364 "camt056" => "FIToFIPmtCxlReq",
365 "camt057" => "NtfctnToRcv",
366 "camt060" => "AcctRptgReq",
367 _ => "Document", }
369 .to_string()
370}
371
372fn envelope_to_xml(envelope: Value, message_type: &str) -> Result<String> {
374 match message_type {
376 "pacs008" => {
377 use crate::document::pacs_008_001_08::FIToFICustomerCreditTransferV08;
378 use crate::header::bah_pacs_008_001_08::BusinessApplicationHeaderV02;
379
380 let header: BusinessApplicationHeaderV02 =
381 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
382 ValidationError::new(9997, format!("Failed to parse pacs008 header: {e}"))
383 })?;
384
385 let message: FIToFICustomerCreditTransferV08 = serde_json::from_value(
386 envelope["Document"]["FIToFICstmrCdtTrf"].clone(),
387 )
388 .map_err(|e| {
389 ValidationError::new(9997, format!("Failed to parse pacs008 document: {e}"))
390 })?;
391
392 to_mx_xml(&message, header, "pacs.008", None)
393 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
394 }
395 "pacs009" => {
396 use crate::document::pacs_009_001_08::FinancialInstitutionCreditTransferV08;
397 use crate::header::bah_pacs_009_001_08::BusinessApplicationHeaderV02;
398
399 let header: BusinessApplicationHeaderV02 =
400 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
401 ValidationError::new(9997, format!("Failed to parse pacs009 header: {e}"))
402 })?;
403
404 let message: FinancialInstitutionCreditTransferV08 = serde_json::from_value(
405 envelope["Document"]["FinInstnCdtTrf"].clone(),
406 )
407 .map_err(|e| {
408 ValidationError::new(9997, format!("Failed to parse pacs009 document: {e}"))
409 })?;
410
411 to_mx_xml(&message, header, "pacs.009", None)
412 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
413 }
414 "pacs003" => {
415 use crate::document::pacs_003_001_08::FIToFICustomerDirectDebitV08;
416 use crate::header::bah_pacs_003_001_08::BusinessApplicationHeaderV02;
417
418 let header: BusinessApplicationHeaderV02 =
419 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
420 ValidationError::new(9997, format!("Failed to parse pacs003 header: {e}"))
421 })?;
422
423 let message: FIToFICustomerDirectDebitV08 =
424 serde_json::from_value(envelope["Document"]["FIToFICstmrDrctDbt"].clone())
425 .map_err(|e| {
426 ValidationError::new(9997, format!("Failed to parse pacs003 document: {e}"))
427 })?;
428
429 to_mx_xml(&message, header, "pacs.003", None)
430 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
431 }
432 "pacs004" => {
433 use crate::document::pacs_004_001_09::PaymentReturnV09;
434 use crate::header::bah_pacs_004_001_09::BusinessApplicationHeaderV02;
435
436 let header: BusinessApplicationHeaderV02 =
437 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
438 ValidationError::new(9997, format!("Failed to parse pacs004 header: {e}"))
439 })?;
440
441 let message: PaymentReturnV09 =
442 serde_json::from_value(envelope["Document"]["PmtRtr"].clone()).map_err(|e| {
443 ValidationError::new(9997, format!("Failed to parse pacs004 document: {e}"))
444 })?;
445
446 to_mx_xml(&message, header, "pacs.004", None)
447 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
448 }
449 "pacs002" => {
450 use crate::document::pacs_002_001_10::FIToFIPaymentStatusReportV10;
451 use crate::header::bah_pacs_002_001_10::BusinessApplicationHeaderV02;
452
453 let header: BusinessApplicationHeaderV02 =
454 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
455 ValidationError::new(9997, format!("Failed to parse pacs002 header: {e}"))
456 })?;
457
458 let message: FIToFIPaymentStatusReportV10 = serde_json::from_value(
459 envelope["Document"]["FIToFIPmtStsRpt"].clone(),
460 )
461 .map_err(|e| {
462 ValidationError::new(9997, format!("Failed to parse pacs002 document: {e}"))
463 })?;
464
465 to_mx_xml(&message, header, "pacs.002", None)
466 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
467 }
468 "pain001" => {
469 use crate::document::pain_001_001_09::CustomerCreditTransferInitiationV09;
470 use crate::header::bah_pain_001_001_09::BusinessApplicationHeaderV02;
471
472 let header: BusinessApplicationHeaderV02 =
473 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
474 ValidationError::new(9997, format!("Failed to parse pain001 header: {e}"))
475 })?;
476
477 let message: CustomerCreditTransferInitiationV09 = serde_json::from_value(
478 envelope["Document"]["CstmrCdtTrfInitn"].clone(),
479 )
480 .map_err(|e| {
481 ValidationError::new(9997, format!("Failed to parse pain001 document: {e}"))
482 })?;
483
484 to_mx_xml(&message, header, "pain.001", None)
485 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
486 }
487 "pain008" => {
488 use crate::document::pain_008_001_08::CustomerDirectDebitInitiationV08;
489 use crate::header::bah_pain_008_001_08::BusinessApplicationHeaderV02;
490
491 let header: BusinessApplicationHeaderV02 =
492 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
493 ValidationError::new(9997, format!("Failed to parse pain008 header: {e}"))
494 })?;
495
496 let message: CustomerDirectDebitInitiationV08 = serde_json::from_value(
497 envelope["Document"]["CstmrDrctDbtInitn"].clone(),
498 )
499 .map_err(|e| {
500 ValidationError::new(9997, format!("Failed to parse pain008 document: {e}"))
501 })?;
502
503 to_mx_xml(&message, header, "pain.008", None)
504 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
505 }
506 "camt025" => {
507 use crate::document::camt_025_001_08::ReceiptV08;
508 use crate::header::bah_camt_025_001_08::BusinessApplicationHeaderV02;
509
510 let header: BusinessApplicationHeaderV02 =
511 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
512 ValidationError::new(9997, format!("Failed to parse camt025 header: {e}"))
513 })?;
514
515 let message: ReceiptV08 = serde_json::from_value(envelope["Document"]["Rcpt"].clone())
516 .map_err(|e| {
517 ValidationError::new(9997, format!("Failed to parse camt025 document: {e}"))
518 })?;
519
520 to_mx_xml(&message, header, "camt.025", None)
521 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
522 }
523 "camt029" => {
524 use crate::document::camt_029_001_09::ResolutionOfInvestigationV09;
525 use crate::header::bah_camt_029_001::BusinessApplicationHeaderV02;
526
527 let header: BusinessApplicationHeaderV02 =
528 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
529 ValidationError::new(9997, format!("Failed to parse camt029 header: {e}"))
530 })?;
531
532 let message: ResolutionOfInvestigationV09 = serde_json::from_value(
533 envelope["Document"]["RsltnOfInvstgtn"].clone(),
534 )
535 .map_err(|e| {
536 ValidationError::new(9997, format!("Failed to parse camt029 document: {e}"))
537 })?;
538
539 to_mx_xml(&message, header, "camt.029", None)
540 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
541 }
542 "camt052" => {
543 use crate::document::camt_052_001_08::BankToCustomerAccountReportV08;
544 use crate::header::bah_camt_052_001_08::BusinessApplicationHeaderV02;
545
546 let header: BusinessApplicationHeaderV02 =
547 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
548 ValidationError::new(9997, format!("Failed to parse camt052 header: {e}"))
549 })?;
550
551 let message: BankToCustomerAccountReportV08 = serde_json::from_value(
552 envelope["Document"]["BkToCstmrAcctRpt"].clone(),
553 )
554 .map_err(|e| {
555 ValidationError::new(9997, format!("Failed to parse camt052 document: {e}"))
556 })?;
557
558 to_mx_xml(&message, header, "camt.052", None)
559 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
560 }
561 "camt053" => {
562 use crate::document::camt_053_001_08::BankToCustomerStatementV08;
563 use crate::header::bah_camt_053_001_08::BusinessApplicationHeaderV02;
564
565 let header: BusinessApplicationHeaderV02 =
566 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
567 ValidationError::new(9997, format!("Failed to parse camt053 header: {e}"))
568 })?;
569
570 let message: BankToCustomerStatementV08 = serde_json::from_value(
571 envelope["Document"]["BkToCstmrStmt"].clone(),
572 )
573 .map_err(|e| {
574 ValidationError::new(9997, format!("Failed to parse camt053 document: {e}"))
575 })?;
576
577 to_mx_xml(&message, header, "camt.053", None)
578 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
579 }
580 "camt054" => {
581 use crate::document::camt_054_001_08::BankToCustomerDebitCreditNotificationV08;
582 use crate::header::bah_camt_054_001::BusinessApplicationHeaderV02;
583
584 let header: BusinessApplicationHeaderV02 =
585 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
586 ValidationError::new(9997, format!("Failed to parse camt054 header: {e}"))
587 })?;
588
589 let message: BankToCustomerDebitCreditNotificationV08 =
590 serde_json::from_value(envelope["Document"]["BkToCstmrDbtCdtNtfctn"].clone())
591 .map_err(|e| {
592 ValidationError::new(9997, format!("Failed to parse camt054 document: {e}"))
593 })?;
594
595 to_mx_xml(&message, header, "camt.054", None)
596 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
597 }
598 "camt056" => {
599 use crate::document::camt_056_001_08::FIToFIPaymentCancellationRequestV08;
600 use crate::header::bah_camt_056_001_08::BusinessApplicationHeaderV02;
601
602 let header: BusinessApplicationHeaderV02 =
603 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
604 ValidationError::new(9997, format!("Failed to parse camt056 header: {e}"))
605 })?;
606
607 let message: FIToFIPaymentCancellationRequestV08 = serde_json::from_value(
608 envelope["Document"]["FIToFIPmtCxlReq"].clone(),
609 )
610 .map_err(|e| {
611 ValidationError::new(9997, format!("Failed to parse camt056 document: {e}"))
612 })?;
613
614 to_mx_xml(&message, header, "camt.056", None)
615 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
616 }
617 "camt057" => {
618 use crate::document::camt_057_001_06::NotificationToReceiveV06;
619 use crate::header::bah_camt_057_001_06::BusinessApplicationHeaderV02;
620
621 let header: BusinessApplicationHeaderV02 =
622 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
623 ValidationError::new(9997, format!("Failed to parse camt057 header: {e}"))
624 })?;
625
626 let message: NotificationToReceiveV06 = serde_json::from_value(
627 envelope["Document"]["NtfctnToRcv"].clone(),
628 )
629 .map_err(|e| {
630 ValidationError::new(9997, format!("Failed to parse camt057 document: {e}"))
631 })?;
632
633 to_mx_xml(&message, header, "camt.057", None)
634 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
635 }
636 "camt060" => {
637 use crate::document::camt_060_001_05::AccountReportingRequestV05;
638 use crate::header::bah_camt_060_001_05::BusinessApplicationHeaderV02;
639
640 let header: BusinessApplicationHeaderV02 =
641 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
642 ValidationError::new(9997, format!("Failed to parse camt060 header: {e}"))
643 })?;
644
645 let message: AccountReportingRequestV05 = serde_json::from_value(
646 envelope["Document"]["AcctRptgReq"].clone(),
647 )
648 .map_err(|e| {
649 ValidationError::new(9997, format!("Failed to parse camt060 document: {e}"))
650 })?;
651
652 to_mx_xml(&message, header, "camt.060", None)
653 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
654 }
655 _ => Err(ValidationError::new(
656 9997,
657 format!("Unsupported message type: {message_type}"),
658 )),
659 }
660}