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 "pacs002" => "FIToFIPmtStsRpt",
356 "pain001" => "CstmrCdtTrfInitn",
357 "pain008" => "CstmrDrctDbtInitn",
358 "camt025" => "Rcpt",
359 "camt029" => "RsltnOfInvstgtn",
360 "camt052" => "BkToCstmrAcctRpt",
361 "camt053" => "BkToCstmrStmt",
362 "camt054" => "BkToCstmrDbtCdtNtfctn",
363 "camt056" => "FIToFIPmtCxlReq",
364 "camt057" => "NtfctnToRcv",
365 "camt060" => "AcctRptgReq",
366 _ => "Document", }
368 .to_string()
369}
370
371fn envelope_to_xml(envelope: Value, message_type: &str) -> Result<String> {
373 match message_type {
375 "pacs008" => {
376 use crate::document::pacs_008_001_08::FIToFICustomerCreditTransferV08;
377 use crate::header::bah_pacs_008_001_08::BusinessApplicationHeaderV02;
378
379 let header: BusinessApplicationHeaderV02 =
380 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
381 ValidationError::new(9997, format!("Failed to parse pacs008 header: {e}"))
382 })?;
383
384 let message: FIToFICustomerCreditTransferV08 = serde_json::from_value(
385 envelope["Document"]["FIToFICstmrCdtTrf"].clone(),
386 )
387 .map_err(|e| {
388 ValidationError::new(9997, format!("Failed to parse pacs008 document: {e}"))
389 })?;
390
391 to_mx_xml(&message, header, "pacs.008", None)
392 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
393 }
394 "pacs009" => {
395 use crate::document::pacs_009_001_08::FinancialInstitutionCreditTransferV08;
396 use crate::header::bah_pacs_009_001_08::BusinessApplicationHeaderV02;
397
398 let header: BusinessApplicationHeaderV02 =
399 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
400 ValidationError::new(9997, format!("Failed to parse pacs009 header: {e}"))
401 })?;
402
403 let message: FinancialInstitutionCreditTransferV08 = serde_json::from_value(
404 envelope["Document"]["FinInstnCdtTrf"].clone(),
405 )
406 .map_err(|e| {
407 ValidationError::new(9997, format!("Failed to parse pacs009 document: {e}"))
408 })?;
409
410 to_mx_xml(&message, header, "pacs.009", None)
411 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
412 }
413 "pacs003" => {
414 use crate::document::pacs_003_001_08::FIToFICustomerDirectDebitV08;
415 use crate::header::bah_pacs_003_001_08::BusinessApplicationHeaderV02;
416
417 let header: BusinessApplicationHeaderV02 =
418 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
419 ValidationError::new(9997, format!("Failed to parse pacs003 header: {e}"))
420 })?;
421
422 let message: FIToFICustomerDirectDebitV08 =
423 serde_json::from_value(envelope["Document"]["FIToFICstmrDrctDbt"].clone())
424 .map_err(|e| {
425 ValidationError::new(9997, format!("Failed to parse pacs003 document: {e}"))
426 })?;
427
428 to_mx_xml(&message, header, "pacs.003", None)
429 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
430 }
431 "pacs002" => {
432 use crate::document::pacs_002_001_10::FIToFIPaymentStatusReportV10;
433 use crate::header::bah_pacs_002_001_10::BusinessApplicationHeaderV02;
434
435 let header: BusinessApplicationHeaderV02 =
436 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
437 ValidationError::new(9997, format!("Failed to parse pacs002 header: {e}"))
438 })?;
439
440 let message: FIToFIPaymentStatusReportV10 = serde_json::from_value(
441 envelope["Document"]["FIToFIPmtStsRpt"].clone(),
442 )
443 .map_err(|e| {
444 ValidationError::new(9997, format!("Failed to parse pacs002 document: {e}"))
445 })?;
446
447 to_mx_xml(&message, header, "pacs.002", None)
448 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
449 }
450 "pain001" => {
451 use crate::document::pain_001_001_09::CustomerCreditTransferInitiationV09;
452 use crate::header::bah_pain_001_001_09::BusinessApplicationHeaderV02;
453
454 let header: BusinessApplicationHeaderV02 =
455 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
456 ValidationError::new(9997, format!("Failed to parse pain001 header: {e}"))
457 })?;
458
459 let message: CustomerCreditTransferInitiationV09 = serde_json::from_value(
460 envelope["Document"]["CstmrCdtTrfInitn"].clone(),
461 )
462 .map_err(|e| {
463 ValidationError::new(9997, format!("Failed to parse pain001 document: {e}"))
464 })?;
465
466 to_mx_xml(&message, header, "pain.001", None)
467 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
468 }
469 "pain008" => {
470 use crate::document::pain_008_001_08::CustomerDirectDebitInitiationV08;
471 use crate::header::bah_pain_008_001_08::BusinessApplicationHeaderV02;
472
473 let header: BusinessApplicationHeaderV02 =
474 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
475 ValidationError::new(9997, format!("Failed to parse pain008 header: {e}"))
476 })?;
477
478 let message: CustomerDirectDebitInitiationV08 = serde_json::from_value(
479 envelope["Document"]["CstmrDrctDbtInitn"].clone(),
480 )
481 .map_err(|e| {
482 ValidationError::new(9997, format!("Failed to parse pain008 document: {e}"))
483 })?;
484
485 to_mx_xml(&message, header, "pain.008", None)
486 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
487 }
488 "camt025" => {
489 use crate::document::camt_025_001_08::ReceiptV08;
490 use crate::header::bah_camt_025_001_08::BusinessApplicationHeaderV02;
491
492 let header: BusinessApplicationHeaderV02 =
493 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
494 ValidationError::new(9997, format!("Failed to parse camt025 header: {e}"))
495 })?;
496
497 let message: ReceiptV08 = serde_json::from_value(envelope["Document"]["Rcpt"].clone())
498 .map_err(|e| {
499 ValidationError::new(9997, format!("Failed to parse camt025 document: {e}"))
500 })?;
501
502 to_mx_xml(&message, header, "camt.025", None)
503 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
504 }
505 "camt029" => {
506 use crate::document::camt_029_001_09::ResolutionOfInvestigationV09;
507 use crate::header::bah_camt_029_001::BusinessApplicationHeaderV02;
508
509 let header: BusinessApplicationHeaderV02 =
510 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
511 ValidationError::new(9997, format!("Failed to parse camt029 header: {e}"))
512 })?;
513
514 let message: ResolutionOfInvestigationV09 = serde_json::from_value(
515 envelope["Document"]["RsltnOfInvstgtn"].clone(),
516 )
517 .map_err(|e| {
518 ValidationError::new(9997, format!("Failed to parse camt029 document: {e}"))
519 })?;
520
521 to_mx_xml(&message, header, "camt.029", None)
522 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
523 }
524 "camt052" => {
525 use crate::document::camt_052_001_08::BankToCustomerAccountReportV08;
526 use crate::header::bah_camt_052_001_08::BusinessApplicationHeaderV02;
527
528 let header: BusinessApplicationHeaderV02 =
529 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
530 ValidationError::new(9997, format!("Failed to parse camt052 header: {e}"))
531 })?;
532
533 let message: BankToCustomerAccountReportV08 = serde_json::from_value(
534 envelope["Document"]["BkToCstmrAcctRpt"].clone(),
535 )
536 .map_err(|e| {
537 ValidationError::new(9997, format!("Failed to parse camt052 document: {e}"))
538 })?;
539
540 to_mx_xml(&message, header, "camt.052", None)
541 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
542 }
543 "camt053" => {
544 use crate::document::camt_053_001_08::BankToCustomerStatementV08;
545 use crate::header::bah_camt_053_001_08::BusinessApplicationHeaderV02;
546
547 let header: BusinessApplicationHeaderV02 =
548 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
549 ValidationError::new(9997, format!("Failed to parse camt053 header: {e}"))
550 })?;
551
552 let message: BankToCustomerStatementV08 = serde_json::from_value(
553 envelope["Document"]["BkToCstmrStmt"].clone(),
554 )
555 .map_err(|e| {
556 ValidationError::new(9997, format!("Failed to parse camt053 document: {e}"))
557 })?;
558
559 to_mx_xml(&message, header, "camt.053", None)
560 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
561 }
562 "camt054" => {
563 use crate::document::camt_054_001_08::BankToCustomerDebitCreditNotificationV08;
564 use crate::header::bah_camt_054_001::BusinessApplicationHeaderV02;
565
566 let header: BusinessApplicationHeaderV02 =
567 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
568 ValidationError::new(9997, format!("Failed to parse camt054 header: {e}"))
569 })?;
570
571 let message: BankToCustomerDebitCreditNotificationV08 =
572 serde_json::from_value(envelope["Document"]["BkToCstmrDbtCdtNtfctn"].clone())
573 .map_err(|e| {
574 ValidationError::new(9997, format!("Failed to parse camt054 document: {e}"))
575 })?;
576
577 to_mx_xml(&message, header, "camt.054", None)
578 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
579 }
580 "camt056" => {
581 use crate::document::camt_056_001_08::FIToFIPaymentCancellationRequestV08;
582 use crate::header::bah_camt_056_001_08::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 camt056 header: {e}"))
587 })?;
588
589 let message: FIToFIPaymentCancellationRequestV08 = serde_json::from_value(
590 envelope["Document"]["FIToFIPmtCxlReq"].clone(),
591 )
592 .map_err(|e| {
593 ValidationError::new(9997, format!("Failed to parse camt056 document: {e}"))
594 })?;
595
596 to_mx_xml(&message, header, "camt.056", None)
597 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
598 }
599 "camt057" => {
600 use crate::document::camt_057_001_06::NotificationToReceiveV06;
601 use crate::header::bah_camt_057_001_06::BusinessApplicationHeaderV02;
602
603 let header: BusinessApplicationHeaderV02 =
604 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
605 ValidationError::new(9997, format!("Failed to parse camt057 header: {e}"))
606 })?;
607
608 let message: NotificationToReceiveV06 = serde_json::from_value(
609 envelope["Document"]["NtfctnToRcv"].clone(),
610 )
611 .map_err(|e| {
612 ValidationError::new(9997, format!("Failed to parse camt057 document: {e}"))
613 })?;
614
615 to_mx_xml(&message, header, "camt.057", None)
616 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
617 }
618 "camt060" => {
619 use crate::document::camt_060_001_05::AccountReportingRequestV05;
620 use crate::header::bah_camt_060_001_05::BusinessApplicationHeaderV02;
621
622 let header: BusinessApplicationHeaderV02 =
623 serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
624 ValidationError::new(9997, format!("Failed to parse camt060 header: {e}"))
625 })?;
626
627 let message: AccountReportingRequestV05 = serde_json::from_value(
628 envelope["Document"]["AcctRptgReq"].clone(),
629 )
630 .map_err(|e| {
631 ValidationError::new(9997, format!("Failed to parse camt060 document: {e}"))
632 })?;
633
634 to_mx_xml(&message, header, "camt.060", None)
635 .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
636 }
637 _ => Err(ValidationError::new(
638 9997,
639 format!("Unsupported message type: {message_type}"),
640 )),
641 }
642}