swift_mt_message/fields/
field33.rs

1use super::swift_utils::{
2    format_swift_amount_for_currency, parse_amount_with_currency, parse_currency_non_commodity,
3};
4use crate::errors::ParseError;
5use crate::traits::SwiftField;
6use serde::{Deserialize, Serialize};
7
8/// **Field 33B: Currency/Instructed Amount**
9///
10/// Original instructed currency and amount before conversion or charge deductions.
11/// Used when settlement amount differs from originally instructed amount.
12///
13/// **Format:** `3!a15d` (currency + amount, e.g., `USD1250,00`)
14/// **Constraints:** Valid ISO 4217 currency, positive amount, currency-specific precision
15///
16/// **Example:**
17/// ```text
18/// :33B:USD1250,00
19/// :33B:JPY125000
20/// ```
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub struct Field33B {
23    /// ISO 4217 currency code (e.g., USD, EUR, GBP)
24    pub currency: String,
25    /// Original instructed amount (precision follows currency rules)
26    pub amount: f64,
27}
28
29impl SwiftField for Field33B {
30    fn parse(input: &str) -> crate::Result<Self>
31    where
32        Self: Sized,
33    {
34        // Field33B format: 3!a15d (currency + amount)
35        if input.len() < 4 {
36            // Minimum: 3 chars currency + 1 digit amount
37            return Err(ParseError::InvalidFormat {
38                message: format!(
39                    "Field 33B must be at least 4 characters, found {}",
40                    input.len()
41                ),
42            });
43        }
44
45        // Parse currency code (first 3 characters) - T52 + C08 validation
46        let currency = parse_currency_non_commodity(&input[0..3])?;
47
48        // Parse amount (remaining characters) - T40/T43 + C03 validation
49        let amount_str = &input[3..];
50        if amount_str.is_empty() {
51            return Err(ParseError::InvalidFormat {
52                message: "Field 33B amount cannot be empty".to_string(),
53            });
54        }
55
56        let amount = parse_amount_with_currency(amount_str, &currency)?;
57
58        // Amount must be positive
59        if amount <= 0.0 {
60            return Err(ParseError::InvalidFormat {
61                message: "Field 33B amount must be greater than zero".to_string(),
62            });
63        }
64
65        Ok(Field33B { currency, amount })
66    }
67
68    fn to_swift_string(&self) -> String {
69        format!(
70            ":33B:{}{}",
71            self.currency,
72            format_swift_amount_for_currency(self.amount, &self.currency)
73        )
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_field33b_valid() {
83        let field = Field33B::parse("USD1250,00").unwrap();
84        assert_eq!(field.currency, "USD");
85        assert_eq!(field.amount, 1250.00);
86        assert_eq!(field.to_swift_string(), ":33B:USD1250");
87
88        let field = Field33B::parse("EUR950,50").unwrap();
89        assert_eq!(field.currency, "EUR");
90        assert_eq!(field.amount, 950.50);
91
92        let field = Field33B::parse("JPY125000").unwrap();
93        assert_eq!(field.currency, "JPY");
94        assert_eq!(field.amount, 125000.0);
95    }
96
97    #[test]
98    fn test_field33b_invalid() {
99        // Invalid currency
100        assert!(Field33B::parse("12A100").is_err());
101        assert!(Field33B::parse("US100").is_err());
102
103        // Zero amount
104        assert!(Field33B::parse("USD0").is_err());
105
106        // Negative amount
107        assert!(Field33B::parse("USD-100").is_err());
108
109        // Missing amount
110        assert!(Field33B::parse("USD").is_err());
111    }
112}