Skip to main content

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