mx_message/document/
camt_025_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// MessageHeader91: Date and time at which the message was created.
25#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
26pub struct MessageHeader91 {
27    #[serde(rename = "MsgId")]
28    pub msg_id: String,
29    #[serde(rename = "CreDtTm")]
30    pub cre_dt_tm: String,
31}
32
33impl MessageHeader91 {
34    pub fn validate(&self) -> Result<(), ValidationError> {
35        if self.msg_id.chars().count() < 1 {
36            return Err(ValidationError::new(
37                1001,
38                "msg_id is shorter than the minimum length of 1".to_string(),
39            ));
40        }
41        if self.msg_id.chars().count() > 35 {
42            return Err(ValidationError::new(
43                1002,
44                "msg_id exceeds the maximum length of 35".to_string(),
45            ));
46        }
47        let pattern = Regex::new("[0-9a-zA-Z/\\-\\?:\\(\\)\\.,'\\+ ]+").unwrap();
48        if !pattern.is_match(&self.msg_id) {
49            return Err(ValidationError::new(
50                1005,
51                "msg_id does not match the required pattern".to_string(),
52            ));
53        }
54        let pattern = Regex::new(".*(\\+|-)((0[0-9])|(1[0-4])):[0-5][0-9]").unwrap();
55        if !pattern.is_match(&self.cre_dt_tm) {
56            return Err(ValidationError::new(
57                1005,
58                "cre_dt_tm does not match the required pattern".to_string(),
59            ));
60        }
61        Ok(())
62    }
63}
64
65// OriginalMessageAndIssuer11: Specifies the original message name identifier to which the message refers.
66#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
67pub struct OriginalMessageAndIssuer11 {
68    #[serde(rename = "MsgId")]
69    pub msg_id: String,
70    #[serde(rename = "MsgNmId")]
71    pub msg_nm_id: String,
72}
73
74impl OriginalMessageAndIssuer11 {
75    pub fn validate(&self) -> Result<(), ValidationError> {
76        if self.msg_id.chars().count() < 1 {
77            return Err(ValidationError::new(
78                1001,
79                "msg_id is shorter than the minimum length of 1".to_string(),
80            ));
81        }
82        if self.msg_id.chars().count() > 35 {
83            return Err(ValidationError::new(
84                1002,
85                "msg_id exceeds the maximum length of 35".to_string(),
86            ));
87        }
88        let pattern = Regex::new("[0-9a-zA-Z/\\-\\?:\\(\\)\\.,'\\+ ]+").unwrap();
89        if !pattern.is_match(&self.msg_id) {
90            return Err(ValidationError::new(
91                1005,
92                "msg_id does not match the required pattern".to_string(),
93            ));
94        }
95        if self.msg_nm_id.chars().count() < 1 {
96            return Err(ValidationError::new(
97                1001,
98                "msg_nm_id is shorter than the minimum length of 1".to_string(),
99            ));
100        }
101        if self.msg_nm_id.chars().count() > 35 {
102            return Err(ValidationError::new(
103                1002,
104                "msg_nm_id exceeds the maximum length of 35".to_string(),
105            ));
106        }
107        let pattern = Regex::new("[0-9a-zA-Z/\\-\\?:\\(\\)\\.,'\\+ ]+").unwrap();
108        if !pattern.is_match(&self.msg_nm_id) {
109            return Err(ValidationError::new(
110                1005,
111                "msg_nm_id does not match the required pattern".to_string(),
112            ));
113        }
114        Ok(())
115    }
116}
117
118// Receipt61: Gives the status of the request.
119#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
120pub struct Receipt61 {
121    #[serde(rename = "OrgnlMsgId")]
122    pub orgnl_msg_id: OriginalMessageAndIssuer11,
123    #[serde(rename = "ReqHdlg")]
124    pub req_hdlg: RequestHandling31,
125}
126
127impl Receipt61 {
128    pub fn validate(&self) -> Result<(), ValidationError> {
129        self.orgnl_msg_id.validate()?;
130        self.req_hdlg.validate()?;
131        Ok(())
132    }
133}
134
135// ReceiptV08: Details of the receipt.
136#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
137pub struct ReceiptV08 {
138    #[serde(rename = "MsgHdr")]
139    pub msg_hdr: MessageHeader91,
140    #[serde(rename = "RctDtls")]
141    pub rct_dtls: Vec<Receipt61>,
142}
143
144impl ReceiptV08 {
145    pub fn validate(&self) -> Result<(), ValidationError> {
146        self.msg_hdr.validate()?;
147        for item in &self.rct_dtls {
148            item.validate()?
149        }
150        Ok(())
151    }
152}
153
154// RequestHandling31: Provides detailed information on the status reason.
155#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
156pub struct RequestHandling31 {
157    #[serde(rename = "Sts")]
158    pub sts: RequestStatus1Choice1,
159    #[serde(rename = "StsRsn", skip_serializing_if = "Option::is_none")]
160    pub sts_rsn: Option<StatusReasonInformation141>,
161}
162
163impl RequestHandling31 {
164    pub fn validate(&self) -> Result<(), ValidationError> {
165        self.sts.validate()?;
166        if let Some(ref val) = self.sts_rsn {
167            val.validate()?
168        }
169        Ok(())
170    }
171}
172
173// RequestStatus1Choice1: Request status, as published in an external request status code set.
174#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
175pub struct RequestStatus1Choice1 {
176    #[serde(rename = "Cd", skip_serializing_if = "Option::is_none")]
177    pub cd: Option<String>,
178}
179
180impl RequestStatus1Choice1 {
181    pub fn validate(&self) -> Result<(), ValidationError> {
182        if let Some(ref val) = self.cd {
183            if val.chars().count() < 1 {
184                return Err(ValidationError::new(
185                    1001,
186                    "cd is shorter than the minimum length of 1".to_string(),
187                ));
188            }
189            if val.chars().count() > 4 {
190                return Err(ValidationError::new(
191                    1002,
192                    "cd exceeds the maximum length of 4".to_string(),
193                ));
194            }
195        }
196        Ok(())
197    }
198}
199
200// StatusReason6Choice1: Reason for the status, as published in an external reason code list.
201#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
202pub struct StatusReason6Choice1 {
203    #[serde(rename = "Cd", skip_serializing_if = "Option::is_none")]
204    pub cd: Option<String>,
205}
206
207impl StatusReason6Choice1 {
208    pub fn validate(&self) -> Result<(), ValidationError> {
209        if let Some(ref val) = self.cd {
210            if val.chars().count() < 1 {
211                return Err(ValidationError::new(
212                    1001,
213                    "cd is shorter than the minimum length of 1".to_string(),
214                ));
215            }
216            if val.chars().count() > 4 {
217                return Err(ValidationError::new(
218                    1002,
219                    "cd exceeds the maximum length of 4".to_string(),
220                ));
221            }
222        }
223        Ok(())
224    }
225}
226
227// StatusReasonInformation141: Further details on the status reason.
228//
229// Usage: Additional information can be used for several purposes such as the reporting of repaired information.
230#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
231pub struct StatusReasonInformation141 {
232    #[serde(rename = "Rsn", skip_serializing_if = "Option::is_none")]
233    pub rsn: Option<StatusReason6Choice1>,
234    #[serde(rename = "AddtlInf", skip_serializing_if = "Option::is_none")]
235    pub addtl_inf: Option<Vec<String>>,
236}
237
238impl StatusReasonInformation141 {
239    pub fn validate(&self) -> Result<(), ValidationError> {
240        if let Some(ref val) = self.rsn {
241            val.validate()?
242        }
243        if let Some(ref vec) = self.addtl_inf {
244            for item in vec {
245                if item.chars().count() < 1 {
246                    return Err(ValidationError::new(
247                        1001,
248                        "addtl_inf is shorter than the minimum length of 1".to_string(),
249                    ));
250                }
251                if item.chars().count() > 105 {
252                    return Err(ValidationError::new(
253                        1002,
254                        "addtl_inf exceeds the maximum length of 105".to_string(),
255                    ));
256                }
257                let pattern = Regex::new("[0-9a-zA-Z/\\-\\?:\\(\\)\\.,'\\+ ]+").unwrap();
258                if !pattern.is_match(&item) {
259                    return Err(ValidationError::new(
260                        1005,
261                        "addtl_inf does not match the required pattern".to_string(),
262                    ));
263                }
264            }
265        }
266        Ok(())
267    }
268}