swift_mt_message/
lib.rs

1//! # Swift MT Message Parser - Enhanced Architecture
2//!
3//! A comprehensive Rust library for parsing SWIFT MT (Message Type) messages with strong typing,
4//! complex field structures, comprehensive validation, and flattened JSON serialization.
5//!
6//! ## Key Features
7//!
8//! - **Complex Field Structures**: Full enum-based field variants (Field50: A/F/K, Field59: A/Basic)
9//! - **Flattened JSON Serialization**: Clean JSON output without enum wrapper layers
10//! - **Type-safe field parsing** with dedicated field structs and automatic validation
11//! - **Comprehensive Field Support**: All MT103 fields with proper SWIFT compliance
12//! - **Bidirectional Serialization**: Perfect round-trip JSON serialization/deserialization
13//! - **Extensive Validation**: BIC validation, field length checks, format compliance
14//!
15//! ## Supported Field Types
16//!
17//! ### Complex Enum Fields
18//! - **Field50** (Ordering Customer): 50A (Account+BIC), 50F (Party+Address), 50K (Name+Address)
19//! - **Field59** (Beneficiary Customer): 59A (Account+BIC), 59 (Basic lines)
20//!
21//! ### Institution Fields (with account_line_indicator)
22//! - **Field52A** (Ordering Institution): BIC + optional account + account_line_indicator
23//! - **Field53A-57A** (Correspondent/Intermediary): All with account_line_indicator support
24//!
25//! ### Simple Type Fields
26//! - **Field32A** (Value Date/Currency/Amount): NaiveDate + String + f64
27//! - **Field20, 23B, 70, 71A**: Proper field name alignment with old version
28//!
29//! ## JSON Output Structure
30//!
31//! The library produces clean, flattened JSON without enum wrapper layers:
32//!
33//! ```json
34//! {
35//!   "50": {
36//!     "name_and_address": ["JOHN DOE", "123 MAIN ST"]
37//!   },
38//!   "59": {
39//!     "account": "DE89370400440532013000",
40//!     "bic": "DEUTDEFFXXX"
41//!   }
42//! }
43//! ```
44//!
45//! Instead of nested enum structures like `{"50": {"K": {...}}}`.
46
47use serde::{Deserialize, Serialize};
48use std::any::Any;
49use std::collections::HashMap;
50use std::fmt::Debug;
51
52pub mod errors;
53pub mod fields;
54pub mod headers;
55pub mod messages;
56pub mod parser;
57
58// Re-export core types
59pub use errors::{ParseError, Result, ValidationError};
60pub use headers::{ApplicationHeader, BasicHeader, Trailer, UserHeader};
61pub use parser::SwiftParser;
62
63// Re-export derive macros
64pub use swift_mt_message_macros::{SwiftField, SwiftMessage, field, serde_swift_fields};
65
66/// Simplified result type for SWIFT operations
67pub type SwiftResult<T> = std::result::Result<T, crate::errors::ParseError>;
68
69/// Core trait for all Swift field types
70pub trait SwiftField: Serialize + for<'de> Deserialize<'de> + Clone + std::fmt::Debug {
71    /// Parse field value from string representation
72    fn parse(value: &str) -> Result<Self>
73    where
74        Self: Sized;
75
76    /// Convert field back to SWIFT string format
77    fn to_swift_string(&self) -> String;
78
79    /// Validate field according to SWIFT format rules
80    fn validate(&self) -> ValidationResult;
81
82    /// Get field format specification
83    fn format_spec() -> &'static str;
84}
85
86/// Core trait for Swift message types
87pub trait SwiftMessageBody: Debug + Clone + Send + Sync + Serialize + std::any::Any {
88    /// Get the message type identifier (e.g., "103", "202")
89    fn message_type() -> &'static str;
90
91    /// Create from field map
92    fn from_fields(fields: HashMap<String, Vec<String>>) -> SwiftResult<Self>
93    where
94        Self: Sized;
95
96    /// Convert to field map
97    fn to_fields(&self) -> HashMap<String, Vec<String>>;
98
99    /// Get required field tags for this message type
100    fn required_fields() -> Vec<&'static str>;
101
102    /// Get optional field tags for this message type
103    fn optional_fields() -> Vec<&'static str>;
104}
105
106/// Complete SWIFT message with headers and body
107#[derive(Debug, Clone, Serialize)]
108pub struct SwiftMessage<T: SwiftMessageBody> {
109    /// Basic Header (Block 1)
110    pub basic_header: BasicHeader,
111
112    /// Application Header (Block 2)
113    pub application_header: ApplicationHeader,
114
115    /// User Header (Block 3) - Optional
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub user_header: Option<UserHeader>,
118
119    /// Trailer (Block 5) - Optional
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub trailer: Option<Trailer>,
122
123    /// Raw message blocks for preservation
124    pub blocks: RawBlocks,
125
126    /// Message type identifier
127    pub message_type: String,
128
129    /// Field order as they appeared in the original message
130    pub field_order: Vec<String>,
131
132    /// Parsed message body with typed fields
133    pub fields: T,
134}
135
136/// Raw message blocks for preservation and reconstruction
137#[derive(Debug, Clone, Serialize, Deserialize, Default)]
138pub struct RawBlocks {
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub block1: Option<String>,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub block2: Option<String>,
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub block3: Option<String>,
145    pub block4: String,
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub block5: Option<String>,
148}
149
150/// Validation result for field and message validation
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct ValidationResult {
153    pub is_valid: bool,
154    pub errors: Vec<ValidationError>,
155    pub warnings: Vec<String>,
156}
157
158impl ValidationResult {
159    pub fn valid() -> Self {
160        Self {
161            is_valid: true,
162            errors: Vec::new(),
163            warnings: Vec::new(),
164        }
165    }
166
167    pub fn with_error(error: ValidationError) -> Self {
168        Self {
169            is_valid: false,
170            errors: vec![error],
171            warnings: Vec::new(),
172        }
173    }
174
175    pub fn with_errors(errors: Vec<ValidationError>) -> Self {
176        Self {
177            is_valid: errors.is_empty(),
178            errors,
179            warnings: Vec::new(),
180        }
181    }
182}
183
184/// Enumeration of all supported SWIFT message types for automatic parsing
185#[derive(Debug, Clone, Serialize)]
186#[serde(tag = "message_type")]
187pub enum ParsedSwiftMessage {
188    #[serde(rename = "103")]
189    MT103(Box<SwiftMessage<messages::MT103>>),
190    #[serde(rename = "202")]
191    MT202(Box<SwiftMessage<messages::MT202>>),
192    #[serde(rename = "205")]
193    MT205(Box<SwiftMessage<messages::MT205>>),
194    #[serde(rename = "900")]
195    MT900(Box<SwiftMessage<messages::MT900>>),
196}
197
198impl ParsedSwiftMessage {
199    /// Get the message type as a string
200    pub fn message_type(&self) -> &'static str {
201        match self {
202            ParsedSwiftMessage::MT103(_) => "103",
203            ParsedSwiftMessage::MT202(_) => "202",
204            ParsedSwiftMessage::MT205(_) => "205",
205            ParsedSwiftMessage::MT900(_) => "900",
206        }
207    }
208
209    /// Convert to a specific message type if it matches
210    pub fn as_mt103(&self) -> Option<&SwiftMessage<messages::MT103>> {
211        match self {
212            ParsedSwiftMessage::MT103(msg) => Some(msg),
213            _ => None,
214        }
215    }
216
217    pub fn as_mt202(&self) -> Option<&SwiftMessage<messages::MT202>> {
218        match self {
219            ParsedSwiftMessage::MT202(msg) => Some(msg),
220            _ => None,
221        }
222    }
223
224    pub fn as_mt205(&self) -> Option<&SwiftMessage<messages::MT205>> {
225        match self {
226            ParsedSwiftMessage::MT205(msg) => Some(msg),
227            _ => None,
228        }
229    }
230
231    pub fn as_mt900(&self) -> Option<&SwiftMessage<messages::MT900>> {
232        match self {
233            ParsedSwiftMessage::MT900(msg) => Some(msg),
234            _ => None,
235        }
236    }
237
238    /// Convert into a specific message type if it matches
239    pub fn into_mt103(self) -> Option<SwiftMessage<messages::MT103>> {
240        match self {
241            ParsedSwiftMessage::MT103(msg) => Some(*msg),
242            _ => None,
243        }
244    }
245
246    pub fn into_mt202(self) -> Option<SwiftMessage<messages::MT202>> {
247        match self {
248            ParsedSwiftMessage::MT202(msg) => Some(*msg),
249            _ => None,
250        }
251    }
252
253    pub fn into_mt205(self) -> Option<SwiftMessage<messages::MT205>> {
254        match self {
255            ParsedSwiftMessage::MT205(msg) => Some(*msg),
256            _ => None,
257        }
258    }
259
260    pub fn into_mt900(self) -> Option<SwiftMessage<messages::MT900>> {
261        match self {
262            ParsedSwiftMessage::MT900(msg) => Some(*msg),
263            _ => None,
264        }
265    }
266}
267
268impl<T: SwiftMessageBody> SwiftMessage<T> {
269    /// Check if this message contains reject codes (MT103 specific)
270    ///
271    /// Reject messages are identified by checking:
272    /// 1. Field 20 (Sender's Reference) for "REJT" prefix
273    /// 2. Block 3 field 108 (MUR - Message User Reference) for "REJT"
274    /// 3. Field 72 (Sender to Receiver Information) containing `/REJT/` code
275    pub fn has_reject_codes(&self) -> bool {
276        // Check Block 3 field 108 (MUR - Message User Reference)
277        if let Some(ref user_header) = self.user_header {
278            if let Some(ref mur) = user_header.message_user_reference {
279                if mur.to_uppercase().contains("REJT") {
280                    return true;
281                }
282            }
283        }
284
285        if let Some(mt103_fields) =
286            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT103>()
287        {
288            return mt103_fields.has_reject_codes();
289        } else if let Some(mt202_fields) =
290            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT202>()
291        {
292            return mt202_fields.has_reject_codes();
293        } else if let Some(mt205_fields) =
294            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT205>()
295        {
296            return mt205_fields.has_reject_codes();
297        }
298
299        false
300    }
301
302    /// Check if this message contains return codes (MT103 specific)
303    ///
304    /// Return messages are identified by checking:
305    /// 1. Field 20 (Sender's Reference) for "RETN" prefix
306    /// 2. Block 3 field 108 (MUR - Message User Reference) for "RETN"
307    /// 3. Field 72 (Sender to Receiver Information) containing `/RETN/` code
308    pub fn has_return_codes(&self) -> bool {
309        // Check Block 3 field 108 (MUR - Message User Reference)
310        if let Some(ref user_header) = self.user_header {
311            if let Some(ref mur) = user_header.message_user_reference {
312                if mur.to_uppercase().contains("RETN") {
313                    return true;
314                }
315            }
316        }
317
318        if let Some(mt103_fields) =
319            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT103>()
320        {
321            return mt103_fields.has_return_codes();
322        } else if let Some(mt202_fields) =
323            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT202>()
324        {
325            return mt202_fields.has_return_codes();
326        } else if let Some(mt205_fields) =
327            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT205>()
328        {
329            return mt205_fields.has_return_codes();
330        }
331
332        false
333    }
334
335    pub fn is_cover_message(&self) -> bool {
336        if let Some(mt202_fields) =
337            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT202>()
338        {
339            return mt202_fields.is_cover_message();
340        }
341        if let Some(mt205_fields) =
342            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT205>()
343        {
344            return mt205_fields.is_cover_message();
345        }
346
347        false
348    }
349
350    pub fn is_stp_message(&self) -> bool {
351        if let Some(mt103_fields) =
352            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT103>()
353        {
354            return mt103_fields.is_stp_compliant();
355        }
356
357        false
358    }
359
360    /// Validate message against business rules using JSONLogic
361    /// This validation method has access to both headers and message fields,
362    /// allowing for comprehensive validation of MT103 and other message types.
363    pub fn validate_business_rules(&self) -> ValidationResult {
364        // Check if the message type has validation rules
365        let validation_rules = match T::message_type() {
366            "103" => messages::MT103::validation_rules(),
367            "202" => messages::MT202::validation_rules(),
368            "205" => messages::MT205::validation_rules(),
369            "104" => messages::MT104::validation_rules(),
370            "107" => messages::MT107::validation_rules(),
371            "110" => messages::MT110::validation_rules(),
372            "111" => messages::MT111::validation_rules(),
373            "112" => messages::MT112::validation_rules(),
374            "210" => messages::MT210::validation_rules(),
375            "900" => messages::MT900::validation_rules(),
376            "910" => messages::MT910::validation_rules(),
377            "920" => messages::MT920::validation_rules(),
378            "935" => messages::MT935::validation_rules(),
379            "940" => messages::MT940::validation_rules(),
380            "941" => messages::MT941::validation_rules(),
381            "942" => messages::MT942::validation_rules(),
382            "950" => messages::MT950::validation_rules(),
383            _ => {
384                return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
385                    rule_name: "UNSUPPORTED_MESSAGE_TYPE".to_string(),
386                    message: format!(
387                        "No validation rules defined for message type {}",
388                        T::message_type()
389                    ),
390                });
391            }
392        };
393
394        // Parse the validation rules JSON
395        let rules_json: serde_json::Value = match serde_json::from_str(validation_rules) {
396            Ok(json) => json,
397            Err(e) => {
398                return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
399                    rule_name: "JSON_PARSE".to_string(),
400                    message: format!("Failed to parse validation rules JSON: {}", e),
401                });
402            }
403        };
404
405        // Extract rules array from the JSON
406        let rules = match rules_json.get("rules").and_then(|r| r.as_array()) {
407            Some(rules) => rules,
408            None => {
409                return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
410                    rule_name: "RULES_FORMAT".to_string(),
411                    message: "Validation rules must contain a 'rules' array".to_string(),
412                });
413            }
414        };
415
416        // Get constants if they exist
417        let constants = rules_json
418            .get("constants")
419            .and_then(|c| c.as_object())
420            .cloned()
421            .unwrap_or_default();
422
423        // Create comprehensive data context with headers and fields
424        let context_value = match self.create_validation_context(&constants) {
425            Ok(context) => context,
426            Err(e) => {
427                return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
428                    rule_name: "CONTEXT_CREATION".to_string(),
429                    message: format!("Failed to create validation context: {}", e),
430                });
431            }
432        };
433
434        // Validate each rule using datalogic-rs
435        let mut errors = Vec::new();
436        let mut warnings = Vec::new();
437
438        for (rule_index, rule) in rules.iter().enumerate() {
439            let rule_id = rule
440                .get("id")
441                .and_then(|id| id.as_str())
442                .map(|s| s.to_string())
443                .unwrap_or_else(|| format!("RULE_{}", rule_index));
444
445            let rule_description = rule
446                .get("description")
447                .and_then(|desc| desc.as_str())
448                .unwrap_or("No description");
449
450            if let Some(condition) = rule.get("condition") {
451                // Create DataLogic instance for evaluation
452                let dl = datalogic_rs::DataLogic::new();
453                match dl.evaluate_json(condition, &context_value, None) {
454                    Ok(result) => {
455                        match result.as_bool() {
456                            Some(true) => {
457                                // Rule passed
458                                continue;
459                            }
460                            Some(false) => {
461                                // Rule failed
462                                errors.push(ValidationError::BusinessRuleValidation {
463                                    rule_name: rule_id.clone(),
464                                    message: format!(
465                                        "Business rule validation failed: {} - {}",
466                                        rule_id, rule_description
467                                    ),
468                                });
469                            }
470                            None => {
471                                // Rule returned non-boolean value
472                                warnings.push(format!(
473                                    "Rule {} returned non-boolean value: {:?}",
474                                    rule_id, result
475                                ));
476                            }
477                        }
478                    }
479                    Err(e) => {
480                        // JSONLogic evaluation error
481                        errors.push(ValidationError::BusinessRuleValidation {
482                            rule_name: rule_id.clone(),
483                            message: format!(
484                                "JSONLogic evaluation error for rule {}: {}",
485                                rule_id, e
486                            ),
487                        });
488                    }
489                }
490            } else {
491                warnings.push(format!("Rule {} has no condition", rule_id));
492            }
493        }
494
495        ValidationResult {
496            is_valid: errors.is_empty(),
497            errors,
498            warnings,
499        }
500    }
501
502    /// Create a comprehensive validation context that includes headers, fields, and constants
503    fn create_validation_context(
504        &self,
505        constants: &serde_json::Map<String, serde_json::Value>,
506    ) -> Result<serde_json::Value> {
507        // Serialize the entire message (including headers) to JSON for data context
508        let full_message_data = match serde_json::to_value(self) {
509            Ok(data) => data,
510            Err(e) => {
511                return Err(ParseError::SerializationError {
512                    message: format!("Failed to serialize complete message: {}", e),
513                });
514            }
515        };
516
517        // Create a comprehensive data context
518        let mut data_context = serde_json::Map::new();
519
520        // Add the complete message data
521        if let serde_json::Value::Object(msg_obj) = full_message_data {
522            for (key, value) in msg_obj {
523                data_context.insert(key, value);
524            }
525        }
526
527        // Add constants to data context
528        for (key, value) in constants {
529            data_context.insert(key.clone(), value.clone());
530        }
531
532        // Extract sender and receiver BIC from headers for enhanced validation context
533        let (sender_country, receiver_country) = self.extract_country_codes_from_bics();
534
535        // Add enhanced message context including BIC-derived information
536        data_context.insert("message_context".to_string(), serde_json::json!({
537            "message_type": self.message_type,
538            "sender_country": sender_country,
539            "receiver_country": receiver_country,
540            "sender_bic": self.basic_header.logical_terminal,
541            "receiver_bic": &self.application_header.destination_address,
542            "message_priority": &self.application_header.priority,
543            "delivery_monitoring": self.application_header.delivery_monitoring.as_ref().unwrap_or(&"3".to_string()),
544        }));
545
546        Ok(serde_json::Value::Object(data_context))
547    }
548
549    /// Extract country codes from BIC codes in the headers
550    fn extract_country_codes_from_bics(&self) -> (String, String) {
551        // Extract sender country from basic header BIC (positions 4-5)
552        let sender_country = if self.basic_header.logical_terminal.len() >= 6 {
553            self.basic_header.logical_terminal[4..6].to_string()
554        } else {
555            "XX".to_string() // Unknown country
556        };
557
558        // Extract receiver country from application header destination BIC
559        let receiver_country = if self.application_header.destination_address.len() >= 6 {
560            self.application_header.destination_address[4..6].to_string()
561        } else {
562            "XX".to_string()
563        };
564
565        (sender_country, receiver_country)
566    }
567}