swift_mt_message/
errors.rs

1//! Error types for SWIFT MT message parsing
2//!
3//! This module provides comprehensive error types with rich diagnostics for better debugging
4//! and error reporting. All errors include context information when available.
5
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8
9/// Result type alias for convenience
10pub type Result<T> = std::result::Result<T, ParseError>;
11
12/// Main error type for SWIFT MT parsing
13#[derive(Debug, Error, Clone, Serialize, Deserialize)]
14pub enum ParseError {
15    #[error("Missing required block: {block} (at line {line}, column {column}): {message}")]
16    MissingRequiredBlock {
17        block: String,
18        message: String,
19        line: usize,
20        column: usize,
21    },
22
23    #[error("Invalid block format (at line {line}, column {column}): {message}")]
24    InvalidBlockFormat {
25        message: String,
26        line: usize,
27        column: usize,
28    },
29
30    #[error("Unknown block number: {block_number} (at line {line}, column {column})")]
31    UnknownBlockNumber {
32        block_number: String,
33        line: usize,
34        column: usize,
35    },
36
37    #[error("No blocks found in message: {message}")]
38    NoBlocksFound { message: String },
39
40    #[error("Field parse error: {tag} - {message}")]
41    FieldParseError { tag: String, message: String },
42
43    #[error("Unknown field: {tag} in block {block}")]
44    UnknownField { tag: String, block: String },
45
46    #[error("Invalid field format: {tag} (expected {expected}, got {actual})")]
47    InvalidFieldFormat {
48        tag: String,
49        expected: String,
50        actual: String,
51    },
52
53    #[error("Missing required field: {tag} for message type {message_type}")]
54    MissingRequiredField { tag: String, message_type: String },
55
56    #[error("Invalid field length: {tag} (max {max_length}, got {actual_length})")]
57    InvalidFieldLength {
58        tag: String,
59        max_length: usize,
60        actual_length: usize,
61    },
62
63    #[error("Invalid currency code: {code}")]
64    InvalidCurrencyCode { code: String },
65
66    #[error("Amount parse error: {message}")]
67    AmountParseError { message: String },
68
69    #[error("Date parse error: {message}")]
70    DateParseError { message: String },
71
72    #[error("Time parse error: {message}")]
73    TimeParseError { message: String },
74
75    #[error("Wrong message type: expected {expected}, got {actual}")]
76    WrongMessageType { expected: String, actual: String },
77
78    #[error("Unsupported message type: {message_type}")]
79    UnsupportedMessageType { message_type: String },
80
81    #[error("Invalid message structure: {message}")]
82    InvalidMessageStructure { message: String },
83
84    #[error("Regex error: {message}")]
85    RegexError { message: String },
86
87    #[error("IO error: {message}")]
88    IoError { message: String },
89
90    #[error("Serialization error: {message}")]
91    SerializationError { message: String },
92
93    #[error("Validation error: {message}")]
94    ValidationError { message: String },
95
96    #[error("Format rule error: {message}")]
97    FormatRuleError { message: String },
98
99    #[error("JSON error: {message}")]
100    JsonError { message: String },
101}
102
103/// Field-specific parse errors
104#[derive(Debug, Error, Clone, Serialize, Deserialize)]
105pub enum FieldParseError {
106    #[error("Invalid field usage: {0}")]
107    InvalidUsage(String),
108
109    #[error("Unknown field option: {option} for field {tag}")]
110    UnknownOption { tag: String, option: String },
111
112    #[error("Invalid field option: {option} for field {field}. Valid options: {valid_options:?}")]
113    InvalidFieldOption {
114        field: String,
115        option: String,
116        valid_options: Vec<String>,
117    },
118
119    #[error("Invalid field length: {field} (max {max_length}, got {actual_length})")]
120    InvalidLength {
121        field: String,
122        max_length: usize,
123        actual_length: usize,
124    },
125
126    #[error("Invalid field format: {field} - {message}")]
127    InvalidFormat { field: String, message: String },
128
129    #[error("Missing required data: {field} - {message}")]
130    MissingData { field: String, message: String },
131
132    #[error("Parse error: {message}")]
133    ParseError { message: String },
134}
135
136/// Validation-specific errors
137#[derive(Debug, Error, Clone, Serialize, Deserialize)]
138pub enum ValidationError {
139    #[error("Validation failed for field {tag}: {message}")]
140    FieldValidationFailed { tag: String, message: String },
141
142    #[error("Format rule validation failed: {rule} - {message}")]
143    FormatRuleValidationFailed { rule: String, message: String },
144
145    #[error("Business rule validation failed: {rule} - {message}")]
146    BusinessRuleValidationFailed { rule: String, message: String },
147
148    #[error("Cross-field validation failed: fields {fields:?} - {message}")]
149    CrossFieldValidationFailed {
150        fields: Vec<String>,
151        message: String,
152    },
153
154    #[error("Message structure validation failed: {message}")]
155    MessageStructureValidationFailed { message: String },
156}
157
158/// Conversion from regex::Error
159impl From<regex::Error> for ParseError {
160    fn from(err: regex::Error) -> Self {
161        ParseError::RegexError {
162            message: err.to_string(),
163        }
164    }
165}
166
167/// Conversion from std::io::Error
168impl From<std::io::Error> for ParseError {
169    fn from(err: std::io::Error) -> Self {
170        ParseError::IoError {
171            message: err.to_string(),
172        }
173    }
174}
175
176/// Conversion from FieldParseError to ParseError
177impl From<FieldParseError> for ParseError {
178    fn from(err: FieldParseError) -> Self {
179        ParseError::FieldParseError {
180            tag: "unknown".to_string(),
181            message: err.to_string(),
182        }
183    }
184}
185
186/// Conversion from ValidationError to ParseError
187impl From<ValidationError> for ParseError {
188    fn from(err: ValidationError) -> Self {
189        ParseError::ValidationError {
190            message: err.to_string(),
191        }
192    }
193}
194
195/// Conversion from serde_json::Error
196impl From<serde_json::Error> for ParseError {
197    fn from(err: serde_json::Error) -> Self {
198        ParseError::JsonError {
199            message: err.to_string(),
200        }
201    }
202}
203
204/// Helper functions for creating common errors
205impl ParseError {
206    pub fn missing_required_field(tag: &str) -> Self {
207        ParseError::MissingRequiredField {
208            tag: tag.to_string(),
209            message_type: "unknown".to_string(),
210        }
211    }
212
213    pub fn missing_required_field_for_type(tag: &str, message_type: &str) -> Self {
214        ParseError::MissingRequiredField {
215            tag: tag.to_string(),
216            message_type: message_type.to_string(),
217        }
218    }
219
220    pub fn invalid_field_format(tag: &str, expected: &str, actual: &str) -> Self {
221        ParseError::InvalidFieldFormat {
222            tag: tag.to_string(),
223            expected: expected.to_string(),
224            actual: actual.to_string(),
225        }
226    }
227
228    pub fn field_parse_error(tag: &str, message: &str) -> Self {
229        ParseError::FieldParseError {
230            tag: tag.to_string(),
231            message: message.to_string(),
232        }
233    }
234}
235
236impl FieldParseError {
237    pub fn invalid_length(field: &str, max_length: usize, actual_length: usize) -> Self {
238        FieldParseError::InvalidLength {
239            field: field.to_string(),
240            max_length,
241            actual_length,
242        }
243    }
244
245    pub fn invalid_format(field: &str, message: &str) -> Self {
246        FieldParseError::InvalidFormat {
247            field: field.to_string(),
248            message: message.to_string(),
249        }
250    }
251
252    pub fn missing_data(field: &str, message: &str) -> Self {
253        FieldParseError::MissingData {
254            field: field.to_string(),
255            message: message.to_string(),
256        }
257    }
258}
259
260impl ValidationError {
261    pub fn field_validation_failed(tag: &str, message: &str) -> Self {
262        ValidationError::FieldValidationFailed {
263            tag: tag.to_string(),
264            message: message.to_string(),
265        }
266    }
267
268    pub fn format_rule_validation_failed(rule: &str, message: &str) -> Self {
269        ValidationError::FormatRuleValidationFailed {
270            rule: rule.to_string(),
271            message: message.to_string(),
272        }
273    }
274
275    pub fn business_rule_validation_failed(rule: &str, message: &str) -> Self {
276        ValidationError::BusinessRuleValidationFailed {
277            rule: rule.to_string(),
278            message: message.to_string(),
279        }
280    }
281}
282
283/// Error result with context for better debugging
284#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct ErrorContext {
286    pub error: ParseError,
287    pub line: Option<usize>,
288    pub column: Option<usize>,
289    pub field_tag: Option<String>,
290    pub message_type: Option<String>,
291    pub raw_content: Option<String>,
292}
293
294impl ErrorContext {
295    pub fn new(error: ParseError) -> Self {
296        Self {
297            error,
298            line: None,
299            column: None,
300            field_tag: None,
301            message_type: None,
302            raw_content: None,
303        }
304    }
305
306    pub fn with_location(mut self, line: usize, column: usize) -> Self {
307        self.line = Some(line);
308        self.column = Some(column);
309        self
310    }
311
312    pub fn with_field(mut self, field_tag: String) -> Self {
313        self.field_tag = Some(field_tag);
314        self
315    }
316
317    pub fn with_message_type(mut self, message_type: String) -> Self {
318        self.message_type = Some(message_type);
319        self
320    }
321
322    pub fn with_raw_content(mut self, raw_content: String) -> Self {
323        self.raw_content = Some(raw_content);
324        self
325    }
326}
327
328/// Aggregate multiple errors for batch processing
329#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct ErrorCollection {
331    pub errors: Vec<ErrorContext>,
332    pub warnings: Vec<String>,
333}
334
335impl ErrorCollection {
336    pub fn new() -> Self {
337        Self {
338            errors: Vec::new(),
339            warnings: Vec::new(),
340        }
341    }
342
343    pub fn add_error(&mut self, error: ErrorContext) {
344        self.errors.push(error);
345    }
346
347    pub fn add_warning(&mut self, warning: String) {
348        self.warnings.push(warning);
349    }
350
351    pub fn has_errors(&self) -> bool {
352        !self.errors.is_empty()
353    }
354
355    pub fn has_warnings(&self) -> bool {
356        !self.warnings.is_empty()
357    }
358
359    pub fn error_count(&self) -> usize {
360        self.errors.len()
361    }
362
363    pub fn warning_count(&self) -> usize {
364        self.warnings.len()
365    }
366}
367
368impl Default for ErrorCollection {
369    fn default() -> Self {
370        Self::new()
371    }
372}
373
374// For backward compatibility with existing error module
375// Re-export common error types with old names
376pub use ParseError as MTError;
377pub type MTResult<T> = Result<T>;
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382
383    #[test]
384    fn test_error_creation() {
385        let error = ParseError::missing_required_field("20");
386        assert!(matches!(error, ParseError::MissingRequiredField { .. }));
387    }
388
389    #[test]
390    fn test_error_context() {
391        let error = ParseError::FieldParseError {
392            tag: "20".to_string(),
393            message: "Invalid format".to_string(),
394        };
395        let context = ErrorContext::new(error)
396            .with_location(5, 10)
397            .with_field("20".to_string())
398            .with_message_type("103".to_string());
399
400        assert_eq!(context.line, Some(5));
401        assert_eq!(context.column, Some(10));
402        assert_eq!(context.field_tag, Some("20".to_string()));
403        assert_eq!(context.message_type, Some("103".to_string()));
404    }
405
406    #[test]
407    fn test_error_collection() {
408        let mut collection = ErrorCollection::new();
409        assert!(!collection.has_errors());
410
411        let error = ErrorContext::new(ParseError::missing_required_field("20"));
412        collection.add_error(error);
413        collection.add_warning("This is a test warning".to_string());
414
415        assert!(collection.has_errors());
416        assert!(collection.has_warnings());
417        assert_eq!(collection.error_count(), 1);
418        assert_eq!(collection.warning_count(), 1);
419    }
420}