1use super::swift_utils::{
2    format_swift_amount_for_currency, parse_amount_with_currency, parse_currency_non_commodity,
3    parse_exact_length, parse_uppercase,
4};
5use crate::errors::ParseError;
6use crate::traits::SwiftField;
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21pub struct Field71A {
22    pub code: String,
24}
25
26impl SwiftField for Field71A {
27    fn parse(input: &str) -> crate::Result<Self>
28    where
29        Self: Sized,
30    {
31        let code = parse_exact_length(input, 3, "Field 71A code")?;
33
34        parse_uppercase(&code, "Field 71A code")?;
36
37        const VALID_CODES: &[&str] = &["BEN", "OUR", "SHA"];
39        if !VALID_CODES.contains(&code.as_str()) {
40            return Err(ParseError::InvalidFormat {
41                message: format!(
42                    "Field 71A code must be one of {:?}, found {}",
43                    VALID_CODES, code
44                ),
45            });
46        }
47
48        Ok(Field71A { code })
49    }
50
51    fn to_swift_string(&self) -> String {
52        format!(":71A:{}", self.code)
53    }
54}
55
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67pub struct Field71F {
68    pub currency: String,
70    pub amount: f64,
72}
73
74impl SwiftField for Field71F {
75    fn parse(input: &str) -> crate::Result<Self>
76    where
77        Self: Sized,
78    {
79        if input.len() < 4 {
80            return Err(ParseError::InvalidFormat {
81                message: format!(
82                    "Field 71F must be at least 4 characters, found {}",
83                    input.len()
84                ),
85            });
86        }
87
88        let currency = parse_currency_non_commodity(&input[0..3])?;
90
91        let amount_str = &input[3..];
93        if amount_str.is_empty() {
94            return Err(ParseError::InvalidFormat {
95                message: "Field 71F amount is required".to_string(),
96            });
97        }
98
99        let amount = parse_amount_with_currency(amount_str, ¤cy)?;
100
101        Ok(Field71F { currency, amount })
102    }
103
104    fn to_swift_string(&self) -> String {
105        format!(
106            ":71F:{}{}",
107            self.currency,
108            format_swift_amount_for_currency(self.amount, &self.currency)
109        )
110    }
111}
112
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
124pub struct Field71G {
125    pub currency: String,
127    pub amount: f64,
129}
130
131impl SwiftField for Field71G {
132    fn parse(input: &str) -> crate::Result<Self>
133    where
134        Self: Sized,
135    {
136        if input.len() < 4 {
137            return Err(ParseError::InvalidFormat {
138                message: format!(
139                    "Field 71G must be at least 4 characters, found {}",
140                    input.len()
141                ),
142            });
143        }
144
145        let currency = parse_currency_non_commodity(&input[0..3])?;
147
148        let amount_str = &input[3..];
150        if amount_str.is_empty() {
151            return Err(ParseError::InvalidFormat {
152                message: "Field 71G amount is required".to_string(),
153            });
154        }
155
156        let amount = parse_amount_with_currency(amount_str, ¤cy)?;
157
158        Ok(Field71G { currency, amount })
159    }
160
161    fn to_swift_string(&self) -> String {
162        format!(
163            ":71G:{}{}",
164            self.currency,
165            format_swift_amount_for_currency(self.amount, &self.currency)
166        )
167    }
168}
169
170#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
183pub struct Field71B {
184    pub details: Vec<String>,
186}
187
188impl SwiftField for Field71B {
189    fn parse(input: &str) -> crate::Result<Self>
190    where
191        Self: Sized,
192    {
193        use super::field_utils::parse_multiline_text;
194
195        let details = parse_multiline_text(input, 6, 35)?;
197
198        if details.is_empty() {
199            return Err(ParseError::InvalidFormat {
200                message: "Field 71B must have at least one line of details".to_string(),
201            });
202        }
203
204        Ok(Field71B { details })
205    }
206
207    fn to_swift_string(&self) -> String {
208        format!(":71B:{}", self.details.join("\n"))
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    #[test]
217    fn test_field71a() {
218        let field = Field71A::parse("BEN").unwrap();
220        assert_eq!(field.code, "BEN");
221
222        let field = Field71A::parse("OUR").unwrap();
223        assert_eq!(field.code, "OUR");
224
225        let field = Field71A::parse("SHA").unwrap();
226        assert_eq!(field.code, "SHA");
227
228        assert!(Field71A::parse("BE").is_err());
230        assert!(Field71A::parse("BENE").is_err());
231
232        assert!(Field71A::parse("XXX").is_err());
234
235        assert!(Field71A::parse("ben").is_err());
237
238        let field = Field71A {
240            code: "SHA".to_string(),
241        };
242        assert_eq!(field.to_swift_string(), ":71A:SHA");
243    }
244
245    #[test]
246    fn test_field71f() {
247        let field = Field71F::parse("USD100.50").unwrap();
249        assert_eq!(field.currency, "USD");
250        assert_eq!(field.amount, 100.50);
251
252        let field = Field71F::parse("EUR1234,56").unwrap();
254        assert_eq!(field.currency, "EUR");
255        assert_eq!(field.amount, 1234.56);
256
257        let field = Field71F::parse("GBP500").unwrap();
259        assert_eq!(field.currency, "GBP");
260        assert_eq!(field.amount, 500.0);
261
262        let field = Field71F {
264            currency: "USD".to_string(),
265            amount: 250.75,
266        };
267        assert_eq!(field.to_swift_string(), ":71F:USD250,75");
268
269        assert!(Field71F::parse("US100").is_err());
271        assert!(Field71F::parse("123100").is_err());
272
273        assert!(Field71F::parse("USD").is_err());
275    }
276
277    #[test]
278    fn test_field71b() {
279        let field = Field71B::parse("COMMISSION USD 25.00").unwrap();
281        assert_eq!(field.details.len(), 1);
282        assert_eq!(field.details[0], "COMMISSION USD 25.00");
283
284        let input = "COMMISSION USD 25.00\nINTEREST USD 10.50\nSERVICE FEE USD 5.00";
286        let field = Field71B::parse(input).unwrap();
287        assert_eq!(field.details.len(), 3);
288        assert_eq!(field.details[0], "COMMISSION USD 25.00");
289        assert_eq!(field.details[1], "INTEREST USD 10.50");
290        assert_eq!(field.details[2], "SERVICE FEE USD 5.00");
291
292        assert_eq!(field.to_swift_string(), format!(":71B:{}", input));
294
295        }
297
298    #[test]
299    fn test_field71g() {
300        let field = Field71G::parse("CHF75.25").unwrap();
302        assert_eq!(field.currency, "CHF");
303        assert_eq!(field.amount, 75.25);
304
305        let field = Field71G::parse("JPY1000000").unwrap();
307        assert_eq!(field.currency, "JPY");
308        assert_eq!(field.amount, 1000000.0);
309
310        let field = Field71G {
312            currency: "CAD".to_string(),
313            amount: 99.99,
314        };
315        assert_eq!(field.to_swift_string(), ":71G:CAD99,99");
316
317        }
319}