swift_mt_message/fields/
field57a.rs

1use crate::common::BIC;
2use crate::{SwiftField, ValidationError, ValidationResult};
3use serde::{Deserialize, Serialize};
4
5/// # Field 57A: Account With Institution
6///
7/// ## Overview
8/// Field 57A identifies the account with institution in SWIFT payment messages using a BIC code.
9/// This field specifies the financial institution where the beneficiary maintains their account
10/// or where the final credit should be made. The account with institution is typically the
11/// beneficiary's bank and represents the final destination in the payment routing chain,
12/// playing a crucial role in completing the payment transaction.
13///
14/// ## Format Specification
15/// **Format**: `[/34x]4!a2!a2!c[3!c]`
16/// - **34x**: Optional account number (up to 34 characters)
17/// - **4!a2!a2!c[3!c]**: BIC code (8 or 11 characters)
18///   - **4!a**: Bank code (4 alphabetic characters)
19///   - **2!a**: Country code (2 alphabetic characters, ISO 3166-1)
20///   - **2!c**: Location code (2 alphanumeric characters)
21///   - **3!c**: Optional branch code (3 alphanumeric characters)
22///
23/// ## Structure
24/// ```text
25/// /1234567890123456789012345678901234
26/// CHASUS33XXX
27/// │       │││
28/// │       │└┴┴ Branch code (optional, XXX)
29/// │       └┴── Location code (2 chars, 33)
30/// │     └┴──── Country code (2 chars, US)
31/// │ └┴┴┴────── Bank code (4 chars, CHAS)
32/// └─────────── Account number (optional)
33/// ```
34///
35/// ## Field Components
36/// - **Account Number**: Beneficiary's account at the institution (optional)
37/// - **BIC Code**: Business Identifier Code for beneficiary's bank
38/// - **Bank Code**: 4-letter code identifying the beneficiary's bank
39/// - **Country Code**: 2-letter ISO country code
40/// - **Location Code**: 2-character location identifier
41/// - **Branch Code**: 3-character branch identifier (optional)
42///
43/// ## Usage Context
44/// Field 57A is used in:
45/// - **MT103**: Single Customer Credit Transfer
46/// - **MT200**: Financial Institution Transfer
47/// - **MT202**: General Financial Institution Transfer
48/// - **MT202COV**: Cover for customer credit transfer
49/// - **MT205**: Financial Institution Transfer for its own account
50///
51/// ### Business Applications
52/// - **Final credit destination**: Identifying beneficiary's bank
53/// - **Account crediting**: Specifying where funds should be credited
54/// - **Payment completion**: Ensuring proper payment delivery
55/// - **Correspondent banking**: Managing beneficiary bank relationships
56/// - **Cross-border payments**: International payment settlement
57/// - **Regulatory compliance**: Meeting beneficiary identification requirements
58///
59/// ## Examples
60/// ```text
61/// :57A:CHASUS33
62/// └─── JPMorgan Chase Bank, New York (beneficiary's bank)
63///
64/// :57A:/DE89370400440532013000
65/// DEUTDEFF500
66/// └─── Deutsche Bank AG, Frankfurt with beneficiary account
67///
68/// :57A:BARCGB22
69/// └─── Barclays Bank PLC, London (8-character BIC)
70///
71/// :57A:/BENEFICIARYACCT123456
72/// BNPAFRPP
73/// └─── BNP Paribas, Paris with beneficiary 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's account number or identifier
87/// - **Usage**: When specific account designation is required
88/// - **Omission**: When only institution identification is needed
89/// - **Standards**: May include IBAN or local account formats
90///
91/// ## Validation Rules
92/// 1. **BIC format**: Must be valid 8 or 11 character BIC code
93/// 2. **Bank code**: Must be 4 alphabetic characters
94/// 3. **Country code**: Must be 2 alphabetic characters
95/// 4. **Location code**: Must be 2 alphanumeric characters
96/// 5. **Branch code**: Must be 3 alphanumeric characters (if present)
97/// 6. **Account number**: Maximum 34 characters (if present)
98/// 7. **Character validation**: All components must be printable ASCII
99///
100/// ## Network Validated Rules (SWIFT Standards)
101/// - BIC must be valid and registered in SWIFT network (Error: T10)
102/// - BIC format must comply with ISO 13616 standards (Error: T11)
103/// - Account number cannot exceed 34 characters (Error: T14)
104/// - Bank code must be alphabetic only (Error: T15)
105/// - Country code must be valid ISO 3166-1 code (Error: T16)
106/// - Location code must be alphanumeric (Error: T17)
107/// - Branch code must be alphanumeric if present (Error: T18)
108/// - Field 57A alternative to 57B/57C/57D (Error: C57)
109///
110
111#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
112pub struct Field57A {
113    /// Account line indicator (optional, 1 character)
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub account_line_indicator: Option<String>,
116    /// Account number (optional, up to 34 characters)
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub account_number: Option<String>,
119    /// BIC code (8 or 11 characters)
120    #[serde(flatten)]
121    pub bic: BIC,
122}
123
124impl Field57A {
125    /// Create a new Field57A with validation
126    pub fn new(
127        account_line_indicator: Option<String>,
128        account_number: Option<String>,
129        bic: impl Into<String>,
130    ) -> Result<Self, crate::ParseError> {
131        // Parse and validate BIC using the common structure
132        let bic = BIC::parse(&bic.into(), Some("57A"))?;
133
134        // Validate account line indicator if present
135        if let Some(ref indicator) = account_line_indicator {
136            if indicator.len() != 1 {
137                return Err(crate::ParseError::InvalidFieldFormat {
138                    field_tag: "57A".to_string(),
139                    message: "Account line indicator must be exactly 1 character".to_string(),
140                });
141            }
142
143            if !indicator.chars().all(|c| c.is_ascii_alphanumeric()) {
144                return Err(crate::ParseError::InvalidFieldFormat {
145                    field_tag: "57A".to_string(),
146                    message: "Account line indicator must be alphanumeric".to_string(),
147                });
148            }
149        }
150
151        // Validate account number if present
152        if let Some(ref account) = account_number {
153            if account.len() > 34 {
154                return Err(crate::ParseError::InvalidFieldFormat {
155                    field_tag: "57A".to_string(),
156                    message: "Account number cannot exceed 34 characters".to_string(),
157                });
158            }
159
160            if !account.chars().all(|c| c.is_ascii() && !c.is_control()) {
161                return Err(crate::ParseError::InvalidFieldFormat {
162                    field_tag: "57A".to_string(),
163                    message: "Account number contains invalid characters".to_string(),
164                });
165            }
166        }
167
168        Ok(Field57A {
169            account_line_indicator,
170            account_number,
171            bic,
172        })
173    }
174
175    /// Get the account line indicator
176    pub fn account_line_indicator(&self) -> Option<&str> {
177        self.account_line_indicator.as_deref()
178    }
179
180    /// Get the account number
181    pub fn account_number(&self) -> Option<&str> {
182        self.account_number.as_deref()
183    }
184
185    /// Get the BIC code
186    pub fn bic(&self) -> &str {
187        self.bic.value()
188    }
189
190    /// Check if this is a full BIC (11 characters)
191    pub fn is_full_bic(&self) -> bool {
192        self.bic.is_full_bic()
193    }
194
195    /// Get bank code from BIC
196    pub fn bank_code(&self) -> &str {
197        self.bic.bank_code()
198    }
199
200    /// Get country code from BIC
201    pub fn country_code(&self) -> &str {
202        self.bic.country_code()
203    }
204
205    /// Get location code from BIC
206    pub fn location_code(&self) -> &str {
207        self.bic.location_code()
208    }
209
210    /// Get branch code from BIC if present
211    pub fn branch_code(&self) -> Option<&str> {
212        self.bic.branch_code()
213    }
214
215    /// Get human-readable description
216    pub fn description(&self) -> String {
217        match &self.account_number {
218            Some(account) => format!("Account With Institution: {} ({})", self.bic, account),
219            None => format!("Account With Institution: {}", self.bic),
220        }
221    }
222}
223
224impl SwiftField for Field57A {
225    fn parse(value: &str) -> Result<Self, crate::ParseError> {
226        let content = if let Some(stripped) = value.strip_prefix(":57A:") {
227            stripped
228        } else if let Some(stripped) = value.strip_prefix("57A:") {
229            stripped
230        } else {
231            value
232        };
233
234        let content = content.trim();
235
236        if content.is_empty() {
237            return Err(crate::ParseError::InvalidFieldFormat {
238                field_tag: "57A".to_string(),
239                message: "Field content cannot be empty".to_string(),
240            });
241        }
242
243        let mut account_number = None;
244        let bic;
245
246        if content.starts_with('/') {
247            let lines: Vec<&str> = content.lines().collect();
248
249            if lines.len() == 1 {
250                let parts: Vec<&str> = lines[0].splitn(2, ' ').collect();
251                if parts.len() == 2 {
252                    account_number = Some(parts[0][1..].to_string());
253                    bic = parts[1].to_string();
254                } else {
255                    return Err(crate::ParseError::InvalidFieldFormat {
256                        field_tag: "57A".to_string(),
257                        message: "Invalid format: expected account and BIC".to_string(),
258                    });
259                }
260            } else if lines.len() == 2 {
261                account_number = Some(lines[0][1..].to_string());
262                bic = lines[1].to_string();
263            } else {
264                return Err(crate::ParseError::InvalidFieldFormat {
265                    field_tag: "57A".to_string(),
266                    message: "Invalid format: too many lines".to_string(),
267                });
268            }
269        } else {
270            bic = content.to_string();
271        }
272
273        let parsed_bic = BIC::parse(&bic, Some("57A"))?;
274
275        Ok(Field57A {
276            account_line_indicator: None,
277            account_number,
278            bic: parsed_bic,
279        })
280    }
281
282    fn to_swift_string(&self) -> String {
283        match &self.account_number {
284            Some(account) => format!(":57A:/{}\n{}", account, self.bic.value()),
285            None => format!(":57A:{}", self.bic.value()),
286        }
287    }
288
289    fn validate(&self) -> ValidationResult {
290        let mut errors = Vec::new();
291
292        if let Some(ref account) = self.account_number {
293            if account.is_empty() {
294                errors.push(ValidationError::ValueValidation {
295                    field_tag: "57A".to_string(),
296                    message: "Account number cannot be empty if specified".to_string(),
297                });
298            }
299
300            if account.len() > 34 {
301                errors.push(ValidationError::LengthValidation {
302                    field_tag: "57A".to_string(),
303                    expected: "max 34 characters".to_string(),
304                    actual: account.len(),
305                });
306            }
307        }
308
309        // Validate BIC format using the common BIC validation
310        let bic_validation = self.bic.validate();
311        if !bic_validation.is_valid {
312            errors.extend(bic_validation.errors);
313        }
314
315        ValidationResult {
316            is_valid: errors.is_empty(),
317            errors,
318            warnings: Vec::new(),
319        }
320    }
321
322    fn format_spec() -> &'static str {
323        "[/34x]4!a2!a2!c[3!c]"
324    }
325}
326
327impl std::fmt::Display for Field57A {
328    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329        match &self.account_number {
330            Some(account) => write!(f, "/{} {}", account, self.bic.value()),
331            None => write!(f, "{}", self.bic.value()),
332        }
333    }
334}
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339
340    #[test]
341    fn test_field57a_creation() {
342        let field = Field57A::new(None, None, "DEUTDEFF").unwrap();
343        assert_eq!(field.bic(), "DEUTDEFF");
344        assert!(field.account_number().is_none());
345    }
346
347    #[test]
348    fn test_field57a_with_account() {
349        let field = Field57A::new(None, Some("1234567890".to_string()), "DEUTDEFF500").unwrap();
350        assert_eq!(field.bic(), "DEUTDEFF500");
351        assert_eq!(field.account_number(), Some("1234567890"));
352        assert!(field.is_full_bic());
353    }
354
355    #[test]
356    fn test_field57a_parse() {
357        let field = Field57A::parse("CHASUS33").unwrap();
358        assert_eq!(field.bic(), "CHASUS33");
359    }
360
361    #[test]
362    fn test_field57a_to_swift_string() {
363        let field = Field57A::new(None, None, "DEUTDEFF").unwrap();
364        assert_eq!(field.to_swift_string(), ":57A:DEUTDEFF");
365    }
366
367    #[test]
368    fn test_field57a_validation() {
369        let field = Field57A::new(None, Some("1234567890".to_string()), "DEUTDEFF").unwrap();
370        let validation = field.validate();
371        assert!(validation.is_valid);
372    }
373}