mx_message/
xml.rs

1// XML Serialization and Deserialization utilities for MX Messages
2
3use crate::mx_envelope::MxEnvelope;
4use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
5use quick_xml::writer::Writer;
6use quick_xml::{de::from_str as xml_from_str, se::to_string as xml_to_string};
7use serde::{Deserialize, Serialize};
8use std::error::Error;
9use std::fmt;
10use std::io::Cursor;
11
12/// Configuration for XML serialization
13#[derive(Debug, Clone)]
14pub struct XmlConfig {
15    /// Whether to include XML declaration
16    pub include_declaration: bool,
17    /// Whether to format with indentation
18    pub pretty_print: bool,
19    /// Indentation string (e.g., "  " for 2 spaces)
20    pub indent: String,
21    /// Whether to include schema location
22    pub include_schema_location: bool,
23}
24
25impl Default for XmlConfig {
26    fn default() -> Self {
27        Self {
28            include_declaration: true,
29            pretty_print: true,
30            indent: "  ".to_string(),
31            include_schema_location: false,
32        }
33    }
34}
35
36/// Error type for XML operations
37#[derive(Debug)]
38pub enum XmlError {
39    SerializationError(String),
40    DeserializationError(String),
41    ValidationError(String),
42}
43
44impl fmt::Display for XmlError {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        match self {
47            XmlError::SerializationError(msg) => write!(f, "XML Serialization Error: {msg}"),
48            XmlError::DeserializationError(msg) => write!(f, "XML Deserialization Error: {msg}"),
49            XmlError::ValidationError(msg) => write!(f, "XML Validation Error: {msg}"),
50        }
51    }
52}
53
54impl Error for XmlError {}
55
56/// Serialize any MX message to complete XML with envelope
57pub fn to_mx_xml<H, D>(
58    message: D,
59    header: H,
60    message_type: &str,
61    config: Option<XmlConfig>,
62) -> Result<String, XmlError>
63where
64    H: Serialize,
65    D: Serialize,
66{
67    let config = config.unwrap_or_default();
68
69    // Determine the namespace based on message type
70    let document_namespace = get_namespace_for_message_type(message_type);
71
72    // Create the envelope
73    let envelope = MxEnvelope::new(header, message, document_namespace);
74
75    // Use custom XML writer for proper formatting
76    if config.pretty_print {
77        format_mx_xml(&envelope, &config)
78    } else {
79        // Use quick-xml for compact output
80        xml_to_string(&envelope).map_err(|e| XmlError::SerializationError(e.to_string()))
81    }
82}
83
84/// Parse complete MX XML with envelope
85pub fn from_mx_xml<H, D>(xml: &str) -> Result<MxEnvelope<H, D>, XmlError>
86where
87    H: for<'de> Deserialize<'de>,
88    D: for<'de> Deserialize<'de>,
89{
90    xml_from_str(xml).map_err(|e| XmlError::DeserializationError(e.to_string()))
91}
92
93/// Format MX XML with proper indentation and structure
94fn format_mx_xml<H, D>(envelope: &MxEnvelope<H, D>, config: &XmlConfig) -> Result<String, XmlError>
95where
96    H: Serialize,
97    D: Serialize,
98{
99    let mut writer = Writer::new_with_indent(Cursor::new(Vec::new()), b' ', config.indent.len());
100
101    // Write XML declaration
102    if config.include_declaration {
103        writer
104            .write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
105            .map_err(|e| XmlError::SerializationError(e.to_string()))?;
106    }
107
108    // Start envelope element without namespace (just a wrapper)
109    let envelope_elem = BytesStart::new("Envelope");
110    writer
111        .write_event(Event::Start(envelope_elem))
112        .map_err(|e| XmlError::SerializationError(e.to_string()))?;
113
114    // Write AppHdr with its namespace
115    let mut app_hdr_elem = BytesStart::new("AppHdr");
116    app_hdr_elem.push_attribute(("xmlns", "urn:iso:std:iso:20022:tech:xsd:head.001.001.02"));
117
118    writer
119        .write_event(Event::Start(app_hdr_elem))
120        .map_err(|e| XmlError::SerializationError(e.to_string()))?;
121
122    // Serialize the AppHdr content (without the wrapper element)
123    let app_hdr_xml = xml_to_string(&envelope.app_hdr)
124        .map_err(|e| XmlError::SerializationError(e.to_string()))?;
125
126    // Extract just the inner content (remove XML declaration and AppHdr wrapper tags)
127    let app_hdr_xml = app_hdr_xml
128        .trim_start_matches("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
129        .trim();
130
131    // Remove the opening and closing AppHdr tags to get just the content
132    let app_hdr_inner = if app_hdr_xml.starts_with("<AppHdr>") {
133        app_hdr_xml
134            .trim_start_matches("<AppHdr>")
135            .trim_end_matches("</AppHdr>")
136    } else if app_hdr_xml.starts_with("<AppHdr") {
137        // Handle case where AppHdr might have attributes
138        if let Some(pos) = app_hdr_xml.find('>') {
139            let content = &app_hdr_xml[pos + 1..];
140            content.trim_end_matches("</AppHdr>")
141        } else {
142            app_hdr_xml
143        }
144    } else {
145        app_hdr_xml
146    };
147
148    writer
149        .write_event(Event::Text(BytesText::from_escaped(app_hdr_inner)))
150        .map_err(|e| XmlError::SerializationError(e.to_string()))?;
151
152    // Close AppHdr
153    writer
154        .write_event(Event::End(BytesEnd::new("AppHdr")))
155        .map_err(|e| XmlError::SerializationError(e.to_string()))?;
156
157    // Write Document with its namespace
158    let mut doc_elem = BytesStart::new("Document");
159    if let Some(ref xmlns) = envelope.document.xmlns {
160        doc_elem.push_attribute(("xmlns", xmlns.as_str()));
161    }
162
163    writer
164        .write_event(Event::Start(doc_elem))
165        .map_err(|e| XmlError::SerializationError(e.to_string()))?;
166
167    // Write the actual message content
168    let message_xml = xml_to_string(&envelope.document.message)
169        .map_err(|e| XmlError::SerializationError(e.to_string()))?;
170
171    // Remove the XML declaration from the inner serialization if present
172    let message_xml = message_xml
173        .trim_start_matches("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
174        .trim();
175
176    writer
177        .write_event(Event::Text(BytesText::from_escaped(message_xml)))
178        .map_err(|e| XmlError::SerializationError(e.to_string()))?;
179
180    // Close Document
181    writer
182        .write_event(Event::End(BytesEnd::new("Document")))
183        .map_err(|e| XmlError::SerializationError(e.to_string()))?;
184
185    // Close Envelope
186    writer
187        .write_event(Event::End(BytesEnd::new("Envelope")))
188        .map_err(|e| XmlError::SerializationError(e.to_string()))?;
189
190    let result = writer.into_inner().into_inner();
191    String::from_utf8(result).map_err(|e| XmlError::SerializationError(e.to_string()))
192}
193
194/// Message type to namespace mapping
195/// Format: (short_form, full_form, namespace)
196static NAMESPACE_MAPPINGS: &[(&str, &str, &str)] = &[
197    (
198        "pacs.008",
199        "pacs.008.001.08",
200        "urn:iso:std:iso:20022:tech:xsd:pacs.008.001.08",
201    ),
202    (
203        "pacs.009",
204        "pacs.009.001.08",
205        "urn:iso:std:iso:20022:tech:xsd:pacs.009.001.08",
206    ),
207    (
208        "pacs.003",
209        "pacs.003.001.08",
210        "urn:iso:std:iso:20022:tech:xsd:pacs.003.001.08",
211    ),
212    (
213        "pacs.004",
214        "pacs.004.001.09",
215        "urn:iso:std:iso:20022:tech:xsd:pacs.004.001.09",
216    ),
217    (
218        "pacs.002",
219        "pacs.002.001.10",
220        "urn:iso:std:iso:20022:tech:xsd:pacs.002.001.10",
221    ),
222    (
223        "pacs.010",
224        "pacs.010.001.03",
225        "urn:iso:std:iso:20022:tech:xsd:pacs.010.001.03",
226    ),
227    (
228        "pain.001",
229        "pain.001.001.09",
230        "urn:iso:std:iso:20022:tech:xsd:pain.001.001.09",
231    ),
232    (
233        "pain.002",
234        "pain.002.001.10",
235        "urn:iso:std:iso:20022:tech:xsd:pain.002.001.10",
236    ),
237    (
238        "pain.008",
239        "pain.008.001.08",
240        "urn:iso:std:iso:20022:tech:xsd:pain.008.001.08",
241    ),
242    (
243        "camt.025",
244        "camt.025.001.08",
245        "urn:iso:std:iso:20022:tech:xsd:camt.025.001.08",
246    ),
247    (
248        "camt.027",
249        "camt.027.001.07",
250        "urn:iso:std:iso:20022:tech:xsd:camt.027.001.07",
251    ),
252    (
253        "camt.029",
254        "camt.029.001.09",
255        "urn:iso:std:iso:20022:tech:xsd:camt.029.001.09",
256    ),
257    (
258        "camt.052",
259        "camt.052.001.08",
260        "urn:iso:std:iso:20022:tech:xsd:camt.052.001.08",
261    ),
262    (
263        "camt.053",
264        "camt.053.001.08",
265        "urn:iso:std:iso:20022:tech:xsd:camt.053.001.08",
266    ),
267    (
268        "camt.054",
269        "camt.054.001.08",
270        "urn:iso:std:iso:20022:tech:xsd:camt.054.001.08",
271    ),
272    (
273        "camt.056",
274        "camt.056.001.08",
275        "urn:iso:std:iso:20022:tech:xsd:camt.056.001.08",
276    ),
277    (
278        "camt.057",
279        "camt.057.001.06",
280        "urn:iso:std:iso:20022:tech:xsd:camt.057.001.06",
281    ),
282    (
283        "camt.060",
284        "camt.060.001.05",
285        "urn:iso:std:iso:20022:tech:xsd:camt.060.001.05",
286    ),
287    (
288        "camt.107",
289        "camt.107.001.01",
290        "urn:iso:std:iso:20022:tech:xsd:camt.107.001.01",
291    ),
292    (
293        "camt.108",
294        "camt.108.001.01",
295        "urn:iso:std:iso:20022:tech:xsd:camt.108.001.01",
296    ),
297    (
298        "camt.109",
299        "camt.109.001.01",
300        "urn:iso:std:iso:20022:tech:xsd:camt.109.001.01",
301    ),
302];
303
304/// Get the appropriate namespace for a message type
305fn get_namespace_for_message_type(message_type: &str) -> String {
306    // Look up in the static mapping
307    for (short_form, full_form, namespace) in NAMESPACE_MAPPINGS {
308        if message_type == *short_form || message_type == *full_form {
309            return namespace.to_string();
310        }
311    }
312
313    // Default fallback: construct namespace from message type
314    format!("urn:iso:std:iso:20022:tech:xsd:{}", message_type)
315}
316
317/// Helper function to create XML for a specific message type
318pub fn create_pacs008_xml<D: Serialize>(
319    message: D,
320    from_bic: String,
321    to_bic: String,
322    business_msg_id: String,
323) -> Result<String, XmlError> {
324    use crate::header::bah_pacs_008_001_08::{
325        BranchAndFinancialInstitutionIdentification62, BusinessApplicationHeaderV02,
326        FinancialInstitutionIdentification182, Party44Choice1,
327    };
328
329    let header = BusinessApplicationHeaderV02 {
330        char_set: None,
331        fr: Party44Choice1 {
332            fi_id: Some(BranchAndFinancialInstitutionIdentification62 {
333                fin_instn_id: FinancialInstitutionIdentification182 {
334                    bicfi: from_bic,
335                    clr_sys_mmb_id: None,
336                    lei: None,
337                },
338            }),
339        },
340        to: Party44Choice1 {
341            fi_id: Some(BranchAndFinancialInstitutionIdentification62 {
342                fin_instn_id: FinancialInstitutionIdentification182 {
343                    bicfi: to_bic,
344                    clr_sys_mmb_id: None,
345                    lei: None,
346                },
347            }),
348        },
349        biz_msg_idr: business_msg_id,
350        msg_def_idr: "pacs.008.001.08".to_string(),
351        biz_svc: "swift.ug".to_string(),
352        mkt_prctc: None,
353        cre_dt: chrono::Utc::now()
354            .format("%Y-%m-%dT%H:%M:%S%.3fZ")
355            .to_string(),
356        cpy_dplct: None,
357        pssbl_dplct: None,
358        prty: None,
359        rltd: None,
360    };
361
362    to_mx_xml(message, header, "pacs.008", None)
363}
364
365/// Helper function to create XML for pain.001 message
366pub fn create_pain001_xml<D: Serialize>(
367    message: D,
368    from_bic: String,
369    to_bic: String,
370    business_msg_id: String,
371) -> Result<String, XmlError> {
372    use crate::header::bah_pain_001_001_09::{
373        BranchAndFinancialInstitutionIdentification64, BusinessApplicationHeaderV02,
374        FinancialInstitutionIdentification183, Party44Choice1,
375    };
376
377    let header = BusinessApplicationHeaderV02 {
378        char_set: None,
379        fr: Party44Choice1 {
380            fi_id: Some(BranchAndFinancialInstitutionIdentification64 {
381                fin_instn_id: FinancialInstitutionIdentification183 {
382                    bicfi: from_bic,
383                    clr_sys_mmb_id: None,
384                    lei: None,
385                },
386            }),
387        },
388        to: Party44Choice1 {
389            fi_id: Some(BranchAndFinancialInstitutionIdentification64 {
390                fin_instn_id: FinancialInstitutionIdentification183 {
391                    bicfi: to_bic,
392                    clr_sys_mmb_id: None,
393                    lei: None,
394                },
395            }),
396        },
397        biz_msg_idr: business_msg_id,
398        msg_def_idr: "pain.001.001.09".to_string(),
399        biz_svc: "swift.ug".to_string(),
400        mkt_prctc: None,
401        cre_dt: chrono::Utc::now()
402            .format("%Y-%m-%dT%H:%M:%S%.3fZ")
403            .to_string(),
404        cpy_dplct: None,
405        pssbl_dplct: None,
406        prty: None,
407        rltd: None,
408    };
409
410    to_mx_xml(message, header, "pain.001", None)
411}
412
413/// Helper function to create XML for camt.053 message
414pub fn create_camt053_xml<D: Serialize>(
415    message: D,
416    from_bic: String,
417    to_bic: String,
418    business_msg_id: String,
419) -> Result<String, XmlError> {
420    use crate::header::bah_camt_053_001_08::{
421        BranchAndFinancialInstitutionIdentification63, BusinessApplicationHeaderV02,
422        FinancialInstitutionIdentification182, Party44Choice1,
423    };
424
425    let header = BusinessApplicationHeaderV02 {
426        char_set: None,
427        fr: Party44Choice1 {
428            fi_id: Some(BranchAndFinancialInstitutionIdentification63 {
429                fin_instn_id: FinancialInstitutionIdentification182 {
430                    bicfi: from_bic,
431                    clr_sys_mmb_id: None,
432                    lei: None,
433                },
434            }),
435        },
436        to: Party44Choice1 {
437            fi_id: Some(BranchAndFinancialInstitutionIdentification63 {
438                fin_instn_id: FinancialInstitutionIdentification182 {
439                    bicfi: to_bic,
440                    clr_sys_mmb_id: None,
441                    lei: None,
442                },
443            }),
444        },
445        biz_msg_idr: business_msg_id,
446        msg_def_idr: "camt.053.001.08".to_string(),
447        biz_svc: "swift.ug".to_string(),
448        mkt_prctc: None,
449        cre_dt: chrono::Utc::now()
450            .format("%Y-%m-%dT%H:%M:%S%.3fZ")
451            .to_string(),
452        cpy_dplct: None,
453        pssbl_dplct: None,
454        prty: None,
455        rltd: None,
456    };
457
458    to_mx_xml(message, header, "camt.053", None)
459}
460
461/// Convert JSON Value to MX XML using typed document structs
462/// This function deserializes JSON into the appropriate typed struct,
463/// then uses quick-xml's serializer which correctly handles arrays and attributes.
464///
465/// This replaces the old custom json_value_to_xml implementation which had issues with:
466/// - Arrays being concatenated without separation
467/// - Complex nested structures not handled properly
468pub fn json_to_typed_xml(
469    json_data: &serde_json::Value,
470    message_type: &str,
471) -> Result<String, XmlError> {
472    use crate::document::*;
473
474    // Extract the Document content from the JSON
475    let document_json = json_data.get("Document").unwrap_or(json_data);
476
477    // Get the inner message element (e.g., FIToFICstmrCdtTrf)
478    let inner_json = if let Some(obj) = document_json.as_object() {
479        // Get the first (and usually only) key from Document
480        if let Some((_key, value)) = obj.iter().next() {
481            value
482        } else {
483            document_json
484        }
485    } else {
486        document_json
487    };
488
489    // Match on message type and deserialize into appropriate typed struct
490    let xml = match message_type {
491        "pacs.008" => {
492            let mx_struct = serde_json::from_value::<
493                pacs_008_001_08::FIToFICustomerCreditTransferV08,
494            >(inner_json.clone())
495            .map_err(|e| {
496                XmlError::DeserializationError(format!("Failed to parse pacs.008: {}", e))
497            })?;
498            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
499        }
500        "pacs.009" => {
501            let mx_struct = serde_json::from_value::<
502                pacs_009_001_08::FinancialInstitutionCreditTransferV08,
503            >(inner_json.clone())
504            .map_err(|e| {
505                XmlError::DeserializationError(format!("Failed to parse pacs.009: {}", e))
506            })?;
507            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
508        }
509        "pacs.003" => {
510            let mx_struct =
511                serde_json::from_value::<pacs_003_001_08::FIToFICustomerDirectDebitV08>(
512                    inner_json.clone(),
513                )
514                .map_err(|e| {
515                    XmlError::DeserializationError(format!("Failed to parse pacs.003: {}", e))
516                })?;
517            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
518        }
519        "pacs.004" => {
520            let mx_struct =
521                serde_json::from_value::<pacs_004_001_09::PaymentReturnV09>(inner_json.clone())
522                    .map_err(|e| {
523                        XmlError::DeserializationError(format!("Failed to parse pacs.004: {}", e))
524                    })?;
525            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
526        }
527        "pacs.010" => {
528            let mx_struct = serde_json::from_value::<
529                pacs_010_001_03::FinancialInstitutionDirectDebitV03,
530            >(inner_json.clone())
531            .map_err(|e| {
532                XmlError::DeserializationError(format!("Failed to parse pacs.010: {}", e))
533            })?;
534            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
535        }
536        "pacs.002" => {
537            let mx_struct =
538                serde_json::from_value::<pacs_002_001_10::FIToFIPaymentStatusReportV10>(
539                    inner_json.clone(),
540                )
541                .map_err(|e| {
542                    XmlError::DeserializationError(format!("Failed to parse pacs.002: {}", e))
543                })?;
544            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
545        }
546        "pain.001" => {
547            let mx_struct = serde_json::from_value::<
548                pain_001_001_09::CustomerCreditTransferInitiationV09,
549            >(inner_json.clone())
550            .map_err(|e| {
551                XmlError::DeserializationError(format!("Failed to parse pain.001: {}", e))
552            })?;
553            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
554        }
555        "pain.008" => {
556            let mx_struct = serde_json::from_value::<
557                pain_008_001_08::CustomerDirectDebitInitiationV08,
558            >(inner_json.clone())
559            .map_err(|e| {
560                XmlError::DeserializationError(format!("Failed to parse pain.008: {}", e))
561            })?;
562            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
563        }
564        "camt.025" => {
565            let mx_struct =
566                serde_json::from_value::<camt_025_001_08::ReceiptV08>(inner_json.clone()).map_err(
567                    |e| XmlError::DeserializationError(format!("Failed to parse camt.025: {}", e)),
568                )?;
569            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
570        }
571        "camt.029" => {
572            let mx_struct =
573                serde_json::from_value::<camt_029_001_09::ResolutionOfInvestigationV09>(
574                    inner_json.clone(),
575                )
576                .map_err(|e| {
577                    XmlError::DeserializationError(format!("Failed to parse camt.029: {}", e))
578                })?;
579            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
580        }
581        "camt.052" => {
582            let mx_struct =
583                serde_json::from_value::<camt_052_001_08::BankToCustomerAccountReportV08>(
584                    inner_json.clone(),
585                )
586                .map_err(|e| {
587                    XmlError::DeserializationError(format!("Failed to parse camt.052: {}", e))
588                })?;
589            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
590        }
591        "camt.053" => {
592            let mx_struct = serde_json::from_value::<camt_053_001_08::BankToCustomerStatementV08>(
593                inner_json.clone(),
594            )
595            .map_err(|e| {
596                XmlError::DeserializationError(format!("Failed to parse camt.053: {}", e))
597            })?;
598            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
599        }
600        "camt.054" => {
601            let mx_struct = serde_json::from_value::<
602                camt_054_001_08::BankToCustomerDebitCreditNotificationV08,
603            >(inner_json.clone())
604            .map_err(|e| {
605                XmlError::DeserializationError(format!("Failed to parse camt.054: {}", e))
606            })?;
607            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
608        }
609        "camt.056" => {
610            let mx_struct = serde_json::from_value::<
611                camt_056_001_08::FIToFIPaymentCancellationRequestV08,
612            >(inner_json.clone())
613            .map_err(|e| {
614                XmlError::DeserializationError(format!("Failed to parse camt.056: {}", e))
615            })?;
616            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
617        }
618        "camt.057" => {
619            let mx_struct = serde_json::from_value::<camt_057_001_06::NotificationToReceiveV06>(
620                inner_json.clone(),
621            )
622            .map_err(|e| {
623                XmlError::DeserializationError(format!("Failed to parse camt.057: {}", e))
624            })?;
625            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
626        }
627        "camt.060" => {
628            let mx_struct = serde_json::from_value::<camt_060_001_05::AccountReportingRequestV05>(
629                inner_json.clone(),
630            )
631            .map_err(|e| {
632                XmlError::DeserializationError(format!("Failed to parse camt.060: {}", e))
633            })?;
634            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
635        }
636        "camt.107" => {
637            let mx_struct = serde_json::from_value::<
638                camt_107_001_01::ChequePresentmentNotificationV01,
639            >(inner_json.clone())
640            .map_err(|e| {
641                XmlError::DeserializationError(format!("Failed to parse camt.107: {}", e))
642            })?;
643            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
644        }
645        "camt.108" => {
646            let mx_struct = serde_json::from_value::<
647                camt_108_001_01::ChequeCancellationOrStopRequestV01,
648            >(inner_json.clone())
649            .map_err(|e| {
650                XmlError::DeserializationError(format!("Failed to parse camt.108: {}", e))
651            })?;
652            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
653        }
654        "camt.109" => {
655            let mx_struct = serde_json::from_value::<
656                camt_109_001_01::ChequeCancellationOrStopReportV01,
657            >(inner_json.clone())
658            .map_err(|e| {
659                XmlError::DeserializationError(format!("Failed to parse camt.109: {}", e))
660            })?;
661            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
662        }
663        "admi.024" => {
664            let mx_struct = serde_json::from_value::<
665                admi_024_001_01::NotificationOfCorrespondenceV01,
666            >(inner_json.clone())
667            .map_err(|e| {
668                XmlError::DeserializationError(format!("Failed to parse admi.024: {}", e))
669            })?;
670            xml_to_string(&mx_struct).map_err(|e| XmlError::SerializationError(e.to_string()))?
671        }
672        _ => {
673            return Err(XmlError::SerializationError(format!(
674                "Unsupported message type: {}",
675                message_type
676            )));
677        }
678    };
679
680    // Wrap in Document element and add XML declaration
681    Ok(format!(
682        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Document>\n{}</Document>\n",
683        xml.trim_start_matches("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
684            .trim()
685    ))
686}
687
688/// Deserialize XML string directly to typed struct for validation (Document only)
689/// This validates the XML structure and content by attempting deserialization
690pub fn from_mx_xml_str(xml: &str, message_type: &str) -> Result<(), XmlError> {
691    use crate::document::*;
692
693    // Extract the inner XML content (strip Document wrapper and XML declaration)
694    let inner_xml = xml
695        .trim()
696        .trim_start_matches("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
697        .trim()
698        .trim_start_matches("<Document>")
699        .trim_end_matches("</Document>")
700        .trim();
701
702    // Try to deserialize to the appropriate typed struct
703    match message_type {
704        "pacs.008" => {
705            xml_from_str::<pacs_008_001_08::FIToFICustomerCreditTransferV08>(inner_xml).map_err(
706                |e| XmlError::DeserializationError(format!("Failed to parse pacs.008: {}", e)),
707            )?;
708        }
709        "pacs.002" => {
710            xml_from_str::<pacs_002_001_10::FIToFIPaymentStatusReportV10>(inner_xml).map_err(
711                |e| XmlError::DeserializationError(format!("Failed to parse pacs.002: {}", e)),
712            )?;
713        }
714        "pacs.009" => {
715            xml_from_str::<pacs_009_001_08::FinancialInstitutionCreditTransferV08>(inner_xml)
716                .map_err(|e| {
717                    XmlError::DeserializationError(format!("Failed to parse pacs.009: {}", e))
718                })?;
719        }
720        "pacs.004" => {
721            xml_from_str::<pacs_004_001_09::PaymentReturnV09>(inner_xml).map_err(|e| {
722                XmlError::DeserializationError(format!("Failed to parse pacs.004: {}", e))
723            })?;
724        }
725        "pacs.003" => {
726            xml_from_str::<pacs_003_001_08::FIToFICustomerDirectDebitV08>(inner_xml).map_err(
727                |e| XmlError::DeserializationError(format!("Failed to parse pacs.003: {}", e)),
728            )?;
729        }
730        "pacs.010" => {
731            xml_from_str::<pacs_010_001_03::FinancialInstitutionDirectDebitV03>(inner_xml)
732                .map_err(|e| {
733                    XmlError::DeserializationError(format!("Failed to parse pacs.010: {}", e))
734                })?;
735        }
736        "camt.052" => {
737            xml_from_str::<camt_052_001_08::BankToCustomerAccountReportV08>(inner_xml).map_err(
738                |e| XmlError::DeserializationError(format!("Failed to parse camt.052: {}", e)),
739            )?;
740        }
741        "camt.053" => {
742            xml_from_str::<camt_053_001_08::BankToCustomerStatementV08>(inner_xml).map_err(
743                |e| XmlError::DeserializationError(format!("Failed to parse camt.053: {}", e)),
744            )?;
745        }
746        "camt.054" => {
747            xml_from_str::<camt_054_001_08::BankToCustomerDebitCreditNotificationV08>(inner_xml)
748                .map_err(|e| {
749                    XmlError::DeserializationError(format!("Failed to parse camt.054: {}", e))
750                })?;
751        }
752        "camt.060" => {
753            xml_from_str::<camt_060_001_05::AccountReportingRequestV05>(inner_xml).map_err(
754                |e| XmlError::DeserializationError(format!("Failed to parse camt.060: {}", e)),
755            )?;
756        }
757        "camt.029" => {
758            xml_from_str::<camt_029_001_09::ResolutionOfInvestigationV09>(inner_xml).map_err(
759                |e| XmlError::DeserializationError(format!("Failed to parse camt.029: {}", e)),
760            )?;
761        }
762        "camt.025" => {
763            xml_from_str::<camt_025_001_08::ReceiptV08>(inner_xml).map_err(|e| {
764                XmlError::DeserializationError(format!("Failed to parse camt.025: {}", e))
765            })?;
766        }
767        "camt.056" => {
768            xml_from_str::<camt_056_001_08::FIToFIPaymentCancellationRequestV08>(inner_xml)
769                .map_err(|e| {
770                    XmlError::DeserializationError(format!("Failed to parse camt.056: {}", e))
771                })?;
772        }
773        "camt.057" => {
774            xml_from_str::<camt_057_001_06::NotificationToReceiveV06>(inner_xml).map_err(|e| {
775                XmlError::DeserializationError(format!("Failed to parse camt.057: {}", e))
776            })?;
777        }
778        "camt.107" => {
779            xml_from_str::<camt_107_001_01::ChequePresentmentNotificationV01>(inner_xml).map_err(
780                |e| XmlError::DeserializationError(format!("Failed to parse camt.107: {}", e)),
781            )?;
782        }
783        "camt.108" => {
784            xml_from_str::<camt_108_001_01::ChequeCancellationOrStopRequestV01>(inner_xml)
785                .map_err(|e| {
786                    XmlError::DeserializationError(format!("Failed to parse camt.108: {}", e))
787                })?;
788        }
789        "camt.109" => {
790            xml_from_str::<camt_109_001_01::ChequeCancellationOrStopReportV01>(inner_xml).map_err(
791                |e| XmlError::DeserializationError(format!("Failed to parse camt.109: {}", e)),
792            )?;
793        }
794        "pain.001" => {
795            xml_from_str::<pain_001_001_09::CustomerCreditTransferInitiationV09>(inner_xml)
796                .map_err(|e| {
797                    XmlError::DeserializationError(format!("Failed to parse pain.001: {}", e))
798                })?;
799        }
800        "pain.002" => {
801            xml_from_str::<pain_002_001_10::CustomerPaymentStatusReportV10>(inner_xml).map_err(
802                |e| XmlError::DeserializationError(format!("Failed to parse pain.002: {}", e)),
803            )?;
804        }
805        "pain.008" => {
806            xml_from_str::<pain_008_001_08::CustomerDirectDebitInitiationV08>(inner_xml).map_err(
807                |e| XmlError::DeserializationError(format!("Failed to parse pain.008: {}", e)),
808            )?;
809        }
810        "admi.024" => {
811            xml_from_str::<admi_024_001_01::NotificationOfCorrespondenceV01>(inner_xml).map_err(
812                |e| XmlError::DeserializationError(format!("Failed to parse admi.024: {}", e)),
813            )?;
814        }
815        _ => {
816            return Err(XmlError::DeserializationError(format!(
817                "Unsupported message type for validation: {}",
818                message_type
819            )));
820        }
821    }
822
823    Ok(())
824}
825
826/// Deserialize complete MX XML envelope (with AppHdr) to typed structs for validation
827/// This validates both the header and document structure
828pub fn from_mx_xml_envelope_str(xml: &str, message_type: &str) -> Result<(), XmlError> {
829    use crate::document::*;
830    use crate::header::bah_pacs_008_001_08::BusinessApplicationHeaderV02;
831
832    // Check if XML contains AppHdr (full envelope) or just Document
833    let has_envelope = xml.contains("<AppHdr") || xml.contains("<Envelope");
834
835    if !has_envelope {
836        // No envelope, just validate the Document part
837        return from_mx_xml_str(xml, message_type);
838    }
839
840    // Parse as full envelope with AppHdr
841    match message_type {
842        "pacs.008" => {
843            from_mx_xml::<
844                BusinessApplicationHeaderV02,
845                pacs_008_001_08::FIToFICustomerCreditTransferV08,
846            >(xml)
847            .map_err(|e| {
848                XmlError::DeserializationError(format!("Failed to parse pacs.008 envelope: {}", e))
849            })?;
850        }
851        "pacs.002" => {
852            from_mx_xml::<
853                BusinessApplicationHeaderV02,
854                pacs_002_001_10::FIToFIPaymentStatusReportV10,
855            >(xml)
856            .map_err(|e| {
857                XmlError::DeserializationError(format!("Failed to parse pacs.002 envelope: {}", e))
858            })?;
859        }
860        "pacs.009" => {
861            from_mx_xml::<
862                BusinessApplicationHeaderV02,
863                pacs_009_001_08::FinancialInstitutionCreditTransferV08,
864            >(xml)
865            .map_err(|e| {
866                XmlError::DeserializationError(format!("Failed to parse pacs.009 envelope: {}", e))
867            })?;
868        }
869        "pacs.004" => {
870            from_mx_xml::<BusinessApplicationHeaderV02, pacs_004_001_09::PaymentReturnV09>(xml)
871                .map_err(|e| {
872                    XmlError::DeserializationError(format!(
873                        "Failed to parse pacs.004 envelope: {}",
874                        e
875                    ))
876                })?;
877        }
878        "pacs.003" => {
879            from_mx_xml::<
880                BusinessApplicationHeaderV02,
881                pacs_003_001_08::FIToFICustomerDirectDebitV08,
882            >(xml)
883            .map_err(|e| {
884                XmlError::DeserializationError(format!("Failed to parse pacs.003 envelope: {}", e))
885            })?;
886        }
887        "pacs.010" => {
888            from_mx_xml::<
889                BusinessApplicationHeaderV02,
890                pacs_010_001_03::FinancialInstitutionDirectDebitV03,
891            >(xml)
892            .map_err(|e| {
893                XmlError::DeserializationError(format!("Failed to parse pacs.010 envelope: {}", e))
894            })?;
895        }
896        "camt.052" => {
897            from_mx_xml::<
898                BusinessApplicationHeaderV02,
899                camt_052_001_08::BankToCustomerAccountReportV08,
900            >(xml)
901            .map_err(|e| {
902                XmlError::DeserializationError(format!("Failed to parse camt.052 envelope: {}", e))
903            })?;
904        }
905        "camt.053" => {
906            from_mx_xml::<
907                BusinessApplicationHeaderV02,
908                camt_053_001_08::BankToCustomerStatementV08,
909            >(xml)
910            .map_err(|e| {
911                XmlError::DeserializationError(format!(
912                    "Failed to parse camt.053 envelope: {}",
913                    e
914                ))
915            })?;
916        }
917        "camt.054" => {
918            from_mx_xml::<
919                BusinessApplicationHeaderV02,
920                camt_054_001_08::BankToCustomerDebitCreditNotificationV08,
921            >(xml)
922            .map_err(|e| {
923                XmlError::DeserializationError(format!("Failed to parse camt.054 envelope: {}", e))
924            })?;
925        }
926        "camt.060" => {
927            from_mx_xml::<
928                BusinessApplicationHeaderV02,
929                camt_060_001_05::AccountReportingRequestV05,
930            >(xml)
931            .map_err(|e| {
932                XmlError::DeserializationError(format!(
933                    "Failed to parse camt.060 envelope: {}",
934                    e
935                ))
936            })?;
937        }
938        "camt.029" => {
939            from_mx_xml::<
940                BusinessApplicationHeaderV02,
941                camt_029_001_09::ResolutionOfInvestigationV09,
942            >(xml)
943            .map_err(|e| {
944                XmlError::DeserializationError(format!("Failed to parse camt.029 envelope: {}", e))
945            })?;
946        }
947        "camt.025" => {
948            from_mx_xml::<BusinessApplicationHeaderV02, camt_025_001_08::ReceiptV08>(xml).map_err(
949                |e| {
950                    XmlError::DeserializationError(format!(
951                        "Failed to parse camt.025 envelope: {}",
952                        e
953                    ))
954                },
955            )?;
956        }
957        "camt.056" => {
958            from_mx_xml::<
959                BusinessApplicationHeaderV02,
960                camt_056_001_08::FIToFIPaymentCancellationRequestV08,
961            >(xml)
962            .map_err(|e| {
963                XmlError::DeserializationError(format!("Failed to parse camt.056 envelope: {}", e))
964            })?;
965        }
966        "camt.057" => {
967            from_mx_xml::<BusinessApplicationHeaderV02, camt_057_001_06::NotificationToReceiveV06>(
968                xml,
969            )
970            .map_err(|e| {
971                XmlError::DeserializationError(format!("Failed to parse camt.057 envelope: {}", e))
972            })?;
973        }
974        "camt.107" => {
975            from_mx_xml::<
976                BusinessApplicationHeaderV02,
977                camt_107_001_01::ChequePresentmentNotificationV01,
978            >(xml)
979            .map_err(|e| {
980                XmlError::DeserializationError(format!("Failed to parse camt.107 envelope: {}", e))
981            })?;
982        }
983        "camt.108" => {
984            from_mx_xml::<
985                BusinessApplicationHeaderV02,
986                camt_108_001_01::ChequeCancellationOrStopRequestV01,
987            >(xml)
988            .map_err(|e| {
989                XmlError::DeserializationError(format!("Failed to parse camt.108 envelope: {}", e))
990            })?;
991        }
992        "camt.109" => {
993            from_mx_xml::<
994                BusinessApplicationHeaderV02,
995                camt_109_001_01::ChequeCancellationOrStopReportV01,
996            >(xml)
997            .map_err(|e| {
998                XmlError::DeserializationError(format!("Failed to parse camt.109 envelope: {}", e))
999            })?;
1000        }
1001        "pain.001" => {
1002            from_mx_xml::<
1003                BusinessApplicationHeaderV02,
1004                pain_001_001_09::CustomerCreditTransferInitiationV09,
1005            >(xml)
1006            .map_err(|e| {
1007                XmlError::DeserializationError(format!("Failed to parse pain.001 envelope: {}", e))
1008            })?;
1009        }
1010        "pain.002" => {
1011            from_mx_xml::<
1012                BusinessApplicationHeaderV02,
1013                pain_002_001_10::CustomerPaymentStatusReportV10,
1014            >(xml)
1015            .map_err(|e| {
1016                XmlError::DeserializationError(format!("Failed to parse pain.002 envelope: {}", e))
1017            })?;
1018        }
1019        "pain.008" => {
1020            from_mx_xml::<
1021                BusinessApplicationHeaderV02,
1022                pain_008_001_08::CustomerDirectDebitInitiationV08,
1023            >(xml)
1024            .map_err(|e| {
1025                XmlError::DeserializationError(format!("Failed to parse pain.008 envelope: {}", e))
1026            })?;
1027        }
1028        "admi.024" => {
1029            from_mx_xml::<
1030                BusinessApplicationHeaderV02,
1031                admi_024_001_01::NotificationOfCorrespondenceV01,
1032            >(xml)
1033            .map_err(|e| {
1034                XmlError::DeserializationError(format!("Failed to parse admi.024 envelope: {}", e))
1035            })?;
1036        }
1037        _ => {
1038            return Err(XmlError::DeserializationError(format!(
1039                "Unsupported message type for envelope validation: {}",
1040                message_type
1041            )));
1042        }
1043    }
1044
1045    Ok(())
1046}
1047
1048/// Deprecated: Use json_to_typed_xml instead
1049/// This function is kept for backward compatibility but now delegates to json_to_typed_xml
1050#[deprecated(
1051    since = "3.2.0",
1052    note = "Use json_to_typed_xml instead for correct array and attribute handling"
1053)]
1054pub fn to_mx_xml_with_config(
1055    json_data: &serde_json::Value,
1056    _config: &XmlConfig,
1057) -> Result<String, XmlError> {
1058    // Try to extract message type from the JSON data
1059    let message_type = json_data
1060        .get("message_type")
1061        .and_then(|v| v.as_str())
1062        .ok_or_else(|| {
1063            XmlError::SerializationError("Missing message_type field in JSON data".to_string())
1064        })?;
1065
1066    json_to_typed_xml(json_data, message_type)
1067}
1068
1069/// Helper function to get the Document element name for a message type
1070fn get_document_element_name(message_type: &str) -> &'static str {
1071    match message_type {
1072        "pacs.008" => "FIToFICstmrCdtTrf",
1073        "pacs.009" => "FIToFICstmrCdtTrf",
1074        "pacs.003" => "FIToFICstmrDrctDbt",
1075        "pacs.004" => "PmtRtr",
1076        "pacs.010" => "FIDrctDbt",
1077        "pacs.002" => "FIToFIPmtStsRpt",
1078        "pain.001" => "CstmrCdtTrfInitn",
1079        "pain.008" => "CstmrDrctDbtInitn",
1080        "camt.025" => "Rct",
1081        "camt.029" => "RsltnOfInvstgtn",
1082        "camt.052" => "BkToCstmrAcctRpt",
1083        "camt.053" => "BkToCstmrStmt",
1084        "camt.054" => "BkToCstmrDbtCdtNtfctn",
1085        "camt.056" => "FIToFIPmtCxlReq",
1086        "camt.057" => "NtfctnToRcv",
1087        "camt.060" => "AcctRptgReq",
1088        "camt.107" => "ChqPresntmntNtfctn",
1089        "camt.108" => "ChqCxlOrStopReq",
1090        "camt.109" => "ChqCxlOrStopRpt",
1091        "admi.024" => "NtfctnOfCrspdc",
1092        _ => "Unknown",
1093    }
1094}
1095
1096/// Parse XML to JSON using typed ISO20022 document structures
1097/// This is the correct way to parse XML that preserves arrays and complex structures
1098pub fn xml_to_json_via_document(
1099    xml: &str,
1100    message_type: &str,
1101) -> Result<serde_json::Value, XmlError> {
1102    use crate::document::*;
1103    use serde_json::json;
1104
1105    // Extract inner XML (strip Document wrapper and XML declaration)
1106    let inner_xml = xml
1107        .trim()
1108        .trim_start_matches("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
1109        .trim();
1110
1111    // Find the Document wrapper and extract inner content
1112    let inner_xml = if let Some(start_idx) = inner_xml.find("<Document") {
1113        if let Some(end_bracket) = inner_xml[start_idx..].find('>') {
1114            let content_start = start_idx + end_bracket + 1;
1115            if let Some(end_idx) = inner_xml.rfind("</Document>") {
1116                &inner_xml[content_start..end_idx]
1117            } else {
1118                inner_xml
1119            }
1120        } else {
1121            inner_xml
1122        }
1123    } else {
1124        inner_xml
1125    };
1126
1127    let inner_xml = inner_xml.trim();
1128
1129    // Deserialize XML to typed struct, then serialize to JSON
1130    let json_value = match message_type {
1131        "pacs.008" => {
1132            let doc = xml_from_str::<pacs_008_001_08::FIToFICustomerCreditTransferV08>(inner_xml)
1133                .map_err(|e| {
1134                XmlError::DeserializationError(format!("Failed to parse pacs.008: {}", e))
1135            })?;
1136            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1137        }
1138        "pacs.009" => {
1139            let doc =
1140                xml_from_str::<pacs_009_001_08::FinancialInstitutionCreditTransferV08>(inner_xml)
1141                    .map_err(|e| {
1142                    XmlError::DeserializationError(format!("Failed to parse pacs.009: {}", e))
1143                })?;
1144            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1145        }
1146        "pacs.003" => {
1147            let doc = xml_from_str::<pacs_003_001_08::FIToFICustomerDirectDebitV08>(inner_xml)
1148                .map_err(|e| {
1149                    XmlError::DeserializationError(format!("Failed to parse pacs.003: {}", e))
1150                })?;
1151            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1152        }
1153        "pacs.004" => {
1154            let doc =
1155                xml_from_str::<pacs_004_001_09::PaymentReturnV09>(inner_xml).map_err(|e| {
1156                    XmlError::DeserializationError(format!("Failed to parse pacs.004: {}", e))
1157                })?;
1158            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1159        }
1160        "pacs.010" => {
1161            let doc =
1162                xml_from_str::<pacs_010_001_03::FinancialInstitutionDirectDebitV03>(inner_xml)
1163                    .map_err(|e| {
1164                        XmlError::DeserializationError(format!("Failed to parse pacs.010: {}", e))
1165                    })?;
1166            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1167        }
1168        "pacs.002" => {
1169            let doc = xml_from_str::<pacs_002_001_10::FIToFIPaymentStatusReportV10>(inner_xml)
1170                .map_err(|e| {
1171                    XmlError::DeserializationError(format!("Failed to parse pacs.002: {}", e))
1172                })?;
1173            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1174        }
1175        "pain.001" => {
1176            let doc =
1177                xml_from_str::<pain_001_001_09::CustomerCreditTransferInitiationV09>(inner_xml)
1178                    .map_err(|e| {
1179                        XmlError::DeserializationError(format!("Failed to parse pain.001: {}", e))
1180                    })?;
1181            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1182        }
1183        "pain.008" => {
1184            let doc = xml_from_str::<pain_008_001_08::CustomerDirectDebitInitiationV08>(inner_xml)
1185                .map_err(|e| {
1186                    XmlError::DeserializationError(format!("Failed to parse pain.008: {}", e))
1187                })?;
1188            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1189        }
1190        "camt.025" => {
1191            let doc = xml_from_str::<camt_025_001_08::ReceiptV08>(inner_xml).map_err(|e| {
1192                XmlError::DeserializationError(format!("Failed to parse camt.025: {}", e))
1193            })?;
1194            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1195        }
1196        "camt.029" => {
1197            let doc = xml_from_str::<camt_029_001_09::ResolutionOfInvestigationV09>(inner_xml)
1198                .map_err(|e| {
1199                    XmlError::DeserializationError(format!("Failed to parse camt.029: {}", e))
1200                })?;
1201            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1202        }
1203        "camt.052" => {
1204            let doc = xml_from_str::<camt_052_001_08::BankToCustomerAccountReportV08>(inner_xml)
1205                .map_err(|e| {
1206                    XmlError::DeserializationError(format!("Failed to parse camt.052: {}", e))
1207                })?;
1208            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1209        }
1210        "camt.053" => {
1211            let doc = xml_from_str::<camt_053_001_08::BankToCustomerStatementV08>(inner_xml)
1212                .map_err(|e| {
1213                    XmlError::DeserializationError(format!("Failed to parse camt.053: {}", e))
1214                })?;
1215            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1216        }
1217        "camt.054" => {
1218            let doc = xml_from_str::<camt_054_001_08::BankToCustomerDebitCreditNotificationV08>(
1219                inner_xml,
1220            )
1221            .map_err(|e| {
1222                XmlError::DeserializationError(format!("Failed to parse camt.054: {}", e))
1223            })?;
1224            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1225        }
1226        "camt.056" => {
1227            let doc =
1228                xml_from_str::<camt_056_001_08::FIToFIPaymentCancellationRequestV08>(inner_xml)
1229                    .map_err(|e| {
1230                        XmlError::DeserializationError(format!("Failed to parse camt.056: {}", e))
1231                    })?;
1232            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1233        }
1234        "camt.057" => {
1235            let doc = xml_from_str::<camt_057_001_06::NotificationToReceiveV06>(inner_xml)
1236                .map_err(|e| {
1237                    XmlError::DeserializationError(format!("Failed to parse camt.057: {}", e))
1238                })?;
1239            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1240        }
1241        "camt.060" => {
1242            let doc = xml_from_str::<camt_060_001_05::AccountReportingRequestV05>(inner_xml)
1243                .map_err(|e| {
1244                    XmlError::DeserializationError(format!("Failed to parse camt.060: {}", e))
1245                })?;
1246            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1247        }
1248        "camt.107" => {
1249            let doc = xml_from_str::<camt_107_001_01::ChequePresentmentNotificationV01>(inner_xml)
1250                .map_err(|e| {
1251                    XmlError::DeserializationError(format!("Failed to parse camt.107: {}", e))
1252                })?;
1253            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1254        }
1255        "camt.108" => {
1256            let doc =
1257                xml_from_str::<camt_108_001_01::ChequeCancellationOrStopRequestV01>(inner_xml)
1258                    .map_err(|e| {
1259                        XmlError::DeserializationError(format!("Failed to parse camt.108: {}", e))
1260                    })?;
1261            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1262        }
1263        "camt.109" => {
1264            let doc = xml_from_str::<camt_109_001_01::ChequeCancellationOrStopReportV01>(inner_xml)
1265                .map_err(|e| {
1266                    XmlError::DeserializationError(format!("Failed to parse camt.109: {}", e))
1267                })?;
1268            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1269        }
1270        "admi.024" => {
1271            let doc = xml_from_str::<admi_024_001_01::NotificationOfCorrespondenceV01>(inner_xml)
1272                .map_err(|e| {
1273                XmlError::DeserializationError(format!("Failed to parse admi.024: {}", e))
1274            })?;
1275            serde_json::to_value(&doc).map_err(|e| XmlError::SerializationError(e.to_string()))?
1276        }
1277        _ => {
1278            return Err(XmlError::DeserializationError(format!(
1279                "Unsupported message type: {}",
1280                message_type
1281            )));
1282        }
1283    };
1284
1285    // Wrap in Document structure with correct element name
1286    let element_name = get_document_element_name(message_type);
1287    Ok(json!({
1288        "Document": {
1289            element_name: json_value
1290        }
1291    }))
1292}
1293
1294/// Helper function to parse XML to JSON Value (for dataflow plugins)
1295/// This function parses XML and converts it to a JSON structure
1296///
1297/// Note: This is a simplified XML parser for plugin use.
1298/// It converts XML elements to JSON objects preserving the structure.
1299///
1300/// DEPRECATED: Use xml_to_json_via_document() instead, which uses typed structs
1301/// and correctly handles arrays and complex structures.
1302pub fn from_mx_xml_to_json(xml: &str) -> Result<serde_json::Value, XmlError> {
1303    use quick_xml::Reader;
1304    use quick_xml::events::Event;
1305
1306    let mut reader = Reader::from_str(xml);
1307    reader.config_mut().trim_text(true);
1308
1309    let mut buf = Vec::new();
1310    let mut stack: Vec<(String, serde_json::Map<String, serde_json::Value>)> = Vec::new();
1311
1312    loop {
1313        match reader.read_event_into(&mut buf) {
1314            Ok(Event::Start(e)) => {
1315                let name = String::from_utf8_lossy(e.name().as_ref()).to_string();
1316                let mut map = serde_json::Map::new();
1317
1318                // Process attributes
1319                for attr in e.attributes().flatten() {
1320                    let key = format!("@{}", String::from_utf8_lossy(attr.key.as_ref()));
1321                    let value = String::from_utf8_lossy(&attr.value).to_string();
1322                    map.insert(key, serde_json::Value::String(value));
1323                }
1324
1325                stack.push((name, map));
1326            }
1327            Ok(Event::Text(e)) => {
1328                if let Some((_, map)) = stack.last_mut() {
1329                    let text_bytes = e.as_ref();
1330                    let text_str = String::from_utf8_lossy(text_bytes).trim().to_string();
1331                    if !text_str.is_empty() {
1332                        map.insert("$value".to_string(), serde_json::Value::String(text_str));
1333                    }
1334                }
1335            }
1336            Ok(Event::End(_)) => {
1337                if let Some((name, map)) = stack.pop() {
1338                    let value = if map.len() == 1 && map.contains_key("$value") {
1339                        // Just text content, unwrap it
1340                        map.get("$value").unwrap().clone()
1341                    } else {
1342                        serde_json::Value::Object(map)
1343                    };
1344
1345                    if let Some((_, parent_map)) = stack.last_mut() {
1346                        parent_map.insert(name, value);
1347                    } else {
1348                        // Root element
1349                        let mut root = serde_json::Map::new();
1350                        root.insert(name, value);
1351                        return Ok(serde_json::Value::Object(root));
1352                    }
1353                }
1354            }
1355            Ok(Event::Eof) => break,
1356            Ok(_) => {}
1357            Err(e) => {
1358                return Err(XmlError::DeserializationError(format!(
1359                    "XML parsing error: {}",
1360                    e
1361                )));
1362            }
1363        }
1364        buf.clear();
1365    }
1366
1367    Ok(serde_json::Value::Null)
1368}
1369
1370#[cfg(test)]
1371mod tests {
1372    use super::*;
1373
1374    #[test]
1375    fn test_namespace_lookup() {
1376        assert_eq!(
1377            get_namespace_for_message_type("pacs.008"),
1378            "urn:iso:std:iso:20022:tech:xsd:pacs.008.001.08"
1379        );
1380        assert_eq!(
1381            get_namespace_for_message_type("pain.001"),
1382            "urn:iso:std:iso:20022:tech:xsd:pain.001.001.09"
1383        );
1384        assert_eq!(
1385            get_namespace_for_message_type("camt.053"),
1386            "urn:iso:std:iso:20022:tech:xsd:camt.053.001.08"
1387        );
1388    }
1389
1390    #[test]
1391    fn test_xml_config_default() {
1392        let config = XmlConfig::default();
1393        assert!(config.include_declaration);
1394        assert!(config.pretty_print);
1395        assert_eq!(config.indent, "  ");
1396        assert!(!config.include_schema_location);
1397    }
1398}