mx_message/header/
bah_camt_058_001_08.rs

1// Plasmatic MX Message Parsing Library
2// https://github.com/GoPlasmatic/MXMessage
3//
4// Copyright (c) 2025 Plasmatic
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//     http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16//
17// You may obtain a copy of this library at
18// https://github.com/GoPlasmatic/MXMessage
19
20use crate::error::*;
21use regex::Regex;
22use serde::{Deserialize, Serialize};
23
24// BranchAndFinancialInstitutionIdentification62: Unique and unambiguous identification of a financial institution, as assigned under an internationally recognised or proprietary identification scheme.
25#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
26pub struct BranchAndFinancialInstitutionIdentification62 {
27    #[serde(rename = "FinInstnId")]
28    pub fin_instn_id: FinancialInstitutionIdentification182,
29}
30
31impl BranchAndFinancialInstitutionIdentification62 {
32    pub fn validate(&self) -> Result<(), ValidationError> {
33        self.fin_instn_id.validate()?;
34        Ok(())
35    }
36}
37
38// BranchAndFinancialInstitutionIdentification63: Unique and unambiguous identification of a financial institution, as assigned under an internationally recognised or proprietary identification scheme.
39#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
40pub struct BranchAndFinancialInstitutionIdentification63 {
41    #[serde(rename = "FinInstnId")]
42    pub fin_instn_id: FinancialInstitutionIdentification183,
43}
44
45impl BranchAndFinancialInstitutionIdentification63 {
46    pub fn validate(&self) -> Result<(), ValidationError> {
47        self.fin_instn_id.validate()?;
48        Ok(())
49    }
50}
51
52// BusinessApplicationHeader51: Relative indication of the processing precedence of the message over a (set of) Business Messages with assigned priorities.
53#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
54pub struct BusinessApplicationHeader51 {
55    #[serde(rename = "CharSet", skip_serializing_if = "Option::is_none")]
56    pub char_set: Option<String>,
57    #[serde(rename = "Fr")]
58    pub fr: Party44Choice2,
59    #[serde(rename = "To")]
60    pub to: Party44Choice2,
61    #[serde(rename = "BizMsgIdr")]
62    pub biz_msg_idr: String,
63    #[serde(rename = "MsgDefIdr")]
64    pub msg_def_idr: String,
65    #[serde(rename = "BizSvc", skip_serializing_if = "Option::is_none")]
66    pub biz_svc: Option<String>,
67    #[serde(rename = "CreDt")]
68    pub cre_dt: String,
69    #[serde(rename = "CpyDplct", skip_serializing_if = "Option::is_none")]
70    pub cpy_dplct: Option<CopyDuplicate1Code>,
71    #[serde(rename = "Prty", skip_serializing_if = "Option::is_none")]
72    pub prty: Option<String>,
73}
74
75impl BusinessApplicationHeader51 {
76    pub fn validate(&self) -> Result<(), ValidationError> {
77        self.fr.validate()?;
78        self.to.validate()?;
79        if self.biz_msg_idr.chars().count() < 1 {
80            return Err(ValidationError::new(
81                1001,
82                "biz_msg_idr is shorter than the minimum length of 1".to_string(),
83            ));
84        }
85        if self.biz_msg_idr.chars().count() > 35 {
86            return Err(ValidationError::new(
87                1002,
88                "biz_msg_idr exceeds the maximum length of 35".to_string(),
89            ));
90        }
91        if self.msg_def_idr.chars().count() < 1 {
92            return Err(ValidationError::new(
93                1001,
94                "msg_def_idr is shorter than the minimum length of 1".to_string(),
95            ));
96        }
97        if self.msg_def_idr.chars().count() > 35 {
98            return Err(ValidationError::new(
99                1002,
100                "msg_def_idr exceeds the maximum length of 35".to_string(),
101            ));
102        }
103        if let Some(ref val) = self.biz_svc {
104            if val.chars().count() < 1 {
105                return Err(ValidationError::new(
106                    1001,
107                    "biz_svc is shorter than the minimum length of 1".to_string(),
108                ));
109            }
110            if val.chars().count() > 35 {
111                return Err(ValidationError::new(
112                    1002,
113                    "biz_svc exceeds the maximum length of 35".to_string(),
114                ));
115            }
116        }
117        let pattern = Regex::new(".*(\\+|-)((0[0-9])|(1[0-4])):[0-5][0-9]").unwrap();
118        if !pattern.is_match(&self.cre_dt) {
119            return Err(ValidationError::new(
120                1005,
121                "cre_dt does not match the required pattern".to_string(),
122            ));
123        }
124        if let Some(ref val) = self.cpy_dplct {
125            val.validate()?
126        }
127        Ok(())
128    }
129}
130
131// BusinessApplicationHeaderV02: Specifies the Business Application Header(s) of the Business Message(s) to which this Business Message relates.
132// Can be used when replying to a query; can also be used when canceling or amending.
133#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
134pub struct BusinessApplicationHeaderV02 {
135    #[serde(rename = "CharSet", skip_serializing_if = "Option::is_none")]
136    pub char_set: Option<String>,
137    #[serde(rename = "Fr")]
138    pub fr: Party44Choice1,
139    #[serde(rename = "To")]
140    pub to: Party44Choice1,
141    #[serde(rename = "BizMsgIdr")]
142    pub biz_msg_idr: String,
143    #[serde(rename = "MsgDefIdr")]
144    pub msg_def_idr: String,
145    #[serde(rename = "BizSvc")]
146    pub biz_svc: String,
147    #[serde(rename = "MktPrctc", skip_serializing_if = "Option::is_none")]
148    pub mkt_prctc: Option<ImplementationSpecification1>,
149    #[serde(rename = "CreDt")]
150    pub cre_dt: String,
151    #[serde(rename = "CpyDplct", skip_serializing_if = "Option::is_none")]
152    pub cpy_dplct: Option<CopyDuplicate1Code>,
153    #[serde(rename = "PssblDplct", skip_serializing_if = "Option::is_none")]
154    pub pssbl_dplct: Option<bool>,
155    #[serde(rename = "Prty", skip_serializing_if = "Option::is_none")]
156    pub prty: Option<Priority2Code>,
157    #[serde(rename = "Rltd", skip_serializing_if = "Option::is_none")]
158    pub rltd: Option<Vec<BusinessApplicationHeader51>>,
159}
160
161impl BusinessApplicationHeaderV02 {
162    pub fn validate(&self) -> Result<(), ValidationError> {
163        self.fr.validate()?;
164        self.to.validate()?;
165        if self.biz_msg_idr.chars().count() < 1 {
166            return Err(ValidationError::new(
167                1001,
168                "biz_msg_idr is shorter than the minimum length of 1".to_string(),
169            ));
170        }
171        if self.biz_msg_idr.chars().count() > 35 {
172            return Err(ValidationError::new(
173                1002,
174                "biz_msg_idr exceeds the maximum length of 35".to_string(),
175            ));
176        }
177        if self.msg_def_idr.chars().count() < 1 {
178            return Err(ValidationError::new(
179                1001,
180                "msg_def_idr is shorter than the minimum length of 1".to_string(),
181            ));
182        }
183        if self.msg_def_idr.chars().count() > 35 {
184            return Err(ValidationError::new(
185                1002,
186                "msg_def_idr exceeds the maximum length of 35".to_string(),
187            ));
188        }
189        if self.biz_svc.chars().count() < 6 {
190            return Err(ValidationError::new(
191                1001,
192                "biz_svc is shorter than the minimum length of 6".to_string(),
193            ));
194        }
195        if self.biz_svc.chars().count() > 35 {
196            return Err(ValidationError::new(
197                1002,
198                "biz_svc exceeds the maximum length of 35".to_string(),
199            ));
200        }
201        let pattern = Regex::new("[a-z0-9]{1,10}\\.([a-z0-9]{1,10}\\.)+\\d\\d").unwrap();
202        if !pattern.is_match(&self.biz_svc) {
203            return Err(ValidationError::new(
204                1005,
205                "biz_svc does not match the required pattern".to_string(),
206            ));
207        }
208        if let Some(ref val) = self.mkt_prctc {
209            val.validate()?
210        }
211        let pattern = Regex::new(".*(\\+|-)((0[0-9])|(1[0-4])):[0-5][0-9]").unwrap();
212        if !pattern.is_match(&self.cre_dt) {
213            return Err(ValidationError::new(
214                1005,
215                "cre_dt does not match the required pattern".to_string(),
216            ));
217        }
218        if let Some(ref val) = self.cpy_dplct {
219            val.validate()?
220        }
221        if let Some(ref val) = self.prty {
222            val.validate()?
223        }
224        if let Some(ref vec) = self.rltd {
225            for item in vec {
226                item.validate()?
227            }
228        }
229        Ok(())
230    }
231}
232
233// ClearingSystemIdentification2Choice: Identification code for a clearing system, that has not yet been identified in the list of clearing systems.
234#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
235pub struct ClearingSystemIdentification2Choice {
236    #[serde(rename = "Cd", skip_serializing_if = "Option::is_none")]
237    pub cd: Option<String>,
238    #[serde(rename = "Prtry", skip_serializing_if = "Option::is_none")]
239    pub prtry: Option<String>,
240}
241
242impl ClearingSystemIdentification2Choice {
243    pub fn validate(&self) -> Result<(), ValidationError> {
244        if let Some(ref val) = self.cd {
245            if val.chars().count() < 1 {
246                return Err(ValidationError::new(
247                    1001,
248                    "cd is shorter than the minimum length of 1".to_string(),
249                ));
250            }
251            if val.chars().count() > 5 {
252                return Err(ValidationError::new(
253                    1002,
254                    "cd exceeds the maximum length of 5".to_string(),
255                ));
256            }
257        }
258        if let Some(ref val) = self.prtry {
259            if val.chars().count() < 1 {
260                return Err(ValidationError::new(
261                    1001,
262                    "prtry is shorter than the minimum length of 1".to_string(),
263                ));
264            }
265            if val.chars().count() > 35 {
266                return Err(ValidationError::new(
267                    1002,
268                    "prtry exceeds the maximum length of 35".to_string(),
269                ));
270            }
271        }
272        Ok(())
273    }
274}
275
276// ClearingSystemIdentification2Choice1: Identification of a clearing system, in a coded form as published in an external list.
277#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
278pub struct ClearingSystemIdentification2Choice1 {
279    #[serde(rename = "Cd", skip_serializing_if = "Option::is_none")]
280    pub cd: Option<String>,
281}
282
283impl ClearingSystemIdentification2Choice1 {
284    pub fn validate(&self) -> Result<(), ValidationError> {
285        if let Some(ref val) = self.cd {
286            if val.chars().count() < 1 {
287                return Err(ValidationError::new(
288                    1001,
289                    "cd is shorter than the minimum length of 1".to_string(),
290                ));
291            }
292            if val.chars().count() > 5 {
293                return Err(ValidationError::new(
294                    1002,
295                    "cd exceeds the maximum length of 5".to_string(),
296                ));
297            }
298        }
299        Ok(())
300    }
301}
302
303// ClearingSystemMemberIdentification2: Identification of a member of a clearing system.
304#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
305pub struct ClearingSystemMemberIdentification2 {
306    #[serde(rename = "ClrSysId", skip_serializing_if = "Option::is_none")]
307    pub clr_sys_id: Option<ClearingSystemIdentification2Choice>,
308    #[serde(rename = "MmbId")]
309    pub mmb_id: String,
310}
311
312impl ClearingSystemMemberIdentification2 {
313    pub fn validate(&self) -> Result<(), ValidationError> {
314        if let Some(ref val) = self.clr_sys_id {
315            val.validate()?
316        }
317        if self.mmb_id.chars().count() < 1 {
318            return Err(ValidationError::new(
319                1001,
320                "mmb_id is shorter than the minimum length of 1".to_string(),
321            ));
322        }
323        if self.mmb_id.chars().count() > 35 {
324            return Err(ValidationError::new(
325                1002,
326                "mmb_id exceeds the maximum length of 35".to_string(),
327            ));
328        }
329        Ok(())
330    }
331}
332
333// ClearingSystemMemberIdentification21: Identification of a member of a clearing system.
334#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
335pub struct ClearingSystemMemberIdentification21 {
336    #[serde(rename = "ClrSysId")]
337    pub clr_sys_id: ClearingSystemIdentification2Choice1,
338    #[serde(rename = "MmbId")]
339    pub mmb_id: String,
340}
341
342impl ClearingSystemMemberIdentification21 {
343    pub fn validate(&self) -> Result<(), ValidationError> {
344        self.clr_sys_id.validate()?;
345        if self.mmb_id.chars().count() < 1 {
346            return Err(ValidationError::new(
347                1001,
348                "mmb_id is shorter than the minimum length of 1".to_string(),
349            ));
350        }
351        if self.mmb_id.chars().count() > 28 {
352            return Err(ValidationError::new(
353                1002,
354                "mmb_id exceeds the maximum length of 28".to_string(),
355            ));
356        }
357        let pattern = Regex::new("[0-9a-zA-Z/\\-\\?:\\(\\)\\.,'\\+ ]+").unwrap();
358        if !pattern.is_match(&self.mmb_id) {
359            return Err(ValidationError::new(
360                1005,
361                "mmb_id does not match the required pattern".to_string(),
362            ));
363        }
364        Ok(())
365    }
366}
367
368// CopyDuplicate1Code: Message is for information/confirmation purposes. It is a duplicate of a message previously sent.
369#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
370pub enum CopyDuplicate1Code {
371    #[default]
372    #[serde(rename = "CODU")]
373    CodeCODU,
374    #[serde(rename = "COPY")]
375    CodeCOPY,
376    #[serde(rename = "DUPL")]
377    CodeDUPL,
378}
379
380impl CopyDuplicate1Code {
381    pub fn validate(&self) -> Result<(), ValidationError> {
382        Ok(())
383    }
384}
385
386// FinancialInstitutionIdentification182: Legal entity identifier of the financial institution.
387#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
388pub struct FinancialInstitutionIdentification182 {
389    #[serde(rename = "BICFI")]
390    pub bicfi: String,
391    #[serde(rename = "ClrSysMmbId", skip_serializing_if = "Option::is_none")]
392    pub clr_sys_mmb_id: Option<ClearingSystemMemberIdentification21>,
393    #[serde(rename = "LEI", skip_serializing_if = "Option::is_none")]
394    pub lei: Option<String>,
395}
396
397impl FinancialInstitutionIdentification182 {
398    pub fn validate(&self) -> Result<(), ValidationError> {
399        let pattern =
400            Regex::new("[A-Z0-9]{4,4}[A-Z]{2,2}[A-Z0-9]{2,2}([A-Z0-9]{3,3}){0,1}").unwrap();
401        if !pattern.is_match(&self.bicfi) {
402            return Err(ValidationError::new(
403                1005,
404                "bicfi does not match the required pattern".to_string(),
405            ));
406        }
407        if let Some(ref val) = self.clr_sys_mmb_id {
408            val.validate()?
409        }
410        if let Some(ref val) = self.lei {
411            let pattern = Regex::new("[A-Z0-9]{18,18}[0-9]{2,2}").unwrap();
412            if !pattern.is_match(val) {
413                return Err(ValidationError::new(
414                    1005,
415                    "lei does not match the required pattern".to_string(),
416                ));
417            }
418        }
419        Ok(())
420    }
421}
422
423// FinancialInstitutionIdentification183: Legal entity identifier of the financial institution.
424#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
425pub struct FinancialInstitutionIdentification183 {
426    #[serde(rename = "BICFI", skip_serializing_if = "Option::is_none")]
427    pub bicfi: Option<String>,
428    #[serde(rename = "ClrSysMmbId", skip_serializing_if = "Option::is_none")]
429    pub clr_sys_mmb_id: Option<ClearingSystemMemberIdentification2>,
430    #[serde(rename = "LEI", skip_serializing_if = "Option::is_none")]
431    pub lei: Option<String>,
432}
433
434impl FinancialInstitutionIdentification183 {
435    pub fn validate(&self) -> Result<(), ValidationError> {
436        if let Some(ref val) = self.bicfi {
437            let pattern =
438                Regex::new("[A-Z0-9]{4,4}[A-Z]{2,2}[A-Z0-9]{2,2}([A-Z0-9]{3,3}){0,1}").unwrap();
439            if !pattern.is_match(val) {
440                return Err(ValidationError::new(
441                    1005,
442                    "bicfi does not match the required pattern".to_string(),
443                ));
444            }
445        }
446        if let Some(ref val) = self.clr_sys_mmb_id {
447            val.validate()?
448        }
449        if let Some(ref val) = self.lei {
450            let pattern = Regex::new("[A-Z0-9]{18,18}[0-9]{2,2}").unwrap();
451            if !pattern.is_match(val) {
452                return Err(ValidationError::new(
453                    1005,
454                    "lei does not match the required pattern".to_string(),
455                ));
456            }
457        }
458        Ok(())
459    }
460}
461
462// ImplementationSpecification1: Identifier which unambiguously identifies, within the implementation specification registry, the implementation specification to which the ISO 20022 message is compliant. This can be done via a URN. It can also contain a version number or date.
463// For instance, "2018-01-01 – Version 2" or "urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66".
464#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
465pub struct ImplementationSpecification1 {
466    #[serde(rename = "Regy")]
467    pub regy: String,
468    #[serde(rename = "Id")]
469    pub id: String,
470}
471
472impl ImplementationSpecification1 {
473    pub fn validate(&self) -> Result<(), ValidationError> {
474        if self.regy.chars().count() < 1 {
475            return Err(ValidationError::new(
476                1001,
477                "regy is shorter than the minimum length of 1".to_string(),
478            ));
479        }
480        if self.regy.chars().count() > 350 {
481            return Err(ValidationError::new(
482                1002,
483                "regy exceeds the maximum length of 350".to_string(),
484            ));
485        }
486        if self.id.chars().count() < 1 {
487            return Err(ValidationError::new(
488                1001,
489                "id is shorter than the minimum length of 1".to_string(),
490            ));
491        }
492        if self.id.chars().count() > 2048 {
493            return Err(ValidationError::new(
494                1002,
495                "id exceeds the maximum length of 2048".to_string(),
496            ));
497        }
498        Ok(())
499    }
500}
501
502// Party44Choice1: Identification of a financial institution.
503#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
504pub struct Party44Choice1 {
505    #[serde(rename = "FIId", skip_serializing_if = "Option::is_none")]
506    pub fi_id: Option<BranchAndFinancialInstitutionIdentification62>,
507}
508
509impl Party44Choice1 {
510    pub fn validate(&self) -> Result<(), ValidationError> {
511        if let Some(ref val) = self.fi_id {
512            val.validate()?
513        }
514        Ok(())
515    }
516}
517
518// Party44Choice2: Identification of a financial institution.
519#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
520pub struct Party44Choice2 {
521    #[serde(rename = "FIId", skip_serializing_if = "Option::is_none")]
522    pub fi_id: Option<BranchAndFinancialInstitutionIdentification63>,
523}
524
525impl Party44Choice2 {
526    pub fn validate(&self) -> Result<(), ValidationError> {
527        if let Some(ref val) = self.fi_id {
528            val.validate()?
529        }
530        Ok(())
531    }
532}
533
534// Priority2Code: Priority level is normal.
535#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
536pub enum Priority2Code {
537    #[default]
538    #[serde(rename = "HIGH")]
539    CodeHIGH,
540    #[serde(rename = "NORM")]
541    CodeNORM,
542}
543
544impl Priority2Code {
545    pub fn validate(&self) -> Result<(), ValidationError> {
546        Ok(())
547    }
548}