1use super::swift_utils::{parse_exact_length, parse_swift_chars, parse_uppercase};
2use crate::errors::ParseError;
3use crate::traits::SwiftField;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub struct Field23 {
21 pub function_code: String,
23 pub days: Option<u32>,
25 pub reference: String,
27}
28
29impl SwiftField for Field23 {
30 fn parse(input: &str) -> crate::Result<Self>
31 where
32 Self: Sized,
33 {
34 if input.len() < 4 {
35 return Err(ParseError::InvalidFormat {
37 message: format!(
38 "Field 23 must be at least 4 characters, found {}",
39 input.len()
40 ),
41 });
42 }
43
44 let function_code = parse_exact_length(&input[0..3], 3, "Field 23 function code")?;
46 parse_uppercase(&function_code, "Field 23 function code")?;
47
48 let (days, reference_start) = if input.len() >= 5 {
50 let potential_days = &input[3..5];
51 if potential_days.chars().all(|c| c.is_numeric()) {
52 let days_value =
53 potential_days
54 .parse::<u32>()
55 .map_err(|_| ParseError::InvalidFormat {
56 message: "Invalid days value in Field 23".to_string(),
57 })?;
58
59 if days_value == 0 || days_value > 99 {
61 return Err(ParseError::InvalidFormat {
62 message: format!(
63 "Field 23 days must be between 1 and 99, found {}",
64 days_value
65 ),
66 });
67 }
68
69 if function_code != "NOT" && function_code != "NOTICE" {
71 return Err(ParseError::InvalidFormat {
72 message: format!(
73 "Days field only allowed for NOTICE function code, found {}",
74 function_code
75 ),
76 });
77 }
78
79 (Some(days_value), 5)
80 } else {
81 (None, 3)
82 }
83 } else {
84 (None, 3)
85 };
86
87 let reference = if input.len() > reference_start {
89 let ref_str = &input[reference_start..];
90 if ref_str.len() > 11 {
91 return Err(ParseError::InvalidFormat {
92 message: format!(
93 "Field 23 reference must be at most 11 characters, found {}",
94 ref_str.len()
95 ),
96 });
97 }
98 parse_swift_chars(ref_str, "Field 23 reference")?;
99 ref_str.to_string()
100 } else {
101 return Err(ParseError::InvalidFormat {
102 message: "Field 23 reference is required".to_string(),
103 });
104 };
105
106 Ok(Field23 {
107 function_code,
108 days,
109 reference,
110 })
111 }
112
113 fn to_swift_string(&self) -> String {
114 let mut result = String::from(":23:");
115 result.push_str(&self.function_code);
116 if let Some(days) = self.days {
117 result.push_str(&format!("{:02}", days));
118 }
119 result.push_str(&self.reference);
120 result
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
137pub struct Field23B {
138 pub instruction_code: String,
140}
141
142impl SwiftField for Field23B {
143 fn parse(input: &str) -> crate::Result<Self>
144 where
145 Self: Sized,
146 {
147 let instruction_code = parse_exact_length(input, 4, "Field 23B instruction code")?;
149
150 parse_uppercase(&instruction_code, "Field 23B instruction code")?;
152
153 const VALID_CODES: &[&str] = &[
158 "CRED", "CRTS", "SPAY", "SPRI", "SSTD", "URGP", "SDVA", "TELB", "PHON", "PHOB", "PHOI",
159 "TELE", "REPA", "CORT", "INTC", "HOLD",
160 ];
161 if !VALID_CODES.contains(&instruction_code.as_str()) {
162 return Err(ParseError::InvalidFormat {
163 message: format!(
164 "Field 23B instruction code must be one of {:?}, found {}",
165 VALID_CODES, instruction_code
166 ),
167 });
168 }
169
170 Ok(Field23B { instruction_code })
171 }
172
173 fn to_swift_string(&self) -> String {
174 format!(":23B:{}", self.instruction_code)
175 }
176}
177
178#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
191pub struct Field23E {
192 pub instruction_code: String,
194 pub additional_info: Option<String>,
196}
197
198impl SwiftField for Field23E {
199 fn parse(input: &str) -> crate::Result<Self>
200 where
201 Self: Sized,
202 {
203 if input.len() < 4 {
204 return Err(ParseError::InvalidFormat {
205 message: format!(
206 "Field 23E must be at least 4 characters, found {}",
207 input.len()
208 ),
209 });
210 }
211
212 let instruction_code = parse_exact_length(&input[0..4], 4, "Field 23E instruction code")?;
214 parse_uppercase(&instruction_code, "Field 23E instruction code")?;
215
216 let additional_info = if input.len() > 4 {
218 if !input[4..].starts_with('/') {
219 return Err(ParseError::InvalidFormat {
220 message: "Field 23E additional information must start with '/'".to_string(),
221 });
222 }
223
224 let info = &input[5..];
225 if info.len() > 35 {
226 return Err(ParseError::InvalidFormat {
227 message: format!(
228 "Field 23E additional information must be at most 35 characters, found {}",
229 info.len()
230 ),
231 });
232 }
233
234 parse_swift_chars(info, "Field 23E additional information")?;
235 Some(info.to_string())
236 } else {
237 None
238 };
239
240 Ok(Field23E {
241 instruction_code,
242 additional_info,
243 })
244 }
245
246 fn to_swift_string(&self) -> String {
247 let mut result = String::from(":23E:");
248 result.push_str(&self.instruction_code);
249 if let Some(ref info) = self.additional_info {
250 result.push('/');
251 result.push_str(info);
252 }
253 result
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn test_field23() {
263 let field = Field23::parse("NOT02REFERENCE1").unwrap();
265 assert_eq!(field.function_code, "NOT");
266 assert_eq!(field.days, Some(2));
267 assert_eq!(field.reference, "REFERENCE1");
268
269 let field = Field23::parse("BASREFERENCE").unwrap();
271 assert_eq!(field.function_code, "BAS");
272 assert_eq!(field.days, None);
273 assert_eq!(field.reference, "REFERENCE");
274
275 let field = Field23 {
277 function_code: "NOT".to_string(),
278 days: Some(15),
279 reference: "REF123".to_string(),
280 };
281 assert_eq!(field.to_swift_string(), ":23:NOT15REF123");
282 }
283
284 #[test]
285 fn test_field23b() {
286 let field = Field23B::parse("SSTD").unwrap();
288 assert_eq!(field.instruction_code, "SSTD");
289
290 let field = Field23B::parse("SPRI").unwrap();
291 assert_eq!(field.instruction_code, "SPRI");
292
293 assert!(Field23B::parse("SST").is_err());
295 assert!(Field23B::parse("SSTDD").is_err());
296
297 assert!(Field23B::parse("XXXX").is_err());
299
300 assert!(Field23B::parse("sstd").is_err());
302 }
303
304 #[test]
305 fn test_field23e() {
306 let field = Field23E::parse("CHQB/CHECK NUMBER 12345").unwrap();
308 assert_eq!(field.instruction_code, "CHQB");
309 assert_eq!(
310 field.additional_info,
311 Some("CHECK NUMBER 12345".to_string())
312 );
313
314 let field = Field23E::parse("URGP").unwrap();
316 assert_eq!(field.instruction_code, "URGP");
317 assert_eq!(field.additional_info, None);
318
319 let field = Field23E {
321 instruction_code: "HOLD".to_string(),
322 additional_info: Some("COMPLIANCE REVIEW".to_string()),
323 };
324 assert_eq!(field.to_swift_string(), ":23E:HOLD/COMPLIANCE REVIEW");
325
326 let long_info = "A".repeat(35);
328 let input = format!("CODE/{}", long_info);
329 assert!(Field23E::parse(&input).is_ok());
330
331 let too_long = "A".repeat(36);
333 let input = format!("CODE/{}", too_long);
334 assert!(Field23E::parse(&input).is_err());
335 }
336}