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, String>) -> SwiftResult<Self>
103    where
104        Self: Sized;
105
106    /// Convert to field map
107    fn to_fields(&self) -> HashMap<String, 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    pub user_header: Option<UserHeader>,
127
128    /// Trailer (Block 5) - Optional
129    pub trailer: Option<Trailer>,
130
131    /// Raw message blocks for preservation
132    pub blocks: RawBlocks,
133
134    /// Message type identifier
135    pub message_type: String,
136
137    /// Field order as they appeared in the original message
138    pub field_order: Vec<String>,
139
140    /// Parsed message body with typed fields
141    pub fields: T,
142}
143
144/// Raw message blocks for preservation and reconstruction
145#[derive(Debug, Clone, Serialize, Deserialize, Default)]
146pub struct RawBlocks {
147    pub block1: Option<String>,
148    pub block2: Option<String>,
149    pub block3: Option<String>,
150    pub block4: String,
151    pub block5: Option<String>,
152}
153
154/// Validation result for field and message validation
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct ValidationResult {
157    pub is_valid: bool,
158    pub errors: Vec<ValidationError>,
159    pub warnings: Vec<String>,
160}
161
162impl ValidationResult {
163    pub fn valid() -> Self {
164        Self {
165            is_valid: true,
166            errors: Vec::new(),
167            warnings: Vec::new(),
168        }
169    }
170
171    pub fn with_error(error: ValidationError) -> Self {
172        Self {
173            is_valid: false,
174            errors: vec![error],
175            warnings: Vec::new(),
176        }
177    }
178
179    pub fn with_errors(errors: Vec<ValidationError>) -> Self {
180        Self {
181            is_valid: errors.is_empty(),
182            errors,
183            warnings: Vec::new(),
184        }
185    }
186}
187
188/// Common data types used across multiple fields
189pub mod common {
190    use serde::{Deserialize, Serialize};
191
192    /// SWIFT BIC (Bank Identifier Code)
193    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
194    pub struct BIC {
195        pub value: String,
196    }
197
198    impl BIC {
199        pub fn new(value: String) -> Self {
200            Self { value }
201        }
202
203        pub fn validate(&self) -> bool {
204            // BIC validation logic: 8 or 11 characters, alphanumeric
205            let len = self.value.len();
206            (len == 8 || len == 11) && self.value.chars().all(|c| c.is_alphanumeric())
207        }
208    }
209
210    /// SWIFT Currency Code (ISO 4217)
211    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
212    pub struct Currency {
213        pub code: String,
214    }
215
216    impl Currency {
217        pub fn new(code: String) -> Self {
218            Self {
219                code: code.to_uppercase(),
220            }
221        }
222    }
223
224    /// SWIFT Amount with decimal handling
225    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
226    pub struct Amount {
227        pub value: String,
228        pub decimal_places: u8,
229    }
230
231    impl Amount {
232        pub fn new(value: String) -> Self {
233            let decimal_places = if value.contains(',') {
234                value.split(',').nth(1).map(|s| s.len() as u8).unwrap_or(0)
235            } else {
236                0
237            };
238
239            Self {
240                value,
241                decimal_places,
242            }
243        }
244
245        pub fn to_decimal(&self) -> Result<f64, std::num::ParseFloatError> {
246            self.value.replace(',', ".").parse()
247        }
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254    use crate::messages::mt103::MT103;
255
256    #[test]
257    fn test_full_mt103_parsing() {
258        let raw_message = r#"{1:F01BNPAFRPPXXX0000000000}{2:O1031234240101DEUTDEFFXXXX12345678952401011234N}{3:{103:EBA}}{4:
259:20:FT21001234567890
260:23B:CRED
261:32A:240101USD1000,00
262:50K:/1234567890
263ACME CORPORATION
264123 MAIN STREET
265NEW YORK NY 10001
266:52A:BNPAFRPPXXX
267:57A:DEUTDEFFXXX
268:59:/DE89370400440532013000
269MUELLER GMBH
270HAUPTSTRASSE 1
27110115 BERLIN
272:70:PAYMENT FOR INVOICE 12345
273:71A:OUR
274-}"#;
275
276        let result = SwiftParser::parse::<MT103>(raw_message);
277        assert!(result.is_ok(), "Parsing should succeed: {:?}", result.err());
278
279        let parsed = result.unwrap();
280        assert_eq!(parsed.message_type, "103");
281
282        // Test JSON serialization
283        let json = serde_json::to_string_pretty(&parsed);
284        assert!(json.is_ok(), "JSON serialization should work");
285        println!("Parsed MT103 JSON:\n{}", json.unwrap());
286    }
287
288    #[test]
289    fn test_field_parsing() {
290        use crate::fields::field20::Field20;
291
292        let result = Field20::parse(":20:FT21001234567890");
293        assert!(result.is_ok());
294
295        let field = result.unwrap();
296        assert_eq!(field.to_swift_string(), ":20:FT21001234567890");
297    }
298}