swift_mt_message/fields/
field37.rs

1use super::swift_utils::parse_amount;
2use crate::errors::ParseError;
3use crate::traits::SwiftField;
4use serde::{Deserialize, Serialize};
5
6/// **Field 37H: Interest Rate**
7///
8/// Specifies interest rates for financial instruments, typically expressed as percentage.
9/// Supports negative rates for low interest rate environments.
10///
11/// **Format:** `1!a[N]12d` (indicator + optional N for negative + rate)
12/// **Constraints:** C (Credit) or D (Debit) indicator, rate with comma separator
13///
14/// **Example:**
15/// ```text
16/// :37H:C2,5000
17/// :37H:CN0,2500
18/// ```
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub struct Field37H {
21    /// Rate type indicator: 'C' (Credit) or 'D' (Debit)
22    pub rate_indicator: char,
23
24    /// Negative rate indicator (Some(true) if rate is negative)
25    pub is_negative: Option<bool>,
26
27    /// Interest rate value (e.g., 2.5000 = 2.5%)
28    pub rate: f64,
29}
30
31impl SwiftField for Field37H {
32    fn parse(input: &str) -> crate::Result<Self>
33    where
34        Self: Sized,
35    {
36        let mut remaining = input;
37
38        // Parse rate indicator (1!a)
39        if remaining.is_empty() {
40            return Err(ParseError::InvalidFormat {
41                message: "Field37H requires rate indicator".to_string(),
42            });
43        }
44
45        let rate_indicator = remaining.chars().next().unwrap();
46        if rate_indicator != 'C' && rate_indicator != 'D' {
47            return Err(ParseError::InvalidFormat {
48                message: "Field37H rate indicator must be 'C' or 'D'".to_string(),
49            });
50        }
51        remaining = &remaining[1..];
52
53        // Parse optional negative indicator ([1!a])
54        let is_negative = if remaining.starts_with('N') {
55            remaining = &remaining[1..];
56            Some(true)
57        } else {
58            None
59        };
60
61        // Parse rate value (12d)
62        if remaining.is_empty() {
63            return Err(ParseError::InvalidFormat {
64                message: "Field37H requires rate value".to_string(),
65            });
66        }
67
68        let rate = if is_negative.is_some() {
69            -parse_amount(remaining)?
70        } else {
71            parse_amount(remaining)?
72        };
73
74        Ok(Field37H {
75            rate_indicator,
76            is_negative,
77            rate,
78        })
79    }
80
81    fn to_swift_string(&self) -> String {
82        let negative_indicator = if self.is_negative.is_some() { "N" } else { "" };
83        let rate_str = format!("{:.4}", self.rate.abs()).replace('.', ",");
84        format!(
85            ":37H:{}{}{}",
86            self.rate_indicator, negative_indicator, rate_str
87        )
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_field37h_parse() {
97        // Test positive credit rate
98        let field = Field37H::parse("C2,5000").unwrap();
99        assert_eq!(field.rate_indicator, 'C');
100        assert_eq!(field.is_negative, None);
101        assert_eq!(field.rate, 2.5);
102
103        // Test positive debit rate
104        let field = Field37H::parse("D3,7500").unwrap();
105        assert_eq!(field.rate_indicator, 'D');
106        assert_eq!(field.is_negative, None);
107        assert_eq!(field.rate, 3.75);
108
109        // Test negative credit rate
110        let field = Field37H::parse("CN0,2500").unwrap();
111        assert_eq!(field.rate_indicator, 'C');
112        assert_eq!(field.is_negative, Some(true));
113        assert_eq!(field.rate, -0.25);
114    }
115
116    #[test]
117    fn test_field37h_to_swift_string() {
118        let field = Field37H {
119            rate_indicator: 'C',
120            is_negative: None,
121            rate: 2.5,
122        };
123        assert_eq!(field.to_swift_string(), ":37H:C2,5000");
124
125        let field = Field37H {
126            rate_indicator: 'D',
127            is_negative: None,
128            rate: 3.75,
129        };
130        assert_eq!(field.to_swift_string(), ":37H:D3,7500");
131
132        let field = Field37H {
133            rate_indicator: 'C',
134            is_negative: Some(true),
135            rate: -0.25,
136        };
137        assert_eq!(field.to_swift_string(), ":37H:CN0,2500");
138    }
139
140    #[test]
141    fn test_field37h_parse_invalid() {
142        // Invalid rate indicator
143        assert!(Field37H::parse("X2,5000").is_err());
144
145        // Missing rate
146        assert!(Field37H::parse("C").is_err());
147
148        // Invalid rate format
149        assert!(Field37H::parse("Cabc").is_err());
150
151        // Empty input
152        assert!(Field37H::parse("").is_err());
153    }
154}