swift_mt_message/fields/
field58a.rs

1use crate::common::BIC;
2use crate::{SwiftField, ValidationError, ValidationResult};
3use serde::{Deserialize, Serialize};
4
5/// # Field 58A: Beneficiary Institution
6///
7/// ## Overview
8/// Field 58A identifies the beneficiary institution in SWIFT payment messages using a BIC code.
9/// This field specifies the financial institution that will receive the funds on behalf of
10/// the beneficiary customer. The beneficiary institution is the final institution in the
11/// payment chain that will credit the beneficiary's account with the transferred funds.
12///
13/// ## Format Specification
14/// **Format**: `[/34x]4!a2!a2!c[3!c]`
15/// - **34x**: Optional account number (up to 34 characters)
16/// - **4!a2!a2!c[3!c]**: BIC code (8 or 11 characters)
17///   - **4!a**: Bank code (4 alphabetic characters)
18///   - **2!a**: Country code (2 alphabetic characters, ISO 3166-1)
19///   - **2!c**: Location code (2 alphanumeric characters)
20///   - **3!c**: Optional branch code (3 alphanumeric characters)
21///
22/// ## Structure
23/// ```text
24/// /1234567890123456789012345678901234
25/// CHASUS33XXX
26/// │       │││
27/// │       │└┴┴ Branch code (optional, XXX)
28/// │       └┴── Location code (2 chars, 33)
29/// │     └┴──── Country code (2 chars, US)
30/// │ └┴┴┴────── Bank code (4 chars, CHAS)
31/// └─────────── Account number (optional)
32/// ```
33///
34/// ## Field Components
35/// - **Account Number**: Institution's account for beneficiary funds (optional)
36/// - **BIC Code**: Business Identifier Code for beneficiary institution identification
37/// - **Bank Code**: 4-letter code identifying the beneficiary bank
38/// - **Country Code**: 2-letter ISO country code
39/// - **Location Code**: 2-character location identifier
40/// - **Branch Code**: 3-character branch identifier (optional)
41///
42/// ## Usage Context
43/// Field 58A is used in:
44/// - **MT202**: General Financial Institution Transfer
45/// - **MT202COV**: Cover for customer credit transfer
46/// - **MT205**: Financial Institution Transfer for its own account
47/// - **MT103**: Single Customer Credit Transfer (institutional beneficiary)
48/// - **MT200**: Financial Institution Transfer
49///
50/// ### Business Applications
51/// - **Final settlement**: Identifying the institution that will receive funds
52/// - **Beneficiary banking**: Establishing the beneficiary's banking relationship
53/// - **Account crediting**: Directing funds to the correct institution for crediting
54/// - **Cross-border payments**: Facilitating international transfers to beneficiaries
55/// - **Correspondent banking**: Managing final leg of correspondent banking chains
56/// - **Regulatory compliance**: Meeting beneficiary identification requirements
57/// - **Risk management**: Identifying final counterparty in payment chain
58///
59/// ## Examples
60/// ```text
61/// :58A:CHASUS33
62/// └─── JPMorgan Chase Bank, New York (beneficiary institution)
63///
64/// :58A:/BENEFICIARY123456789012345678901234
65/// DEUTDEFF500
66/// └─── Deutsche Bank AG, Frankfurt with beneficiary account
67///
68/// :58A:BARCGB22
69/// └─── Barclays Bank PLC, London (8-character BIC)
70///
71/// :58A:/FINAL001
72/// BNPAFRPP
73/// └─── BNP Paribas, Paris with final settlement account
74/// ```
75///
76/// ## BIC Code Structure
77/// - **8-character BIC**: BANKCCLL (Bank-Country-Location)
78/// - **11-character BIC**: BANKCCLLBBB (Bank-Country-Location-Branch)
79/// - **Bank Code**: 4 letters identifying the institution
80/// - **Country Code**: 2 letters (ISO 3166-1 alpha-2)
81/// - **Location Code**: 2 alphanumeric characters
82/// - **Branch Code**: 3 alphanumeric characters (optional)
83///
84/// ## Account Number Guidelines
85/// - **Format**: Up to 34 alphanumeric characters
86/// - **Content**: Beneficiary institution's account number or identifier
87/// - **Usage**: When specific account designation is required for settlement
88/// - **Omission**: When only institution identification is needed
89/// - **Purpose**: Facilitates direct settlement to specific account
90///
91/// ## Payment Chain Context
92/// In the payment chain hierarchy:
93/// - **Field 50**: Ordering Customer (originator)
94/// - **Field 52A/D**: Ordering Institution (sender's bank)
95/// - **Field 53A/B/D**: Sender's Correspondent (intermediate institution)
96/// - **Field 54A/B/D**: Receiver's Correspondent (intermediate institution)
97/// - **Field 55A/B/D**: Third Reimbursement Institution (intermediate institution)
98/// - **Field 56A/C/D**: Intermediary Institution (intermediate institution)
99/// - **Field 57A/B/C/D**: Account With Institution (final institution)
100/// - **Field 58A/D**: Beneficiary Institution (final beneficiary bank)
101/// - **Field 59**: Beneficiary Customer (final recipient)
102///
103/// ## Validation Rules
104/// 1. **BIC format**: Must be valid 8 or 11 character BIC code
105/// 2. **Bank code**: Must be 4 alphabetic characters
106/// 3. **Country code**: Must be 2 alphabetic characters
107/// 4. **Location code**: Must be 2 alphanumeric characters
108/// 5. **Branch code**: Must be 3 alphanumeric characters (if present)
109/// 6. **Account number**: Maximum 34 characters (if present)
110/// 7. **Character validation**: All components must be printable ASCII
111///
112/// ## Network Validated Rules (SWIFT Standards)
113/// - BIC must be valid and registered in SWIFT network (Error: T10)
114/// - BIC format must comply with ISO 13616 standards (Error: T11)
115/// - Account number cannot exceed 34 characters (Error: T14)
116/// - Bank code must be alphabetic only (Error: T15)
117/// - Country code must be valid ISO 3166-1 code (Error: T16)
118/// - Location code must be alphanumeric (Error: T17)
119/// - Branch code must be alphanumeric if present (Error: T18)
120/// - Field 58A alternative to 58D (Error: C58)
121/// - Institution must be capable of receiving funds (Error: C59)
122///
123
124#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
125pub struct Field58A {
126    /// Account line indicator (optional, 1 character)
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub account_line_indicator: Option<String>,
129    /// Account number (optional, up to 34 characters)
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub account_number: Option<String>,
132    /// BIC code (8 or 11 characters)
133    #[serde(flatten)]
134    pub bic: BIC,
135}
136
137impl Field58A {
138    /// Create a new Field58A with validation
139    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        // Validate account line indicator if present
147        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        // Validate account number if present
171        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        // Parse and validate BIC using the common structure
195        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    /// Get the account line indicator
205    pub fn account_line_indicator(&self) -> Option<&str> {
206        self.account_line_indicator.as_deref()
207    }
208
209    /// Get the account number
210    pub fn account_number(&self) -> Option<&str> {
211        self.account_number.as_deref()
212    }
213
214    /// Get the BIC code
215    pub fn bic(&self) -> &str {
216        self.bic.value()
217    }
218
219    /// Check if this is a full BIC (11 characters) or short BIC (8 characters)
220    pub fn is_full_bic(&self) -> bool {
221        self.bic.is_full_bic()
222    }
223
224    /// Get the bank code component of the BIC
225    pub fn bank_code(&self) -> &str {
226        self.bic.bank_code()
227    }
228
229    /// Get the country code component of the BIC
230    pub fn country_code(&self) -> &str {
231        self.bic.country_code()
232    }
233
234    /// Get the location code component of the BIC
235    pub fn location_code(&self) -> &str {
236        self.bic.location_code()
237    }
238
239    /// Get the branch code component of the BIC (if present)
240    pub fn branch_code(&self) -> Option<&str> {
241        self.bic.branch_code()
242    }
243
244    /// Get human-readable description
245    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    /// Check if this institution is in a major financial center
257    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") | // New York
264            ("GB", "22") | // London
265            ("DE", "FF") | // Frankfurt
266            ("JP", "22") | // Tokyo
267            ("HK", "HK") | // Hong Kong
268            ("SG", "SG") | // Singapore
269            ("FR", "PP") | // Paris
270            ("CH", "ZZ") | // Zurich
271            ("CA", "TT") | // Toronto
272            ("AU", "MM") // Melbourne/Sydney
273        )
274    }
275
276    /// Check if this is a retail banking institution
277    pub fn is_retail_bank(&self) -> bool {
278        let bank_code = self.bank_code();
279
280        // Common retail bank codes (this is a simplified check)
281        matches!(
282            bank_code,
283            "CHAS" | // Chase
284            "BOFA" | // Bank of America
285            "WELL" | // Wells Fargo
286            "CITI" | // Citibank
287            "HSBC" | // HSBC
288            "BARC" | // Barclays
289            "LLOY" | // Lloyds
290            "NATS" | // NatWest
291            "DEUT" | // Deutsche Bank
292            "COMM" | // Commerzbank
293            "BNPA" | // BNP Paribas
294            "CRED" | // Credit Agricole
295            "UBSW" | // UBS
296            "CRSU" | // Credit Suisse
297            "ROYA" | // Royal Bank of Canada
298            "TDOM" | // TD Bank
299            "ANZI" | // ANZ
300            "CTBA" | // Commonwealth Bank
301            "WEST" | // Westpac
302            "MUFG" | // MUFG Bank
303            "SMBC" | // Sumitomo Mitsui
304            "MIZB" // Mizuho Bank
305        )
306    }
307
308    /// Check if this institution supports real-time payments
309    pub fn supports_real_time_payments(&self) -> bool {
310        let country = self.country_code();
311
312        // Countries with major real-time payment systems
313        matches!(
314            country,
315            "US" | // FedNow, RTP
316            "GB" | // Faster Payments
317            "DE" | // Instant Payments
318            "NL" | // iDEAL
319            "SE" | // Swish
320            "DK" | // MobilePay
321            "AU" | // NPP
322            "SG" | // FAST
323            "IN" | // UPI
324            "BR" | // PIX
325            "MX" | // SPEI
326            "JP" | // Zengin
327            "KR" | // KFTC
328            "CN" // CIPS
329        )
330    }
331
332    /// Get the regulatory jurisdiction for this institution
333    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                // Single line format: /account BIC
382                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                // Two line format: /account \n BIC
394                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 only
404            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        // Validate BIC format using the common BIC validation
439        let bic_validation = self.bic.validate();
440        if !bic_validation.is_valid {
441            errors.extend(bic_validation.errors);
442        }
443
444        // Add business logic warnings
445        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        // Invalid bank code (contains numbers)
576        let result = Field58A::new(None, None, "123AUSGG");
577        assert!(result.is_err());
578
579        // Invalid country code (contains numbers)
580        let result = Field58A::new(None, None, "DEUT1EFF");
581        assert!(result.is_err());
582    }
583
584    #[test]
585    fn test_field58a_invalid_account() {
586        // Account too long
587        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        // Empty account
593        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        // Test JSON serialization
704        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        // Test minimum BIC length
715        let field = Field58A::new(None, None, "TESTAA11").unwrap();
716        assert_eq!(field.bic().len(), 8);
717
718        // Test maximum BIC length
719        let field = Field58A::new(None, None, "TESTAA11XXX").unwrap();
720        assert_eq!(field.bic().len(), 11);
721
722        // Test maximum account length
723        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        // Test valid field
731        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        // Test invalid BIC - this should fail in constructor
736        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        // Should be valid but may have warnings
746        assert!(validation.is_valid);
747        // May have warnings about not being in major financial center
748        // This depends on the implementation details
749    }
750
751    #[test]
752    fn test_field58a_real_world_examples() {
753        // Test major US bank
754        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        // Test major European bank
764        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}