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)]
12pub struct Field52A {
13 #[serde(skip_serializing_if = "Option::is_none")]
15 pub party_identifier: Option<String>,
16
17 pub bic: String,
19}
20
21impl SwiftField for Field52A {
22 fn parse(input: &str) -> crate::Result<Self>
23 where
24 Self: Sized,
25 {
26 let lines: Vec<&str> = input.lines().collect();
27
28 if lines.is_empty() {
29 return Err(ParseError::InvalidFormat {
30 message: "Field 52A cannot be empty".to_string(),
31 });
32 }
33
34 let mut party_identifier = None;
35 let mut bic_line_idx = 0;
36
37 if let Some(party_id) = parse_party_identifier(lines[0])? {
39 party_identifier = Some(party_id);
40 bic_line_idx = 1;
41 }
42
43 if bic_line_idx >= lines.len() {
45 return Err(ParseError::InvalidFormat {
46 message: "Field 52A missing BIC code".to_string(),
47 });
48 }
49
50 let bic = parse_bic(lines[bic_line_idx])?;
51
52 Ok(Field52A {
53 party_identifier,
54 bic,
55 })
56 }
57
58 fn to_swift_string(&self) -> String {
59 let mut lines = Vec::new();
60
61 if let Some(ref id) = self.party_identifier {
62 lines.push(format!("/{}", id));
63 }
64
65 lines.push(self.bic.clone());
66 format!(":52A:{}", lines.join("\n"))
67 }
68}
69
70#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
75pub struct Field52B {
76 pub party_identifier: Option<String>,
78
79 pub location: Option<String>,
81}
82
83impl SwiftField for Field52B {
84 fn parse(input: &str) -> crate::Result<Self>
85 where
86 Self: Sized,
87 {
88 if input.is_empty() {
89 return Ok(Field52B {
90 party_identifier: None,
91 location: None,
92 });
93 }
94
95 let lines: Vec<&str> = input.lines().collect();
96 let mut party_identifier = None;
97 let mut location = None;
98 let mut current_idx = 0;
99
100 if !lines.is_empty() && lines[0].starts_with('/') {
102 let line = &lines[0][1..]; if let Some(slash_pos) = line.find('/') {
106 let code = &line[..slash_pos];
107 let id = &line[slash_pos + 1..];
108
109 if code.len() == 1
110 && code.chars().all(|c| c.is_ascii_alphabetic())
111 && id.len() <= 34
112 {
113 parse_swift_chars(id, "Field 52B party identifier")?;
114 party_identifier = Some(format!("{}/{}", code, id));
115 current_idx = 1;
116 }
117 } else if line.len() <= 34 {
118 parse_swift_chars(line, "Field 52B party identifier")?;
120 party_identifier = Some(line.to_string());
121 current_idx = 1;
122 }
123 }
124
125 if current_idx < lines.len() {
127 let loc = lines[current_idx];
128 if !loc.is_empty() && loc.len() <= 35 {
129 parse_swift_chars(loc, "Field 52B location")?;
130 location = Some(loc.to_string());
131 }
132 }
133
134 Ok(Field52B {
135 party_identifier,
136 location,
137 })
138 }
139
140 fn to_swift_string(&self) -> String {
141 let mut result = Vec::new();
142
143 if let Some(ref id) = self.party_identifier {
144 result.push(format!("/{}", id));
145 }
146
147 if let Some(ref loc) = self.location {
148 result.push(loc.clone());
149 }
150
151 format!(":52B:{}", result.join("\n"))
152 }
153}
154
155#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
160pub struct Field52C {
161 pub party_identifier: String,
163}
164
165impl SwiftField for Field52C {
166 fn parse(input: &str) -> crate::Result<Self>
167 where
168 Self: Sized,
169 {
170 if !input.starts_with('/') {
171 return Err(ParseError::InvalidFormat {
172 message: "Field 52C must start with '/'".to_string(),
173 });
174 }
175
176 let identifier = &input[1..];
177
178 if identifier.is_empty() || identifier.len() > 34 {
179 return Err(ParseError::InvalidFormat {
180 message: "Field 52C party identifier must be 1-34 characters".to_string(),
181 });
182 }
183
184 parse_swift_chars(identifier, "Field 52C party identifier")?;
185
186 Ok(Field52C {
187 party_identifier: identifier.to_string(),
188 })
189 }
190
191 fn to_swift_string(&self) -> String {
192 format!(":52C:/{}", self.party_identifier)
193 }
194}
195
196#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
201pub struct Field52D {
202 pub party_identifier: Option<String>,
204
205 pub name_and_address: Vec<String>,
207}
208
209impl SwiftField for Field52D {
210 fn parse(input: &str) -> crate::Result<Self>
211 where
212 Self: Sized,
213 {
214 let lines: Vec<&str> = input.lines().collect();
215
216 if lines.is_empty() {
217 return Err(ParseError::InvalidFormat {
218 message: "Field 52D must have at least one line".to_string(),
219 });
220 }
221
222 let mut party_identifier = None;
223 let mut start_idx = 0;
224
225 if lines[0].starts_with('/') {
227 let line = &lines[0][1..]; if let Some(slash_pos) = line.find('/') {
231 let code = &line[..slash_pos];
232 let id = &line[slash_pos + 1..];
233
234 if code.len() == 1
235 && code.chars().all(|c| c.is_ascii_alphabetic())
236 && id.len() <= 34
237 {
238 parse_swift_chars(id, "Field 52D party identifier")?;
239 party_identifier = Some(format!("{}/{}", code, id));
240 start_idx = 1;
241 }
242 } else if line.len() <= 34 {
243 parse_swift_chars(line, "Field 52D party identifier")?;
245 party_identifier = Some(line.to_string());
246 start_idx = 1;
247 }
248 }
249
250 let name_and_address = parse_name_and_address(&lines, start_idx, "Field 52D")?;
252
253 Ok(Field52D {
254 party_identifier,
255 name_and_address,
256 })
257 }
258
259 fn to_swift_string(&self) -> String {
260 let mut lines = Vec::new();
261
262 if let Some(ref id) = self.party_identifier {
263 lines.push(format!("/{}", id));
264 }
265
266 for line in &self.name_and_address {
267 lines.push(line.clone());
268 }
269
270 format!(":52D:{}", lines.join("\n"))
271 }
272}
273
274#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
276pub enum Field52AccountServicingInstitution {
277 #[serde(rename = "52A")]
278 A(Field52A),
279 #[serde(rename = "52C")]
280 C(Field52C),
281}
282
283impl SwiftField for Field52AccountServicingInstitution {
284 fn parse(input: &str) -> crate::Result<Self>
285 where
286 Self: Sized,
287 {
288 if let Ok(field) = Field52A::parse(input) {
290 return Ok(Field52AccountServicingInstitution::A(field));
291 }
292
293 if let Ok(field) = Field52C::parse(input) {
295 return Ok(Field52AccountServicingInstitution::C(field));
296 }
297
298 Err(ParseError::InvalidFormat {
299 message: "Field 52 Account Servicing Institution could not be parsed as option A or C"
300 .to_string(),
301 })
302 }
303
304 fn parse_with_variant(
305 value: &str,
306 variant: Option<&str>,
307 _field_tag: Option<&str>,
308 ) -> crate::Result<Self>
309 where
310 Self: Sized,
311 {
312 match variant {
313 Some("A") => {
314 let field = Field52A::parse(value)?;
315 Ok(Field52AccountServicingInstitution::A(field))
316 }
317 Some("C") => {
318 let field = Field52C::parse(value)?;
319 Ok(Field52AccountServicingInstitution::C(field))
320 }
321 _ => {
322 Self::parse(value)
324 }
325 }
326 }
327
328 fn to_swift_string(&self) -> String {
329 match self {
330 Field52AccountServicingInstitution::A(field) => field.to_swift_string(),
331 Field52AccountServicingInstitution::C(field) => field.to_swift_string(),
332 }
333 }
334}
335
336#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
338pub enum Field52OrderingInstitution {
339 #[serde(rename = "52A")]
340 A(Field52A),
341 #[serde(rename = "52D")]
342 D(Field52D),
343}
344
345impl SwiftField for Field52OrderingInstitution {
346 fn parse(input: &str) -> crate::Result<Self>
347 where
348 Self: Sized,
349 {
350 if let Ok(field) = Field52A::parse(input) {
352 return Ok(Field52OrderingInstitution::A(field));
353 }
354
355 if let Ok(field) = Field52D::parse(input) {
357 return Ok(Field52OrderingInstitution::D(field));
358 }
359
360 Err(ParseError::InvalidFormat {
361 message: "Field 52 Ordering Institution could not be parsed as option A or D"
362 .to_string(),
363 })
364 }
365
366 fn parse_with_variant(
367 value: &str,
368 variant: Option<&str>,
369 _field_tag: Option<&str>,
370 ) -> crate::Result<Self>
371 where
372 Self: Sized,
373 {
374 match variant {
375 Some("A") => {
376 let field = Field52A::parse(value)?;
377 Ok(Field52OrderingInstitution::A(field))
378 }
379 Some("D") => {
380 let field = Field52D::parse(value)?;
381 Ok(Field52OrderingInstitution::D(field))
382 }
383 _ => {
384 Self::parse(value)
386 }
387 }
388 }
389
390 fn to_swift_string(&self) -> String {
391 match self {
392 Field52OrderingInstitution::A(field) => field.to_swift_string(),
393 Field52OrderingInstitution::D(field) => field.to_swift_string(),
394 }
395 }
396
397 fn get_variant_tag(&self) -> Option<&'static str> {
398 match self {
399 Field52OrderingInstitution::A(_) => Some("A"),
400 Field52OrderingInstitution::D(_) => Some("D"),
401 }
402 }
403}
404
405#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
407pub enum Field52CreditorBank {
408 #[serde(rename = "52A")]
409 A(Field52A),
410 #[serde(rename = "52C")]
411 C(Field52C),
412 #[serde(rename = "52D")]
413 D(Field52D),
414}
415
416impl SwiftField for Field52CreditorBank {
417 fn parse(input: &str) -> crate::Result<Self>
418 where
419 Self: Sized,
420 {
421 if let Ok(field) = Field52A::parse(input) {
423 return Ok(Field52CreditorBank::A(field));
424 }
425
426 if input.starts_with('/')
428 && !input.contains('\n')
429 && let Ok(field) = Field52C::parse(input)
430 {
431 return Ok(Field52CreditorBank::C(field));
432 }
433
434 if let Ok(field) = Field52D::parse(input) {
436 return Ok(Field52CreditorBank::D(field));
437 }
438
439 Err(ParseError::InvalidFormat {
440 message: "Field 52 Creditor Bank could not be parsed as option A, C or D".to_string(),
441 })
442 }
443
444 fn parse_with_variant(
445 value: &str,
446 variant: Option<&str>,
447 _field_tag: Option<&str>,
448 ) -> crate::Result<Self>
449 where
450 Self: Sized,
451 {
452 match variant {
453 Some("A") => {
454 let field = Field52A::parse(value)?;
455 Ok(Field52CreditorBank::A(field))
456 }
457 Some("C") => {
458 let field = Field52C::parse(value)?;
459 Ok(Field52CreditorBank::C(field))
460 }
461 Some("D") => {
462 let field = Field52D::parse(value)?;
463 Ok(Field52CreditorBank::D(field))
464 }
465 _ => {
466 Self::parse(value)
468 }
469 }
470 }
471
472 fn to_swift_string(&self) -> String {
473 match self {
474 Field52CreditorBank::A(field) => field.to_swift_string(),
475 Field52CreditorBank::C(field) => field.to_swift_string(),
476 Field52CreditorBank::D(field) => field.to_swift_string(),
477 }
478 }
479}
480
481#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
483pub enum Field52DrawerBank {
484 #[serde(rename = "52A")]
485 A(Field52A),
486 #[serde(rename = "52B")]
487 B(Field52B),
488 #[serde(rename = "52D")]
489 D(Field52D),
490}
491
492impl SwiftField for Field52DrawerBank {
493 fn parse(input: &str) -> crate::Result<Self>
494 where
495 Self: Sized,
496 {
497 if let Ok(field) = Field52A::parse(input) {
499 return Ok(Field52DrawerBank::A(field));
500 }
501
502 if let Ok(field) = Field52B::parse(input) {
504 return Ok(Field52DrawerBank::B(field));
505 }
506
507 if let Ok(field) = Field52D::parse(input) {
509 return Ok(Field52DrawerBank::D(field));
510 }
511
512 Err(ParseError::InvalidFormat {
513 message: "Field 52 Drawer Bank could not be parsed as option A, B or D".to_string(),
514 })
515 }
516
517 fn parse_with_variant(
518 value: &str,
519 variant: Option<&str>,
520 _field_tag: Option<&str>,
521 ) -> crate::Result<Self>
522 where
523 Self: Sized,
524 {
525 match variant {
526 Some("A") => {
527 let field = Field52A::parse(value)?;
528 Ok(Field52DrawerBank::A(field))
529 }
530 Some("B") => {
531 let field = Field52B::parse(value)?;
532 Ok(Field52DrawerBank::B(field))
533 }
534 Some("D") => {
535 let field = Field52D::parse(value)?;
536 Ok(Field52DrawerBank::D(field))
537 }
538 _ => {
539 Self::parse(value)
541 }
542 }
543 }
544
545 fn to_swift_string(&self) -> String {
546 match self {
547 Field52DrawerBank::A(field) => field.to_swift_string(),
548 Field52DrawerBank::B(field) => field.to_swift_string(),
549 Field52DrawerBank::D(field) => field.to_swift_string(),
550 }
551 }
552}
553
554#[cfg(test)]
555mod tests {
556 use super::*;
557
558 #[test]
559 fn test_field52a() {
560 let field = Field52A::parse("/C/US123456\nDEUTDEFF").unwrap();
562 assert_eq!(field.party_identifier, Some("C/US123456".to_string()));
563 assert_eq!(field.bic, "DEUTDEFF");
564
565 let field = Field52A::parse("CHASUS33XXX").unwrap();
567 assert_eq!(field.party_identifier, None);
568 assert_eq!(field.bic, "CHASUS33XXX");
569 }
570
571 #[test]
572 fn test_field52b() {
573 let field = Field52B::parse("/A/12345\nNEW YORK").unwrap();
575 assert_eq!(field.party_identifier, Some("A/12345".to_string()));
576 assert_eq!(field.location, Some("NEW YORK".to_string()));
577
578 let field = Field52B::parse("").unwrap();
580 assert_eq!(field.party_identifier, None);
581 assert_eq!(field.location, None);
582 }
583
584 #[test]
585 fn test_field52c() {
586 let field = Field52C::parse("/UKCLEARING123").unwrap();
587 assert_eq!(field.party_identifier, "UKCLEARING123");
588 assert_eq!(field.to_swift_string(), ":52C:/UKCLEARING123");
589 }
590
591 #[test]
592 fn test_field52d() {
593 let field = Field52D::parse("/D/DE123456\nDEUTSCHE BANK\nFRANKFURT").unwrap();
595 assert_eq!(field.party_identifier, Some("D/DE123456".to_string()));
596 assert_eq!(field.name_and_address.len(), 2);
597 assert_eq!(field.name_and_address[0], "DEUTSCHE BANK");
598
599 let field = Field52D::parse("ACME BANK\nLONDON").unwrap();
601 assert_eq!(field.party_identifier, None);
602 assert_eq!(field.name_and_address.len(), 2);
603 }
604
605 #[test]
606 fn test_field52_invalid() {
607 assert!(Field52A::parse("INVALID").is_err());
609
610 assert!(Field52C::parse("NOSLASH").is_err());
612
613 assert!(Field52D::parse("LINE1\nLINE2\nLINE3\nLINE4\nLINE5").is_err());
615 }
616}