swift_mt_message/
parser.rs1use std::collections::HashMap;
2
3use crate::errors::{ParseError, Result};
4use crate::headers::{ApplicationHeader, BasicHeader, Trailer, UserHeader};
5use crate::messages::{MT103, MT202};
6use crate::{ParsedSwiftMessage, RawBlocks, SwiftMessage, SwiftMessageBody};
7
8type FieldParseResult = Result<(HashMap<String, Vec<String>>, Vec<String>)>;
10
11pub struct SwiftParser;
13
14impl SwiftParser {
15 pub fn parse<T: SwiftMessageBody>(raw_message: &str) -> Result<SwiftMessage<T>> {
17 let blocks = Self::extract_blocks(raw_message)?;
18
19 let basic_header = BasicHeader::parse(&blocks.block1.clone().unwrap_or_default())?;
21 let application_header =
22 ApplicationHeader::parse(&blocks.block2.clone().unwrap_or_default())?;
23 let user_header = blocks
24 .block3
25 .as_ref()
26 .map(|b| UserHeader::parse(b))
27 .transpose()?;
28 let trailer = blocks
29 .block5
30 .as_ref()
31 .map(|b| Trailer::parse(b))
32 .transpose()?;
33
34 let message_type = application_header.message_type.clone();
36
37 if message_type != T::message_type() {
39 return Err(ParseError::WrongMessageType {
40 expected: T::message_type().to_string(),
41 actual: message_type,
42 });
43 }
44
45 let (field_map, field_order) = Self::parse_block4_fields(&blocks.block4)?;
47
48 let fields = T::from_fields(field_map)?;
50
51 Ok(SwiftMessage {
52 basic_header,
53 application_header,
54 user_header,
55 trailer,
56 blocks,
57 message_type,
58 field_order,
59 fields,
60 })
61 }
62
63 pub fn parse_auto(raw_message: &str) -> Result<ParsedSwiftMessage> {
65 let blocks = Self::extract_blocks(raw_message)?;
67
68 let application_header =
70 ApplicationHeader::parse(&blocks.block2.clone().unwrap_or_default())?;
71 let message_type = &application_header.message_type;
72
73 match message_type.as_str() {
75 "103" => {
76 let parsed = Self::parse::<MT103>(raw_message)?;
77 Ok(ParsedSwiftMessage::MT103(Box::new(parsed)))
78 }
79 "202" => {
80 let parsed = Self::parse::<MT202>(raw_message)?;
81 Ok(ParsedSwiftMessage::MT202(Box::new(parsed)))
82 }
83 _ => Err(ParseError::UnsupportedMessageType {
84 message_type: message_type.clone(),
85 }),
86 }
87 }
88
89 pub fn extract_blocks(raw_message: &str) -> Result<RawBlocks> {
91 let mut blocks = RawBlocks::default();
92
93 let mut current_pos = 0;
95
96 if let Some(start) = raw_message[current_pos..].find("{1:") {
98 let start = current_pos + start;
99 if let Some(end) = raw_message[start..].find('}') {
100 let end = start + end;
101 blocks.block1 = Some(raw_message[start + 3..end].to_string());
102 current_pos = end + 1;
103 }
104 }
105
106 if let Some(start) = raw_message[current_pos..].find("{2:") {
108 let start = current_pos + start;
109 if let Some(end) = raw_message[start..].find('}') {
110 let end = start + end;
111 blocks.block2 = Some(raw_message[start + 3..end].to_string());
112 current_pos = end + 1;
113 }
114 }
115
116 if let Some(start) = raw_message[current_pos..].find("{3:") {
118 let start = current_pos + start;
119 if let Some(end) = Self::find_matching_brace(&raw_message[start..]) {
121 let end = start + end;
122 blocks.block3 = Some(raw_message[start + 3..end].to_string());
123 current_pos = end + 1;
124 }
125 }
126
127 if let Some(start) = raw_message[current_pos..].find("{4:") {
129 let start = current_pos + start;
130 if let Some(end) = raw_message[start..].find("-}") {
131 let end = start + end;
132 blocks.block4 = raw_message[start + 3..end].to_string();
133 current_pos = end + 2;
134 }
135 }
136
137 if let Some(start) = raw_message[current_pos..].find("{5:") {
139 let start = current_pos + start;
140 if let Some(end) = raw_message[start..].find('}') {
141 let end = start + end;
142 blocks.block5 = Some(raw_message[start + 3..end].to_string());
143 }
144 }
145
146 if blocks.block1.is_none() || blocks.block2.is_none() || blocks.block4.is_empty() {
147 return Err(ParseError::InvalidBlockStructure {
148 message: "Missing required blocks (1, 2, or 4)".to_string(),
149 });
150 }
151
152 Ok(blocks)
153 }
154
155 fn parse_block4_fields(block4: &str) -> FieldParseResult {
157 let mut field_map: HashMap<String, Vec<String>> = HashMap::new();
158 let mut field_order = Vec::new();
159
160 let content = block4.trim();
162
163 let mut current_pos = 0;
165
166 while current_pos < content.len() {
167 if let Some(field_start) = content[current_pos..].find(':') {
169 let field_start = current_pos + field_start;
170
171 if let Some(tag_end) = content[field_start + 1..].find(':') {
173 let tag_end = field_start + 1 + tag_end;
174 let raw_field_tag = content[field_start + 1..tag_end].to_string();
175
176 let field_tag = Self::normalize_field_tag(&raw_field_tag);
178
179 let value_start = tag_end + 1;
181 let value_end = if let Some(next_field) = content[value_start..].find("\n:") {
182 value_start + next_field
183 } else {
184 content.len()
185 };
186
187 let field_value = content[value_start..value_end].trim().to_string();
188
189 let complete_field_string = format!(":{}:{}", raw_field_tag, field_value);
191
192 field_map
194 .entry(field_tag.clone())
195 .or_default()
196 .push(complete_field_string);
197
198 if !field_order.contains(&field_tag) {
200 field_order.push(field_tag);
201 }
202
203 current_pos = value_end;
204 } else {
205 break;
207 }
208 } else {
209 break;
210 }
211 }
212
213 Ok((field_map, field_order))
214 }
215
216 fn normalize_field_tag(raw_tag: &str) -> String {
220 let mut numeric_part = String::new();
222 for ch in raw_tag.chars() {
223 if ch.is_ascii_digit() {
224 numeric_part.push(ch);
225 } else {
226 break;
227 }
228 }
229
230 if numeric_part.len() < raw_tag.len() {
232 let remaining = &raw_tag[numeric_part.len()..];
233
234 match numeric_part.as_str() {
236 "13" | "23" | "26" | "32" | "33" | "52" | "53" | "54" | "55" | "56" | "57"
237 | "58" | "71" | "77" => {
238 return raw_tag.to_string();
253 }
254 _ => {
255 if remaining
257 .chars()
258 .all(|c| c.is_ascii_alphabetic() && c.is_ascii_uppercase())
259 {
260 return numeric_part;
262 }
263 }
264 }
265 }
266
267 raw_tag.to_string()
269 }
270
271 fn find_matching_brace(text: &str) -> Option<usize> {
274 let mut chars = text.char_indices();
275
276 let mut brace_count = if let Some((_, '{')) = chars.next() {
278 1
279 } else {
280 return None;
281 };
282
283 for (i, ch) in chars {
284 match ch {
285 '{' => brace_count += 1,
286 '}' => {
287 brace_count -= 1;
288 if brace_count == 0 {
289 return Some(i);
290 }
291 }
292 _ => {}
293 }
294 }
295
296 None
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 #[test]
305 fn test_extract_blocks() {
306 let raw_message = "{1:F01BANKDEFFAXXX0123456789}{2:I103BANKDEFFAXXXU3003}{4:\n:20:FT21234567890\n:23B:CRED\n-}";
307 let blocks = SwiftParser::extract_blocks(raw_message).unwrap();
308
309 assert!(blocks.block1.is_some());
310 assert!(blocks.block2.is_some());
311 assert!(!blocks.block4.is_empty());
312 assert_eq!(blocks.block1.as_ref().unwrap(), "F01BANKDEFFAXXX0123456789");
313 assert_eq!(blocks.block2.as_ref().unwrap(), "I103BANKDEFFAXXXU3003");
314 }
315
316 #[test]
317 fn test_parse_block4_fields() {
318 let block4 = "\n:20:FT21234567890\n:23B:CRED\n:32A:210315EUR1234567,89\n";
319 let (field_map, field_order) = SwiftParser::parse_block4_fields(block4).unwrap();
320
321 assert_eq!(
322 field_map.get("20"),
323 Some(&vec![":20:FT21234567890".to_string()])
324 );
325 assert_eq!(field_map.get("23B"), Some(&vec![":23B:CRED".to_string()]));
326 assert_eq!(
327 field_map.get("32A"),
328 Some(&vec![":32A:210315EUR1234567,89".to_string()])
329 );
330
331 assert_eq!(field_order, vec!["20", "23B", "32A"]);
332 }
333
334 #[test]
335 fn test_debug_mt103_fields() {
336 let block4 = r#"
337:20:FT21001234567890
338:23B:CRED
339:32A:240101USD1000,00
340:50K:/1234567890
341ACME CORPORATION
342123 MAIN STREET
343NEW YORK NY 10001
344:52A:BNPAFRPPXXX
345:57A:DEUTDEFFXXX
346:59:/DE89370400440532013000
347MUELLER GMBH
348HAUPTSTRASSE 1
34910115 BERLIN
350:70:PAYMENT FOR INVOICE 12345
351:71A:OUR
352"#;
353 let (field_map, field_order) = SwiftParser::parse_block4_fields(block4).unwrap();
354
355 println!("Extracted fields:");
356 for (tag, values) in &field_map {
357 println!(" {}: {:?}", tag, values);
358 }
359 println!("Field order: {:?}", field_order);
360
361 assert!(field_map.contains_key("20"));
363 assert!(field_map.contains_key("23B"));
364 assert!(field_map.contains_key("32A"));
365 assert!(field_map.contains_key("50"));
366 assert!(field_map.contains_key("52A"));
367 assert!(field_map.contains_key("57A"));
368 assert!(field_map.contains_key("59"));
369 assert!(field_map.contains_key("70"));
370 assert!(field_map.contains_key("71A"));
371 }
372
373 #[test]
374 fn test_block3_parsing_with_nested_tags() {
375 let raw_message = r#"{1:F01BNPAFRPPXXX0000000000}{2:O1031234240101DEUTDEFFXXXX12345678952401011234N}{3:{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}}{4:
376:20:FT21001234567890
377:23B:CRED
378-}"#;
379
380 let blocks = SwiftParser::extract_blocks(raw_message).unwrap();
381
382 assert!(blocks.block3.is_some());
383 let block3_content = blocks.block3.unwrap();
384 println!("Block 3 content: '{}'", block3_content);
385
386 assert_eq!(
388 block3_content,
389 "{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}"
390 );
391 assert!(block3_content.contains("103:EBA"));
392 assert!(block3_content.contains("121:180f1e65-90e0-44d5-a49a-92b55eb3025f"));
393 }
394
395 #[test]
396 fn test_find_matching_brace() {
397 assert_eq!(SwiftParser::find_matching_brace("{simple}"), Some(7));
399
400 assert_eq!(
402 SwiftParser::find_matching_brace("{outer{inner}outer}"),
403 Some(18)
404 );
405
406 let test_str = "{{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}}";
408 let expected_pos = test_str.len() - 1; assert_eq!(
410 SwiftParser::find_matching_brace(test_str),
411 Some(expected_pos)
412 );
413
414 assert_eq!(SwiftParser::find_matching_brace("{103:EBA}"), Some(8));
416
417 assert_eq!(SwiftParser::find_matching_brace("{no_close"), None);
419
420 assert_eq!(SwiftParser::find_matching_brace("no_brace"), None);
422 }
423
424 #[test]
425 fn debug_find_matching_brace() {
426 let test_str = "{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}";
427 println!("Test string: '{}'", test_str);
428 println!("Length: {}", test_str.len());
429
430 let result = SwiftParser::find_matching_brace(test_str);
431 println!("Result: {:?}", result);
432
433 for (i, ch) in test_str.char_indices() {
435 println!("Position {}: '{}'", i, ch);
436 }
437 }
438
439 #[test]
440 fn test_parse_auto_mt103() {
441 let raw_mt103 = r#"{1:F01BANKDEFFAXXX0123456789}{2:I103BANKDEFFAXXXU3003}{4:
442:20:FT21234567890
443:23B:CRED
444:32A:210315EUR1234567,89
445:50K:ACME CORPORATION
446123 BUSINESS AVENUE
447NEW YORK NY 10001
448:52A:BANKDEFF
449:57A:DEUTDEFF
450:59A:/DE89370400440532013000
451DEUTDEFF
452:70:PAYMENT FOR SERVICES
453:71A:OUR
454-}"#;
455
456 let parsed = SwiftParser::parse_auto(raw_mt103).unwrap();
457
458 assert_eq!(parsed.message_type(), "103");
460
461 let mt103_msg = parsed.as_mt103().unwrap();
463 assert_eq!(mt103_msg.message_type, "103");
464
465 println!("Successfully parsed MT103 message with auto-detection");
466 }
467
468 #[test]
469 fn test_parse_auto_unsupported_type() {
470 let raw_message = r#"{1:F01BANKDEFFAXXX0123456789}{2:I999BANKDEFFAXXXU3003}{4:
471:20:FT21234567890
472-}"#;
473
474 let result = SwiftParser::parse_auto(raw_message);
475 assert!(result.is_err());
476
477 if let Err(ParseError::UnsupportedMessageType { message_type }) = result {
478 assert_eq!(message_type, "999");
479 } else {
480 panic!("Expected UnsupportedMessageType error");
481 }
482 }
483}