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}