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