swift_mt_message/
parser.rs

1//! Core parsing logic for SWIFT MT messages
2
3use regex::Regex;
4use std::collections::HashMap;
5
6use crate::common::{Field, MessageBlock};
7use crate::error::{MTError, Result};
8use crate::messages::{MTMessage, MTMessageType};
9
10/// Main parser for SWIFT MT messages
11pub struct MTParser {
12    block_regex: Regex,
13}
14
15impl MTParser {
16    pub fn new() -> Result<Self> {
17        let block_regex = Regex::new(r"\{(\d):([^}]*)\}")?;
18        
19        Ok(Self {
20            block_regex,
21        })
22    }
23
24    /// Parse a complete SWIFT MT message
25    pub fn parse(&self, input: &str) -> Result<MTMessage> {
26        let blocks = self.parse_blocks(input)?;
27        let message_type = self.extract_message_type(&blocks)?;
28        
29        match message_type.as_str() {
30            "103" => {
31                let mt103 = crate::messages::mt103::MT103::from_blocks(blocks)?;
32                Ok(MTMessage::MT103(mt103))
33            }
34            "102" => {
35                let mt102 = crate::messages::mt102::MT102::from_blocks(blocks)?;
36                Ok(MTMessage::MT102(mt102))
37            }
38            "202" => {
39                let mt202 = crate::messages::mt202::MT202::from_blocks(blocks)?;
40                Ok(MTMessage::MT202(mt202))
41            }
42            "940" => {
43                let mt940 = crate::messages::mt940::MT940::from_blocks(blocks)?;
44                Ok(MTMessage::MT940(mt940))
45            }
46            "941" => {
47                let mt941 = crate::messages::mt941::MT941::from_blocks(blocks)?;
48                Ok(MTMessage::MT941(mt941))
49            }
50            "942" => {
51                let mt942 = crate::messages::mt942::MT942::from_blocks(blocks)?;
52                Ok(MTMessage::MT942(mt942))
53            }
54            "192" => {
55                let mt192 = crate::messages::mt192::MT192::from_blocks(blocks)?;
56                Ok(MTMessage::MT192(mt192))
57            }
58            "195" => {
59                let mt195 = crate::messages::mt195::MT195::from_blocks(blocks)?;
60                Ok(MTMessage::MT195(mt195))
61            }
62            "196" => {
63                let mt196 = crate::messages::mt196::MT196::from_blocks(blocks)?;
64                Ok(MTMessage::MT196(mt196))
65            }
66            "197" => {
67                let mt197 = crate::messages::mt197::MT197::from_blocks(blocks)?;
68                Ok(MTMessage::MT197(mt197))
69            }
70            "199" => {
71                let mt199 = crate::messages::mt199::MT199::from_blocks(blocks)?;
72                Ok(MTMessage::MT199(mt199))
73            }
74            _ => Err(MTError::UnsupportedMessageType {
75                message_type: message_type.clone(),
76            }),
77        }
78    }
79
80    /// Parse message blocks from input
81    fn parse_blocks(&self, input: &str) -> Result<Vec<MessageBlock>> {
82        let mut blocks = Vec::new();
83        
84        for captures in self.block_regex.captures_iter(input) {
85            let block_number = &captures[1];
86            let block_content = &captures[2];
87            
88            match block_number {
89                "1" => {
90                    blocks.push(self.parse_basic_header(block_content)?);
91                }
92                "2" => {
93                    blocks.push(self.parse_application_header(block_content)?);
94                }
95                "3" => {
96                    blocks.push(self.parse_user_header(block_content)?);
97                }
98                "4" => {
99                    blocks.push(self.parse_text_block(block_content)?);
100                }
101                "5" => {
102                    blocks.push(self.parse_trailer_block(block_content)?);
103                }
104                _ => {
105                    return Err(MTError::InvalidMessageStructure {
106                        message: format!("Unknown block number: {}", block_number),
107                    });
108                }
109            }
110        }
111        
112        if blocks.is_empty() {
113            return Err(MTError::InvalidMessageStructure {
114                message: "No valid blocks found in message".to_string(),
115            });
116        }
117        
118        Ok(blocks)
119    }
120
121    /// Parse basic header block (Block 1)
122    fn parse_basic_header(&self, content: &str) -> Result<MessageBlock> {
123        // Format: F01BANKDEFFAXXX0123456789
124        if content.len() < 21 {
125            return Err(MTError::InvalidMessageStructure {
126                message: "Basic header block too short".to_string(),
127            });
128        }
129
130        let application_id = content[0..1].to_string();
131        let service_id = content[1..3].to_string();
132        let logical_terminal = content[3..15].to_string();
133        let session_number = content[15..19].to_string();
134        let sequence_number = content[19..].to_string();
135
136        Ok(MessageBlock::BasicHeader {
137            application_id,
138            service_id,
139            logical_terminal,
140            session_number,
141            sequence_number,
142        })
143    }
144
145    /// Parse application header block (Block 2)
146    fn parse_application_header(&self, content: &str) -> Result<MessageBlock> {
147        // Format: I103BANKDEFFAXXXU3003 or O1031535010605BANKDEFFAXXXU3003
148        if content.is_empty() {
149            return Err(MTError::InvalidMessageStructure {
150                message: "Application header block is empty".to_string(),
151            });
152        }
153
154        let input_output_identifier = content[0..1].to_string();
155        
156        if content.len() < 4 {
157            return Err(MTError::InvalidMessageStructure {
158                message: "Application header block too short".to_string(),
159            });
160        }
161
162        let message_type = content[1..4].to_string();
163        let remaining = &content[4..];
164        
165        // Parse the rest based on input/output identifier
166        let (destination_address, priority, delivery_monitoring, obsolescence_period) = 
167            if input_output_identifier == "I" {
168                // Input message format
169                if remaining.len() >= 12 {
170                    let dest = remaining[0..12].to_string();
171                    let prio = remaining.get(12..13).unwrap_or("").to_string();
172                    let del_mon = remaining.get(13..14).map(|s| s.to_string());
173                    let obs_per = remaining.get(14..17).map(|s| s.to_string());
174                    (dest, prio, del_mon, obs_per)
175                } else {
176                    (remaining.to_string(), String::new(), None, None)
177                }
178            } else {
179                // Output message format
180                (remaining.to_string(), String::new(), None, None)
181            };
182
183        Ok(MessageBlock::ApplicationHeader {
184            input_output_identifier,
185            message_type,
186            destination_address,
187            priority,
188            delivery_monitoring,
189            obsolescence_period,
190        })
191    }
192
193    /// Parse user header block (Block 3)
194    fn parse_user_header(&self, content: &str) -> Result<MessageBlock> {
195        let mut fields = HashMap::new();
196        
197        // Parse user header fields (format: {tag:value})
198        let user_field_regex = Regex::new(r"\{(\w+):([^}]*)\}")?;
199        for captures in user_field_regex.captures_iter(content) {
200            let tag = captures[1].to_string();
201            let value = captures[2].to_string();
202            fields.insert(tag, value);
203        }
204
205        Ok(MessageBlock::UserHeader { fields })
206    }
207
208    /// Parse text block (Block 4)
209    fn parse_text_block(&self, content: &str) -> Result<MessageBlock> {
210        let mut fields = Vec::new();
211        let lines: Vec<&str> = content.lines().collect();
212        
213        let mut current_tag = String::new();
214        let mut current_value = String::new();
215        
216        for line in lines {
217            let line = line.trim();
218            if line.is_empty() || line == "-" {
219                continue;
220            }
221            
222            if line.starts_with(':') && line.contains(':') {
223                // Save previous field if exists
224                if !current_tag.is_empty() {
225                    fields.push(Field::new(current_tag.clone(), current_value.trim().to_string()));
226                }
227                
228                // Parse new field
229                if let Some(colon_pos) = line[1..].find(':') {
230                    current_tag = line[1..colon_pos + 1].to_string();
231                    current_value = line[colon_pos + 2..].to_string();
232                } else {
233                    return Err(MTError::ParseError {
234                        line: 0,
235                        column: 0,
236                        message: format!("Invalid field format: {}", line),
237                    });
238                }
239            } else {
240                // Continuation of previous field
241                if !current_value.is_empty() {
242                    current_value.push('\n');
243                }
244                current_value.push_str(line);
245            }
246        }
247        
248        // Save last field
249        if !current_tag.is_empty() {
250            fields.push(Field::new(current_tag, current_value.trim().to_string()));
251        }
252
253        Ok(MessageBlock::TextBlock { fields })
254    }
255
256    /// Parse trailer block (Block 5)
257    fn parse_trailer_block(&self, content: &str) -> Result<MessageBlock> {
258        let mut fields = HashMap::new();
259        
260        // Parse trailer fields (format: {tag:value})
261        let trailer_field_regex = Regex::new(r"\{(\w+):([^}]*)\}")?;
262        for captures in trailer_field_regex.captures_iter(content) {
263            let tag = captures[1].to_string();
264            let value = captures[2].to_string();
265            fields.insert(tag, value);
266        }
267
268        Ok(MessageBlock::TrailerBlock { fields })
269    }
270
271    /// Extract message type from blocks
272    fn extract_message_type(&self, blocks: &[MessageBlock]) -> Result<String> {
273        for block in blocks {
274            if let MessageBlock::ApplicationHeader { message_type, .. } = block {
275                return Ok(message_type.clone());
276            }
277        }
278        
279        Err(MTError::InvalidMessageStructure {
280            message: "No application header block found".to_string(),
281        })
282    }
283}
284
285impl Default for MTParser {
286    fn default() -> Self {
287        Self::new().expect("Failed to create default parser")
288    }
289}
290
291/// Parse a SWIFT MT message from text
292pub fn parse_message(input: &str) -> Result<MTMessage> {
293    let parser = MTParser::new()?;
294    parser.parse(input)
295}
296
297/// Extract fields from a text block
298pub fn extract_fields(text_block: &str) -> Result<Vec<Field>> {
299    let parser = MTParser::new()?;
300    if let MessageBlock::TextBlock { fields } = parser.parse_text_block(text_block)? {
301        Ok(fields)
302    } else {
303        Err(MTError::InvalidMessageStructure {
304            message: "Failed to parse text block".to_string(),
305        })
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312
313    #[test]
314    fn test_basic_header_parsing() {
315        let parser = MTParser::new().unwrap();
316        let result = parser.parse_basic_header("F01BANKDEFFAXXX0123456789").unwrap();
317        
318        if let MessageBlock::BasicHeader { 
319            application_id, 
320            service_id, 
321            logical_terminal, 
322            session_number, 
323            sequence_number 
324        } = result {
325            assert_eq!(application_id, "F");
326            assert_eq!(service_id, "01");
327            assert_eq!(logical_terminal, "BANKDEFFAXXX");
328            assert_eq!(session_number, "0123");
329            assert_eq!(sequence_number, "456789");
330        } else {
331            panic!("Expected BasicHeader block");
332        }
333    }
334
335    #[test]
336    fn test_application_header_parsing() {
337        let parser = MTParser::new().unwrap();
338        let result = parser.parse_application_header("I103BANKDEFFAXXXU3003").unwrap();
339        
340        if let MessageBlock::ApplicationHeader { 
341            input_output_identifier, 
342            message_type, 
343            destination_address, 
344            priority,
345            .. 
346        } = result {
347            assert_eq!(input_output_identifier, "I");
348            assert_eq!(message_type, "103");
349            assert_eq!(destination_address, "BANKDEFFAXXX");
350            assert_eq!(priority, "U");
351        } else {
352            panic!("Expected ApplicationHeader block");
353        }
354    }
355
356    #[test]
357    fn test_text_block_parsing() {
358        let parser = MTParser::new().unwrap();
359        let text = ":20:FT21234567890\n:23B:CRED\n:32A:210315EUR1234567,89\n:50K:JOHN DOE\n:59:JANE SMITH";
360        let result = parser.parse_text_block(text).unwrap();
361        
362        if let MessageBlock::TextBlock { fields } = result {
363            assert_eq!(fields.len(), 5);
364            assert_eq!(fields[0].tag.as_str(), "20");
365            assert_eq!(fields[0].value(), "FT21234567890");
366            assert_eq!(fields[1].tag.as_str(), "23B");
367            assert_eq!(fields[1].value(), "CRED");
368        } else {
369            panic!("Expected TextBlock");
370        }
371    }
372}