Production-grade ISO 20022 toolkit for Rust. Strongly-typed message models generated from official XSD schemas, with built-in validation for FedNow, SEPA, and CBPR+ payment rails and bidirectional SWIFT MT ↔ MX translation — everything you need to build, validate, and migrate financial messages in a single crate.
- 13 message schemas — pacs.008, pacs.002, pacs.004, pacs.009, pacs.028, pain.001, pain.002, pain.013, camt.053, camt.054, camt.056, camt.029, head.001
- Strongly typed — every element, attribute, and enum variant is a named Rust type with serde Serialize/Deserialize
- Builder pattern — all struct types expose a builder with required-field validation
- XML round-trip — parse and serialize ISO 20022 XML via quick-xml + serde
- MT parser — SWIFT FIN message parser for MT103, MT202, MT940
- Bidirectional translation — MT103 ↔ pacs.008, MT202 ↔ pacs.009, MT940 ↔ camt.053
- Validation — IBAN, BIC, LEI, currency, amount format, plus scheme-level rules for FedNow, SEPA, and CBPR+
- XSD constraint validation — every generated newtype validates pattern,
length, and range constraints at construction (
TryFrom<String>) and on full message trees via theValidatabletrait - Code generation — XSD → intermediate representation → Rust source
The easiest way to get started — re-exports model, parse, validate, and translate with all message families enabled:
cargo add mx20022Pick only what you need for smaller compile times and dependency footprint:
cargo add mx20022-model # Generated types + common types
cargo add mx20022-parse # XML parsing and serialization
cargo add mx20022-validate # Schema, business rule, and scheme validation
cargo add mx20022-translate # MT ↔ MX bidirectional translation
cargo add mx20022-codegen # XSD → Rust code generatorcargo install mx20022-cliFeature flags on mx20022-model control which message families are compiled.
The umbrella crate enables all of them.
| Feature | Message families | Default |
|---|---|---|
pacs |
pacs.002, pacs.004, pacs.008, pacs.009, pacs.028 | yes |
pain |
pain.001, pain.002, pain.013 | no |
camt |
camt.029, camt.053, camt.054, camt.056 | no |
head |
head.001 | no |
all |
all of the above | no |
use mx20022::model::generated::pacs::pacs_008_001_13::Document;
use mx20022::parse::{de, envelope};
let xml = std::fs::read_to_string("message.xml")?;
// Detect on the complete stream so wrapper-inherited namespaces remain in
// scope. The Document namespace still wins over a preceding AppHdr namespace.
let msg_id = envelope::detect_message_type(&xml)?;
println!("Message type: {}", msg_id.dotted()); // e.g. "pacs.008.001.13"
// Then unwrap the payload Document without copying and deserialize it.
let document_xml = de::document_xml(&xml)?;
let doc: Document = de::from_str(document_xml)?;
let hdr = &doc.fi_to_fi_cstmr_cdt_trf.grp_hdr;
println!("Message ID: {}", hdr.msg_id.0);
println!("Transactions: {}", hdr.nb_of_txs.0);use mx20022::validate::schemes::{fednow::FedNowValidator, SchemeValidator};
let validator = FedNowValidator::new(); // $500K default limit
// validate() accepts a bare or transport-wrapped payload Document. For
// pacs.008.001.13 it parses the Document and delegates to typed field rules;
// older pacs.008 versions use the same version-neutral field rules.
let result = validator.validate(&xml, "pacs.008.001.13");
if result.is_valid() {
println!("Valid ({} warnings)", result.warning_count());
} else {
for err in &result.errors {
println!("[{}] {} — {}", err.rule_id, err.path, err.message);
}
}use mx20022::model::common::validate::IsoMessage;
use mx20022::model::generated::pacs::pacs_008_001_13::Document;
use mx20022::parse::de;
let xml = std::fs::read_to_string("message.xml")?;
let doc: Document = de::from_str(&xml)?;
let violations = doc.validate_message();
if violations.is_empty() {
println!("All XSD constraints satisfied");
} else {
for v in &violations {
println!("[{:?}] {} — {}", v.kind, v.path, v.message);
}
}use mx20022::translate::mt;
use mx20022::translate::mt::fields::mt103::parse_mt103;
use mx20022::translate::mappings::mt103_to_pacs008::mt103_to_pacs008;
use mx20022::parse::ser;
let mt_text = std::fs::read_to_string("mt103.txt")?;
// Parse raw SWIFT FIN blocks → MT103 fields
let msg = mt::parse(&mt_text)?;
let mt103 = parse_mt103(&msg.block4)?;
// Translate to pacs.008 document
let result = mt103_to_pacs008(&mt103, "MSG-001", "2024-01-15T10:00:00")?;
// Serialize back to XML
let xml = ser::to_string(&result.message)?;
println!("{xml}");# Inspect message structure
mx20022-cli inspect testdata/xml/pacs/pacs_008_001_13_minimal.xml
# Validate with optional scheme rules
mx20022-cli validate testdata/xml/pacs/pacs_008_001_13_minimal.xml
mx20022-cli validate message.xml --scheme fednow
# Translate between MT and MX
mx20022-cli translate testdata/mt/mt103.txt --to pacs008
mx20022-cli translate message.xml --to mt103
# Generate Rust types from an XSD
mx20022-cli codegen schemas/pacs/pacs.008.001.13.xsd -o output.rsSupported --to targets: pacs008, mt103, pacs009, mt202, camt053, mt940.
Supported --scheme values: fednow, sepa, cbpr.
The CLI rejects input files larger than 10 MB to prevent out-of-memory conditions.
# Run all checks (check + test + clippy + fmt)
just
# Individual commands
just check # cargo check --workspace --all-features
just test # cargo test --workspace --all-features
just clippy # clippy with -D warnings
just fmt # auto-format
just fmt-check # format check only
just deny # cargo-deny license/advisory audit
just doc # build documentation
just bench # run criterion benchmarks
just coverage # HTML coverage report via llvm-cov
just msrv # verify MSRV 1.86.0 compatibility
# Full CI suite locally
just cimx20022/
├── crates/
│ ├── mx20022/ # Umbrella re-export + examples
│ ├── mx20022-model/ # Generated types (src/generated/) + common types (src/common/)
│ ├── mx20022-parse/ # XML parsing and serialization
│ ├── mx20022-validate/ # Schema, business rule, and scheme validation
│ ├── mx20022-translate/ # MT ↔ MX translation + MT parser
│ ├── mx20022-codegen/ # XSD → Rust code generator
│ └── mx20022-cli/ # Command-line tool
├── schemas/ # ISO 20022 XSD source files
├── testdata/ # Shared test fixtures (XML, MT, translation pairs)
├── docs/ # Project documentation
├── justfile # Task runner
├── deny.toml # cargo-deny configuration
└── rust-toolchain.toml # Rust stable + components
Key boundary: Files under crates/mx20022-model/src/generated/ are
machine-written by the codegen tool. Do not hand-edit them — modify the code
generator instead.
See ARCHITECTURE.md for a detailed design walkthrough of each crate, the code generation pipeline, and key architectural decisions.
cargo run -p mx20022 --example parse_pacs008
cargo run -p mx20022 --example validate_message
cargo run -p mx20022 --example translate_mt103
cargo run -p mx20022 --example roundtrip| Family | Message | Version | Description |
|---|---|---|---|
| head | 001.001 | 04 | Business Application Header |
| pacs | 008.001 | 13 | FI to FI Customer Credit Transfer |
| pacs | 002.001 | 14 | Payment Status Report |
| pacs | 004.001 | 11 | Return of Funds |
| pacs | 009.001 | 10 | FI to FI Credit Transfer |
| pacs | 028.001 | 05 | FI Status Request |
| pain | 001.001 | 11 | Customer Credit Transfer Initiation |
| pain | 002.001 | 13 | Customer Payment Status Report |
| pain | 013.001 | 09 | Creditor Payment Activation Request |
| camt | 053.001 | 11 | Bank to Customer Statement |
| camt | 054.001 | 11 | Bank to Customer Debit/Credit Notification |
| camt | 056.001 | 11 | Payment Cancellation Request |
| camt | 029.001 | 12 | Resolution of Investigation |
- Fork the repository
- Create a feature branch (
git checkout -b feat/my-change) - Ensure all checks pass:
just ci - Open a pull request against
main
All code must pass cargo check, cargo test, cargo clippy -- -D warnings,
and cargo fmt --check before merge.
CI is Walden CI, not GitHub Actions — the repo carries no workflow files.
The gate is declared in ci-walden/interface.json
and executed by ci-walden/tenant.sh; a push to a
watched branch posts a ci-walden/full-pipeline commit status. Run the same
gate locally with bash ci-walden/tenant.sh or just ci.
The declared MSRV is 1.86.0. Walden CI runs the repository's gate on the
runner image's Rust toolchain, so check the 1.86 floor explicitly with
just msrv before release.
unsafe code is forbidden workspace-wide.
Licensed under the Apache License, Version 2.0 (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0).