swift_mt_message/messages/
mt920.rs1use crate::errors::SwiftValidationError;
2use crate::fields::{field34::Field34F, *};
3use crate::parser::utils::*;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13pub struct MT920 {
14 #[serde(rename = "20")]
16 pub field_20: Field20,
17
18 #[serde(rename = "#")]
20 pub sequence: Vec<MT920Sequence>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
25pub struct MT920Sequence {
26 #[serde(rename = "12")]
28 pub field_12: Field12,
29
30 #[serde(rename = "25")]
32 pub field_25: Field25,
33
34 #[serde(rename = "34F_1")]
36 pub floor_limit_debit: Option<Field34F>,
37
38 #[serde(rename = "34F_2")]
40 pub floor_limit_credit: Option<Field34F>,
41}
42
43impl MT920 {
44 pub fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
46 let mut parser = crate::parser::MessageParser::new(block4, "920");
47
48 let field_20 = parser.parse_field::<Field20>("20")?;
50
51 let mut sequence = Vec::new();
53
54 parser = parser.with_duplicates(true);
56
57 while parser.detect_field("12") {
59 let field_12 = parser.parse_field::<Field12>("12")?;
60 let field_25 = parser.parse_field::<Field25>("25")?;
61 let floor_limit_debit = parser.parse_optional_field::<Field34F>("34F")?;
62 let floor_limit_credit = parser.parse_optional_field::<Field34F>("34F")?;
63
64 if sequence.len() >= 100 {
66 return Err(crate::errors::ParseError::InvalidFormat {
67 message: "Maximum 100 repetitions allowed".to_string(),
68 });
69 }
70
71 sequence.push(MT920Sequence {
72 field_12,
73 field_25,
74 floor_limit_debit,
75 floor_limit_credit,
76 });
77 }
78
79 if sequence.is_empty() {
81 return Err(crate::errors::ParseError::InvalidFormat {
82 message: "At least one sequence is required in MT920".to_string(),
83 });
84 }
85
86 verify_parser_complete(&parser)?;
88
89 Ok(Self { field_20, sequence })
90 }
91
92 pub fn parse(input: &str) -> Result<Self, crate::errors::ParseError> {
94 let block4 = extract_block4(input)?;
95 Self::parse_from_block4(&block4)
96 }
97
98 pub fn to_mt_string(&self) -> String {
100 let mut result = String::new();
101
102 append_field(&mut result, &self.field_20);
104
105 for seq in &self.sequence {
107 append_field(&mut result, &seq.field_12);
108 append_field(&mut result, &seq.field_25);
109 append_optional_field(&mut result, &seq.floor_limit_debit);
110 append_optional_field(&mut result, &seq.floor_limit_credit);
111 }
112
113 result.push('-');
114 result
115 }
116
117 const VALID_MESSAGE_TYPES: &'static [&'static str] = &["940", "941", "942", "950"];
123
124 fn validate_t88_message_type(&self) -> Vec<SwiftValidationError> {
131 let mut errors = Vec::new();
132
133 for (idx, seq) in self.sequence.iter().enumerate() {
134 let type_code = &seq.field_12.type_code;
135 if !Self::VALID_MESSAGE_TYPES.contains(&type_code.as_str()) {
136 errors.push(SwiftValidationError::format_error(
137 "T88",
138 "12",
139 type_code,
140 &format!("One of: {}", Self::VALID_MESSAGE_TYPES.join(", ")),
141 &format!(
142 "Sequence {}: Field 12 message type '{}' is not valid. Valid types: {}",
143 idx + 1,
144 type_code,
145 Self::VALID_MESSAGE_TYPES.join(", ")
146 ),
147 ));
148 }
149 }
150
151 errors
152 }
153
154 fn validate_c1_field_34f_requirement(&self) -> Vec<SwiftValidationError> {
157 let mut errors = Vec::new();
158
159 for (idx, seq) in self.sequence.iter().enumerate() {
160 if seq.field_12.type_code == "942" && seq.floor_limit_debit.is_none() {
161 errors.push(SwiftValidationError::business_error(
162 "C22",
163 "34F",
164 vec!["12".to_string()],
165 &format!(
166 "Sequence {}: Field 34F (Debit/Debit and Credit Floor Limit) is mandatory when field 12 contains '942'",
167 idx + 1
168 ),
169 "When requesting MT 942 (Interim Transaction Report), at least the debit floor limit must be specified",
170 ));
171 }
172 }
173
174 errors
175 }
176
177 fn validate_c2_dc_mark_usage(&self) -> Vec<SwiftValidationError> {
181 let mut errors = Vec::new();
182
183 for (idx, seq) in self.sequence.iter().enumerate() {
184 let has_debit = seq.floor_limit_debit.is_some();
185 let has_credit = seq.floor_limit_credit.is_some();
186
187 if has_debit && !has_credit {
188 if let Some(ref debit_field) = seq.floor_limit_debit
190 && debit_field.indicator.is_some()
191 {
192 errors.push(SwiftValidationError::business_error(
193 "C23",
194 "34F",
195 vec![],
196 &format!(
197 "Sequence {}: D/C Mark must not be used when only one field 34F is present",
198 idx + 1
199 ),
200 "When only one field 34F is present, the floor limit applies to both debit and credit amounts, and the D/C Mark subfield must not be used",
201 ));
202 }
203 } else if has_debit && has_credit {
204 let debit_field = seq.floor_limit_debit.as_ref().unwrap();
206 let credit_field = seq.floor_limit_credit.as_ref().unwrap();
207
208 if debit_field.indicator != Some('D') {
209 errors.push(SwiftValidationError::business_error(
210 "C23",
211 "34F",
212 vec![],
213 &format!(
214 "Sequence {}: First field 34F must contain D/C Mark 'D' when both floor limits are present",
215 idx + 1
216 ),
217 "When both fields 34F are present, the first field (debit floor limit) must contain D/C Mark = 'D'",
218 ));
219 }
220
221 if credit_field.indicator != Some('C') {
222 errors.push(SwiftValidationError::business_error(
223 "C23",
224 "34F",
225 vec![],
226 &format!(
227 "Sequence {}: Second field 34F must contain D/C Mark 'C' when both floor limits are present",
228 idx + 1
229 ),
230 "When both fields 34F are present, the second field (credit floor limit) must contain D/C Mark = 'C'",
231 ));
232 }
233 }
234 }
235
236 errors
237 }
238
239 fn validate_c3_currency_consistency(&self) -> Vec<SwiftValidationError> {
242 let mut errors = Vec::new();
243
244 for (idx, seq) in self.sequence.iter().enumerate() {
245 if let (Some(debit_field), Some(credit_field)) =
246 (&seq.floor_limit_debit, &seq.floor_limit_credit)
247 && debit_field.currency != credit_field.currency
248 {
249 errors.push(SwiftValidationError::business_error(
250 "C40",
251 "34F",
252 vec![],
253 &format!(
254 "Sequence {}: Currency code must be the same for all field 34F occurrences. Found '{}' and '{}'",
255 idx + 1,
256 debit_field.currency,
257 credit_field.currency
258 ),
259 "Within each repetitive sequence, the currency code must be the same for each occurrence of field 34F",
260 ));
261 }
262 }
263
264 errors
265 }
266
267 pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
270 let mut all_errors = Vec::new();
271
272 let t88_errors = self.validate_t88_message_type();
274 all_errors.extend(t88_errors);
275 if stop_on_first_error && !all_errors.is_empty() {
276 return all_errors;
277 }
278
279 let c1_errors = self.validate_c1_field_34f_requirement();
281 all_errors.extend(c1_errors);
282 if stop_on_first_error && !all_errors.is_empty() {
283 return all_errors;
284 }
285
286 let c2_errors = self.validate_c2_dc_mark_usage();
288 all_errors.extend(c2_errors);
289 if stop_on_first_error && !all_errors.is_empty() {
290 return all_errors;
291 }
292
293 let c3_errors = self.validate_c3_currency_consistency();
295 all_errors.extend(c3_errors);
296
297 all_errors
298 }
299}
300
301impl crate::traits::SwiftMessageBody for MT920 {
302 fn message_type() -> &'static str {
303 "920"
304 }
305
306 fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
307 MT920::parse_from_block4(block4)
309 }
310
311 fn to_mt_string(&self) -> String {
312 MT920::to_mt_string(self)
314 }
315
316 fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
317 MT920::validate_network_rules(self, stop_on_first_error)
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[test]
327 fn test_mt920_parse() {
328 let mt920_text = r#":20:REQ123456
329:12:100
330:25:/GB12ABCD12345678901234
331:12:200
332:25:/US98EFGH98765432109876
333-"#;
334 let result = MT920::parse_from_block4(mt920_text);
335 if let Err(ref e) = result {
336 eprintln!("MT920 parse error: {:?}", e);
337 }
338 assert!(result.is_ok());
339 let mt920 = result.unwrap();
340 assert_eq!(mt920.field_20.reference, "REQ123456");
341 assert_eq!(mt920.sequence.len(), 2);
342 assert_eq!(mt920.sequence[0].field_12.type_code, "100");
343 assert_eq!(mt920.sequence[1].field_12.type_code, "200");
344 }
345
346 #[test]
347 fn test_mt920_validation() {
348 let mt920_text = r#":20:REQ123456
350-"#;
351 let result = MT920::parse(mt920_text);
352 assert!(result.is_err());
353 assert!(
354 result
355 .unwrap_err()
356 .to_string()
357 .contains("At least one sequence")
358 );
359 }
360
361 #[test]
362 fn test_mt920_json_deserialization() {
363 let json = r##"{
365 "20": {
366 "reference": "REQ123456"
367 },
368 "#": [
369 {
370 "12": {
371 "type_code": "940"
372 },
373 "25": {
374 "authorisation": "1234567890"
375 }
376 }
377 ]
378 }"##;
379
380 let result = serde_json::from_str::<MT920>(json);
381 if let Err(ref e) = result {
382 eprintln!("MT920 JSON deserialization error: {}", e);
383 }
384 assert!(result.is_ok(), "Failed to deserialize MT920 from JSON");
385 let mt920 = result.unwrap();
386 assert_eq!(mt920.field_20.reference, "REQ123456");
387 assert_eq!(mt920.sequence.len(), 1);
388 assert_eq!(mt920.sequence[0].field_12.type_code, "940");
389 assert_eq!(mt920.sequence[0].field_25.authorisation, "1234567890");
390 }
391
392 #[test]
393 fn test_mt920_swift_message_json() {
394 use crate::swift_message::SwiftMessage;
395
396 let json = r##"{
398 "basic_header": {
399 "application_id": "F",
400 "service_id": "01",
401 "sender_bic": "DEUTDEFF",
402 "logical_terminal": "DEUTDEFFXXXX",
403 "session_number": "0001",
404 "sequence_number": "000123"
405 },
406 "application_header": {
407 "direction": "I",
408 "message_type": "920",
409 "receiver_bic": "DEUTDEFF",
410 "destination_address": "DEUTDEFFXXXX",
411 "priority": "N"
412 },
413 "message_type": "920",
414 "fields": {
415 "20": {
416 "reference": "REQ123456"
417 },
418 "#": [
419 {
420 "12": {
421 "type_code": "940"
422 },
423 "25": {
424 "authorisation": "1234567890"
425 }
426 }
427 ]
428 }
429 }"##;
430
431 let result = serde_json::from_str::<SwiftMessage<MT920>>(json);
432 if let Err(ref e) = result {
433 eprintln!("SwiftMessage<MT920> JSON deserialization error: {}", e);
434 }
435 assert!(
436 result.is_ok(),
437 "Failed to deserialize SwiftMessage<MT920> from JSON"
438 );
439 let swift_msg = result.unwrap();
440 assert_eq!(swift_msg.message_type, "920");
441 assert_eq!(swift_msg.fields.field_20.reference, "REQ123456");
442 assert_eq!(swift_msg.fields.sequence.len(), 1);
443 }
444}