1use super::field_utils::{parse_name_and_address, parse_party_identifier};
2use super::swift_utils::{parse_bic, parse_swift_chars};
3use crate::errors::ParseError;
4use crate::traits::SwiftField;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21pub struct Field57A {
22 #[serde(skip_serializing_if = "Option::is_none")]
24 pub party_identifier: Option<String>,
25
26 pub bic: String,
28}
29
30impl SwiftField for Field57A {
31 fn parse(input: &str) -> crate::Result<Self>
32 where
33 Self: Sized,
34 {
35 let lines: Vec<&str> = input.lines().collect();
36
37 if lines.is_empty() {
38 return Err(ParseError::InvalidFormat {
39 message: "Field 57A cannot be empty".to_string(),
40 });
41 }
42
43 let mut party_identifier = None;
44 let mut bic_line_idx = 0;
45
46 if let Some(party_id) = parse_party_identifier(lines[0])? {
48 party_identifier = Some(party_id);
49 bic_line_idx = 1;
50 }
51
52 if bic_line_idx >= lines.len() {
54 return Err(ParseError::InvalidFormat {
55 message: "Field 57A missing BIC code".to_string(),
56 });
57 }
58
59 let bic = parse_bic(lines[bic_line_idx])?;
60
61 Ok(Field57A {
62 party_identifier,
63 bic,
64 })
65 }
66
67 fn to_swift_string(&self) -> String {
68 let mut result = Vec::new();
69
70 if let Some(ref id) = self.party_identifier {
71 result.push(format!("/{}", id));
72 }
73
74 result.push(self.bic.clone());
75 format!(":57A:{}", result.join("\n"))
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
84pub struct Field57B {
85 pub party_identifier: Option<String>,
87
88 pub location: Option<String>,
90}
91
92impl SwiftField for Field57B {
93 fn parse(input: &str) -> crate::Result<Self>
94 where
95 Self: Sized,
96 {
97 if input.is_empty() {
98 return Ok(Field57B {
99 party_identifier: None,
100 location: None,
101 });
102 }
103
104 let lines: Vec<&str> = input.lines().collect();
105 let mut party_identifier = None;
106 let mut location = None;
107 let mut current_idx = 0;
108
109 if !lines.is_empty()
111 && let Some(party_id) = parse_party_identifier(lines[0])?
112 {
113 party_identifier = Some(party_id);
114 current_idx = 1;
115 }
116
117 if current_idx < lines.len() {
119 let loc = lines[current_idx];
120 if !loc.is_empty() && loc.len() <= 35 {
121 parse_swift_chars(loc, "Field 57B location")?;
122 location = Some(loc.to_string());
123 }
124 }
125
126 Ok(Field57B {
127 party_identifier,
128 location,
129 })
130 }
131
132 fn to_swift_string(&self) -> String {
133 let mut result = Vec::new();
134
135 if let Some(ref id) = self.party_identifier {
136 result.push(format!("/{}", id));
137 }
138
139 if let Some(ref loc) = self.location {
140 result.push(loc.clone());
141 }
142
143 format!(":57B:{}", result.join("\n"))
144 }
145}
146
147#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
152pub struct Field57C {
153 pub party_identifier: String,
155}
156
157impl SwiftField for Field57C {
158 fn parse(input: &str) -> crate::Result<Self>
159 where
160 Self: Sized,
161 {
162 if !input.starts_with('/') {
163 return Err(ParseError::InvalidFormat {
164 message: "Field 57C must start with '/'".to_string(),
165 });
166 }
167
168 let identifier = &input[1..];
169
170 if identifier.is_empty() || identifier.len() > 34 {
171 return Err(ParseError::InvalidFormat {
172 message: "Field 57C party identifier must be 1-34 characters".to_string(),
173 });
174 }
175
176 parse_swift_chars(identifier, "Field 57C party identifier")?;
177
178 Ok(Field57C {
179 party_identifier: identifier.to_string(),
180 })
181 }
182
183 fn to_swift_string(&self) -> String {
184 format!(":57C:/{}", self.party_identifier)
185 }
186}
187
188#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
193pub struct Field57D {
194 pub party_identifier: Option<String>,
196
197 pub name_and_address: Vec<String>,
199}
200
201impl SwiftField for Field57D {
202 fn parse(input: &str) -> crate::Result<Self>
203 where
204 Self: Sized,
205 {
206 let lines: Vec<&str> = input.lines().collect();
207
208 if lines.is_empty() {
209 return Err(ParseError::InvalidFormat {
210 message: "Field 57D must have at least one line".to_string(),
211 });
212 }
213
214 let mut party_identifier = None;
215 let mut start_idx = 0;
216
217 if let Some(party_id) = parse_party_identifier(lines[0])? {
219 party_identifier = Some(party_id);
220 start_idx = 1;
221 }
222
223 let name_and_address = parse_name_and_address(&lines, start_idx, "Field57D")?;
225
226 Ok(Field57D {
227 party_identifier,
228 name_and_address,
229 })
230 }
231
232 fn to_swift_string(&self) -> String {
233 let mut result = Vec::new();
234
235 if let Some(ref id) = self.party_identifier {
236 result.push(format!("/{}", id));
237 }
238
239 for line in &self.name_and_address {
240 result.push(line.clone());
241 }
242
243 format!(":57D:{}", result.join("\n"))
244 }
245}
246
247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
249pub enum Field57 {
250 #[serde(rename = "57A")]
251 A(Field57A),
252 #[serde(rename = "57B")]
253 B(Field57B),
254 #[serde(rename = "57C")]
255 C(Field57C),
256 #[serde(rename = "57D")]
257 D(Field57D),
258}
259
260impl SwiftField for Field57 {
261 fn parse(input: &str) -> crate::Result<Self>
262 where
263 Self: Sized,
264 {
265 if let Ok(field) = Field57A::parse(input) {
267 return Ok(Field57::A(field));
268 }
269
270 if input.starts_with('/')
272 && !input.contains('\n')
273 && let Ok(field) = Field57C::parse(input)
274 {
275 return Ok(Field57::C(field));
276 }
277
278 if let Ok(field) = Field57B::parse(input) {
280 return Ok(Field57::B(field));
281 }
282
283 if let Ok(field) = Field57D::parse(input) {
285 return Ok(Field57::D(field));
286 }
287
288 Err(ParseError::InvalidFormat {
289 message: "Field 57 could not be parsed as option A, B, C or D".to_string(),
290 })
291 }
292
293 fn parse_with_variant(
294 value: &str,
295 variant: Option<&str>,
296 _field_tag: Option<&str>,
297 ) -> crate::Result<Self>
298 where
299 Self: Sized,
300 {
301 match variant {
302 Some("A") => {
303 let field = Field57A::parse(value)?;
304 Ok(Field57::A(field))
305 }
306 Some("B") => {
307 let field = Field57B::parse(value)?;
308 Ok(Field57::B(field))
309 }
310 Some("C") => {
311 let field = Field57C::parse(value)?;
312 Ok(Field57::C(field))
313 }
314 Some("D") => {
315 let field = Field57D::parse(value)?;
316 Ok(Field57::D(field))
317 }
318 _ => {
319 Self::parse(value)
321 }
322 }
323 }
324
325 fn to_swift_string(&self) -> String {
326 match self {
327 Field57::A(field) => field.to_swift_string(),
328 Field57::B(field) => field.to_swift_string(),
329 Field57::C(field) => field.to_swift_string(),
330 Field57::D(field) => field.to_swift_string(),
331 }
332 }
333
334 fn get_variant_tag(&self) -> Option<&'static str> {
335 match self {
336 Field57::A(_) => Some("A"),
337 Field57::B(_) => Some("B"),
338 Field57::C(_) => Some("C"),
339 Field57::D(_) => Some("D"),
340 }
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347
348 #[test]
349 fn test_field57a() {
350 let field = Field57A::parse("//FW123456\nDEUTDEFF").unwrap();
352 assert_eq!(field.party_identifier, Some("/FW123456".to_string()));
353 assert_eq!(field.bic, "DEUTDEFF");
354
355 let field = Field57A::parse("/C/US123456\nDEUTDEFF").unwrap();
357 assert_eq!(field.party_identifier, Some("C/US123456".to_string()));
358 assert_eq!(field.bic, "DEUTDEFF");
359
360 let field = Field57A::parse("CHASUS33XXX").unwrap();
362 assert_eq!(field.party_identifier, None);
363 assert_eq!(field.bic, "CHASUS33XXX");
364 }
365
366 #[test]
367 fn test_field57b() {
368 let field = Field57B::parse("/A/12345\nNEW YORK").unwrap();
370 assert_eq!(field.party_identifier, Some("A/12345".to_string()));
371 assert_eq!(field.location, Some("NEW YORK".to_string()));
372
373 let field = Field57B::parse("").unwrap();
375 assert_eq!(field.party_identifier, None);
376 assert_eq!(field.location, None);
377 }
378
379 #[test]
380 fn test_field57c() {
381 let field = Field57C::parse("/UKCLEARING123").unwrap();
382 assert_eq!(field.party_identifier, "UKCLEARING123");
383 assert_eq!(field.to_swift_string(), ":57C:/UKCLEARING123");
384 }
385
386 #[test]
387 fn test_field57d() {
388 let field = Field57D::parse("//FW\nCHASE BANK\nNEW YORK").unwrap();
390 assert_eq!(field.party_identifier, Some("/FW".to_string()));
391 assert_eq!(field.name_and_address.len(), 2);
392 assert_eq!(field.name_and_address[0], "CHASE BANK");
393
394 let field = Field57D::parse("BENEFICIARY BANK\nLONDON").unwrap();
396 assert_eq!(field.party_identifier, None);
397 assert_eq!(field.name_and_address.len(), 2);
398 }
399
400 #[test]
401 fn test_field57_payment_method_codes() {
402 let field = Field57A::parse("//FW\nCHASUS33").unwrap();
404 assert_eq!(field.party_identifier, Some("/FW".to_string()));
405
406 let field = Field57A::parse("//RT\nDEUTDEFF").unwrap();
408 assert_eq!(field.party_identifier, Some("/RT".to_string()));
409
410 let field = Field57A::parse("//AU\nANZBAU3M").unwrap();
412 assert_eq!(field.party_identifier, Some("/AU".to_string()));
413
414 let field = Field57A::parse("//IN\nHDFCINBB").unwrap();
416 assert_eq!(field.party_identifier, Some("/IN".to_string()));
417 }
418
419 #[test]
420 fn test_field57_invalid() {
421 assert!(Field57A::parse("INVALID").is_err());
423
424 assert!(Field57C::parse("NOSLASH").is_err());
426
427 assert!(Field57D::parse("LINE1\nLINE2\nLINE3\nLINE4\nLINE5").is_err());
429 }
430}
431
432pub type Field57AccountWithInstitution = Field57;
434pub type Field57DebtorBank = Field57;
435
436#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
441pub enum Field57DebtInstitution {
442 #[serde(rename = "57A")]
443 A(Field57A),
444 #[serde(rename = "57B")]
445 B(Field57B),
446 #[serde(rename = "57D")]
447 D(Field57D),
448}
449
450impl SwiftField for Field57DebtInstitution {
451 fn parse(input: &str) -> crate::Result<Self>
452 where
453 Self: Sized,
454 {
455 if let Ok(field) = Field57A::parse(input) {
457 return Ok(Field57DebtInstitution::A(field));
458 }
459
460 if let Ok(field) = Field57B::parse(input) {
462 return Ok(Field57DebtInstitution::B(field));
463 }
464
465 if let Ok(field) = Field57D::parse(input) {
467 return Ok(Field57DebtInstitution::D(field));
468 }
469
470 Err(ParseError::InvalidFormat {
471 message: "Field 57 must be one of formats: 57A (party + BIC), 57B (party + location), or 57D (name + address)".to_string(),
472 })
473 }
474
475 fn parse_with_variant(
476 value: &str,
477 variant: Option<&str>,
478 _field_tag: Option<&str>,
479 ) -> crate::Result<Self>
480 where
481 Self: Sized,
482 {
483 match variant {
484 Some("A") => {
485 let field = Field57A::parse(value)?;
486 Ok(Field57DebtInstitution::A(field))
487 }
488 Some("B") => {
489 let field = Field57B::parse(value)?;
490 Ok(Field57DebtInstitution::B(field))
491 }
492 Some("D") => {
493 let field = Field57D::parse(value)?;
494 Ok(Field57DebtInstitution::D(field))
495 }
496 _ => {
497 Self::parse(value)
499 }
500 }
501 }
502
503 fn to_swift_string(&self) -> String {
504 match self {
505 Field57DebtInstitution::A(field) => field.to_swift_string(),
506 Field57DebtInstitution::B(field) => field.to_swift_string(),
507 Field57DebtInstitution::D(field) => field.to_swift_string(),
508 }
509 }
510}
511
512pub type Field57AccountWithABD = Field57DebtInstitution;