rust_iso20022 0.1.4

ISO 20022 financial message parser and generated Rust types for SWIFT MX XML/JSON.
//! Inspect an incoming ISO 20022 business message — a realistic "what did I just
//! receive?" flow that needs **no `model` feature**: identify the message, read
//! its Business Application Header, extract business metadata, and pull specific
//! fields out of the generic tree.
//!
//! Run it:
//! ```bash
//! cargo run --example inspect_message
//! cargo run --example inspect_message --features model-pacs
//! ```

use rust_iso20022::{MxNode, detect, read_business_message};

/// A pacs.008 (FI-to-FI customer credit transfer) wrapped in an AppHdr envelope,
/// the way it would arrive on the wire.
const MESSAGE: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<BizMsgEnvlp>
  <AppHdr xmlns="urn:iso:std:iso:20022:tech:xsd:head.001.001.02">
    <Fr><FIId><FinInstnId><BICFI>BANKBEBB</BICFI></FinInstnId></FIId></Fr>
    <To><FIId><FinInstnId><BICFI>BANKDEFF</BICFI></FinInstnId></FIId></To>
    <BizMsgIdr>MSG-2026-0001</BizMsgIdr>
    <MsgDefIdr>pacs.008.001.08</MsgDefIdr>
    <CreDt>2026-06-27T10:30:00Z</CreDt>
  </AppHdr>
  <Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.08">
    <FIToFICstmrCdtTrf>
      <GrpHdr>
        <MsgId>ABC-2026-0001</MsgId>
        <CreDtTm>2026-06-27T10:30:00Z</CreDtTm>
        <NbOfTxs>1</NbOfTxs>
        <SttlmInf><SttlmMtd>INDA</SttlmMtd></SttlmInf>
      </GrpHdr>
      <CdtTrfTxInf>
        <PmtId><EndToEndId>E2E-REF-001</EndToEndId></PmtId>
        <IntrBkSttlmAmt Ccy="EUR">1234.56</IntrBkSttlmAmt>
        <IntrBkSttlmDt>2026-06-30</IntrBkSttlmDt>
        <Dbtr><Nm>Alice Inc.</Nm></Dbtr>
        <Cdtr><Nm>Bob Ltd.</Nm></Cdtr>
      </CdtTrfTxInf>
    </FIToFICstmrCdtTrf>
  </Document>
</BizMsgEnvlp>"#;

fn main() {
    // 1. What message is this? (from the Document namespace)
    let id = detect(MESSAGE).expect("a recognisable ISO 20022 message");
    println!(
        "Message type : {} ({})",
        id.message_name(),
        id.business_area.description()
    );

    // 2. Header + identification + business metadata in one call.
    let bm = read_business_message(MESSAGE);
    if let Some(h) = &bm.header {
        println!("From / To    : {} -> {}", opt(&h.from), opt(&h.to));
        println!("Biz Msg Id   : {}", opt(&h.biz_msg_idr));
    }
    let m = &bm.metadata;
    println!("Group Msg Id : {}", opt(&m.message_id));
    println!("Amount       : {} {}", opt(&m.amount), opt(&m.currency));
    println!("Value date   : {}", opt(&m.value_date));

    // 3. Pull specific fields from the generic tree (no typed model needed).
    let doc = MxNode::parse(MESSAGE).expect("parseable tree");
    let e2e = doc.find("EndToEndId").and_then(|n| n.text()).unwrap_or("-");
    let dbtr = doc
        .find("Dbtr")
        .and_then(|n| n.get("Nm"))
        .and_then(|n| n.text())
        .unwrap_or("-");
    let cdtr = doc
        .find("Cdtr")
        .and_then(|n| n.get("Nm"))
        .and_then(|n| n.text())
        .unwrap_or("-");
    println!("End-to-end   : {e2e}");
    println!("Debtor       : {dbtr}");
    println!("Creditor     : {cdtr}");

    // The amount element with its `Ccy` attribute, straight from the tree:
    if let Some(amt) = doc.find("IntrBkSttlmAmt") {
        println!(
            "Settlement   : {} {}",
            amt.text().unwrap_or("-"),
            amt.attr("Ccy").unwrap_or("-")
        );
    }

    // Enabling only the matching business area adds typed access to the exact
    // generated root value; the uniform wrapper does not copy payment fields.
    #[cfg(feature = "model-pacs")]
    {
        let parsed = rust_iso20022::parse(MESSAGE).expect("typed pacs parse");
        let generated = parsed
            .message()
            .as_pacs_008_001_08()
            .expect("pacs.008 generated value");
        let _: &rust_iso20022::generated::pacs::pacs_008_001_08::Document = generated;
        println!(
            "Generated API: {} / {}",
            parsed.message_id(),
            parsed.root_element()
        );
    }
}

fn opt(v: &Option<String>) -> &str {
    v.as_deref().unwrap_or("-")
}