swift_mt_message/
parser.rs1use regex::Regex;
4use std::collections::HashMap;
5
6use crate::common::{Field, MessageBlock};
7use crate::error::{MTError, Result};
8use crate::messages::{MTMessage, MTMessageType};
9
10pub struct MTParser {
12 block_regex: Regex,
13}
14
15impl MTParser {
16 pub fn new() -> Result<Self> {
17 let block_regex = Regex::new(r"\{(\d):([^}]*)\}")?;
18
19 Ok(Self {
20 block_regex,
21 })
22 }
23
24 pub fn parse(&self, input: &str) -> Result<MTMessage> {
26 let blocks = self.parse_blocks(input)?;
27 let message_type = self.extract_message_type(&blocks)?;
28
29 match message_type.as_str() {
30 "103" => {
31 let mt103 = crate::messages::mt103::MT103::from_blocks(blocks)?;
32 Ok(MTMessage::MT103(mt103))
33 }
34 "102" => {
35 let mt102 = crate::messages::mt102::MT102::from_blocks(blocks)?;
36 Ok(MTMessage::MT102(mt102))
37 }
38 "202" => {
39 let mt202 = crate::messages::mt202::MT202::from_blocks(blocks)?;
40 Ok(MTMessage::MT202(mt202))
41 }
42 "940" => {
43 let mt940 = crate::messages::mt940::MT940::from_blocks(blocks)?;
44 Ok(MTMessage::MT940(mt940))
45 }
46 "941" => {
47 let mt941 = crate::messages::mt941::MT941::from_blocks(blocks)?;
48 Ok(MTMessage::MT941(mt941))
49 }
50 "942" => {
51 let mt942 = crate::messages::mt942::MT942::from_blocks(blocks)?;
52 Ok(MTMessage::MT942(mt942))
53 }
54 "192" => {
55 let mt192 = crate::messages::mt192::MT192::from_blocks(blocks)?;
56 Ok(MTMessage::MT192(mt192))
57 }
58 "195" => {
59 let mt195 = crate::messages::mt195::MT195::from_blocks(blocks)?;
60 Ok(MTMessage::MT195(mt195))
61 }
62 "196" => {
63 let mt196 = crate::messages::mt196::MT196::from_blocks(blocks)?;
64 Ok(MTMessage::MT196(mt196))
65 }
66 "197" => {
67 let mt197 = crate::messages::mt197::MT197::from_blocks(blocks)?;
68 Ok(MTMessage::MT197(mt197))
69 }
70 "199" => {
71 let mt199 = crate::messages::mt199::MT199::from_blocks(blocks)?;
72 Ok(MTMessage::MT199(mt199))
73 }
74 _ => Err(MTError::UnsupportedMessageType {
75 message_type: message_type.clone(),
76 }),
77 }
78 }
79
80 fn parse_blocks(&self, input: &str) -> Result<Vec<MessageBlock>> {
82 let mut blocks = Vec::new();
83
84 for captures in self.block_regex.captures_iter(input) {
85 let block_number = &captures[1];
86 let block_content = &captures[2];
87
88 match block_number {
89 "1" => {
90 blocks.push(self.parse_basic_header(block_content)?);
91 }
92 "2" => {
93 blocks.push(self.parse_application_header(block_content)?);
94 }
95 "3" => {
96 blocks.push(self.parse_user_header(block_content)?);
97 }
98 "4" => {
99 blocks.push(self.parse_text_block(block_content)?);
100 }
101 "5" => {
102 blocks.push(self.parse_trailer_block(block_content)?);
103 }
104 _ => {
105 return Err(MTError::InvalidMessageStructure {
106 message: format!("Unknown block number: {}", block_number),
107 });
108 }
109 }
110 }
111
112 if blocks.is_empty() {
113 return Err(MTError::InvalidMessageStructure {
114 message: "No valid blocks found in message".to_string(),
115 });
116 }
117
118 Ok(blocks)
119 }
120
121 fn parse_basic_header(&self, content: &str) -> Result<MessageBlock> {
123 if content.len() < 21 {
125 return Err(MTError::InvalidMessageStructure {
126 message: "Basic header block too short".to_string(),
127 });
128 }
129
130 let application_id = content[0..1].to_string();
131 let service_id = content[1..3].to_string();
132 let logical_terminal = content[3..15].to_string();
133 let session_number = content[15..19].to_string();
134 let sequence_number = content[19..].to_string();
135
136 Ok(MessageBlock::BasicHeader {
137 application_id,
138 service_id,
139 logical_terminal,
140 session_number,
141 sequence_number,
142 })
143 }
144
145 fn parse_application_header(&self, content: &str) -> Result<MessageBlock> {
147 if content.is_empty() {
149 return Err(MTError::InvalidMessageStructure {
150 message: "Application header block is empty".to_string(),
151 });
152 }
153
154 let input_output_identifier = content[0..1].to_string();
155
156 if content.len() < 4 {
157 return Err(MTError::InvalidMessageStructure {
158 message: "Application header block too short".to_string(),
159 });
160 }
161
162 let message_type = content[1..4].to_string();
163 let remaining = &content[4..];
164
165 let (destination_address, priority, delivery_monitoring, obsolescence_period) =
167 if input_output_identifier == "I" {
168 if remaining.len() >= 12 {
170 let dest = remaining[0..12].to_string();
171 let prio = remaining.get(12..13).unwrap_or("").to_string();
172 let del_mon = remaining.get(13..14).map(|s| s.to_string());
173 let obs_per = remaining.get(14..17).map(|s| s.to_string());
174 (dest, prio, del_mon, obs_per)
175 } else {
176 (remaining.to_string(), String::new(), None, None)
177 }
178 } else {
179 (remaining.to_string(), String::new(), None, None)
181 };
182
183 Ok(MessageBlock::ApplicationHeader {
184 input_output_identifier,
185 message_type,
186 destination_address,
187 priority,
188 delivery_monitoring,
189 obsolescence_period,
190 })
191 }
192
193 fn parse_user_header(&self, content: &str) -> Result<MessageBlock> {
195 let mut fields = HashMap::new();
196
197 let user_field_regex = Regex::new(r"\{(\w+):([^}]*)\}")?;
199 for captures in user_field_regex.captures_iter(content) {
200 let tag = captures[1].to_string();
201 let value = captures[2].to_string();
202 fields.insert(tag, value);
203 }
204
205 Ok(MessageBlock::UserHeader { fields })
206 }
207
208 fn parse_text_block(&self, content: &str) -> Result<MessageBlock> {
210 let mut fields = Vec::new();
211 let lines: Vec<&str> = content.lines().collect();
212
213 let mut current_tag = String::new();
214 let mut current_value = String::new();
215
216 for line in lines {
217 let line = line.trim();
218 if line.is_empty() || line == "-" {
219 continue;
220 }
221
222 if line.starts_with(':') && line.contains(':') {
223 if !current_tag.is_empty() {
225 fields.push(Field::new(current_tag.clone(), current_value.trim().to_string()));
226 }
227
228 if let Some(colon_pos) = line[1..].find(':') {
230 current_tag = line[1..colon_pos + 1].to_string();
231 current_value = line[colon_pos + 2..].to_string();
232 } else {
233 return Err(MTError::ParseError {
234 line: 0,
235 column: 0,
236 message: format!("Invalid field format: {}", line),
237 });
238 }
239 } else {
240 if !current_value.is_empty() {
242 current_value.push('\n');
243 }
244 current_value.push_str(line);
245 }
246 }
247
248 if !current_tag.is_empty() {
250 fields.push(Field::new(current_tag, current_value.trim().to_string()));
251 }
252
253 Ok(MessageBlock::TextBlock { fields })
254 }
255
256 fn parse_trailer_block(&self, content: &str) -> Result<MessageBlock> {
258 let mut fields = HashMap::new();
259
260 let trailer_field_regex = Regex::new(r"\{(\w+):([^}]*)\}")?;
262 for captures in trailer_field_regex.captures_iter(content) {
263 let tag = captures[1].to_string();
264 let value = captures[2].to_string();
265 fields.insert(tag, value);
266 }
267
268 Ok(MessageBlock::TrailerBlock { fields })
269 }
270
271 fn extract_message_type(&self, blocks: &[MessageBlock]) -> Result<String> {
273 for block in blocks {
274 if let MessageBlock::ApplicationHeader { message_type, .. } = block {
275 return Ok(message_type.clone());
276 }
277 }
278
279 Err(MTError::InvalidMessageStructure {
280 message: "No application header block found".to_string(),
281 })
282 }
283}
284
285impl Default for MTParser {
286 fn default() -> Self {
287 Self::new().expect("Failed to create default parser")
288 }
289}
290
291pub fn parse_message(input: &str) -> Result<MTMessage> {
293 let parser = MTParser::new()?;
294 parser.parse(input)
295}
296
297pub fn extract_fields(text_block: &str) -> Result<Vec<Field>> {
299 let parser = MTParser::new()?;
300 if let MessageBlock::TextBlock { fields } = parser.parse_text_block(text_block)? {
301 Ok(fields)
302 } else {
303 Err(MTError::InvalidMessageStructure {
304 message: "Failed to parse text block".to_string(),
305 })
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 #[test]
314 fn test_basic_header_parsing() {
315 let parser = MTParser::new().unwrap();
316 let result = parser.parse_basic_header("F01BANKDEFFAXXX0123456789").unwrap();
317
318 if let MessageBlock::BasicHeader {
319 application_id,
320 service_id,
321 logical_terminal,
322 session_number,
323 sequence_number
324 } = result {
325 assert_eq!(application_id, "F");
326 assert_eq!(service_id, "01");
327 assert_eq!(logical_terminal, "BANKDEFFAXXX");
328 assert_eq!(session_number, "0123");
329 assert_eq!(sequence_number, "456789");
330 } else {
331 panic!("Expected BasicHeader block");
332 }
333 }
334
335 #[test]
336 fn test_application_header_parsing() {
337 let parser = MTParser::new().unwrap();
338 let result = parser.parse_application_header("I103BANKDEFFAXXXU3003").unwrap();
339
340 if let MessageBlock::ApplicationHeader {
341 input_output_identifier,
342 message_type,
343 destination_address,
344 priority,
345 ..
346 } = result {
347 assert_eq!(input_output_identifier, "I");
348 assert_eq!(message_type, "103");
349 assert_eq!(destination_address, "BANKDEFFAXXX");
350 assert_eq!(priority, "U");
351 } else {
352 panic!("Expected ApplicationHeader block");
353 }
354 }
355
356 #[test]
357 fn test_text_block_parsing() {
358 let parser = MTParser::new().unwrap();
359 let text = ":20:FT21234567890\n:23B:CRED\n:32A:210315EUR1234567,89\n:50K:JOHN DOE\n:59:JANE SMITH";
360 let result = parser.parse_text_block(text).unwrap();
361
362 if let MessageBlock::TextBlock { fields } = result {
363 assert_eq!(fields.len(), 5);
364 assert_eq!(fields[0].tag.as_str(), "20");
365 assert_eq!(fields[0].value(), "FT21234567890");
366 assert_eq!(fields[1].tag.as_str(), "23B");
367 assert_eq!(fields[1].value(), "CRED");
368 } else {
369 panic!("Expected TextBlock");
370 }
371 }
372}