swift_mt_message/
lib.rs

1//! # Swift MT Message Parser - Enhanced Architecture
2//!
3//! A comprehensive Rust library for parsing SWIFT MT (Message Type) messages with strong typing,
4//! complex field structures, comprehensive validation, and flattened JSON serialization.
5//!
6//! ## Key Features
7//!
8//! - **Complex Field Structures**: Full enum-based field variants (Field50: A/F/K, Field59: A/Basic)
9//! - **Flattened JSON Serialization**: Clean JSON output without enum wrapper layers
10//! - **Type-safe field parsing** with dedicated field structs and automatic validation
11//! - **Comprehensive Field Support**: All MT103 fields with proper SWIFT compliance
12//! - **Bidirectional Serialization**: Perfect round-trip JSON serialization/deserialization
13//! - **Extensive Validation**: BIC validation, field length checks, format compliance
14//!
15//! ## Supported Field Types
16//!
17//! ### Complex Enum Fields
18//! - **Field50** (Ordering Customer): 50A (Account+BIC), 50F (Party+Address), 50K (Name+Address)
19//! - **Field59** (Beneficiary Customer): 59A (Account+BIC), 59 (Basic lines)
20//!
21//! ### Institution Fields (with account_line_indicator)
22//! - **Field52A** (Ordering Institution): BIC + optional account + account_line_indicator
23//! - **Field53A-57A** (Correspondent/Intermediary): All with account_line_indicator support
24//!
25//! ### Simple Type Fields
26//! - **Field32A** (Value Date/Currency/Amount): NaiveDate + String + f64
27//! - **Field20, 23B, 70, 71A**: Proper field name alignment with old version
28//!
29//! ## Example Usage
30//!
31//! ```rust
32//! use swift_mt_message::{SwiftParser, SwiftMessage, messages::MT103};
33//!
34//! let raw_mt103 = "{1:F01BANKDEFFAXXX0123456789}{2:I103BANKDEFFAXXXU3003}{4:\n:20:FT21234567890\n:23B:CRED\n:32A:210315EUR1234567,89\n:50K:JOHN DOE\n:52A:BANKDEFF\n:57A:DEUTDEFF\n:59A:/DE89370400440532013000\nDEUTDEFF\n:70:PAYMENT\n:71A:OUR\n-}";
35//! let parsed: SwiftMessage<MT103> = SwiftParser::parse(raw_mt103)?;
36//! let json_output = serde_json::to_string_pretty(&parsed)?;
37//! # Ok::<(), Box<dyn std::error::Error>>(())
38//! ```
39//!
40//! ## JSON Output Structure
41//!
42//! The library produces clean, flattened JSON without enum wrapper layers:
43//!
44//! ```json
45//! {
46//!   "50": {
47//!     "name_and_address": ["JOHN DOE", "123 MAIN ST"]
48//!   },
49//!   "59": {
50//!     "account": "DE89370400440532013000",
51//!     "bic": "DEUTDEFFXXX"
52//!   }
53//! }
54//! ```
55//!
56//! Instead of nested enum structures like `{"50": {"K": {...}}}`.
57
58use serde::{Deserialize, Serialize};
59use std::collections::HashMap;
60use std::fmt::Debug;
61
62pub mod errors;
63pub mod fields;
64pub mod headers;
65pub mod messages;
66pub mod parser;
67
68// Re-export core types
69pub use errors::{ParseError, Result, ValidationError};
70pub use headers::{ApplicationHeader, BasicHeader, Trailer, UserHeader};
71pub use parser::SwiftParser;
72
73// Re-export derive macros
74pub use swift_mt_message_macros::{SwiftField, SwiftMessage, swift_serde};
75
76/// Simplified result type for SWIFT operations
77pub type SwiftResult<T> = std::result::Result<T, crate::errors::ParseError>;
78
79/// Core trait for all Swift field types
80pub trait SwiftField: Serialize + for<'de> Deserialize<'de> + Clone + std::fmt::Debug {
81    /// Parse field value from string representation
82    fn parse(value: &str) -> Result<Self>
83    where
84        Self: Sized;
85
86    /// Convert field back to SWIFT string format
87    fn to_swift_string(&self) -> String;
88
89    /// Validate field according to SWIFT format rules
90    fn validate(&self) -> ValidationResult;
91
92    /// Get field format specification
93    fn format_spec() -> &'static str;
94}
95
96/// Core trait for Swift message types
97pub trait SwiftMessageBody: Debug + Clone + Send + Sync + Serialize {
98    /// Get the message type identifier (e.g., "103", "202")
99    fn message_type() -> &'static str;
100
101    /// Create from field map
102    fn from_fields(fields: HashMap<String, Vec<String>>) -> SwiftResult<Self>
103    where
104        Self: Sized;
105
106    /// Convert to field map
107    fn to_fields(&self) -> HashMap<String, Vec<String>>;
108
109    /// Get required field tags for this message type
110    fn required_fields() -> Vec<&'static str>;
111
112    /// Get optional field tags for this message type
113    fn optional_fields() -> Vec<&'static str>;
114}
115
116/// Complete SWIFT message with headers and body
117#[derive(Debug, Clone, Serialize)]
118pub struct SwiftMessage<T: SwiftMessageBody> {
119    /// Basic Header (Block 1)
120    pub basic_header: BasicHeader,
121
122    /// Application Header (Block 2)
123    pub application_header: ApplicationHeader,
124
125    /// User Header (Block 3) - Optional
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub user_header: Option<UserHeader>,
128
129    /// Trailer (Block 5) - Optional
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub trailer: Option<Trailer>,
132
133    /// Raw message blocks for preservation
134    pub blocks: RawBlocks,
135
136    /// Message type identifier
137    pub message_type: String,
138
139    /// Field order as they appeared in the original message
140    pub field_order: Vec<String>,
141
142    /// Parsed message body with typed fields
143    pub fields: T,
144}
145
146/// Raw message blocks for preservation and reconstruction
147#[derive(Debug, Clone, Serialize, Deserialize, Default)]
148pub struct RawBlocks {
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub block1: Option<String>,
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub block2: Option<String>,
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub block3: Option<String>,
155    pub block4: String,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub block5: Option<String>,
158}
159
160/// Validation result for field and message validation
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct ValidationResult {
163    pub is_valid: bool,
164    pub errors: Vec<ValidationError>,
165    pub warnings: Vec<String>,
166}
167
168impl ValidationResult {
169    pub fn valid() -> Self {
170        Self {
171            is_valid: true,
172            errors: Vec::new(),
173            warnings: Vec::new(),
174        }
175    }
176
177    pub fn with_error(error: ValidationError) -> Self {
178        Self {
179            is_valid: false,
180            errors: vec![error],
181            warnings: Vec::new(),
182        }
183    }
184
185    pub fn with_errors(errors: Vec<ValidationError>) -> Self {
186        Self {
187            is_valid: errors.is_empty(),
188            errors,
189            warnings: Vec::new(),
190        }
191    }
192}
193
194/// Common data types used across multiple fields
195pub mod common {
196    use serde::{Deserialize, Serialize};
197
198    /// SWIFT BIC (Bank Identifier Code)
199    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
200    pub struct BIC {
201        pub value: String,
202    }
203
204    impl BIC {
205        pub fn new(value: String) -> Self {
206            Self { value }
207        }
208
209        pub fn validate(&self) -> bool {
210            // BIC validation logic: 8 or 11 characters, alphanumeric
211            let len = self.value.len();
212            (len == 8 || len == 11) && self.value.chars().all(|c| c.is_alphanumeric())
213        }
214    }
215
216    /// SWIFT Currency Code (ISO 4217)
217    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
218    pub struct Currency {
219        pub code: String,
220    }
221
222    impl Currency {
223        pub fn new(code: String) -> Self {
224            Self {
225                code: code.to_uppercase(),
226            }
227        }
228    }
229
230    /// SWIFT Amount with decimal handling
231    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
232    pub struct Amount {
233        pub value: String,
234        pub decimal_places: u8,
235    }
236
237    impl Amount {
238        pub fn new(value: String) -> Self {
239            let decimal_places = if value.contains(',') {
240                value.split(',').nth(1).map(|s| s.len() as u8).unwrap_or(0)
241            } else {
242                0
243            };
244
245            Self {
246                value,
247                decimal_places,
248            }
249        }
250
251        pub fn to_decimal(&self) -> Result<f64, std::num::ParseFloatError> {
252            self.value.replace(',', ".").parse()
253        }
254    }
255}
256
257/// Enumeration of all supported SWIFT message types for automatic parsing
258#[derive(Debug, Clone, Serialize)]
259#[serde(tag = "message_type")]
260pub enum ParsedSwiftMessage {
261    #[serde(rename = "103")]
262    MT103(Box<SwiftMessage<messages::MT103>>),
263    #[serde(rename = "202")]
264    MT202(Box<SwiftMessage<messages::MT202>>),
265}
266
267impl ParsedSwiftMessage {
268    /// Get the message type as a string
269    pub fn message_type(&self) -> &'static str {
270        match self {
271            ParsedSwiftMessage::MT103(_) => "103",
272            ParsedSwiftMessage::MT202(_) => "202",
273        }
274    }
275
276    /// Convert to a specific message type if it matches
277    pub fn as_mt103(&self) -> Option<&SwiftMessage<messages::MT103>> {
278        match self {
279            ParsedSwiftMessage::MT103(msg) => Some(msg),
280            _ => None,
281        }
282    }
283
284    pub fn as_mt202(&self) -> Option<&SwiftMessage<messages::MT202>> {
285        match self {
286            ParsedSwiftMessage::MT202(msg) => Some(msg),
287            _ => None,
288        }
289    }
290
291    /// Convert into a specific message type if it matches
292    pub fn into_mt103(self) -> Option<SwiftMessage<messages::MT103>> {
293        match self {
294            ParsedSwiftMessage::MT103(msg) => Some(*msg),
295            _ => None,
296        }
297    }
298
299    pub fn into_mt202(self) -> Option<SwiftMessage<messages::MT202>> {
300        match self {
301            ParsedSwiftMessage::MT202(msg) => Some(*msg),
302            _ => None,
303        }
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310    use crate::messages::mt103::MT103;
311
312    #[test]
313    fn test_full_mt103_parsing() {
314        let raw_message = r#"{1:F01BNPAFRPPXXX0000000000}{2:O1031234240101DEUTDEFFXXXX12345678952401011234N}{3:{103:EBA}}{4:
315:20:FT21001234567890
316:23B:CRED
317:32A:240101USD1000,00
318:50K:/1234567890
319ACME CORPORATION
320123 MAIN STREET
321NEW YORK NY 10001
322:52A:BNPAFRPPXXX
323:57A:DEUTDEFFXXX
324:59:/DE89370400440532013000
325MUELLER GMBH
326HAUPTSTRASSE 1
32710115 BERLIN
328:70:PAYMENT FOR INVOICE 12345
329:71A:OUR
330-}"#;
331
332        let result = SwiftParser::parse::<MT103>(raw_message);
333        assert!(result.is_ok(), "Parsing should succeed: {:?}", result.err());
334
335        let parsed = result.unwrap();
336        assert_eq!(parsed.message_type, "103");
337
338        // Test JSON serialization
339        let json = serde_json::to_string_pretty(&parsed);
340        assert!(json.is_ok(), "JSON serialization should work");
341        println!("Parsed MT103 JSON:\n{}", json.unwrap());
342    }
343
344    #[test]
345    fn test_field_parsing() {
346        use crate::fields::field20::Field20;
347
348        let result = Field20::parse(":20:FT21001234567890");
349        assert!(result.is_ok());
350
351        let field = result.unwrap();
352        assert_eq!(field.to_swift_string(), ":20:FT21001234567890");
353    }
354}