swift_mt_message/fields/
field28.rs

1use super::swift_utils::{parse_swift_digits, split_at_first};
2use crate::errors::ParseError;
3use crate::traits::SwiftField;
4use serde::{Deserialize, Serialize};
5
6/// **Field 28: Statement Number/Sequence Number**
7///
8/// Statement numbering with optional sequence for account statements.
9///
10/// **Format:** `5n[/2n]` (statement number + optional sequence)
11///
12/// **Example:**
13/// ```text
14/// :28:12345/01
15/// ```
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17pub struct Field28 {
18    /// Statement number (up to 5 digits)
19    pub statement_number: u32,
20
21    /// Optional sequence number (up to 2 digits)
22    pub sequence_number: Option<u8>,
23}
24
25impl SwiftField for Field28 {
26    fn parse(input: &str) -> crate::Result<Self>
27    where
28        Self: Sized,
29    {
30        let (statement_str, sequence_str) = split_at_first(input, '/');
31
32        // Parse statement number (5n)
33        if statement_str.len() > 5 {
34            return Err(ParseError::InvalidFormat {
35                message: "Statement number must be at most 5 digits".to_string(),
36            });
37        }
38
39        parse_swift_digits(&statement_str, "Statement number")?;
40        let statement_number: u32 =
41            statement_str
42                .parse()
43                .map_err(|_| ParseError::InvalidFormat {
44                    message: "Invalid statement number".to_string(),
45                })?;
46
47        // Parse optional sequence number ([/2n])
48        let sequence_number = if let Some(seq_str) = sequence_str {
49            if seq_str.len() > 2 {
50                return Err(ParseError::InvalidFormat {
51                    message: "Sequence number must be at most 2 digits".to_string(),
52                });
53            }
54            parse_swift_digits(&seq_str, "Sequence number")?;
55            let seq: u8 = seq_str.parse().map_err(|_| ParseError::InvalidFormat {
56                message: "Invalid sequence number".to_string(),
57            })?;
58            Some(seq)
59        } else {
60            None
61        };
62
63        Ok(Field28 {
64            statement_number,
65            sequence_number,
66        })
67    }
68
69    fn to_swift_string(&self) -> String {
70        if let Some(seq) = self.sequence_number {
71            format!(":28:{}/{:02}", self.statement_number, seq)
72        } else {
73            format!(":28:{}", self.statement_number)
74        }
75    }
76}
77
78/// **Field 28C: Extended Statement Number/Sequence Number**
79///
80/// Extended statement numbering with larger sequence capacity.
81///
82/// **Format:** `5n[/5n]` (statement number + optional extended sequence)
83///
84/// **Example:**
85/// ```text
86/// :28C:98765/00123
87/// ```
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
89pub struct Field28C {
90    /// Statement number (up to 5 digits)
91    pub statement_number: u32,
92
93    /// Optional extended sequence number (up to 5 digits)
94    pub sequence_number: Option<u32>,
95}
96
97impl SwiftField for Field28C {
98    fn parse(input: &str) -> crate::Result<Self>
99    where
100        Self: Sized,
101    {
102        let (statement_str, sequence_str) = split_at_first(input, '/');
103
104        // Parse statement number (5n)
105        if statement_str.len() > 5 {
106            return Err(ParseError::InvalidFormat {
107                message: "Statement number must be at most 5 digits".to_string(),
108            });
109        }
110
111        parse_swift_digits(&statement_str, "Statement number")?;
112        let statement_number: u32 =
113            statement_str
114                .parse()
115                .map_err(|_| ParseError::InvalidFormat {
116                    message: "Invalid statement number".to_string(),
117                })?;
118
119        // Parse optional sequence number ([/5n])
120        let sequence_number = if let Some(seq_str) = sequence_str {
121            if seq_str.len() > 5 {
122                return Err(ParseError::InvalidFormat {
123                    message: "Sequence number must be at most 5 digits".to_string(),
124                });
125            }
126            parse_swift_digits(&seq_str, "Sequence number")?;
127            let seq: u32 = seq_str.parse().map_err(|_| ParseError::InvalidFormat {
128                message: "Invalid sequence number".to_string(),
129            })?;
130            Some(seq)
131        } else {
132            None
133        };
134
135        Ok(Field28C {
136            statement_number,
137            sequence_number,
138        })
139    }
140
141    fn to_swift_string(&self) -> String {
142        if let Some(seq) = self.sequence_number {
143            format!(":28C:{}/{}", self.statement_number, seq)
144        } else {
145            format!(":28C:{}", self.statement_number)
146        }
147    }
148}
149
150/// **Field 28D: Message Index/Total**
151///
152/// Message indexing for batch operations and completeness verification.
153///
154/// **Format:** `5n/5n` (index/total)
155/// **Constraints:** Index must not exceed total
156///
157/// **Example:**
158/// ```text
159/// :28D:001/010
160/// ```
161#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
162pub struct Field28D {
163    /// Message index (current position, up to 5 digits)
164    pub index: u32,
165
166    /// Total message count (up to 5 digits)
167    pub total: u32,
168}
169
170impl SwiftField for Field28D {
171    fn parse(input: &str) -> crate::Result<Self>
172    where
173        Self: Sized,
174    {
175        let (index_str, total_str) = split_at_first(input, '/');
176
177        // Parse index number (5n)
178        if index_str.len() > 5 {
179            return Err(ParseError::InvalidFormat {
180                message: "Index number must be at most 5 digits".to_string(),
181            });
182        }
183
184        parse_swift_digits(&index_str, "Index number")?;
185        let index: u32 = index_str.parse().map_err(|_| ParseError::InvalidFormat {
186            message: "Invalid index number".to_string(),
187        })?;
188
189        // Parse total count (required for Field28D)
190        let total_str = total_str.ok_or_else(|| ParseError::InvalidFormat {
191            message: "Field28D requires both index and total separated by '/'".to_string(),
192        })?;
193
194        if total_str.len() > 5 {
195            return Err(ParseError::InvalidFormat {
196                message: "Total count must be at most 5 digits".to_string(),
197            });
198        }
199
200        parse_swift_digits(&total_str, "Total count")?;
201        let total: u32 = total_str.parse().map_err(|_| ParseError::InvalidFormat {
202            message: "Invalid total count".to_string(),
203        })?;
204
205        // Validate that index doesn't exceed total
206        if index > total {
207            return Err(ParseError::InvalidFormat {
208                message: format!("Index {} cannot exceed total {}", index, total),
209            });
210        }
211
212        if index == 0 || total == 0 {
213            return Err(ParseError::InvalidFormat {
214                message: "Index and total must be greater than zero".to_string(),
215            });
216        }
217
218        Ok(Field28D { index, total })
219    }
220
221    fn to_swift_string(&self) -> String {
222        format!(":28D:{:03}/{:03}", self.index, self.total)
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229
230    #[test]
231    fn test_field28_parse() {
232        // Test with sequence number
233        let field = Field28::parse("12345/67").unwrap();
234        assert_eq!(field.statement_number, 12345);
235        assert_eq!(field.sequence_number, Some(67));
236
237        // Test without sequence number
238        let field = Field28::parse("12345").unwrap();
239        assert_eq!(field.statement_number, 12345);
240        assert_eq!(field.sequence_number, None);
241
242        // Test with single digit sequence
243        let field = Field28::parse("123/5").unwrap();
244        assert_eq!(field.statement_number, 123);
245        assert_eq!(field.sequence_number, Some(5));
246    }
247
248    #[test]
249    fn test_field28c_parse() {
250        // Test with extended sequence number
251        let field = Field28C::parse("12345/99999").unwrap();
252        assert_eq!(field.statement_number, 12345);
253        assert_eq!(field.sequence_number, Some(99999));
254
255        // Test without sequence number
256        let field = Field28C::parse("12345").unwrap();
257        assert_eq!(field.statement_number, 12345);
258        assert_eq!(field.sequence_number, None);
259    }
260
261    #[test]
262    fn test_field28d_parse() {
263        // Test valid index/total
264        let field = Field28D::parse("001/010").unwrap();
265        assert_eq!(field.index, 1);
266        assert_eq!(field.total, 10);
267
268        // Test another valid case
269        let field = Field28D::parse("5/5").unwrap();
270        assert_eq!(field.index, 5);
271        assert_eq!(field.total, 5);
272    }
273
274    #[test]
275    fn test_field28_to_swift_string() {
276        let field = Field28 {
277            statement_number: 12345,
278            sequence_number: Some(67),
279        };
280        assert_eq!(field.to_swift_string(), ":28:12345/67");
281
282        let field = Field28 {
283            statement_number: 12345,
284            sequence_number: None,
285        };
286        assert_eq!(field.to_swift_string(), ":28:12345");
287    }
288
289    #[test]
290    fn test_field28c_to_swift_string() {
291        let field = Field28C {
292            statement_number: 12345,
293            sequence_number: Some(99999),
294        };
295        assert_eq!(field.to_swift_string(), ":28C:12345/99999");
296    }
297
298    #[test]
299    fn test_field28d_to_swift_string() {
300        let field = Field28D {
301            index: 1,
302            total: 10,
303        };
304        assert_eq!(field.to_swift_string(), ":28D:001/010");
305    }
306
307    #[test]
308    fn test_field28d_validation_errors() {
309        // Index exceeds total
310        assert!(Field28D::parse("11/10").is_err());
311
312        // Zero values
313        assert!(Field28D::parse("0/10").is_err());
314        assert!(Field28D::parse("1/0").is_err());
315
316        // Missing total
317        assert!(Field28D::parse("5").is_err());
318    }
319}