use crate::errors::{ParseError, SwiftValidationError};
use crate::fields::*;
use crate::parser::MessageParser;
use crate::parser::utils::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct MT210 {
#[serde(rename = "20")]
pub transaction_reference: Field20,
#[serde(rename = "25", skip_serializing_if = "Option::is_none")]
pub account_identification: Option<Field25NoOption>,
#[serde(rename = "30")]
pub value_date: Field30,
#[serde(rename = "#", default)]
pub transactions: Vec<MT210Transaction>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct MT210Transaction {
#[serde(rename = "21", skip_serializing_if = "Option::is_none")]
pub related_reference: Option<Field21NoOption>,
#[serde(rename = "32B")]
pub currency_amount: Field32B,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub ordering_customer: Option<Field50>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub ordering_institution: Option<Field52OrderingInstitution>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub intermediary: Option<Field56>,
}
impl MT210 {
pub fn parse_from_block4(block4: &str) -> Result<Self, ParseError> {
let mut parser = MessageParser::new(block4, "210");
let transaction_reference = parser.parse_field::<Field20>("20")?;
let account_identification = parser.parse_optional_field::<Field25NoOption>("25")?;
let value_date = parser.parse_field::<Field30>("30")?;
parser = parser.with_duplicates(true);
let mut transactions = Vec::new();
while parser.detect_field("21") || parser.detect_field("32B") {
let related_reference = parser.parse_optional_field::<Field21NoOption>("21")?;
let currency_amount = parser.parse_field::<Field32B>("32B")?;
let ordering_customer = parser.parse_optional_variant_field::<Field50>("50")?;
let ordering_institution =
parser.parse_optional_variant_field::<Field52OrderingInstitution>("52")?;
let intermediary = parser.parse_optional_variant_field::<Field56>("56")?;
transactions.push(MT210Transaction {
related_reference,
currency_amount,
ordering_customer,
ordering_institution,
intermediary,
});
if transactions.len() >= 10 {
break;
}
}
Ok(MT210 {
transaction_reference,
account_identification,
value_date,
transactions,
})
}
const MAX_REPETITIVE_SEQUENCES: usize = 10;
fn validate_c1_repetitive_sequence_count(&self) -> Option<SwiftValidationError> {
if self.transactions.len() > Self::MAX_REPETITIVE_SEQUENCES {
return Some(SwiftValidationError::format_error(
"T10",
"21",
&self.transactions.len().to_string(),
&format!("Max {} occurrences", Self::MAX_REPETITIVE_SEQUENCES),
&format!(
"The repetitive sequence must not appear more than {} times. Found {} occurrences",
Self::MAX_REPETITIVE_SEQUENCES,
self.transactions.len()
),
));
}
None
}
fn validate_c2_mutual_exclusivity(&self) -> Vec<SwiftValidationError> {
let mut errors = Vec::new();
for (idx, transaction) in self.transactions.iter().enumerate() {
let has_ordering_customer = transaction.ordering_customer.is_some();
let has_ordering_institution = transaction.ordering_institution.is_some();
if has_ordering_customer && has_ordering_institution {
errors.push(SwiftValidationError::content_error(
"C06",
"50a/52a",
"",
&format!(
"Transaction {}: Either field 50a (Ordering Customer) or field 52a (Ordering Institution), but not both, must be present",
idx + 1
),
"Field 50a and field 52a are mutually exclusive. Only one may be present in each repetitive sequence",
));
} else if !has_ordering_customer && !has_ordering_institution {
errors.push(SwiftValidationError::content_error(
"C06",
"50a/52a",
"",
&format!(
"Transaction {}: Either field 50a (Ordering Customer) or field 52a (Ordering Institution) must be present",
idx + 1
),
"At least one of field 50a or field 52a must be present in each repetitive sequence",
));
}
}
errors
}
fn validate_c3_currency_consistency(&self) -> Option<SwiftValidationError> {
if self.transactions.is_empty() {
return None;
}
let first_currency = &self.transactions[0].currency_amount.currency;
for (idx, transaction) in self.transactions.iter().enumerate().skip(1) {
if &transaction.currency_amount.currency != first_currency {
return Some(SwiftValidationError::content_error(
"C02",
"32B",
&transaction.currency_amount.currency,
&format!(
"Transaction {}: Currency code in field 32B ({}) must be the same as in the first transaction ({})",
idx + 1,
transaction.currency_amount.currency,
first_currency
),
"The currency code must be the same for all occurrences of field 32B in the message",
));
}
}
None
}
pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
let mut all_errors = Vec::new();
if let Some(error) = self.validate_c1_repetitive_sequence_count() {
all_errors.push(error);
if stop_on_first_error {
return all_errors;
}
}
let c2_errors = self.validate_c2_mutual_exclusivity();
all_errors.extend(c2_errors);
if stop_on_first_error && !all_errors.is_empty() {
return all_errors;
}
if let Some(error) = self.validate_c3_currency_consistency() {
all_errors.push(error);
}
all_errors
}
}
impl crate::traits::SwiftMessageBody for MT210 {
fn message_type() -> &'static str {
"210"
}
fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
Self::parse_from_block4(block4)
}
fn to_mt_string(&self) -> String {
let mut result = String::new();
append_field(&mut result, &self.transaction_reference);
append_optional_field(&mut result, &self.account_identification);
append_field(&mut result, &self.value_date);
for txn in &self.transactions {
append_optional_field(&mut result, &txn.related_reference);
append_field(&mut result, &txn.currency_amount);
append_optional_field(&mut result, &txn.ordering_customer);
append_optional_field(&mut result, &txn.ordering_institution);
append_optional_field(&mut result, &txn.intermediary);
}
finalize_mt_string(result, false)
}
fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
MT210::validate_network_rules(self, stop_on_first_error)
}
}