swift_mt_message/messages/
mt210.rs1use crate::errors::{ParseError, SwiftValidationError};
2use crate::fields::*;
3use crate::parser::MessageParser;
4use crate::parser::utils::*;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub struct MT210 {
15 #[serde(rename = "20")]
17 pub transaction_reference: Field20,
18
19 #[serde(rename = "25", skip_serializing_if = "Option::is_none")]
21 pub account_identification: Option<Field25NoOption>,
22
23 #[serde(rename = "30")]
25 pub value_date: Field30,
26
27 #[serde(rename = "#", default)]
29 pub transactions: Vec<MT210Transaction>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
34pub struct MT210Transaction {
35 #[serde(rename = "21", skip_serializing_if = "Option::is_none")]
37 pub related_reference: Option<Field21NoOption>,
38
39 #[serde(rename = "32B")]
41 pub currency_amount: Field32B,
42
43 #[serde(flatten, skip_serializing_if = "Option::is_none")]
45 pub ordering_customer: Option<Field50>,
46
47 #[serde(flatten, skip_serializing_if = "Option::is_none")]
49 pub ordering_institution: Option<Field52OrderingInstitution>,
50
51 #[serde(flatten, skip_serializing_if = "Option::is_none")]
53 pub intermediary: Option<Field56>,
54}
55
56impl MT210 {
57 pub fn parse_from_block4(block4: &str) -> Result<Self, ParseError> {
59 let mut parser = MessageParser::new(block4, "210");
60
61 let transaction_reference = parser.parse_field::<Field20>("20")?;
63
64 let account_identification = parser.parse_optional_field::<Field25NoOption>("25")?;
66
67 let value_date = parser.parse_field::<Field30>("30")?;
69
70 parser = parser.with_duplicates(true);
72 let mut transactions = Vec::new();
73
74 while parser.detect_field("21") || parser.detect_field("32B") {
75 let related_reference = parser.parse_optional_field::<Field21NoOption>("21")?;
77
78 let currency_amount = parser.parse_field::<Field32B>("32B")?;
80
81 let ordering_customer = parser.parse_optional_variant_field::<Field50>("50")?;
83
84 let ordering_institution =
86 parser.parse_optional_variant_field::<Field52OrderingInstitution>("52")?;
87
88 let intermediary = parser.parse_optional_variant_field::<Field56>("56")?;
90
91 transactions.push(MT210Transaction {
92 related_reference,
93 currency_amount,
94 ordering_customer,
95 ordering_institution,
96 intermediary,
97 });
98
99 if transactions.len() >= 10 {
101 break;
102 }
103 }
104
105 Ok(MT210 {
106 transaction_reference,
107 account_identification,
108 value_date,
109 transactions,
110 })
111 }
112
113 const MAX_REPETITIVE_SEQUENCES: usize = 10;
119
120 fn validate_c1_repetitive_sequence_count(&self) -> Option<SwiftValidationError> {
127 if self.transactions.len() > Self::MAX_REPETITIVE_SEQUENCES {
128 return Some(SwiftValidationError::format_error(
129 "T10",
130 "21",
131 &self.transactions.len().to_string(),
132 &format!("Max {} occurrences", Self::MAX_REPETITIVE_SEQUENCES),
133 &format!(
134 "The repetitive sequence must not appear more than {} times. Found {} occurrences",
135 Self::MAX_REPETITIVE_SEQUENCES,
136 self.transactions.len()
137 ),
138 ));
139 }
140
141 None
142 }
143
144 fn validate_c2_mutual_exclusivity(&self) -> Vec<SwiftValidationError> {
147 let mut errors = Vec::new();
148
149 for (idx, transaction) in self.transactions.iter().enumerate() {
150 let has_ordering_customer = transaction.ordering_customer.is_some();
151 let has_ordering_institution = transaction.ordering_institution.is_some();
152
153 if has_ordering_customer && has_ordering_institution {
154 errors.push(SwiftValidationError::content_error(
156 "C06",
157 "50a/52a",
158 "",
159 &format!(
160 "Transaction {}: Either field 50a (Ordering Customer) or field 52a (Ordering Institution), but not both, must be present",
161 idx + 1
162 ),
163 "Field 50a and field 52a are mutually exclusive. Only one may be present in each repetitive sequence",
164 ));
165 } else if !has_ordering_customer && !has_ordering_institution {
166 errors.push(SwiftValidationError::content_error(
168 "C06",
169 "50a/52a",
170 "",
171 &format!(
172 "Transaction {}: Either field 50a (Ordering Customer) or field 52a (Ordering Institution) must be present",
173 idx + 1
174 ),
175 "At least one of field 50a or field 52a must be present in each repetitive sequence",
176 ));
177 }
178 }
179
180 errors
181 }
182
183 fn validate_c3_currency_consistency(&self) -> Option<SwiftValidationError> {
186 if self.transactions.is_empty() {
187 return None;
188 }
189
190 let first_currency = &self.transactions[0].currency_amount.currency;
192
193 for (idx, transaction) in self.transactions.iter().enumerate().skip(1) {
195 if &transaction.currency_amount.currency != first_currency {
196 return Some(SwiftValidationError::content_error(
197 "C02",
198 "32B",
199 &transaction.currency_amount.currency,
200 &format!(
201 "Transaction {}: Currency code in field 32B ({}) must be the same as in the first transaction ({})",
202 idx + 1,
203 transaction.currency_amount.currency,
204 first_currency
205 ),
206 "The currency code must be the same for all occurrences of field 32B in the message",
207 ));
208 }
209 }
210
211 None
212 }
213
214 pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
217 let mut all_errors = Vec::new();
218
219 if let Some(error) = self.validate_c1_repetitive_sequence_count() {
221 all_errors.push(error);
222 if stop_on_first_error {
223 return all_errors;
224 }
225 }
226
227 let c2_errors = self.validate_c2_mutual_exclusivity();
229 all_errors.extend(c2_errors);
230 if stop_on_first_error && !all_errors.is_empty() {
231 return all_errors;
232 }
233
234 if let Some(error) = self.validate_c3_currency_consistency() {
236 all_errors.push(error);
237 }
238
239 all_errors
240 }
241}
242
243impl crate::traits::SwiftMessageBody for MT210 {
244 fn message_type() -> &'static str {
245 "210"
246 }
247
248 fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
249 Self::parse_from_block4(block4)
250 }
251
252 fn to_mt_string(&self) -> String {
253 let mut result = String::new();
254
255 append_field(&mut result, &self.transaction_reference);
256 append_optional_field(&mut result, &self.account_identification);
257 append_field(&mut result, &self.value_date);
258
259 for txn in &self.transactions {
261 append_optional_field(&mut result, &txn.related_reference);
262 append_field(&mut result, &txn.currency_amount);
263 append_optional_field(&mut result, &txn.ordering_customer);
264 append_optional_field(&mut result, &txn.ordering_institution);
265 append_optional_field(&mut result, &txn.intermediary);
266 }
267
268 finalize_mt_string(result, false)
269 }
270
271 fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
272 MT210::validate_network_rules(self, stop_on_first_error)
274 }
275}