1use super::field_utils::parse_party_identifier;
2use super::swift_utils::{parse_bic, parse_max_length};
3use crate::errors::ParseError;
4use crate::traits::SwiftField;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct Field53A {
13 pub party_identifier: Option<String>,
15
16 pub bic: String,
18}
19
20impl SwiftField for Field53A {
21 fn parse(input: &str) -> crate::Result<Self>
22 where
23 Self: Sized,
24 {
25 let lines: Vec<&str> = input.split('\n').collect();
26
27 if lines.is_empty() {
28 return Err(ParseError::InvalidFormat {
29 message: "Field 53A requires input".to_string(),
30 });
31 }
32
33 let mut line_idx = 0;
34 let mut party_identifier = None;
35
36 if let Some(party_id) = parse_party_identifier(lines[0])? {
38 party_identifier = Some(format!("/{}", party_id));
39 line_idx = 1;
40 }
41
42 if line_idx >= lines.len() {
44 return Err(ParseError::InvalidFormat {
45 message: "Field 53A requires BIC code after party identifier".to_string(),
46 });
47 }
48
49 let bic = parse_bic(lines[line_idx])?;
51
52 Ok(Field53A {
53 party_identifier,
54 bic,
55 })
56 }
57
58 fn to_swift_string(&self) -> String {
59 let mut result = String::new();
60 if let Some(ref party_id) = self.party_identifier {
61 result.push_str(party_id);
62 result.push('\n');
63 }
64 result.push_str(&self.bic);
65 format!(":53A:{}", result)
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
74pub struct Field53B {
75 pub party_identifier: Option<String>,
77
78 pub location: Option<String>,
80}
81
82impl SwiftField for Field53B {
83 fn parse(input: &str) -> crate::Result<Self>
84 where
85 Self: Sized,
86 {
87 if input.is_empty() {
88 return Ok(Field53B {
89 party_identifier: None,
90 location: None,
91 });
92 }
93
94 let lines: Vec<&str> = input.split('\n').collect();
95 let mut party_identifier = None;
96 let mut location = None;
97
98 if lines.len() >= 2 {
105 if !lines[0].is_empty() {
107 party_identifier =
108 Some(parse_max_length(lines[0], 34, "Field53B party_identifier")?);
109 }
110 if !lines[1].is_empty() {
111 location = Some(parse_max_length(lines[1], 35, "Field53B location")?);
112 }
113 } else if lines.len() == 1 && !lines[0].is_empty() {
114 let line = lines[0];
115
116 let is_party_identifier = line.starts_with('/')
118 || ((8..=11).contains(&line.len())
119 && line
120 .chars()
121 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit()));
122
123 if is_party_identifier {
124 party_identifier = Some(parse_max_length(line, 34, "Field53B party_identifier")?);
125 } else {
126 location = Some(parse_max_length(line, 35, "Field53B location")?);
127 }
128 }
129
130 Ok(Field53B {
131 party_identifier,
132 location,
133 })
134 }
135
136 fn to_swift_string(&self) -> String {
137 let mut result = String::new();
138 if let Some(ref party_id) = self.party_identifier {
139 result.push_str(party_id);
140 if self.location.is_some() {
141 result.push('\n');
142 }
143 }
144 if let Some(ref loc) = self.location {
145 result.push_str(loc);
146 }
147 format!(":53B:{}", result)
148 }
149}
150
151#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
156pub struct Field53D {
157 pub party_identifier: Option<String>,
159
160 pub name_and_address: Vec<String>,
162}
163
164impl SwiftField for Field53D {
165 fn parse(input: &str) -> crate::Result<Self>
166 where
167 Self: Sized,
168 {
169 let mut lines = input.split('\n').collect::<Vec<_>>();
170
171 if lines.is_empty() {
172 return Err(ParseError::InvalidFormat {
173 message: "Field 53D requires at least one line".to_string(),
174 });
175 }
176
177 let mut party_identifier = None;
178
179 if let Some(first_line) = lines.first() {
183 let looks_like_party_id = first_line.starts_with('/')
185 || (first_line.len() <= 34
186 && !first_line.contains(' ')
187 && first_line.chars().any(|c| c.is_ascii_digit()));
188
189 if looks_like_party_id && !first_line.is_empty() && lines.len() > 1 {
190 party_identifier = Some(first_line.to_string());
192 lines.remove(0);
193 }
194 }
195
196 let mut name_and_address = Vec::new();
198 for (i, line) in lines.iter().enumerate() {
199 if i >= 4 {
200 break;
201 }
202 if line.len() > 35 {
203 return Err(ParseError::InvalidFormat {
204 message: format!("Field 53D line {} exceeds 35 characters", i + 1),
205 });
206 }
207 name_and_address.push(line.to_string());
208 }
209
210 if name_and_address.is_empty() {
211 return Err(ParseError::InvalidFormat {
212 message: "Field 53D must contain name and address information".to_string(),
213 });
214 }
215
216 Ok(Field53D {
217 party_identifier,
218 name_and_address,
219 })
220 }
221
222 fn to_swift_string(&self) -> String {
223 let mut result = String::new();
224 if let Some(ref party_id) = self.party_identifier {
225 result.push_str(party_id);
226 result.push('\n');
227 }
228 for (i, line) in self.name_and_address.iter().enumerate() {
229 if i > 0 {
230 result.push('\n');
231 }
232 result.push_str(line);
233 }
234 format!(":53D:{}", result)
235 }
236}
237
238#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
239pub enum Field53SenderCorrespondent {
240 #[serde(rename = "53A")]
241 A(Field53A),
242 #[serde(rename = "53B")]
243 B(Field53B),
244 #[serde(rename = "53D")]
245 D(Field53D),
246}
247
248impl SwiftField for Field53SenderCorrespondent {
249 fn parse(input: &str) -> crate::Result<Self>
250 where
251 Self: Sized,
252 {
253 let lines: Vec<&str> = input.split('\n').collect();
259 let last_line = lines.last().unwrap_or(&"");
260
261 if (8..=11).contains(&last_line.len())
263 && last_line
264 .chars()
265 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
266 {
267 if let Ok(field) = Field53A::parse(input) {
269 return Ok(Field53SenderCorrespondent::A(field));
270 }
271 }
272
273 if lines.len() > 2 || (lines.len() == 2 && !lines[0].starts_with('/')) {
275 if let Ok(field) = Field53D::parse(input) {
277 return Ok(Field53SenderCorrespondent::D(field));
278 }
279 }
280
281 if let Ok(field) = Field53B::parse(input) {
283 return Ok(Field53SenderCorrespondent::B(field));
284 }
285
286 if let Ok(field) = Field53A::parse(input) {
288 return Ok(Field53SenderCorrespondent::A(field));
289 }
290 if let Ok(field) = Field53D::parse(input) {
291 return Ok(Field53SenderCorrespondent::D(field));
292 }
293
294 Err(ParseError::InvalidFormat {
295 message: "Field 53 could not be parsed as any valid option (A, B, or D)".to_string(),
296 })
297 }
298
299 fn parse_with_variant(
300 value: &str,
301 variant: Option<&str>,
302 _field_tag: Option<&str>,
303 ) -> crate::Result<Self>
304 where
305 Self: Sized,
306 {
307 match variant {
308 Some("A") => {
309 let field = Field53A::parse(value)?;
310 Ok(Field53SenderCorrespondent::A(field))
311 }
312 Some("B") => {
313 let field = Field53B::parse(value)?;
314 Ok(Field53SenderCorrespondent::B(field))
315 }
316 Some("D") => {
317 let field = Field53D::parse(value)?;
318 Ok(Field53SenderCorrespondent::D(field))
319 }
320 _ => {
321 Self::parse(value)
323 }
324 }
325 }
326
327 fn to_swift_string(&self) -> String {
328 match self {
329 Field53SenderCorrespondent::A(field) => field.to_swift_string(),
330 Field53SenderCorrespondent::B(field) => field.to_swift_string(),
331 Field53SenderCorrespondent::D(field) => field.to_swift_string(),
332 }
333 }
334
335 fn get_variant_tag(&self) -> Option<&'static str> {
336 match self {
337 Field53SenderCorrespondent::A(_) => Some("A"),
338 Field53SenderCorrespondent::B(_) => Some("B"),
339 Field53SenderCorrespondent::D(_) => Some("D"),
340 }
341 }
342}
343
344pub type Field53 = Field53SenderCorrespondent;
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350
351 #[test]
352 fn test_field53a_valid() {
353 let field = Field53A::parse("CHASUS33XXX").unwrap();
355 assert_eq!(field.bic, "CHASUS33XXX");
356 assert_eq!(field.party_identifier, None);
357 assert_eq!(field.to_swift_string(), ":53A:CHASUS33XXX");
358
359 let field = Field53A::parse("/C/12345678\nCHASUS33").unwrap();
361 assert_eq!(field.bic, "CHASUS33");
362 assert_eq!(field.party_identifier, Some("/C/12345678".to_string()));
363 assert_eq!(field.to_swift_string(), ":53A:/C/12345678\nCHASUS33");
364 }
365
366 #[test]
367 fn test_field53b_valid() {
368 let field = Field53B::parse("NEW YORK BRANCH").unwrap();
370 assert_eq!(field.location, Some("NEW YORK BRANCH".to_string()));
371 assert_eq!(field.party_identifier, None);
372
373 let field = Field53B::parse("/D/98765432\nNEW YORK").unwrap();
375 assert_eq!(field.party_identifier, Some("/D/98765432".to_string()));
376 assert_eq!(field.location, Some("NEW YORK".to_string()));
377
378 let field = Field53B::parse("").unwrap();
380 assert_eq!(field.party_identifier, None);
381 assert_eq!(field.location, None);
382 }
383
384 #[test]
385 fn test_field53d_valid() {
386 let field =
388 Field53D::parse("/C/12345678\nCORRESPONDENT BANK\n123 MAIN ST\nNEW YORK\nUSA").unwrap();
389 assert_eq!(field.party_identifier, Some("/C/12345678".to_string()));
390 assert_eq!(field.name_and_address.len(), 4);
391 assert_eq!(field.name_and_address[0], "CORRESPONDENT BANK");
392 assert_eq!(field.name_and_address[3], "USA");
393
394 let field = Field53D::parse("CORRESPONDENT BANK\nNEW YORK").unwrap();
396 assert_eq!(field.party_identifier, None);
397 assert_eq!(field.name_and_address.len(), 2);
398 }
399
400 #[test]
401 fn test_field53_enum() {
402 let field = Field53SenderCorrespondent::parse("CHASUS33XXX").unwrap();
404 assert!(matches!(field, Field53SenderCorrespondent::A(_)));
405
406 let field = Field53SenderCorrespondent::parse("NEW YORK BRANCH").unwrap();
408 assert!(matches!(field, Field53SenderCorrespondent::B(_)));
409
410 let field = Field53SenderCorrespondent::parse("BANK NAME\nADDRESS LINE 1\nCITY").unwrap();
412 assert!(matches!(field, Field53SenderCorrespondent::D(_)));
413 }
414}