1use crate::{
4 errors::ParseError,
5 headers::{ApplicationHeader, BasicHeader, Trailer, UserHeader},
6 messages,
7 parser::extract_base_tag,
8 traits::SwiftMessageBody,
9 Result, ValidationError, ValidationResult,
10};
11use serde::{Deserialize, Serialize};
12use std::any::Any;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct SwiftMessage<T: SwiftMessageBody> {
17 pub basic_header: BasicHeader,
19
20 pub application_header: ApplicationHeader,
22
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub user_header: Option<UserHeader>,
26
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub trailer: Option<Trailer>,
30
31 pub message_type: String,
33
34 pub fields: T,
36}
37
38impl<T: SwiftMessageBody> SwiftMessage<T> {
39 pub fn has_reject_codes(&self) -> bool {
46 if let Some(ref user_header) = self.user_header {
48 if let Some(ref mur) = user_header.message_user_reference {
49 if mur.to_uppercase().contains("REJT") {
50 return true;
51 }
52 }
53 }
54
55 if let Some(mt103_fields) =
56 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT103>()
57 {
58 return mt103_fields.has_reject_codes();
59 } else if let Some(mt202_fields) =
60 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT202>()
61 {
62 return mt202_fields.has_reject_codes();
63 } else if let Some(mt205_fields) =
64 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT205>()
65 {
66 return mt205_fields.has_reject_codes();
67 }
68
69 false
70 }
71
72 pub fn has_return_codes(&self) -> bool {
79 if let Some(ref user_header) = self.user_header {
81 if let Some(ref mur) = user_header.message_user_reference {
82 if mur.to_uppercase().contains("RETN") {
83 return true;
84 }
85 }
86 }
87
88 if let Some(mt103_fields) =
89 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT103>()
90 {
91 return mt103_fields.has_return_codes();
92 } else if let Some(mt202_fields) =
93 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT202>()
94 {
95 return mt202_fields.has_return_codes();
96 } else if let Some(mt205_fields) =
97 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT205>()
98 {
99 return mt205_fields.has_return_codes();
100 }
101
102 false
103 }
104
105 pub fn is_cover_message(&self) -> bool {
106 if let Some(mt202_fields) =
107 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT202>()
108 {
109 return mt202_fields.is_cover_message();
110 }
111 if let Some(mt205_fields) =
112 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT205>()
113 {
114 return mt205_fields.is_cover_message();
115 }
116
117 false
118 }
119
120 pub fn is_stp_message(&self) -> bool {
121 if let Some(mt103_fields) =
122 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT103>()
123 {
124 return mt103_fields.is_stp_compliant();
125 }
126
127 false
128 }
129
130 pub fn validate(&self) -> ValidationResult {
134 let validation_rules = match T::message_type() {
136 "101" => messages::MT101::validate(),
137 "103" => messages::MT103::validate(),
138 "104" => messages::MT104::validate(),
139 "107" => messages::MT107::validate(),
140 "110" => messages::MT110::validate(),
141 "111" => messages::MT111::validate(),
142 "112" => messages::MT112::validate(),
143 "202" => messages::MT202::validate(),
144 "205" => messages::MT205::validate(),
145 "210" => messages::MT210::validate(),
146 "900" => messages::MT900::validate(),
147 "910" => messages::MT910::validate(),
148 "920" => messages::MT920::validate(),
149 "935" => messages::MT935::validate(),
150 "940" => messages::MT940::validate(),
151 "941" => messages::MT941::validate(),
152 "942" => messages::MT942::validate(),
153 "950" => messages::MT950::validate(),
154 "192" => messages::MT192::validate(),
155 "196" => messages::MT196::validate(),
156 "292" => messages::MT292::validate(),
157 "296" => messages::MT296::validate(),
158 "199" => messages::MT199::validate(),
159 "299" => messages::MT299::validate(),
160 _ => {
161 return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
162 rule_name: "UNSUPPORTED_MESSAGE_TYPE".to_string(),
163 message: format!(
164 "No validation rules defined for message type {}",
165 T::message_type()
166 ),
167 });
168 }
169 };
170
171 let rules_json: serde_json::Value = match serde_json::from_str(validation_rules) {
173 Ok(json) => json,
174 Err(e) => {
175 return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
176 rule_name: "JSON_PARSE".to_string(),
177 message: format!("Failed to parse validation rules JSON: {e}"),
178 });
179 }
180 };
181
182 let rules = match rules_json.get("rules").and_then(|r| r.as_array()) {
184 Some(rules) => rules,
185 None => {
186 return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
187 rule_name: "RULES_FORMAT".to_string(),
188 message: "Validation rules must contain a 'rules' array".to_string(),
189 });
190 }
191 };
192
193 let constants = rules_json
195 .get("constants")
196 .and_then(|c| c.as_object())
197 .cloned()
198 .unwrap_or_default();
199
200 let context_value = match self.create_validation_context(&constants) {
202 Ok(context) => context,
203 Err(e) => {
204 return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
205 rule_name: "CONTEXT_CREATION".to_string(),
206 message: format!("Failed to create validation context: {e}"),
207 });
208 }
209 };
210
211 let mut errors = Vec::new();
213 let mut warnings = Vec::new();
214
215 for (rule_index, rule) in rules.iter().enumerate() {
216 let rule_id = rule
217 .get("id")
218 .and_then(|id| id.as_str())
219 .map(|s| s.to_string())
220 .unwrap_or_else(|| format!("RULE_{rule_index}"));
221
222 let rule_description = rule
223 .get("description")
224 .and_then(|desc| desc.as_str())
225 .unwrap_or("No description");
226
227 if let Some(condition) = rule.get("condition") {
228 let dl = datalogic_rs::DataLogic::new();
230 match dl.evaluate_json(condition, &context_value, None) {
231 Ok(result) => {
232 match result.as_bool() {
233 Some(true) => {
234 continue;
236 }
237 Some(false) => {
238 errors.push(ValidationError::BusinessRuleValidation {
240 rule_name: rule_id.clone(),
241 message: format!(
242 "Business rule validation failed: {rule_id} - {rule_description}"
243 ),
244 });
245 }
246 None => {
247 warnings.push(format!(
249 "Rule {rule_id} returned non-boolean value: {result:?}"
250 ));
251 }
252 }
253 }
254 Err(e) => {
255 errors.push(ValidationError::BusinessRuleValidation {
257 rule_name: rule_id.clone(),
258 message: format!("JSONLogic evaluation error for rule {rule_id}: {e}"),
259 });
260 }
261 }
262 } else {
263 warnings.push(format!("Rule {rule_id} has no condition"));
264 }
265 }
266
267 ValidationResult {
268 is_valid: errors.is_empty(),
269 errors,
270 warnings,
271 }
272 }
273
274 fn create_validation_context(
276 &self,
277 constants: &serde_json::Map<String, serde_json::Value>,
278 ) -> Result<serde_json::Value> {
279 let full_message_data = match serde_json::to_value(self) {
281 Ok(data) => data,
282 Err(e) => {
283 return Err(ParseError::SerializationError {
284 message: format!("Failed to serialize complete message: {e}"),
285 });
286 }
287 };
288
289 let mut data_context = serde_json::Map::new();
291
292 if let serde_json::Value::Object(msg_obj) = full_message_data {
294 for (key, value) in msg_obj {
295 data_context.insert(key, value);
296 }
297 }
298
299 for (key, value) in constants {
301 data_context.insert(key.clone(), value.clone());
302 }
303
304 let (sender_country, receiver_country) = self.extract_country_codes_from_bics();
306
307 data_context.insert("message_context".to_string(), serde_json::json!({
309 "message_type": self.message_type,
310 "sender_country": sender_country,
311 "receiver_country": receiver_country,
312 "sender_bic": self.basic_header.logical_terminal,
313 "receiver_bic": &self.application_header.destination_address,
314 "message_priority": &self.application_header.priority,
315 "delivery_monitoring": self.application_header.delivery_monitoring.as_ref().unwrap_or(&"3".to_string()),
316 }));
317
318 Ok(serde_json::Value::Object(data_context))
319 }
320
321 fn extract_country_codes_from_bics(&self) -> (String, String) {
323 let sender_country = if self.basic_header.logical_terminal.len() >= 6 {
325 self.basic_header.logical_terminal[4..6].to_string()
326 } else {
327 "XX".to_string() };
329
330 let receiver_country = if self.application_header.destination_address.len() >= 6 {
332 self.application_header.destination_address[4..6].to_string()
333 } else {
334 "XX".to_string()
335 };
336
337 (sender_country, receiver_country)
338 }
339
340 pub fn to_mt_message(&self) -> String {
341 let estimated_size = 200 + self.fields.to_fields().len() * 50;
344 let mut swift_message = String::with_capacity(estimated_size);
345
346 let block1 = &self.basic_header.to_string();
348 swift_message.push_str(&format!("{{1:{block1}}}\n"));
349
350 let block2 = &self.application_header.to_string();
352 swift_message.push_str(&format!("{{2:{block2}}}\n"));
353
354 if let Some(ref user_header) = self.user_header {
356 let block3 = &user_header.to_string();
357 swift_message.push_str(&format!("{{3:{block3}}}\n"));
358 }
359
360 let mut block4 = String::new();
362
363 let optional_fields: std::collections::HashSet<String> = T::optional_fields()
365 .into_iter()
366 .map(|s| s.to_string())
367 .collect();
368
369 let ordered_fields = self.fields.to_ordered_fields();
371
372 for (field_tag, field_value) in ordered_fields {
374 if optional_fields.contains(&field_tag) && field_value.trim().is_empty() {
376 continue;
377 }
378
379 if field_value.starts_with(':') {
382 block4.push_str(&format!("\n{field_value}"));
384 } else {
385 block4.push_str(&format!(
387 "\n:{}:{field_value}",
388 extract_base_tag(&field_tag)
389 ));
390 }
391 }
392
393 swift_message.push_str(&format!("{{4:{block4}\n-}}\n"));
394
395 if let Some(ref trailer) = self.trailer {
397 let block5 = &trailer.to_string();
398 swift_message.push_str(&format!("{{5:{block5}}}\n"));
399 }
400
401 swift_message
402 }
403}