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, MT205, MT900};
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 "205" => {
84 let parsed = Self::parse::<MT205>(raw_message)?;
85 Ok(ParsedSwiftMessage::MT205(Box::new(parsed)))
86 }
87 "900" => {
88 let parsed = Self::parse::<MT900>(raw_message)?;
89 Ok(ParsedSwiftMessage::MT900(Box::new(parsed)))
90 }
91 _ => Err(ParseError::UnsupportedMessageType {
92 message_type: message_type.clone(),
93 }),
94 }
95 }
96
97 pub fn extract_blocks(raw_message: &str) -> Result<RawBlocks> {
99 let mut blocks = RawBlocks::default();
100
101 let mut current_pos = 0;
103
104 if let Some(start) = raw_message[current_pos..].find("{1:") {
106 let start = current_pos + start;
107 if let Some(end) = raw_message[start..].find('}') {
108 let end = start + end;
109 blocks.block1 = Some(raw_message[start + 3..end].to_string());
110 current_pos = end + 1;
111 }
112 }
113
114 if let Some(start) = raw_message[current_pos..].find("{2:") {
116 let start = current_pos + start;
117 if let Some(end) = raw_message[start..].find('}') {
118 let end = start + end;
119 blocks.block2 = Some(raw_message[start + 3..end].to_string());
120 current_pos = end + 1;
121 }
122 }
123
124 if let Some(start) = raw_message[current_pos..].find("{3:") {
126 let start = current_pos + start;
127 if let Some(end) = Self::find_matching_brace(&raw_message[start..]) {
129 let end = start + end;
130 blocks.block3 = Some(raw_message[start + 3..end].to_string());
131 current_pos = end + 1;
132 }
133 }
134
135 if let Some(start) = raw_message[current_pos..].find("{4:") {
137 let start = current_pos + start;
138 if let Some(end) = raw_message[start..].find("-}") {
139 let end = start + end;
140 blocks.block4 = raw_message[start + 3..end].to_string();
141 current_pos = end + 2;
142 }
143 }
144
145 if let Some(start) = raw_message[current_pos..].find("{5:") {
147 let start = current_pos + start;
148 if let Some(end) = raw_message[start..].find('}') {
149 let end = start + end;
150 blocks.block5 = Some(raw_message[start + 3..end].to_string());
151 }
152 }
153
154 if blocks.block1.is_none() || blocks.block2.is_none() || blocks.block4.is_empty() {
155 return Err(ParseError::InvalidBlockStructure {
156 message: "Missing required blocks (1, 2, or 4)".to_string(),
157 });
158 }
159
160 Ok(blocks)
161 }
162
163 fn parse_block4_fields(block4: &str) -> FieldParseResult {
165 let mut field_map: HashMap<String, Vec<String>> = HashMap::new();
166 let mut field_order = Vec::new();
167
168 let content = block4.trim();
170
171 let mut current_pos = 0;
173
174 while current_pos < content.len() {
175 if let Some(field_start) = content[current_pos..].find(':') {
177 let field_start = current_pos + field_start;
178
179 if let Some(tag_end) = content[field_start + 1..].find(':') {
181 let tag_end = field_start + 1 + tag_end;
182 let raw_field_tag = content[field_start + 1..tag_end].to_string();
183
184 let field_tag = Self::normalize_field_tag(&raw_field_tag);
186
187 let value_start = tag_end + 1;
189 let value_end = if let Some(next_field) = content[value_start..].find("\n:") {
190 value_start + next_field
191 } else {
192 content.len()
193 };
194
195 let field_value = content[value_start..value_end].trim().to_string();
196
197 let complete_field_string = format!(":{}:{}", raw_field_tag, field_value);
199
200 field_map
202 .entry(field_tag.clone())
203 .or_default()
204 .push(complete_field_string);
205
206 if !field_order.contains(&field_tag) {
208 field_order.push(field_tag);
209 }
210
211 current_pos = value_end;
212 } else {
213 break;
215 }
216 } else {
217 break;
218 }
219 }
220
221 Ok((field_map, field_order))
222 }
223
224 fn normalize_field_tag(raw_tag: &str) -> String {
228 let mut numeric_part = String::new();
230 for ch in raw_tag.chars() {
231 if ch.is_ascii_digit() {
232 numeric_part.push(ch);
233 } else {
234 break;
235 }
236 }
237
238 if numeric_part.len() < raw_tag.len() {
240 let remaining = &raw_tag[numeric_part.len()..];
241
242 match numeric_part.as_str() {
244 "13" | "23" | "26" | "32" | "33" | "52" | "53" | "54" | "55" | "56" | "57"
245 | "58" | "71" | "77" => {
246 return raw_tag.to_string();
261 }
262 _ => {
263 if remaining
265 .chars()
266 .all(|c| c.is_ascii_alphabetic() && c.is_ascii_uppercase())
267 {
268 return numeric_part;
270 }
271 }
272 }
273 }
274
275 raw_tag.to_string()
277 }
278
279 fn find_matching_brace(text: &str) -> Option<usize> {
282 let mut chars = text.char_indices();
283
284 let mut brace_count = if let Some((_, '{')) = chars.next() {
286 1
287 } else {
288 return None;
289 };
290
291 for (i, ch) in chars {
292 match ch {
293 '{' => brace_count += 1,
294 '}' => {
295 brace_count -= 1;
296 if brace_count == 0 {
297 return Some(i);
298 }
299 }
300 _ => {}
301 }
302 }
303
304 None
305 }
306}
307
308pub fn parse_swift_message_from_string(value: &str) -> Result<HashMap<String, Vec<String>>> {
311 let mut field_map = HashMap::new();
318
319 for line in value.lines() {
321 if line.trim().is_empty() {
322 continue;
323 }
324
325 if let Some(colon_pos) = line.find(':') {
327 if let Some(second_colon) = line[colon_pos + 1..].find(':') {
328 let second_colon_pos = colon_pos + 1 + second_colon;
329 let field_tag = line[colon_pos + 1..second_colon_pos].to_string();
330 let _field_value = line[second_colon_pos + 1..].to_string();
331
332 field_map
333 .entry(field_tag)
334 .or_insert_with(Vec::new)
335 .push(format!(":{}", &line[colon_pos + 1..]));
336 }
337 }
338 }
339
340 Ok(field_map)
341}
342
343pub fn serialize_swift_message_to_string(fields: &HashMap<String, Vec<String>>) -> String {
346 let mut result = String::new();
351
352 for field_values in fields.values() {
354 for field_value in field_values {
355 result.push_str(field_value);
357 result.push('\n');
358 }
359 }
360
361 if result.ends_with('\n') {
363 result.pop();
364 }
365
366 result
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 #[test]
374 fn test_extract_blocks() {
375 let raw_message = "{1:F01BANKDEFFAXXX0123456789}{2:I103BANKDEFFAXXXU3003}{4:\n:20:FT21234567890\n:23B:CRED\n-}";
376 let blocks = SwiftParser::extract_blocks(raw_message).unwrap();
377
378 assert!(blocks.block1.is_some());
379 assert!(blocks.block2.is_some());
380 assert!(!blocks.block4.is_empty());
381 assert_eq!(blocks.block1.as_ref().unwrap(), "F01BANKDEFFAXXX0123456789");
382 assert_eq!(blocks.block2.as_ref().unwrap(), "I103BANKDEFFAXXXU3003");
383 }
384
385 #[test]
386 fn test_parse_block4_fields() {
387 let block4 = "\n:20:FT21234567890\n:23B:CRED\n:32A:210315EUR1234567,89\n";
388 let (field_map, field_order) = SwiftParser::parse_block4_fields(block4).unwrap();
389
390 assert_eq!(
391 field_map.get("20"),
392 Some(&vec![":20:FT21234567890".to_string()])
393 );
394 assert_eq!(field_map.get("23B"), Some(&vec![":23B:CRED".to_string()]));
395 assert_eq!(
396 field_map.get("32A"),
397 Some(&vec![":32A:210315EUR1234567,89".to_string()])
398 );
399
400 assert_eq!(field_order, vec!["20", "23B", "32A"]);
401 }
402
403 #[test]
404 fn test_debug_mt103_fields() {
405 let block4 = r#"
406:20:FT21001234567890
407:23B:CRED
408:32A:240101USD1000,00
409:50K:/1234567890
410ACME CORPORATION
411123 MAIN STREET
412NEW YORK NY 10001
413:52A:BNPAFRPPXXX
414:57A:DEUTDEFFXXX
415:59:/DE89370400440532013000
416MUELLER GMBH
417HAUPTSTRASSE 1
41810115 BERLIN
419:70:PAYMENT FOR INVOICE 12345
420:71A:OUR
421"#;
422 let (field_map, field_order) = SwiftParser::parse_block4_fields(block4).unwrap();
423
424 println!("Extracted fields:");
425 for (tag, values) in &field_map {
426 println!(" {}: {:?}", tag, values);
427 }
428 println!("Field order: {:?}", field_order);
429
430 assert!(field_map.contains_key("20"));
432 assert!(field_map.contains_key("23B"));
433 assert!(field_map.contains_key("32A"));
434 assert!(field_map.contains_key("50"));
435 assert!(field_map.contains_key("52A"));
436 assert!(field_map.contains_key("57A"));
437 assert!(field_map.contains_key("59"));
438 assert!(field_map.contains_key("70"));
439 assert!(field_map.contains_key("71A"));
440 }
441
442 #[test]
443 fn test_block3_parsing_with_nested_tags() {
444 let raw_message = r#"{1:F01BNPAFRPPXXX0000000000}{2:O1031234240101DEUTDEFFXXXX12345678952401011234N}{3:{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}}{4:
445:20:FT21001234567890
446:23B:CRED
447-}"#;
448
449 let blocks = SwiftParser::extract_blocks(raw_message).unwrap();
450
451 assert!(blocks.block3.is_some());
452 let block3_content = blocks.block3.unwrap();
453 println!("Block 3 content: '{}'", block3_content);
454
455 assert_eq!(
457 block3_content,
458 "{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}"
459 );
460 assert!(block3_content.contains("103:EBA"));
461 assert!(block3_content.contains("121:180f1e65-90e0-44d5-a49a-92b55eb3025f"));
462 }
463
464 #[test]
465 fn test_find_matching_brace() {
466 assert_eq!(SwiftParser::find_matching_brace("{simple}"), Some(7));
468
469 assert_eq!(
471 SwiftParser::find_matching_brace("{outer{inner}outer}"),
472 Some(18)
473 );
474
475 let test_str = "{{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}}";
477 let expected_pos = test_str.len() - 1; assert_eq!(
479 SwiftParser::find_matching_brace(test_str),
480 Some(expected_pos)
481 );
482
483 assert_eq!(SwiftParser::find_matching_brace("{103:EBA}"), Some(8));
485
486 assert_eq!(SwiftParser::find_matching_brace("{no_close"), None);
488
489 assert_eq!(SwiftParser::find_matching_brace("no_brace"), None);
491 }
492
493 #[test]
494 fn debug_find_matching_brace() {
495 let test_str = "{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}";
496 println!("Test string: '{}'", test_str);
497 println!("Length: {}", test_str.len());
498
499 let result = SwiftParser::find_matching_brace(test_str);
500 println!("Result: {:?}", result);
501
502 for (i, ch) in test_str.char_indices() {
504 println!("Position {}: '{}'", i, ch);
505 }
506 }
507
508 #[test]
509 fn test_parse_auto_mt103() {
510 let raw_mt103 = r#"{1:F01BANKDEFFAXXX0123456789}{2:I103BANKDEFFAXXXU3003}{4:
511:20:FT21234567890
512:23B:CRED
513:32A:210315EUR1234567,89
514:50K:ACME CORPORATION
515123 BUSINESS AVENUE
516NEW YORK NY 10001
517:52A:BANKDEFF
518:57A:DEUTDEFF
519:59A:/DE89370400440532013000
520DEUTDEFF
521:70:PAYMENT FOR SERVICES
522:71A:OUR
523-}"#;
524
525 let parsed = SwiftParser::parse_auto(raw_mt103).unwrap();
526
527 assert_eq!(parsed.message_type(), "103");
529
530 let mt103_msg = parsed.as_mt103().unwrap();
532 assert_eq!(mt103_msg.message_type, "103");
533
534 println!("Successfully parsed MT103 message with auto-detection");
535 }
536
537 #[test]
538 fn test_parse_auto_unsupported_type() {
539 let raw_message = r#"{1:F01BANKDEFFAXXX0123456789}{2:I999BANKDEFFAXXXU3003}{4:
540:20:FT21234567890
541-}"#;
542
543 let result = SwiftParser::parse_auto(raw_message);
544 assert!(result.is_err());
545
546 if let Err(ParseError::UnsupportedMessageType { message_type }) = result {
547 assert_eq!(message_type, "999");
548 } else {
549 panic!("Expected UnsupportedMessageType error");
550 }
551 }
552}