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 finalize_mt_string(result, false)
114 }
115
116 const VALID_MESSAGE_TYPES: &'static [&'static str] = &["940", "941", "942", "950"];
122
123 fn validate_t88_message_type(&self) -> Vec<SwiftValidationError> {
130 let mut errors = Vec::new();
131
132 for (idx, seq) in self.sequence.iter().enumerate() {
133 let type_code = &seq.field_12.type_code;
134 if !Self::VALID_MESSAGE_TYPES.contains(&type_code.as_str()) {
135 errors.push(SwiftValidationError::format_error(
136 "T88",
137 "12",
138 type_code,
139 &format!("One of: {}", Self::VALID_MESSAGE_TYPES.join(", ")),
140 &format!(
141 "Sequence {}: Field 12 message type '{}' is not valid. Valid types: {}",
142 idx + 1,
143 type_code,
144 Self::VALID_MESSAGE_TYPES.join(", ")
145 ),
146 ));
147 }
148 }
149
150 errors
151 }
152
153 fn validate_c1_field_34f_requirement(&self) -> Vec<SwiftValidationError> {
156 let mut errors = Vec::new();
157
158 for (idx, seq) in self.sequence.iter().enumerate() {
159 if seq.field_12.type_code == "942" && seq.floor_limit_debit.is_none() {
160 errors.push(SwiftValidationError::business_error(
161 "C22",
162 "34F",
163 vec!["12".to_string()],
164 &format!(
165 "Sequence {}: Field 34F (Debit/Debit and Credit Floor Limit) is mandatory when field 12 contains '942'",
166 idx + 1
167 ),
168 "When requesting MT 942 (Interim Transaction Report), at least the debit floor limit must be specified",
169 ));
170 }
171 }
172
173 errors
174 }
175
176 fn validate_c2_dc_mark_usage(&self) -> Vec<SwiftValidationError> {
180 let mut errors = Vec::new();
181
182 for (idx, seq) in self.sequence.iter().enumerate() {
183 let has_debit = seq.floor_limit_debit.is_some();
184 let has_credit = seq.floor_limit_credit.is_some();
185
186 if has_debit && !has_credit {
187 if let Some(ref debit_field) = seq.floor_limit_debit
189 && debit_field.indicator.is_some()
190 {
191 errors.push(SwiftValidationError::business_error(
192 "C23",
193 "34F",
194 vec![],
195 &format!(
196 "Sequence {}: D/C Mark must not be used when only one field 34F is present",
197 idx + 1
198 ),
199 "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",
200 ));
201 }
202 } else if has_debit && has_credit {
203 let debit_field = seq.floor_limit_debit.as_ref().unwrap();
205 let credit_field = seq.floor_limit_credit.as_ref().unwrap();
206
207 if debit_field.indicator != Some('D') {
208 errors.push(SwiftValidationError::business_error(
209 "C23",
210 "34F",
211 vec![],
212 &format!(
213 "Sequence {}: First field 34F must contain D/C Mark 'D' when both floor limits are present",
214 idx + 1
215 ),
216 "When both fields 34F are present, the first field (debit floor limit) must contain D/C Mark = 'D'",
217 ));
218 }
219
220 if credit_field.indicator != Some('C') {
221 errors.push(SwiftValidationError::business_error(
222 "C23",
223 "34F",
224 vec![],
225 &format!(
226 "Sequence {}: Second field 34F must contain D/C Mark 'C' when both floor limits are present",
227 idx + 1
228 ),
229 "When both fields 34F are present, the second field (credit floor limit) must contain D/C Mark = 'C'",
230 ));
231 }
232 }
233 }
234
235 errors
236 }
237
238 fn validate_c3_currency_consistency(&self) -> Vec<SwiftValidationError> {
241 let mut errors = Vec::new();
242
243 for (idx, seq) in self.sequence.iter().enumerate() {
244 if let (Some(debit_field), Some(credit_field)) =
245 (&seq.floor_limit_debit, &seq.floor_limit_credit)
246 && debit_field.currency != credit_field.currency
247 {
248 errors.push(SwiftValidationError::business_error(
249 "C40",
250 "34F",
251 vec![],
252 &format!(
253 "Sequence {}: Currency code must be the same for all field 34F occurrences. Found '{}' and '{}'",
254 idx + 1,
255 debit_field.currency,
256 credit_field.currency
257 ),
258 "Within each repetitive sequence, the currency code must be the same for each occurrence of field 34F",
259 ));
260 }
261 }
262
263 errors
264 }
265
266 pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
269 let mut all_errors = Vec::new();
270
271 let t88_errors = self.validate_t88_message_type();
273 all_errors.extend(t88_errors);
274 if stop_on_first_error && !all_errors.is_empty() {
275 return all_errors;
276 }
277
278 let c1_errors = self.validate_c1_field_34f_requirement();
280 all_errors.extend(c1_errors);
281 if stop_on_first_error && !all_errors.is_empty() {
282 return all_errors;
283 }
284
285 let c2_errors = self.validate_c2_dc_mark_usage();
287 all_errors.extend(c2_errors);
288 if stop_on_first_error && !all_errors.is_empty() {
289 return all_errors;
290 }
291
292 let c3_errors = self.validate_c3_currency_consistency();
294 all_errors.extend(c3_errors);
295
296 all_errors
297 }
298}
299
300impl crate::traits::SwiftMessageBody for MT920 {
301 fn message_type() -> &'static str {
302 "920"
303 }
304
305 fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
306 MT920::parse_from_block4(block4)
308 }
309
310 fn to_mt_string(&self) -> String {
311 MT920::to_mt_string(self)
313 }
314
315 fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
316 MT920::validate_network_rules(self, stop_on_first_error)
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn test_mt920_parse() {
327 let mt920_text = r#":20:REQ123456
328:12:100
329:25:/GB12ABCD12345678901234
330:12:200
331:25:/US98EFGH98765432109876
332-"#;
333 let result = MT920::parse_from_block4(mt920_text);
334 if let Err(ref e) = result {
335 eprintln!("MT920 parse error: {:?}", e);
336 }
337 assert!(result.is_ok());
338 let mt920 = result.unwrap();
339 assert_eq!(mt920.field_20.reference, "REQ123456");
340 assert_eq!(mt920.sequence.len(), 2);
341 assert_eq!(mt920.sequence[0].field_12.type_code, "100");
342 assert_eq!(mt920.sequence[1].field_12.type_code, "200");
343 }
344
345 #[test]
346 fn test_mt920_validation() {
347 let mt920_text = r#":20:REQ123456
349-"#;
350 let result = MT920::parse(mt920_text);
351 assert!(result.is_err());
352 assert!(
353 result
354 .unwrap_err()
355 .to_string()
356 .contains("At least one sequence")
357 );
358 }
359
360 #[test]
361 fn test_mt920_json_deserialization() {
362 let json = r##"{
364 "20": {
365 "reference": "REQ123456"
366 },
367 "#": [
368 {
369 "12": {
370 "type_code": "940"
371 },
372 "25": {
373 "authorisation": "1234567890"
374 }
375 }
376 ]
377 }"##;
378
379 let result = serde_json::from_str::<MT920>(json);
380 if let Err(ref e) = result {
381 eprintln!("MT920 JSON deserialization error: {}", e);
382 }
383 assert!(result.is_ok(), "Failed to deserialize MT920 from JSON");
384 let mt920 = result.unwrap();
385 assert_eq!(mt920.field_20.reference, "REQ123456");
386 assert_eq!(mt920.sequence.len(), 1);
387 assert_eq!(mt920.sequence[0].field_12.type_code, "940");
388 assert_eq!(mt920.sequence[0].field_25.authorisation, "1234567890");
389 }
390
391 #[test]
392 fn test_mt920_swift_message_json() {
393 use crate::swift_message::SwiftMessage;
394
395 let json = r##"{
397 "basic_header": {
398 "application_id": "F",
399 "service_id": "01",
400 "sender_bic": "DEUTDEFF",
401 "logical_terminal": "DEUTDEFFXXXX",
402 "session_number": "0001",
403 "sequence_number": "000123"
404 },
405 "application_header": {
406 "direction": "I",
407 "message_type": "920",
408 "receiver_bic": "DEUTDEFF",
409 "destination_address": "DEUTDEFFXXXX",
410 "priority": "N"
411 },
412 "message_type": "920",
413 "fields": {
414 "20": {
415 "reference": "REQ123456"
416 },
417 "#": [
418 {
419 "12": {
420 "type_code": "940"
421 },
422 "25": {
423 "authorisation": "1234567890"
424 }
425 }
426 ]
427 }
428 }"##;
429
430 let result = serde_json::from_str::<SwiftMessage<MT920>>(json);
431 if let Err(ref e) = result {
432 eprintln!("SwiftMessage<MT920> JSON deserialization error: {}", e);
433 }
434 assert!(
435 result.is_ok(),
436 "Failed to deserialize SwiftMessage<MT920> from JSON"
437 );
438 let swift_msg = result.unwrap();
439 assert_eq!(swift_msg.message_type, "920");
440 assert_eq!(swift_msg.fields.field_20.reference, "REQ123456");
441 assert_eq!(swift_msg.fields.sequence.len(), 1);
442 }
443}