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