swift_mt_message/messages/
mt910.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 MT910 {
14    #[serde(rename = "20")]
16    pub field_20: Field20,
17
18    #[serde(rename = "21")]
20    pub field_21: Field21NoOption,
21
22    #[serde(rename = "25")]
24    pub field_25: Field25AccountIdentification,
25
26    #[serde(rename = "13D")]
28    pub field_13d: Option<Field13D>,
29
30    #[serde(rename = "32A")]
32    pub field_32a: Field32A,
33
34    #[serde(flatten)]
36    pub field_50: Option<Field50OrderingCustomerAFK>,
37
38    #[serde(flatten)]
40    pub field_52: Option<Field52OrderingInstitution>,
41
42    #[serde(flatten)]
44    pub field_56: Option<Field56Intermediary>,
45
46    #[serde(rename = "72")]
48    pub field_72: Option<Field72>,
49}
50
51impl MT910 {
52    pub fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
54        let mut parser = crate::parser::MessageParser::new(block4, "910");
55
56        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        let field_13d = parser.parse_optional_field::<Field13D>("13D")?;
63
64        let field_32a = parser.parse_field::<Field32A>("32A")?;
66
67        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_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    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    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    fn has_ordering_customer(&self) -> bool {
122        self.field_50.is_some()
123    }
124
125    fn has_ordering_institution(&self) -> bool {
127        self.field_52.is_some()
128    }
129
130    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    pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
156        let mut all_errors = Vec::new();
157
158        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        MT910::parse_from_block4(block4)
178    }
179
180    fn to_mt_string(&self) -> String {
181        MT910::to_mt_string(self)
183    }
184
185    fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
186        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        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        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        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        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        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        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        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        let errors = mt910.validate_network_rules(false);
290        assert_eq!(errors.len(), 0);
291    }
292}