use super::field_utils::{parse_name_and_address, parse_party_identifier};
use super::swift_utils::{parse_bic, parse_swift_chars};
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 Field57A {
#[serde(skip_serializing_if = "Option::is_none")]
pub party_identifier: Option<String>,
pub bic: String,
}
impl SwiftField for Field57A {
fn parse(input: &str) -> crate::Result<Self>
where
Self: Sized,
{
let lines: Vec<&str> = input.lines().collect();
if lines.is_empty() {
return Err(ParseError::InvalidFormat {
message: "Field 57A cannot be empty".to_string(),
});
}
let mut party_identifier = None;
let mut bic_line_idx = 0;
if let Some(party_id) = parse_party_identifier(lines[0])? {
party_identifier = Some(party_id);
bic_line_idx = 1;
}
if bic_line_idx >= lines.len() {
return Err(ParseError::InvalidFormat {
message: "Field 57A missing BIC code".to_string(),
});
}
let bic = parse_bic(lines[bic_line_idx])?;
Ok(Field57A {
party_identifier,
bic,
})
}
fn to_swift_string(&self) -> String {
let mut result = Vec::new();
if let Some(ref id) = self.party_identifier {
result.push(format!("/{}", id));
}
result.push(self.bic.clone());
format!(":57A:{}", result.join("\n"))
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct Field57B {
pub party_identifier: Option<String>,
pub location: Option<String>,
}
impl SwiftField for Field57B {
fn parse(input: &str) -> crate::Result<Self>
where
Self: Sized,
{
if input.is_empty() {
return Ok(Field57B {
party_identifier: None,
location: None,
});
}
let lines: Vec<&str> = input.lines().collect();
let mut party_identifier = None;
let mut location = None;
let mut current_idx = 0;
if !lines.is_empty()
&& let Some(party_id) = parse_party_identifier(lines[0])?
{
party_identifier = Some(party_id);
current_idx = 1;
}
if current_idx < lines.len() {
let loc = lines[current_idx];
if !loc.is_empty() && loc.len() <= 35 {
parse_swift_chars(loc, "Field 57B location")?;
location = Some(loc.to_string());
}
}
Ok(Field57B {
party_identifier,
location,
})
}
fn to_swift_string(&self) -> String {
let mut result = Vec::new();
if let Some(ref id) = self.party_identifier {
result.push(format!("/{}", id));
}
if let Some(ref loc) = self.location {
result.push(loc.clone());
}
format!(":57B:{}", result.join("\n"))
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct Field57C {
pub party_identifier: String,
}
impl SwiftField for Field57C {
fn parse(input: &str) -> crate::Result<Self>
where
Self: Sized,
{
if !input.starts_with('/') {
return Err(ParseError::InvalidFormat {
message: "Field 57C must start with '/'".to_string(),
});
}
let identifier = &input[1..];
if identifier.is_empty() || identifier.len() > 34 {
return Err(ParseError::InvalidFormat {
message: "Field 57C party identifier must be 1-34 characters".to_string(),
});
}
parse_swift_chars(identifier, "Field 57C party identifier")?;
Ok(Field57C {
party_identifier: identifier.to_string(),
})
}
fn to_swift_string(&self) -> String {
format!(":57C:/{}", self.party_identifier)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct Field57D {
pub party_identifier: Option<String>,
pub name_and_address: Vec<String>,
}
impl SwiftField for Field57D {
fn parse(input: &str) -> crate::Result<Self>
where
Self: Sized,
{
let lines: Vec<&str> = input.lines().collect();
if lines.is_empty() {
return Err(ParseError::InvalidFormat {
message: "Field 57D must have at least one line".to_string(),
});
}
let mut party_identifier = None;
let mut start_idx = 0;
if let Some(party_id) = parse_party_identifier(lines[0])? {
party_identifier = Some(party_id);
start_idx = 1;
}
let name_and_address = parse_name_and_address(&lines, start_idx, "Field57D")?;
Ok(Field57D {
party_identifier,
name_and_address,
})
}
fn to_swift_string(&self) -> String {
let mut result = Vec::new();
if let Some(ref id) = self.party_identifier {
result.push(format!("/{}", id));
}
for line in &self.name_and_address {
result.push(line.clone());
}
format!(":57D:{}", result.join("\n"))
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum Field57 {
#[serde(rename = "57A")]
A(Field57A),
#[serde(rename = "57B")]
B(Field57B),
#[serde(rename = "57C")]
C(Field57C),
#[serde(rename = "57D")]
D(Field57D),
}
impl SwiftField for Field57 {
fn parse(input: &str) -> crate::Result<Self>
where
Self: Sized,
{
if let Ok(field) = Field57A::parse(input) {
return Ok(Field57::A(field));
}
if input.starts_with('/')
&& !input.contains('\n')
&& let Ok(field) = Field57C::parse(input)
{
return Ok(Field57::C(field));
}
if let Ok(field) = Field57B::parse(input) {
return Ok(Field57::B(field));
}
if let Ok(field) = Field57D::parse(input) {
return Ok(Field57::D(field));
}
Err(ParseError::InvalidFormat {
message: "Field 57 could not be parsed as option A, B, C 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 = Field57A::parse(value)?;
Ok(Field57::A(field))
}
Some("B") => {
let field = Field57B::parse(value)?;
Ok(Field57::B(field))
}
Some("C") => {
let field = Field57C::parse(value)?;
Ok(Field57::C(field))
}
Some("D") => {
let field = Field57D::parse(value)?;
Ok(Field57::D(field))
}
_ => {
Self::parse(value)
}
}
}
fn to_swift_string(&self) -> String {
match self {
Field57::A(field) => field.to_swift_string(),
Field57::B(field) => field.to_swift_string(),
Field57::C(field) => field.to_swift_string(),
Field57::D(field) => field.to_swift_string(),
}
}
fn get_variant_tag(&self) -> Option<&'static str> {
match self {
Field57::A(_) => Some("A"),
Field57::B(_) => Some("B"),
Field57::C(_) => Some("C"),
Field57::D(_) => Some("D"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_field57a() {
let field = Field57A::parse("//FW123456\nDEUTDEFF").unwrap();
assert_eq!(field.party_identifier, Some("/FW123456".to_string()));
assert_eq!(field.bic, "DEUTDEFF");
let field = Field57A::parse("/C/US123456\nDEUTDEFF").unwrap();
assert_eq!(field.party_identifier, Some("C/US123456".to_string()));
assert_eq!(field.bic, "DEUTDEFF");
let field = Field57A::parse("CHASUS33XXX").unwrap();
assert_eq!(field.party_identifier, None);
assert_eq!(field.bic, "CHASUS33XXX");
}
#[test]
fn test_field57b() {
let field = Field57B::parse("/A/12345\nNEW YORK").unwrap();
assert_eq!(field.party_identifier, Some("A/12345".to_string()));
assert_eq!(field.location, Some("NEW YORK".to_string()));
let field = Field57B::parse("").unwrap();
assert_eq!(field.party_identifier, None);
assert_eq!(field.location, None);
}
#[test]
fn test_field57c() {
let field = Field57C::parse("/UKCLEARING123").unwrap();
assert_eq!(field.party_identifier, "UKCLEARING123");
assert_eq!(field.to_swift_string(), ":57C:/UKCLEARING123");
}
#[test]
fn test_field57d() {
let field = Field57D::parse("//FW\nCHASE BANK\nNEW YORK").unwrap();
assert_eq!(field.party_identifier, Some("/FW".to_string()));
assert_eq!(field.name_and_address.len(), 2);
assert_eq!(field.name_and_address[0], "CHASE BANK");
let field = Field57D::parse("BENEFICIARY BANK\nLONDON").unwrap();
assert_eq!(field.party_identifier, None);
assert_eq!(field.name_and_address.len(), 2);
}
#[test]
fn test_field57_payment_method_codes() {
let field = Field57A::parse("//FW\nCHASUS33").unwrap();
assert_eq!(field.party_identifier, Some("/FW".to_string()));
let field = Field57A::parse("//RT\nDEUTDEFF").unwrap();
assert_eq!(field.party_identifier, Some("/RT".to_string()));
let field = Field57A::parse("//AU\nANZBAU3M").unwrap();
assert_eq!(field.party_identifier, Some("/AU".to_string()));
let field = Field57A::parse("//IN\nHDFCINBB").unwrap();
assert_eq!(field.party_identifier, Some("/IN".to_string()));
}
#[test]
fn test_field57_invalid() {
assert!(Field57A::parse("INVALID").is_err());
assert!(Field57C::parse("NOSLASH").is_err());
assert!(Field57D::parse("LINE1\nLINE2\nLINE3\nLINE4\nLINE5").is_err());
}
}
pub type Field57AccountWithInstitution = Field57;
pub type Field57DebtorBank = Field57;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum Field57DebtInstitution {
#[serde(rename = "57A")]
A(Field57A),
#[serde(rename = "57B")]
B(Field57B),
#[serde(rename = "57D")]
D(Field57D),
}
impl SwiftField for Field57DebtInstitution {
fn parse(input: &str) -> crate::Result<Self>
where
Self: Sized,
{
if let Ok(field) = Field57A::parse(input) {
return Ok(Field57DebtInstitution::A(field));
}
if let Ok(field) = Field57B::parse(input) {
return Ok(Field57DebtInstitution::B(field));
}
if let Ok(field) = Field57D::parse(input) {
return Ok(Field57DebtInstitution::D(field));
}
Err(ParseError::InvalidFormat {
message: "Field 57 must be one of formats: 57A (party + BIC), 57B (party + location), or 57D (name + address)".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 = Field57A::parse(value)?;
Ok(Field57DebtInstitution::A(field))
}
Some("B") => {
let field = Field57B::parse(value)?;
Ok(Field57DebtInstitution::B(field))
}
Some("D") => {
let field = Field57D::parse(value)?;
Ok(Field57DebtInstitution::D(field))
}
_ => {
Self::parse(value)
}
}
}
fn to_swift_string(&self) -> String {
match self {
Field57DebtInstitution::A(field) => field.to_swift_string(),
Field57DebtInstitution::B(field) => field.to_swift_string(),
Field57DebtInstitution::D(field) => field.to_swift_string(),
}
}
}
pub type Field57AccountWithABD = Field57DebtInstitution;