mx_message/header/
bah_camt_054_001.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// BranchAndFinancialInstitutionIdentification68: 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 BranchAndFinancialInstitutionIdentification68 {
27    #[serde(rename = "FinInstnId")]
28    pub fin_instn_id: FinancialInstitutionIdentification187,
29}
30
31impl BranchAndFinancialInstitutionIdentification68 {
32    pub fn validate(&self) -> Result<(), ValidationError> {
33        self.fin_instn_id.validate()?;
34        Ok(())
35    }
36}
37
38// BusinessApplicationHeader51: Relative indication of the processing precedence of the message over a (set of) Business Messages with assigned priorities.
39#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
40pub struct BusinessApplicationHeader51 {
41    #[serde(rename = "CharSet", skip_serializing_if = "Option::is_none")]
42    pub char_set: Option<String>,
43    #[serde(rename = "Fr")]
44    pub fr: Party44Choice1,
45    #[serde(rename = "To")]
46    pub to: Party44Choice1,
47    #[serde(rename = "BizMsgIdr")]
48    pub biz_msg_idr: String,
49    #[serde(rename = "MsgDefIdr")]
50    pub msg_def_idr: String,
51    #[serde(rename = "BizSvc", skip_serializing_if = "Option::is_none")]
52    pub biz_svc: Option<String>,
53    #[serde(rename = "CreDt")]
54    pub cre_dt: String,
55    #[serde(rename = "CpyDplct", skip_serializing_if = "Option::is_none")]
56    pub cpy_dplct: Option<CopyDuplicate1Code>,
57    #[serde(rename = "Prty", skip_serializing_if = "Option::is_none")]
58    pub prty: Option<String>,
59}
60
61impl BusinessApplicationHeader51 {
62    pub fn validate(&self) -> Result<(), ValidationError> {
63        self.fr.validate()?;
64        self.to.validate()?;
65        if self.biz_msg_idr.chars().count() < 1 {
66            return Err(ValidationError::new(
67                1001,
68                "biz_msg_idr is shorter than the minimum length of 1".to_string(),
69            ));
70        }
71        if self.biz_msg_idr.chars().count() > 35 {
72            return Err(ValidationError::new(
73                1002,
74                "biz_msg_idr exceeds the maximum length of 35".to_string(),
75            ));
76        }
77        if self.msg_def_idr.chars().count() < 1 {
78            return Err(ValidationError::new(
79                1001,
80                "msg_def_idr is shorter than the minimum length of 1".to_string(),
81            ));
82        }
83        if self.msg_def_idr.chars().count() > 35 {
84            return Err(ValidationError::new(
85                1002,
86                "msg_def_idr exceeds the maximum length of 35".to_string(),
87            ));
88        }
89        if let Some(ref val) = self.biz_svc {
90            if val.chars().count() < 1 {
91                return Err(ValidationError::new(
92                    1001,
93                    "biz_svc is shorter than the minimum length of 1".to_string(),
94                ));
95            }
96            if val.chars().count() > 35 {
97                return Err(ValidationError::new(
98                    1002,
99                    "biz_svc exceeds the maximum length of 35".to_string(),
100                ));
101            }
102        }
103        let pattern = Regex::new(".*(\\+|-)((0[0-9])|(1[0-4])):[0-5][0-9]").unwrap();
104        if !pattern.is_match(&self.cre_dt) {
105            return Err(ValidationError::new(
106                1005,
107                "cre_dt does not match the required pattern".to_string(),
108            ));
109        }
110        if let Some(ref val) = self.cpy_dplct {
111            val.validate()?
112        }
113        Ok(())
114    }
115}
116
117// BusinessApplicationHeaderV02: Specifies the Business Application Header(s) of the Business Message(s) to which this Business Message relates.
118// Can be used when replying to a query; can also be used when canceling or amending.
119#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
120pub struct BusinessApplicationHeaderV02 {
121    #[serde(rename = "CharSet", skip_serializing_if = "Option::is_none")]
122    pub char_set: Option<String>,
123    #[serde(rename = "Fr")]
124    pub fr: Party44Choice1,
125    #[serde(rename = "To")]
126    pub to: Party44Choice1,
127    #[serde(rename = "BizMsgIdr")]
128    pub biz_msg_idr: String,
129    #[serde(rename = "MsgDefIdr")]
130    pub msg_def_idr: String,
131    #[serde(rename = "BizSvc")]
132    pub biz_svc: String,
133    #[serde(rename = "MktPrctc", skip_serializing_if = "Option::is_none")]
134    pub mkt_prctc: Option<ImplementationSpecification1>,
135    #[serde(rename = "CreDt")]
136    pub cre_dt: String,
137    #[serde(rename = "CpyDplct", skip_serializing_if = "Option::is_none")]
138    pub cpy_dplct: Option<CopyDuplicate1Code>,
139    #[serde(rename = "PssblDplct", skip_serializing_if = "Option::is_none")]
140    pub pssbl_dplct: Option<bool>,
141    #[serde(rename = "Prty", skip_serializing_if = "Option::is_none")]
142    pub prty: Option<Priority2Code>,
143    #[serde(rename = "Rltd", skip_serializing_if = "Option::is_none")]
144    pub rltd: Option<BusinessApplicationHeader51>,
145}
146
147impl BusinessApplicationHeaderV02 {
148    pub fn validate(&self) -> Result<(), ValidationError> {
149        self.fr.validate()?;
150        self.to.validate()?;
151        if self.biz_msg_idr.chars().count() < 1 {
152            return Err(ValidationError::new(
153                1001,
154                "biz_msg_idr is shorter than the minimum length of 1".to_string(),
155            ));
156        }
157        if self.biz_msg_idr.chars().count() > 35 {
158            return Err(ValidationError::new(
159                1002,
160                "biz_msg_idr exceeds the maximum length of 35".to_string(),
161            ));
162        }
163        if self.msg_def_idr.chars().count() < 1 {
164            return Err(ValidationError::new(
165                1001,
166                "msg_def_idr is shorter than the minimum length of 1".to_string(),
167            ));
168        }
169        if self.msg_def_idr.chars().count() > 35 {
170            return Err(ValidationError::new(
171                1002,
172                "msg_def_idr exceeds the maximum length of 35".to_string(),
173            ));
174        }
175        if self.biz_svc.chars().count() < 6 {
176            return Err(ValidationError::new(
177                1001,
178                "biz_svc is shorter than the minimum length of 6".to_string(),
179            ));
180        }
181        if self.biz_svc.chars().count() > 35 {
182            return Err(ValidationError::new(
183                1002,
184                "biz_svc exceeds the maximum length of 35".to_string(),
185            ));
186        }
187        let pattern = Regex::new("[a-z0-9]{1,10}\\.([a-z0-9]{1,10}\\.)+\\d\\d").unwrap();
188        if !pattern.is_match(&self.biz_svc) {
189            return Err(ValidationError::new(
190                1005,
191                "biz_svc does not match the required pattern".to_string(),
192            ));
193        }
194        if let Some(ref val) = self.mkt_prctc {
195            val.validate()?
196        }
197        let pattern = Regex::new(".*(\\+|-)((0[0-9])|(1[0-4])):[0-5][0-9]").unwrap();
198        if !pattern.is_match(&self.cre_dt) {
199            return Err(ValidationError::new(
200                1005,
201                "cre_dt does not match the required pattern".to_string(),
202            ));
203        }
204        if let Some(ref val) = self.cpy_dplct {
205            val.validate()?
206        }
207        if let Some(ref val) = self.prty {
208            val.validate()?
209        }
210        if let Some(ref val) = self.rltd {
211            val.validate()?
212        }
213        Ok(())
214    }
215}
216
217// ClearingSystemIdentification2Choice1: Identification of a clearing system, in a coded form as published in an external list.
218#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
219pub struct ClearingSystemIdentification2Choice1 {
220    #[serde(rename = "Cd", skip_serializing_if = "Option::is_none")]
221    pub cd: Option<String>,
222}
223
224impl ClearingSystemIdentification2Choice1 {
225    pub fn validate(&self) -> Result<(), ValidationError> {
226        if let Some(ref val) = self.cd {
227            if val.chars().count() < 1 {
228                return Err(ValidationError::new(
229                    1001,
230                    "cd is shorter than the minimum length of 1".to_string(),
231                ));
232            }
233            if val.chars().count() > 5 {
234                return Err(ValidationError::new(
235                    1002,
236                    "cd exceeds the maximum length of 5".to_string(),
237                ));
238            }
239        }
240        Ok(())
241    }
242}
243
244// ClearingSystemMemberIdentification21: Identification of a member of a clearing system.
245#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
246pub struct ClearingSystemMemberIdentification21 {
247    #[serde(rename = "ClrSysId")]
248    pub clr_sys_id: ClearingSystemIdentification2Choice1,
249    #[serde(rename = "MmbId")]
250    pub mmb_id: String,
251}
252
253impl ClearingSystemMemberIdentification21 {
254    pub fn validate(&self) -> Result<(), ValidationError> {
255        self.clr_sys_id.validate()?;
256        if self.mmb_id.chars().count() < 1 {
257            return Err(ValidationError::new(
258                1001,
259                "mmb_id is shorter than the minimum length of 1".to_string(),
260            ));
261        }
262        if self.mmb_id.chars().count() > 28 {
263            return Err(ValidationError::new(
264                1002,
265                "mmb_id exceeds the maximum length of 28".to_string(),
266            ));
267        }
268        let pattern = Regex::new("[0-9a-zA-Z/\\-\\?:\\(\\)\\.,'\\+ ]+").unwrap();
269        if !pattern.is_match(&self.mmb_id) {
270            return Err(ValidationError::new(
271                1005,
272                "mmb_id does not match the required pattern".to_string(),
273            ));
274        }
275        Ok(())
276    }
277}
278
279// CopyDuplicate1Code: Message is for information/confirmation purposes. It is a duplicate of a message previously sent.
280#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
281pub enum CopyDuplicate1Code {
282    #[default]
283    #[serde(rename = "CODU")]
284    CodeCODU,
285    #[serde(rename = "COPY")]
286    CodeCOPY,
287    #[serde(rename = "DUPL")]
288    CodeDUPL,
289}
290
291impl CopyDuplicate1Code {
292    pub fn validate(&self) -> Result<(), ValidationError> {
293        Ok(())
294    }
295}
296
297// FinancialInstitutionIdentification187: Legal entity identifier of the financial institution.
298#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
299pub struct FinancialInstitutionIdentification187 {
300    #[serde(rename = "BICFI")]
301    pub bicfi: String,
302    #[serde(rename = "ClrSysMmbId", skip_serializing_if = "Option::is_none")]
303    pub clr_sys_mmb_id: Option<ClearingSystemMemberIdentification21>,
304    #[serde(rename = "LEI", skip_serializing_if = "Option::is_none")]
305    pub lei: Option<String>,
306}
307
308impl FinancialInstitutionIdentification187 {
309    pub fn validate(&self) -> Result<(), ValidationError> {
310        let pattern =
311            Regex::new("[A-Z0-9]{4,4}[A-Z]{2,2}[A-Z0-9]{2,2}([A-Z0-9]{3,3}){0,1}").unwrap();
312        if !pattern.is_match(&self.bicfi) {
313            return Err(ValidationError::new(
314                1005,
315                "bicfi does not match the required pattern".to_string(),
316            ));
317        }
318        if let Some(ref val) = self.clr_sys_mmb_id {
319            val.validate()?
320        }
321        if let Some(ref val) = self.lei {
322            let pattern = Regex::new("[A-Z0-9]{18,18}[0-9]{2,2}").unwrap();
323            if !pattern.is_match(val) {
324                return Err(ValidationError::new(
325                    1005,
326                    "lei does not match the required pattern".to_string(),
327                ));
328            }
329        }
330        Ok(())
331    }
332}
333
334// 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.
335// For instance, "2018-01-01 – Version 2" or "urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66".
336#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
337pub struct ImplementationSpecification1 {
338    #[serde(rename = "Regy")]
339    pub regy: String,
340    #[serde(rename = "Id")]
341    pub id: String,
342}
343
344impl ImplementationSpecification1 {
345    pub fn validate(&self) -> Result<(), ValidationError> {
346        if self.regy.chars().count() < 1 {
347            return Err(ValidationError::new(
348                1001,
349                "regy is shorter than the minimum length of 1".to_string(),
350            ));
351        }
352        if self.regy.chars().count() > 350 {
353            return Err(ValidationError::new(
354                1002,
355                "regy exceeds the maximum length of 350".to_string(),
356            ));
357        }
358        if self.id.chars().count() < 1 {
359            return Err(ValidationError::new(
360                1001,
361                "id is shorter than the minimum length of 1".to_string(),
362            ));
363        }
364        if self.id.chars().count() > 2048 {
365            return Err(ValidationError::new(
366                1002,
367                "id exceeds the maximum length of 2048".to_string(),
368            ));
369        }
370        Ok(())
371    }
372}
373
374// Party44Choice1: Identification of a financial institution.
375#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
376pub struct Party44Choice1 {
377    #[serde(rename = "FIId", skip_serializing_if = "Option::is_none")]
378    pub fi_id: Option<BranchAndFinancialInstitutionIdentification68>,
379}
380
381impl Party44Choice1 {
382    pub fn validate(&self) -> Result<(), ValidationError> {
383        if let Some(ref val) = self.fi_id {
384            val.validate()?
385        }
386        Ok(())
387    }
388}
389
390// Priority2Code: Priority level is normal.
391#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
392pub enum Priority2Code {
393    #[default]
394    #[serde(rename = "HIGH")]
395    CodeHIGH,
396    #[serde(rename = "NORM")]
397    CodeNORM,
398}
399
400impl Priority2Code {
401    pub fn validate(&self) -> Result<(), ValidationError> {
402        Ok(())
403    }
404}