swift_mt_message/messages/
mt110.rs1use crate::errors::SwiftValidationError;
2use crate::fields::Field52DrawerBank;
3use crate::fields::*;
4use crate::parser::utils::*;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
9pub struct MT110Cheque {
10 #[serde(rename = "21")]
12 pub field_21: Field21NoOption,
13
14 #[serde(rename = "30")]
16 pub field_30: Field30,
17
18 #[serde(flatten)]
20 pub field_32: Field32AB,
21
22 #[serde(flatten, skip_serializing_if = "Option::is_none")]
24 pub field_50: Option<Field50OrderingCustomerAFK>,
25
26 #[serde(flatten, skip_serializing_if = "Option::is_none")]
28 pub field_52: Option<Field52DrawerBank>,
29
30 #[serde(flatten)]
32 pub field_59: Field59,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
43pub struct MT110 {
44 #[serde(rename = "20")]
46 pub field_20: Field20,
47
48 #[serde(flatten, skip_serializing_if = "Option::is_none")]
50 pub field_53a: Option<Field53SenderCorrespondent>,
51
52 #[serde(flatten, skip_serializing_if = "Option::is_none")]
54 pub field_54a: Option<Field54ReceiverCorrespondent>,
55
56 #[serde(rename = "72", skip_serializing_if = "Option::is_none")]
58 pub field_72: Option<Field72>,
59
60 #[serde(rename = "#", default)]
62 pub cheques: Vec<MT110Cheque>,
63}
64
65impl MT110 {
66 pub fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
68 let mut parser = crate::parser::MessageParser::new(block4, "110");
69
70 let field_20 = parser.parse_field::<Field20>("20")?;
72
73 let field_53a = parser.parse_optional_variant_field::<Field53SenderCorrespondent>("53")?;
75 let field_54a =
76 parser.parse_optional_variant_field::<Field54ReceiverCorrespondent>("54")?;
77 let field_72 = parser.parse_optional_field::<Field72>("72")?;
78
79 let mut cheques = Vec::new();
81 parser = parser.with_duplicates(true);
82
83 while parser.detect_field("21") {
85 let field_21 = parser.parse_field::<Field21NoOption>("21")?;
86 let field_30 = parser.parse_field::<Field30>("30")?;
87
88 let field_32 = parser.parse_variant_field::<Field32AB>("32")?;
90
91 let field_50 =
93 parser.parse_optional_variant_field::<Field50OrderingCustomerAFK>("50")?;
94 let field_52 = parser.parse_optional_variant_field::<Field52DrawerBank>("52")?;
95
96 let field_59 = parser.parse_variant_field::<Field59>("59")?;
98
99 cheques.push(MT110Cheque {
100 field_21,
101 field_30,
102 field_32,
103 field_50,
104 field_52,
105 field_59,
106 });
107 }
108
109 if cheques.is_empty() {
111 return Err(crate::errors::ParseError::InvalidFormat {
112 message: "MT110: At least one cheque detail is required".to_string(),
113 });
114 }
115
116 Ok(MT110 {
120 field_20,
121 field_53a,
122 field_54a,
123 field_72,
124 cheques,
125 })
126 }
127
128 pub fn parse(input: &str) -> Result<Self, crate::errors::ParseError> {
130 let block4 = extract_block4(input)?;
131 Self::parse_from_block4(&block4)
132 }
133
134 fn validate_c1_max_repetitions(&self) -> Option<SwiftValidationError> {
141 if self.cheques.len() > 10 {
142 return Some(SwiftValidationError::content_error(
143 "T10",
144 "21-59a",
145 "",
146 &format!(
147 "The repetitive sequence (cheque details) appears {} times, but maximum 10 occurrences are allowed",
148 self.cheques.len()
149 ),
150 "The repetitive sequence containing fields 21, 30, 32a, 50a, 52a, and 59a must not be present more than ten times in the message",
151 ));
152 }
153
154 None
155 }
156
157 fn validate_c2_currency_consistency(&self) -> Option<SwiftValidationError> {
160 if self.cheques.is_empty() {
161 return None;
162 }
163
164 let first_currency = match &self.cheques[0].field_32 {
166 Field32AB::A(amt) => &amt.currency,
167 Field32AB::B(amt) => &amt.currency,
168 };
169
170 for (idx, cheque) in self.cheques.iter().enumerate().skip(1) {
172 let cheque_currency = match &cheque.field_32 {
173 Field32AB::A(amt) => &amt.currency,
174 Field32AB::B(amt) => &amt.currency,
175 };
176
177 if cheque_currency != first_currency {
178 return Some(SwiftValidationError::content_error(
179 "C02",
180 "32a",
181 cheque_currency,
182 &format!(
183 "Cheque {}: Currency code in field 32a ({}) must be the same as in other occurrences ({})",
184 idx + 1,
185 cheque_currency,
186 first_currency
187 ),
188 "The currency code in the amount field 32a must be the same for all occurrences of this field in the message",
189 ));
190 }
191 }
192
193 None
194 }
195
196 pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
199 let mut all_errors = Vec::new();
200
201 if let Some(error) = self.validate_c1_max_repetitions() {
203 all_errors.push(error);
204 if stop_on_first_error {
205 return all_errors;
206 }
207 }
208
209 if let Some(error) = self.validate_c2_currency_consistency() {
211 all_errors.push(error);
212 }
213
214 all_errors
215 }
216}
217
218impl crate::traits::SwiftMessageBody for MT110 {
219 fn message_type() -> &'static str {
220 "110"
221 }
222
223 fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
224 MT110::parse_from_block4(block4)
226 }
227
228 fn to_mt_string(&self) -> String {
229 let mut result = String::new();
230
231 append_field(&mut result, &self.field_20);
233 append_optional_field(&mut result, &self.field_53a);
234 append_optional_field(&mut result, &self.field_54a);
235 append_optional_field(&mut result, &self.field_72);
236
237 for cheque in &self.cheques {
239 append_field(&mut result, &cheque.field_21);
240 append_field(&mut result, &cheque.field_30);
241 append_field(&mut result, &cheque.field_32);
242 append_optional_field(&mut result, &cheque.field_50);
243 append_optional_field(&mut result, &cheque.field_52);
244 append_field(&mut result, &cheque.field_59);
245 }
246
247 finalize_mt_string(result, false)
248 }
249
250 fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
251 MT110::validate_network_rules(self, stop_on_first_error)
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn test_validate_c2_currency_consistency_pass() {
262 let input = r#":20:TESTREF123
264:21:CHQ001
265:30:250101
266:32B:USD1234,56
267:59:JOHN DOE
268123 MAIN ST
269NEW YORK
270:21:CHQ002
271:30:250102
272:32B:USD2345,67
273:59:JANE SMITH
274456 ELM ST
275BOSTON
276-"#;
277
278 let msg = MT110::parse_from_block4(input).expect("Should parse successfully");
279 let errors = msg.validate_network_rules(false);
280 assert!(
281 errors.is_empty(),
282 "Should have no validation errors for consistent currencies"
283 );
284 }
285
286 #[test]
287 fn test_validate_c2_currency_consistency_fail() {
288 let input = r#":20:TESTREF123
290:21:CHQ001
291:30:250101
292:32B:USD1234,56
293:59:JOHN DOE
294123 MAIN ST
295NEW YORK
296:21:CHQ002
297:30:250102
298:32B:EUR2345,67
299:59:JANE SMITH
300456 ELM ST
301BOSTON
302-"#;
303
304 let msg = MT110::parse_from_block4(input).expect("Should parse successfully");
305 let errors = msg.validate_network_rules(false);
306 assert_eq!(errors.len(), 1, "Should have exactly one validation error");
307 assert_eq!(errors[0].code(), "C02");
308 assert!(errors[0].message().contains("Currency code in field 32a"));
309 }
310
311 #[test]
312 fn test_validate_c1_max_repetitions() {
313 let mut cheque_details = String::new();
315 for i in 1..=11 {
316 cheque_details.push_str(&format!(
317 r#":21:CHQ{:03}
318:30:250101
319:32B:USD100,00
320:59:PAYEE {}
321ADDRESS LINE
322CITY
323"#,
324 i, i
325 ));
326 }
327
328 let input = format!(":20:TESTREF123\n{}-", cheque_details);
329
330 let msg = MT110::parse_from_block4(&input).expect("Should parse successfully");
331 let errors = msg.validate_network_rules(false);
332 assert_eq!(errors.len(), 1, "Should have exactly one validation error");
333 assert_eq!(errors[0].code(), "T10");
334 assert!(errors[0].message().contains("maximum 10 occurrences"));
335 }
336
337 #[test]
338 fn test_validate_stop_on_first_error() {
339 let mut cheque_details = String::new();
341 for i in 1..=11 {
342 let currency = if i % 2 == 0 { "EUR" } else { "USD" };
343 cheque_details.push_str(&format!(
344 r#":21:CHQ{:03}
345:30:250101
346:32B:{}100,00
347:59:PAYEE {}
348ADDRESS LINE
349CITY
350"#,
351 i, currency, i
352 ));
353 }
354
355 let input = format!(":20:TESTREF123\n{}-", cheque_details);
356
357 let msg = MT110::parse_from_block4(&input).expect("Should parse successfully");
358
359 let errors_stop = msg.validate_network_rules(true);
361 assert_eq!(errors_stop.len(), 1, "Should stop after first error");
362
363 let errors_all = msg.validate_network_rules(false);
365 assert_eq!(errors_all.len(), 2, "Should collect all errors");
366 assert!(errors_all.iter().any(|e| e.code() == "T10"));
367 assert!(errors_all.iter().any(|e| e.code() == "C02"));
368 }
369}