swift_mt_message/
parser.rs1use 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
11type FieldParseResult = Result<(HashMap<String, Vec<String>>, Vec<String>)>;
13
14pub struct SwiftParser;
16
17impl SwiftParser {
18 pub fn parse<T: SwiftMessageBody>(raw_message: &str) -> Result<SwiftMessage<T>> {
20 let blocks = Self::extract_blocks(raw_message)?;
21
22 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 let message_type = application_header.message_type.clone();
39
40 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 let (field_map, field_order) = Self::parse_block4_fields(&block4)?;
51
52 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 pub fn parse_auto(raw_message: &str) -> Result<ParsedSwiftMessage> {
69 let blocks = Self::extract_blocks(raw_message)?;
71
72 let application_header =
74 ApplicationHeader::parse(&blocks.block2.clone().unwrap_or_default())?;
75 let message_type = &application_header.message_type;
76
77 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 pub fn extract_blocks(raw_message: &str) -> Result<RawBlocks> {
175 let mut blocks = RawBlocks::default();
176
177 let mut current_pos = 0;
179
180 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 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 if let Some(start) = raw_message[current_pos..].find("{3:") {
202 let start = current_pos + start;
203 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 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 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 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 let content = block4.trim();
246
247 let mut current_pos = 0;
249
250 while current_pos < content.len() {
251 if let Some(field_start) = content[current_pos..].find(':') {
253 let field_start = current_pos + field_start;
254
255 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 let field_tag = Self::normalize_field_tag(&raw_field_tag);
262
263 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 let complete_field_string = format!(":{raw_field_tag}:{field_value}");
275
276 field_map
278 .entry(field_tag.clone())
279 .or_default()
280 .push(complete_field_string);
281
282 if !field_order.contains(&field_tag) {
284 field_order.push(field_tag);
285 }
286
287 current_pos = value_end;
288 } else {
289 break;
291 }
292 } else {
293 break;
294 }
295 }
296
297 Ok((field_map, field_order))
298 }
299
300 fn normalize_field_tag(raw_tag: &str) -> String {
304 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 numeric_part.len() < raw_tag.len() {
316 let remaining = &raw_tag[numeric_part.len()..];
317
318 match numeric_part.as_str() {
320 "11" | "13" | "23" | "26" | "32" | "33" | "52" | "53" | "54" | "55" | "56"
321 | "57" | "58" | "71" | "77" => {
322 return raw_tag.to_string();
338 }
339 _ => {
340 if remaining
342 .chars()
343 .all(|c| c.is_ascii_alphabetic() && c.is_ascii_uppercase())
344 {
345 return numeric_part;
347 }
348 }
349 }
350 }
351
352 raw_tag.to_string()
354 }
355
356 fn find_matching_brace(text: &str) -> Option<usize> {
359 let mut chars = text.char_indices();
360
361 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
385pub fn parse_swift_message_from_string(value: &str) -> Result<HashMap<String, Vec<String>>> {
388 let mut field_map = HashMap::new();
395
396 for line in value.lines() {
398 if line.trim().is_empty() {
399 continue;
400 }
401
402 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
420pub fn serialize_swift_message_to_string(fields: &HashMap<String, Vec<String>>) -> String {
423 let mut result = String::new();
428
429 for field_values in fields.values() {
431 for field_value in field_values {
432 result.push_str(field_value);
434 result.push('\n');
435 }
436 }
437
438 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 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 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 assert_eq!(SwiftParser::find_matching_brace("{simple}"), Some(7));
545
546 assert_eq!(
548 SwiftParser::find_matching_brace("{outer{inner}outer}"),
549 Some(18)
550 );
551
552 let test_str = "{{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}}";
554 let expected_pos = test_str.len() - 1; assert_eq!(
556 SwiftParser::find_matching_brace(test_str),
557 Some(expected_pos)
558 );
559
560 assert_eq!(SwiftParser::find_matching_brace("{103:EBA}"), Some(8));
562
563 assert_eq!(SwiftParser::find_matching_brace("{no_close"), None);
565
566 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 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 assert_eq!(parsed.message_type(), "103");
606
607 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}