swift_mt_message/
swift_message.rs

1//! # SwiftMessage
2//!
3//! Complete SWIFT message with headers (Blocks 1-3, 5) and typed message body (Block 4).
4
5use crate::{
6    ValidationError, ValidationResult,
7    headers::{ApplicationHeader, BasicHeader, Trailer, UserHeader},
8    traits::SwiftMessageBody,
9};
10use serde::{Deserialize, Serialize};
11use std::any::Any;
12
13/// Complete SWIFT message (headers + typed body)
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct SwiftMessage<T: SwiftMessageBody> {
16    /// Basic Header (Block 1)
17    pub basic_header: BasicHeader,
18
19    /// Application Header (Block 2)
20    pub application_header: ApplicationHeader,
21
22    /// User Header (Block 3) - Optional
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub user_header: Option<UserHeader>,
25
26    /// Trailer (Block 5) - Optional
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub trailer: Option<Trailer>,
29
30    /// Message type identifier
31    pub message_type: String,
32
33    /// Parsed message body with typed fields
34    pub fields: T,
35}
36
37impl<T: SwiftMessageBody> SwiftMessage<T> {
38    /// Check if message contains reject codes (REJT in field 20, block 3 MUR, or field 72)
39    pub fn has_reject_codes(&self) -> bool {
40        // Check Block 3 field 108 (MUR - Message User Reference)
41        if let Some(ref user_header) = self.user_header
42            && let Some(ref mur) = user_header.message_user_reference
43            && mur.to_uppercase().contains("REJT")
44        {
45            return true;
46        }
47
48        if let Some(mt103_fields) =
49            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT103>()
50        {
51            return mt103_fields.has_reject_codes();
52        } else if let Some(mt202_fields) =
53            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT202>()
54        {
55            return mt202_fields.has_reject_codes();
56        } else if let Some(mt205_fields) =
57            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT205>()
58        {
59            return mt205_fields.has_reject_codes();
60        }
61
62        false
63    }
64
65    /// Check if message contains return codes (RETN in field 20, block 3 MUR, or field 72)
66    pub fn has_return_codes(&self) -> bool {
67        // Check Block 3 field 108 (MUR - Message User Reference)
68        if let Some(ref user_header) = self.user_header
69            && let Some(ref mur) = user_header.message_user_reference
70            && mur.to_uppercase().contains("RETN")
71        {
72            return true;
73        }
74
75        if let Some(mt103_fields) =
76            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT103>()
77        {
78            return mt103_fields.has_return_codes();
79        } else if let Some(mt202_fields) =
80            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT202>()
81        {
82            return mt202_fields.has_return_codes();
83        } else if let Some(mt205_fields) =
84            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT205>()
85        {
86            return mt205_fields.has_return_codes();
87        }
88
89        false
90    }
91
92    pub fn is_cover_message(&self) -> bool {
93        if let Some(mt202_fields) =
94            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT202>()
95        {
96            return mt202_fields.is_cover_message();
97        }
98        if let Some(mt205_fields) =
99            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT205>()
100        {
101            return mt205_fields.is_cover_message();
102        }
103
104        false
105    }
106
107    pub fn is_stp_message(&self) -> bool {
108        if let Some(mt103_fields) =
109            (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT103>()
110        {
111            return mt103_fields.is_stp_compliant();
112        }
113
114        false
115    }
116
117    /// Validate message using SWIFT SR2025 network validation rules
118    pub fn validate(&self) -> ValidationResult {
119        // Use the new validate_network_rules method
120        let validation_errors = self.fields.validate_network_rules(false);
121
122        // Convert SwiftValidationError to ValidationError for backward compatibility
123        let errors: Vec<ValidationError> = validation_errors
124            .into_iter()
125            .map(|swift_error| {
126                let message = format!("{}", swift_error);
127                ValidationError::BusinessRuleValidation {
128                    rule_name: swift_error.error_code().to_string(),
129                    message,
130                }
131            })
132            .collect();
133
134        ValidationResult {
135            is_valid: errors.is_empty(),
136            errors,
137            warnings: Vec::new(),
138        }
139    }
140
141    pub fn to_mt_message(&self) -> String {
142        // Pre-allocate capacity based on typical message size
143        // Headers ~200 chars + typical message body ~2000 chars
144        let mut swift_message = String::with_capacity(2200);
145
146        // Block 1: Basic Header
147        let block1 = &self.basic_header.to_string();
148        swift_message.push_str(&format!("{{1:{block1}}}\n"));
149
150        // Block 2: Application Header
151        let block2 = &self.application_header.to_string();
152        swift_message.push_str(&format!("{{2:{block2}}}\n"));
153
154        // Block 3: User Header (if present)
155        if let Some(ref user_header) = self.user_header {
156            let block3 = &user_header.to_string();
157            swift_message.push_str(&format!("{{3:{block3}}}\n"));
158        }
159
160        // Block 4: Text Block with fields
161        // Use the message type's to_mt_string() implementation
162        let mut block4_content = self.fields.to_mt_string();
163
164        // Convert \r\n to \n for consistency with existing format
165        if block4_content.contains("\r\n") {
166            block4_content = block4_content.replace("\r\n", "\n");
167        }
168
169        // Add leading newline if content doesn't already have one
170        let block4 = if block4_content.starts_with('\n') {
171            block4_content
172        } else {
173            format!("\n{}", block4_content)
174        };
175
176        swift_message.push_str(&format!("{{4:{block4}\n-}}\n"));
177
178        // Block 5: Trailer (if present)
179        if let Some(ref trailer) = self.trailer {
180            let block5 = &trailer.to_string();
181            swift_message.push_str(&format!("{{5:{block5}}}\n"));
182        }
183
184        swift_message
185    }
186}