swift_mt_message/
parser.rs

1use std::collections::HashMap;
2
3use crate::errors::{ParseError, Result};
4use crate::headers::{ApplicationHeader, BasicHeader, Trailer, UserHeader};
5use crate::messages::{
6    MT101, MT103, MT104, MT107, MT110, MT111, MT112, MT192, MT196, MT202, MT205, MT210, MT292,
7    MT296, MT900, MT910, MT920, MT935, MT940, MT941, MT942, MT950,
8};
9use crate::{ParsedSwiftMessage, RawBlocks, SwiftMessage, SwiftMessageBody};
10
11/// Type alias for the complex return type of field parsing
12type FieldParseResult = Result<(HashMap<String, Vec<String>>, Vec<String>)>;
13
14/// Main parser for SWIFT MT messages
15pub struct SwiftParser;
16
17impl SwiftParser {
18    /// Parse a raw SWIFT message string into a typed message
19    pub fn parse<T: SwiftMessageBody>(raw_message: &str) -> Result<SwiftMessage<T>> {
20        let blocks = Self::extract_blocks(raw_message)?;
21
22        // Parse headers
23        let basic_header = BasicHeader::parse(&blocks.block1.clone().unwrap_or_default())?;
24        let application_header =
25            ApplicationHeader::parse(&blocks.block2.clone().unwrap_or_default())?;
26        let user_header = blocks
27            .block3
28            .as_ref()
29            .map(|b| UserHeader::parse(b))
30            .transpose()?;
31        let trailer = blocks
32            .block5
33            .as_ref()
34            .map(|b| Trailer::parse(b))
35            .transpose()?;
36
37        // Extract message type from application header
38        let message_type = application_header.message_type.clone();
39
40        // Validate message type matches expected type
41        if message_type != T::message_type() {
42            return Err(ParseError::WrongMessageType {
43                expected: T::message_type().to_string(),
44                actual: message_type,
45            });
46        }
47
48        let block4 = blocks.block4.clone().unwrap_or_default();
49        // Parse block 4 fields
50        let (field_map, field_order) = Self::parse_block4_fields(&block4)?;
51
52        // Parse message body using the field map
53        let fields = T::from_fields(field_map)?;
54
55        Ok(SwiftMessage {
56            basic_header,
57            application_header,
58            user_header,
59            trailer,
60            blocks: Some(blocks),
61            message_type,
62            field_order,
63            fields,
64        })
65    }
66
67    /// Parse a raw SWIFT message string with automatic message type detection
68    pub fn parse_auto(raw_message: &str) -> Result<ParsedSwiftMessage> {
69        // First, extract blocks to get the message type
70        let blocks = Self::extract_blocks(raw_message)?;
71
72        // Parse application header to get message type
73        let application_header =
74            ApplicationHeader::parse(&blocks.block2.clone().unwrap_or_default())?;
75        let message_type = &application_header.message_type;
76
77        // Route to appropriate parser based on message type
78        match message_type.as_str() {
79            "101" => {
80                let parsed = Self::parse::<MT101>(raw_message)?;
81                Ok(ParsedSwiftMessage::MT101(Box::new(parsed)))
82            }
83            "103" => {
84                let parsed = Self::parse::<MT103>(raw_message)?;
85                Ok(ParsedSwiftMessage::MT103(Box::new(parsed)))
86            }
87            "104" => {
88                let parsed = Self::parse::<MT104>(raw_message)?;
89                Ok(ParsedSwiftMessage::MT104(Box::new(parsed)))
90            }
91            "107" => {
92                let parsed = Self::parse::<MT107>(raw_message)?;
93                Ok(ParsedSwiftMessage::MT107(Box::new(parsed)))
94            }
95            "110" => {
96                let parsed = Self::parse::<MT110>(raw_message)?;
97                Ok(ParsedSwiftMessage::MT110(Box::new(parsed)))
98            }
99            "111" => {
100                let parsed = Self::parse::<MT111>(raw_message)?;
101                Ok(ParsedSwiftMessage::MT111(Box::new(parsed)))
102            }
103            "112" => {
104                let parsed = Self::parse::<MT112>(raw_message)?;
105                Ok(ParsedSwiftMessage::MT112(Box::new(parsed)))
106            }
107            "202" => {
108                let parsed = Self::parse::<MT202>(raw_message)?;
109                Ok(ParsedSwiftMessage::MT202(Box::new(parsed)))
110            }
111            "205" => {
112                let parsed = Self::parse::<MT205>(raw_message)?;
113                Ok(ParsedSwiftMessage::MT205(Box::new(parsed)))
114            }
115            "210" => {
116                let parsed = Self::parse::<MT210>(raw_message)?;
117                Ok(ParsedSwiftMessage::MT210(Box::new(parsed)))
118            }
119            "900" => {
120                let parsed = Self::parse::<MT900>(raw_message)?;
121                Ok(ParsedSwiftMessage::MT900(Box::new(parsed)))
122            }
123            "910" => {
124                let parsed = Self::parse::<MT910>(raw_message)?;
125                Ok(ParsedSwiftMessage::MT910(Box::new(parsed)))
126            }
127            "920" => {
128                let parsed = Self::parse::<MT920>(raw_message)?;
129                Ok(ParsedSwiftMessage::MT920(Box::new(parsed)))
130            }
131            "935" => {
132                let parsed = Self::parse::<MT935>(raw_message)?;
133                Ok(ParsedSwiftMessage::MT935(Box::new(parsed)))
134            }
135            "940" => {
136                let parsed = Self::parse::<MT940>(raw_message)?;
137                Ok(ParsedSwiftMessage::MT940(Box::new(parsed)))
138            }
139            "941" => {
140                let parsed = Self::parse::<MT941>(raw_message)?;
141                Ok(ParsedSwiftMessage::MT941(Box::new(parsed)))
142            }
143            "942" => {
144                let parsed = Self::parse::<MT942>(raw_message)?;
145                Ok(ParsedSwiftMessage::MT942(Box::new(parsed)))
146            }
147            "950" => {
148                let parsed = Self::parse::<MT950>(raw_message)?;
149                Ok(ParsedSwiftMessage::MT950(Box::new(parsed)))
150            }
151            "192" => {
152                let parsed = Self::parse::<MT192>(raw_message)?;
153                Ok(ParsedSwiftMessage::MT192(Box::new(parsed)))
154            }
155            "196" => {
156                let parsed = Self::parse::<MT196>(raw_message)?;
157                Ok(ParsedSwiftMessage::MT196(Box::new(parsed)))
158            }
159            "292" => {
160                let parsed = Self::parse::<MT292>(raw_message)?;
161                Ok(ParsedSwiftMessage::MT292(Box::new(parsed)))
162            }
163            "296" => {
164                let parsed = Self::parse::<MT296>(raw_message)?;
165                Ok(ParsedSwiftMessage::MT296(Box::new(parsed)))
166            }
167            _ => Err(ParseError::UnsupportedMessageType {
168                message_type: message_type.clone(),
169            }),
170        }
171    }
172
173    /// Extract message blocks from raw SWIFT message
174    pub fn extract_blocks(raw_message: &str) -> Result<RawBlocks> {
175        let mut blocks = RawBlocks::default();
176
177        // Find block boundaries
178        let mut current_pos = 0;
179
180        // Block 1: Basic Header {1:...}
181        if let Some(start) = raw_message[current_pos..].find("{1:") {
182            let start = current_pos + start;
183            if let Some(end) = raw_message[start..].find('}') {
184                let end = start + end;
185                blocks.block1 = Some(raw_message[start + 3..end].to_string());
186                current_pos = end + 1;
187            }
188        }
189
190        // Block 2: Application Header {2:...}
191        if let Some(start) = raw_message[current_pos..].find("{2:") {
192            let start = current_pos + start;
193            if let Some(end) = raw_message[start..].find('}') {
194                let end = start + end;
195                blocks.block2 = Some(raw_message[start + 3..end].to_string());
196                current_pos = end + 1;
197            }
198        }
199
200        // Block 3: User Header {3:...} (optional)
201        if let Some(start) = raw_message[current_pos..].find("{3:") {
202            let start = current_pos + start;
203            // Find matching closing brace for block 3
204            if let Some(end) = Self::find_matching_brace(&raw_message[start..]) {
205                let end = start + end;
206                blocks.block3 = Some(raw_message[start + 3..end].to_string());
207                current_pos = end + 1;
208            }
209        }
210
211        // Block 4: Text Block {4:\n...-}
212        if let Some(start) = raw_message[current_pos..].find("{4:") {
213            let start = current_pos + start;
214            if let Some(end) = raw_message[start..].find("-}") {
215                let end = start + end;
216                blocks.block4 = Some(raw_message[start + 3..end].to_string());
217                current_pos = end + 2;
218            }
219        }
220
221        // Block 5: Trailer {5:...} (optional)
222        if let Some(start) = raw_message[current_pos..].find("{5:") {
223            let start = current_pos + start;
224            if let Some(end) = raw_message[start..].find('}') {
225                let end = start + end;
226                blocks.block5 = Some(raw_message[start + 3..end].to_string());
227            }
228        }
229
230        if blocks.block1.is_none() || blocks.block2.is_none() || blocks.block4.is_none() {
231            return Err(ParseError::InvalidBlockStructure {
232                message: "Missing required blocks (1, 2, or 4)".to_string(),
233            });
234        }
235
236        Ok(blocks)
237    }
238
239    /// Parse block 4 fields into a field map and preserve field order
240    fn parse_block4_fields(block4: &str) -> FieldParseResult {
241        let mut field_map: HashMap<String, Vec<String>> = HashMap::new();
242        let mut field_order = Vec::new();
243
244        // Remove leading/trailing whitespace and newlines
245        let content = block4.trim();
246
247        // Split by field markers (:XX:)
248        let mut current_pos = 0;
249
250        while current_pos < content.len() {
251            // Find next field marker
252            if let Some(field_start) = content[current_pos..].find(':') {
253                let field_start = current_pos + field_start;
254
255                // Extract field tag (characters after : until next :)
256                if let Some(tag_end) = content[field_start + 1..].find(':') {
257                    let tag_end = field_start + 1 + tag_end;
258                    let raw_field_tag = content[field_start + 1..tag_end].to_string();
259
260                    // Normalize field tag by removing option letters (A, F, K, etc.)
261                    let field_tag = Self::normalize_field_tag(&raw_field_tag);
262
263                    // Find the end of field value (next field marker or end of content)
264                    let value_start = tag_end + 1;
265                    let value_end = if let Some(next_field) = content[value_start..].find("\n:") {
266                        value_start + next_field
267                    } else {
268                        content.len()
269                    };
270
271                    let field_value = content[value_start..value_end].trim().to_string();
272
273                    // Store the complete field string including tag prefix for compatibility
274                    let complete_field_string = format!(":{raw_field_tag}:{field_value}");
275
276                    // Add to existing Vec or create new Vec for this field tag
277                    field_map
278                        .entry(field_tag.clone())
279                        .or_default()
280                        .push(complete_field_string);
281
282                    // Only add to field_order if this is the first occurrence of this field
283                    if !field_order.contains(&field_tag) {
284                        field_order.push(field_tag);
285                    }
286
287                    current_pos = value_end;
288                } else {
289                    // Last field or malformed
290                    break;
291                }
292            } else {
293                break;
294            }
295        }
296
297        Ok((field_map, field_order))
298    }
299
300    /// Normalize field tag by removing option letters (A, F, K, etc.)
301    /// Example: "50K" -> "50", "59A" -> "59", "20" -> "20"
302    /// But preserve option letters for fields that have multiple variants like 23B/23E, 71A/71F/71G
303    fn normalize_field_tag(raw_tag: &str) -> String {
304        // Extract the numeric part at the beginning
305        let mut numeric_part = String::new();
306        for ch in raw_tag.chars() {
307            if ch.is_ascii_digit() {
308                numeric_part.push(ch);
309            } else {
310                break;
311            }
312        }
313
314        // If we have letters after the number, check if it's a known option
315        if numeric_part.len() < raw_tag.len() {
316            let remaining = &raw_tag[numeric_part.len()..];
317
318            // For certain field numbers, preserve the option letter to avoid conflicts
319            match numeric_part.as_str() {
320                "11" | "13" | "23" | "26" | "32" | "33" | "52" | "53" | "54" | "55" | "56"
321                | "57" | "58" | "71" | "77" => {
322                    // Keep option letters for fields that have multiple variants or specific formats
323                    // 11A (MT and Date - Option A), 11S (MT and Date - Option S)
324                    // 13C (Time Indication)
325                    // 23B (Bank Operation Code) vs 23E (Instruction Code)
326                    // 26T (Transaction Type Code)
327                    // 32A (Value Date/Currency/Amount)
328                    // 33B (Currency/Instructed Amount)
329                    // 52A (Ordering Institution)
330                    // 53A (Sender's Correspondent)
331                    // 54A (Receiver's Correspondent)
332                    // 55A (Third Reimbursement Institution)
333                    // 56A (Intermediary Institution)
334                    // 57A (Account With Institution)
335                    // 71A (Details of Charges) vs 71F (Sender's Charges) vs 71G (Receiver's Charges)
336                    // 77B (Regulatory Reporting)
337                    return raw_tag.to_string();
338                }
339                _ => {
340                    // For other fields, remove option letters as before
341                    if remaining
342                        .chars()
343                        .all(|c| c.is_ascii_alphabetic() && c.is_ascii_uppercase())
344                    {
345                        // It's an option letter, return just the numeric part
346                        return numeric_part;
347                    }
348                }
349            }
350        }
351
352        // If no option letter found, return the original tag
353        raw_tag.to_string()
354    }
355
356    /// Find the matching closing brace for a block that starts with an opening brace
357    /// Handles nested braces correctly
358    fn find_matching_brace(text: &str) -> Option<usize> {
359        let mut chars = text.char_indices();
360
361        // Skip the first character (should be '{')
362        let mut brace_count = if let Some((_, '{')) = chars.next() {
363            1
364        } else {
365            return None;
366        };
367
368        for (i, ch) in chars {
369            match ch {
370                '{' => brace_count += 1,
371                '}' => {
372                    brace_count -= 1;
373                    if brace_count == 0 {
374                        return Some(i);
375                    }
376                }
377                _ => {}
378            }
379        }
380
381        None
382    }
383}
384
385/// Parse a SwiftMessage from a string representation
386/// This is a placeholder implementation for the macro system
387pub fn parse_swift_message_from_string(value: &str) -> Result<HashMap<String, Vec<String>>> {
388    // For now, this is a stub implementation
389    // In a real implementation, this would parse the string representation
390    // of a SwiftMessage back into a field map
391
392    // As a temporary solution, we'll assume the value is a simple field representation
393    // and try to parse it as a mini SWIFT block
394    let mut field_map = HashMap::new();
395
396    // Split by lines and parse each field
397    for line in value.lines() {
398        if line.trim().is_empty() {
399            continue;
400        }
401
402        // Look for field pattern :XX:value
403        if let Some(colon_pos) = line.find(':') {
404            if let Some(second_colon) = line[colon_pos + 1..].find(':') {
405                let second_colon_pos = colon_pos + 1 + second_colon;
406                let field_tag = line[colon_pos + 1..second_colon_pos].to_string();
407                let _field_value = line[second_colon_pos + 1..].to_string();
408
409                field_map
410                    .entry(field_tag)
411                    .or_insert_with(Vec::new)
412                    .push(format!(":{}", &line[colon_pos + 1..]));
413            }
414        }
415    }
416
417    Ok(field_map)
418}
419
420/// Serialize a SwiftMessage field map to a string representation
421/// This is a placeholder implementation for the macro system
422pub fn serialize_swift_message_to_string(fields: &HashMap<String, Vec<String>>) -> String {
423    // For now, this is a stub implementation
424    // In a real implementation, this would serialize the field map
425    // into a string representation of a SwiftMessage
426
427    let mut result = String::new();
428
429    // Simple serialization: just join all field values with newlines
430    for field_values in fields.values() {
431        for field_value in field_values {
432            // field_value should already be in the format ":XX:value"
433            result.push_str(field_value);
434            result.push('\n');
435        }
436    }
437
438    // Remove trailing newline
439    if result.ends_with('\n') {
440        result.pop();
441    }
442
443    result
444}
445
446#[cfg(test)]
447mod tests {
448    use super::*;
449
450    #[test]
451    fn test_extract_blocks() {
452        let raw_message = "{1:F01BANKDEFFAXXX0123456789}{2:I103BANKDEFFAXXXU3003}{4:\n:20:FT21234567890\n:23B:CRED\n-}";
453        let blocks = SwiftParser::extract_blocks(raw_message).unwrap();
454
455        assert!(blocks.block1.is_some());
456        assert!(blocks.block2.is_some());
457        assert!(blocks.block4.is_some());
458        assert_eq!(blocks.block1.as_ref().unwrap(), "F01BANKDEFFAXXX0123456789");
459        assert_eq!(blocks.block2.as_ref().unwrap(), "I103BANKDEFFAXXXU3003");
460    }
461
462    #[test]
463    fn test_parse_block4_fields() {
464        let block4 = "\n:20:FT21234567890\n:23B:CRED\n:32A:210315EUR1234567,89\n";
465        let (field_map, field_order) = SwiftParser::parse_block4_fields(block4).unwrap();
466
467        assert_eq!(
468            field_map.get("20"),
469            Some(&vec![":20:FT21234567890".to_string()])
470        );
471        assert_eq!(field_map.get("23B"), Some(&vec![":23B:CRED".to_string()]));
472        assert_eq!(
473            field_map.get("32A"),
474            Some(&vec![":32A:210315EUR1234567,89".to_string()])
475        );
476
477        assert_eq!(field_order, vec!["20", "23B", "32A"]);
478    }
479
480    #[test]
481    fn test_debug_mt103_fields() {
482        let block4 = r#"
483:20:FT21001234567890
484:23B:CRED
485:32A:240101USD1000,00
486:50K:/1234567890
487ACME CORPORATION
488123 MAIN STREET
489NEW YORK NY 10001
490:52A:BNPAFRPPXXX
491:57A:DEUTDEFFXXX
492:59:/DE89370400440532013000
493MUELLER GMBH
494HAUPTSTRASSE 1
49510115 BERLIN
496:70:PAYMENT FOR INVOICE 12345
497:71A:OUR
498"#;
499        let (field_map, field_order) = SwiftParser::parse_block4_fields(block4).unwrap();
500
501        println!("Extracted fields:");
502        for (tag, values) in &field_map {
503            println!("  {tag}: {values:?}");
504        }
505        println!("Field order: {field_order:?}");
506
507        // Check specific fields
508        assert!(field_map.contains_key("20"));
509        assert!(field_map.contains_key("23B"));
510        assert!(field_map.contains_key("32A"));
511        assert!(field_map.contains_key("50"));
512        assert!(field_map.contains_key("52A"));
513        assert!(field_map.contains_key("57A"));
514        assert!(field_map.contains_key("59"));
515        assert!(field_map.contains_key("70"));
516        assert!(field_map.contains_key("71A"));
517    }
518
519    #[test]
520    fn test_block3_parsing_with_nested_tags() {
521        let raw_message = r#"{1:F01BNPAFRPPXXX0000000000}{2:O1031234240101DEUTDEFFXXXX12345678952401011234N}{3:{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}}{4:
522:20:FT21001234567890
523:23B:CRED
524-}"#;
525
526        let blocks = SwiftParser::extract_blocks(raw_message).unwrap();
527
528        assert!(blocks.block3.is_some());
529        let block3_content = blocks.block3.unwrap();
530        println!("Block 3 content: '{block3_content}'");
531
532        // Should contain both tags
533        assert_eq!(
534            block3_content,
535            "{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}"
536        );
537        assert!(block3_content.contains("103:EBA"));
538        assert!(block3_content.contains("121:180f1e65-90e0-44d5-a49a-92b55eb3025f"));
539    }
540
541    #[test]
542    fn test_find_matching_brace() {
543        // Simple case: "{simple}" -> closing } at position 7
544        assert_eq!(SwiftParser::find_matching_brace("{simple}"), Some(7));
545
546        // Nested braces: "{outer{inner}outer}" -> closing } at position 18
547        assert_eq!(
548            SwiftParser::find_matching_brace("{outer{inner}outer}"),
549            Some(18)
550        );
551
552        // SWIFT block 3 case: "{{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}}"
553        let test_str = "{{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}}";
554        let expected_pos = test_str.len() - 1; // Last character position
555        assert_eq!(
556            SwiftParser::find_matching_brace(test_str),
557            Some(expected_pos)
558        );
559
560        // Simple nested: "{103:EBA}" -> closing } at position 8
561        assert_eq!(SwiftParser::find_matching_brace("{103:EBA}"), Some(8));
562
563        // No closing brace
564        assert_eq!(SwiftParser::find_matching_brace("{no_close"), None);
565
566        // Not starting with brace
567        assert_eq!(SwiftParser::find_matching_brace("no_brace"), None);
568    }
569
570    #[test]
571    fn debug_find_matching_brace() {
572        let test_str = "{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}";
573        println!("Test string: '{test_str}'");
574        println!("Length: {}", test_str.len());
575
576        let result = SwiftParser::find_matching_brace(test_str);
577        println!("Result: {result:?}");
578
579        // Let's manually check what character is at different positions
580        for (i, ch) in test_str.char_indices() {
581            println!("Position {i}: '{ch}'");
582        }
583    }
584
585    #[test]
586    fn test_parse_auto_mt103() {
587        let raw_mt103 = r#"{1:F01BANKDEFFAXXX0123456789}{2:I103BANKDEFFAXXXU3003}{4:
588:20:FT21234567890
589:23B:CRED
590:32A:210315EUR1234567,89
591:50K:ACME CORPORATION
592123 BUSINESS AVENUE
593NEW YORK NY 10001
594:52A:BANKDEFF
595:57A:DEUTDEFF
596:59A:/DE89370400440532013000
597DEUTDEFF
598:70:PAYMENT FOR SERVICES
599:71A:OUR
600-}"#;
601
602        let parsed = SwiftParser::parse_auto(raw_mt103).unwrap();
603
604        // Check that it detected the correct message type
605        assert_eq!(parsed.message_type(), "103");
606
607        // Check that we can extract the MT103 message
608        let mt103_msg = parsed.as_mt103().unwrap();
609        assert_eq!(mt103_msg.message_type, "103");
610
611        println!("Successfully parsed MT103 message with auto-detection");
612    }
613
614    #[test]
615    fn test_parse_auto_unsupported_type() {
616        let raw_message = r#"{1:F01BANKDEFFAXXX0123456789}{2:I999BANKDEFFAXXXU3003}{4:
617:20:FT21234567890
618-}"#;
619
620        let result = SwiftParser::parse_auto(raw_message);
621        assert!(result.is_err());
622
623        if let Err(ParseError::UnsupportedMessageType { message_type }) = result {
624            assert_eq!(message_type, "999");
625        } else {
626            panic!("Expected UnsupportedMessageType error");
627        }
628    }
629}