swift_mt_message/fields/
field11.rs

1use super::swift_utils::parse_swift_digits;
2use crate::errors::ParseError;
3use crate::traits::SwiftField;
4use chrono::{Datelike, NaiveDate};
5use serde::{Deserialize, Serialize};
6
7/// **Field 11R: MT Reference (Option R)**
8///
9/// References the original message in acknowledgment and response messages.
10///
11/// **Format:** `3!n6!n[4!n][6!n]` (MT type + date + optional session + optional sequence)
12///
13/// **Example:**
14/// ```text
15/// :11R:1032407191234567890
16/// ```
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18pub struct Field11R {
19    /// Message type (3 digits)
20    pub message_type: String,
21
22    /// Date (YYMMDD format)
23    #[serde(with = "date_string")]
24    pub date: NaiveDate,
25
26    /// Session number (4 digits, optional)
27    pub session_number: Option<String>,
28
29    /// Input sequence number (6 digits, optional)
30    pub input_sequence_number: Option<String>,
31}
32
33impl SwiftField for Field11R {
34    fn parse(input: &str) -> crate::Result<Self>
35    where
36        Self: Sized,
37    {
38        let mut remaining = input;
39
40        // Parse message type (3!n)
41        if remaining.len() < 3 {
42            return Err(ParseError::InvalidFormat {
43                message: "Field 11R message type requires exactly 3 digits".to_string(),
44            });
45        }
46        let message_type = parse_swift_digits(&remaining[..3], "Field 11R message type")?;
47        remaining = &remaining[3..];
48
49        // Parse date (6!n for YYMMDD)
50        if remaining.len() < 6 {
51            return Err(ParseError::InvalidFormat {
52                message: "Field 11R date requires exactly 6 digits".to_string(),
53            });
54        }
55        let date_str = parse_swift_digits(&remaining[..6], "Field 11R date")?;
56        remaining = &remaining[6..];
57
58        // Parse date
59        let year = 2000
60            + date_str[0..2]
61                .parse::<i32>()
62                .map_err(|_| ParseError::InvalidFormat {
63                    message: "Invalid year in Field 11R".to_string(),
64                })?;
65        let month = date_str[2..4]
66            .parse::<u32>()
67            .map_err(|_| ParseError::InvalidFormat {
68                message: "Invalid month in Field 11R".to_string(),
69            })?;
70        let day = date_str[4..6]
71            .parse::<u32>()
72            .map_err(|_| ParseError::InvalidFormat {
73                message: "Invalid day in Field 11R".to_string(),
74            })?;
75
76        let date =
77            NaiveDate::from_ymd_opt(year, month, day).ok_or_else(|| ParseError::InvalidFormat {
78                message: format!("Invalid date in Field 11R: {}", date_str),
79            })?;
80
81        // Parse optional session number (4!n)
82        let session_number =
83            if remaining.len() >= 4 && remaining[..4].chars().all(|c| c.is_ascii_digit()) {
84                let session = Some(remaining[..4].to_string());
85                remaining = &remaining[4..];
86                session
87            } else {
88                None
89            };
90
91        // Parse optional input sequence number (6!n)
92        let input_sequence_number =
93            if remaining.len() >= 6 && remaining[..6].chars().all(|c| c.is_ascii_digit()) {
94                Some(remaining[..6].to_string())
95            } else {
96                None
97            };
98
99        Ok(Field11R {
100            message_type,
101            date,
102            session_number,
103            input_sequence_number,
104        })
105    }
106
107    fn to_swift_string(&self) -> String {
108        let date_str = format!(
109            "{:02}{:02}{:02}",
110            self.date.year() % 100,
111            self.date.month(),
112            self.date.day()
113        );
114
115        let mut result = format!(":11R:{}{}", self.message_type, date_str);
116
117        if let Some(ref session) = self.session_number {
118            result.push_str(session);
119        }
120
121        if let Some(ref seq) = self.input_sequence_number {
122            result.push_str(seq);
123        }
124
125        result
126    }
127}
128
129/// **Field 11S: MT Reference (Option S)**
130///
131/// References messages in cancellation requests and status inquiries.
132///
133/// **Format:** `3!n6!n[4!n][6!n]` (MT type + date + optional session + optional sequence)
134///
135/// **Example:**
136/// ```text
137/// :11S:1922407191234567890
138/// ```
139#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
140pub struct Field11S {
141    /// Message type (3 digits)
142    pub message_type: String,
143
144    /// Date (YYMMDD format)
145    #[serde(with = "date_string")]
146    pub date: NaiveDate,
147
148    /// Session number (4 digits, optional)
149    pub session_number: Option<String>,
150
151    /// Input sequence number (6 digits, optional)
152    pub input_sequence_number: Option<String>,
153}
154
155// Custom serialization for dates as strings
156mod date_string {
157    use chrono::NaiveDate;
158    use serde::{Deserialize, Deserializer, Serializer};
159
160    pub fn serialize<S>(date: &NaiveDate, serializer: S) -> Result<S::Ok, S::Error>
161    where
162        S: Serializer,
163    {
164        serializer.serialize_str(&date.format("%Y-%m-%d").to_string())
165    }
166
167    pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveDate, D::Error>
168    where
169        D: Deserializer<'de>,
170    {
171        let s = String::deserialize(deserializer)?;
172        NaiveDate::parse_from_str(&s, "%Y-%m-%d").map_err(serde::de::Error::custom)
173    }
174}
175
176impl SwiftField for Field11S {
177    fn parse(input: &str) -> crate::Result<Self>
178    where
179        Self: Sized,
180    {
181        let mut remaining = input;
182
183        // Parse message type (3!n)
184        if remaining.len() < 3 {
185            return Err(ParseError::InvalidFormat {
186                message: "Field 11S message type requires exactly 3 digits".to_string(),
187            });
188        }
189        let message_type = parse_swift_digits(&remaining[..3], "Field 11S message type")?;
190        remaining = &remaining[3..];
191
192        // Parse date (6!n for YYMMDD)
193        if remaining.len() < 6 {
194            return Err(ParseError::InvalidFormat {
195                message: "Field 11S date requires exactly 6 digits".to_string(),
196            });
197        }
198        let date_str = parse_swift_digits(&remaining[..6], "Field 11S date")?;
199        remaining = &remaining[6..];
200
201        // Parse date
202        let year = 2000
203            + date_str[0..2]
204                .parse::<i32>()
205                .map_err(|_| ParseError::InvalidFormat {
206                    message: "Invalid year in Field 11S".to_string(),
207                })?;
208        let month = date_str[2..4]
209            .parse::<u32>()
210            .map_err(|_| ParseError::InvalidFormat {
211                message: "Invalid month in Field 11S".to_string(),
212            })?;
213        let day = date_str[4..6]
214            .parse::<u32>()
215            .map_err(|_| ParseError::InvalidFormat {
216                message: "Invalid day in Field 11S".to_string(),
217            })?;
218
219        let date =
220            NaiveDate::from_ymd_opt(year, month, day).ok_or_else(|| ParseError::InvalidFormat {
221                message: format!("Invalid date in Field 11S: {}", date_str),
222            })?;
223
224        // Parse optional session number (4!n)
225        let session_number =
226            if remaining.len() >= 4 && remaining[..4].chars().all(|c| c.is_ascii_digit()) {
227                let session = Some(remaining[..4].to_string());
228                remaining = &remaining[4..];
229                session
230            } else {
231                None
232            };
233
234        // Parse optional input sequence number (6!n)
235        let input_sequence_number =
236            if remaining.len() >= 6 && remaining[..6].chars().all(|c| c.is_ascii_digit()) {
237                Some(remaining[..6].to_string())
238            } else {
239                None
240            };
241
242        Ok(Field11S {
243            message_type,
244            date,
245            session_number,
246            input_sequence_number,
247        })
248    }
249
250    fn to_swift_string(&self) -> String {
251        let date_str = format!(
252            "{:02}{:02}{:02}",
253            self.date.year() % 100,
254            self.date.month(),
255            self.date.day()
256        );
257
258        let mut result = format!(":11S:{}{}", self.message_type, date_str);
259
260        if let Some(ref session) = self.session_number {
261            result.push_str(session);
262        }
263
264        if let Some(ref seq) = self.input_sequence_number {
265            result.push_str(seq);
266        }
267
268        result
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275
276    #[test]
277    fn test_field11r_parse() {
278        // Test with all components
279        let field = Field11R::parse("1032407191234567890").unwrap();
280        assert_eq!(field.message_type, "103");
281        assert_eq!(field.date.year(), 2024);
282        assert_eq!(field.date.month(), 7);
283        assert_eq!(field.date.day(), 19);
284        assert_eq!(field.session_number, Some("1234".to_string()));
285        assert_eq!(field.input_sequence_number, Some("567890".to_string()));
286
287        // Test without optional components
288        let field = Field11R::parse("202240315").unwrap();
289        assert_eq!(field.message_type, "202");
290        assert_eq!(field.date.year(), 2024);
291        assert_eq!(field.date.month(), 3);
292        assert_eq!(field.date.day(), 15);
293        assert_eq!(field.session_number, None);
294        assert_eq!(field.input_sequence_number, None);
295
296        // Test with session number only
297        let field = Field11R::parse("9402407191234").unwrap();
298        assert_eq!(field.message_type, "940");
299        assert_eq!(field.session_number, Some("1234".to_string()));
300        assert_eq!(field.input_sequence_number, None);
301    }
302
303    #[test]
304    fn test_field11s_parse() {
305        // Test with all components
306        let field = Field11S::parse("1922407191234567890").unwrap();
307        assert_eq!(field.message_type, "192");
308        assert_eq!(field.date.year(), 2024);
309        assert_eq!(field.date.month(), 7);
310        assert_eq!(field.date.day(), 19);
311        assert_eq!(field.session_number, Some("1234".to_string()));
312        assert_eq!(field.input_sequence_number, Some("567890".to_string()));
313
314        // Test without optional components
315        let field = Field11S::parse("292240315").unwrap();
316        assert_eq!(field.message_type, "292");
317        assert_eq!(field.date.year(), 2024);
318        assert_eq!(field.date.month(), 3);
319        assert_eq!(field.date.day(), 15);
320        assert_eq!(field.session_number, None);
321        assert_eq!(field.input_sequence_number, None);
322    }
323
324    #[test]
325    fn test_field11r_to_swift_string() {
326        let field = Field11R {
327            message_type: "103".to_string(),
328            date: NaiveDate::from_ymd_opt(2024, 7, 19).unwrap(),
329            session_number: Some("1234".to_string()),
330            input_sequence_number: Some("567890".to_string()),
331        };
332        assert_eq!(field.to_swift_string(), ":11R:1032407191234567890");
333
334        let field = Field11R {
335            message_type: "202".to_string(),
336            date: NaiveDate::from_ymd_opt(2024, 3, 15).unwrap(),
337            session_number: None,
338            input_sequence_number: None,
339        };
340        assert_eq!(field.to_swift_string(), ":11R:202240315");
341    }
342
343    #[test]
344    fn test_field11s_to_swift_string() {
345        let field = Field11S {
346            message_type: "192".to_string(),
347            date: NaiveDate::from_ymd_opt(2024, 7, 19).unwrap(),
348            session_number: Some("1234".to_string()),
349            input_sequence_number: Some("567890".to_string()),
350        };
351        assert_eq!(field.to_swift_string(), ":11S:1922407191234567890");
352
353        let field = Field11S {
354            message_type: "292".to_string(),
355            date: NaiveDate::from_ymd_opt(2024, 3, 15).unwrap(),
356            session_number: None,
357            input_sequence_number: None,
358        };
359        assert_eq!(field.to_swift_string(), ":11S:292240315");
360    }
361}
362
363/// **Field 11: MT and Date of Original Message**
364///
365/// Identifies the message type and date of the original message being referenced.
366///
367/// **Format:** `3!n6!n` (MT type + date)
368///
369/// **Example:**
370/// ```text
371/// :11:196240719
372/// ```
373#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
374pub struct Field11 {
375    /// Message type (3 digits)
376    pub message_type: String,
377
378    /// Date (YYMMDD format)
379    pub date: NaiveDate,
380}
381
382impl SwiftField for Field11 {
383    fn parse(input: &str) -> crate::Result<Self>
384    where
385        Self: Sized,
386    {
387        // Field 11 requires at least 9 characters (3 for MT + 6 for date)
388        if input.len() < 9 {
389            return Err(ParseError::InvalidFormat {
390                message: "Field 11 requires at least 9 characters (3 for MT + 6 for date)"
391                    .to_string(),
392            });
393        }
394
395        // Parse message type (3!n)
396        let message_type = parse_swift_digits(&input[..3], "Field 11 message type")?;
397
398        // Parse date (6!n for YYMMDD)
399        let date_str = parse_swift_digits(&input[3..9], "Field 11 date")?;
400
401        // Parse date
402        let year = 2000
403            + date_str[0..2]
404                .parse::<i32>()
405                .map_err(|_| ParseError::InvalidFormat {
406                    message: "Invalid year in Field 11".to_string(),
407                })?;
408        let month = date_str[2..4]
409            .parse::<u32>()
410            .map_err(|_| ParseError::InvalidFormat {
411                message: "Invalid month in Field 11".to_string(),
412            })?;
413        let day = date_str[4..6]
414            .parse::<u32>()
415            .map_err(|_| ParseError::InvalidFormat {
416                message: "Invalid day in Field 11".to_string(),
417            })?;
418
419        let date =
420            NaiveDate::from_ymd_opt(year, month, day).ok_or_else(|| ParseError::InvalidFormat {
421                message: format!("Invalid date in Field 11: {}", date_str),
422            })?;
423
424        Ok(Field11 { message_type, date })
425    }
426
427    fn to_swift_string(&self) -> String {
428        let date_str = format!(
429            "{:02}{:02}{:02}",
430            self.date.year() % 100,
431            self.date.month(),
432            self.date.day()
433        );
434        format!(":11:{}{}", self.message_type, date_str)
435    }
436}
437
438#[cfg(test)]
439mod field11_tests {
440    use super::*;
441
442    #[test]
443    fn test_field11_parse() {
444        let field = Field11::parse("192240719").unwrap();
445        assert_eq!(field.message_type, "192");
446        assert_eq!(field.date.year(), 2024);
447        assert_eq!(field.date.month(), 7);
448        assert_eq!(field.date.day(), 19);
449    }
450
451    #[test]
452    fn test_field11_to_swift_string() {
453        let field = Field11 {
454            message_type: "196".to_string(),
455            date: NaiveDate::from_ymd_opt(2024, 7, 19).unwrap(),
456        };
457        assert_eq!(field.to_swift_string(), ":11:196240719");
458    }
459}