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