swift_mt_message/messages/
mt910.rs

1use crate::errors::SwiftValidationError;
2use crate::fields::*;
3use crate::parser::utils::*;
4use serde::{Deserialize, Serialize};
5
6/// **MT910: Confirmation of Credit**
7///
8/// Confirms credit to account servicing institution's account.
9///
10/// **Usage:** Credit confirmations, account reconciliation
11/// **Category:** Category 9 (Cash Management & Customer Status)
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13pub struct MT910 {
14    /// Transaction Reference Number (Field 20)
15    #[serde(rename = "20")]
16    pub field_20: Field20,
17
18    /// Related Reference (Field 21)
19    #[serde(rename = "21")]
20    pub field_21: Field21NoOption,
21
22    /// Account Identification (Field 25)
23    #[serde(rename = "25")]
24    pub field_25: Field25AccountIdentification,
25
26    /// Date/Time Indication (Field 13D)
27    #[serde(rename = "13D")]
28    pub field_13d: Option<Field13D>,
29
30    /// Value Date, Currency Code, Amount (Field 32A)
31    #[serde(rename = "32A")]
32    pub field_32a: Field32A,
33
34    /// Ordering Customer (Field 50)
35    #[serde(flatten)]
36    pub field_50: Option<Field50OrderingCustomerAFK>,
37
38    /// Ordering Institution (Field 52)
39    #[serde(flatten)]
40    pub field_52: Option<Field52OrderingInstitution>,
41
42    /// Intermediary (Field 56)
43    #[serde(flatten)]
44    pub field_56: Option<Field56Intermediary>,
45
46    /// Sender to Receiver Information (Field 72)
47    #[serde(rename = "72")]
48    pub field_72: Option<Field72>,
49}
50
51impl MT910 {
52    /// Parse message from Block 4 content
53    pub fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
54        let mut parser = crate::parser::MessageParser::new(block4, "910");
55
56        // Parse mandatory fields
57        let field_20 = parser.parse_field::<Field20>("20")?;
58        let field_21 = parser.parse_field::<Field21NoOption>("21")?;
59        let field_25 = parser.parse_field::<Field25AccountIdentification>("25")?;
60
61        // Parse optional field 13D (comes before 32A)
62        let field_13d = parser.parse_optional_field::<Field13D>("13D")?;
63
64        // Parse mandatory field 32A
65        let field_32a = parser.parse_field::<Field32A>("32A")?;
66
67        // Parse remaining optional fields
68        let field_50 = parser.parse_optional_variant_field::<Field50OrderingCustomerAFK>("50")?;
69        let field_52 = parser.parse_optional_variant_field::<Field52OrderingInstitution>("52")?;
70        let field_56 = parser.parse_optional_variant_field::<Field56Intermediary>("56")?;
71        let field_72 = parser.parse_optional_field::<Field72>("72")?;
72
73        // Verify all content is consumed
74        verify_parser_complete(&parser)?;
75
76        Ok(Self {
77            field_20,
78            field_21,
79            field_25,
80            field_13d,
81            field_32a,
82            field_50,
83            field_52,
84            field_56,
85            field_72,
86        })
87    }
88
89    /// Parse from SWIFT MT text format
90    pub fn parse(input: &str) -> Result<Self, crate::errors::ParseError> {
91        let block4 = extract_block4(input)?;
92        Self::parse_from_block4(&block4)
93    }
94
95    /// Convert to SWIFT MT text format
96    pub fn to_mt_string(&self) -> String {
97        let mut result = String::new();
98
99        append_field(&mut result, &self.field_20);
100        append_field(&mut result, &self.field_21);
101        append_field(&mut result, &self.field_25);
102        append_optional_field(&mut result, &self.field_13d);
103        append_field(&mut result, &self.field_32a);
104        append_optional_field(&mut result, &self.field_50);
105        append_optional_field(&mut result, &self.field_52);
106        append_optional_field(&mut result, &self.field_56);
107        append_optional_field(&mut result, &self.field_72);
108
109        result.push('-');
110        result
111    }
112
113    // ========================================================================
114    // NETWORK VALIDATION RULES (SR 2025 MT910)
115    // ========================================================================
116
117    // ========================================================================
118    // HELPER METHODS
119    // ========================================================================
120
121    /// Check if ordering customer (field 50a) is present
122    fn has_ordering_customer(&self) -> bool {
123        self.field_50.is_some()
124    }
125
126    /// Check if ordering institution (field 52a) is present
127    fn has_ordering_institution(&self) -> bool {
128        self.field_52.is_some()
129    }
130
131    // ========================================================================
132    // VALIDATION RULES (C1)
133    // ========================================================================
134
135    /// C1: Ordering Customer or Ordering Institution Required (Error code: C06)
136    /// Either field 50a or field 52a must be present
137    fn validate_c1_ordering_party(&self) -> Option<SwiftValidationError> {
138        let has_50 = self.has_ordering_customer();
139        let has_52 = self.has_ordering_institution();
140
141        if !has_50 && !has_52 {
142            return Some(SwiftValidationError::business_error(
143                "C06",
144                "50a/52a",
145                vec![],
146                "Either field 50a (Ordering Customer) or field 52a (Ordering Institution) must be present",
147                "At least one of fields 50a or 52a must be present in the message. If field 50a is present, field 52a is optional. If field 52a is present, field 50a is optional. Both fields cannot be absent",
148            ));
149        }
150
151        None
152    }
153
154    /// Main validation method - validates all network rules
155    /// Returns array of validation errors, respects stop_on_first_error flag
156    pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
157        let mut all_errors = Vec::new();
158
159        // C1: Ordering Customer or Ordering Institution
160        if let Some(error) = self.validate_c1_ordering_party() {
161            all_errors.push(error);
162            if stop_on_first_error {
163                return all_errors;
164            }
165        }
166
167        all_errors
168    }
169}
170
171impl crate::traits::SwiftMessageBody for MT910 {
172    fn message_type() -> &'static str {
173        "910"
174    }
175
176    fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
177        // Call the existing public method implementation
178        MT910::parse_from_block4(block4)
179    }
180
181    fn to_mt_string(&self) -> String {
182        // Call the existing public method implementation
183        MT910::to_mt_string(self)
184    }
185
186    fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
187        // Call the existing public method implementation
188        MT910::validate_network_rules(self, stop_on_first_error)
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195
196    #[test]
197    fn test_mt910_parse() {
198        let mt910_text = r#":20:20240719001
199:21:REF20240719001
200:25:12345678901234567890
201:32A:240719USD1000,00
202:50K:JOHN DOE
203123 MAIN STREET
204NEW YORK
205-"#;
206        let result = MT910::parse_from_block4(mt910_text);
207        assert!(result.is_ok());
208        let mt910 = result.unwrap();
209        assert_eq!(mt910.field_20.reference, "20240719001");
210        assert_eq!(mt910.field_21.reference, "REF20240719001");
211    }
212
213    #[test]
214    fn test_mt910_validation_c1_fails_without_ordering_party() {
215        // Test without field 50 and 52 - should fail validation
216        let mt910_text = r#":20:20240719001
217:21:REF20240719001
218:25:12345678901234567890
219:32A:240719USD1000,00
220-"#;
221        let result = MT910::parse_from_block4(mt910_text);
222        assert!(result.is_ok());
223        let mt910 = result.unwrap();
224
225        // Validation should fail
226        let errors = mt910.validate_network_rules(false);
227        assert_eq!(errors.len(), 1);
228        assert_eq!(errors[0].code(), "C06");
229        assert!(errors[0].message().contains("Either field 50a"));
230    }
231
232    #[test]
233    fn test_mt910_validation_c1_passes_with_field_50() {
234        // Test with field 50 only - should pass validation
235        let mt910_text = r#":20:20240719001
236:21:REF20240719001
237:25:12345678901234567890
238:32A:240719USD1000,00
239:50K:JOHN DOE
240123 MAIN STREET
241NEW YORK
242-"#;
243        let result = MT910::parse_from_block4(mt910_text);
244        assert!(result.is_ok());
245        let mt910 = result.unwrap();
246
247        // Validation should pass
248        let errors = mt910.validate_network_rules(false);
249        assert_eq!(errors.len(), 0);
250    }
251
252    #[test]
253    fn test_mt910_validation_c1_passes_with_field_52() {
254        // Test with field 52 only - should pass validation
255        let mt910_text = r#":20:20240719001
256:21:REF20240719001
257:25:12345678901234567890
258:32A:240719USD1000,00
259:52A:DEUTDEFFXXX
260-"#;
261        let result = MT910::parse_from_block4(mt910_text);
262        if let Err(ref e) = result {
263            eprintln!("Parse error: {:?}", e);
264        }
265        assert!(result.is_ok());
266        let mt910 = result.unwrap();
267
268        // Validation should pass
269        let errors = mt910.validate_network_rules(false);
270        assert_eq!(errors.len(), 0);
271    }
272
273    #[test]
274    fn test_mt910_validation_c1_passes_with_both_fields() {
275        // Test with both fields 50 and 52 - should pass validation
276        let mt910_text = r#":20:20240719001
277:21:REF20240719001
278:25:12345678901234567890
279:32A:240719USD1000,00
280:50K:JOHN DOE
281123 MAIN STREET
282NEW YORK
283:52A:DEUTDEFFXXX
284-"#;
285        let result = MT910::parse_from_block4(mt910_text);
286        assert!(result.is_ok());
287        let mt910 = result.unwrap();
288
289        // Validation should pass
290        let errors = mt910.validate_network_rules(false);
291        assert_eq!(errors.len(), 0);
292    }
293}