1use crate::common::BIC;
2use crate::{SwiftField, ValidationError, ValidationResult};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
107pub struct Field51A {
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub account_line_indicator: Option<String>,
111
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub account_number: Option<String>,
115
116 #[serde(flatten)]
118 pub bic: BIC,
119}
120
121impl SwiftField for Field51A {
122 fn parse(value: &str) -> Result<Self, crate::ParseError> {
123 let content = if let Some(stripped) = value.strip_prefix(":51A:") {
124 stripped
125 } else if let Some(stripped) = value.strip_prefix("51A:") {
126 stripped
127 } else {
128 value
129 };
130
131 if content.is_empty() {
132 return Err(crate::ParseError::InvalidFieldFormat {
133 field_tag: "51A".to_string(),
134 message: "Field content cannot be empty".to_string(),
135 });
136 }
137
138 let lines: Vec<&str> = content.lines().collect();
139
140 if lines.is_empty() {
141 return Err(crate::ParseError::InvalidFieldFormat {
142 field_tag: "51A".to_string(),
143 message: "No content found".to_string(),
144 });
145 }
146
147 let mut account_line_indicator = None;
148 let mut account_number = None;
149 let mut bic_line = lines[0];
150
151 if bic_line.starts_with('/') {
153 let account_part = &bic_line[1..]; if let Some(second_slash) = account_part.find('/') {
157 account_line_indicator = Some(account_part[..second_slash].to_string());
159 account_number = Some(account_part[second_slash + 1..].to_string());
160 } else {
161 account_number = Some(account_part.to_string());
163 }
164
165 if lines.len() < 2 {
167 return Err(crate::ParseError::InvalidFieldFormat {
168 field_tag: "51A".to_string(),
169 message: "BIC code missing after account information".to_string(),
170 });
171 }
172 bic_line = lines[1];
173 }
174
175 let bic = BIC::parse(bic_line.trim(), Some("51A"))?;
176
177 Ok(Self {
178 account_line_indicator,
179 account_number,
180 bic,
181 })
182 }
183
184 fn to_swift_string(&self) -> String {
185 let mut result = ":51A:".to_string();
186
187 if self.account_line_indicator.is_some() || self.account_number.is_some() {
188 result.push('/');
189
190 if let Some(indicator) = &self.account_line_indicator {
191 result.push_str(indicator);
192 result.push('/');
193 }
194
195 if let Some(account) = &self.account_number {
196 result.push_str(account);
197 }
198
199 result.push('\n');
200 }
201
202 result.push_str(self.bic.value());
203 result
204 }
205
206 fn validate(&self) -> ValidationResult {
207 let mut errors = Vec::new();
208
209 let bic_validation = self.bic.validate();
211 if !bic_validation.is_valid {
212 errors.extend(bic_validation.errors);
213 }
214
215 if let Some(indicator) = &self.account_line_indicator {
217 if indicator.len() != 1 {
218 errors.push(ValidationError::LengthValidation {
219 field_tag: "51A".to_string(),
220 expected: "1 character".to_string(),
221 actual: indicator.len(),
222 });
223 }
224
225 if !indicator.chars().all(|c| c.is_ascii_alphanumeric()) {
226 errors.push(ValidationError::FormatValidation {
227 field_tag: "51A".to_string(),
228 message: "Account line indicator must be alphanumeric".to_string(),
229 });
230 }
231 }
232
233 if let Some(account) = &self.account_number {
235 if account.len() > 34 {
236 errors.push(ValidationError::LengthValidation {
237 field_tag: "51A".to_string(),
238 expected: "max 34 characters".to_string(),
239 actual: account.len(),
240 });
241 }
242
243 if !account.chars().all(|c| c.is_ascii() && !c.is_control()) {
244 errors.push(ValidationError::FormatValidation {
245 field_tag: "51A".to_string(),
246 message: "Account number contains invalid characters".to_string(),
247 });
248 }
249 }
250
251 ValidationResult {
252 is_valid: errors.is_empty(),
253 errors,
254 warnings: Vec::new(),
255 }
256 }
257
258 fn format_spec() -> &'static str {
259 "[/1!a][/34x]4!a2!a2!c[3!c]"
260 }
261}
262
263impl Field51A {
264 pub fn new(
285 account_line_indicator: Option<String>,
286 account_number: Option<String>,
287 bic: impl Into<String>,
288 ) -> crate::Result<Self> {
289 let account_line_indicator = account_line_indicator.map(|s| s.trim().to_string());
290 let account_number = account_number.map(|s| s.trim().to_string());
291
292 let bic = BIC::parse(&bic.into(), Some("51A"))?;
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,
333 })
334 }
335
336 pub fn bic(&self) -> &str {
338 self.bic.value()
339 }
340
341 pub fn account_line_indicator(&self) -> Option<&str> {
343 self.account_line_indicator.as_deref()
344 }
345
346 pub fn account_number(&self) -> Option<&str> {
348 self.account_number.as_deref()
349 }
350
351 pub fn is_stp_allowed(&self) -> bool {
355 false
356 }
357
358 pub fn bank_code(&self) -> &str {
360 self.bic.bank_code()
361 }
362
363 pub fn country_code(&self) -> &str {
365 self.bic.country_code()
366 }
367
368 pub fn location_code(&self) -> &str {
370 self.bic.location_code()
371 }
372
373 pub fn branch_code(&self) -> Option<&str> {
375 self.bic.branch_code()
376 }
377}
378
379impl std::fmt::Display for Field51A {
380 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
381 if let Some(indicator) = &self.account_line_indicator {
382 if let Some(account) = &self.account_number {
383 write!(f, "/{}/{} {}", indicator, account, self.bic)
384 } else {
385 write!(f, "/{} {}", indicator, self.bic)
386 }
387 } else if let Some(account) = &self.account_number {
388 write!(f, "/{} {}", account, self.bic)
389 } else {
390 write!(f, "{}", self.bic)
391 }
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398
399 #[test]
400 fn test_field51a_creation() {
401 let field = Field51A::new(None, None, "CHASUS33XXX").unwrap();
402 assert_eq!(field.bic(), "CHASUS33XXX");
403 assert_eq!(field.account_line_indicator(), None);
404 assert_eq!(field.account_number(), None);
405 }
406
407 #[test]
408 fn test_field51a_with_account() {
409 let field = Field51A::new(None, Some("1234567890".to_string()), "DEUTDEFF500").unwrap();
410 assert_eq!(field.account_number(), Some("1234567890"));
411 assert_eq!(field.bic(), "DEUTDEFF500");
412 assert!(field.account_line_indicator().is_none());
413 }
414
415 #[test]
416 fn test_field51a_with_indicator_and_account() {
417 let field = Field51A::new(
418 Some("C".to_string()),
419 Some("1234567890".to_string()),
420 "HSBCHKHH",
421 )
422 .unwrap();
423 assert_eq!(field.account_line_indicator(), Some("C"));
424 assert_eq!(field.account_number(), Some("1234567890"));
425 assert_eq!(field.bic(), "HSBCHKHH");
426 }
427
428 #[test]
429 fn test_field51a_parse() {
430 let field = Field51A::parse("CHASUS33XXX").unwrap();
431 assert_eq!(field.bic(), "CHASUS33XXX");
432
433 let field = Field51A::parse("/1234567890\nDEUTDEFF500").unwrap();
434 assert_eq!(field.bic(), "DEUTDEFF500");
435 assert_eq!(field.account_number(), Some("1234567890"));
436
437 let field = Field51A::parse("/C/1234567890\nHSBCHKHH").unwrap();
438 assert_eq!(field.bic(), "HSBCHKHH");
439 assert_eq!(field.account_line_indicator(), Some("C"));
440 assert_eq!(field.account_number(), Some("1234567890"));
441 }
442
443 #[test]
444 fn test_field51a_parse_with_prefix() {
445 let field = Field51A::parse(":51A:CHASUS33XXX").unwrap();
446 assert_eq!(field.bic(), "CHASUS33XXX");
447
448 let field = Field51A::parse("51A:/1234567890\nDEUTDEFF500").unwrap();
449 assert_eq!(field.bic(), "DEUTDEFF500");
450 assert_eq!(field.account_number(), Some("1234567890"));
451 }
452
453 #[test]
454 fn test_field51a_invalid_bic() {
455 let result = Field51A::new(None, None, "INVALID");
456 assert!(result.is_err());
457
458 let result = Field51A::new(None, None, "CHAS1233");
459 assert!(result.is_err());
460 }
461
462 #[test]
463 fn test_field51a_invalid_account() {
464 let result = Field51A::new(None, Some("a".repeat(35)), "CHASUS33XXX");
465 assert!(result.is_err());
466
467 let result = Field51A::new(Some("AB".to_string()), None, "CHASUS33XXX");
468 assert!(result.is_err());
469 }
470
471 #[test]
472 fn test_field51a_to_swift_string() {
473 let field = Field51A::new(None, None, "CHASUS33XXX").unwrap();
474 assert_eq!(field.to_swift_string(), ":51A:CHASUS33XXX");
475
476 let field = Field51A::new(None, Some("1234567890".to_string()), "DEUTDEFF500").unwrap();
477 assert_eq!(field.to_swift_string(), ":51A:/1234567890\nDEUTDEFF500");
478
479 let field = Field51A::new(
480 Some("C".to_string()),
481 Some("1234567890".to_string()),
482 "HSBCHKHH",
483 )
484 .unwrap();
485 assert_eq!(field.to_swift_string(), ":51A:/C/1234567890\nHSBCHKHH");
486 }
487
488 #[test]
489 fn test_field51a_validation() {
490 let field = Field51A::new(None, None, "CHASUS33XXX").unwrap();
491 let result = field.validate();
492 assert!(result.is_valid);
493
494 let invalid_field = Field51A {
495 account_line_indicator: None,
496 account_number: None,
497 bic: BIC::new_unchecked("INVALID"),
498 };
499 let result = invalid_field.validate();
500 assert!(!result.is_valid);
501 }
502
503 #[test]
504 fn test_field51a_stp_compliance() {
505 let field = Field51A::new(None, None, "CHASUS33XXX").unwrap();
506 assert!(!field.is_stp_allowed()); }
508
509 #[test]
510 fn test_field51a_bic_components() {
511 let field = Field51A::new(None, None, "CHASUS33XXX").unwrap();
512 assert_eq!(field.bank_code(), "CHAS");
513 assert_eq!(field.country_code(), "US");
514 assert_eq!(field.location_code(), "33");
515 assert_eq!(field.branch_code(), Some("XXX"));
516
517 let field = Field51A::new(None, None, "DEUTDEFF").unwrap();
518 assert_eq!(field.bank_code(), "DEUT");
519 assert_eq!(field.country_code(), "DE");
520 assert_eq!(field.location_code(), "FF");
521 assert_eq!(field.branch_code(), None);
522 }
523
524 #[test]
525 fn test_field51a_display() {
526 let field = Field51A::new(None, None, "CHASUS33XXX").unwrap();
527 assert_eq!(format!("{}", field), "CHASUS33XXX");
528
529 let field = Field51A::new(None, Some("1234567890".to_string()), "DEUTDEFF500").unwrap();
530 assert_eq!(format!("{}", field), "/1234567890 DEUTDEFF500");
531
532 let field = Field51A::new(
533 Some("C".to_string()),
534 Some("1234567890".to_string()),
535 "HSBCHKHH",
536 )
537 .unwrap();
538 assert_eq!(format!("{}", field), "/C/1234567890 HSBCHKHH");
539 }
540
541 #[test]
542 fn test_field51a_format_spec() {
543 assert_eq!(Field51A::format_spec(), "[/1!a][/34x]4!a2!a2!c[3!c]");
544 }
545}