use super::swift_utils::{
format_swift_amount_for_currency, parse_amount_with_currency, parse_currency_non_commodity,
parse_exact_length, parse_uppercase,
};
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 Field71A {
pub code: String,
}
impl SwiftField for Field71A {
fn parse(input: &str) -> crate::Result<Self>
where
Self: Sized,
{
let code = parse_exact_length(input, 3, "Field 71A code")?;
parse_uppercase(&code, "Field 71A code")?;
const VALID_CODES: &[&str] = &["BEN", "OUR", "SHA"];
if !VALID_CODES.contains(&code.as_str()) {
return Err(ParseError::InvalidFormat {
message: format!(
"Field 71A code must be one of {:?}, found {}",
VALID_CODES, code
),
});
}
Ok(Field71A { code })
}
fn to_swift_string(&self) -> String {
format!(":71A:{}", self.code)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct Field71F {
pub currency: String,
pub amount: f64,
}
impl SwiftField for Field71F {
fn parse(input: &str) -> crate::Result<Self>
where
Self: Sized,
{
if input.len() < 4 {
return Err(ParseError::InvalidFormat {
message: format!(
"Field 71F must be at least 4 characters, found {}",
input.len()
),
});
}
let currency = parse_currency_non_commodity(&input[0..3])?;
let amount_str = &input[3..];
if amount_str.is_empty() {
return Err(ParseError::InvalidFormat {
message: "Field 71F amount is required".to_string(),
});
}
let amount = parse_amount_with_currency(amount_str, ¤cy)?;
Ok(Field71F { currency, amount })
}
fn to_swift_string(&self) -> String {
format!(
":71F:{}{}",
self.currency,
format_swift_amount_for_currency(self.amount, &self.currency)
)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct Field71G {
pub currency: String,
pub amount: f64,
}
impl SwiftField for Field71G {
fn parse(input: &str) -> crate::Result<Self>
where
Self: Sized,
{
if input.len() < 4 {
return Err(ParseError::InvalidFormat {
message: format!(
"Field 71G must be at least 4 characters, found {}",
input.len()
),
});
}
let currency = parse_currency_non_commodity(&input[0..3])?;
let amount_str = &input[3..];
if amount_str.is_empty() {
return Err(ParseError::InvalidFormat {
message: "Field 71G amount is required".to_string(),
});
}
let amount = parse_amount_with_currency(amount_str, ¤cy)?;
Ok(Field71G { currency, amount })
}
fn to_swift_string(&self) -> String {
format!(
":71G:{}{}",
self.currency,
format_swift_amount_for_currency(self.amount, &self.currency)
)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct Field71B {
pub details: Vec<String>,
}
impl SwiftField for Field71B {
fn parse(input: &str) -> crate::Result<Self>
where
Self: Sized,
{
use super::field_utils::parse_multiline_text;
let details = parse_multiline_text(input, 6, 35)?;
if details.is_empty() {
return Err(ParseError::InvalidFormat {
message: "Field 71B must have at least one line of details".to_string(),
});
}
Ok(Field71B { details })
}
fn to_swift_string(&self) -> String {
format!(":71B:{}", self.details.join("\n"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_field71a() {
let field = Field71A::parse("BEN").unwrap();
assert_eq!(field.code, "BEN");
let field = Field71A::parse("OUR").unwrap();
assert_eq!(field.code, "OUR");
let field = Field71A::parse("SHA").unwrap();
assert_eq!(field.code, "SHA");
assert!(Field71A::parse("BE").is_err());
assert!(Field71A::parse("BENE").is_err());
assert!(Field71A::parse("XXX").is_err());
assert!(Field71A::parse("ben").is_err());
let field = Field71A {
code: "SHA".to_string(),
};
assert_eq!(field.to_swift_string(), ":71A:SHA");
}
#[test]
fn test_field71f() {
let field = Field71F::parse("USD100.50").unwrap();
assert_eq!(field.currency, "USD");
assert_eq!(field.amount, 100.50);
let field = Field71F::parse("EUR1234,56").unwrap();
assert_eq!(field.currency, "EUR");
assert_eq!(field.amount, 1234.56);
let field = Field71F::parse("GBP500").unwrap();
assert_eq!(field.currency, "GBP");
assert_eq!(field.amount, 500.0);
let field = Field71F {
currency: "USD".to_string(),
amount: 250.75,
};
assert_eq!(field.to_swift_string(), ":71F:USD250,75");
assert!(Field71F::parse("US100").is_err());
assert!(Field71F::parse("123100").is_err());
assert!(Field71F::parse("USD").is_err());
}
#[test]
fn test_field71b() {
let field = Field71B::parse("COMMISSION USD 25.00").unwrap();
assert_eq!(field.details.len(), 1);
assert_eq!(field.details[0], "COMMISSION USD 25.00");
let input = "COMMISSION USD 25.00\nINTEREST USD 10.50\nSERVICE FEE USD 5.00";
let field = Field71B::parse(input).unwrap();
assert_eq!(field.details.len(), 3);
assert_eq!(field.details[0], "COMMISSION USD 25.00");
assert_eq!(field.details[1], "INTEREST USD 10.50");
assert_eq!(field.details[2], "SERVICE FEE USD 5.00");
assert_eq!(field.to_swift_string(), format!(":71B:{}", input));
}
#[test]
fn test_field71g() {
let field = Field71G::parse("CHF75.25").unwrap();
assert_eq!(field.currency, "CHF");
assert_eq!(field.amount, 75.25);
let field = Field71G::parse("JPY1000000").unwrap();
assert_eq!(field.currency, "JPY");
assert_eq!(field.amount, 1000000.0);
let field = Field71G {
currency: "CAD".to_string(),
amount: 99.99,
};
assert_eq!(field.to_swift_string(), ":71G:CAD99,99");
}
}