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        finalize_mt_string(result, false)
114    }
115
116    // ========================================================================
117    // NETWORK VALIDATION RULES (SR 2025 MT920)
118    // ========================================================================
119
120    /// Field 12 valid message type codes for MT920
121    const VALID_MESSAGE_TYPES: &'static [&'static str] = &["940", "941", "942", "950"];
122
123    // ========================================================================
124    // VALIDATION RULES (T88, C1, C2, C3)
125    // ========================================================================
126
127    /// T88: Field 12 Message Type Validation (Error code: T88)
128    /// Field 12 must contain one of: 940, 941, 942, 950
129    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    /// C1: Field 34F Requirement for MT 942 Requests (Error code: C22)
154    /// If field 12 contains '942', at least field 34F Debit/(Debit and Credit) must be present
155    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    /// C2: Field 34F D/C Mark Usage (Error code: C23)
177    /// When only one 34F present: D/C Mark must NOT be used
178    /// When both 34F present: First must have 'D', second must have 'C'
179    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                // Only debit field present - D/C Mark must NOT be used
188                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                // Both fields present - First must have 'D', second must have 'C'
204                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    /// C3: Currency Consistency Within Repetitive Sequence (Error code: C40)
239    /// Currency code must be the same for each occurrence of field 34F within each sequence
240    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    /// Main validation method - validates all network rules
267    /// Returns array of validation errors, respects stop_on_first_error flag
268    pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
269        let mut all_errors = Vec::new();
270
271        // T88: Message Type Validation
272        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        // C1: Field 34F Requirement for MT 942
279        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        // C2: Field 34F D/C Mark Usage
286        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        // C3: Currency Consistency
293        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        // Call the existing public method implementation
307        MT920::parse_from_block4(block4)
308    }
309
310    fn to_mt_string(&self) -> String {
311        // Call the existing public method implementation
312        MT920::to_mt_string(self)
313    }
314
315    fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
316        // Call the existing public method implementation
317        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        // Test empty sequence - should fail
348        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        // Test JSON deserialization for MT920
363        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        // Test complete SwiftMessage<MT920> JSON deserialization
396        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}