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 "191" => messages::MT191::validate(),
143 "200" => messages::MT200::validate(),
144 "202" => messages::MT202::validate(),
145 "204" => messages::MT204::validate(),
146 "205" => messages::MT205::validate(),
147 "210" => messages::MT210::validate(),
148 "290" => messages::MT290::validate(),
149 "291" => messages::MT291::validate(),
150 "900" => messages::MT900::validate(),
151 "910" => messages::MT910::validate(),
152 "920" => messages::MT920::validate(),
153 "935" => messages::MT935::validate(),
154 "940" => messages::MT940::validate(),
155 "941" => messages::MT941::validate(),
156 "942" => messages::MT942::validate(),
157 "950" => messages::MT950::validate(),
158 "192" => messages::MT192::validate(),
159 "196" => messages::MT196::validate(),
160 "292" => messages::MT292::validate(),
161 "296" => messages::MT296::validate(),
162 "199" => messages::MT199::validate(),
163 "299" => messages::MT299::validate(),
164 _ => {
165 return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
166 rule_name: "UNSUPPORTED_MESSAGE_TYPE".to_string(),
167 message: format!(
168 "No validation rules defined for message type {}",
169 T::message_type()
170 ),
171 });
172 }
173 };
174
175 let rules_json: serde_json::Value = match serde_json::from_str(validation_rules) {
177 Ok(json) => json,
178 Err(e) => {
179 return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
180 rule_name: "JSON_PARSE".to_string(),
181 message: format!("Failed to parse validation rules JSON: {e}"),
182 });
183 }
184 };
185
186 let rules = match rules_json.get("rules").and_then(|r| r.as_array()) {
188 Some(rules) => rules,
189 None => {
190 return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
191 rule_name: "RULES_FORMAT".to_string(),
192 message: "Validation rules must contain a 'rules' array".to_string(),
193 });
194 }
195 };
196
197 let constants = rules_json
199 .get("constants")
200 .and_then(|c| c.as_object())
201 .cloned()
202 .unwrap_or_default();
203
204 let context_value = match self.create_validation_context(&constants) {
206 Ok(context) => {
207 if std::env::var("TEST_DEBUG").is_ok()
209 && let Ok(context_str) = serde_json::to_string_pretty(&context)
210 {
211 eprintln!("\n=== VALIDATION CONTEXT for {} ===", T::message_type());
212 eprintln!("{}", context_str);
213 eprintln!("=== END VALIDATION CONTEXT ===\n");
214 }
215 context
216 }
217 Err(e) => {
218 return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
219 rule_name: "CONTEXT_CREATION".to_string(),
220 message: format!("Failed to create validation context: {e}"),
221 });
222 }
223 };
224
225 let mut errors = Vec::new();
227 let mut warnings = Vec::new();
228
229 for (rule_index, rule) in rules.iter().enumerate() {
230 let rule_id = rule
231 .get("id")
232 .and_then(|id| id.as_str())
233 .map(|s| s.to_string())
234 .unwrap_or_else(|| format!("RULE_{rule_index}"));
235
236 let rule_description = rule
237 .get("description")
238 .and_then(|desc| desc.as_str())
239 .unwrap_or("No description");
240
241 if let Some(condition) = rule.get("condition") {
242 let dl = datalogic_rs::DataLogic::new();
244 match dl.evaluate_json(condition, &context_value) {
245 Ok(result) => {
246 match result.as_bool() {
247 Some(true) => {
248 continue;
250 }
251 Some(false) => {
252 errors.push(ValidationError::BusinessRuleValidation {
254 rule_name: rule_id.clone(),
255 message: format!(
256 "Business rule validation failed: {rule_id} - {rule_description}"
257 ),
258 });
259 }
260 None => {
261 warnings.push(format!(
263 "Rule {rule_id} returned non-boolean value: {result:?}"
264 ));
265 }
266 }
267 }
268 Err(e) => {
269 errors.push(ValidationError::BusinessRuleValidation {
271 rule_name: rule_id.clone(),
272 message: format!("JSONLogic evaluation error for rule {rule_id}: {e}"),
273 });
274 }
275 }
276 } else {
277 warnings.push(format!("Rule {rule_id} has no condition"));
278 }
279 }
280
281 ValidationResult {
282 is_valid: errors.is_empty(),
283 errors,
284 warnings,
285 }
286 }
287
288 fn create_validation_context(
290 &self,
291 constants: &serde_json::Map<String, serde_json::Value>,
292 ) -> Result<serde_json::Value> {
293 let full_message_data = match serde_json::to_value(self) {
295 Ok(data) => data,
296 Err(e) => {
297 return Err(ParseError::SerializationError {
298 message: format!("Failed to serialize complete message: {e}"),
299 });
300 }
301 };
302
303 let mut data_context = serde_json::Map::new();
305
306 if let serde_json::Value::Object(msg_obj) = full_message_data {
308 for (key, value) in msg_obj {
309 data_context.insert(key, value);
310 }
311 }
312
313 for (key, value) in constants {
315 data_context.insert(key.clone(), value.clone());
316 }
317
318 let (sender_country, receiver_country) = self.extract_country_codes_from_bics();
320
321 data_context.insert("message_context".to_string(), serde_json::json!({
323 "message_type": self.message_type,
324 "sender_country": sender_country,
325 "receiver_country": receiver_country,
326 "sender_bic": self.basic_header.logical_terminal,
327 "receiver_bic": &self.application_header.destination_address,
328 "message_priority": &self.application_header.priority,
329 "delivery_monitoring": self.application_header.delivery_monitoring.as_ref().unwrap_or(&"3".to_string()),
330 }));
331
332 Ok(serde_json::Value::Object(data_context))
333 }
334
335 fn extract_country_codes_from_bics(&self) -> (String, String) {
337 let sender_country = if self.basic_header.logical_terminal.len() >= 6 {
339 self.basic_header.logical_terminal[4..6].to_string()
340 } else {
341 "XX".to_string() };
343
344 let receiver_country = if self.application_header.destination_address.len() >= 6 {
346 self.application_header.destination_address[4..6].to_string()
347 } else {
348 "XX".to_string()
349 };
350
351 (sender_country, receiver_country)
352 }
353
354 pub fn to_mt_message(&self) -> String {
355 let estimated_size = 200 + self.fields.to_fields().len() * 50;
358 let mut swift_message = String::with_capacity(estimated_size);
359
360 let block1 = &self.basic_header.to_string();
362 swift_message.push_str(&format!("{{1:{block1}}}\n"));
363
364 let block2 = &self.application_header.to_string();
366 swift_message.push_str(&format!("{{2:{block2}}}\n"));
367
368 if let Some(ref user_header) = self.user_header {
370 let block3 = &user_header.to_string();
371 swift_message.push_str(&format!("{{3:{block3}}}\n"));
372 }
373
374 let mut block4 = String::new();
376
377 let optional_fields: std::collections::HashSet<String> = T::optional_fields()
379 .into_iter()
380 .map(|s| s.to_string())
381 .collect();
382
383 let ordered_fields = self.fields.to_ordered_fields();
385
386 for (field_tag, field_value) in ordered_fields {
388 if optional_fields.contains(&field_tag) && field_value.trim().is_empty() {
390 continue;
391 }
392
393 if field_value.starts_with(':') {
396 block4.push_str(&format!("\n{field_value}"));
398 } else {
399 block4.push_str(&format!(
401 "\n:{}:{field_value}",
402 extract_base_tag(&field_tag)
403 ));
404 }
405 }
406
407 swift_message.push_str(&format!("{{4:{block4}\n-}}\n"));
408
409 if let Some(ref trailer) = self.trailer {
411 let block5 = &trailer.to_string();
412 swift_message.push_str(&format!("{{5:{block5}}}\n"));
413 }
414
415 swift_message
416 }
417}