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        finalize_mt_string(result, false)
89    }
90
91    // ========================================================================
92    // NETWORK VALIDATION RULES (SR 2025 MT200)
93    // ========================================================================
94
95    /// Special codes requiring SWIFT Payments Reject/Return Guidelines compliance
96    const SPECIAL_72_CODES: &'static [&'static str] = &["REJT", "RETN"];
97
98    // ========================================================================
99    // HELPER METHODS
100    // ========================================================================
101
102    /// Extract codes from field 72 content
103    fn extract_72_codes(&self) -> Vec<String> {
104        let mut codes = Vec::new();
105
106        if let Some(ref field_72) = self.field_72 {
107            // Parse field 72 content for codes (format: /CODE/additional text)
108            for line in &field_72.information {
109                let trimmed = line.trim();
110                if let Some(without_prefix) = trimmed.strip_prefix('/') {
111                    if let Some(end_idx) = without_prefix.find('/') {
112                        let code = &without_prefix[..end_idx];
113                        codes.push(code.to_uppercase());
114                    } else if !without_prefix.is_empty() {
115                        // Code without trailing slash
116                        if let Some(space_idx) = without_prefix.find(|c: char| c.is_whitespace()) {
117                            codes.push(without_prefix[..space_idx].to_uppercase());
118                        } else {
119                            codes.push(without_prefix.to_uppercase());
120                        }
121                    }
122                }
123            }
124        }
125
126        codes
127    }
128
129    // ========================================================================
130    // VALIDATION RULES
131    // ========================================================================
132
133    /// T80: Field 72 REJT/RETN Compliance
134    /// If /REJT/ or /RETN/ present, must follow SWIFT Payments Reject/Return Guidelines
135    fn validate_t80_field_72_special_codes(&self) -> Vec<SwiftValidationError> {
136        let mut errors = Vec::new();
137
138        let codes = self.extract_72_codes();
139
140        for code in &codes {
141            if Self::SPECIAL_72_CODES.contains(&code.as_str()) {
142                // Note: This is a guideline check - actual compliance verification
143                // would require checking the full message structure according to
144                // SWIFT Payments Reject/Return Guidelines
145                errors.push(SwiftValidationError::content_error(
146                    "T80",
147                    "72",
148                    code,
149                    &format!(
150                        "Field 72 contains code /{}/. Message must comply with SWIFT Payments Reject/Return Guidelines",
151                        code
152                    ),
153                    "When field 72 contains /REJT/ or /RETN/, the message must follow SWIFT Payments Reject/Return Guidelines",
154                ));
155            }
156        }
157
158        errors
159    }
160
161    /// Main validation method - validates all network rules
162    /// Returns array of validation errors, respects stop_on_first_error flag
163    pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
164        let mut all_errors = Vec::new();
165
166        // Note: Per SR 2025 specification, MT200 has no standard network validated rules.
167        // However, field-specific validation rules still apply.
168
169        // T80: Field 72 REJT/RETN Guidelines Compliance
170        let t80_errors = self.validate_t80_field_72_special_codes();
171        all_errors.extend(t80_errors);
172        if stop_on_first_error && !all_errors.is_empty() {
173            return all_errors;
174        }
175
176        all_errors
177    }
178}
179
180impl crate::traits::SwiftMessageBody for MT200 {
181    fn message_type() -> &'static str {
182        "200"
183    }
184
185    fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
186        // Call the existing public method implementation
187        MT200::parse_from_block4(block4)
188    }
189
190    fn to_mt_string(&self) -> String {
191        // Call the existing public method implementation
192        MT200::to_mt_string(self)
193    }
194
195    fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
196        // Call the existing public method implementation
197        MT200::validate_network_rules(self, stop_on_first_error)
198    }
199}