swift_mt_message/messages/
mt920.rs

1use crate::errors::SwiftValidationError;
2use crate::fields::{field34::Field34F, *};
3use crate::parser::utils::*;
4use serde::{Deserialize, Serialize};
5
6/// **MT920: Request Message**
7///
8/// Requests specific account information or statement messages.
9///
10/// **Usage:** Statement requests, account information inquiries
11/// **Category:** Category 9 (Cash Management & Customer Status)
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13pub struct MT920 {
14    /// Transaction Reference Number (Field 20)
15    #[serde(rename = "20")]
16    pub field_20: Field20,
17
18    /// Repetitive sequence (1-100 occurrences)
19    #[serde(rename = "#")]
20    pub sequence: Vec<MT920Sequence>,
21}
22
23/// Repetitive sequence for MT920 request message
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
25pub struct MT920Sequence {
26    /// Message Type (Field 12)
27    #[serde(rename = "12")]
28    pub field_12: Field12,
29
30    /// Account Identification (Field 25)
31    #[serde(rename = "25")]
32    pub field_25: Field25,
33
34    /// Debit Floor Limit (Field 34F)
35    #[serde(rename = "34F_1")]
36    pub floor_limit_debit: Option<Field34F>,
37
38    /// Credit Floor Limit (Field 34F)
39    #[serde(rename = "34F_2")]
40    pub floor_limit_credit: Option<Field34F>,
41}
42
43impl MT920 {
44    /// Parse message from Block 4 content
45    pub fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
46        let mut parser = crate::parser::MessageParser::new(block4, "920");
47
48        // Parse header field
49        let field_20 = parser.parse_field::<Field20>("20")?;
50
51        // Parse repetitive sequences
52        let mut sequence = Vec::new();
53
54        // Enable duplicate field handling for repeated sequences
55        parser = parser.with_duplicates(true);
56
57        // Detect and parse each sequence (field 12 marks the start of a new sequence)
58        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            // Apply max repetitions validation
65            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        // Validate at least one sequence is present
80        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 all content is consumed
87        verify_parser_complete(&parser)?;
88
89        Ok(Self { field_20, sequence })
90    }
91
92    /// Parse from SWIFT MT text format
93    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    /// Convert to SWIFT MT text format
99    pub fn to_mt_string(&self) -> String {
100        let mut result = String::new();
101
102        // Add header field
103        append_field(&mut result, &self.field_20);
104
105        // Add sequences
106        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    // ========================================================================
118    // NETWORK VALIDATION RULES (SR 2025 MT920)
119    // ========================================================================
120
121    /// Field 12 valid message type codes for MT920
122    const VALID_MESSAGE_TYPES: &'static [&'static str] = &["940", "941", "942", "950"];
123
124    // ========================================================================
125    // VALIDATION RULES (T88, C1, C2, C3)
126    // ========================================================================
127
128    /// T88: Field 12 Message Type Validation (Error code: T88)
129    /// Field 12 must contain one of: 940, 941, 942, 950
130    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    /// C1: Field 34F Requirement for MT 942 Requests (Error code: C22)
155    /// If field 12 contains '942', at least field 34F Debit/(Debit and Credit) must be present
156    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    /// C2: Field 34F D/C Mark Usage (Error code: C23)
178    /// When only one 34F present: D/C Mark must NOT be used
179    /// When both 34F present: First must have 'D', second must have 'C'
180    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                // Only debit field present - D/C Mark must NOT be used
189                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                // Both fields present - First must have 'D', second must have 'C'
205                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    /// C3: Currency Consistency Within Repetitive Sequence (Error code: C40)
240    /// Currency code must be the same for each occurrence of field 34F within each sequence
241    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    /// Main validation method - validates all network rules
268    /// Returns array of validation errors, respects stop_on_first_error flag
269    pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
270        let mut all_errors = Vec::new();
271
272        // T88: Message Type Validation
273        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        // C1: Field 34F Requirement for MT 942
280        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        // C2: Field 34F D/C Mark Usage
287        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        // C3: Currency Consistency
294        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        // Call the existing public method implementation
308        MT920::parse_from_block4(block4)
309    }
310
311    fn to_mt_string(&self) -> String {
312        // Call the existing public method implementation
313        MT920::to_mt_string(self)
314    }
315
316    fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
317        // Call the existing public method implementation
318        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        // Test empty sequence - should fail
349        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        // Test JSON deserialization for MT920
364        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        // Test complete SwiftMessage<MT920> JSON deserialization
397        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}