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::MT940(mt940) => {
118                Self::validate_mt940(mt940, level, &mut result);
119            }
120            MTMessage::MT941(mt941) => {
121                Self::validate_mt941(mt941, level, &mut result);
122            }
123            MTMessage::MT942(mt942) => {
124                Self::validate_mt942(mt942, level, &mut result);
125            }
126            MTMessage::MT192(mt192) => {
127                Self::validate_mt192(mt192, level, &mut result);
128            }
129            MTMessage::MT195(mt195) => {
130                Self::validate_mt195(mt195, level, &mut result);
131            }
132            MTMessage::MT196(mt196) => {
133                Self::validate_mt196(mt196, level, &mut result);
134            }
135            MTMessage::MT197(mt197) => {
136                Self::validate_mt197(mt197, level, &mut result);
137            }
138            MTMessage::MT199(mt199) => {
139                Self::validate_mt199(mt199, level, &mut result);
140            }
141        }
142
143        result
144    }
145
146    fn validate_mt103(
147        _mt103: &crate::messages::mt103::MT103,
148        level: ValidationLevel,
149        _result: &mut ValidationResult,
150    ) {
151        match level {
152            ValidationLevel::Basic => {
153                // Basic validation - check required fields exist
154                // This will be implemented when we have the MT103 struct
155            }
156            ValidationLevel::Standard => {
157                // Standard validation - check field formats
158            }
159            ValidationLevel::Strict => {
160                // Strict validation - check business rules
161            }
162        }
163    }
164
165    fn validate_mt102(
166        _mt102: &crate::messages::mt102::MT102,
167        _level: ValidationLevel,
168        _result: &mut ValidationResult,
169    ) {
170        // TODO: Implement MT102 validation
171    }
172
173    fn validate_mt202(
174        _mt202: &crate::messages::mt202::MT202,
175        _level: ValidationLevel,
176        _result: &mut ValidationResult,
177    ) {
178        // TODO: Implement MT202 validation
179    }
180
181    fn validate_mt940(
182        _mt940: &crate::messages::mt940::MT940,
183        _level: ValidationLevel,
184        _result: &mut ValidationResult,
185    ) {
186        // TODO: Implement MT940 validation
187    }
188
189    fn validate_mt941(
190        _mt941: &crate::messages::mt941::MT941,
191        _level: ValidationLevel,
192        _result: &mut ValidationResult,
193    ) {
194        // TODO: Implement MT941 validation
195    }
196
197    fn validate_mt942(
198        _mt942: &crate::messages::mt942::MT942,
199        _level: ValidationLevel,
200        _result: &mut ValidationResult,
201    ) {
202        // TODO: Implement MT942 validation
203    }
204
205    fn validate_mt192(
206        _mt192: &crate::messages::mt192::MT192,
207        _level: ValidationLevel,
208        _result: &mut ValidationResult,
209    ) {
210        // TODO: Implement MT192 validation
211    }
212
213    fn validate_mt195(
214        _mt195: &crate::messages::mt195::MT195,
215        _level: ValidationLevel,
216        _result: &mut ValidationResult,
217    ) {
218        // TODO: Implement MT195 validation
219    }
220
221    fn validate_mt196(
222        _mt196: &crate::messages::mt196::MT196,
223        _level: ValidationLevel,
224        _result: &mut ValidationResult,
225    ) {
226        // TODO: Implement MT196 validation
227    }
228
229    fn validate_mt197(
230        _mt197: &crate::messages::mt197::MT197,
231        _level: ValidationLevel,
232        _result: &mut ValidationResult,
233    ) {
234        // TODO: Implement MT197 validation
235    }
236
237    fn validate_mt199(
238        _mt199: &crate::messages::mt199::MT199,
239        _level: ValidationLevel,
240        _result: &mut ValidationResult,
241    ) {
242        // TODO: Implement MT199 validation
243    }
244}
245
246/// Validate a SWIFT MT message
247pub fn validate_message(message: &MTMessage, level: ValidationLevel) -> ValidationResult {
248    MTValidator::validate(message, level)
249}
250
251/// Common field validation functions
252pub mod field_validators {
253    use super::*;
254    use regex::Regex;
255
256    /// Validate BIC (Bank Identifier Code)
257    pub fn validate_bic(bic: &str) -> Result<()> {
258        let bic_regex = Regex::new(r"^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$").unwrap();
259        if bic_regex.is_match(bic) {
260            Ok(())
261        } else {
262            Err(MTError::ValidationError {
263                field: "BIC".to_string(),
264                message: format!("Invalid BIC format: {}", bic),
265            })
266        }
267    }
268
269    /// Validate IBAN (International Bank Account Number)
270    pub fn validate_iban(iban: &str) -> Result<()> {
271        // Basic IBAN format validation
272        let iban_regex = Regex::new(r"^[A-Z]{2}[0-9]{2}[A-Z0-9]{4}[0-9]{7}([A-Z0-9]?){0,16}$").unwrap();
273        if iban_regex.is_match(iban) {
274            Ok(())
275        } else {
276            Err(MTError::ValidationError {
277                field: "IBAN".to_string(),
278                message: format!("Invalid IBAN format: {}", iban),
279            })
280        }
281    }
282
283    /// Validate amount format
284    pub fn validate_amount(amount: &str) -> Result<()> {
285        let amount_regex = Regex::new(r"^[A-Z]{3}[0-9]+([,.][0-9]{1,2})?$").unwrap();
286        if amount_regex.is_match(amount) {
287            Ok(())
288        } else {
289            Err(MTError::ValidationError {
290                field: "Amount".to_string(),
291                message: format!("Invalid amount format: {}", amount),
292            })
293        }
294    }
295
296    /// Validate date format (YYMMDD)
297    pub fn validate_date_yymmdd(date: &str) -> Result<()> {
298        use crate::common::SwiftDate;
299        
300        let date_regex = Regex::new(r"^[0-9]{6}$").unwrap();
301        if date_regex.is_match(date) {
302            // Use SwiftDate to validate the actual date
303            SwiftDate::parse_yymmdd(date).map_err(|_| MTError::ValidationError {
304                field: "Date".to_string(),
305                message: format!("Invalid date: {}", date),
306            })?;
307            Ok(())
308        } else {
309            Err(MTError::ValidationError {
310                field: "Date".to_string(),
311                message: format!("Invalid date format, expected YYMMDD: {}", date),
312            })
313        }
314    }
315
316    /// Validate reference number format
317    pub fn validate_reference(reference: &str) -> Result<()> {
318        if reference.len() > 16 {
319            return Err(MTError::ValidationError {
320                field: "Reference".to_string(),
321                message: format!("Reference too long (max 16 chars): {}", reference),
322            });
323        }
324
325        let ref_regex = Regex::new(r"^[A-Z0-9/\-?:().,'+\s]*$").unwrap();
326        if ref_regex.is_match(reference) {
327            Ok(())
328        } else {
329            Err(MTError::ValidationError {
330                field: "Reference".to_string(),
331                message: format!("Invalid reference format: {}", reference),
332            })
333        }
334    }
335}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340    use super::field_validators::*;
341
342    #[test]
343    fn test_validation_result() {
344        let mut result = ValidationResult::new();
345        assert!(result.is_valid());
346        assert!(!result.has_warnings());
347
348        result.add_error(ValidationError::new(
349            Some("20".to_string()),
350            "Missing field".to_string(),
351            "E001".to_string(),
352        ));
353        assert!(!result.is_valid());
354
355        result.add_warning(ValidationWarning::new(
356            Some("23B".to_string()),
357            "Deprecated field".to_string(),
358            "W001".to_string(),
359        ));
360        assert!(result.has_warnings());
361    }
362
363    #[test]
364    fn test_bic_validation() {
365        assert!(validate_bic("BANKDEFFXXX").is_ok());
366        assert!(validate_bic("BANKDEFF").is_ok());
367        assert!(validate_bic("BANKDE").is_err());
368        assert!(validate_bic("bankdeffxxx").is_err());
369    }
370
371    #[test]
372    fn test_amount_validation() {
373        assert!(validate_amount("EUR1234567,89").is_ok());
374        assert!(validate_amount("USD1000.50").is_ok());
375        assert!(validate_amount("EUR1000").is_ok());
376        assert!(validate_amount("1000.50").is_err());
377        assert!(validate_amount("EUR").is_err());
378    }
379
380    #[test]
381    fn test_date_validation() {
382        assert!(validate_date_yymmdd("210315").is_ok());
383        assert!(validate_date_yymmdd("991231").is_ok());
384        assert!(validate_date_yymmdd("210230").is_err()); // Invalid date (Feb 30)
385        assert!(validate_date_yymmdd("211301").is_err()); // Invalid month
386        assert!(validate_date_yymmdd("21031").is_err());  // Too short
387    }
388
389    #[test]
390    fn test_reference_validation() {
391        assert!(validate_reference("FT21234567890").is_ok());
392        assert!(validate_reference("REF-123/456").is_ok());
393        assert!(validate_reference("A".repeat(17).as_str()).is_err()); // Too long
394    }
395}