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}