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        finalize_mt_string(result, false)
110    }
111
112    // ========================================================================
113    // NETWORK VALIDATION RULES (SR 2025 MT910)
114    // ========================================================================
115
116    // ========================================================================
117    // HELPER METHODS
118    // ========================================================================
119
120    /// Check if ordering customer (field 50a) is present
121    fn has_ordering_customer(&self) -> bool {
122        self.field_50.is_some()
123    }
124
125    /// Check if ordering institution (field 52a) is present
126    fn has_ordering_institution(&self) -> bool {
127        self.field_52.is_some()
128    }
129
130    // ========================================================================
131    // VALIDATION RULES (C1)
132    // ========================================================================
133
134    /// C1: Ordering Customer or Ordering Institution Required (Error code: C06)
135    /// Either field 50a or field 52a must be present
136    fn validate_c1_ordering_party(&self) -> Option<SwiftValidationError> {
137        let has_50 = self.has_ordering_customer();
138        let has_52 = self.has_ordering_institution();
139
140        if !has_50 && !has_52 {
141            return Some(SwiftValidationError::business_error(
142                "C06",
143                "50a/52a",
144                vec![],
145                "Either field 50a (Ordering Customer) or field 52a (Ordering Institution) must be present",
146                "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",
147            ));
148        }
149
150        None
151    }
152
153    /// Main validation method - validates all network rules
154    /// Returns array of validation errors, respects stop_on_first_error flag
155    pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
156        let mut all_errors = Vec::new();
157
158        // C1: Ordering Customer or Ordering Institution
159        if let Some(error) = self.validate_c1_ordering_party() {
160            all_errors.push(error);
161            if stop_on_first_error {
162                return all_errors;
163            }
164        }
165
166        all_errors
167    }
168}
169
170impl crate::traits::SwiftMessageBody for MT910 {
171    fn message_type() -> &'static str {
172        "910"
173    }
174
175    fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
176        // Call the existing public method implementation
177        MT910::parse_from_block4(block4)
178    }
179
180    fn to_mt_string(&self) -> String {
181        // Call the existing public method implementation
182        MT910::to_mt_string(self)
183    }
184
185    fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
186        // Call the existing public method implementation
187        MT910::validate_network_rules(self, stop_on_first_error)
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn test_mt910_parse() {
197        let mt910_text = r#":20:20240719001
198:21:REF20240719001
199:25:12345678901234567890
200:32A:240719USD1000,00
201:50K:JOHN DOE
202123 MAIN STREET
203NEW YORK
204-"#;
205        let result = MT910::parse_from_block4(mt910_text);
206        assert!(result.is_ok());
207        let mt910 = result.unwrap();
208        assert_eq!(mt910.field_20.reference, "20240719001");
209        assert_eq!(mt910.field_21.reference, "REF20240719001");
210    }
211
212    #[test]
213    fn test_mt910_validation_c1_fails_without_ordering_party() {
214        // Test without field 50 and 52 - should fail validation
215        let mt910_text = r#":20:20240719001
216:21:REF20240719001
217:25:12345678901234567890
218:32A:240719USD1000,00
219-"#;
220        let result = MT910::parse_from_block4(mt910_text);
221        assert!(result.is_ok());
222        let mt910 = result.unwrap();
223
224        // Validation should fail
225        let errors = mt910.validate_network_rules(false);
226        assert_eq!(errors.len(), 1);
227        assert_eq!(errors[0].code(), "C06");
228        assert!(errors[0].message().contains("Either field 50a"));
229    }
230
231    #[test]
232    fn test_mt910_validation_c1_passes_with_field_50() {
233        // Test with field 50 only - should pass validation
234        let mt910_text = r#":20:20240719001
235:21:REF20240719001
236:25:12345678901234567890
237:32A:240719USD1000,00
238:50K:JOHN DOE
239123 MAIN STREET
240NEW YORK
241-"#;
242        let result = MT910::parse_from_block4(mt910_text);
243        assert!(result.is_ok());
244        let mt910 = result.unwrap();
245
246        // Validation should pass
247        let errors = mt910.validate_network_rules(false);
248        assert_eq!(errors.len(), 0);
249    }
250
251    #[test]
252    fn test_mt910_validation_c1_passes_with_field_52() {
253        // Test with field 52 only - should pass validation
254        let mt910_text = r#":20:20240719001
255:21:REF20240719001
256:25:12345678901234567890
257:32A:240719USD1000,00
258:52A:DEUTDEFFXXX
259-"#;
260        let result = MT910::parse_from_block4(mt910_text);
261        if let Err(ref e) = result {
262            eprintln!("Parse error: {:?}", e);
263        }
264        assert!(result.is_ok());
265        let mt910 = result.unwrap();
266
267        // Validation should pass
268        let errors = mt910.validate_network_rules(false);
269        assert_eq!(errors.len(), 0);
270    }
271
272    #[test]
273    fn test_mt910_validation_c1_passes_with_both_fields() {
274        // Test with both fields 50 and 52 - should pass validation
275        let mt910_text = r#":20:20240719001
276:21:REF20240719001
277:25:12345678901234567890
278:32A:240719USD1000,00
279:50K:JOHN DOE
280123 MAIN STREET
281NEW YORK
282:52A:DEUTDEFFXXX
283-"#;
284        let result = MT910::parse_from_block4(mt910_text);
285        assert!(result.is_ok());
286        let mt910 = result.unwrap();
287
288        // Validation should pass
289        let errors = mt910.validate_network_rules(false);
290        assert_eq!(errors.len(), 0);
291    }
292}