use super::swift_utils::{parse_bic, parse_max_length, 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 Field25NoOption {
pub authorisation: String,
}
impl SwiftField for Field25NoOption {
fn parse(input: &str) -> crate::Result<Self>
where
Self: Sized,
{
let input_stripped = input.strip_prefix('/').unwrap_or(input);
let authorisation = parse_max_length(input_stripped, 35, "Field 25 authorisation")?;
parse_swift_chars(&authorisation, "Field 25 authorisation")?;
Ok(Field25NoOption { authorisation })
}
fn to_swift_string(&self) -> String {
if self.authorisation.starts_with('/') {
format!(":25:{}", self.authorisation)
} else {
format!(":25:/{}", self.authorisation)
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct Field25A {
pub account: String,
}
impl SwiftField for Field25A {
fn parse(input: &str) -> crate::Result<Self>
where
Self: Sized,
{
if !input.starts_with('/') {
return Err(ParseError::InvalidFormat {
message: "Field 25A must start with '/'".to_string(),
});
}
let account_part = &input[1..];
if account_part.is_empty() {
return Err(ParseError::InvalidFormat {
message: "Field 25A account cannot be empty after '/'".to_string(),
});
}
if account_part.len() > 34 {
return Err(ParseError::InvalidFormat {
message: format!(
"Field 25A account must not exceed 34 characters, found {}",
account_part.len()
),
});
}
parse_swift_chars(account_part, "Field 25A account")?;
Ok(Field25A {
account: account_part.to_string(), })
}
fn to_swift_string(&self) -> String {
if self.account.starts_with('/') {
format!(":25A:{}", self.account)
} else {
format!(":25A:/{}", self.account)
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct Field25P {
pub account: String,
pub bic: String,
}
impl SwiftField for Field25P {
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 25P cannot be empty".to_string(),
});
}
let account = parse_max_length(lines[0], 35, "Field 25P account")?;
parse_swift_chars(&account, "Field 25P account")?;
let bic = if lines.len() > 1 {
parse_bic(lines[1])?
} else {
if input.len() > 8 {
let potential_bic_11 = &input[input.len().saturating_sub(11)..];
let potential_bic_8 = &input[input.len().saturating_sub(8)..];
if potential_bic_11.len() == 11
&& let Ok(bic) = parse_bic(potential_bic_11)
{
let account_part = &input[..input.len() - 11];
return Ok(Field25P {
account: parse_max_length(account_part, 35, "Field 25P account")?,
bic,
});
}
if potential_bic_8.len() == 8
&& let Ok(bic) = parse_bic(potential_bic_8)
{
let account_part = &input[..input.len() - 8];
return Ok(Field25P {
account: parse_max_length(account_part, 35, "Field 25P account")?,
bic,
});
}
}
return Err(ParseError::InvalidFormat {
message: "Field 25P requires a BIC code".to_string(),
});
};
Ok(Field25P { account, bic })
}
fn to_swift_string(&self) -> String {
format!(":25P:{}\n{}", self.account, self.bic)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum Field25AccountIdentification {
NoOption(Field25NoOption),
P(Field25P),
}
impl SwiftField for Field25AccountIdentification {
fn parse(input: &str) -> crate::Result<Self>
where
Self: Sized,
{
if input.contains('\n')
|| (input.len() > 8 && parse_bic(&input[input.len().saturating_sub(11)..]).is_ok())
|| (input.len() > 8 && parse_bic(&input[input.len().saturating_sub(8)..]).is_ok())
{
Ok(Field25AccountIdentification::P(Field25P::parse(input)?))
} else {
Ok(Field25AccountIdentification::NoOption(
Field25NoOption::parse(input)?,
))
}
}
fn to_swift_string(&self) -> String {
match self {
Field25AccountIdentification::NoOption(field) => field.to_swift_string(),
Field25AccountIdentification::P(field) => field.to_swift_string(),
}
}
}
pub type Field25 = Field25NoOption;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_field25_no_option() {
let field = Field25NoOption::parse("AUTH123456789").unwrap();
assert_eq!(field.authorisation, "AUTH123456789");
assert_eq!(field.to_swift_string(), ":25:/AUTH123456789");
let field = Field25NoOption::parse("/1234567890").unwrap();
assert_eq!(field.authorisation, "1234567890"); assert_eq!(field.to_swift_string(), ":25:/1234567890");
let long_auth = "A".repeat(35);
let field = Field25NoOption::parse(&long_auth).unwrap();
assert_eq!(field.authorisation, long_auth);
let too_long = "A".repeat(36);
assert!(Field25NoOption::parse(&too_long).is_err());
}
#[test]
fn test_field25a() {
let field = Field25A::parse("/GB82WEST12345698765432").unwrap();
assert_eq!(field.account, "GB82WEST12345698765432"); assert_eq!(field.to_swift_string(), ":25A:/GB82WEST12345698765432");
let field = Field25A::parse("/1234567890").unwrap();
assert_eq!(field.account, "1234567890");
assert!(Field25A::parse("1234567890").is_err());
assert!(Field25A::parse("/").is_err());
let too_long = format!("/{}", "A".repeat(35));
assert!(Field25A::parse(&too_long).is_err());
}
#[test]
fn test_field25p() {
let field = Field25P::parse("CHF1234567890\nUBSWCHZH80A").unwrap();
assert_eq!(field.account, "CHF1234567890");
assert_eq!(field.bic, "UBSWCHZH80A");
assert_eq!(field.to_swift_string(), ":25P:CHF1234567890\nUBSWCHZH80A");
let field = Field25P::parse("ACCOUNT123DEUTDEFF").unwrap();
assert_eq!(field.account, "ACCOUNT123");
assert_eq!(field.bic, "DEUTDEFF");
let field = Field25P::parse("ACC456DEUTDEFFXXX").unwrap();
assert_eq!(field.account, "ACC456");
assert_eq!(field.bic, "DEUTDEFFXXX");
}
#[test]
fn test_field25_account_identification() {
let field = Field25AccountIdentification::parse("AUTH999").unwrap();
match field {
Field25AccountIdentification::NoOption(f) => assert_eq!(f.authorisation, "AUTH999"),
_ => panic!("Expected NoOption variant"),
}
let field = Field25AccountIdentification::parse("MYACCOUNT\nDEUTDEFF").unwrap();
match field {
Field25AccountIdentification::P(f) => {
assert_eq!(f.account, "MYACCOUNT");
assert_eq!(f.bic, "DEUTDEFF");
}
_ => panic!("Expected P variant"),
}
}
}