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