1use crate::common::BIC;
2use crate::{SwiftField, ValidationError, ValidationResult};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
125pub struct Field58A {
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub account_line_indicator: Option<String>,
129 #[serde(skip_serializing_if = "Option::is_none")]
131 pub account_number: Option<String>,
132 #[serde(flatten)]
134 pub bic: BIC,
135}
136
137impl Field58A {
138 pub fn new(
140 account_line_indicator: Option<String>,
141 account_number: Option<String>,
142 bic: impl Into<String>,
143 ) -> Result<Self, crate::ParseError> {
144 let bic = bic.into().to_uppercase();
145
146 if let Some(ref indicator) = account_line_indicator {
148 if indicator.is_empty() {
149 return Err(crate::ParseError::InvalidFieldFormat {
150 field_tag: "58A".to_string(),
151 message: "Account line indicator cannot be empty if specified".to_string(),
152 });
153 }
154
155 if indicator.len() != 1 {
156 return Err(crate::ParseError::InvalidFieldFormat {
157 field_tag: "58A".to_string(),
158 message: "Account line indicator must be exactly 1 character".to_string(),
159 });
160 }
161
162 if !indicator.chars().all(|c| c.is_ascii() && !c.is_control()) {
163 return Err(crate::ParseError::InvalidFieldFormat {
164 field_tag: "58A".to_string(),
165 message: "Account line indicator contains invalid characters".to_string(),
166 });
167 }
168 }
169
170 if let Some(ref account) = account_number {
172 if account.is_empty() {
173 return Err(crate::ParseError::InvalidFieldFormat {
174 field_tag: "58A".to_string(),
175 message: "Account number cannot be empty if specified".to_string(),
176 });
177 }
178
179 if account.len() > 34 {
180 return Err(crate::ParseError::InvalidFieldFormat {
181 field_tag: "58A".to_string(),
182 message: "Account number too long (max 34 characters)".to_string(),
183 });
184 }
185
186 if !account.chars().all(|c| c.is_ascii() && !c.is_control()) {
187 return Err(crate::ParseError::InvalidFieldFormat {
188 field_tag: "58A".to_string(),
189 message: "Account number contains invalid characters".to_string(),
190 });
191 }
192 }
193
194 let parsed_bic = BIC::parse(&bic, Some("58A"))?;
196
197 Ok(Field58A {
198 account_line_indicator,
199 account_number,
200 bic: parsed_bic,
201 })
202 }
203
204 pub fn account_line_indicator(&self) -> Option<&str> {
206 self.account_line_indicator.as_deref()
207 }
208
209 pub fn account_number(&self) -> Option<&str> {
211 self.account_number.as_deref()
212 }
213
214 pub fn bic(&self) -> &str {
216 self.bic.value()
217 }
218
219 pub fn is_full_bic(&self) -> bool {
221 self.bic.is_full_bic()
222 }
223
224 pub fn bank_code(&self) -> &str {
226 self.bic.bank_code()
227 }
228
229 pub fn country_code(&self) -> &str {
231 self.bic.country_code()
232 }
233
234 pub fn location_code(&self) -> &str {
236 self.bic.location_code()
237 }
238
239 pub fn branch_code(&self) -> Option<&str> {
241 self.bic.branch_code()
242 }
243
244 pub fn description(&self) -> String {
246 match &self.account_number {
247 Some(account) => format!(
248 "Beneficiary Institution: {} ({})",
249 self.bic.value(),
250 account
251 ),
252 None => format!("Beneficiary Institution: {}", self.bic.value()),
253 }
254 }
255
256 pub fn is_major_financial_center(&self) -> bool {
258 let country = self.country_code();
259 let location = self.location_code();
260
261 matches!(
262 (country, location),
263 ("US", "33") | ("GB", "22") | ("DE", "FF") | ("JP", "22") | ("HK", "HK") | ("SG", "SG") | ("FR", "PP") | ("CH", "ZZ") | ("CA", "TT") | ("AU", "MM") )
274 }
275
276 pub fn is_retail_bank(&self) -> bool {
278 let bank_code = self.bank_code();
279
280 matches!(
282 bank_code,
283 "CHAS" | "BOFA" | "WELL" | "CITI" | "HSBC" | "BARC" | "LLOY" | "NATS" | "DEUT" | "COMM" | "BNPA" | "CRED" | "UBSW" | "CRSU" | "ROYA" | "TDOM" | "ANZI" | "CTBA" | "WEST" | "MUFG" | "SMBC" | "MIZB" )
306 }
307
308 pub fn supports_real_time_payments(&self) -> bool {
310 let country = self.country_code();
311
312 matches!(
314 country,
315 "US" | "GB" | "DE" | "NL" | "SE" | "DK" | "AU" | "SG" | "IN" | "BR" | "MX" | "JP" | "KR" | "CN" )
330 }
331
332 pub fn regulatory_jurisdiction(&self) -> &'static str {
334 match self.country_code() {
335 "US" => "Federal Reserve / OCC / FDIC",
336 "GB" => "Bank of England / PRA / FCA",
337 "DE" => "BaFin / ECB",
338 "FR" => "ACPR / ECB",
339 "JP" => "JFSA / Bank of Japan",
340 "CH" => "FINMA / SNB",
341 "CA" => "OSFI / Bank of Canada",
342 "AU" => "APRA / RBA",
343 "SG" => "MAS",
344 "HK" => "HKMA",
345 "CN" => "PBOC / CBIRC",
346 "IN" => "RBI",
347 "BR" => "Central Bank of Brazil",
348 "MX" => "CNBV / Banxico",
349 _ => "Other National Authority",
350 }
351 }
352}
353
354impl SwiftField for Field58A {
355 fn parse(value: &str) -> Result<Self, crate::ParseError> {
356 let content = if let Some(stripped) = value.strip_prefix(":58A:") {
357 stripped
358 } else if let Some(stripped) = value.strip_prefix("58A:") {
359 stripped
360 } else {
361 value
362 };
363
364 let content = content.trim();
365
366 if content.is_empty() {
367 return Err(crate::ParseError::InvalidFieldFormat {
368 field_tag: "58A".to_string(),
369 message: "Field content cannot be empty".to_string(),
370 });
371 }
372
373 let account_line_indicator = None;
374 let mut account_number = None;
375 let bic;
376
377 if content.starts_with('/') {
378 let lines: Vec<&str> = content.lines().collect();
379
380 if lines.len() == 1 {
381 let parts: Vec<&str> = lines[0].splitn(2, ' ').collect();
383 if parts.len() == 2 {
384 account_number = Some(parts[0][1..].to_string());
385 bic = parts[1].to_string();
386 } else {
387 return Err(crate::ParseError::InvalidFieldFormat {
388 field_tag: "58A".to_string(),
389 message: "Invalid format: expected account and BIC".to_string(),
390 });
391 }
392 } else if lines.len() == 2 {
393 account_number = Some(lines[0][1..].to_string());
395 bic = lines[1].to_string();
396 } else {
397 return Err(crate::ParseError::InvalidFieldFormat {
398 field_tag: "58A".to_string(),
399 message: "Invalid format: too many lines".to_string(),
400 });
401 }
402 } else {
403 bic = content.to_string();
405 }
406
407 Self::new(account_line_indicator, account_number, bic)
408 }
409
410 fn to_swift_string(&self) -> String {
411 match &self.account_number {
412 Some(account) => format!(":58A:/{}\n{}", account, self.bic.value()),
413 None => format!(":58A:{}", self.bic.value()),
414 }
415 }
416
417 fn validate(&self) -> ValidationResult {
418 let mut errors = Vec::new();
419 let mut warnings = Vec::new();
420
421 if let Some(ref account) = self.account_number {
422 if account.is_empty() {
423 errors.push(ValidationError::ValueValidation {
424 field_tag: "58A".to_string(),
425 message: "Account number cannot be empty if specified".to_string(),
426 });
427 }
428
429 if account.len() > 34 {
430 errors.push(ValidationError::LengthValidation {
431 field_tag: "58A".to_string(),
432 expected: "max 34 characters".to_string(),
433 actual: account.len(),
434 });
435 }
436 }
437
438 let bic_validation = self.bic.validate();
440 if !bic_validation.is_valid {
441 errors.extend(bic_validation.errors);
442 }
443
444 if !self.is_major_financial_center() {
446 warnings.push("Beneficiary institution is not in a major financial center".to_string());
447 }
448
449 ValidationResult {
450 is_valid: errors.is_empty(),
451 errors,
452 warnings,
453 }
454 }
455
456 fn format_spec() -> &'static str {
457 "[/34x]4!a2!a2!c[3!c]"
458 }
459}
460
461impl std::fmt::Display for Field58A {
462 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
463 match &self.account_number {
464 Some(account) => write!(f, "/{} {}", account, self.bic.value()),
465 None => write!(f, "{}", self.bic.value()),
466 }
467 }
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473
474 #[test]
475 fn test_field58a_creation() {
476 let field = Field58A::new(None, None, "DEUTDEFF").unwrap();
477 assert_eq!(field.bic(), "DEUTDEFF");
478 assert!(field.account_number().is_none());
479 assert!(!field.is_full_bic());
480 }
481
482 #[test]
483 fn test_field58a_with_account() {
484 let field = Field58A::new(None, Some("1234567890".to_string()), "DEUTDEFF500").unwrap();
485 assert_eq!(field.bic(), "DEUTDEFF500");
486 assert_eq!(field.account_number(), Some("1234567890"));
487 assert!(field.is_full_bic());
488 }
489
490 #[test]
491 fn test_field58a_with_account_line_indicator() {
492 let field = Field58A::new(
493 Some("A".to_string()),
494 Some("1234567890".to_string()),
495 "CHASUS33XXX",
496 )
497 .unwrap();
498 assert_eq!(field.bic(), "CHASUS33XXX");
499 assert_eq!(field.account_number(), Some("1234567890"));
500 assert_eq!(field.account_line_indicator(), Some("A"));
501 assert!(field.is_full_bic());
502 }
503
504 #[test]
505 fn test_field58a_parse_bic_only() {
506 let field = Field58A::parse("CHASUS33").unwrap();
507 assert_eq!(field.bic(), "CHASUS33");
508 assert!(field.account_number().is_none());
509 }
510
511 #[test]
512 fn test_field58a_parse_with_account_single_line() {
513 let field = Field58A::parse("/1234567890 CHASUS33XXX").unwrap();
514 assert_eq!(field.bic(), "CHASUS33XXX");
515 assert_eq!(field.account_number(), Some("1234567890"));
516 }
517
518 #[test]
519 fn test_field58a_parse_with_account_two_lines() {
520 let field = Field58A::parse("/1234567890\nCHASUS33XXX").unwrap();
521 assert_eq!(field.bic(), "CHASUS33XXX");
522 assert_eq!(field.account_number(), Some("1234567890"));
523 }
524
525 #[test]
526 fn test_field58a_parse_with_prefix() {
527 let field = Field58A::parse(":58A:CHASUS33").unwrap();
528 assert_eq!(field.bic(), "CHASUS33");
529 }
530
531 #[test]
532 fn test_field58a_to_swift_string_bic_only() {
533 let field = Field58A::new(None, None, "DEUTDEFF").unwrap();
534 assert_eq!(field.to_swift_string(), ":58A:DEUTDEFF");
535 }
536
537 #[test]
538 fn test_field58a_to_swift_string_with_account() {
539 let field = Field58A::new(None, Some("1234567890".to_string()), "DEUTDEFF500").unwrap();
540 assert_eq!(field.to_swift_string(), ":58A:/1234567890\nDEUTDEFF500");
541 }
542
543 #[test]
544 fn test_field58a_bic_components() {
545 let field = Field58A::new(None, None, "CHASUS33XXX").unwrap();
546 assert_eq!(field.bank_code(), "CHAS");
547 assert_eq!(field.country_code(), "US");
548 assert_eq!(field.location_code(), "33");
549 assert_eq!(field.branch_code(), Some("XXX"));
550 }
551
552 #[test]
553 fn test_field58a_short_bic_components() {
554 let field = Field58A::new(None, None, "DEUTDEFF").unwrap();
555 assert_eq!(field.bank_code(), "DEUT");
556 assert_eq!(field.country_code(), "DE");
557 assert_eq!(field.location_code(), "FF");
558 assert_eq!(field.branch_code(), None);
559 }
560
561 #[test]
562 fn test_field58a_invalid_bic_length() {
563 let result = Field58A::new(None, None, "INVALID");
564 assert!(result.is_err());
565 assert!(
566 result
567 .unwrap_err()
568 .to_string()
569 .contains("8 or 11 characters")
570 );
571 }
572
573 #[test]
574 fn test_field58a_invalid_bic_format() {
575 let result = Field58A::new(None, None, "123AUSGG");
577 assert!(result.is_err());
578
579 let result = Field58A::new(None, None, "DEUT1EFF");
581 assert!(result.is_err());
582 }
583
584 #[test]
585 fn test_field58a_invalid_account() {
586 let long_account = "A".repeat(35);
588 let result = Field58A::new(None, Some(long_account), "DEUTDEFF");
589 assert!(result.is_err());
590 assert!(result.unwrap_err().to_string().contains("too long"));
591
592 let result = Field58A::new(None, Some("".to_string()), "DEUTDEFF");
594 assert!(result.is_err());
595 assert!(result.unwrap_err().to_string().contains("cannot be empty"));
596 }
597
598 #[test]
599 fn test_field58a_validation() {
600 let field = Field58A::new(None, Some("1234567890".to_string()), "DEUTDEFF").unwrap();
601 let validation = field.validate();
602 assert!(validation.is_valid);
603 }
604
605 #[test]
606 fn test_field58a_display() {
607 let field_bic_only = Field58A::new(None, None, "DEUTDEFF").unwrap();
608 assert_eq!(format!("{}", field_bic_only), "DEUTDEFF");
609
610 let field_with_account =
611 Field58A::new(None, Some("1234567890".to_string()), "DEUTDEFF").unwrap();
612 assert_eq!(format!("{}", field_with_account), "/1234567890 DEUTDEFF");
613 }
614
615 #[test]
616 fn test_field58a_description() {
617 let field = Field58A::new(None, None, "CHASUS33").unwrap();
618 assert_eq!(field.description(), "Beneficiary Institution: CHASUS33");
619
620 let field_with_account =
621 Field58A::new(None, Some("1234567890".to_string()), "CHASUS33").unwrap();
622 assert_eq!(
623 field_with_account.description(),
624 "Beneficiary Institution: CHASUS33 (1234567890)"
625 );
626 }
627
628 #[test]
629 fn test_field58a_major_financial_center() {
630 let ny_field = Field58A::new(None, None, "CHASUS33").unwrap();
631 assert!(ny_field.is_major_financial_center());
632
633 let london_field = Field58A::new(None, None, "BARCGB22").unwrap();
634 assert!(london_field.is_major_financial_center());
635
636 let small_field = Field58A::new(None, None, "TESTAA11").unwrap();
637 assert!(!small_field.is_major_financial_center());
638 }
639
640 #[test]
641 fn test_field58a_retail_bank() {
642 let chase_field = Field58A::new(None, None, "CHASUS33").unwrap();
643 assert!(chase_field.is_retail_bank());
644
645 let hsbc_field = Field58A::new(None, None, "HSBCGB2L").unwrap();
646 assert!(hsbc_field.is_retail_bank());
647
648 let unknown_field = Field58A::new(None, None, "TESTAA11").unwrap();
649 assert!(!unknown_field.is_retail_bank());
650 }
651
652 #[test]
653 fn test_field58a_real_time_payments() {
654 let us_field = Field58A::new(None, None, "CHASUS33").unwrap();
655 assert!(us_field.supports_real_time_payments());
656
657 let uk_field = Field58A::new(None, None, "BARCGB22").unwrap();
658 assert!(uk_field.supports_real_time_payments());
659
660 let other_field = Field58A::new(None, None, "TESTAA11").unwrap();
661 assert!(!other_field.supports_real_time_payments());
662 }
663
664 #[test]
665 fn test_field58a_regulatory_jurisdiction() {
666 let us_field = Field58A::new(None, None, "CHASUS33").unwrap();
667 assert_eq!(
668 us_field.regulatory_jurisdiction(),
669 "Federal Reserve / OCC / FDIC"
670 );
671
672 let uk_field = Field58A::new(None, None, "BARCGB22").unwrap();
673 assert_eq!(
674 uk_field.regulatory_jurisdiction(),
675 "Bank of England / PRA / FCA"
676 );
677
678 let de_field = Field58A::new(None, None, "DEUTDEFF").unwrap();
679 assert_eq!(de_field.regulatory_jurisdiction(), "BaFin / ECB");
680
681 let other_field = Field58A::new(None, None, "TESTAA11").unwrap();
682 assert_eq!(
683 other_field.regulatory_jurisdiction(),
684 "Other National Authority"
685 );
686 }
687
688 #[test]
689 fn test_field58a_case_normalization() {
690 let field = Field58A::new(None, None, "chasus33xxx").unwrap();
691 assert_eq!(field.bic(), "CHASUS33XXX");
692 }
693
694 #[test]
695 fn test_field58a_format_spec() {
696 assert_eq!(Field58A::format_spec(), "[/34x]4!a2!a2!c[3!c]");
697 }
698
699 #[test]
700 fn test_field58a_serialization() {
701 let field = Field58A::new(None, Some("1234567890".to_string()), "CHASUS33XXX").unwrap();
702
703 let json = serde_json::to_string(&field).unwrap();
705 let deserialized: Field58A = serde_json::from_str(&json).unwrap();
706
707 assert_eq!(field, deserialized);
708 assert_eq!(field.bic(), deserialized.bic());
709 assert_eq!(field.account_number(), deserialized.account_number());
710 }
711
712 #[test]
713 fn test_field58a_edge_cases() {
714 let field = Field58A::new(None, None, "TESTAA11").unwrap();
716 assert_eq!(field.bic().len(), 8);
717
718 let field = Field58A::new(None, None, "TESTAA11XXX").unwrap();
720 assert_eq!(field.bic().len(), 11);
721
722 let max_account = "A".repeat(34);
724 let field = Field58A::new(None, Some(max_account.clone()), "TESTAA11").unwrap();
725 assert_eq!(field.account_number(), Some(max_account.as_str()));
726 }
727
728 #[test]
729 fn test_field58a_comprehensive_validation() {
730 let valid_field = Field58A::new(None, Some("1234567890".to_string()), "CHASUS33").unwrap();
732 let validation = valid_field.validate();
733 assert!(validation.is_valid);
734
735 let result = Field58A::new(None, None, "INVALID");
737 assert!(result.is_err());
738 }
739
740 #[test]
741 fn test_field58a_business_logic_warnings() {
742 let field = Field58A::new(None, None, "TESTAA11").unwrap();
743 let validation = field.validate();
744
745 assert!(validation.is_valid);
747 }
750
751 #[test]
752 fn test_field58a_real_world_examples() {
753 let chase_field =
755 Field58A::new(None, Some("1234567890".to_string()), "CHASUS33XXX").unwrap();
756 assert_eq!(chase_field.bic(), "CHASUS33XXX");
757 assert_eq!(chase_field.bank_code(), "CHAS");
758 assert_eq!(chase_field.country_code(), "US");
759 assert!(chase_field.is_major_financial_center());
760 assert!(chase_field.is_retail_bank());
761 assert!(chase_field.supports_real_time_payments());
762
763 let deutsche_field = Field58A::new(None, None, "DEUTDEFF500").unwrap();
765 assert_eq!(deutsche_field.bic(), "DEUTDEFF500");
766 assert_eq!(deutsche_field.bank_code(), "DEUT");
767 assert_eq!(deutsche_field.country_code(), "DE");
768 assert!(deutsche_field.is_major_financial_center());
769 assert!(deutsche_field.is_retail_bank());
770 assert!(deutsche_field.supports_real_time_payments());
771 }
772}