1use crate::{
4 Result, ValidationError, ValidationResult,
5 errors::ParseError,
6 headers::{ApplicationHeader, BasicHeader, Trailer, UserHeader},
7 messages,
8 parser::extract_base_tag,
9 traits::SwiftMessageBody,
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 && let Some(ref mur) = user_header.message_user_reference
49 && mur.to_uppercase().contains("REJT")
50 {
51 return true;
52 }
53
54 if let Some(mt103_fields) =
55 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT103>()
56 {
57 return mt103_fields.has_reject_codes();
58 } else if let Some(mt202_fields) =
59 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT202>()
60 {
61 return mt202_fields.has_reject_codes();
62 } else if let Some(mt205_fields) =
63 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT205>()
64 {
65 return mt205_fields.has_reject_codes();
66 }
67
68 false
69 }
70
71 pub fn has_return_codes(&self) -> bool {
78 if let Some(ref user_header) = self.user_header
80 && let Some(ref mur) = user_header.message_user_reference
81 && mur.to_uppercase().contains("RETN")
82 {
83 return true;
84 }
85
86 if let Some(mt103_fields) =
87 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT103>()
88 {
89 return mt103_fields.has_return_codes();
90 } else if let Some(mt202_fields) =
91 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT202>()
92 {
93 return mt202_fields.has_return_codes();
94 } else if let Some(mt205_fields) =
95 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT205>()
96 {
97 return mt205_fields.has_return_codes();
98 }
99
100 false
101 }
102
103 pub fn is_cover_message(&self) -> bool {
104 if let Some(mt202_fields) =
105 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT202>()
106 {
107 return mt202_fields.is_cover_message();
108 }
109 if let Some(mt205_fields) =
110 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT205>()
111 {
112 return mt205_fields.is_cover_message();
113 }
114
115 false
116 }
117
118 pub fn is_stp_message(&self) -> bool {
119 if let Some(mt103_fields) =
120 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT103>()
121 {
122 return mt103_fields.is_stp_compliant();
123 }
124
125 false
126 }
127
128 pub fn validate(&self) -> ValidationResult {
132 let validation_rules = match T::message_type() {
134 "101" => messages::MT101::validate(),
135 "103" => messages::MT103::validate(),
136 "104" => messages::MT104::validate(),
137 "107" => messages::MT107::validate(),
138 "110" => messages::MT110::validate(),
139 "111" => messages::MT111::validate(),
140 "112" => messages::MT112::validate(),
141 "190" => messages::MT190::validate(),
142 "200" => messages::MT200::validate(),
143 "202" => messages::MT202::validate(),
144 "204" => messages::MT204::validate(),
145 "205" => messages::MT205::validate(),
146 "210" => messages::MT210::validate(),
147 "290" => messages::MT290::validate(),
148 "900" => messages::MT900::validate(),
149 "910" => messages::MT910::validate(),
150 "920" => messages::MT920::validate(),
151 "935" => messages::MT935::validate(),
152 "940" => messages::MT940::validate(),
153 "941" => messages::MT941::validate(),
154 "942" => messages::MT942::validate(),
155 "950" => messages::MT950::validate(),
156 "192" => messages::MT192::validate(),
157 "196" => messages::MT196::validate(),
158 "292" => messages::MT292::validate(),
159 "296" => messages::MT296::validate(),
160 "199" => messages::MT199::validate(),
161 "299" => messages::MT299::validate(),
162 _ => {
163 return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
164 rule_name: "UNSUPPORTED_MESSAGE_TYPE".to_string(),
165 message: format!(
166 "No validation rules defined for message type {}",
167 T::message_type()
168 ),
169 });
170 }
171 };
172
173 let rules_json: serde_json::Value = match serde_json::from_str(validation_rules) {
175 Ok(json) => json,
176 Err(e) => {
177 return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
178 rule_name: "JSON_PARSE".to_string(),
179 message: format!("Failed to parse validation rules JSON: {e}"),
180 });
181 }
182 };
183
184 let rules = match rules_json.get("rules").and_then(|r| r.as_array()) {
186 Some(rules) => rules,
187 None => {
188 return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
189 rule_name: "RULES_FORMAT".to_string(),
190 message: "Validation rules must contain a 'rules' array".to_string(),
191 });
192 }
193 };
194
195 let constants = rules_json
197 .get("constants")
198 .and_then(|c| c.as_object())
199 .cloned()
200 .unwrap_or_default();
201
202 let context_value = match self.create_validation_context(&constants) {
204 Ok(context) => {
205 if std::env::var("TEST_DEBUG").is_ok()
207 && let Ok(context_str) = serde_json::to_string_pretty(&context)
208 {
209 eprintln!("\n=== VALIDATION CONTEXT for {} ===", T::message_type());
210 eprintln!("{}", context_str);
211 eprintln!("=== END VALIDATION CONTEXT ===\n");
212 }
213 context
214 }
215 Err(e) => {
216 return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
217 rule_name: "CONTEXT_CREATION".to_string(),
218 message: format!("Failed to create validation context: {e}"),
219 });
220 }
221 };
222
223 let mut errors = Vec::new();
225 let mut warnings = Vec::new();
226
227 for (rule_index, rule) in rules.iter().enumerate() {
228 let rule_id = rule
229 .get("id")
230 .and_then(|id| id.as_str())
231 .map(|s| s.to_string())
232 .unwrap_or_else(|| format!("RULE_{rule_index}"));
233
234 let rule_description = rule
235 .get("description")
236 .and_then(|desc| desc.as_str())
237 .unwrap_or("No description");
238
239 if let Some(condition) = rule.get("condition") {
240 let dl = datalogic_rs::DataLogic::new();
242 match dl.evaluate_json(condition, &context_value, None) {
243 Ok(result) => {
244 match result.as_bool() {
245 Some(true) => {
246 continue;
248 }
249 Some(false) => {
250 errors.push(ValidationError::BusinessRuleValidation {
252 rule_name: rule_id.clone(),
253 message: format!(
254 "Business rule validation failed: {rule_id} - {rule_description}"
255 ),
256 });
257 }
258 None => {
259 warnings.push(format!(
261 "Rule {rule_id} returned non-boolean value: {result:?}"
262 ));
263 }
264 }
265 }
266 Err(e) => {
267 errors.push(ValidationError::BusinessRuleValidation {
269 rule_name: rule_id.clone(),
270 message: format!("JSONLogic evaluation error for rule {rule_id}: {e}"),
271 });
272 }
273 }
274 } else {
275 warnings.push(format!("Rule {rule_id} has no condition"));
276 }
277 }
278
279 ValidationResult {
280 is_valid: errors.is_empty(),
281 errors,
282 warnings,
283 }
284 }
285
286 fn create_validation_context(
288 &self,
289 constants: &serde_json::Map<String, serde_json::Value>,
290 ) -> Result<serde_json::Value> {
291 let full_message_data = match serde_json::to_value(self) {
293 Ok(data) => data,
294 Err(e) => {
295 return Err(ParseError::SerializationError {
296 message: format!("Failed to serialize complete message: {e}"),
297 });
298 }
299 };
300
301 let mut data_context = serde_json::Map::new();
303
304 if let serde_json::Value::Object(msg_obj) = full_message_data {
306 for (key, value) in msg_obj {
307 data_context.insert(key, value);
308 }
309 }
310
311 for (key, value) in constants {
313 data_context.insert(key.clone(), value.clone());
314 }
315
316 let (sender_country, receiver_country) = self.extract_country_codes_from_bics();
318
319 data_context.insert("message_context".to_string(), serde_json::json!({
321 "message_type": self.message_type,
322 "sender_country": sender_country,
323 "receiver_country": receiver_country,
324 "sender_bic": self.basic_header.logical_terminal,
325 "receiver_bic": &self.application_header.destination_address,
326 "message_priority": &self.application_header.priority,
327 "delivery_monitoring": self.application_header.delivery_monitoring.as_ref().unwrap_or(&"3".to_string()),
328 }));
329
330 Ok(serde_json::Value::Object(data_context))
331 }
332
333 fn extract_country_codes_from_bics(&self) -> (String, String) {
335 let sender_country = if self.basic_header.logical_terminal.len() >= 6 {
337 self.basic_header.logical_terminal[4..6].to_string()
338 } else {
339 "XX".to_string() };
341
342 let receiver_country = if self.application_header.destination_address.len() >= 6 {
344 self.application_header.destination_address[4..6].to_string()
345 } else {
346 "XX".to_string()
347 };
348
349 (sender_country, receiver_country)
350 }
351
352 pub fn to_mt_message(&self) -> String {
353 let estimated_size = 200 + self.fields.to_fields().len() * 50;
356 let mut swift_message = String::with_capacity(estimated_size);
357
358 let block1 = &self.basic_header.to_string();
360 swift_message.push_str(&format!("{{1:{block1}}}\n"));
361
362 let block2 = &self.application_header.to_string();
364 swift_message.push_str(&format!("{{2:{block2}}}\n"));
365
366 if let Some(ref user_header) = self.user_header {
368 let block3 = &user_header.to_string();
369 swift_message.push_str(&format!("{{3:{block3}}}\n"));
370 }
371
372 let mut block4 = String::new();
374
375 let optional_fields: std::collections::HashSet<String> = T::optional_fields()
377 .into_iter()
378 .map(|s| s.to_string())
379 .collect();
380
381 let ordered_fields = self.fields.to_ordered_fields();
383
384 for (field_tag, field_value) in ordered_fields {
386 if optional_fields.contains(&field_tag) && field_value.trim().is_empty() {
388 continue;
389 }
390
391 if field_value.starts_with(':') {
394 block4.push_str(&format!("\n{field_value}"));
396 } else {
397 block4.push_str(&format!(
399 "\n:{}:{field_value}",
400 extract_base_tag(&field_tag)
401 ));
402 }
403 }
404
405 swift_message.push_str(&format!("{{4:{block4}\n-}}\n"));
406
407 if let Some(ref trailer) = self.trailer {
409 let block5 = &trailer.to_string();
410 swift_message.push_str(&format!("{{5:{block5}}}\n"));
411 }
412
413 swift_message
414 }
415}