swift_mt_message/
validation.rs

1//! Message validation logic for SWIFT MT messages
2
3use crate::error::{MTError, Result};
4use crate::messages::MTMessage;
5
6/// Validation levels for MT messages
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ValidationLevel {
9    /// Basic structure validation only
10    Basic,
11    /// Standard validation including field formats
12    Standard,
13    /// Strict validation including business rules
14    Strict,
15}
16
17/// Validation result containing errors and warnings
18#[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/// Validation error
64#[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/// Validation warning
82#[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
99/// Main validator for MT messages
100pub struct MTValidator;
101
102impl MTValidator {
103    /// Validate an MT message with the specified validation level
104    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                // Basic validation - check required fields exist
160                // This will be implemented when we have the MT103 struct
161            }
162            ValidationLevel::Standard => {
163                // Standard validation - check field formats
164            }
165            ValidationLevel::Strict => {
166                // Strict validation - check business rules
167            }
168        }
169    }
170
171    fn validate_mt102(
172        _mt102: &crate::messages::mt102::MT102,
173        _level: ValidationLevel,
174        _result: &mut ValidationResult,
175    ) {
176        // TODO: Implement MT102 validation
177    }
178
179    fn validate_mt202(
180        _mt202: &crate::messages::mt202::MT202,
181        _level: ValidationLevel,
182        _result: &mut ValidationResult,
183    ) {
184        // TODO: Implement MT202 validation
185    }
186
187    fn validate_mt202cov(
188        _mt202cov: &crate::messages::mt202cov::MT202COV,
189        _level: ValidationLevel,
190        _result: &mut ValidationResult,
191    ) {
192        // TODO: Implement MT202COV validation
193    }
194
195    fn validate_mt210(
196        _mt210: &crate::messages::mt210::MT210,
197        _level: ValidationLevel,
198        _result: &mut ValidationResult,
199    ) {
200        // TODO: Implement MT210 validation
201    }
202
203    fn validate_mt940(
204        _mt940: &crate::messages::mt940::MT940,
205        _level: ValidationLevel,
206        _result: &mut ValidationResult,
207    ) {
208        // TODO: Implement MT940 validation
209    }
210
211    fn validate_mt941(
212        _mt941: &crate::messages::mt941::MT941,
213        _level: ValidationLevel,
214        _result: &mut ValidationResult,
215    ) {
216        // TODO: Implement MT941 validation
217    }
218
219    fn validate_mt942(
220        _mt942: &crate::messages::mt942::MT942,
221        _level: ValidationLevel,
222        _result: &mut ValidationResult,
223    ) {
224        // TODO: Implement MT942 validation
225    }
226
227    fn validate_mt192(
228        _mt192: &crate::messages::mt192::MT192,
229        _level: ValidationLevel,
230        _result: &mut ValidationResult,
231    ) {
232        // TODO: Implement MT192 validation
233    }
234
235    fn validate_mt195(
236        _mt195: &crate::messages::mt195::MT195,
237        _level: ValidationLevel,
238        _result: &mut ValidationResult,
239    ) {
240        // TODO: Implement MT195 validation
241    }
242
243    fn validate_mt196(
244        _mt196: &crate::messages::mt196::MT196,
245        _level: ValidationLevel,
246        _result: &mut ValidationResult,
247    ) {
248        // TODO: Implement MT196 validation
249    }
250
251    fn validate_mt197(
252        _mt197: &crate::messages::mt197::MT197,
253        _level: ValidationLevel,
254        _result: &mut ValidationResult,
255    ) {
256        // TODO: Implement MT197 validation
257    }
258
259    fn validate_mt199(
260        _mt199: &crate::messages::mt199::MT199,
261        _level: ValidationLevel,
262        _result: &mut ValidationResult,
263    ) {
264        // TODO: Implement MT199 validation
265    }
266}
267
268/// Validate a SWIFT MT message
269pub fn validate_message(message: &MTMessage, level: ValidationLevel) -> ValidationResult {
270    MTValidator::validate(message, level)
271}
272
273/// Common field validation functions
274pub mod field_validators {
275    use super::*;
276    use regex::Regex;
277
278    /// Validate BIC (Bank Identifier Code)
279    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    /// Validate IBAN (International Bank Account Number)
292    pub fn validate_iban(iban: &str) -> Result<()> {
293        // Basic IBAN format validation
294        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    /// Validate amount format
307    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    /// Validate date format (YYMMDD)
320    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            // Use SwiftDate to validate the actual date
326            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    /// Validate reference number format
340    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()); // Invalid date (Feb 30)
408        assert!(validate_date_yymmdd("211301").is_err()); // Invalid month
409        assert!(validate_date_yymmdd("21031").is_err()); // Too short
410    }
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()); // Too long
417    }
418}