1use 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#[derive(Debug, Clone)]
14pub struct XmlConfig {
15 pub include_declaration: bool,
17 pub pretty_print: bool,
19 pub indent: String,
21 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#[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
56pub 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 let document_namespace = get_namespace_for_message_type(message_type);
71
72 let envelope = MxEnvelope::new(header, message, document_namespace);
74
75 if config.pretty_print {
77 format_mx_xml(&envelope, &config)
78 } else {
79 xml_to_string(&envelope).map_err(|e| XmlError::SerializationError(e.to_string()))
81 }
82}
83
84pub 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
93fn 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 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 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 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 let app_hdr_xml = xml_to_string(&envelope.app_hdr)
124 .map_err(|e| XmlError::SerializationError(e.to_string()))?;
125
126 let app_hdr_xml = app_hdr_xml
128 .trim_start_matches("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
129 .trim();
130
131 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 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 writer
154 .write_event(Event::End(BytesEnd::new("AppHdr")))
155 .map_err(|e| XmlError::SerializationError(e.to_string()))?;
156
157 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 let message_xml = xml_to_string(&envelope.document.message)
169 .map_err(|e| XmlError::SerializationError(e.to_string()))?;
170
171 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 writer
182 .write_event(Event::End(BytesEnd::new("Document")))
183 .map_err(|e| XmlError::SerializationError(e.to_string()))?;
184
185 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
194static 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
304fn get_namespace_for_message_type(message_type: &str) -> String {
306 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 format!("urn:iso:std:iso:20022:tech:xsd:{}", message_type)
315}
316
317pub 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
365pub 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
413pub 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
461pub 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 let document_json = json_data.get("Document").unwrap_or(json_data);
476
477 let inner_json = if let Some(obj) = document_json.as_object() {
479 if let Some((_key, value)) = obj.iter().next() {
481 value
482 } else {
483 document_json
484 }
485 } else {
486 document_json
487 };
488
489 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 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
688pub fn from_mx_xml_str(xml: &str, message_type: &str) -> Result<(), XmlError> {
691 use crate::document::*;
692
693 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 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
826pub 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 let has_envelope = xml.contains("<AppHdr") || xml.contains("<Envelope");
834
835 if !has_envelope {
836 return from_mx_xml_str(xml, message_type);
838 }
839
840 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(
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 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
1069fn 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
1096pub 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 let inner_xml = xml
1107 .trim()
1108 .trim_start_matches("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
1109 .trim();
1110
1111 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 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 let element_name = get_document_element_name(message_type);
1287 Ok(json!({
1288 "Document": {
1289 element_name: json_value
1290 }
1291 }))
1292}
1293
1294pub 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 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 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 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}