swift_mt_message/parser/
parser_impl.rs

1//! # SWIFT MT Message Parser
2//!
3//! ## Purpose
4//! Comprehensive parser for SWIFT MT (Message Type) messages that converts raw SWIFT message strings
5//! into typed, structured data with full validation and field consumption tracking.
6//!
7//! ## Features
8//! - **Multi-Format Support**: Handles all supported MT message types (101, 103, 104, etc.)
9//! - **Block Structure Parsing**: Extracts and validates all 5 SWIFT message blocks
10//! - **Field Consumption Tracking**: Sequential processing of duplicate fields with position tracking
11//! - **Type-Safe Parsing**: Converts raw strings to strongly-typed field structures
12//! - **Automatic Message Detection**: Auto-detects message type from application header
13//! - **Comprehensive Validation**: Validates message structure, field formats, and business rules
14//!
15//! ## Architecture
16//! The parser follows a layered approach:
17//! 1. **Block Extraction**: Extracts blocks 1-5 from raw SWIFT message
18//! 2. **Header Parsing**: Parses blocks 1, 2, 3, and 5 into header structures
19//! 3. **Field Parsing**: Parses block 4 fields with position and variant tracking
20//! 4. **Message Construction**: Builds typed message structures with sequential field consumption
21//! 5. **Validation**: Applies format and business rule validation
22//!
23//! ## Usage Examples
24//! ```rust
25//! use swift_mt_message::parser::SwiftParser;
26//! use swift_mt_message::messages::MT103;
27//! use swift_mt_message::ParsedSwiftMessage;
28//!
29//! # fn main() -> swift_mt_message::Result<()> {
30//! # let swift_message_string = "{1:F01BANKDEFFAXXX0123456789}{2:I103BANKDEFFAXXXU3003}{4:\n:20:TXN123456\n:23B:CRED\n:32A:240315USD1000,00\n:50K:JOHN DOE\n123 MAIN ST\n:59:DE89370400440532013000\nBENEFICIARY NAME\n:71A:SHA\n-}";
31//! // Parse specific message type
32//! let mt103 = SwiftParser::parse::<MT103>(&swift_message_string)?;
33//!
34//! // Auto-detect message type
35//! let parsed_message = SwiftParser::parse_auto(&swift_message_string)?;
36//! match parsed_message {
37//!     ParsedSwiftMessage::MT103(msg) => println!("Parsed MT103: {:?}", msg),
38//!     ParsedSwiftMessage::MT202(msg) => println!("Parsed MT202: {:?}", msg),
39//!     _ => println!("Other message type"),
40//! }
41//! # Ok(())
42//! # }
43//! ```
44
45use std::collections::{HashMap, HashSet};
46
47use crate::errors::{ParseError, ParserConfig, Result, SwiftValidationError};
48use crate::headers::{ApplicationHeader, BasicHeader, Trailer, UserHeader};
49use crate::messages::{
50    MT101, MT103, MT104, MT107, MT110, MT111, MT112, MT192, MT196, MT199, MT200, MT202, MT204,
51    MT205, MT210, MT292, MT296, MT299, MT900, MT910, MT920, MT935, MT940, MT941, MT942, MT950,
52};
53use crate::swift_error_codes::t_series;
54use crate::{ParsedSwiftMessage, SwiftMessage, SwiftMessageBody};
55
56/// Type alias for the field parsing result with position tracking
57///
58/// Maps field tags to vectors of (field_value, position_in_message) tuples.
59/// This enables sequential consumption of duplicate fields while maintaining message order.
60type FieldParseResult = Result<HashMap<String, Vec<(String, usize)>>>;
61
62/// Parsing context that flows through the parsing pipeline
63#[derive(Debug, Clone)]
64pub struct ParsingContext {
65    /// Current field being parsed
66    pub current_field: Option<String>,
67    /// Current component being parsed
68    pub current_component: Option<String>,
69    /// Message type
70    pub message_type: String,
71    /// Original message for context
72    pub original_message: String,
73}
74
75impl ParsingContext {
76    /// Create a new parsing context
77    pub fn new(message_type: String, original_message: String) -> Self {
78        Self {
79            current_field: None,
80            current_component: None,
81            message_type,
82            original_message,
83        }
84    }
85
86    /// Create a context with field information
87    pub fn with_field(&self, field: String) -> Self {
88        let mut ctx = self.clone();
89        ctx.current_field = Some(field);
90        ctx.current_component = None;
91        ctx
92    }
93
94    /// Create a context with component information
95    pub fn with_component(&self, component: String) -> Self {
96        let mut ctx = self.clone();
97        ctx.current_component = Some(component);
98        ctx
99    }
100}
101
102/// Field consumption tracker for sequential processing of duplicate fields
103///
104/// ## Purpose
105/// Ensures that when a message contains multiple instances of the same field (e.g., multiple :50: fields),
106/// they are consumed sequentially in the order they appear in the original message. This is critical
107/// for messages like MT101 where sequence matters.
108///
109/// ## Implementation
110/// - Tracks consumed field indices by tag
111/// - Provides next available field value for sequential consumption
112/// - Maintains message order integrity during field processing
113///
114/// ## Example
115/// ```rust
116/// use swift_mt_message::parser::FieldConsumptionTracker;
117///
118/// let mut tracker = FieldConsumptionTracker::new();
119/// // Field "50" has values at positions [5, 15, 25] in message
120/// let field_values = vec![
121///     ("value1".to_string(), 5),
122///     ("value2".to_string(), 15),
123///     ("value3".to_string(), 25),
124/// ];
125/// let (value1, pos1) = tracker.get_next_available("50", &field_values).unwrap();
126/// tracker.mark_consumed("50", pos1);
127/// let (value2, pos2) = tracker.get_next_available("50", &field_values).unwrap();
128/// // Ensures value2 is from position 15, not 5 or 25
129/// ```
130#[derive(Debug, Clone)]
131pub struct FieldConsumptionTracker {
132    /// Maps field tags to sets of consumed position indices
133    consumed_indices: HashMap<String, HashSet<usize>>,
134}
135
136impl Default for FieldConsumptionTracker {
137    fn default() -> Self {
138        Self::new()
139    }
140}
141
142impl FieldConsumptionTracker {
143    /// Create a new consumption tracker
144    pub fn new() -> Self {
145        Self {
146            consumed_indices: HashMap::new(),
147        }
148    }
149
150    /// Mark a field value at a specific position as consumed
151    pub fn mark_consumed(&mut self, tag: &str, index: usize) {
152        // Avoid allocation when the key already exists
153        use std::collections::hash_map::Entry;
154        match self.consumed_indices.entry(tag.to_string()) {
155            Entry::Occupied(mut e) => {
156                e.get_mut().insert(index);
157            }
158            Entry::Vacant(e) => {
159                let mut set = HashSet::new();
160                set.insert(index);
161                e.insert(set);
162            }
163        }
164    }
165
166    /// Get the next available (unconsumed) field value for a tag
167    pub fn get_next_available<'a>(
168        &self,
169        tag: &str,
170        values: &'a [(String, usize)],
171    ) -> Option<(&'a str, usize)> {
172        let consumed_set = self.consumed_indices.get(tag);
173
174        // Find first unconsumed value in original message order
175        values
176            .iter()
177            .find(|(_, pos)| consumed_set.is_none_or(|set| !set.contains(pos)))
178            .map(|(value, pos)| (value.as_str(), *pos))
179    }
180}
181
182/// Find field values by base tag with sequential consumption tracking and variant constraints
183///
184/// ## Purpose
185/// Enhanced version of find_field_with_variant_sequential that accepts a list of valid variants
186/// to constrain the search. This is crucial for numbered field tags like "50#1" and "50#2"
187/// where different positions accept different variants.
188///
189/// ## Parameters
190/// - `fields`: HashMap of all parsed fields with position tracking
191/// - `base_tag`: Base field tag (e.g., "50", "59")
192/// - `tracker`: Mutable reference to consumption tracker for sequential processing
193/// - `valid_variants`: Optional list of valid variant letters (e.g., ["C", "L"] for Field50InstructingParty)
194///
195/// ## Returns
196/// `Option<(field_value, variant, position)>` where:
197/// - `field_value`: The actual field content
198/// - `variant`: Optional variant letter (A, F, K, etc.) for enum fields
199/// - `position`: Original position in the message for ordering
200pub fn find_field_with_variant_sequential_constrained(
201    fields: &HashMap<String, Vec<(String, usize)>>,
202    base_tag: &str,
203    tracker: &mut FieldConsumptionTracker,
204    valid_variants: Option<&[&str]>,
205) -> Option<(String, Option<String>, usize)> {
206    // First try to find exact match (for non-variant fields)
207    if let Some(values) = fields.get(base_tag)
208        && let Some((value, pos)) = tracker.get_next_available(base_tag, values)
209    {
210        tracker.mark_consumed(base_tag, pos);
211        return Some((value.to_string(), None, pos));
212    }
213
214    // For enum fields, look for variant tags (50A, 50F, 50K, etc.)
215    // Sort tags by position to ensure we process them in order
216    let mut variant_candidates: Vec<(&String, &Vec<(String, usize)>)> = fields
217        .iter()
218        .filter(|(tag, _)| {
219            tag.starts_with(base_tag)
220                && tag.len() == base_tag.len() + 1
221                && tag
222                    .chars()
223                    .last()
224                    .is_some_and(|c| c.is_ascii_alphabetic() && c.is_ascii_uppercase())
225        })
226        .collect();
227
228    // Sort by the minimum unconsumed position in each tag's values
229    variant_candidates.sort_by_key(|(tag, values)| {
230        values
231            .iter()
232            .filter(|(_, pos)| {
233                tracker
234                    .consumed_indices
235                    .get(*tag)
236                    .is_none_or(|set| !set.contains(pos))
237            })
238            .map(|(_, pos)| *pos)
239            .min()
240            .unwrap_or(usize::MAX)
241    });
242
243    for (tag, values) in variant_candidates {
244        let variant_char = tag.chars().last().unwrap();
245        let variant_str = variant_char.to_string();
246
247        // Check if this variant is allowed (if constraints are provided)
248        if let Some(valid) = valid_variants
249            && !valid.contains(&variant_str.as_str())
250        {
251            continue; // Skip variants that aren't in the valid list
252        }
253
254        if let Some((value, pos)) = tracker.get_next_available(tag, values) {
255            tracker.mark_consumed(tag, pos);
256            return Some((value.to_string(), Some(variant_str), pos));
257        }
258    }
259
260    None
261}
262
263/// Main parser for SWIFT MT messages
264///
265/// ## Purpose
266/// Primary parsing engine that converts raw SWIFT message strings into typed, validated message structures.
267/// Handles all aspects of SWIFT message processing including block extraction, header parsing, field parsing,
268/// and type-safe message construction.
269///
270/// ## Capabilities
271/// - **Multi-Message Support**: Parses all 24 supported MT message types
272/// - **Flexible Parsing**: Both type-specific and auto-detection parsing modes
273/// - **Robust Error Handling**: Comprehensive error reporting for malformed messages
274/// - **Field Validation**: Format validation and business rule checking
275/// - **Position Tracking**: Maintains field order for sequential processing requirements
276///
277/// ## Parsing Process
278/// 1. **Block Extraction**: Identifies and extracts SWIFT blocks 1-5
279/// 2. **Header Validation**: Parses and validates basic, application, user headers and trailer
280/// 3. **Message Type Detection**: Determines message type from application header
281/// 4. **Field Processing**: Parses block 4 fields with position and variant tracking
282/// 5. **Type Construction**: Builds strongly-typed message structures
283/// 6. **Validation**: Applies format and business rule validation
284///
285/// ## Thread Safety
286/// SwiftParser is stateless and thread-safe. All methods are static and can be called
287/// concurrently from multiple threads.
288pub struct SwiftParser {
289    config: ParserConfig,
290}
291
292impl Default for SwiftParser {
293    fn default() -> Self {
294        Self::new()
295    }
296}
297
298impl SwiftParser {
299    /// Create a new parser with default configuration
300    pub fn new() -> Self {
301        Self {
302            config: ParserConfig::default(),
303        }
304    }
305
306    /// Create a new parser with custom configuration
307    pub fn with_config(config: ParserConfig) -> Self {
308        Self { config }
309    }
310
311    /// Parse a message and return ParseResult with all errors collected
312    pub fn parse_with_errors<T: SwiftMessageBody>(
313        &self,
314        raw_message: &str,
315    ) -> Result<crate::errors::ParseResult<SwiftMessage<T>>> {
316        let block1 = Self::extract_block(raw_message, 1)?;
317        let block2 = Self::extract_block(raw_message, 2)?;
318        let block3 = Self::extract_block(raw_message, 3)?;
319        let block4 = Self::extract_block(raw_message, 4)?;
320        let block5 = Self::extract_block(raw_message, 5)?;
321
322        // Parse headers
323        let basic_header = BasicHeader::parse(&block1.unwrap_or_default())?;
324        let application_header = ApplicationHeader::parse(&block2.unwrap_or_default())?;
325        let user_header = block3.map(|b| UserHeader::parse(&b)).transpose()?;
326        let trailer = block5.map(|b| Trailer::parse(&b)).transpose()?;
327
328        // Extract message type from application header
329        let message_type = application_header.message_type.clone();
330
331        // Validate message type matches expected type using SWIFT error codes
332        if message_type != T::message_type() {
333            return Err(ParseError::SwiftValidation(Box::new(
334                SwiftValidationError::format_error(
335                    t_series::T03,
336                    "MESSAGE_TYPE",
337                    &message_type,
338                    T::message_type(),
339                    &format!(
340                        "Message type mismatch: expected {}, got {}",
341                        T::message_type(),
342                        message_type
343                    ),
344                ),
345            )));
346        }
347
348        // Parse block 4 fields with position tracking
349        let field_map_with_positions = Self::parse_block4_fields(&block4.unwrap_or_default())?;
350
351        // Use configuration-aware parsing
352        let parse_result = T::from_fields_with_config(field_map_with_positions, &self.config)?;
353
354        match parse_result {
355            crate::errors::ParseResult::Success(fields) => {
356                Ok(crate::errors::ParseResult::Success(SwiftMessage {
357                    basic_header,
358                    application_header,
359                    user_header,
360                    trailer,
361                    message_type,
362                    fields,
363                }))
364            }
365            crate::errors::ParseResult::PartialSuccess(fields, errors) => {
366                Ok(crate::errors::ParseResult::PartialSuccess(
367                    SwiftMessage {
368                        basic_header,
369                        application_header,
370                        user_header,
371                        trailer,
372                        message_type,
373                        fields,
374                    },
375                    errors,
376                ))
377            }
378            crate::errors::ParseResult::Failure(errors) => {
379                Ok(crate::errors::ParseResult::Failure(errors))
380            }
381        }
382    }
383    /// Parse a raw SWIFT message string into a typed message (static method for backward compatibility)
384    pub fn parse<T: SwiftMessageBody>(raw_message: &str) -> Result<SwiftMessage<T>> {
385        Self::new().parse_message(raw_message)
386    }
387
388    /// Parse a raw SWIFT message string into a typed message with configuration support
389    pub fn parse_message<T: SwiftMessageBody>(&self, raw_message: &str) -> Result<SwiftMessage<T>> {
390        let block1 = Self::extract_block(raw_message, 1)?;
391        let block2 = Self::extract_block(raw_message, 2)?;
392        let block3 = Self::extract_block(raw_message, 3)?;
393        let block4 = Self::extract_block(raw_message, 4)?;
394        let block5 = Self::extract_block(raw_message, 5)?;
395
396        // Parse headers
397        let basic_header = BasicHeader::parse(&block1.unwrap_or_default())?;
398        let application_header = ApplicationHeader::parse(&block2.unwrap_or_default())?;
399        let user_header = block3.map(|b| UserHeader::parse(&b)).transpose()?;
400        let trailer = block5.map(|b| Trailer::parse(&b)).transpose()?;
401
402        // Extract message type from application header
403        let message_type = application_header.message_type.clone();
404
405        // Validate message type matches expected type using SWIFT error codes
406        if message_type != T::message_type() {
407            return Err(ParseError::SwiftValidation(Box::new(
408                SwiftValidationError::format_error(
409                    t_series::T03,
410                    "MESSAGE_TYPE",
411                    &message_type,
412                    T::message_type(),
413                    &format!(
414                        "Message type mismatch: expected {}, got {}",
415                        T::message_type(),
416                        message_type
417                    ),
418                ),
419            )));
420        }
421
422        // Parse block 4 fields with position tracking
423        let field_map_with_positions = Self::parse_block4_fields(&block4.unwrap_or_default())?;
424
425        // Use configuration-aware parsing
426        let parse_result = T::from_fields_with_config(field_map_with_positions, &self.config)?;
427
428        match parse_result {
429            crate::errors::ParseResult::Success(fields) => Ok(SwiftMessage {
430                basic_header,
431                application_header,
432                user_header,
433                trailer,
434                message_type,
435                fields,
436            }),
437            crate::errors::ParseResult::PartialSuccess(fields, errors) => {
438                // For partial success, we could log the errors but still return the message
439                // For now, we'll return the message but the errors are available in the ParseResult
440                eprintln!("Warning: Parsed with {} non-critical errors", errors.len());
441                for error in &errors {
442                    eprintln!("  - {error}");
443                }
444                Ok(SwiftMessage {
445                    basic_header,
446                    application_header,
447                    user_header,
448                    trailer,
449                    message_type,
450                    fields,
451                })
452            }
453            crate::errors::ParseResult::Failure(errors) => {
454                // Convert to MultipleErrors
455                Err(ParseError::MultipleErrors(errors))
456            }
457        }
458    }
459
460    /// Parse a raw SWIFT message string with automatic message type detection (static method for backward compatibility)
461    pub fn parse_auto(raw_message: &str) -> Result<ParsedSwiftMessage> {
462        Self::new().parse_message_auto(raw_message)
463    }
464
465    /// Parse a raw SWIFT message string with automatic message type detection and configuration support
466    pub fn parse_message_auto(&self, raw_message: &str) -> Result<ParsedSwiftMessage> {
467        // First, extract blocks to get the message type
468        let block2 = Self::extract_block(raw_message, 2)?;
469
470        // Parse application header to get message type
471        let application_header = ApplicationHeader::parse(&block2.unwrap_or_default())?;
472        let message_type = &application_header.message_type;
473
474        // Route to appropriate parser based on message type
475        match message_type.as_str() {
476            "101" => {
477                let parsed = self.parse_message::<MT101>(raw_message)?;
478                Ok(ParsedSwiftMessage::MT101(Box::new(parsed)))
479            }
480            "103" => {
481                let parsed = self.parse_message::<MT103>(raw_message)?;
482                Ok(ParsedSwiftMessage::MT103(Box::new(parsed)))
483            }
484            "104" => {
485                let parsed = self.parse_message::<MT104>(raw_message)?;
486                Ok(ParsedSwiftMessage::MT104(Box::new(parsed)))
487            }
488            "107" => {
489                let parsed = self.parse_message::<MT107>(raw_message)?;
490                Ok(ParsedSwiftMessage::MT107(Box::new(parsed)))
491            }
492            "110" => {
493                let parsed = self.parse_message::<MT110>(raw_message)?;
494                Ok(ParsedSwiftMessage::MT110(Box::new(parsed)))
495            }
496            "111" => {
497                let parsed = self.parse_message::<MT111>(raw_message)?;
498                Ok(ParsedSwiftMessage::MT111(Box::new(parsed)))
499            }
500            "112" => {
501                let parsed = self.parse_message::<MT112>(raw_message)?;
502                Ok(ParsedSwiftMessage::MT112(Box::new(parsed)))
503            }
504            "200" => {
505                let parsed = self.parse_message::<MT200>(raw_message)?;
506                Ok(ParsedSwiftMessage::MT200(Box::new(parsed)))
507            }
508            "202" => {
509                let parsed = self.parse_message::<MT202>(raw_message)?;
510                Ok(ParsedSwiftMessage::MT202(Box::new(parsed)))
511            }
512            "204" => {
513                let parsed = self.parse_message::<MT204>(raw_message)?;
514                Ok(ParsedSwiftMessage::MT204(Box::new(parsed)))
515            }
516            "205" => {
517                let parsed = self.parse_message::<MT205>(raw_message)?;
518                Ok(ParsedSwiftMessage::MT205(Box::new(parsed)))
519            }
520            "210" => {
521                let parsed = self.parse_message::<MT210>(raw_message)?;
522                Ok(ParsedSwiftMessage::MT210(Box::new(parsed)))
523            }
524            "900" => {
525                let parsed = self.parse_message::<MT900>(raw_message)?;
526                Ok(ParsedSwiftMessage::MT900(Box::new(parsed)))
527            }
528            "910" => {
529                let parsed = self.parse_message::<MT910>(raw_message)?;
530                Ok(ParsedSwiftMessage::MT910(Box::new(parsed)))
531            }
532            "920" => {
533                let parsed = self.parse_message::<MT920>(raw_message)?;
534                Ok(ParsedSwiftMessage::MT920(Box::new(parsed)))
535            }
536            "935" => {
537                let parsed = self.parse_message::<MT935>(raw_message)?;
538                Ok(ParsedSwiftMessage::MT935(Box::new(parsed)))
539            }
540            "940" => {
541                let parsed = self.parse_message::<MT940>(raw_message)?;
542                Ok(ParsedSwiftMessage::MT940(Box::new(parsed)))
543            }
544            "941" => {
545                let parsed = self.parse_message::<MT941>(raw_message)?;
546                Ok(ParsedSwiftMessage::MT941(Box::new(parsed)))
547            }
548            "942" => {
549                let parsed = self.parse_message::<MT942>(raw_message)?;
550                Ok(ParsedSwiftMessage::MT942(Box::new(parsed)))
551            }
552            "950" => {
553                let parsed = self.parse_message::<MT950>(raw_message)?;
554                Ok(ParsedSwiftMessage::MT950(Box::new(parsed)))
555            }
556            "192" => {
557                let parsed = self.parse_message::<MT192>(raw_message)?;
558                Ok(ParsedSwiftMessage::MT192(Box::new(parsed)))
559            }
560            "196" => {
561                let parsed = self.parse_message::<MT196>(raw_message)?;
562                Ok(ParsedSwiftMessage::MT196(Box::new(parsed)))
563            }
564            "292" => {
565                let parsed = self.parse_message::<MT292>(raw_message)?;
566                Ok(ParsedSwiftMessage::MT292(Box::new(parsed)))
567            }
568            "296" => {
569                let parsed = self.parse_message::<MT296>(raw_message)?;
570                Ok(ParsedSwiftMessage::MT296(Box::new(parsed)))
571            }
572            "199" => {
573                let parsed = self.parse_message::<MT199>(raw_message)?;
574                Ok(ParsedSwiftMessage::MT199(Box::new(parsed)))
575            }
576            "299" => {
577                let parsed = self.parse_message::<MT299>(raw_message)?;
578                Ok(ParsedSwiftMessage::MT299(Box::new(parsed)))
579            }
580            _ => Err(ParseError::UnsupportedMessageType {
581                message_type: message_type.clone(),
582            }),
583        }
584    }
585
586    /// Extract a specific message block from raw SWIFT message with SWIFT validation
587    pub fn extract_block(raw_message: &str, block_index: u8) -> Result<Option<String>> {
588        // Validate block index using SWIFT error codes
589        if !(1..=5).contains(&block_index) {
590            return Err(ParseError::SwiftValidation(Box::new(
591                crate::errors::SwiftValidationError::format_error(
592                    crate::swift_error_codes::t_series::T01,
593                    "BLOCK_INDEX",
594                    &block_index.to_string(),
595                    "1-5",
596                    &format!("Invalid block index: {block_index}"),
597                ),
598            )));
599        }
600
601        let block_marker = format!("{{{block_index}:");
602
603        if let Some(start) = raw_message.find(&block_marker) {
604            let content_start = start + block_marker.len();
605
606            match block_index {
607                1 | 2 => {
608                    // Blocks 1 and 2 end with simple closing brace (no nested content)
609                    if let Some(end) = raw_message[start..].find('}') {
610                        let end = start + end;
611                        Ok(Some(raw_message[content_start..end].to_string()))
612                    } else {
613                        Ok(None)
614                    }
615                }
616                3 | 5 => {
617                    // Blocks 3 and 5 may have nested braces (e.g., {103:EBA} or {CHK:...})
618                    if let Some(end) = Self::find_matching_brace(&raw_message[start..]) {
619                        let end = start + end;
620                        Ok(Some(raw_message[content_start..end].to_string()))
621                    } else {
622                        Ok(None)
623                    }
624                }
625                4 => {
626                    // Block 4 ends with "-}"
627                    if let Some(end) = raw_message[start..].find("-}") {
628                        let end = start + end;
629                        Ok(Some(raw_message[content_start..end].to_string()))
630                    } else {
631                        Ok(None)
632                    }
633                }
634                _ => Err(ParseError::SwiftValidation(Box::new(
635                    crate::errors::SwiftValidationError::format_error(
636                        crate::swift_error_codes::t_series::T02,
637                        "BLOCK",
638                        &block_index.to_string(),
639                        "1-5",
640                        &format!("Invalid block index: {block_index}"),
641                    ),
642                ))),
643            }
644        } else {
645            Ok(None)
646        }
647    }
648
649    /// Parse block 4 fields into a field map with enhanced position tracking
650    fn parse_block4_fields(block4: &str) -> FieldParseResult {
651        // Use the generated parser function from the generated module
652        crate::parser::parse_block4_fields(block4)
653    }
654
655    /// Find the matching closing brace for a block that starts with an opening brace
656    /// Handles nested braces correctly
657    fn find_matching_brace(text: &str) -> Option<usize> {
658        let mut chars = text.char_indices();
659
660        // Skip the first character (should be '{')
661        let mut brace_count = if let Some((_, '{')) = chars.next() {
662            1
663        } else {
664            return None;
665        };
666
667        for (i, ch) in chars {
668            match ch {
669                '{' => brace_count += 1,
670                '}' => {
671                    brace_count -= 1;
672                    if brace_count == 0 {
673                        return Some(i);
674                    }
675                }
676                _ => {}
677            }
678        }
679
680        None
681    }
682}
683
684/// Parse a SwiftMessage from a string representation
685/// This is a placeholder implementation for the macro system
686pub fn parse_swift_message_from_string(value: &str) -> Result<HashMap<String, Vec<String>>> {
687    // For now, this is a stub implementation
688    // In a real implementation, this would parse the string representation
689    // of a SwiftMessage back into a field map
690
691    // As a temporary solution, we'll assume the value is a simple field representation
692    // and try to parse it as a mini SWIFT block
693    let mut field_map = HashMap::new();
694
695    // Split by lines and parse each field
696    for line in value.lines() {
697        if line.trim().is_empty() {
698            continue;
699        }
700
701        // Look for field pattern :XX:value
702        if let Some(colon_pos) = line.find(':')
703            && let Some(second_colon) = line[colon_pos + 1..].find(':')
704        {
705            let second_colon_pos = colon_pos + 1 + second_colon;
706            let field_tag = line[colon_pos + 1..second_colon_pos].to_string();
707            let _field_value = line[second_colon_pos + 1..].to_string();
708
709            field_map
710                .entry(field_tag)
711                .or_insert_with(Vec::new)
712                .push(format!(":{}", &line[colon_pos + 1..]));
713        }
714    }
715
716    Ok(field_map)
717}
718
719/// Parse sequence fields (e.g., transactions in MT101, MT104)
720///
721/// This function identifies sequence boundaries and parses each sequence into the target type.
722/// Sequences typically start with a mandatory field (like Field 21) that marks the beginning
723/// of each repetition.
724pub fn parse_sequences<T>(
725    fields: &HashMap<String, Vec<(String, usize)>>,
726    tracker: &mut FieldConsumptionTracker,
727) -> Result<Vec<T>>
728where
729    T: crate::SwiftMessageBody,
730{
731    // Use the enhanced sequence parser for messages that have complex sequences
732    let message_type = std::any::type_name::<T>();
733
734    if message_type.contains("MT104Transaction") {
735        // For MT104, we need to handle the three-sequence structure
736        use crate::parser::sequence_parser::{get_sequence_config, split_into_sequences};
737
738        let config = get_sequence_config("MT104");
739        let parsed_sequences = split_into_sequences(fields, &config)?;
740
741        // Parse only sequence B (transactions)
742        return parse_sequence_b_items::<T>(&parsed_sequences.sequence_b, tracker);
743    }
744
745    if message_type.contains("MT204Transaction") {
746        // MT204 has a special structure where fields are grouped by type, not by transaction
747        // We need to reconstruct transactions from the grouped fields
748
749        // Count how many transactions we have (based on field 20 occurrences, excluding the first one)
750        let field_20_count = fields.get("20").map(|v| v.len()).unwrap_or(0);
751        if field_20_count <= 1 {
752            return Ok(Vec::new()); // No transactions if only one or zero field 20s
753        }
754
755        let num_transactions = field_20_count - 1; // First field 20 is for sequence A
756
757        // Build transactions by distributing fields
758        let mut transactions = Vec::new();
759
760        for i in 0..num_transactions {
761            let mut tx_fields = HashMap::new();
762
763            // Get field 20 (skip the first one which is for sequence A)
764            if let Some(field_20_values) = fields.get("20")
765                && i + 1 < field_20_values.len()
766            {
767                tx_fields.insert("20".to_string(), vec![field_20_values[i + 1].clone()]);
768            }
769
770            // Get field 21 if present (optional)
771            if let Some(field_21_values) = fields.get("21")
772                && i < field_21_values.len()
773            {
774                tx_fields.insert("21".to_string(), vec![field_21_values[i].clone()]);
775            }
776
777            // Get field 32B
778            if let Some(field_32b_values) = fields.get("32B")
779                && i < field_32b_values.len()
780            {
781                tx_fields.insert("32B".to_string(), vec![field_32b_values[i].clone()]);
782            }
783
784            // Get field 53 (various variants)
785            for variant in ["53", "53A", "53B", "53D"] {
786                if let Some(field_53_values) = fields.get(variant)
787                    && i < field_53_values.len()
788                {
789                    tx_fields.insert(variant.to_string(), vec![field_53_values[i].clone()]);
790                    break; // Only one variant per transaction
791                }
792            }
793
794            // Get field 72 if present (optional)
795            if let Some(field_72_values) = fields.get("72") {
796                // Field 72 can appear multiple times, need to figure out which ones belong to transactions
797                // For now, skip the first two (which are for sequence A) and take one per transaction
798                if i + 2 < field_72_values.len() {
799                    tx_fields.insert("72".to_string(), vec![field_72_values[i + 2].clone()]);
800                }
801            }
802
803            // Parse the transaction
804            if let Ok(transaction) = T::from_fields(tx_fields) {
805                transactions.push(transaction);
806            }
807        }
808
809        return Ok(transactions);
810    }
811
812    // Get all fields sorted by position
813    let mut all_fields: Vec<(String, String, usize)> = Vec::new();
814    for (tag, values) in fields {
815        for (value, pos) in values {
816            // Only include unconsumed fields
817            if tracker
818                .consumed_indices
819                .get(tag)
820                .is_none_or(|set| !set.contains(pos))
821            {
822                all_fields.push((tag.clone(), value.clone(), *pos));
823            }
824        }
825    }
826    all_fields.sort_by_key(|(_, _, pos)| *pos);
827
828    // Determine the sequence start marker based on message type
829    let (primary_marker, secondary_marker) = if message_type.contains("MT920Sequence") {
830        ("12", None)
831    } else if message_type.contains("MT935RateChange") {
832        ("23", Some("25"))
833    } else if message_type.contains("MT940StatementLine")
834        || message_type.contains("MT942StatementLine")
835    {
836        ("61", None)
837    } else {
838        ("21", None)
839    };
840
841    let mut sequences = Vec::new();
842    let mut current_sequence_fields: HashMap<String, Vec<(String, usize)>> = HashMap::new();
843    let mut in_sequence = false;
844
845    for (tag, value, pos) in all_fields {
846        // Check if this is the start of a new sequence
847        let is_sequence_start = (tag == primary_marker
848            || secondary_marker.is_some_and(|m| tag == m))
849            && !tag.ends_with("R")
850            && !tag.ends_with("F")
851            && !tag.ends_with("C")
852            && !tag.ends_with("D");
853
854        if is_sequence_start {
855            // If we were already in a sequence, parse the previous one
856            if in_sequence && !current_sequence_fields.is_empty() {
857                if let Ok(sequence_item) = T::from_fields(current_sequence_fields.clone()) {
858                    sequences.push(sequence_item);
859                }
860                current_sequence_fields.clear();
861            }
862            in_sequence = true;
863        }
864
865        // Add field to current sequence if we're in one
866        if in_sequence {
867            current_sequence_fields
868                .entry(tag.clone())
869                .or_default()
870                .push((value, pos));
871
872            // Mark this field as consumed
873            tracker.mark_consumed(&tag, pos);
874        }
875    }
876
877    // Parse the last sequence if there is one
878    if in_sequence && !current_sequence_fields.is_empty() {
879        match T::from_fields(current_sequence_fields) {
880            Ok(sequence_item) => {
881                sequences.push(sequence_item);
882            }
883            Err(_e) => {
884                #[cfg(debug_assertions)]
885                eprintln!("DEBUG: Failed to parse final sequence item: {_e:?}");
886            }
887        }
888    }
889
890    Ok(sequences)
891}
892
893/// Parse sequence B items from already split fields
894fn parse_sequence_b_items<T>(
895    fields: &HashMap<String, Vec<(String, usize)>>,
896    tracker: &mut FieldConsumptionTracker,
897) -> Result<Vec<T>>
898where
899    T: crate::SwiftMessageBody,
900{
901    let mut sequences = Vec::new();
902
903    // Get all fields sorted by position
904    let mut all_fields: Vec<(String, String, usize)> = Vec::new();
905    for (tag, values) in fields {
906        for (value, pos) in values {
907            all_fields.push((tag.clone(), value.clone(), *pos));
908        }
909    }
910    all_fields.sort_by_key(|(_, _, pos)| *pos);
911
912    // Determine the sequence start tag based on message type
913    let message_type = std::any::type_name::<T>();
914    let sequence_start_tag = if message_type.contains("MT204Transaction") {
915        "20" // MT204 transactions start with field 20
916    } else {
917        "21" // Most other transactions start with field 21
918    };
919    let mut current_sequence_fields: HashMap<String, Vec<(String, usize)>> = HashMap::new();
920    let mut in_sequence = false;
921
922    for (tag, value, pos) in all_fields {
923        // Check if this is the start of a new sequence
924        if tag == sequence_start_tag
925            && !tag.ends_with("R")
926            && !tag.ends_with("F")
927            && !tag.ends_with("C")
928            && !tag.ends_with("D")
929        {
930            // If we were already in a sequence, parse the previous one
931            if in_sequence && !current_sequence_fields.is_empty() {
932                if let Ok(sequence_item) = T::from_fields(current_sequence_fields.clone()) {
933                    sequences.push(sequence_item);
934                }
935                current_sequence_fields.clear();
936            }
937            in_sequence = true;
938        }
939
940        // Add field to current sequence if we're in one
941        if in_sequence {
942            current_sequence_fields
943                .entry(tag.clone())
944                .or_default()
945                .push((value, pos));
946
947            // Mark this field as consumed
948            tracker.mark_consumed(&tag, pos);
949        }
950    }
951
952    // Parse the last sequence if there is one
953    if in_sequence && !current_sequence_fields.is_empty() {
954        match T::from_fields(current_sequence_fields) {
955            Ok(sequence_item) => {
956                sequences.push(sequence_item);
957            }
958            Err(_e) => {
959                #[cfg(debug_assertions)]
960                eprintln!("DEBUG: Failed to parse final sequence item: {_e:?}");
961            }
962        }
963    }
964
965    Ok(sequences)
966}
967
968/// Serialize a SwiftMessage field map to a string representation
969/// This is a placeholder implementation for the macro system
970pub fn serialize_swift_message_to_string(fields: &HashMap<String, Vec<String>>) -> String {
971    // For now, this is a stub implementation
972    // In a real implementation, this would serialize the field map
973    // into a string representation of a SwiftMessage
974
975    let mut result = String::new();
976
977    // Simple serialization: just join all field values with newlines
978    for field_values in fields.values() {
979        for field_value in field_values {
980            // field_value should already be in the format ":XX:value"
981            result.push_str(field_value);
982            result.push('\n');
983        }
984    }
985
986    // Remove trailing newline
987    if result.ends_with('\n') {
988        result.pop();
989    }
990
991    result
992}