swift_mt_message/messages/
mt940.rs1use crate::errors::SwiftValidationError;
2use crate::fields::*;
3use crate::parser::utils::*;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub struct MT940 {
15 #[serde(rename = "20")]
17 pub field_20: Field20,
18
19 #[serde(rename = "21", skip_serializing_if = "Option::is_none")]
21 pub field_21: Option<Field21NoOption>,
22
23 #[serde(rename = "25")]
25 pub field_25: Field25NoOption,
26
27 #[serde(rename = "28C")]
29 pub field_28c: Field28C,
30
31 #[serde(rename = "60F")]
33 pub field_60f: Field60F,
34
35 #[serde(rename = "statement_lines")]
37 pub statement_lines: Vec<MT940StatementLine>,
38
39 #[serde(rename = "62F")]
41 pub field_62f: Field62F,
42
43 #[serde(rename = "64", skip_serializing_if = "Option::is_none")]
45 pub field_64: Option<Field64>,
46
47 #[serde(rename = "65", skip_serializing_if = "Option::is_none")]
49 pub field_65: Option<Vec<Field65>>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
54pub struct MT940StatementLine {
55 #[serde(rename = "61")]
57 pub field_61: Field61,
58
59 #[serde(rename = "86", skip_serializing_if = "Option::is_none")]
61 pub field_86: Option<Field86>,
62}
63
64impl MT940 {
65 pub fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
67 let mut parser = crate::parser::MessageParser::new(block4, "940");
68
69 let field_20 = parser.parse_field::<Field20>("20")?;
71 let field_21 = parser.parse_optional_field::<Field21NoOption>("21")?;
72 let field_25 = parser.parse_field::<Field25NoOption>("25")?;
73 let field_28c = parser.parse_field::<Field28C>("28C")?;
74 let field_60f = parser.parse_field::<Field60F>("60F")?;
75
76 parser = parser.with_duplicates(true);
78
79 let mut statement_lines = Vec::new();
81
82 while parser.detect_field("61") && statement_lines.len() < 500 {
83 let field_61 = parser.parse_field::<Field61>("61")?;
84 let field_86 = parser.parse_optional_field::<Field86>("86")?;
85
86 statement_lines.push(MT940StatementLine { field_61, field_86 });
87 }
88
89 parser = parser.with_duplicates(false);
91
92 if statement_lines.is_empty() {
94 return Err(crate::errors::ParseError::InvalidFormat {
95 message: "MT940: At least one statement line (field 61) is required".to_string(),
96 });
97 }
98
99 let field_62f = parser.parse_field::<Field62F>("62F")?;
101
102 let field_64 = parser.parse_optional_field::<Field64>("64")?;
104
105 parser = parser.with_duplicates(true);
107 let mut forward_balances = Vec::new();
108 while let Ok(field_65) = parser.parse_field::<Field65>("65") {
109 forward_balances.push(field_65);
110 }
111
112 let field_65 = if forward_balances.is_empty() {
113 None
114 } else {
115 Some(forward_balances)
116 };
117
118 Ok(MT940 {
119 field_20,
120 field_21,
121 field_25,
122 field_28c,
123 field_60f,
124 statement_lines,
125 field_62f,
126 field_64,
127 field_65,
128 })
129 }
130
131 pub fn validate_instance(&self) -> Result<(), crate::errors::ParseError> {
133 if self.statement_lines.is_empty() || self.statement_lines.len() > 500 {
135 return Err(crate::errors::ParseError::InvalidFormat {
136 message: format!(
137 "MT940: Statement lines must occur 1-500 times, found {}",
138 self.statement_lines.len()
139 ),
140 });
141 }
142
143 Ok(())
146 }
147
148 fn get_field_60f_currency(&self) -> &str {
158 &self.field_60f.currency
159 }
160
161 fn get_field_62f_currency(&self) -> &str {
163 &self.field_62f.currency
164 }
165
166 fn get_field_64_currency(&self) -> Option<&str> {
168 self.field_64.as_ref().map(|f| f.currency.as_str())
169 }
170
171 fn get_currency_prefix(currency: &str) -> &str {
173 if currency.len() >= 2 {
174 ¤cy[0..2]
175 } else {
176 currency
177 }
178 }
179
180 fn validate_c1_field_86_follows_61(&self) -> Vec<SwiftValidationError> {
195 Vec::new()
200 }
201
202 fn validate_c2_currency_consistency(&self) -> Vec<SwiftValidationError> {
206 let mut errors = Vec::new();
207
208 let reference_currency = self.get_field_60f_currency();
210 let reference_prefix = Self::get_currency_prefix(reference_currency);
211
212 let field_62f_currency = self.get_field_62f_currency();
214 let field_62f_prefix = Self::get_currency_prefix(field_62f_currency);
215
216 if field_62f_prefix != reference_prefix {
217 errors.push(SwiftValidationError::content_error(
218 "C27",
219 "62F",
220 field_62f_currency,
221 &format!(
222 "Currency prefix in field 62F ('{}') must match the prefix in field 60F ('{}') - found '{}' vs '{}'",
223 field_62f_prefix, reference_prefix, field_62f_currency, reference_currency
224 ),
225 "The first two characters of the currency code must be the same in fields 60a, 62a, 64 and 65",
226 ));
227 }
228
229 if let Some(field_64_currency) = self.get_field_64_currency() {
231 let field_64_prefix = Self::get_currency_prefix(field_64_currency);
232
233 if field_64_prefix != reference_prefix {
234 errors.push(SwiftValidationError::content_error(
235 "C27",
236 "64",
237 field_64_currency,
238 &format!(
239 "Currency prefix in field 64 ('{}') must match the prefix in field 60F ('{}') - found '{}' vs '{}'",
240 field_64_prefix, reference_prefix, field_64_currency, reference_currency
241 ),
242 "The first two characters of the currency code must be the same in fields 60a, 62a, 64 and 65",
243 ));
244 }
245 }
246
247 if let Some(field_65_vec) = &self.field_65 {
249 for (idx, field_65) in field_65_vec.iter().enumerate() {
250 let field_65_currency = &field_65.currency;
251 let field_65_prefix = Self::get_currency_prefix(field_65_currency);
252
253 if field_65_prefix != reference_prefix {
254 errors.push(SwiftValidationError::content_error(
255 "C27",
256 "65",
257 field_65_currency,
258 &format!(
259 "Currency prefix in field 65 occurrence {} ('{}') must match the prefix in field 60F ('{}') - found '{}' vs '{}'",
260 idx + 1, field_65_prefix, reference_prefix, field_65_currency, reference_currency
261 ),
262 "The first two characters of the currency code must be the same in fields 60a, 62a, 64 and 65",
263 ));
264 }
265 }
266 }
267
268 errors
269 }
270
271 pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
274 let mut all_errors = Vec::new();
275
276 let c1_errors = self.validate_c1_field_86_follows_61();
278 all_errors.extend(c1_errors);
279 if stop_on_first_error && !all_errors.is_empty() {
280 return all_errors;
281 }
282
283 let c2_errors = self.validate_c2_currency_consistency();
285 all_errors.extend(c2_errors);
286
287 all_errors
288 }
289}
290
291impl crate::traits::SwiftMessageBody for MT940 {
292 fn message_type() -> &'static str {
293 "940"
294 }
295
296 fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
297 MT940::parse_from_block4(block4)
299 }
300
301 fn to_mt_string(&self) -> String {
302 MT940::to_mt_string(self)
304 }
305
306 fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
307 MT940::validate_network_rules(self, stop_on_first_error)
309 }
310}
311
312impl MT940 {
313 pub fn to_mt_string(&self) -> String {
315 let mut result = String::new();
316
317 append_field(&mut result, &self.field_20);
318 append_optional_field(&mut result, &self.field_21);
319 append_field(&mut result, &self.field_25);
320 append_field(&mut result, &self.field_28c);
321 append_field(&mut result, &self.field_60f);
322
323 for statement_line in &self.statement_lines {
325 append_field(&mut result, &statement_line.field_61);
326 append_optional_field(&mut result, &statement_line.field_86);
327 }
328
329 append_field(&mut result, &self.field_62f);
330 append_optional_field(&mut result, &self.field_64);
331 append_vec_field(&mut result, &self.field_65);
332
333 finalize_mt_string(result, false)
334 }
335}