swift_mt_message/fields/
field36.rs

1use super::swift_utils::parse_amount;
2use crate::errors::ParseError;
3use crate::traits::SwiftField;
4use serde::{Deserialize, Serialize};
5
6/// **Field 36: Exchange Rate**
7///
8/// Specifies the exchange rate used to convert instructed currency to settlement currency.
9/// Used when Field 33B currency differs from Field 32A currency.
10///
11/// **Format:** `12d` (decimal rate with comma separator)
12/// **Constraints:** Positive rate, within reasonable market range (0.0001 to 100000)
13///
14/// **Example:**
15/// ```text
16/// :36:1,2500
17/// ```
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19pub struct Field36 {
20    /// Exchange rate (from Field 33B currency to Field 32A currency)
21    pub rate: f64,
22}
23
24impl SwiftField for Field36 {
25    fn parse(input: &str) -> crate::Result<Self>
26    where
27        Self: Sized,
28    {
29        if input.is_empty() {
30            return Err(ParseError::InvalidFormat {
31                message: "Field 36 exchange rate cannot be empty".to_string(),
32            });
33        }
34
35        // Parse rate (up to 12 digits including decimal)
36        if input.len() > 12 {
37            return Err(ParseError::InvalidFormat {
38                message: format!(
39                    "Field 36 must not exceed 12 characters, found {}",
40                    input.len()
41                ),
42            });
43        }
44
45        let rate = parse_amount(input)?;
46
47        // Rate must be positive
48        if rate <= 0.0 {
49            return Err(ParseError::InvalidFormat {
50                message: "Field 36 exchange rate must be greater than zero".to_string(),
51            });
52        }
53
54        // Basic sanity check - exchange rate shouldn't be absurdly high or low
55        // Most real-world exchange rates are between 0.0001 and 100000
56        if !(0.0001..=100000.0).contains(&rate) {
57            return Err(ParseError::InvalidFormat {
58                message: format!(
59                    "Field 36 exchange rate {} appears to be outside reasonable range",
60                    rate
61                ),
62            });
63        }
64
65        Ok(Field36 { rate })
66    }
67
68    fn to_swift_string(&self) -> String {
69        // Format with comma as decimal separator
70        format!(":36:{}", self.rate.to_string().replace('.', ","))
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_field36_valid() {
80        let field = Field36::parse("1,2500").unwrap();
81        assert_eq!(field.rate, 1.25);
82        assert_eq!(field.to_swift_string(), ":36:1,25");
83
84        let field = Field36::parse("0,8500").unwrap();
85        assert_eq!(field.rate, 0.85);
86
87        let field = Field36::parse("110,2500").unwrap();
88        assert_eq!(field.rate, 110.25);
89
90        let field = Field36::parse("1").unwrap();
91        assert_eq!(field.rate, 1.0);
92
93        // Edge cases within reasonable range
94        let field = Field36::parse("0,0001").unwrap();
95        assert_eq!(field.rate, 0.0001);
96
97        let field = Field36::parse("99999").unwrap();
98        assert_eq!(field.rate, 99999.0);
99    }
100
101    #[test]
102    fn test_field36_invalid() {
103        // Empty
104        assert!(Field36::parse("").is_err());
105
106        // Too long
107        assert!(Field36::parse("1234567890123").is_err());
108
109        // Zero rate
110        assert!(Field36::parse("0").is_err());
111        assert!(Field36::parse("0,00").is_err());
112
113        // Negative rate
114        assert!(Field36::parse("-1,25").is_err());
115
116        // Unreasonably small rate
117        assert!(Field36::parse("0,00001").is_err());
118
119        // Unreasonably large rate
120        assert!(Field36::parse("999999").is_err());
121    }
122}