swift_mt_message/messages/
mt935.rs1use crate::errors::SwiftValidationError;
2use crate::fields::*;
3use crate::parser::utils::*;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13pub struct MT935 {
14 #[serde(rename = "20")]
16 pub field_20: Field20,
17
18 #[serde(rename = "#")]
20 pub rate_changes: Vec<MT935RateChange>,
21
22 #[serde(rename = "72", skip_serializing_if = "Option::is_none")]
24 pub field_72: Option<Field72>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
29pub struct MT935RateChange {
30 #[serde(rename = "23", skip_serializing_if = "Option::is_none")]
32 pub field_23: Option<Field23>,
33
34 #[serde(rename = "25", skip_serializing_if = "Option::is_none")]
36 pub field_25: Option<Field25NoOption>,
37
38 #[serde(rename = "30")]
40 pub field_30: Field30,
41
42 #[serde(rename = "37H")]
44 pub field_37h: Vec<Field37H>,
45}
46
47impl MT935 {
48 pub fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
50 let mut parser = crate::parser::MessageParser::new(block4, "935");
51
52 let field_20 = parser.parse_field::<Field20>("20")?;
54
55 parser = parser.with_duplicates(true);
57
58 let mut rate_changes = Vec::new();
60
61 while (parser.detect_field("23") || parser.detect_field("25")) && rate_changes.len() < 10 {
62 let field_23 = parser.parse_optional_field::<Field23>("23")?;
64
65 let field_25 = parser.parse_optional_field::<Field25NoOption>("25")?;
67
68 let field_30 = parser.parse_field::<Field30>("30")?;
70
71 let mut field_37h = Vec::new();
73 while let Ok(rate) = parser.parse_field::<Field37H>("37H") {
74 field_37h.push(rate);
75 if !parser.detect_field("37H") {
77 break;
78 }
79 }
80
81 if field_37h.is_empty() {
83 return Err(crate::errors::ParseError::InvalidFormat {
84 message: format!(
85 "MT935: At least one field 37H is required for sequence {}",
86 rate_changes.len() + 1
87 ),
88 });
89 }
90
91 rate_changes.push(MT935RateChange {
92 field_23,
93 field_25,
94 field_30,
95 field_37h,
96 });
97 }
98
99 parser = parser.with_duplicates(false);
101
102 if rate_changes.is_empty() {
104 return Err(crate::errors::ParseError::InvalidFormat {
105 message: "MT935: At least one rate change sequence is required".to_string(),
106 });
107 }
108
109 let field_72 = parser.parse_optional_field::<Field72>("72")?;
111
112 Ok(MT935 {
113 field_20,
114 rate_changes,
115 field_72,
116 })
117 }
118
119 const VALID_23_FUNCTION_CODES: &'static [&'static str] = &[
125 "BASE",
126 "CALL",
127 "COMMERCIAL",
128 "CURRENT",
129 "DEPOSIT",
130 "NOTICE",
131 "PRIME",
132 ];
133
134 fn has_field_23(seq: &MT935RateChange) -> bool {
140 seq.field_23.is_some()
141 }
142
143 fn has_field_25(seq: &MT935RateChange) -> bool {
145 seq.field_25.is_some()
146 }
147
148 fn validate_c1_sequence_occurrence(&self) -> Option<SwiftValidationError> {
155 let num_sequences = self.rate_changes.len();
156
157 if num_sequences == 0 {
158 return Some(SwiftValidationError::content_error(
159 "T10",
160 "RateChangeSequence",
161 "0",
162 "The repetitive sequence must appear at least once",
163 "The repetitive sequence (fields 23/25, 30, 37H) must appear at least once",
164 ));
165 }
166
167 if num_sequences > 10 {
168 return Some(SwiftValidationError::content_error(
169 "T10",
170 "RateChangeSequence",
171 &num_sequences.to_string(),
172 &format!(
173 "The repetitive sequence must not appear more than ten times, found {}",
174 num_sequences
175 ),
176 "The repetitive sequence (fields 23/25, 30, 37H) must not appear more than ten times",
177 ));
178 }
179
180 None
181 }
182
183 fn validate_c2_field_23_25_mutual_exclusivity(&self) -> Vec<SwiftValidationError> {
186 let mut errors = Vec::new();
187
188 for (idx, seq) in self.rate_changes.iter().enumerate() {
189 let has_23 = Self::has_field_23(seq);
190 let has_25 = Self::has_field_25(seq);
191
192 if has_23 && has_25 {
193 errors.push(SwiftValidationError::relation_error(
195 "C83",
196 "23/25",
197 vec!["23".to_string(), "25".to_string()],
198 &format!(
199 "Sequence {}: Both field 23 and field 25 are present. Either field 23 or field 25, but not both, must be present",
200 idx + 1
201 ),
202 "Either field 23 or field 25, but not both, must be present in any repetitive sequence",
203 ));
204 } else if !has_23 && !has_25 {
205 errors.push(SwiftValidationError::relation_error(
207 "C83",
208 "23/25",
209 vec!["23".to_string(), "25".to_string()],
210 &format!(
211 "Sequence {}: Neither field 23 nor field 25 is present. Either field 23 or field 25 must be present",
212 idx + 1
213 ),
214 "Either field 23 or field 25, but not both, must be present in any repetitive sequence",
215 ));
216 }
217 }
218
219 errors
220 }
221
222 fn validate_field_23(&self) -> Vec<SwiftValidationError> {
225 let mut errors = Vec::new();
226
227 for (idx, seq) in self.rate_changes.iter().enumerate() {
228 if let Some(ref field_23) = seq.field_23 {
229 let mut value = field_23.function_code.clone();
231 if let Some(days) = field_23.days {
232 value.push_str(&format!("{:02}", days));
233 }
234 value.push_str(&field_23.reference);
235
236 if value.len() < 4 {
238 errors.push(SwiftValidationError::format_error(
239 "T26",
240 "23",
241 &value,
242 "3!a[2!n]11x",
243 &format!(
244 "Sequence {}: Field 23 must be at least 4 characters (currency code + function)",
245 idx + 1
246 ),
247 ));
248 continue;
249 }
250
251 let currency = &value[..3];
253
254 if !currency.chars().all(|c| c.is_ascii_alphabetic()) {
256 errors.push(SwiftValidationError::format_error(
257 "T26",
258 "23",
259 &value,
260 "3!a[2!n]11x",
261 &format!(
262 "Sequence {}: Currency code '{}' must be 3 alphabetic characters",
263 idx + 1,
264 currency
265 ),
266 ));
267 }
268
269 let remaining = &value[3..];
271
272 let (num_days, function_start) =
274 if remaining.len() >= 2 && remaining[..2].chars().all(|c| c.is_ascii_digit()) {
275 (Some(&remaining[..2]), 2)
276 } else {
277 (None, 0)
278 };
279
280 let function = &remaining[function_start..];
282
283 if !Self::VALID_23_FUNCTION_CODES.contains(&function) {
285 errors.push(SwiftValidationError::content_error(
286 "T26",
287 "23",
288 function,
289 &format!(
290 "Sequence {}: Function code '{}' is not valid. Valid codes: {}",
291 idx + 1,
292 function,
293 Self::VALID_23_FUNCTION_CODES.join(", ")
294 ),
295 &format!(
296 "Function code must be one of: {}",
297 Self::VALID_23_FUNCTION_CODES.join(", ")
298 ),
299 ));
300 }
301
302 if let Some(days) = num_days
304 && function != "NOTICE"
305 {
306 errors.push(SwiftValidationError::content_error(
307 "T26",
308 "23",
309 &value,
310 &format!(
311 "Sequence {}: Number of Days '{}' is only allowed when Function is NOTICE, but found '{}'",
312 idx + 1, days, function
313 ),
314 "Number of Days must only be used when Function is NOTICE",
315 ));
316 }
317 }
318 }
319
320 errors
321 }
322
323 fn validate_field_37h(&self) -> Vec<SwiftValidationError> {
327 let mut errors = Vec::new();
328
329 for (seq_idx, seq) in self.rate_changes.iter().enumerate() {
330 for (field_idx, field_37h) in seq.field_37h.iter().enumerate() {
331 let indicator = field_37h.rate_indicator;
332 let is_negative = field_37h.is_negative;
333 let rate = field_37h.rate;
334
335 if indicator != 'C' && indicator != 'D' {
337 errors.push(SwiftValidationError::format_error(
338 "T51",
339 "37H",
340 &indicator.to_string(),
341 "C or D",
342 &format!(
343 "Sequence {}, Rate {}: Indicator '{}' is not valid. Must be C (Credit) or D (Debit)",
344 seq_idx + 1,
345 field_idx + 1,
346 indicator
347 ),
348 ));
349 }
350
351 if rate.abs() < 0.00001 && is_negative.is_some() {
353 errors.push(SwiftValidationError::content_error(
354 "T14",
355 "37H",
356 &rate.to_string(),
357 &format!(
358 "Sequence {}, Rate {}: Sign must not be used when rate is zero",
359 seq_idx + 1,
360 field_idx + 1
361 ),
362 "Sign (N for negative) must not be used if Rate is zero",
363 ));
364 }
365 }
366 }
367
368 errors
369 }
370
371 pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
374 let mut all_errors = Vec::new();
375
376 if let Some(error) = self.validate_c1_sequence_occurrence() {
378 all_errors.push(error);
379 if stop_on_first_error {
380 return all_errors;
381 }
382 }
383
384 let c2_errors = self.validate_c2_field_23_25_mutual_exclusivity();
386 all_errors.extend(c2_errors);
387 if stop_on_first_error && !all_errors.is_empty() {
388 return all_errors;
389 }
390
391 let f23_errors = self.validate_field_23();
393 all_errors.extend(f23_errors);
394 if stop_on_first_error && !all_errors.is_empty() {
395 return all_errors;
396 }
397
398 let f37h_errors = self.validate_field_37h();
400 all_errors.extend(f37h_errors);
401
402 all_errors
403 }
404}
405
406impl crate::traits::SwiftMessageBody for MT935 {
408 fn message_type() -> &'static str {
409 "935"
410 }
411
412 fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
413 Self::parse_from_block4(block4)
414 }
415
416 fn to_mt_string(&self) -> String {
417 use crate::traits::SwiftField;
418 let mut result = String::new();
419
420 append_field(&mut result, &self.field_20);
421
422 for rate_change in &self.rate_changes {
424 append_optional_field(&mut result, &rate_change.field_23);
425 append_optional_field(&mut result, &rate_change.field_25);
426 append_field(&mut result, &rate_change.field_30);
427
428 for field_37h in &rate_change.field_37h {
430 result.push_str(&field_37h.to_swift_string());
431 result.push_str("\r\n");
432 }
433 }
434
435 append_optional_field(&mut result, &self.field_72);
436
437 finalize_mt_string(result, false)
438 }
439
440 fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
441 MT935::validate_network_rules(self, stop_on_first_error)
443 }
444}