1use crate::{SwiftField, ValidationError, ValidationResult};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
106pub struct Field51A {
107 #[serde(skip_serializing_if = "Option::is_none")]
109 pub account_line_indicator: Option<String>,
110
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub account_number: Option<String>,
114
115 pub bic: String,
117}
118
119impl SwiftField for Field51A {
120 fn parse(value: &str) -> Result<Self, crate::ParseError> {
121 let content = if let Some(stripped) = value.strip_prefix(":51A:") {
122 stripped
123 } else if let Some(stripped) = value.strip_prefix("51A:") {
124 stripped
125 } else {
126 value
127 };
128
129 if content.is_empty() {
130 return Err(crate::ParseError::InvalidFieldFormat {
131 field_tag: "51A".to_string(),
132 message: "Field content cannot be empty".to_string(),
133 });
134 }
135
136 let lines: Vec<&str> = content.lines().collect();
137
138 if lines.is_empty() {
139 return Err(crate::ParseError::InvalidFieldFormat {
140 field_tag: "51A".to_string(),
141 message: "No content found".to_string(),
142 });
143 }
144
145 let mut account_line_indicator = None;
146 let mut account_number = None;
147 let mut bic_line = lines[0];
148
149 if bic_line.starts_with('/') {
151 let account_part = &bic_line[1..]; if let Some(second_slash) = account_part.find('/') {
155 account_line_indicator = Some(account_part[..second_slash].to_string());
157 account_number = Some(account_part[second_slash + 1..].to_string());
158 } else {
159 account_number = Some(account_part.to_string());
161 }
162
163 if lines.len() < 2 {
165 return Err(crate::ParseError::InvalidFieldFormat {
166 field_tag: "51A".to_string(),
167 message: "BIC code missing after account information".to_string(),
168 });
169 }
170 bic_line = lines[1];
171 }
172
173 let bic = bic_line.trim().to_string();
174
175 Self::new(account_line_indicator, account_number, bic)
176 }
177
178 fn to_swift_string(&self) -> String {
179 let mut result = ":51A:".to_string();
180
181 if self.account_line_indicator.is_some() || self.account_number.is_some() {
182 result.push('/');
183
184 if let Some(indicator) = &self.account_line_indicator {
185 result.push_str(indicator);
186 result.push('/');
187 }
188
189 if let Some(account) = &self.account_number {
190 result.push_str(account);
191 }
192
193 result.push('\n');
194 }
195
196 result.push_str(&self.bic);
197 result
198 }
199
200 fn validate(&self) -> ValidationResult {
201 let mut errors = Vec::new();
202
203 if let Err(e) = Self::validate_bic(&self.bic) {
205 errors.push(ValidationError::FormatValidation {
206 field_tag: "51A".to_string(),
207 message: format!("BIC validation failed: {}", e),
208 });
209 }
210
211 if let Some(indicator) = &self.account_line_indicator {
213 if indicator.len() != 1 {
214 errors.push(ValidationError::LengthValidation {
215 field_tag: "51A".to_string(),
216 expected: "1 character".to_string(),
217 actual: indicator.len(),
218 });
219 }
220
221 if !indicator.chars().all(|c| c.is_ascii_alphanumeric()) {
222 errors.push(ValidationError::FormatValidation {
223 field_tag: "51A".to_string(),
224 message: "Account line indicator must be alphanumeric".to_string(),
225 });
226 }
227 }
228
229 if let Some(account) = &self.account_number {
231 if account.len() > 34 {
232 errors.push(ValidationError::LengthValidation {
233 field_tag: "51A".to_string(),
234 expected: "max 34 characters".to_string(),
235 actual: account.len(),
236 });
237 }
238
239 if !account.chars().all(|c| c.is_ascii() && !c.is_control()) {
240 errors.push(ValidationError::FormatValidation {
241 field_tag: "51A".to_string(),
242 message: "Account number contains invalid characters".to_string(),
243 });
244 }
245 }
246
247 ValidationResult {
248 is_valid: errors.is_empty(),
249 errors,
250 warnings: Vec::new(),
251 }
252 }
253
254 fn format_spec() -> &'static str {
255 "[/1!a][/34x]4!a2!a2!c[3!c]"
256 }
257}
258
259impl Field51A {
260 pub fn new(
281 account_line_indicator: Option<String>,
282 account_number: Option<String>,
283 bic: impl Into<String>,
284 ) -> crate::Result<Self> {
285 let account_line_indicator = account_line_indicator.map(|s| s.trim().to_string());
286 let account_number = account_number.map(|s| s.trim().to_string());
287 let bic = bic.into().trim().to_uppercase();
288
289 Self::validate_bic(&bic).map_err(|msg| crate::ParseError::InvalidFieldFormat {
291 field_tag: "51A".to_string(),
292 message: format!("BIC validation failed: {}", msg),
293 })?;
294
295 if let Some(ref indicator) = account_line_indicator {
297 if indicator.len() != 1 {
298 return Err(crate::ParseError::InvalidFieldFormat {
299 field_tag: "51A".to_string(),
300 message: "Account line indicator must be exactly 1 character".to_string(),
301 });
302 }
303
304 if !indicator.chars().all(|c| c.is_ascii_alphanumeric()) {
305 return Err(crate::ParseError::InvalidFieldFormat {
306 field_tag: "51A".to_string(),
307 message: "Account line indicator must be alphanumeric".to_string(),
308 });
309 }
310 }
311
312 if let Some(ref account) = account_number {
314 if account.len() > 34 {
315 return Err(crate::ParseError::InvalidFieldFormat {
316 field_tag: "51A".to_string(),
317 message: "Account number cannot exceed 34 characters".to_string(),
318 });
319 }
320
321 if !account.chars().all(|c| c.is_ascii() && !c.is_control()) {
322 return Err(crate::ParseError::InvalidFieldFormat {
323 field_tag: "51A".to_string(),
324 message: "Account number contains invalid characters".to_string(),
325 });
326 }
327 }
328
329 Ok(Field51A {
330 account_line_indicator,
331 account_number,
332 bic: bic.to_string(),
333 })
334 }
335
336 fn validate_bic(bic: &str) -> Result<(), String> {
338 if bic.len() != 8 && bic.len() != 11 {
339 return Err("BIC must be 8 or 11 characters".to_string());
340 }
341
342 let bank_code = &bic[0..4];
343 let country_code = &bic[4..6];
344 let location_code = &bic[6..8];
345
346 if !bank_code.chars().all(|c| c.is_ascii_alphabetic()) {
348 return Err("Bank code must be 4 alphabetic characters".to_string());
349 }
350
351 if !country_code.chars().all(|c| c.is_ascii_alphabetic()) {
353 return Err("Country code must be 2 alphabetic characters".to_string());
354 }
355
356 if !location_code.chars().all(|c| c.is_ascii_alphanumeric()) {
358 return Err("Location code must be 2 alphanumeric characters".to_string());
359 }
360
361 if bic.len() == 11 {
363 let branch_code = &bic[8..11];
364 if !branch_code.chars().all(|c| c.is_ascii_alphanumeric()) {
365 return Err("Branch code must be 3 alphanumeric characters".to_string());
366 }
367 }
368
369 Ok(())
370 }
371
372 pub fn bic(&self) -> &str {
374 &self.bic
375 }
376
377 pub fn account_line_indicator(&self) -> Option<&str> {
379 self.account_line_indicator.as_deref()
380 }
381
382 pub fn account_number(&self) -> Option<&str> {
384 self.account_number.as_deref()
385 }
386
387 pub fn is_stp_allowed(&self) -> bool {
391 false
392 }
393
394 pub fn bank_code(&self) -> &str {
396 &self.bic[0..4]
397 }
398
399 pub fn country_code(&self) -> &str {
401 &self.bic[4..6]
402 }
403
404 pub fn location_code(&self) -> &str {
406 &self.bic[6..8]
407 }
408
409 pub fn branch_code(&self) -> Option<&str> {
411 if self.bic.len() == 11 {
412 Some(&self.bic[8..11])
413 } else {
414 None
415 }
416 }
417}
418
419impl std::fmt::Display for Field51A {
420 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
421 if let Some(indicator) = &self.account_line_indicator {
422 if let Some(account) = &self.account_number {
423 write!(f, "/{}/{} {}", indicator, account, self.bic)
424 } else {
425 write!(f, "/{} {}", indicator, self.bic)
426 }
427 } else if let Some(account) = &self.account_number {
428 write!(f, "/{} {}", account, self.bic)
429 } else {
430 write!(f, "{}", self.bic)
431 }
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438
439 #[test]
440 fn test_field51a_creation() {
441 let field = Field51A::new(None, None, "CHASUS33XXX").unwrap();
442 assert_eq!(field.bic(), "CHASUS33XXX");
443 assert_eq!(field.account_line_indicator(), None);
444 assert_eq!(field.account_number(), None);
445 }
446
447 #[test]
448 fn test_field51a_with_account() {
449 let field = Field51A::new(None, Some("1234567890".to_string()), "DEUTDEFF500").unwrap();
450 assert_eq!(field.account_number(), Some("1234567890"));
451 assert_eq!(field.bic(), "DEUTDEFF500");
452 assert!(field.account_line_indicator().is_none());
453 }
454
455 #[test]
456 fn test_field51a_with_indicator_and_account() {
457 let field = Field51A::new(
458 Some("C".to_string()),
459 Some("1234567890".to_string()),
460 "HSBCHKHH",
461 )
462 .unwrap();
463 assert_eq!(field.account_line_indicator(), Some("C"));
464 assert_eq!(field.account_number(), Some("1234567890"));
465 assert_eq!(field.bic(), "HSBCHKHH");
466 }
467
468 #[test]
469 fn test_field51a_parse() {
470 let field = Field51A::parse("CHASUS33XXX").unwrap();
471 assert_eq!(field.bic(), "CHASUS33XXX");
472
473 let field = Field51A::parse("/1234567890\nDEUTDEFF500").unwrap();
474 assert_eq!(field.bic(), "DEUTDEFF500");
475 assert_eq!(field.account_number(), Some("1234567890"));
476
477 let field = Field51A::parse("/C/1234567890\nHSBCHKHH").unwrap();
478 assert_eq!(field.bic(), "HSBCHKHH");
479 assert_eq!(field.account_line_indicator(), Some("C"));
480 assert_eq!(field.account_number(), Some("1234567890"));
481 }
482
483 #[test]
484 fn test_field51a_parse_with_prefix() {
485 let field = Field51A::parse(":51A:CHASUS33XXX").unwrap();
486 assert_eq!(field.bic(), "CHASUS33XXX");
487
488 let field = Field51A::parse("51A:/1234567890\nDEUTDEFF500").unwrap();
489 assert_eq!(field.bic(), "DEUTDEFF500");
490 assert_eq!(field.account_number(), Some("1234567890"));
491 }
492
493 #[test]
494 fn test_field51a_invalid_bic() {
495 let result = Field51A::new(None, None, "INVALID");
496 assert!(result.is_err());
497
498 let result = Field51A::new(None, None, "CHAS1233");
499 assert!(result.is_err());
500 }
501
502 #[test]
503 fn test_field51a_invalid_account() {
504 let result = Field51A::new(None, Some("a".repeat(35)), "CHASUS33XXX");
505 assert!(result.is_err());
506
507 let result = Field51A::new(Some("AB".to_string()), None, "CHASUS33XXX");
508 assert!(result.is_err());
509 }
510
511 #[test]
512 fn test_field51a_to_swift_string() {
513 let field = Field51A::new(None, None, "CHASUS33XXX").unwrap();
514 assert_eq!(field.to_swift_string(), ":51A:CHASUS33XXX");
515
516 let field = Field51A::new(None, Some("1234567890".to_string()), "DEUTDEFF500").unwrap();
517 assert_eq!(field.to_swift_string(), ":51A:/1234567890\nDEUTDEFF500");
518
519 let field = Field51A::new(
520 Some("C".to_string()),
521 Some("1234567890".to_string()),
522 "HSBCHKHH",
523 )
524 .unwrap();
525 assert_eq!(field.to_swift_string(), ":51A:/C/1234567890\nHSBCHKHH");
526 }
527
528 #[test]
529 fn test_field51a_validation() {
530 let field = Field51A::new(None, None, "CHASUS33XXX").unwrap();
531 let result = field.validate();
532 assert!(result.is_valid);
533
534 let invalid_field = Field51A {
535 account_line_indicator: None,
536 account_number: None,
537 bic: "INVALID".to_string(),
538 };
539 let result = invalid_field.validate();
540 assert!(!result.is_valid);
541 }
542
543 #[test]
544 fn test_field51a_stp_compliance() {
545 let field = Field51A::new(None, None, "CHASUS33XXX").unwrap();
546 assert!(!field.is_stp_allowed()); }
548
549 #[test]
550 fn test_field51a_bic_components() {
551 let field = Field51A::new(None, None, "CHASUS33XXX").unwrap();
552 assert_eq!(field.bank_code(), "CHAS");
553 assert_eq!(field.country_code(), "US");
554 assert_eq!(field.location_code(), "33");
555 assert_eq!(field.branch_code(), Some("XXX"));
556
557 let field = Field51A::new(None, None, "DEUTDEFF").unwrap();
558 assert_eq!(field.bank_code(), "DEUT");
559 assert_eq!(field.country_code(), "DE");
560 assert_eq!(field.location_code(), "FF");
561 assert_eq!(field.branch_code(), None);
562 }
563
564 #[test]
565 fn test_field51a_display() {
566 let field = Field51A::new(None, None, "CHASUS33XXX").unwrap();
567 assert_eq!(format!("{}", field), "CHASUS33XXX");
568
569 let field = Field51A::new(None, Some("1234567890".to_string()), "DEUTDEFF500").unwrap();
570 assert_eq!(format!("{}", field), "/1234567890 DEUTDEFF500");
571
572 let field = Field51A::new(
573 Some("C".to_string()),
574 Some("1234567890".to_string()),
575 "HSBCHKHH",
576 )
577 .unwrap();
578 assert_eq!(format!("{}", field), "/C/1234567890 HSBCHKHH");
579 }
580
581 #[test]
582 fn test_field51a_format_spec() {
583 assert_eq!(Field51A::format_spec(), "[/1!a][/34x]4!a2!a2!c[3!c]");
584 }
585}