use super::field_utils::parse_party_identifier;
use super::swift_utils::{parse_bic, parse_max_length};
use crate::errors::ParseError;
use crate::traits::SwiftField;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct Field53A {
pub party_identifier: Option<String>,
pub bic: String,
}
impl SwiftField for Field53A {
fn parse(input: &str) -> crate::Result<Self>
where
Self: Sized,
{
let lines: Vec<&str> = input.split('\n').collect();
if lines.is_empty() {
return Err(ParseError::InvalidFormat {
message: "Field 53A requires input".to_string(),
});
}
let mut line_idx = 0;
let mut party_identifier = None;
if let Some(party_id) = parse_party_identifier(lines[0])? {
party_identifier = Some(format!("/{}", party_id));
line_idx = 1;
}
if line_idx >= lines.len() {
return Err(ParseError::InvalidFormat {
message: "Field 53A requires BIC code after party identifier".to_string(),
});
}
let bic = parse_bic(lines[line_idx])?;
Ok(Field53A {
party_identifier,
bic,
})
}
fn to_swift_string(&self) -> String {
let mut result = String::new();
if let Some(ref party_id) = self.party_identifier {
result.push_str(party_id);
result.push('\n');
}
result.push_str(&self.bic);
format!(":53A:{}", result)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct Field53B {
pub party_identifier: Option<String>,
pub location: Option<String>,
}
impl SwiftField for Field53B {
fn parse(input: &str) -> crate::Result<Self>
where
Self: Sized,
{
if input.is_empty() {
return Ok(Field53B {
party_identifier: None,
location: None,
});
}
let lines: Vec<&str> = input.split('\n').collect();
let mut party_identifier = None;
let mut location = None;
if lines.len() >= 2 {
if !lines[0].is_empty() {
party_identifier =
Some(parse_max_length(lines[0], 34, "Field53B party_identifier")?);
}
if !lines[1].is_empty() {
location = Some(parse_max_length(lines[1], 35, "Field53B location")?);
}
} else if lines.len() == 1 && !lines[0].is_empty() {
let line = lines[0];
let is_party_identifier = line.starts_with('/')
|| ((8..=11).contains(&line.len())
&& line
.chars()
.all(|c| c.is_ascii_uppercase() || c.is_ascii_digit()));
if is_party_identifier {
party_identifier = Some(parse_max_length(line, 34, "Field53B party_identifier")?);
} else {
location = Some(parse_max_length(line, 35, "Field53B location")?);
}
}
Ok(Field53B {
party_identifier,
location,
})
}
fn to_swift_string(&self) -> String {
let mut result = String::new();
if let Some(ref party_id) = self.party_identifier {
result.push_str(party_id);
if self.location.is_some() {
result.push('\n');
}
}
if let Some(ref loc) = self.location {
result.push_str(loc);
}
format!(":53B:{}", result)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct Field53D {
pub party_identifier: Option<String>,
pub name_and_address: Vec<String>,
}
impl SwiftField for Field53D {
fn parse(input: &str) -> crate::Result<Self>
where
Self: Sized,
{
let mut lines = input.split('\n').collect::<Vec<_>>();
if lines.is_empty() {
return Err(ParseError::InvalidFormat {
message: "Field 53D requires at least one line".to_string(),
});
}
let mut party_identifier = None;
if let Some(first_line) = lines.first() {
let looks_like_party_id = first_line.starts_with('/')
|| (first_line.len() <= 34
&& !first_line.contains(' ')
&& first_line.chars().any(|c| c.is_ascii_digit()));
if looks_like_party_id && !first_line.is_empty() && lines.len() > 1 {
party_identifier = Some(first_line.to_string());
lines.remove(0);
}
}
let mut name_and_address = Vec::new();
for (i, line) in lines.iter().enumerate() {
if i >= 4 {
break;
}
if line.len() > 35 {
return Err(ParseError::InvalidFormat {
message: format!("Field 53D line {} exceeds 35 characters", i + 1),
});
}
name_and_address.push(line.to_string());
}
if name_and_address.is_empty() {
return Err(ParseError::InvalidFormat {
message: "Field 53D must contain name and address information".to_string(),
});
}
Ok(Field53D {
party_identifier,
name_and_address,
})
}
fn to_swift_string(&self) -> String {
let mut result = String::new();
if let Some(ref party_id) = self.party_identifier {
result.push_str(party_id);
result.push('\n');
}
for (i, line) in self.name_and_address.iter().enumerate() {
if i > 0 {
result.push('\n');
}
result.push_str(line);
}
format!(":53D:{}", result)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum Field53SenderCorrespondent {
#[serde(rename = "53A")]
A(Field53A),
#[serde(rename = "53B")]
B(Field53B),
#[serde(rename = "53D")]
D(Field53D),
}
impl SwiftField for Field53SenderCorrespondent {
fn parse(input: &str) -> crate::Result<Self>
where
Self: Sized,
{
let lines: Vec<&str> = input.split('\n').collect();
let last_line = lines.last().unwrap_or(&"");
if (8..=11).contains(&last_line.len())
&& last_line
.chars()
.all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
{
if let Ok(field) = Field53A::parse(input) {
return Ok(Field53SenderCorrespondent::A(field));
}
}
if lines.len() > 2 || (lines.len() == 2 && !lines[0].starts_with('/')) {
if let Ok(field) = Field53D::parse(input) {
return Ok(Field53SenderCorrespondent::D(field));
}
}
if let Ok(field) = Field53B::parse(input) {
return Ok(Field53SenderCorrespondent::B(field));
}
if let Ok(field) = Field53A::parse(input) {
return Ok(Field53SenderCorrespondent::A(field));
}
if let Ok(field) = Field53D::parse(input) {
return Ok(Field53SenderCorrespondent::D(field));
}
Err(ParseError::InvalidFormat {
message: "Field 53 could not be parsed as any valid option (A, B, or D)".to_string(),
})
}
fn parse_with_variant(
value: &str,
variant: Option<&str>,
_field_tag: Option<&str>,
) -> crate::Result<Self>
where
Self: Sized,
{
match variant {
Some("A") => {
let field = Field53A::parse(value)?;
Ok(Field53SenderCorrespondent::A(field))
}
Some("B") => {
let field = Field53B::parse(value)?;
Ok(Field53SenderCorrespondent::B(field))
}
Some("D") => {
let field = Field53D::parse(value)?;
Ok(Field53SenderCorrespondent::D(field))
}
_ => {
Self::parse(value)
}
}
}
fn to_swift_string(&self) -> String {
match self {
Field53SenderCorrespondent::A(field) => field.to_swift_string(),
Field53SenderCorrespondent::B(field) => field.to_swift_string(),
Field53SenderCorrespondent::D(field) => field.to_swift_string(),
}
}
fn get_variant_tag(&self) -> Option<&'static str> {
match self {
Field53SenderCorrespondent::A(_) => Some("A"),
Field53SenderCorrespondent::B(_) => Some("B"),
Field53SenderCorrespondent::D(_) => Some("D"),
}
}
}
pub type Field53 = Field53SenderCorrespondent;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_field53a_valid() {
let field = Field53A::parse("CHASUS33XXX").unwrap();
assert_eq!(field.bic, "CHASUS33XXX");
assert_eq!(field.party_identifier, None);
assert_eq!(field.to_swift_string(), ":53A:CHASUS33XXX");
let field = Field53A::parse("/C/12345678\nCHASUS33").unwrap();
assert_eq!(field.bic, "CHASUS33");
assert_eq!(field.party_identifier, Some("/C/12345678".to_string()));
assert_eq!(field.to_swift_string(), ":53A:/C/12345678\nCHASUS33");
}
#[test]
fn test_field53b_valid() {
let field = Field53B::parse("NEW YORK BRANCH").unwrap();
assert_eq!(field.location, Some("NEW YORK BRANCH".to_string()));
assert_eq!(field.party_identifier, None);
let field = Field53B::parse("/D/98765432\nNEW YORK").unwrap();
assert_eq!(field.party_identifier, Some("/D/98765432".to_string()));
assert_eq!(field.location, Some("NEW YORK".to_string()));
let field = Field53B::parse("").unwrap();
assert_eq!(field.party_identifier, None);
assert_eq!(field.location, None);
}
#[test]
fn test_field53d_valid() {
let field =
Field53D::parse("/C/12345678\nCORRESPONDENT BANK\n123 MAIN ST\nNEW YORK\nUSA").unwrap();
assert_eq!(field.party_identifier, Some("/C/12345678".to_string()));
assert_eq!(field.name_and_address.len(), 4);
assert_eq!(field.name_and_address[0], "CORRESPONDENT BANK");
assert_eq!(field.name_and_address[3], "USA");
let field = Field53D::parse("CORRESPONDENT BANK\nNEW YORK").unwrap();
assert_eq!(field.party_identifier, None);
assert_eq!(field.name_and_address.len(), 2);
}
#[test]
fn test_field53_enum() {
let field = Field53SenderCorrespondent::parse("CHASUS33XXX").unwrap();
assert!(matches!(field, Field53SenderCorrespondent::A(_)));
let field = Field53SenderCorrespondent::parse("NEW YORK BRANCH").unwrap();
assert!(matches!(field, Field53SenderCorrespondent::B(_)));
let field = Field53SenderCorrespondent::parse("BANK NAME\nADDRESS LINE 1\nCITY").unwrap();
assert!(matches!(field, Field53SenderCorrespondent::D(_)));
}
}