swift_mt_message/fields/field56a.rs
1use crate::common::BIC;
2use crate::{SwiftField, ValidationError, ValidationResult};
3use serde::{Deserialize, Serialize};
4
5/// # Field 56A: Intermediary Institution
6///
7/// ## Overview
8/// Field 56A identifies an intermediary institution in SWIFT payment messages using a BIC code.
9/// This field specifies a financial institution that acts as an intermediary in the payment
10/// routing chain, facilitating the transfer of funds between the ordering institution and the
11/// account with institution. Intermediary institutions play a crucial role in correspondent
12/// banking networks and cross-border payment processing.
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**: Intermediary's account for settlement (optional)
37/// - **BIC Code**: Business Identifier Code for intermediary identification
38/// - **Bank Code**: 4-letter code identifying the intermediary 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 56A 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/// - **Payment routing**: Directing payments through intermediary banks
53/// - **Correspondent banking**: Managing correspondent relationships
54/// - **Cross-border payments**: Facilitating international transfers
55/// - **Settlement optimization**: Using intermediaries for efficient settlement
56/// - **Regulatory compliance**: Meeting routing requirements
57/// - **Risk management**: Diversifying counterparty exposure
58///
59/// ## Examples
60/// ```text
61/// :56A:CHASUS33
62/// └─── JPMorgan Chase Bank, New York (intermediary)
63///
64/// :56A:/INTERMEDIARYACCT123456
65/// DEUTDEFF500
66/// └─── Deutsche Bank AG, Frankfurt with intermediary account
67///
68/// :56A:BARCGB22
69/// └─── Barclays Bank PLC, London (8-character BIC)
70///
71/// :56A:/NOSTRO001
72/// BNPAFRPP
73/// └─── BNP Paribas, Paris with nostro 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**: Intermediary account number or identifier
87/// - **Usage**: When specific account designation is required
88/// - **Omission**: When only institution identification is needed
89///
90/// ## Validation Rules
91/// 1. **BIC format**: Must be valid 8 or 11 character BIC code
92/// 2. **Bank code**: Must be 4 alphabetic characters
93/// 3. **Country code**: Must be 2 alphabetic characters
94/// 4. **Location code**: Must be 2 alphanumeric characters
95/// 5. **Branch code**: Must be 3 alphanumeric characters (if present)
96/// 6. **Account number**: Maximum 34 characters (if present)
97/// 7. **Character validation**: All components must be printable ASCII
98///
99/// ## Network Validated Rules (SWIFT Standards)
100/// - BIC must be valid and registered in SWIFT network (Error: T10)
101/// - BIC format must comply with ISO 13616 standards (Error: T11)
102/// - Account number cannot exceed 34 characters (Error: T14)
103/// - Bank code must be alphabetic only (Error: T15)
104/// - Country code must be valid ISO 3166-1 code (Error: T16)
105/// - Location code must be alphanumeric (Error: T17)
106/// - Branch code must be alphanumeric if present (Error: T18)
107/// - Field 56A alternative to 56C/56D (Error: C56)
108///
109
110#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
111pub struct Field56A {
112 /// Account line indicator (optional, 1 character)
113 #[serde(skip_serializing_if = "Option::is_none")]
114 pub account_line_indicator: Option<String>,
115 /// Account number (optional, up to 34 characters)
116 #[serde(skip_serializing_if = "Option::is_none")]
117 pub account_number: Option<String>,
118 /// BIC code (8 or 11 characters)
119 #[serde(flatten)]
120 pub bic: BIC,
121}
122
123impl Field56A {
124 /// Create a new Field56A with validation
125 pub fn new(
126 account_line_indicator: Option<String>,
127 account_number: Option<String>,
128 bic: impl Into<String>,
129 ) -> Result<Self, crate::ParseError> {
130 let bic = bic.into().to_uppercase();
131
132 // Validate account line indicator if present
133 if let Some(ref indicator) = account_line_indicator {
134 if indicator.is_empty() {
135 return Err(crate::ParseError::InvalidFieldFormat {
136 field_tag: "56A".to_string(),
137 message: "Account line indicator cannot be empty if specified".to_string(),
138 });
139 }
140
141 if indicator.len() != 1 {
142 return Err(crate::ParseError::InvalidFieldFormat {
143 field_tag: "56A".to_string(),
144 message: "Account line indicator must be exactly 1 character".to_string(),
145 });
146 }
147
148 if !indicator.chars().all(|c| c.is_ascii() && !c.is_control()) {
149 return Err(crate::ParseError::InvalidFieldFormat {
150 field_tag: "56A".to_string(),
151 message: "Account line indicator contains invalid characters".to_string(),
152 });
153 }
154 }
155
156 // Validate account number if present
157 if let Some(ref account) = account_number {
158 if account.is_empty() {
159 return Err(crate::ParseError::InvalidFieldFormat {
160 field_tag: "56A".to_string(),
161 message: "Account number cannot be empty if specified".to_string(),
162 });
163 }
164
165 if account.len() > 34 {
166 return Err(crate::ParseError::InvalidFieldFormat {
167 field_tag: "56A".to_string(),
168 message: "Account number too long (max 34 characters)".to_string(),
169 });
170 }
171
172 if !account.chars().all(|c| c.is_ascii() && !c.is_control()) {
173 return Err(crate::ParseError::InvalidFieldFormat {
174 field_tag: "56A".to_string(),
175 message: "Account number contains invalid characters".to_string(),
176 });
177 }
178 }
179
180 // Parse and validate BIC using the common structure
181 let parsed_bic = BIC::parse(&bic, Some("56A"))?;
182
183 Ok(Field56A {
184 account_line_indicator,
185 account_number,
186 bic: parsed_bic,
187 })
188 }
189
190 /// Get the account line indicator
191 pub fn account_line_indicator(&self) -> Option<&str> {
192 self.account_line_indicator.as_deref()
193 }
194
195 /// Get the account number
196 pub fn account_number(&self) -> Option<&str> {
197 self.account_number.as_deref()
198 }
199
200 /// Get the BIC code
201 pub fn bic(&self) -> &str {
202 self.bic.value()
203 }
204
205 /// Check if this is a full BIC (11 characters) or short BIC (8 characters)
206 pub fn is_full_bic(&self) -> bool {
207 self.bic.is_full_bic()
208 }
209
210 /// Get human-readable description
211 pub fn description(&self) -> String {
212 match &self.account_number {
213 Some(account) => format!(
214 "Intermediary Institution: {} ({})",
215 self.bic.value(),
216 account
217 ),
218 None => format!("Intermediary Institution: {}", self.bic.value()),
219 }
220 }
221}
222
223impl SwiftField for Field56A {
224 fn parse(value: &str) -> Result<Self, crate::ParseError> {
225 let content = if let Some(stripped) = value.strip_prefix(":56A:") {
226 stripped
227 } else if let Some(stripped) = value.strip_prefix("56A:") {
228 stripped
229 } else {
230 value
231 };
232
233 let content = content.trim();
234
235 if content.is_empty() {
236 return Err(crate::ParseError::InvalidFieldFormat {
237 field_tag: "56A".to_string(),
238 message: "Field content cannot be empty".to_string(),
239 });
240 }
241
242 let mut account_number = None;
243 let bic;
244
245 if content.starts_with('/') {
246 let lines: Vec<&str> = content.lines().collect();
247
248 if lines.len() == 1 {
249 let parts: Vec<&str> = lines[0].splitn(2, ' ').collect();
250 if parts.len() == 2 {
251 account_number = Some(parts[0][1..].to_string());
252 bic = parts[1].to_string();
253 } else {
254 return Err(crate::ParseError::InvalidFieldFormat {
255 field_tag: "56A".to_string(),
256 message: "Invalid format: expected account and BIC".to_string(),
257 });
258 }
259 } else if lines.len() == 2 {
260 account_number = Some(lines[0][1..].to_string());
261 bic = lines[1].to_string();
262 } else {
263 return Err(crate::ParseError::InvalidFieldFormat {
264 field_tag: "56A".to_string(),
265 message: "Invalid format: too many lines".to_string(),
266 });
267 }
268 } else {
269 bic = content.to_string();
270 }
271
272 Self::new(None, account_number, bic)
273 }
274
275 fn to_swift_string(&self) -> String {
276 match &self.account_number {
277 Some(account) => format!(":56A:/{}\n{}", account, self.bic.value()),
278 None => format!(":56A:{}", self.bic.value()),
279 }
280 }
281
282 fn validate(&self) -> ValidationResult {
283 let mut errors = Vec::new();
284
285 if let Some(ref account) = self.account_number {
286 if account.is_empty() {
287 errors.push(ValidationError::ValueValidation {
288 field_tag: "56A".to_string(),
289 message: "Account number cannot be empty if specified".to_string(),
290 });
291 }
292
293 if account.len() > 34 {
294 errors.push(ValidationError::LengthValidation {
295 field_tag: "56A".to_string(),
296 expected: "max 34 characters".to_string(),
297 actual: account.len(),
298 });
299 }
300 }
301
302 // Validate BIC format using the common BIC validation
303 let bic_validation = self.bic.validate();
304 if !bic_validation.is_valid {
305 errors.extend(bic_validation.errors);
306 }
307
308 ValidationResult {
309 is_valid: errors.is_empty(),
310 errors,
311 warnings: Vec::new(),
312 }
313 }
314
315 fn format_spec() -> &'static str {
316 "[/34x]4!a2!a2!c[3!c]"
317 }
318}
319
320impl std::fmt::Display for Field56A {
321 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
322 match &self.account_number {
323 Some(account) => write!(f, "/{} {}", account, self.bic.value()),
324 None => write!(f, "{}", self.bic.value()),
325 }
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn test_field56a_creation() {
335 let field = Field56A::new(None, None, "DEUTDEFF").unwrap();
336 assert_eq!(field.bic(), "DEUTDEFF");
337 assert!(field.account_number().is_none());
338 }
339
340 #[test]
341 fn test_field56a_with_account() {
342 let field = Field56A::new(None, Some("1234567890".to_string()), "DEUTDEFF500").unwrap();
343 assert_eq!(field.bic(), "DEUTDEFF500");
344 assert_eq!(field.account_number(), Some("1234567890"));
345 assert!(field.is_full_bic());
346 }
347
348 #[test]
349 fn test_field56a_parse() {
350 let field = Field56A::parse("CHASUS33").unwrap();
351 assert_eq!(field.bic(), "CHASUS33");
352 }
353
354 #[test]
355 fn test_field56a_to_swift_string() {
356 let field = Field56A::new(None, None, "DEUTDEFF").unwrap();
357 assert_eq!(field.to_swift_string(), ":56A:DEUTDEFF");
358 }
359
360 #[test]
361 fn test_field56a_validation() {
362 let field = Field56A::new(None, Some("1234567890".to_string()), "DEUTDEFF").unwrap();
363 let validation = field.validate();
364 assert!(validation.is_valid);
365 }
366}