1use 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
68pub use errors::{ParseError, Result, ValidationError};
70pub use headers::{ApplicationHeader, BasicHeader, Trailer, UserHeader};
71pub use parser::SwiftParser;
72
73pub use swift_mt_message_macros::{SwiftField, SwiftMessage, swift_serde};
75
76pub type SwiftResult<T> = std::result::Result<T, crate::errors::ParseError>;
78
79pub trait SwiftField: Serialize + for<'de> Deserialize<'de> + Clone + std::fmt::Debug {
81 fn parse(value: &str) -> Result<Self>
83 where
84 Self: Sized;
85
86 fn to_swift_string(&self) -> String;
88
89 fn validate(&self) -> ValidationResult;
91
92 fn format_spec() -> &'static str;
94}
95
96pub trait SwiftMessageBody: Debug + Clone + Send + Sync + Serialize {
98 fn message_type() -> &'static str;
100
101 fn from_fields(fields: HashMap<String, Vec<String>>) -> SwiftResult<Self>
103 where
104 Self: Sized;
105
106 fn to_fields(&self) -> HashMap<String, Vec<String>>;
108
109 fn required_fields() -> Vec<&'static str>;
111
112 fn optional_fields() -> Vec<&'static str>;
114}
115
116#[derive(Debug, Clone, Serialize)]
118pub struct SwiftMessage<T: SwiftMessageBody> {
119 pub basic_header: BasicHeader,
121
122 pub application_header: ApplicationHeader,
124
125 #[serde(skip_serializing_if = "Option::is_none")]
127 pub user_header: Option<UserHeader>,
128
129 #[serde(skip_serializing_if = "Option::is_none")]
131 pub trailer: Option<Trailer>,
132
133 pub blocks: RawBlocks,
135
136 pub message_type: String,
138
139 pub field_order: Vec<String>,
141
142 pub fields: T,
144}
145
146#[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#[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
194pub mod common {
196 use serde::{Deserialize, Serialize};
197
198 #[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 let len = self.value.len();
212 (len == 8 || len == 11) && self.value.chars().all(|c| c.is_alphanumeric())
213 }
214 }
215
216 #[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 #[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#[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 pub fn message_type(&self) -> &'static str {
270 match self {
271 ParsedSwiftMessage::MT103(_) => "103",
272 ParsedSwiftMessage::MT202(_) => "202",
273 }
274 }
275
276 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 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 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}