1use super::swift_utils::{parse_amount, parse_currency, parse_date_yymmdd, parse_exact_length};
2use crate::errors::ParseError;
3use crate::traits::SwiftField;
4use chrono::NaiveDate;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub struct Field60F {
24 pub debit_credit_mark: String,
26
27 pub value_date: NaiveDate,
29
30 pub currency: String,
32
33 pub amount: f64,
35}
36
37#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41pub struct Field60M {
42 pub debit_credit_mark: String,
44
45 pub value_date: NaiveDate,
47
48 pub currency: String,
50
51 pub amount: f64,
53}
54
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
56pub enum Field60 {
57 F(Field60F),
58 M(Field60M),
59}
60
61impl SwiftField for Field60F {
62 fn parse(input: &str) -> crate::Result<Self>
63 where
64 Self: Sized,
65 {
66 if input.len() < 10 {
68 return Err(ParseError::InvalidFormat {
69 message: "Field 60F must be at least 10 characters long".to_string(),
70 });
71 }
72
73 let debit_credit_mark = parse_exact_length(&input[0..1], 1, "Field 60F debit/credit mark")?;
75 if debit_credit_mark != "D" && debit_credit_mark != "C" {
76 return Err(ParseError::InvalidFormat {
77 message: "Field 60F debit/credit mark must be 'D' or 'C'".to_string(),
78 });
79 }
80
81 let date_str = parse_exact_length(&input[1..7], 6, "Field 60F value date")?;
83 let value_date = parse_date_yymmdd(&date_str)?;
84
85 let currency = parse_exact_length(&input[7..10], 3, "Field 60F currency")?;
87 let currency = parse_currency(¤cy)?;
88
89 let amount_str = &input[10..];
91 let amount = parse_amount(amount_str)?;
92
93 Ok(Field60F {
94 debit_credit_mark,
95 value_date,
96 currency,
97 amount,
98 })
99 }
100
101 fn to_swift_string(&self) -> String {
102 format!(
103 ":60F:{}{}{}{}",
104 self.debit_credit_mark,
105 self.value_date.format("%y%m%d"),
106 self.currency,
107 format!("{:.2}", self.amount).replace('.', ",")
108 )
109 }
110}
111
112impl SwiftField for Field60M {
113 fn parse(input: &str) -> crate::Result<Self>
114 where
115 Self: Sized,
116 {
117 if input.len() < 10 {
119 return Err(ParseError::InvalidFormat {
120 message: "Field 60M must be at least 10 characters long".to_string(),
121 });
122 }
123
124 let debit_credit_mark = parse_exact_length(&input[0..1], 1, "Field 60M debit/credit mark")?;
126 if debit_credit_mark != "D" && debit_credit_mark != "C" {
127 return Err(ParseError::InvalidFormat {
128 message: "Field 60M debit/credit mark must be 'D' or 'C'".to_string(),
129 });
130 }
131
132 let date_str = parse_exact_length(&input[1..7], 6, "Field 60M value date")?;
134 let value_date = parse_date_yymmdd(&date_str)?;
135
136 let currency = parse_exact_length(&input[7..10], 3, "Field 60M currency")?;
138 let currency = parse_currency(¤cy)?;
139
140 let amount_str = &input[10..];
142 let amount = parse_amount(amount_str)?;
143
144 Ok(Field60M {
145 debit_credit_mark,
146 value_date,
147 currency,
148 amount,
149 })
150 }
151
152 fn to_swift_string(&self) -> String {
153 format!(
154 ":60M:{}{}{}{}",
155 self.debit_credit_mark,
156 self.value_date.format("%y%m%d"),
157 self.currency,
158 format!("{:.2}", self.amount).replace('.', ",")
159 )
160 }
161}
162
163impl SwiftField for Field60 {
164 fn parse(_input: &str) -> crate::Result<Self>
165 where
166 Self: Sized,
167 {
168 Err(ParseError::InvalidFormat {
171 message: "Field60 enum should not be parsed directly".to_string(),
172 })
173 }
174
175 fn parse_with_variant(
176 value: &str,
177 variant: Option<&str>,
178 _field_tag: Option<&str>,
179 ) -> crate::Result<Self>
180 where
181 Self: Sized,
182 {
183 match variant {
184 Some("F") => {
185 let field = Field60F::parse(value)?;
186 Ok(Field60::F(field))
187 }
188 Some("M") => {
189 let field = Field60M::parse(value)?;
190 Ok(Field60::M(field))
191 }
192 _ => {
193 Err(ParseError::InvalidFormat {
195 message: "Field60 requires variant F or M".to_string(),
196 })
197 }
198 }
199 }
200
201 fn to_swift_string(&self) -> String {
202 match self {
203 Field60::F(field) => field.to_swift_string(),
204 Field60::M(field) => field.to_swift_string(),
205 }
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212 use chrono::NaiveDate;
213
214 #[test]
215 fn test_field60f_parse_valid() {
216 let field = Field60F::parse("C231225USD1234,56").unwrap();
217 assert_eq!(field.debit_credit_mark, "C");
218 assert_eq!(
219 field.value_date,
220 NaiveDate::from_ymd_opt(2023, 12, 25).unwrap()
221 );
222 assert_eq!(field.currency, "USD");
223 assert_eq!(field.amount, 1234.56);
224 }
225
226 #[test]
227 fn test_field60m_parse_valid() {
228 let field = Field60M::parse("D991231EUR500,00").unwrap();
229 assert_eq!(field.debit_credit_mark, "D");
230 assert_eq!(
231 field.value_date,
232 NaiveDate::from_ymd_opt(1999, 12, 31).unwrap()
233 );
234 assert_eq!(field.currency, "EUR");
235 assert_eq!(field.amount, 500.00);
236 }
237
238 #[test]
239 fn test_field60f_invalid_debit_credit_mark() {
240 assert!(Field60F::parse("X231225USD1234,56").is_err());
241 }
242
243 #[test]
244 fn test_field60f_to_swift_string() {
245 let field = Field60F {
246 debit_credit_mark: "C".to_string(),
247 value_date: NaiveDate::from_ymd_opt(2023, 12, 25).unwrap(),
248 currency: "USD".to_string(),
249 amount: 1234.56,
250 };
251 assert_eq!(field.to_swift_string(), ":60F:C231225USD1234,56");
252 }
253
254 #[test]
255 fn test_field60_enum_to_swift_string() {
256 let field_f = Field60::F(Field60F {
257 debit_credit_mark: "C".to_string(),
258 value_date: NaiveDate::from_ymd_opt(2023, 12, 25).unwrap(),
259 currency: "USD".to_string(),
260 amount: 1234.56,
261 });
262 assert_eq!(field_f.to_swift_string(), ":60F:C231225USD1234,56");
263
264 let field_m = Field60::M(Field60M {
265 debit_credit_mark: "D".to_string(),
266 value_date: NaiveDate::from_ymd_opt(2023, 12, 25).unwrap(),
267 currency: "EUR".to_string(),
268 amount: 500.00,
269 });
270 assert_eq!(field_m.to_swift_string(), ":60M:D231225EUR500,00");
271 }
272}