1use crate::{SwiftField, ValidationError, ValidationResult};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
50pub struct Field36 {
51 pub rate: f64,
53 pub raw_rate: String,
55}
56
57impl Field36 {
58 pub fn new(rate: f64) -> Result<Self, crate::ParseError> {
60 if rate <= 0.0 {
62 return Err(crate::ParseError::InvalidFieldFormat {
63 field_tag: "36".to_string(),
64 message: "Exchange rate must be positive".to_string(),
65 });
66 }
67
68 let raw_rate = Self::format_rate(rate);
69
70 let normalized_rate = raw_rate.replace(',', ".");
72 if normalized_rate.len() > 12 {
73 return Err(crate::ParseError::InvalidFieldFormat {
74 field_tag: "36".to_string(),
75 message: "Exchange rate too long (max 12 digits)".to_string(),
76 });
77 }
78
79 Ok(Field36 { rate, raw_rate })
80 }
81
82 pub fn from_raw(raw_rate: impl Into<String>) -> Result<Self, crate::ParseError> {
84 let raw_rate = raw_rate.into();
85 let rate = Self::parse_rate(&raw_rate)?;
86
87 Ok(Field36 {
88 rate,
89 raw_rate: raw_rate.to_string(),
90 })
91 }
92
93 pub fn rate(&self) -> f64 {
95 self.rate
96 }
97
98 pub fn raw_rate(&self) -> &str {
100 &self.raw_rate
101 }
102
103 pub fn format_rate(rate: f64) -> String {
105 let formatted = format!("{:.6}", rate);
107 let trimmed = formatted.trim_end_matches('0').trim_end_matches('.');
108 trimmed.replace('.', ",")
109 }
110
111 fn parse_rate(rate_str: &str) -> Result<f64, crate::ParseError> {
113 let normalized_rate = rate_str.replace(',', ".");
114
115 let rate =
116 normalized_rate
117 .parse::<f64>()
118 .map_err(|_| crate::ParseError::InvalidFieldFormat {
119 field_tag: "36".to_string(),
120 message: "Invalid exchange rate format".to_string(),
121 })?;
122
123 if rate <= 0.0 {
124 return Err(crate::ParseError::InvalidFieldFormat {
125 field_tag: "36".to_string(),
126 message: "Exchange rate must be positive".to_string(),
127 });
128 }
129
130 Ok(rate)
131 }
132
133 pub fn is_reasonable_rate(&self) -> bool {
135 self.rate >= 0.0001 && self.rate <= 10000.0
136 }
137
138 pub fn description(&self) -> String {
140 format!("Exchange Rate: {}", self.raw_rate)
141 }
142}
143
144impl SwiftField for Field36 {
145 fn parse(value: &str) -> Result<Self, crate::ParseError> {
146 let content = if let Some(stripped) = value.strip_prefix(":36:") {
147 stripped } else if let Some(stripped) = value.strip_prefix("36:") {
149 stripped } else {
151 value
152 };
153
154 let content = content.trim();
155
156 if content.is_empty() {
157 return Err(crate::ParseError::InvalidFieldFormat {
158 field_tag: "36".to_string(),
159 message: "Exchange rate cannot be empty".to_string(),
160 });
161 }
162
163 if content.len() > 12 {
165 return Err(crate::ParseError::InvalidFieldFormat {
166 field_tag: "36".to_string(),
167 message: "Exchange rate too long (max 12 characters)".to_string(),
168 });
169 }
170
171 if !content
173 .chars()
174 .all(|c| c.is_ascii_digit() || c == ',' || c == '.')
175 {
176 return Err(crate::ParseError::InvalidFieldFormat {
177 field_tag: "36".to_string(),
178 message: "Exchange rate must contain only digits and decimal separator".to_string(),
179 });
180 }
181
182 let rate = Self::parse_rate(content)?;
183
184 Ok(Field36 {
185 rate,
186 raw_rate: content.to_string(),
187 })
188 }
189
190 fn to_swift_string(&self) -> String {
191 format!(":36:{}", self.raw_rate)
192 }
193
194 fn validate(&self) -> ValidationResult {
195 let mut errors = Vec::new();
196 let mut warnings = Vec::new();
197
198 if self.rate <= 0.0 {
200 errors.push(ValidationError::ValueValidation {
201 field_tag: "36".to_string(),
202 message: "Exchange rate must be positive".to_string(),
203 });
204 }
205
206 if self.raw_rate.is_empty() {
208 errors.push(ValidationError::ValueValidation {
209 field_tag: "36".to_string(),
210 message: "Exchange rate cannot be empty".to_string(),
211 });
212 }
213
214 if self.raw_rate.len() > 12 {
216 errors.push(ValidationError::LengthValidation {
217 field_tag: "36".to_string(),
218 expected: "max 12 characters".to_string(),
219 actual: self.raw_rate.len(),
220 });
221 }
222
223 if !self.is_reasonable_rate() {
225 warnings.push(format!(
226 "Exchange rate {} may be unreasonable (typical range: 0.0001 to 10000)",
227 self.rate
228 ));
229 }
230
231 ValidationResult {
232 is_valid: errors.is_empty(),
233 errors,
234 warnings,
235 }
236 }
237
238 fn format_spec() -> &'static str {
239 "12d"
240 }
241}
242
243impl std::fmt::Display for Field36 {
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 write!(f, "{}", self.raw_rate)
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn test_field36_creation() {
255 let field = Field36::new(1.2345).unwrap();
256 assert_eq!(field.rate(), 1.2345);
257 assert_eq!(field.raw_rate(), "1,2345");
258 }
259
260 #[test]
261 fn test_field36_from_raw() {
262 let field = Field36::from_raw("0,8567").unwrap();
263 assert_eq!(field.rate(), 0.8567);
264 assert_eq!(field.raw_rate(), "0,8567");
265 }
266
267 #[test]
268 fn test_field36_parse() {
269 let field = Field36::parse("1,5678").unwrap();
270 assert_eq!(field.rate(), 1.5678);
271 assert_eq!(field.raw_rate(), "1,5678");
272 }
273
274 #[test]
275 fn test_field36_parse_with_prefix() {
276 let field = Field36::parse(":36:2,3456").unwrap();
277 assert_eq!(field.rate(), 2.3456);
278 assert_eq!(field.raw_rate(), "2,3456");
279 }
280
281 #[test]
282 fn test_field36_parse_dot_decimal() {
283 let field = Field36::parse("1.2345").unwrap();
284 assert_eq!(field.rate(), 1.2345);
285 assert_eq!(field.raw_rate(), "1.2345");
286 }
287
288 #[test]
289 fn test_field36_to_swift_string() {
290 let field = Field36::new(0.9876).unwrap();
291 assert_eq!(field.to_swift_string(), ":36:0,9876");
292 }
293
294 #[test]
295 fn test_field36_zero_rate() {
296 let result = Field36::new(0.0);
297 assert!(result.is_err());
298 }
299
300 #[test]
301 fn test_field36_negative_rate() {
302 let result = Field36::new(-1.5);
303 assert!(result.is_err());
304 }
305
306 #[test]
307 fn test_field36_too_long() {
308 let result = Field36::parse("123456789012345"); assert!(result.is_err());
310 }
311
312 #[test]
313 fn test_field36_invalid_characters() {
314 let result = Field36::parse("1.23a45");
315 assert!(result.is_err());
316
317 let result = Field36::parse("1,23-45");
318 assert!(result.is_err());
319 }
320
321 #[test]
322 fn test_field36_empty() {
323 let result = Field36::parse("");
324 assert!(result.is_err());
325 }
326
327 #[test]
328 fn test_field36_validation() {
329 let field = Field36::new(1.5).unwrap();
330 let validation = field.validate();
331 assert!(validation.is_valid);
332 assert!(validation.errors.is_empty());
333 }
334
335 #[test]
336 fn test_field36_unreasonable_rate_warning() {
337 let field = Field36::new(50000.0).unwrap();
338 let validation = field.validate();
339 assert!(validation.is_valid); assert!(!validation.warnings.is_empty());
341 }
342
343 #[test]
344 fn test_field36_is_reasonable_rate() {
345 let field1 = Field36::new(1.5).unwrap();
346 assert!(field1.is_reasonable_rate());
347
348 let field2 = Field36::new(0.00001).unwrap();
349 assert!(!field2.is_reasonable_rate());
350
351 let field3 = Field36::new(50000.0).unwrap();
352 assert!(!field3.is_reasonable_rate());
353 }
354
355 #[test]
356 fn test_field36_display() {
357 let field = Field36::new(1.2345).unwrap();
358 assert_eq!(format!("{}", field), "1,2345");
359 }
360
361 #[test]
362 fn test_field36_description() {
363 let field = Field36::new(0.8765).unwrap();
364 assert_eq!(field.description(), "Exchange Rate: 0,8765");
365 }
366
367 #[test]
368 fn test_field36_format_rate() {
369 assert_eq!(Field36::format_rate(1.0), "1");
370 assert_eq!(Field36::format_rate(1.5), "1,5");
371 assert_eq!(Field36::format_rate(1.123456), "1,123456");
372 assert_eq!(Field36::format_rate(1.100000), "1,1");
373 }
374}