swift_mt_message/messages/
mt200.rs

1use crate::errors::{ParseError, SwiftValidationError};
2use crate::fields::*;
3use crate::parser::MessageParser;
4use crate::parser::utils::*;
5use serde::{Deserialize, Serialize};
6
7/// **MT200: Financial Institution Transfer for Own Account**
8///
9/// Financial institution funds transfer for their own account.
10///
11/// **Usage:** Nostro funding, liquidity management, internal transfers
12/// **Category:** Category 2 (Financial Institution Transfers)
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub struct MT200 {
15    /// Transaction Reference Number (Field 20)
16    #[serde(rename = "20")]
17    pub field_20: Field20,
18
19    /// Value Date, Currency Code, Amount (Field 32A)
20    #[serde(rename = "32A")]
21    pub field_32a: Field32A,
22
23    /// Sender's Correspondent (Field 53B)
24    #[serde(rename = "53B", skip_serializing_if = "Option::is_none")]
25    pub field_53b: Option<Field53B>,
26
27    /// Intermediary Institution (Field 56)
28    #[serde(flatten, skip_serializing_if = "Option::is_none")]
29    pub field_56: Option<Field56IntermediaryAD>,
30
31    /// Account With Institution (Field 57)
32    #[serde(flatten)]
33    pub field_57: Field57DebtInstitution,
34
35    /// Sender to Receiver Information (Field 72)
36    #[serde(rename = "72", skip_serializing_if = "Option::is_none")]
37    pub field_72: Option<Field72>,
38}
39
40impl MT200 {
41    /// Parse MT200 from a raw SWIFT message string
42    pub fn parse_from_block4(block4: &str) -> Result<Self, ParseError> {
43        let mut parser = MessageParser::new(block4, "200");
44
45        // Parse mandatory fields
46        let field_20 = parser.parse_field::<Field20>("20")?;
47        let field_32a = parser.parse_field::<Field32A>("32A")?;
48
49        // Parse optional Field 53B - Sender's Correspondent
50        let field_53b = parser.parse_optional_field::<Field53B>("53B")?;
51
52        // Parse optional Field 56 - Intermediary Institution
53        let field_56 = parser.parse_optional_variant_field::<Field56IntermediaryAD>("56")?;
54
55        // Parse mandatory Field 57 - Account With Institution
56        let field_57 = parser.parse_variant_field::<Field57DebtInstitution>("57")?;
57
58        // Parse optional Field 72
59        let field_72 = parser.parse_optional_field::<Field72>("72")?;
60
61        Ok(MT200 {
62            field_20,
63            field_32a,
64            field_53b,
65            field_56,
66            field_57,
67            field_72,
68        })
69    }
70
71    /// Parse from generic SWIFT input (tries to detect blocks)
72    pub fn parse(input: &str) -> Result<Self, crate::errors::ParseError> {
73        let block4 = extract_block4(input)?;
74        Self::parse_from_block4(&block4)
75    }
76
77    /// Convert to SWIFT MT text format
78    pub fn to_mt_string(&self) -> String {
79        let mut result = String::new();
80
81        append_field(&mut result, &self.field_20);
82        append_field(&mut result, &self.field_32a);
83        append_optional_field(&mut result, &self.field_53b);
84        append_optional_field(&mut result, &self.field_56);
85        append_field(&mut result, &self.field_57);
86        append_optional_field(&mut result, &self.field_72);
87
88        result.push('-');
89        result
90    }
91
92    // ========================================================================
93    // NETWORK VALIDATION RULES (SR 2025 MT200)
94    // ========================================================================
95
96    /// Special codes requiring SWIFT Payments Reject/Return Guidelines compliance
97    const SPECIAL_72_CODES: &'static [&'static str] = &["REJT", "RETN"];
98
99    // ========================================================================
100    // HELPER METHODS
101    // ========================================================================
102
103    /// Extract codes from field 72 content
104    fn extract_72_codes(&self) -> Vec<String> {
105        let mut codes = Vec::new();
106
107        if let Some(ref field_72) = self.field_72 {
108            // Parse field 72 content for codes (format: /CODE/additional text)
109            for line in &field_72.information {
110                let trimmed = line.trim();
111                if let Some(without_prefix) = trimmed.strip_prefix('/') {
112                    if let Some(end_idx) = without_prefix.find('/') {
113                        let code = &without_prefix[..end_idx];
114                        codes.push(code.to_uppercase());
115                    } else if !without_prefix.is_empty() {
116                        // Code without trailing slash
117                        if let Some(space_idx) = without_prefix.find(|c: char| c.is_whitespace()) {
118                            codes.push(without_prefix[..space_idx].to_uppercase());
119                        } else {
120                            codes.push(without_prefix.to_uppercase());
121                        }
122                    }
123                }
124            }
125        }
126
127        codes
128    }
129
130    // ========================================================================
131    // VALIDATION RULES
132    // ========================================================================
133
134    /// T80: Field 72 REJT/RETN Compliance
135    /// If /REJT/ or /RETN/ present, must follow SWIFT Payments Reject/Return Guidelines
136    fn validate_t80_field_72_special_codes(&self) -> Vec<SwiftValidationError> {
137        let mut errors = Vec::new();
138
139        let codes = self.extract_72_codes();
140
141        for code in &codes {
142            if Self::SPECIAL_72_CODES.contains(&code.as_str()) {
143                // Note: This is a guideline check - actual compliance verification
144                // would require checking the full message structure according to
145                // SWIFT Payments Reject/Return Guidelines
146                errors.push(SwiftValidationError::content_error(
147                    "T80",
148                    "72",
149                    code,
150                    &format!(
151                        "Field 72 contains code /{}/. Message must comply with SWIFT Payments Reject/Return Guidelines",
152                        code
153                    ),
154                    "When field 72 contains /REJT/ or /RETN/, the message must follow SWIFT Payments Reject/Return Guidelines",
155                ));
156            }
157        }
158
159        errors
160    }
161
162    /// Main validation method - validates all network rules
163    /// Returns array of validation errors, respects stop_on_first_error flag
164    pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
165        let mut all_errors = Vec::new();
166
167        // Note: Per SR 2025 specification, MT200 has no standard network validated rules.
168        // However, field-specific validation rules still apply.
169
170        // T80: Field 72 REJT/RETN Guidelines Compliance
171        let t80_errors = self.validate_t80_field_72_special_codes();
172        all_errors.extend(t80_errors);
173        if stop_on_first_error && !all_errors.is_empty() {
174            return all_errors;
175        }
176
177        all_errors
178    }
179}
180
181impl crate::traits::SwiftMessageBody for MT200 {
182    fn message_type() -> &'static str {
183        "200"
184    }
185
186    fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
187        // Call the existing public method implementation
188        MT200::parse_from_block4(block4)
189    }
190
191    fn to_mt_string(&self) -> String {
192        // Call the existing public method implementation
193        MT200::to_mt_string(self)
194    }
195
196    fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
197        // Call the existing public method implementation
198        MT200::validate_network_rules(self, stop_on_first_error)
199    }
200}