swift_mt_message/parser/
message_parser.rs

1//! # Message Parser Module
2//!
3//! Pointer-based parser for SWIFT MT messages that tracks position while parsing fields sequentially.
4//! This replaces the HashMap-based approach with a more efficient single-pass parser.
5
6use crate::errors::{InvalidFieldFormatError, ParseError};
7use crate::traits::SwiftField;
8use std::collections::HashSet;
9
10use super::field_extractor::extract_field_content;
11
12/// Message parser that tracks position while parsing SWIFT messages
13#[derive(Debug)]
14pub struct MessageParser<'a> {
15    /// The input message text
16    input: &'a str,
17    /// Current position in the input
18    position: usize,
19    /// Fields that have been parsed (for duplicate detection)
20    fields_seen: HashSet<String>,
21    /// Message type being parsed
22    message_type: String,
23    /// Whether to allow duplicate fields
24    allow_duplicates: bool,
25}
26
27impl<'a> MessageParser<'a> {
28    /// Create a new message parser
29    pub fn new(input: &'a str, message_type: &str) -> Self {
30        Self {
31            input,
32            position: 0,
33            fields_seen: HashSet::new(),
34            message_type: message_type.to_string(),
35            allow_duplicates: false,
36        }
37    }
38
39    /// Enable or disable duplicate field handling
40    pub fn with_duplicates(mut self, allow: bool) -> Self {
41        self.allow_duplicates = allow;
42        self
43    }
44
45    /// Parse a required field
46    pub fn parse_field<T: SwiftField>(&mut self, tag: &str) -> Result<T, ParseError> {
47        let field_content = self.extract_field(tag, false)?;
48
49        // Try to parse the field
50        T::parse(&field_content).map_err(|e| {
51            ParseError::InvalidFieldFormat(Box::new(InvalidFieldFormatError {
52                field_tag: tag.to_string(),
53                component_name: "field".to_string(),
54                value: field_content,
55                format_spec: "field format".to_string(),
56                position: Some(self.position),
57                inner_error: e.to_string(),
58            }))
59        })
60    }
61
62    /// Parse an optional field (only checks immediate next field, not searching ahead)
63    pub fn parse_optional_field<T: SwiftField>(
64        &mut self,
65        tag: &str,
66    ) -> Result<Option<T>, ParseError> {
67        // For optional fields, only check if the immediate next field matches
68        // Don't search ahead in the input to avoid consuming fields from later sections
69        if !self.detect_field(tag) {
70            return Ok(None);
71        }
72
73        // If immediate next field matches, extract and parse it
74        match self.extract_field(tag, true) {
75            Ok(content) => {
76                let parsed = T::parse(&content).map_err(|e| {
77                    ParseError::InvalidFieldFormat(Box::new(InvalidFieldFormatError {
78                        field_tag: tag.to_string(),
79                        component_name: "field".to_string(),
80                        value: content,
81                        format_spec: "field format".to_string(),
82                        position: Some(self.position),
83                        inner_error: e.to_string(),
84                    }))
85                })?;
86                Ok(Some(parsed))
87            }
88            Err(_) => Ok(None), // Field not found, return None for optional
89        }
90    }
91
92    /// Parse a repeated field (returns Vec)
93    pub fn parse_repeated_field<T: SwiftField>(&mut self, tag: &str) -> Result<Vec<T>, ParseError> {
94        let mut results = Vec::new();
95
96        // Keep parsing until no more instances found
97        while let Ok(content) = self.extract_field(tag, true) {
98            let parsed = T::parse(&content).map_err(|e| {
99                ParseError::InvalidFieldFormat(Box::new(InvalidFieldFormatError {
100                    field_tag: tag.to_string(),
101                    component_name: "field".to_string(),
102                    value: content,
103                    format_spec: "field format".to_string(),
104                    position: Some(self.position),
105                    inner_error: e.to_string(),
106                }))
107            })?;
108            results.push(parsed);
109        }
110
111        Ok(results)
112    }
113
114    /// Parse a field with variant detection (for enum fields)
115    pub fn parse_variant_field<T: SwiftField>(&mut self, base_tag: &str) -> Result<T, ParseError> {
116        // Look ahead to find which variant is present
117        let variant = self.detect_variant(base_tag)?;
118        let full_tag = format!("{}{}", base_tag, variant);
119        let field_content = self.extract_field(&full_tag, false)?;
120
121        // Use parse_with_variant for enum fields
122        T::parse_with_variant(&field_content, Some(&variant), Some(base_tag)).map_err(|e| {
123            ParseError::InvalidFieldFormat(Box::new(InvalidFieldFormatError {
124                field_tag: full_tag,
125                component_name: "field".to_string(),
126                value: field_content,
127                format_spec: "field format".to_string(),
128                position: Some(self.position),
129                inner_error: e.to_string(),
130            }))
131        })
132    }
133
134    /// Parse an optional field with variant detection
135    pub fn parse_optional_variant_field<T: SwiftField>(
136        &mut self,
137        base_tag: &str,
138    ) -> Result<Option<T>, ParseError> {
139        match self.detect_variant_optional(base_tag) {
140            Some(variant) => {
141                let full_tag = format!("{}{}", base_tag, variant);
142                if let Ok(content) = self.extract_field(&full_tag, true) {
143                    let parsed = T::parse_with_variant(&content, Some(&variant), Some(base_tag))
144                        .map_err(|e| {
145                            ParseError::InvalidFieldFormat(Box::new(InvalidFieldFormatError {
146                                field_tag: full_tag,
147                                component_name: "field".to_string(),
148                                value: content,
149                                format_spec: "field format".to_string(),
150                                position: Some(self.position),
151                                inner_error: e.to_string(),
152                            }))
153                        })?;
154                    Ok(Some(parsed))
155                } else {
156                    Ok(None)
157                }
158            }
159            None => Ok(None), // No variant found
160        }
161    }
162
163    /// Extract field content from the message
164    fn extract_field(&mut self, tag: &str, optional: bool) -> Result<String, ParseError> {
165        // Check for duplicates if not allowed
166        if !self.allow_duplicates && self.fields_seen.contains(tag) && !optional {
167            return Err(ParseError::InvalidFormat {
168                message: format!("Duplicate field: {}", tag),
169            });
170        }
171
172        // Extract field content using the field_extractor module
173        let extract_result = extract_field_content(&self.input[self.position..], tag);
174
175        match extract_result {
176            Some((content, consumed)) => {
177                self.position += consumed;
178                // Only track fields if duplicates are not allowed
179                if !self.allow_duplicates {
180                    self.fields_seen.insert(tag.to_string());
181                }
182                Ok(content)
183            }
184            None => {
185                if optional {
186                    // For optional fields, just return a format error that will be caught
187                    Err(ParseError::InvalidFormat {
188                        message: format!("Optional field {} not found", tag),
189                    })
190                } else {
191                    Err(ParseError::MissingRequiredField {
192                        field_tag: tag.to_string(),
193                        field_name: tag.to_string(),
194                        message_type: self.message_type.clone(),
195                        position_in_block4: Some(self.position),
196                    })
197                }
198            }
199        }
200    }
201
202    /// Detect which variant is present for an enum field
203    fn detect_variant(&self, base_tag: &str) -> Result<String, ParseError> {
204        // Look for common variants in order of preference
205        let common_variants = vec!["A", "B", "C", "D", "F", "K", "L"];
206
207        // Get the remaining input
208        let remaining = &self.input[self.position..];
209
210        // For required fields, we should find it immediately (possibly after whitespace)
211        let trimmed = remaining.trim_start_matches(|c: char| c.is_whitespace());
212
213        for variant in common_variants {
214            let full_tag = format!("{}{}", base_tag, variant);
215            if trimmed.starts_with(&format!(":{}:", full_tag)) {
216                return Ok(variant.to_string());
217            }
218        }
219
220        // Also check for no-variant version (just the base tag)
221        if trimmed.starts_with(&format!(":{}:", base_tag)) {
222            return Ok(String::new());
223        }
224
225        Err(ParseError::MissingRequiredField {
226            field_tag: base_tag.to_string(),
227            field_name: base_tag.to_string(),
228            message_type: self.message_type.clone(),
229            position_in_block4: Some(self.position),
230        })
231    }
232
233    /// Detect variant for optional fields
234    pub fn detect_variant_optional(&self, base_tag: &str) -> Option<String> {
235        // Look for common variants
236        let common_variants = vec!["A", "B", "C", "D", "F", "K", "L"];
237
238        // Get the remaining input
239        let remaining = &self.input[self.position..];
240
241        // Skip any leading whitespace
242        let trimmed = remaining.trim_start_matches(|c: char| c.is_whitespace());
243
244        // Check if the immediate next field is one of our variants
245        for variant in common_variants {
246            let full_tag = format!("{}{}", base_tag, variant);
247            if trimmed.starts_with(&format!(":{}:", full_tag)) {
248                return Some(variant.to_string());
249            }
250        }
251
252        // Check for no-variant version
253        if trimmed.starts_with(&format!(":{}:", base_tag)) {
254            return Some(String::new());
255        }
256
257        None
258    }
259
260    /// Get current position in input
261    pub fn position(&self) -> usize {
262        self.position
263    }
264
265    /// Get remaining unparsed content (useful for debugging)
266    pub fn remaining(&self) -> &str {
267        &self.input[self.position..]
268    }
269
270    /// Check if we've reached the end of input
271    pub fn is_complete(&self) -> bool {
272        self.position >= self.input.len()
273            || self.remaining().trim().is_empty()
274            || self.remaining().trim() == "-"
275    }
276
277    /// Check if a field exists in the remaining content
278    pub fn detect_field(&self, tag: &str) -> bool {
279        let remaining = self.remaining();
280        let trimmed = remaining.trim_start_matches(|c: char| c.is_whitespace());
281        trimmed.starts_with(&format!(":{}:", tag))
282    }
283
284    /// Peek at the variant of a field without consuming it
285    /// Returns the variant letter (e.g., "A", "K", "C", "L") if the field exists
286    pub fn peek_field_variant(&self, base_tag: &str) -> Option<String> {
287        let remaining = self.remaining();
288        let trimmed = remaining.trim_start_matches(|c: char| c.is_whitespace());
289
290        // Try to find field with any variant (e.g., :50A:, :50K:, etc.)
291        for variant in [
292            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
293            'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
294        ] {
295            let search_pattern = format!(":{}{}:", base_tag, variant);
296            if trimmed.starts_with(&search_pattern) {
297                return Some(variant.to_string());
298            }
299        }
300
301        // Check for field without variant (e.g., :50:)
302        let search_pattern = format!(":{}:", base_tag);
303        if trimmed.starts_with(&search_pattern) {
304            return Some("".to_string()); // No variant
305        }
306
307        None
308    }
309}