1use crate::error::{MTError, Result};
4use crate::messages::MTMessage;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ValidationLevel {
9 Basic,
11 Standard,
13 Strict,
15}
16
17#[derive(Debug, Clone)]
19pub struct ValidationResult {
20 pub errors: Vec<ValidationError>,
21 pub warnings: Vec<ValidationWarning>,
22}
23
24impl ValidationResult {
25 pub fn new() -> Self {
26 Self {
27 errors: Vec::new(),
28 warnings: Vec::new(),
29 }
30 }
31
32 pub fn is_valid(&self) -> bool {
33 self.errors.is_empty()
34 }
35
36 pub fn has_warnings(&self) -> bool {
37 !self.warnings.is_empty()
38 }
39
40 pub fn add_error(&mut self, error: ValidationError) {
41 self.errors.push(error);
42 }
43
44 pub fn add_warning(&mut self, warning: ValidationWarning) {
45 self.warnings.push(warning);
46 }
47
48 pub fn errors(&self) -> &[ValidationError] {
49 &self.errors
50 }
51
52 pub fn warnings(&self) -> &[ValidationWarning] {
53 &self.warnings
54 }
55}
56
57impl Default for ValidationResult {
58 fn default() -> Self {
59 Self::new()
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct ValidationError {
66 pub field: Option<String>,
67 pub message: String,
68 pub error_code: String,
69}
70
71impl ValidationError {
72 pub fn new(field: Option<String>, message: String, error_code: String) -> Self {
73 Self {
74 field,
75 message,
76 error_code,
77 }
78 }
79}
80
81#[derive(Debug, Clone)]
83pub struct ValidationWarning {
84 pub field: Option<String>,
85 pub message: String,
86 pub warning_code: String,
87}
88
89impl ValidationWarning {
90 pub fn new(field: Option<String>, message: String, warning_code: String) -> Self {
91 Self {
92 field,
93 message,
94 warning_code,
95 }
96 }
97}
98
99pub struct MTValidator;
101
102impl MTValidator {
103 pub fn validate(message: &MTMessage, level: ValidationLevel) -> ValidationResult {
105 let mut result = ValidationResult::new();
106
107 match message {
108 MTMessage::MT103(mt103) => {
109 Self::validate_mt103(mt103, level, &mut result);
110 }
111 MTMessage::MT102(mt102) => {
112 Self::validate_mt102(mt102, level, &mut result);
113 }
114 MTMessage::MT202(mt202) => {
115 Self::validate_mt202(mt202, level, &mut result);
116 }
117 MTMessage::MT202COV(mt202cov) => {
118 Self::validate_mt202cov(mt202cov, level, &mut result);
119 }
120 MTMessage::MT210(mt210) => {
121 Self::validate_mt210(mt210, level, &mut result);
122 }
123 MTMessage::MT940(mt940) => {
124 Self::validate_mt940(mt940, level, &mut result);
125 }
126 MTMessage::MT941(mt941) => {
127 Self::validate_mt941(mt941, level, &mut result);
128 }
129 MTMessage::MT942(mt942) => {
130 Self::validate_mt942(mt942, level, &mut result);
131 }
132 MTMessage::MT192(mt192) => {
133 Self::validate_mt192(mt192, level, &mut result);
134 }
135 MTMessage::MT195(mt195) => {
136 Self::validate_mt195(mt195, level, &mut result);
137 }
138 MTMessage::MT196(mt196) => {
139 Self::validate_mt196(mt196, level, &mut result);
140 }
141 MTMessage::MT197(mt197) => {
142 Self::validate_mt197(mt197, level, &mut result);
143 }
144 MTMessage::MT199(mt199) => {
145 Self::validate_mt199(mt199, level, &mut result);
146 }
147 }
148
149 result
150 }
151
152 fn validate_mt103(
153 _mt103: &crate::messages::mt103::MT103,
154 level: ValidationLevel,
155 _result: &mut ValidationResult,
156 ) {
157 match level {
158 ValidationLevel::Basic => {
159 }
162 ValidationLevel::Standard => {
163 }
165 ValidationLevel::Strict => {
166 }
168 }
169 }
170
171 fn validate_mt102(
172 _mt102: &crate::messages::mt102::MT102,
173 _level: ValidationLevel,
174 _result: &mut ValidationResult,
175 ) {
176 }
178
179 fn validate_mt202(
180 _mt202: &crate::messages::mt202::MT202,
181 _level: ValidationLevel,
182 _result: &mut ValidationResult,
183 ) {
184 }
186
187 fn validate_mt202cov(
188 _mt202cov: &crate::messages::mt202cov::MT202COV,
189 _level: ValidationLevel,
190 _result: &mut ValidationResult,
191 ) {
192 }
194
195 fn validate_mt210(
196 _mt210: &crate::messages::mt210::MT210,
197 _level: ValidationLevel,
198 _result: &mut ValidationResult,
199 ) {
200 }
202
203 fn validate_mt940(
204 _mt940: &crate::messages::mt940::MT940,
205 _level: ValidationLevel,
206 _result: &mut ValidationResult,
207 ) {
208 }
210
211 fn validate_mt941(
212 _mt941: &crate::messages::mt941::MT941,
213 _level: ValidationLevel,
214 _result: &mut ValidationResult,
215 ) {
216 }
218
219 fn validate_mt942(
220 _mt942: &crate::messages::mt942::MT942,
221 _level: ValidationLevel,
222 _result: &mut ValidationResult,
223 ) {
224 }
226
227 fn validate_mt192(
228 _mt192: &crate::messages::mt192::MT192,
229 _level: ValidationLevel,
230 _result: &mut ValidationResult,
231 ) {
232 }
234
235 fn validate_mt195(
236 _mt195: &crate::messages::mt195::MT195,
237 _level: ValidationLevel,
238 _result: &mut ValidationResult,
239 ) {
240 }
242
243 fn validate_mt196(
244 _mt196: &crate::messages::mt196::MT196,
245 _level: ValidationLevel,
246 _result: &mut ValidationResult,
247 ) {
248 }
250
251 fn validate_mt197(
252 _mt197: &crate::messages::mt197::MT197,
253 _level: ValidationLevel,
254 _result: &mut ValidationResult,
255 ) {
256 }
258
259 fn validate_mt199(
260 _mt199: &crate::messages::mt199::MT199,
261 _level: ValidationLevel,
262 _result: &mut ValidationResult,
263 ) {
264 }
266}
267
268pub fn validate_message(message: &MTMessage, level: ValidationLevel) -> ValidationResult {
270 MTValidator::validate(message, level)
271}
272
273pub mod field_validators {
275 use super::*;
276 use regex::Regex;
277
278 pub fn validate_bic(bic: &str) -> Result<()> {
280 let bic_regex = Regex::new(r"^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$").unwrap();
281 if bic_regex.is_match(bic) {
282 Ok(())
283 } else {
284 Err(MTError::ValidationError {
285 field: "BIC".to_string(),
286 message: format!("Invalid BIC format: {}", bic),
287 })
288 }
289 }
290
291 pub fn validate_iban(iban: &str) -> Result<()> {
293 let iban_regex =
295 Regex::new(r"^[A-Z]{2}[0-9]{2}[A-Z0-9]{4}[0-9]{7}([A-Z0-9]?){0,16}$").unwrap();
296 if iban_regex.is_match(iban) {
297 Ok(())
298 } else {
299 Err(MTError::ValidationError {
300 field: "IBAN".to_string(),
301 message: format!("Invalid IBAN format: {}", iban),
302 })
303 }
304 }
305
306 pub fn validate_amount(amount: &str) -> Result<()> {
308 let amount_regex = Regex::new(r"^[A-Z]{3}[0-9]+([,.][0-9]{1,2})?$").unwrap();
309 if amount_regex.is_match(amount) {
310 Ok(())
311 } else {
312 Err(MTError::ValidationError {
313 field: "Amount".to_string(),
314 message: format!("Invalid amount format: {}", amount),
315 })
316 }
317 }
318
319 pub fn validate_date_yymmdd(date: &str) -> Result<()> {
321 use crate::common::SwiftDate;
322
323 let date_regex = Regex::new(r"^[0-9]{6}$").unwrap();
324 if date_regex.is_match(date) {
325 SwiftDate::parse_yymmdd(date).map_err(|_| MTError::ValidationError {
327 field: "Date".to_string(),
328 message: format!("Invalid date: {}", date),
329 })?;
330 Ok(())
331 } else {
332 Err(MTError::ValidationError {
333 field: "Date".to_string(),
334 message: format!("Invalid date format, expected YYMMDD: {}", date),
335 })
336 }
337 }
338
339 pub fn validate_reference(reference: &str) -> Result<()> {
341 if reference.len() > 16 {
342 return Err(MTError::ValidationError {
343 field: "Reference".to_string(),
344 message: format!("Reference too long (max 16 chars): {}", reference),
345 });
346 }
347
348 let ref_regex = Regex::new(r"^[A-Z0-9/\-?:().,'+\s]*$").unwrap();
349 if ref_regex.is_match(reference) {
350 Ok(())
351 } else {
352 Err(MTError::ValidationError {
353 field: "Reference".to_string(),
354 message: format!("Invalid reference format: {}", reference),
355 })
356 }
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::field_validators::*;
363 use super::*;
364
365 #[test]
366 fn test_validation_result() {
367 let mut result = ValidationResult::new();
368 assert!(result.is_valid());
369 assert!(!result.has_warnings());
370
371 result.add_error(ValidationError::new(
372 Some("20".to_string()),
373 "Missing field".to_string(),
374 "E001".to_string(),
375 ));
376 assert!(!result.is_valid());
377
378 result.add_warning(ValidationWarning::new(
379 Some("23B".to_string()),
380 "Deprecated field".to_string(),
381 "W001".to_string(),
382 ));
383 assert!(result.has_warnings());
384 }
385
386 #[test]
387 fn test_bic_validation() {
388 assert!(validate_bic("BANKDEFFXXX").is_ok());
389 assert!(validate_bic("BANKDEFF").is_ok());
390 assert!(validate_bic("BANKDE").is_err());
391 assert!(validate_bic("bankdeffxxx").is_err());
392 }
393
394 #[test]
395 fn test_amount_validation() {
396 assert!(validate_amount("EUR1234567,89").is_ok());
397 assert!(validate_amount("USD1000.50").is_ok());
398 assert!(validate_amount("EUR1000").is_ok());
399 assert!(validate_amount("1000.50").is_err());
400 assert!(validate_amount("EUR").is_err());
401 }
402
403 #[test]
404 fn test_date_validation() {
405 assert!(validate_date_yymmdd("210315").is_ok());
406 assert!(validate_date_yymmdd("991231").is_ok());
407 assert!(validate_date_yymmdd("210230").is_err()); assert!(validate_date_yymmdd("211301").is_err()); assert!(validate_date_yymmdd("21031").is_err()); }
411
412 #[test]
413 fn test_reference_validation() {
414 assert!(validate_reference("FT21234567890").is_ok());
415 assert!(validate_reference("REF-123/456").is_ok());
416 assert!(validate_reference("A".repeat(17).as_str()).is_err()); }
418}