1use serde::{Deserialize, Serialize};
48use std::any::Any;
49use std::collections::HashMap;
50use std::fmt::Debug;
51
52pub mod errors;
53pub mod fields;
54pub mod headers;
55pub mod messages;
56pub mod parser;
57
58pub use errors::{ParseError, Result, ValidationError};
60pub use headers::{ApplicationHeader, BasicHeader, Trailer, UserHeader};
61pub use parser::SwiftParser;
62
63pub use swift_mt_message_macros::{SwiftField, SwiftMessage, field, serde_swift_fields};
65
66pub type SwiftResult<T> = std::result::Result<T, crate::errors::ParseError>;
68
69pub trait SwiftField: Serialize + for<'de> Deserialize<'de> + Clone + std::fmt::Debug {
71 fn parse(value: &str) -> Result<Self>
73 where
74 Self: Sized;
75
76 fn to_swift_string(&self) -> String;
78
79 fn validate(&self) -> ValidationResult;
81
82 fn format_spec() -> &'static str;
84}
85
86pub trait SwiftMessageBody: Debug + Clone + Send + Sync + Serialize + std::any::Any {
88 fn message_type() -> &'static str;
90
91 fn from_fields(fields: HashMap<String, Vec<String>>) -> SwiftResult<Self>
93 where
94 Self: Sized;
95
96 fn to_fields(&self) -> HashMap<String, Vec<String>>;
98
99 fn required_fields() -> Vec<&'static str>;
101
102 fn optional_fields() -> Vec<&'static str>;
104}
105
106#[derive(Debug, Clone, Serialize)]
108pub struct SwiftMessage<T: SwiftMessageBody> {
109 pub basic_header: BasicHeader,
111
112 pub application_header: ApplicationHeader,
114
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub user_header: Option<UserHeader>,
118
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub trailer: Option<Trailer>,
122
123 pub blocks: RawBlocks,
125
126 pub message_type: String,
128
129 pub field_order: Vec<String>,
131
132 pub fields: T,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize, Default)]
138pub struct RawBlocks {
139 #[serde(skip_serializing_if = "Option::is_none")]
140 pub block1: Option<String>,
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub block2: Option<String>,
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub block3: Option<String>,
145 pub block4: String,
146 #[serde(skip_serializing_if = "Option::is_none")]
147 pub block5: Option<String>,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct ValidationResult {
153 pub is_valid: bool,
154 pub errors: Vec<ValidationError>,
155 pub warnings: Vec<String>,
156}
157
158impl ValidationResult {
159 pub fn valid() -> Self {
160 Self {
161 is_valid: true,
162 errors: Vec::new(),
163 warnings: Vec::new(),
164 }
165 }
166
167 pub fn with_error(error: ValidationError) -> Self {
168 Self {
169 is_valid: false,
170 errors: vec![error],
171 warnings: Vec::new(),
172 }
173 }
174
175 pub fn with_errors(errors: Vec<ValidationError>) -> Self {
176 Self {
177 is_valid: errors.is_empty(),
178 errors,
179 warnings: Vec::new(),
180 }
181 }
182}
183
184#[derive(Debug, Clone, Serialize)]
186#[serde(tag = "message_type")]
187pub enum ParsedSwiftMessage {
188 #[serde(rename = "103")]
189 MT103(Box<SwiftMessage<messages::MT103>>),
190 #[serde(rename = "202")]
191 MT202(Box<SwiftMessage<messages::MT202>>),
192 #[serde(rename = "205")]
193 MT205(Box<SwiftMessage<messages::MT205>>),
194 #[serde(rename = "900")]
195 MT900(Box<SwiftMessage<messages::MT900>>),
196}
197
198impl ParsedSwiftMessage {
199 pub fn message_type(&self) -> &'static str {
201 match self {
202 ParsedSwiftMessage::MT103(_) => "103",
203 ParsedSwiftMessage::MT202(_) => "202",
204 ParsedSwiftMessage::MT205(_) => "205",
205 ParsedSwiftMessage::MT900(_) => "900",
206 }
207 }
208
209 pub fn as_mt103(&self) -> Option<&SwiftMessage<messages::MT103>> {
211 match self {
212 ParsedSwiftMessage::MT103(msg) => Some(msg),
213 _ => None,
214 }
215 }
216
217 pub fn as_mt202(&self) -> Option<&SwiftMessage<messages::MT202>> {
218 match self {
219 ParsedSwiftMessage::MT202(msg) => Some(msg),
220 _ => None,
221 }
222 }
223
224 pub fn as_mt205(&self) -> Option<&SwiftMessage<messages::MT205>> {
225 match self {
226 ParsedSwiftMessage::MT205(msg) => Some(msg),
227 _ => None,
228 }
229 }
230
231 pub fn as_mt900(&self) -> Option<&SwiftMessage<messages::MT900>> {
232 match self {
233 ParsedSwiftMessage::MT900(msg) => Some(msg),
234 _ => None,
235 }
236 }
237
238 pub fn into_mt103(self) -> Option<SwiftMessage<messages::MT103>> {
240 match self {
241 ParsedSwiftMessage::MT103(msg) => Some(*msg),
242 _ => None,
243 }
244 }
245
246 pub fn into_mt202(self) -> Option<SwiftMessage<messages::MT202>> {
247 match self {
248 ParsedSwiftMessage::MT202(msg) => Some(*msg),
249 _ => None,
250 }
251 }
252
253 pub fn into_mt205(self) -> Option<SwiftMessage<messages::MT205>> {
254 match self {
255 ParsedSwiftMessage::MT205(msg) => Some(*msg),
256 _ => None,
257 }
258 }
259
260 pub fn into_mt900(self) -> Option<SwiftMessage<messages::MT900>> {
261 match self {
262 ParsedSwiftMessage::MT900(msg) => Some(*msg),
263 _ => None,
264 }
265 }
266}
267
268impl<T: SwiftMessageBody> SwiftMessage<T> {
269 pub fn has_reject_codes(&self) -> bool {
276 if let Some(ref user_header) = self.user_header {
278 if let Some(ref mur) = user_header.message_user_reference {
279 if mur.to_uppercase().contains("REJT") {
280 return true;
281 }
282 }
283 }
284
285 if let Some(mt103_fields) =
286 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT103>()
287 {
288 return mt103_fields.has_reject_codes();
289 } else if let Some(mt202_fields) =
290 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT202>()
291 {
292 return mt202_fields.has_reject_codes();
293 } else if let Some(mt205_fields) =
294 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT205>()
295 {
296 return mt205_fields.has_reject_codes();
297 }
298
299 false
300 }
301
302 pub fn has_return_codes(&self) -> bool {
309 if let Some(ref user_header) = self.user_header {
311 if let Some(ref mur) = user_header.message_user_reference {
312 if mur.to_uppercase().contains("RETN") {
313 return true;
314 }
315 }
316 }
317
318 if let Some(mt103_fields) =
319 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT103>()
320 {
321 return mt103_fields.has_return_codes();
322 } else if let Some(mt202_fields) =
323 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT202>()
324 {
325 return mt202_fields.has_return_codes();
326 } else if let Some(mt205_fields) =
327 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT205>()
328 {
329 return mt205_fields.has_return_codes();
330 }
331
332 false
333 }
334
335 pub fn is_cover_message(&self) -> bool {
336 if let Some(mt202_fields) =
337 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT202>()
338 {
339 return mt202_fields.is_cover_message();
340 }
341 if let Some(mt205_fields) =
342 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT205>()
343 {
344 return mt205_fields.is_cover_message();
345 }
346
347 false
348 }
349
350 pub fn is_stp_message(&self) -> bool {
351 if let Some(mt103_fields) =
352 (&self.fields as &dyn Any).downcast_ref::<crate::messages::MT103>()
353 {
354 return mt103_fields.is_stp_compliant();
355 }
356
357 false
358 }
359
360 pub fn validate_business_rules(&self) -> ValidationResult {
364 let validation_rules = match T::message_type() {
366 "103" => messages::MT103::validation_rules(),
367 "202" => messages::MT202::validation_rules(),
368 "205" => messages::MT205::validation_rules(),
369 "104" => messages::MT104::validation_rules(),
370 "107" => messages::MT107::validation_rules(),
371 "110" => messages::MT110::validation_rules(),
372 "111" => messages::MT111::validation_rules(),
373 "112" => messages::MT112::validation_rules(),
374 "210" => messages::MT210::validation_rules(),
375 "900" => messages::MT900::validation_rules(),
376 "910" => messages::MT910::validation_rules(),
377 "920" => messages::MT920::validation_rules(),
378 "935" => messages::MT935::validation_rules(),
379 "940" => messages::MT940::validation_rules(),
380 "941" => messages::MT941::validation_rules(),
381 "942" => messages::MT942::validation_rules(),
382 "950" => messages::MT950::validation_rules(),
383 _ => {
384 return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
385 rule_name: "UNSUPPORTED_MESSAGE_TYPE".to_string(),
386 message: format!(
387 "No validation rules defined for message type {}",
388 T::message_type()
389 ),
390 });
391 }
392 };
393
394 let rules_json: serde_json::Value = match serde_json::from_str(validation_rules) {
396 Ok(json) => json,
397 Err(e) => {
398 return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
399 rule_name: "JSON_PARSE".to_string(),
400 message: format!("Failed to parse validation rules JSON: {}", e),
401 });
402 }
403 };
404
405 let rules = match rules_json.get("rules").and_then(|r| r.as_array()) {
407 Some(rules) => rules,
408 None => {
409 return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
410 rule_name: "RULES_FORMAT".to_string(),
411 message: "Validation rules must contain a 'rules' array".to_string(),
412 });
413 }
414 };
415
416 let constants = rules_json
418 .get("constants")
419 .and_then(|c| c.as_object())
420 .cloned()
421 .unwrap_or_default();
422
423 let context_value = match self.create_validation_context(&constants) {
425 Ok(context) => context,
426 Err(e) => {
427 return ValidationResult::with_error(ValidationError::BusinessRuleValidation {
428 rule_name: "CONTEXT_CREATION".to_string(),
429 message: format!("Failed to create validation context: {}", e),
430 });
431 }
432 };
433
434 let mut errors = Vec::new();
436 let mut warnings = Vec::new();
437
438 for (rule_index, rule) in rules.iter().enumerate() {
439 let rule_id = rule
440 .get("id")
441 .and_then(|id| id.as_str())
442 .map(|s| s.to_string())
443 .unwrap_or_else(|| format!("RULE_{}", rule_index));
444
445 let rule_description = rule
446 .get("description")
447 .and_then(|desc| desc.as_str())
448 .unwrap_or("No description");
449
450 if let Some(condition) = rule.get("condition") {
451 let dl = datalogic_rs::DataLogic::new();
453 match dl.evaluate_json(condition, &context_value, None) {
454 Ok(result) => {
455 match result.as_bool() {
456 Some(true) => {
457 continue;
459 }
460 Some(false) => {
461 errors.push(ValidationError::BusinessRuleValidation {
463 rule_name: rule_id.clone(),
464 message: format!(
465 "Business rule validation failed: {} - {}",
466 rule_id, rule_description
467 ),
468 });
469 }
470 None => {
471 warnings.push(format!(
473 "Rule {} returned non-boolean value: {:?}",
474 rule_id, result
475 ));
476 }
477 }
478 }
479 Err(e) => {
480 errors.push(ValidationError::BusinessRuleValidation {
482 rule_name: rule_id.clone(),
483 message: format!(
484 "JSONLogic evaluation error for rule {}: {}",
485 rule_id, e
486 ),
487 });
488 }
489 }
490 } else {
491 warnings.push(format!("Rule {} has no condition", rule_id));
492 }
493 }
494
495 ValidationResult {
496 is_valid: errors.is_empty(),
497 errors,
498 warnings,
499 }
500 }
501
502 fn create_validation_context(
504 &self,
505 constants: &serde_json::Map<String, serde_json::Value>,
506 ) -> Result<serde_json::Value> {
507 let full_message_data = match serde_json::to_value(self) {
509 Ok(data) => data,
510 Err(e) => {
511 return Err(ParseError::SerializationError {
512 message: format!("Failed to serialize complete message: {}", e),
513 });
514 }
515 };
516
517 let mut data_context = serde_json::Map::new();
519
520 if let serde_json::Value::Object(msg_obj) = full_message_data {
522 for (key, value) in msg_obj {
523 data_context.insert(key, value);
524 }
525 }
526
527 for (key, value) in constants {
529 data_context.insert(key.clone(), value.clone());
530 }
531
532 let (sender_country, receiver_country) = self.extract_country_codes_from_bics();
534
535 data_context.insert("message_context".to_string(), serde_json::json!({
537 "message_type": self.message_type,
538 "sender_country": sender_country,
539 "receiver_country": receiver_country,
540 "sender_bic": self.basic_header.logical_terminal,
541 "receiver_bic": &self.application_header.destination_address,
542 "message_priority": &self.application_header.priority,
543 "delivery_monitoring": self.application_header.delivery_monitoring.as_ref().unwrap_or(&"3".to_string()),
544 }));
545
546 Ok(serde_json::Value::Object(data_context))
547 }
548
549 fn extract_country_codes_from_bics(&self) -> (String, String) {
551 let sender_country = if self.basic_header.logical_terminal.len() >= 6 {
553 self.basic_header.logical_terminal[4..6].to_string()
554 } else {
555 "XX".to_string() };
557
558 let receiver_country = if self.application_header.destination_address.len() >= 6 {
560 self.application_header.destination_address[4..6].to_string()
561 } else {
562 "XX".to_string()
563 };
564
565 (sender_country, receiver_country)
566 }
567}