mx_message/
sample.rs

1// Plasmatic MX Message Parsing Library
2// https://github.com/GoPlasmatic/MXMessage
3//
4// Copyright (c) 2025 Plasmatic
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//     http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16//
17// You may obtain a copy of this library at
18// https://github.com/GoPlasmatic/MXMessage
19
20use 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
30/// Generate a sample MX XML message based on test scenarios
31///
32/// This function loads a test scenario configuration for the specified message type
33/// and generates a complete MX XML message with envelope and header.
34///
35/// # Arguments
36///
37/// * `message_type` - The MX message type (e.g., "pacs008", "camt053")
38/// * `scenario_name` - Optional scenario name. If None, uses the default scenario
39/// * `config` - Configuration for scenario file paths
40///
41/// # Returns
42///
43/// Returns a complete MX XML string with envelope and header
44///
45/// # Example
46///
47/// ```no_run
48/// # use mx_message::sample::generate_sample_xml;
49/// # use mx_message::scenario_config::ScenarioConfig;
50/// // Generate a standard pacs.008 message as XML
51/// let pacs008_xml = generate_sample_xml("pacs008", None, &ScenarioConfig::default()).unwrap();
52///
53/// // Generate a specific scenario
54/// let pacs008_high_value_xml = generate_sample_xml("pacs008", Some("high_value"), &ScenarioConfig::default()).unwrap();
55/// ```
56pub fn generate_sample_xml(
57    message_type: &str,
58    scenario_name: Option<&str>,
59    config: &ScenarioConfig,
60) -> Result<String> {
61    // Load the scenario configuration JSON
62    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    // Process the scenario to generate complete envelope using datafake-rs
69    let envelope_data = generate_envelope_from_scenario(&scenario_json)?;
70
71    // Convert envelope to XML based on message type
72    envelope_to_xml(envelope_data, message_type)
73}
74
75/// Generate a sample MX message object based on test scenarios
76///
77/// This function loads a test scenario configuration for the specified message type
78/// and generates the document part of the message as a typed object.
79///
80/// # Arguments
81///
82/// * `message_type` - The MX message type (e.g., "pacs008", "camt053")
83/// * `scenario_name` - Optional scenario name. If None, uses the default scenario
84/// * `config` - Configuration for scenario file paths
85///
86/// # Returns
87///
88/// Returns the document part of the message as a deserializable type
89///
90/// # Example
91///
92/// ```no_run
93/// # use mx_message::sample::generate_sample_object;
94/// # use mx_message::scenario_config::ScenarioConfig;
95/// # use mx_message::document::pacs_008_001_08::FIToFICustomerCreditTransferV08;
96/// // Generate a pacs.008 message object
97/// let pacs008: FIToFICustomerCreditTransferV08 = generate_sample_object("pacs008", None, &ScenarioConfig::default()).unwrap();
98/// ```
99pub 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    // Load the scenario configuration JSON
108    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    // Process the scenario to generate complete envelope
115    let envelope_data = generate_envelope_from_scenario(&scenario_json)?;
116
117    // Extract the document part based on message type
118    let document_data = extract_document_from_envelope(&envelope_data, message_type)?;
119
120    // Convert generated data to string for parsing
121    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    // Parse the generated JSON into the message type
126    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
134// ============================================================================
135// Private implementation functions
136// ============================================================================
137
138/// Generate complete envelope from scenario JSON using datafake-rs
139fn generate_envelope_from_scenario(scenario: &Value) -> Result<Value> {
140    // Convert the scenario format to datafake-rs compatible format
141    let datafake_config = convert_to_datafake_config(scenario)?;
142
143    // Create a DataGenerator from the config
144    let generator = DataGenerator::from_value(datafake_config)
145        .map_err(|e| ValidationError::new(9997, format!("Failed to create generator: {e}")))?;
146
147    // Generate the data
148    generator
149        .generate()
150        .map_err(|e| ValidationError::new(9997, format!("Failed to generate data: {e}")))
151}
152
153/// Convert scenario JSON to datafake-rs compatible configuration
154fn convert_to_datafake_config(scenario: &Value) -> Result<Value> {
155    // Extract variables and schema
156    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    // Convert the configuration to datafake-rs format
165    // Variables section stays mostly the same, but we need to ensure compatibility
166    let converted_variables = convert_variables(variables)?;
167
168    // Schema section needs to be processed to handle special cases
169    let converted_schema = convert_schema(schema)?;
170
171    Ok(json!({
172        "variables": converted_variables,
173        "schema": converted_schema
174    }))
175}
176
177/// Convert variables section to datafake-rs compatible format
178fn 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
194/// Convert a single variable specification
195fn convert_variable_spec(spec: &Value) -> Result<Value> {
196    match spec {
197        Value::Object(obj) => {
198            // Handle cat operations - datafake-rs supports these natively
199            if obj.contains_key("cat") {
200                return Ok(spec.clone());
201            }
202
203            // Handle fake operations - ensure compatibility with datafake-rs
204            if let Some(fake_spec) = obj.get("fake") {
205                if let Value::Array(fake_arr) = fake_spec {
206                    // Map our custom fake types to datafake-rs supported types
207                    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            // Handle pick operations - convert to fake enum
216            if let Some(Value::Array(options)) = obj.get("pick") {
217                // Convert pick to a fake enum operation
218                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            // Static values pass through
229            Ok(spec.clone())
230        }
231        _ => Ok(spec.clone()),
232    }
233}
234
235/// Map custom fake types to datafake-rs supported types
236fn map_fake_type(fake_type: &str, args: &[Value]) -> Result<Value> {
237    // Most fake types are compatible, but we need to handle some special cases
238    match fake_type {
239        // ISO datetime is a custom type in our implementation
240        "iso8601_datetime" => {
241            // Since datafake-rs doesn't have datetime support, generate a static ISO datetime
242            let now = chrono::Utc::now();
243            Ok(json!(now.to_rfc3339()))
244        }
245        // Currency code needs mapping
246        "currency_code" => {
247            // Use enum with common currency codes
248            Ok(json!({"fake": ["enum", "EUR", "USD", "GBP", "CHF", "JPY", "CAD", "AUD"]}))
249        }
250        // IBAN generation with country code
251        "iban" => {
252            let country = args.first().and_then(|v| v.as_str()).unwrap_or("DE");
253            Ok(json!({"fake": ["iban", country]}))
254        }
255        // BIC code generation
256        "bic" => Ok(json!({"fake": ["bic"]})),
257        // LEI code generation
258        "lei" => Ok(json!({"fake": ["lei"]})),
259        // Alphanumeric with length
260        "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 generation
266        "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 patterns - pass through as-is, datafake-rs now supports it
272        "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        // Pass through other types as-is, datafake-rs supports most common ones
280        _ => {
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
290/// Convert schema section to datafake-rs compatible format
291fn convert_schema(schema: &Value) -> Result<Value> {
292    process_schema_value(schema)
293}
294
295/// Process schema value recursively
296fn 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                // Handle special keys
303                if key == "@Ccy" {
304                    // This is an attribute in XML, keep as-is
305                    result.insert(key.clone(), process_schema_value(val)?);
306                } else if key == "$value" {
307                    // This is the element value in XML, keep as-is
308                    result.insert(key.clone(), process_schema_value(val)?);
309                } else if key == "var" {
310                    // Variable reference, keep as-is (datafake-rs supports this)
311                    return Ok(value.clone());
312                } else if key == "fake" || key == "cat" || key == "pick" {
313                    // These are datafake-rs operations, process the spec
314                    return convert_variable_spec(value);
315                } else {
316                    // Regular field, process recursively
317                    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
334/// Extract document from envelope for backward compatibility
335fn 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    // Extract the inner document based on message type
341    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
349/// Get the document root element name for a message type
350fn 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        "camt107" => "ChqPresntmntNtfctn",
368        "camt108" => "ChqCxlOrStopReq",
369        "camt109" => "ChqCxlOrStopRpt",
370        "pacs010" => "FIDrctDbt",
371        _ => "Document", // Fallback
372    }
373    .to_string()
374}
375
376/// Convert envelope JSON to typed XML
377fn envelope_to_xml(envelope: Value, message_type: &str) -> Result<String> {
378    // Parse and generate XML based on message type
379    match message_type {
380        "pacs008" => {
381            use crate::document::pacs_008_001_08::FIToFICustomerCreditTransferV08;
382            use crate::header::bah_pacs_008_001_08::BusinessApplicationHeaderV02;
383
384            let header: BusinessApplicationHeaderV02 =
385                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
386                    ValidationError::new(9997, format!("Failed to parse pacs008 header: {e}"))
387                })?;
388
389            let message: FIToFICustomerCreditTransferV08 = serde_json::from_value(
390                envelope["Document"]["FIToFICstmrCdtTrf"].clone(),
391            )
392            .map_err(|e| {
393                ValidationError::new(9997, format!("Failed to parse pacs008 document: {e}"))
394            })?;
395
396            to_mx_xml(&message, header, "pacs.008", None)
397                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
398        }
399        "pacs009" => {
400            use crate::document::pacs_009_001_08::FinancialInstitutionCreditTransferV08;
401            use crate::header::bah_pacs_009_001_08::BusinessApplicationHeaderV02;
402
403            let header: BusinessApplicationHeaderV02 =
404                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
405                    ValidationError::new(9997, format!("Failed to parse pacs009 header: {e}"))
406                })?;
407
408            let message: FinancialInstitutionCreditTransferV08 = serde_json::from_value(
409                envelope["Document"]["FinInstnCdtTrf"].clone(),
410            )
411            .map_err(|e| {
412                ValidationError::new(9997, format!("Failed to parse pacs009 document: {e}"))
413            })?;
414
415            to_mx_xml(&message, header, "pacs.009", None)
416                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
417        }
418        "pacs003" => {
419            use crate::document::pacs_003_001_08::FIToFICustomerDirectDebitV08;
420            use crate::header::bah_pacs_003_001_08::BusinessApplicationHeaderV02;
421
422            let header: BusinessApplicationHeaderV02 =
423                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
424                    ValidationError::new(9997, format!("Failed to parse pacs003 header: {e}"))
425                })?;
426
427            let message: FIToFICustomerDirectDebitV08 =
428                serde_json::from_value(envelope["Document"]["FIToFICstmrDrctDbt"].clone())
429                    .map_err(|e| {
430                        ValidationError::new(9997, format!("Failed to parse pacs003 document: {e}"))
431                    })?;
432
433            to_mx_xml(&message, header, "pacs.003", None)
434                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
435        }
436        "pacs004" => {
437            use crate::document::pacs_004_001_09::PaymentReturnV09;
438            use crate::header::bah_pacs_004_001_09::BusinessApplicationHeaderV02;
439
440            let header: BusinessApplicationHeaderV02 =
441                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
442                    ValidationError::new(9997, format!("Failed to parse pacs004 header: {e}"))
443                })?;
444
445            let message: PaymentReturnV09 =
446                serde_json::from_value(envelope["Document"]["PmtRtr"].clone()).map_err(|e| {
447                    ValidationError::new(9997, format!("Failed to parse pacs004 document: {e}"))
448                })?;
449
450            to_mx_xml(&message, header, "pacs.004", None)
451                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
452        }
453        "pacs010" => {
454            use crate::document::pacs_010_001_03::FinancialInstitutionDirectDebitV03;
455            use crate::header::bah_pacs_010_001_03::BusinessApplicationHeaderV02;
456
457            let header: BusinessApplicationHeaderV02 =
458                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
459                    ValidationError::new(9997, format!("Failed to parse pacs010 header: {e}"))
460                })?;
461
462            let message: FinancialInstitutionDirectDebitV03 =
463                serde_json::from_value(envelope["Document"]["FIDrctDbt"].clone()).map_err(|e| {
464                    ValidationError::new(9997, format!("Failed to parse pacs010 document: {e}"))
465                })?;
466
467            to_mx_xml(&message, header, "pacs.010", None)
468                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
469        }
470        "pacs002" => {
471            use crate::document::pacs_002_001_10::FIToFIPaymentStatusReportV10;
472            use crate::header::bah_pacs_002_001_10::BusinessApplicationHeaderV02;
473
474            let header: BusinessApplicationHeaderV02 =
475                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
476                    ValidationError::new(9997, format!("Failed to parse pacs002 header: {e}"))
477                })?;
478
479            let message: FIToFIPaymentStatusReportV10 = serde_json::from_value(
480                envelope["Document"]["FIToFIPmtStsRpt"].clone(),
481            )
482            .map_err(|e| {
483                ValidationError::new(9997, format!("Failed to parse pacs002 document: {e}"))
484            })?;
485
486            to_mx_xml(&message, header, "pacs.002", None)
487                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
488        }
489        "pain001" => {
490            use crate::document::pain_001_001_09::CustomerCreditTransferInitiationV09;
491            use crate::header::bah_pain_001_001_09::BusinessApplicationHeaderV02;
492
493            let header: BusinessApplicationHeaderV02 =
494                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
495                    ValidationError::new(9997, format!("Failed to parse pain001 header: {e}"))
496                })?;
497
498            let message: CustomerCreditTransferInitiationV09 = serde_json::from_value(
499                envelope["Document"]["CstmrCdtTrfInitn"].clone(),
500            )
501            .map_err(|e| {
502                ValidationError::new(9997, format!("Failed to parse pain001 document: {e}"))
503            })?;
504
505            to_mx_xml(&message, header, "pain.001", None)
506                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
507        }
508        "pain008" => {
509            use crate::document::pain_008_001_08::CustomerDirectDebitInitiationV08;
510            use crate::header::bah_pain_008_001_08::BusinessApplicationHeaderV02;
511
512            let header: BusinessApplicationHeaderV02 =
513                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
514                    ValidationError::new(9997, format!("Failed to parse pain008 header: {e}"))
515                })?;
516
517            let message: CustomerDirectDebitInitiationV08 = serde_json::from_value(
518                envelope["Document"]["CstmrDrctDbtInitn"].clone(),
519            )
520            .map_err(|e| {
521                ValidationError::new(9997, format!("Failed to parse pain008 document: {e}"))
522            })?;
523
524            to_mx_xml(&message, header, "pain.008", None)
525                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
526        }
527        "camt025" => {
528            use crate::document::camt_025_001_08::ReceiptV08;
529            use crate::header::bah_camt_025_001_08::BusinessApplicationHeaderV02;
530
531            let header: BusinessApplicationHeaderV02 =
532                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
533                    ValidationError::new(9997, format!("Failed to parse camt025 header: {e}"))
534                })?;
535
536            let message: ReceiptV08 = serde_json::from_value(envelope["Document"]["Rcpt"].clone())
537                .map_err(|e| {
538                    ValidationError::new(9997, format!("Failed to parse camt025 document: {e}"))
539                })?;
540
541            to_mx_xml(&message, header, "camt.025", None)
542                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
543        }
544        "camt029" => {
545            use crate::document::camt_029_001_09::ResolutionOfInvestigationV09;
546            use crate::header::bah_camt_029_001::BusinessApplicationHeaderV02;
547
548            let header: BusinessApplicationHeaderV02 =
549                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
550                    ValidationError::new(9997, format!("Failed to parse camt029 header: {e}"))
551                })?;
552
553            let message: ResolutionOfInvestigationV09 = serde_json::from_value(
554                envelope["Document"]["RsltnOfInvstgtn"].clone(),
555            )
556            .map_err(|e| {
557                ValidationError::new(9997, format!("Failed to parse camt029 document: {e}"))
558            })?;
559
560            to_mx_xml(&message, header, "camt.029", None)
561                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
562        }
563        "camt052" => {
564            use crate::document::camt_052_001_08::BankToCustomerAccountReportV08;
565            use crate::header::bah_camt_052_001_08::BusinessApplicationHeaderV02;
566
567            let header: BusinessApplicationHeaderV02 =
568                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
569                    ValidationError::new(9997, format!("Failed to parse camt052 header: {e}"))
570                })?;
571
572            let message: BankToCustomerAccountReportV08 = serde_json::from_value(
573                envelope["Document"]["BkToCstmrAcctRpt"].clone(),
574            )
575            .map_err(|e| {
576                ValidationError::new(9997, format!("Failed to parse camt052 document: {e}"))
577            })?;
578
579            to_mx_xml(&message, header, "camt.052", None)
580                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
581        }
582        "camt053" => {
583            use crate::document::camt_053_001_08::BankToCustomerStatementV08;
584            use crate::header::bah_camt_053_001_08::BusinessApplicationHeaderV02;
585
586            let header: BusinessApplicationHeaderV02 =
587                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
588                    ValidationError::new(9997, format!("Failed to parse camt053 header: {e}"))
589                })?;
590
591            let message: BankToCustomerStatementV08 = serde_json::from_value(
592                envelope["Document"]["BkToCstmrStmt"].clone(),
593            )
594            .map_err(|e| {
595                ValidationError::new(9997, format!("Failed to parse camt053 document: {e}"))
596            })?;
597
598            to_mx_xml(&message, header, "camt.053", None)
599                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
600        }
601        "camt054" => {
602            use crate::document::camt_054_001_08::BankToCustomerDebitCreditNotificationV08;
603            use crate::header::bah_camt_054_001::BusinessApplicationHeaderV02;
604
605            let header: BusinessApplicationHeaderV02 =
606                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
607                    ValidationError::new(9997, format!("Failed to parse camt054 header: {e}"))
608                })?;
609
610            let message: BankToCustomerDebitCreditNotificationV08 =
611                serde_json::from_value(envelope["Document"]["BkToCstmrDbtCdtNtfctn"].clone())
612                    .map_err(|e| {
613                        ValidationError::new(9997, format!("Failed to parse camt054 document: {e}"))
614                    })?;
615
616            to_mx_xml(&message, header, "camt.054", None)
617                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
618        }
619        "camt056" => {
620            use crate::document::camt_056_001_08::FIToFIPaymentCancellationRequestV08;
621            use crate::header::bah_camt_056_001_08::BusinessApplicationHeaderV02;
622
623            let header: BusinessApplicationHeaderV02 =
624                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
625                    ValidationError::new(9997, format!("Failed to parse camt056 header: {e}"))
626                })?;
627
628            let message: FIToFIPaymentCancellationRequestV08 = serde_json::from_value(
629                envelope["Document"]["FIToFIPmtCxlReq"].clone(),
630            )
631            .map_err(|e| {
632                ValidationError::new(9997, format!("Failed to parse camt056 document: {e}"))
633            })?;
634
635            to_mx_xml(&message, header, "camt.056", None)
636                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
637        }
638        "camt057" => {
639            use crate::document::camt_057_001_06::NotificationToReceiveV06;
640            use crate::header::bah_camt_057_001_06::BusinessApplicationHeaderV02;
641
642            let header: BusinessApplicationHeaderV02 =
643                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
644                    ValidationError::new(9997, format!("Failed to parse camt057 header: {e}"))
645                })?;
646
647            let message: NotificationToReceiveV06 = serde_json::from_value(
648                envelope["Document"]["NtfctnToRcv"].clone(),
649            )
650            .map_err(|e| {
651                ValidationError::new(9997, format!("Failed to parse camt057 document: {e}"))
652            })?;
653
654            to_mx_xml(&message, header, "camt.057", None)
655                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
656        }
657        "camt060" => {
658            use crate::document::camt_060_001_05::AccountReportingRequestV05;
659            use crate::header::bah_camt_060_001_05::BusinessApplicationHeaderV02;
660
661            let header: BusinessApplicationHeaderV02 =
662                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
663                    ValidationError::new(9997, format!("Failed to parse camt060 header: {e}"))
664                })?;
665
666            let message: AccountReportingRequestV05 = serde_json::from_value(
667                envelope["Document"]["AcctRptgReq"].clone(),
668            )
669            .map_err(|e| {
670                ValidationError::new(9997, format!("Failed to parse camt060 document: {e}"))
671            })?;
672
673            to_mx_xml(&message, header, "camt.060", None)
674                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
675        }
676        "camt107" => {
677            use crate::document::camt_107_001_01::ChequePresentmentNotificationV01;
678            use crate::header::bah_camt_107_001_01::BusinessApplicationHeaderV02;
679
680            let header: BusinessApplicationHeaderV02 =
681                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
682                    ValidationError::new(9997, format!("Failed to parse camt107 header: {e}"))
683                })?;
684
685            let message: ChequePresentmentNotificationV01 =
686                serde_json::from_value(envelope["Document"]["ChqPresntmntNtfctn"].clone())
687                    .map_err(|e| {
688                        ValidationError::new(9997, format!("Failed to parse camt107 document: {e}"))
689                    })?;
690
691            to_mx_xml(&message, header, "camt.107", None)
692                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
693        }
694        "camt108" => {
695            use crate::document::camt_108_001_01::ChequeCancellationOrStopRequestV01;
696            use crate::header::bah_camt_108_001_01::BusinessApplicationHeaderV02;
697
698            let header: BusinessApplicationHeaderV02 =
699                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
700                    ValidationError::new(9997, format!("Failed to parse camt108 header: {e}"))
701                })?;
702
703            let message: ChequeCancellationOrStopRequestV01 = serde_json::from_value(
704                envelope["Document"]["ChqCxlOrStopReq"].clone(),
705            )
706            .map_err(|e| {
707                ValidationError::new(9997, format!("Failed to parse camt108 document: {e}"))
708            })?;
709
710            to_mx_xml(&message, header, "camt.108", None)
711                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
712        }
713        "camt109" => {
714            use crate::document::camt_109_001_01::ChequeCancellationOrStopReportV01;
715            use crate::header::bah_camt_109_001_01::BusinessApplicationHeaderV02;
716
717            let header: BusinessApplicationHeaderV02 =
718                serde_json::from_value(envelope["AppHdr"].clone()).map_err(|e| {
719                    ValidationError::new(9997, format!("Failed to parse camt109 header: {e}"))
720                })?;
721
722            let message: ChequeCancellationOrStopReportV01 = serde_json::from_value(
723                envelope["Document"]["ChqCxlOrStopRpt"].clone(),
724            )
725            .map_err(|e| {
726                ValidationError::new(9997, format!("Failed to parse camt109 document: {e}"))
727            })?;
728
729            to_mx_xml(&message, header, "camt.109", None)
730                .map_err(|e| ValidationError::new(9997, format!("Failed to generate XML: {e}")))
731        }
732        _ => Err(ValidationError::new(
733            9997,
734            format!("Unsupported message type: {message_type}"),
735        )),
736    }
737}