1use crate::errors::SwiftValidationError;
2use crate::fields::*;
3use crate::parser::utils::*;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13pub struct MT941 {
14 #[serde(rename = "20")]
16 pub field_20: Field20,
17
18 #[serde(rename = "21", skip_serializing_if = "Option::is_none")]
20 pub field_21: Option<Field21NoOption>,
21
22 #[serde(rename = "25")]
24 pub field_25: Field25AccountIdentification,
25
26 #[serde(rename = "28")]
28 pub field_28: Field28,
29
30 #[serde(rename = "13D", skip_serializing_if = "Option::is_none")]
32 pub field_13d: Option<Field13D>,
33
34 #[serde(rename = "60F", skip_serializing_if = "Option::is_none")]
36 pub field_60f: Option<Field60F>,
37
38 #[serde(rename = "90D", skip_serializing_if = "Option::is_none")]
40 pub field_90d: Option<Field90D>,
41
42 #[serde(rename = "90C", skip_serializing_if = "Option::is_none")]
44 pub field_90c: Option<Field90C>,
45
46 #[serde(rename = "62F")]
48 pub field_62f: Field62F,
49
50 #[serde(rename = "64", skip_serializing_if = "Option::is_none")]
52 pub field_64: Option<Field64>,
53
54 #[serde(rename = "65", skip_serializing_if = "Option::is_none")]
56 pub field_65: Option<Vec<Field65>>,
57
58 #[serde(rename = "86", skip_serializing_if = "Option::is_none")]
60 pub field_86: Option<Field86>,
61}
62
63impl MT941 {
64 pub fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
66 let mut parser = crate::parser::MessageParser::new(block4, "941");
67
68 let field_20 = parser.parse_field::<Field20>("20")?;
70 let field_21 = parser.parse_optional_field::<Field21NoOption>("21")?;
71 let field_25 = parser.parse_field::<Field25AccountIdentification>("25")?;
72 let field_28 = parser.parse_field::<Field28>("28")?;
73
74 let field_13d = parser.parse_optional_field::<Field13D>("13D")?;
76
77 let field_60f = parser.parse_optional_field::<Field60F>("60F")?;
79
80 let field_90d = parser.parse_optional_field::<Field90D>("90D")?;
82 let field_90c = parser.parse_optional_field::<Field90C>("90C")?;
83
84 let field_62f = parser.parse_field::<Field62F>("62F")?;
86
87 let field_64 = parser.parse_optional_field::<Field64>("64")?;
89
90 let mut field_65_vec = Vec::new();
92 while parser.detect_field("65") {
93 if let Ok(field) = parser.parse_field::<Field65>("65") {
94 field_65_vec.push(field);
95 } else {
96 break;
97 }
98 }
99 let field_65 = if field_65_vec.is_empty() {
100 None
101 } else {
102 Some(field_65_vec)
103 };
104
105 let field_86 = parser.parse_optional_field::<Field86>("86")?;
107
108 Ok(MT941 {
109 field_20,
110 field_21,
111 field_25,
112 field_28,
113 field_13d,
114 field_60f,
115 field_90d,
116 field_90c,
117 field_62f,
118 field_64,
119 field_65,
120 field_86,
121 })
122 }
123
124 fn get_base_currency(&self) -> &str {
136 &self.field_62f.currency[0..2]
137 }
138
139 fn validate_c1_currency_consistency(
147 &self,
148 stop_on_first_error: bool,
149 ) -> Vec<SwiftValidationError> {
150 let mut errors = Vec::new();
151 let base_currency = self.get_base_currency();
152
153 if let Some(ref field_60f) = self.field_60f
155 && &field_60f.currency[0..2] != base_currency
156 {
157 errors.push(SwiftValidationError::content_error(
158 "C27",
159 "60F",
160 &field_60f.currency,
161 &format!(
162 "Currency code in field 60F ({}) must have the same first two characters as field 62F ({})",
163 &field_60f.currency[0..2],
164 base_currency
165 ),
166 "The first two characters of the three character currency code in fields 60F, 90D, 90C, 62F, 64 and 65 must be the same for all occurrences of these fields",
167 ));
168 if stop_on_first_error {
169 return errors;
170 }
171 }
172
173 if let Some(ref field_90d) = self.field_90d
175 && &field_90d.currency[0..2] != base_currency
176 {
177 errors.push(SwiftValidationError::content_error(
178 "C27",
179 "90D",
180 &field_90d.currency,
181 &format!(
182 "Currency code in field 90D ({}) must have the same first two characters as field 62F ({})",
183 &field_90d.currency[0..2],
184 base_currency
185 ),
186 "The first two characters of the three character currency code in fields 60F, 90D, 90C, 62F, 64 and 65 must be the same for all occurrences of these fields",
187 ));
188 if stop_on_first_error {
189 return errors;
190 }
191 }
192
193 if let Some(ref field_90c) = self.field_90c
195 && &field_90c.currency[0..2] != base_currency
196 {
197 errors.push(SwiftValidationError::content_error(
198 "C27",
199 "90C",
200 &field_90c.currency,
201 &format!(
202 "Currency code in field 90C ({}) must have the same first two characters as field 62F ({})",
203 &field_90c.currency[0..2],
204 base_currency
205 ),
206 "The first two characters of the three character currency code in fields 60F, 90D, 90C, 62F, 64 and 65 must be the same for all occurrences of these fields",
207 ));
208 if stop_on_first_error {
209 return errors;
210 }
211 }
212
213 if let Some(ref field_64) = self.field_64
215 && &field_64.currency[0..2] != base_currency
216 {
217 errors.push(SwiftValidationError::content_error(
218 "C27",
219 "64",
220 &field_64.currency,
221 &format!(
222 "Currency code in field 64 ({}) must have the same first two characters as field 62F ({})",
223 &field_64.currency[0..2],
224 base_currency
225 ),
226 "The first two characters of the three character currency code in fields 60F, 90D, 90C, 62F, 64 and 65 must be the same for all occurrences of these fields",
227 ));
228 if stop_on_first_error {
229 return errors;
230 }
231 }
232
233 if let Some(ref field_65_vec) = self.field_65 {
235 for (idx, field_65) in field_65_vec.iter().enumerate() {
236 if &field_65.currency[0..2] != base_currency {
237 errors.push(SwiftValidationError::content_error(
238 "C27",
239 "65",
240 &field_65.currency,
241 &format!(
242 "Currency code in field 65[{}] ({}) must have the same first two characters as field 62F ({})",
243 idx,
244 &field_65.currency[0..2],
245 base_currency
246 ),
247 "The first two characters of the three character currency code in fields 60F, 90D, 90C, 62F, 64 and 65 must be the same for all occurrences of these fields",
248 ));
249 if stop_on_first_error {
250 return errors;
251 }
252 }
253 }
254 }
255
256 errors
257 }
258
259 pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
262 let mut all_errors = Vec::new();
263
264 let c1_errors = self.validate_c1_currency_consistency(stop_on_first_error);
266 all_errors.extend(c1_errors);
267 if stop_on_first_error && !all_errors.is_empty() {
268 return all_errors;
269 }
270
271 all_errors
272 }
273}
274
275impl crate::traits::SwiftMessageBody for MT941 {
277 fn message_type() -> &'static str {
278 "941"
279 }
280
281 fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
282 Self::parse_from_block4(block4)
283 }
284
285 fn to_mt_string(&self) -> String {
286 let mut result = String::new();
287
288 append_field(&mut result, &self.field_20);
289 append_optional_field(&mut result, &self.field_21);
290 append_field(&mut result, &self.field_25);
291 append_field(&mut result, &self.field_28);
292 append_optional_field(&mut result, &self.field_13d);
293 append_optional_field(&mut result, &self.field_60f);
294 append_optional_field(&mut result, &self.field_90d);
295 append_optional_field(&mut result, &self.field_90c);
296 append_field(&mut result, &self.field_62f);
297 append_optional_field(&mut result, &self.field_64);
298 append_vec_field(&mut result, &self.field_65);
299 append_optional_field(&mut result, &self.field_86);
300
301 finalize_mt_string(result, false)
302 }
303
304 fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
305 MT941::validate_network_rules(self, stop_on_first_error)
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313 use crate::traits::SwiftField;
314
315 #[test]
316 fn test_mt941_validate_c1_currency_consistency_pass() {
317 let mt941 = MT941 {
319 field_20: Field20::parse("BALREP001").unwrap(),
320 field_21: None,
321 field_25: Field25AccountIdentification::parse("1234567890").unwrap(),
322 field_28: Field28::parse("1").unwrap(),
323 field_13d: None,
324 field_60f: Some(Field60F::parse("C251003EUR595771,95").unwrap()),
325 field_90d: Some(Field90D::parse("72EUR385920,").unwrap()),
326 field_90c: Some(Field90C::parse("44EUR450000,").unwrap()),
327 field_62f: Field62F::parse("C251003EUR659851,95").unwrap(),
328 field_64: Some(Field64::parse("C251003EUR480525,87").unwrap()),
329 field_65: Some(vec![Field65::parse("C251004EUR530691,95").unwrap()]),
330 field_86: None,
331 };
332
333 let errors = mt941.validate_network_rules(false);
334 assert!(
335 errors.is_empty(),
336 "Expected no validation errors, got: {:?}",
337 errors
338 );
339 }
340
341 #[test]
342 fn test_mt941_validate_c1_currency_consistency_fail_60f() {
343 let mt941 = MT941 {
345 field_20: Field20::parse("BALREP001").unwrap(),
346 field_21: None,
347 field_25: Field25AccountIdentification::parse("1234567890").unwrap(),
348 field_28: Field28::parse("1").unwrap(),
349 field_13d: None,
350 field_60f: Some(Field60F::parse("C251003USD595771,95").unwrap()), field_90d: None,
352 field_90c: None,
353 field_62f: Field62F::parse("C251003EUR659851,95").unwrap(),
354 field_64: None,
355 field_65: None,
356 field_86: None,
357 };
358
359 let errors = mt941.validate_network_rules(false);
360 assert_eq!(errors.len(), 1);
361 assert!(errors[0].message().contains("60F"));
362 assert!(errors[0].message().contains("US"));
363 assert!(errors[0].message().contains("EU"));
364 }
365
366 #[test]
367 fn test_mt941_validate_c1_currency_consistency_fail_multiple() {
368 let mt941 = MT941 {
370 field_20: Field20::parse("BALREP001").unwrap(),
371 field_21: None,
372 field_25: Field25AccountIdentification::parse("1234567890").unwrap(),
373 field_28: Field28::parse("1").unwrap(),
374 field_13d: None,
375 field_60f: Some(Field60F::parse("C251003USD595771,95").unwrap()), field_90d: Some(Field90D::parse("72GBP385920,").unwrap()), field_90c: Some(Field90C::parse("44JPY450000,").unwrap()), field_62f: Field62F::parse("C251003EUR659851,95").unwrap(), field_64: Some(Field64::parse("C251003CHF480525,87").unwrap()), field_65: Some(vec![
381 Field65::parse("C251004AUD530691,95").unwrap(), Field65::parse("C251005CAD530691,95").unwrap(), ]),
384 field_86: None,
385 };
386
387 let errors = mt941.validate_network_rules(false);
388 assert_eq!(errors.len(), 6); }
390
391 #[test]
392 fn test_mt941_validate_c1_stop_on_first_error() {
393 let mt941 = MT941 {
395 field_20: Field20::parse("BALREP001").unwrap(),
396 field_21: None,
397 field_25: Field25AccountIdentification::parse("1234567890").unwrap(),
398 field_28: Field28::parse("1").unwrap(),
399 field_13d: None,
400 field_60f: Some(Field60F::parse("C251003USD595771,95").unwrap()),
401 field_90d: Some(Field90D::parse("72GBP385920,").unwrap()),
402 field_90c: None,
403 field_62f: Field62F::parse("C251003EUR659851,95").unwrap(),
404 field_64: None,
405 field_65: None,
406 field_86: None,
407 };
408
409 let errors = mt941.validate_network_rules(true); assert_eq!(errors.len(), 1); }
412}