swift_mt_message/
parser.rs1use std::collections::HashMap;
2
3use crate::errors::{ParseError, Result};
4use crate::headers::{ApplicationHeader, BasicHeader, Trailer, UserHeader};
5use crate::{RawBlocks, SwiftMessage, SwiftMessageBody};
6
7type FieldParseResult = Result<(HashMap<String, Vec<String>>, Vec<String>)>;
9
10pub struct SwiftParser;
12
13impl SwiftParser {
14 pub fn parse<T: SwiftMessageBody>(raw_message: &str) -> Result<SwiftMessage<T>> {
16 let blocks = Self::extract_blocks(raw_message)?;
17
18 let basic_header = BasicHeader::parse(&blocks.block1.clone().unwrap_or_default())?;
20 let application_header =
21 ApplicationHeader::parse(&blocks.block2.clone().unwrap_or_default())?;
22 let user_header = blocks
23 .block3
24 .as_ref()
25 .map(|b| UserHeader::parse(b))
26 .transpose()?;
27 let trailer = blocks
28 .block5
29 .as_ref()
30 .map(|b| Trailer::parse(b))
31 .transpose()?;
32
33 let message_type = application_header.message_type.clone();
35
36 if message_type != T::message_type() {
38 return Err(ParseError::WrongMessageType {
39 expected: T::message_type().to_string(),
40 actual: message_type,
41 });
42 }
43
44 let (field_map, field_order) = Self::parse_block4_fields(&blocks.block4)?;
46
47 let fields = T::from_fields(field_map)?;
49
50 Ok(SwiftMessage {
51 basic_header,
52 application_header,
53 user_header,
54 trailer,
55 blocks,
56 message_type,
57 field_order,
58 fields,
59 })
60 }
61
62 fn extract_blocks(raw_message: &str) -> Result<RawBlocks> {
64 let mut blocks = RawBlocks::default();
65
66 let mut current_pos = 0;
68
69 if let Some(start) = raw_message[current_pos..].find("{1:") {
71 let start = current_pos + start;
72 if let Some(end) = raw_message[start..].find('}') {
73 let end = start + end;
74 blocks.block1 = Some(raw_message[start + 3..end].to_string());
75 current_pos = end + 1;
76 }
77 }
78
79 if let Some(start) = raw_message[current_pos..].find("{2:") {
81 let start = current_pos + start;
82 if let Some(end) = raw_message[start..].find('}') {
83 let end = start + end;
84 blocks.block2 = Some(raw_message[start + 3..end].to_string());
85 current_pos = end + 1;
86 }
87 }
88
89 if let Some(start) = raw_message[current_pos..].find("{3:") {
91 let start = current_pos + start;
92 if let Some(end) = Self::find_matching_brace(&raw_message[start..]) {
94 let end = start + end;
95 blocks.block3 = Some(raw_message[start + 3..end].to_string());
96 current_pos = end + 1;
97 }
98 }
99
100 if let Some(start) = raw_message[current_pos..].find("{4:") {
102 let start = current_pos + start;
103 if let Some(end) = raw_message[start..].find("-}") {
104 let end = start + end;
105 blocks.block4 = raw_message[start + 3..end].to_string();
106 current_pos = end + 2;
107 }
108 }
109
110 if let Some(start) = raw_message[current_pos..].find("{5:") {
112 let start = current_pos + start;
113 if let Some(end) = raw_message[start..].find('}') {
114 let end = start + end;
115 blocks.block5 = Some(raw_message[start + 3..end].to_string());
116 }
117 }
118
119 if blocks.block1.is_none() || blocks.block2.is_none() || blocks.block4.is_empty() {
120 return Err(ParseError::InvalidBlockStructure {
121 message: "Missing required blocks (1, 2, or 4)".to_string(),
122 });
123 }
124
125 Ok(blocks)
126 }
127
128 fn parse_block4_fields(block4: &str) -> FieldParseResult {
130 let mut field_map: HashMap<String, Vec<String>> = HashMap::new();
131 let mut field_order = Vec::new();
132
133 let content = block4.trim();
135
136 let mut current_pos = 0;
138
139 while current_pos < content.len() {
140 if let Some(field_start) = content[current_pos..].find(':') {
142 let field_start = current_pos + field_start;
143
144 if let Some(tag_end) = content[field_start + 1..].find(':') {
146 let tag_end = field_start + 1 + tag_end;
147 let raw_field_tag = content[field_start + 1..tag_end].to_string();
148
149 let field_tag = Self::normalize_field_tag(&raw_field_tag);
151
152 let value_start = tag_end + 1;
154 let value_end = if let Some(next_field) = content[value_start..].find("\n:") {
155 value_start + next_field
156 } else {
157 content.len()
158 };
159
160 let field_value = content[value_start..value_end].trim().to_string();
161
162 let complete_field_string = format!(":{}:{}", raw_field_tag, field_value);
164
165 field_map
167 .entry(field_tag.clone())
168 .or_default()
169 .push(complete_field_string);
170
171 if !field_order.contains(&field_tag) {
173 field_order.push(field_tag);
174 }
175
176 current_pos = value_end;
177 } else {
178 break;
180 }
181 } else {
182 break;
183 }
184 }
185
186 Ok((field_map, field_order))
187 }
188
189 fn normalize_field_tag(raw_tag: &str) -> String {
193 let mut numeric_part = String::new();
195 for ch in raw_tag.chars() {
196 if ch.is_ascii_digit() {
197 numeric_part.push(ch);
198 } else {
199 break;
200 }
201 }
202
203 if numeric_part.len() < raw_tag.len() {
205 let remaining = &raw_tag[numeric_part.len()..];
206
207 match numeric_part.as_str() {
209 "13" | "23" | "26" | "32" | "33" | "52" | "53" | "54" | "55" | "56" | "57"
210 | "58" | "71" | "77" => {
211 return raw_tag.to_string();
226 }
227 _ => {
228 if remaining
230 .chars()
231 .all(|c| c.is_ascii_alphabetic() && c.is_ascii_uppercase())
232 {
233 return numeric_part;
235 }
236 }
237 }
238 }
239
240 raw_tag.to_string()
242 }
243
244 fn find_matching_brace(text: &str) -> Option<usize> {
247 let mut chars = text.char_indices();
248
249 let mut brace_count = if let Some((_, '{')) = chars.next() {
251 1
252 } else {
253 return None;
254 };
255
256 for (i, ch) in chars {
257 match ch {
258 '{' => brace_count += 1,
259 '}' => {
260 brace_count -= 1;
261 if brace_count == 0 {
262 return Some(i);
263 }
264 }
265 _ => {}
266 }
267 }
268
269 None
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 #[test]
278 fn test_extract_blocks() {
279 let raw_message = "{1:F01BANKDEFFAXXX0123456789}{2:I103BANKDEFFAXXXU3003}{4:\n:20:FT21234567890\n:23B:CRED\n-}";
280 let blocks = SwiftParser::extract_blocks(raw_message).unwrap();
281
282 assert!(blocks.block1.is_some());
283 assert!(blocks.block2.is_some());
284 assert!(!blocks.block4.is_empty());
285 assert_eq!(blocks.block1.as_ref().unwrap(), "F01BANKDEFFAXXX0123456789");
286 assert_eq!(blocks.block2.as_ref().unwrap(), "I103BANKDEFFAXXXU3003");
287 }
288
289 #[test]
290 fn test_parse_block4_fields() {
291 let block4 = "\n:20:FT21234567890\n:23B:CRED\n:32A:210315EUR1234567,89\n";
292 let (field_map, field_order) = SwiftParser::parse_block4_fields(block4).unwrap();
293
294 assert_eq!(
295 field_map.get("20"),
296 Some(&vec![":20:FT21234567890".to_string()])
297 );
298 assert_eq!(field_map.get("23B"), Some(&vec![":23B:CRED".to_string()]));
299 assert_eq!(
300 field_map.get("32A"),
301 Some(&vec![":32A:210315EUR1234567,89".to_string()])
302 );
303
304 assert_eq!(field_order, vec!["20", "23B", "32A"]);
305 }
306
307 #[test]
308 fn test_debug_mt103_fields() {
309 let block4 = r#"
310:20:FT21001234567890
311:23B:CRED
312:32A:240101USD1000,00
313:50K:/1234567890
314ACME CORPORATION
315123 MAIN STREET
316NEW YORK NY 10001
317:52A:BNPAFRPPXXX
318:57A:DEUTDEFFXXX
319:59:/DE89370400440532013000
320MUELLER GMBH
321HAUPTSTRASSE 1
32210115 BERLIN
323:70:PAYMENT FOR INVOICE 12345
324:71A:OUR
325"#;
326 let (field_map, field_order) = SwiftParser::parse_block4_fields(block4).unwrap();
327
328 println!("Extracted fields:");
329 for (tag, values) in &field_map {
330 println!(" {}: {:?}", tag, values);
331 }
332 println!("Field order: {:?}", field_order);
333
334 assert!(field_map.contains_key("20"));
336 assert!(field_map.contains_key("23B"));
337 assert!(field_map.contains_key("32A"));
338 assert!(field_map.contains_key("50"));
339 assert!(field_map.contains_key("52A"));
340 assert!(field_map.contains_key("57A"));
341 assert!(field_map.contains_key("59"));
342 assert!(field_map.contains_key("70"));
343 assert!(field_map.contains_key("71A"));
344 }
345
346 #[test]
347 fn test_block3_parsing_with_nested_tags() {
348 let raw_message = r#"{1:F01BNPAFRPPXXX0000000000}{2:O1031234240101DEUTDEFFXXXX12345678952401011234N}{3:{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}}{4:
349:20:FT21001234567890
350:23B:CRED
351-}"#;
352
353 let blocks = SwiftParser::extract_blocks(raw_message).unwrap();
354
355 assert!(blocks.block3.is_some());
356 let block3_content = blocks.block3.unwrap();
357 println!("Block 3 content: '{}'", block3_content);
358
359 assert_eq!(
361 block3_content,
362 "{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}"
363 );
364 assert!(block3_content.contains("103:EBA"));
365 assert!(block3_content.contains("121:180f1e65-90e0-44d5-a49a-92b55eb3025f"));
366 }
367
368 #[test]
369 fn test_find_matching_brace() {
370 assert_eq!(SwiftParser::find_matching_brace("{simple}"), Some(7));
372
373 assert_eq!(
375 SwiftParser::find_matching_brace("{outer{inner}outer}"),
376 Some(18)
377 );
378
379 let test_str = "{{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}}";
381 let expected_pos = test_str.len() - 1; assert_eq!(
383 SwiftParser::find_matching_brace(test_str),
384 Some(expected_pos)
385 );
386
387 assert_eq!(SwiftParser::find_matching_brace("{103:EBA}"), Some(8));
389
390 assert_eq!(SwiftParser::find_matching_brace("{no_close"), None);
392
393 assert_eq!(SwiftParser::find_matching_brace("no_brace"), None);
395 }
396
397 #[test]
398 fn debug_find_matching_brace() {
399 let test_str = "{103:EBA}{121:180f1e65-90e0-44d5-a49a-92b55eb3025f}";
400 println!("Test string: '{}'", test_str);
401 println!("Length: {}", test_str.len());
402
403 let result = SwiftParser::find_matching_brace(test_str);
404 println!("Result: {:?}", result);
405
406 for (i, ch) in test_str.char_indices() {
408 println!("Position {}: '{}'", i, ch);
409 }
410 }
411}