iso_20022_sdk/message.rs
1// Copyright 2023 Emergent Financial, LLC - All Rights Reserved
2//
3//
4// This software is licensed under the Emergent Financial Limited Public License Version 1.0
5// (EF-LPLv1). You may use, copy, modify, and distribute this software under the terms and
6// conditions of the EF-LPL. For more information, please refer to the full text of the license
7// at https://github.com/emergentfinancial/ef-lpl.
8//
9//
10// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
11// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
12// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
13// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
14// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
15// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16//
17//! # Message Envelope
18//!
19//! The `Message` type is a high-level abstraction of the ISO-20022 message envelope `BizMsgEnvlp` and its child elements `Hdr`, `Doc`, `Ref` and `SplmtryData`.
20//!
21//! The `Message` type provides a builder pattern for constructing a message envelope and its child elements. The `Message` type also provides methods for serializing the message envelope to XML and deserializing the message envelope from XML.
22//!
23//! ```rust
24//!
25//! // Import common types and traits in the prelude
26//! use iso_20022_sdk::prelude::*;
27//!
28//!
29//! // The `builder` method will return `Message` instance
30//! // after setting default values, e.g. envelope namespace
31//! //
32//! // The initial `Doc` type of the `Message` can be elided
33//! // using `::<_>` turbofish syntax. The compiler will
34//! // infer the type of the `Doc` based on the `set_document()`
35//! // method.
36//! //
37//! // If a type is required from the compiler, the `::<Document>`
38//! // turbofish syntax can be used to specify the enumerable iso-20022 document
39//! // type of the `Doc` element. This value can then be later overridden
40//! // using the `set_document()` method.
41//! let msg = Message::<_>::builder()
42//!
43//!
44//! // Setting the type of document is done using the `set_document()` method.
45//! // In practice, the document type will likely be the result of the document
46//! // builder for the target namespace, e.g.
47//! //
48//! // `documents::pacs::pacs_008_001_07::Document`
49//! //
50//! // The example below uses the default values for the document builder
51//! // for the `pacs.008.001.07` namespace.
52//! //
53//! // NOTE: document namespaces are feature gated and must be enabled
54//! // for the example to work, e.g. `pacs` feature must be enabled in
55//! // Cargo.toml file.
56//! .set_document(Document::from_namespace("pacs.008.001.07"))
57//! // Call the `to_xml` method to serialize the `Message` type to XML
58//! .to_xml();
59//!
60//!
61//! ```
62//!
63//! #### Example XML Output
64//! ```xml
65//! <!-- Example XML Instance -->
66//! <BizMsgEnvlp xmlns="urn:iso:std:iso:20022:tech:xsd:nvlp.001.001.01">
67//! <Hdr>
68//! <!-- Business Application Header (head.001.001.03) -->
69//! </Hdr>
70//! <Doc>
71//! <!-- Document (ISO-20022 Message) -->
72//! </Doc>
73//! <Ref>
74//! <!-- Reference Information -->
75//! </Ref>
76//! <SplmtryData>
77//! <!-- Supplementary Data (Generic Type) -->
78//! </SplmtryData>
79//! </BizMsgEnvlp>
80//! ```
81use std::io::BufReader;
82
83use crate::head::head_001_001_03::{self as head};
84use crate::nvlp::nvlp_001_001_01::{self as nvlp};
85
86use sxd_document::parser;
87use sxd_xpath::{evaluate_xpath};
88
89use xml::{reader::XmlEvent, EventReader};
90
91use crate::crypto::Signature;
92use crate::documents::{Dmkr, Document};
93
94/// Default Envelope Type
95pub type DefaultMsgEnvlp =
96 nvlp::BizMsgEnvlp<head::AppHdr<Signature, Signature>, Document, Dmkr, Dmkr>;
97
98#[derive(Debug, thiserror::Error)]
99pub enum Error {
100 /// Error Serializing / Deserializing XML
101 #[error(transparent)]
102 XmlSerDe(#[from] quick_xml::de::DeError),
103 /// SXD Document Error
104 #[error(transparent)]
105 XsdDocument(#[from] sxd_document::parser::Error),
106 /// SXD XPath Error
107 #[error(transparent)]
108 XsdXPath(#[from] sxd_xpath::Error),
109 /// Signing Error
110 #[error(transparent)]
111 Signing(#[from] signature::Error),
112}
113
114#[derive(Debug, Clone, Default)]
115pub struct Message<
116 'a,
117 Doc: std::fmt::Debug
118 + Default
119 + Clone
120 + PartialEq
121 + ::serde::Serialize
122 + ::serde::Deserialize<'a>
123 + ::validator::Validate,
124> {
125 /// XML string representing the inner type. Used internally to parse the inner type.
126 /// An incoming message will use this field for helping to determine what the
127 /// inner type is.
128 ///
129 /// use the `to_xml()` method to get the XML string representation of the message
130 /// inner type.
131 pub xml_string: &'a str,
132 /// Internal representation of the message envelope
133 pub inner: nvlp::BizMsgEnvlp<head::AppHdr<Signature, Signature>, Doc, Dmkr, Dmkr>,
134}
135
136impl<'a, Doc> Message<'a, Doc>
137where
138 Doc: std::fmt::Debug
139 + Default
140 + Clone
141 + PartialEq
142 + ::serde::Serialize
143 + ::serde::Deserialize<'a>
144 + ::validator::Validate,
145{
146 pub fn builder() -> Self {
147 let envlp = Self::default();
148
149 // Automatically set the envlp and header namespaces
150 envlp.set_namespace()
151 }
152
153 /// Return the application header from the message envelope
154 pub fn app_hdr(&self) -> Option<head::AppHdr<Signature, Signature>> {
155 self.inner.value.hdr.clone().map(|hdr| hdr.value)
156 }
157
158 /// Set the application header AppHdr of the message
159 /// Note, this will overwrite the existing AppHdr
160 pub fn set_app_hdr(self, app_hdr: head::AppHdr<Signature, Signature>) -> Self {
161 let mut msg = self;
162
163 // Set the AppHdr
164 msg.inner.value.hdr = Some(nvlp::LaxPayload { value: app_hdr });
165
166 msg
167 }
168
169 /// Set the recipient of the message
170 pub fn set_recipient(self, recipient: head::Party44Choice) -> Self {
171 let mut app_hdr = self.app_hdr().unwrap_or_default();
172 app_hdr.value.to = recipient;
173
174 self.set_app_hdr(app_hdr)
175 }
176
177 /// Set the recipient organization id of the message.
178 /// This is a simplified version of `set_recipient` that only takes an organization id.
179 /// Use the `set_recipient_fi_id()` method to set a financial institutiton id.
180 /// Note, this will overwrite any existing recipient.
181 pub fn set_recipient_org_id(self, org_id: head::OrganisationIdentification29) -> Self {
182 self.set_recipient(head::Party44Choice {
183 value: head::Party44ChoiceEnum {
184 org_id: Some(head::PartyIdentification135 {
185 id: Some(head::Party38Choice {
186 value: head::Party38ChoiceEnum {
187 org_id: Some(org_id),
188 ..Default::default()
189 },
190 }),
191 ..Default::default()
192 }),
193 ..Default::default()
194 },
195 })
196 }
197
198 /// Set the recipient financial institution id of the message.
199 /// This is a simplified version of `set_recipient` that only takes a financial institution id.
200 /// Use the `set_recipient_org_id()` method to set an organization id.
201 /// Note, this will overwrite any existing recipient.
202 pub fn set_recipient_fi_id(
203 self,
204 fin_instn_id: head::FinancialInstitutionIdentification18,
205 ) -> Self {
206 self.set_recipient(head::Party44Choice {
207 value: head::Party44ChoiceEnum {
208 fi_id: Some(head::BranchAndFinancialInstitutionIdentification6 {
209 fin_instn_id,
210 ..Default::default()
211 }),
212 ..Default::default()
213 },
214 })
215 }
216
217 /// Set the recipient private individual id of the message.
218 /// This is a simplified version of `set_recipient` that only takes a private individual id.
219 /// Use the `set_recipient_org_id()` method to set an organization id or the `set_recipient_fi_id()`
220 /// method to set a financial institution id.
221 /// Note, this will overwrite any existing recipient.
222 pub fn set_recipient_prvt_id(self, prvt_id: head::PersonIdentification13) -> Self {
223 self.set_recipient(head::Party44Choice {
224 value: head::Party44ChoiceEnum {
225 org_id: Some(head::PartyIdentification135 {
226 id: Some(head::Party38Choice {
227 value: head::Party38ChoiceEnum {
228 prvt_id: Some(prvt_id),
229 ..Default::default()
230 },
231 }),
232 ..Default::default()
233 }),
234 ..Default::default()
235 },
236 })
237 }
238
239 /// Set the sender of the message
240 pub fn set_sender(self, sender: head::Party44Choice) -> Self {
241 let mut app_hdr = self.app_hdr().unwrap_or_default();
242 app_hdr.value.fr = sender;
243
244 self.set_app_hdr(app_hdr)
245 }
246
247 /// Set the sender organization id of the message.
248 /// This is a simplified version of `set_sender` that only takes an organization id.
249 /// Use the `set_sender_fi_id()` method to set a financial institutiton id.
250 /// Note, this will overwrite any existing sender.
251 pub fn set_sender_org_id(self, org_id: head::OrganisationIdentification29) -> Self {
252 self.set_sender(head::Party44Choice {
253 value: head::Party44ChoiceEnum {
254 org_id: Some(head::PartyIdentification135 {
255 id: Some(head::Party38Choice {
256 value: head::Party38ChoiceEnum {
257 org_id: Some(org_id),
258 ..Default::default()
259 },
260 }),
261 ..Default::default()
262 }),
263 ..Default::default()
264 },
265 })
266 }
267
268 /// Set the sender financial institution id of the message.
269 /// This is a simplified version of `set_sender` that only takes a financial institution id.
270 /// Use the `set_sender_org_id()` method to set an organization id.
271 /// Note, this will overwrite any existing sender.
272 pub fn set_sender_fi_id(
273 self,
274 fin_instn_id: head::FinancialInstitutionIdentification18,
275 ) -> Self {
276 self.set_sender(head::Party44Choice {
277 value: head::Party44ChoiceEnum {
278 fi_id: Some(head::BranchAndFinancialInstitutionIdentification6 {
279 fin_instn_id,
280 ..Default::default()
281 }),
282 ..Default::default()
283 },
284 })
285 }
286
287 /// Set the sender private individual id of the message.
288 /// This is a simplified version of `set_sender` that only takes a private individual id.
289 /// Use the `set_sender_org_id()` method to set an organization id or the `set_sender_fi_id()`
290 /// method to set a financial institution id.
291 /// Note, this will overwrite any existing sender.
292 pub fn set_sender_prvt_id(self, prvt_id: head::PersonIdentification13) -> Self {
293 self.set_sender(head::Party44Choice {
294 value: head::Party44ChoiceEnum {
295 org_id: Some(head::PartyIdentification135 {
296 id: Some(head::Party38Choice {
297 value: head::Party38ChoiceEnum {
298 prvt_id: Some(prvt_id),
299 ..Default::default()
300 },
301 }),
302 ..Default::default()
303 }),
304 ..Default::default()
305 },
306 })
307 }
308
309 /// e.g. `Document`
310 pub fn set_biz_msg_idr(self, idr: head::Max35Text) -> Self {
311 let mut app_hdr = self.app_hdr().unwrap_or_default();
312 app_hdr.value.biz_msg_idr = idr;
313
314 self.set_app_hdr(app_hdr)
315 }
316
317 /// e.g. `pacs.008.001.07`
318 pub fn set_msg_def_idr(self, idr: head::Max35Text) -> Self {
319 let mut app_hdr = self.app_hdr().unwrap_or_default();
320 app_hdr.value.msg_def_idr = idr;
321
322 self.set_app_hdr(app_hdr)
323 }
324
325 /// Set the created date time of the message.
326 /// This will be set to the current UTC time.
327 pub fn set_cre_dt(self) -> Self {
328 let mut app_hdr = self.app_hdr().unwrap_or_default();
329 app_hdr.value.cre_dt = head::IsoDateTime {
330 value: chrono::Utc::now(),
331 };
332
333 self.set_app_hdr(app_hdr)
334 }
335
336 /// Set the xml namespace of the message and business header.
337 pub fn set_namespace(self) -> Self {
338 let mut envlp = self;
339
340 // Set the envelope namespace
341 envlp.inner.value.xmlns = nvlp::namespace();
342
343 // Set the header namespace
344 let mut app_hdr = envlp.app_hdr().unwrap_or_default();
345 app_hdr.value.xmlns = head::namespace();
346
347 envlp.set_app_hdr(app_hdr)
348 }
349
350 /// Set the document of the message.
351 /// Note, the document must set its own namespace value.
352 /// By default, all root iso-20022 message documents have
353 /// an attribute field, `xmlns`, that is used to set the document namespace.
354 /// The document namespace must be set before calling this method.
355 pub fn set_document(self, doc: Doc) -> Self {
356 let mut envlp = self;
357 envlp.inner.value.doc.value = doc;
358
359 envlp
360 }
361
362 /// Sign the document at an optional xpath, e.g. `/Document/AcctOpngInstr`
363 /// If no xpath is provided, the entire document will be signed, e.g. `/Document`
364 /// Note, this will overwrite any existing signature.
365 /// ```rust
366 /// use iso_20022_sdk::prelude::*;
367 ///
368 /// let msg = Message::<_>::builder()
369 /// // Document must be set before signing
370 /// .set_document(doc)
371 /// // Sign the entire document
372 /// .sign_document(&signer, None);
373 ///
374 ///
375 /// ```
376 pub fn sign_document(
377 self,
378 signer: impl signature::Signer<Signature>,
379 xpath: Option<&str>,
380 ) -> Result<Self, Error> {
381 let xml = quick_xml::se::to_string(&self.document())?;
382 let package = parser::parse(&xml)?;
383 let doc = package.as_document();
384
385 // By default, sign the entire document
386 let xpath = xpath.unwrap_or("/Document");
387 let data = evaluate_xpath(&doc, xpath)?.into_string();
388
389 // TODO, hash the data before signing
390 let data = data.as_bytes();
391
392 // Sign the xpath data
393 let signature = signer.try_sign(data)?;
394
395 // Set the signature in the application header
396 let mut app_hdr = self.app_hdr().unwrap_or_default();
397 app_hdr.value.sgntr = Some(head::SignatureEnvelope { value: signature });
398
399 // Set the application header and return the envelope
400 Ok(self.set_app_hdr(app_hdr))
401 }
402
403 /// Set the related business reference of the message.
404 pub fn set_rltd(self, rltd: head::BusinessApplicationHeader7<Signature>) -> Self {
405 let mut app_hdr = self.app_hdr().unwrap_or_default();
406 app_hdr.value.rltd.push(rltd);
407
408 self.set_app_hdr(app_hdr)
409 }
410
411 /// Return the envelope document.
412 pub fn document(&self) -> Doc {
413 self.inner.value.doc.value.clone()
414 }
415
416 /// Return the serialized xml string of the inner type.
417 pub fn to_xml(&self) -> Result<String, Error> {
418 let xml_string = quick_xml::se::to_string(&self.inner)?;
419
420 Ok(xml_string)
421 }
422
423 /// parse the header from the xml string
424 pub fn from_xml(xml_string: &'a str) -> Result<Self, Error> {
425 let inner = quick_xml::de::from_str(xml_string)?;
426
427 println!("inner: {:?}", inner);
428
429 let mut msg = Self { xml_string, inner };
430
431 println!("msg: {:?}", msg);
432
433 // Parse the msg into the inner type;
434 msg.parse()?;
435
436 Ok(msg)
437 }
438
439 fn parse(&mut self) -> Result<(), Error> {
440 // Use xml-reader to parse the xml string and find the `MsgDefIdr` element in the `head.001.001.03` namespace.
441 let buf_reader = BufReader::new(self.xml_string.as_bytes());
442 let event_reader = EventReader::new(buf_reader);
443
444 for e in event_reader {
445 match e {
446 Ok(XmlEvent::ProcessingInstruction { name, data }) => {
447 println!("name: {:?}", name);
448 println!("data: {:?}", data);
449 }
450 Ok(XmlEvent::StartElement {
451 name:
452 xml::name::OwnedName {
453 local_name,
454 namespace,
455 ..
456 },
457 ..
458 }) => {
459 println!("local_name: {:?}", local_name);
460 println!("namespace: {:?}", namespace);
461 }
462 Ok(XmlEvent::Characters(data)) => {
463 println!("data: {:?}", data);
464 }
465 _ => (),
466 }
467
468 // buf.clear();
469 }
470
471 Ok(())
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478
479 #[test]
480 fn test_message_builder() -> Result<(), Error> {
481 let msg = Message::<_>::builder()
482 .set_cre_dt()
483 .set_msg_def_idr(head::Max35Text {
484 value: "pacs.008.001.07".to_string(),
485 })
486 .set_biz_msg_idr(head::Max35Text {
487 value: "Document".to_string(),
488 })
489 .set_recipient_org_id(head::OrganisationIdentification29 {
490 othr: vec![head::GenericOrganisationIdentification1 {
491 id: head::Max35Text {
492 value: "b3033215-3a30-48ee-b194-5c02e08a5fb3".to_string(),
493 },
494 ..Default::default()
495 }],
496 ..Default::default()
497 })
498 .set_sender_org_id(head::OrganisationIdentification29 {
499 othr: vec![head::GenericOrganisationIdentification1 {
500 id: head::Max35Text {
501 value: "b3033215-3a30-48ee-b194-5c02e08a5fb3".to_string(),
502 },
503 ..Default::default()
504 }],
505 ..Default::default()
506 })
507 .set_document(Document::from_namespace("pacs.008.001.07"));
508
509 let xml = msg.to_xml()?;
510
511 println!("xml: {}", xml);
512
513 Ok(())
514 }
515
516 #[test]
517 fn test_parse_message() -> Result<(), Error> {
518 let file = std::fs::read_to_string("examples/nvlp.xml").expect("Unable to read file");
519
520 // println!("file: {}", file);
521 let _msg = Message::<Dmkr>::from_xml(&file)?;
522
523 Ok(())
524 }
525}