sppd_cli/
errors.rs

1/// Application error types for the SPPD CLI.
2///
3/// Represents all possible errors that can occur during the procurement data download and processing workflow.
4/// Implements `From` traits for common error types, allowing automatic conversion using the `?` operator.
5#[derive(Debug, thiserror::Error)]
6pub enum AppError {
7    /// Network request failed (e.g., HTTP errors, timeouts)
8    #[error("Network error: {0}")]
9    NetworkError(String),
10    /// Failed to parse HTML/XML content
11    #[error("Parse error: {0}")]
12    ParseError(String),
13    /// Invalid URL format
14    #[error("Invalid URL: {0}")]
15    UrlError(String),
16    /// Regex compilation failed
17    #[error("Regex error: {0}")]
18    RegexError(String),
19    /// CSS selector parsing failed
20    #[error("CSS selector error: {0}")]
21    SelectorError(String),
22    /// Period validation failed (requested period not available)
23    #[error("Period '{period}' is not available. Available periods: {available}")]
24    PeriodValidationError { period: String, available: String },
25    /// Invalid input format (e.g., malformed data)
26    #[error("Invalid input: {0}")]
27    InvalidInput(String),
28    /// IO operation failed (e.g., file read/write errors)
29    #[error("IO error: {0}")]
30    IoError(String),
31}
32
33// Conversion implementations for common errors
34impl From<reqwest::Error> for AppError {
35    fn from(err: reqwest::Error) -> Self {
36        AppError::NetworkError(err.to_string())
37    }
38}
39
40impl From<url::ParseError> for AppError {
41    fn from(err: url::ParseError) -> Self {
42        AppError::UrlError(err.to_string())
43    }
44}
45
46impl From<regex::Error> for AppError {
47    fn from(err: regex::Error) -> Self {
48        AppError::RegexError(err.to_string())
49    }
50}
51
52impl From<std::num::ParseIntError> for AppError {
53    fn from(err: std::num::ParseIntError) -> Self {
54        AppError::InvalidInput(err.to_string())
55    }
56}
57
58impl From<std::io::Error> for AppError {
59    fn from(err: std::io::Error) -> Self {
60        AppError::IoError(err.to_string())
61    }
62}
63
64impl From<quick_xml::Error> for AppError {
65    fn from(err: quick_xml::Error) -> Self {
66        AppError::ParseError(format!("XML parsing error: {err}"))
67    }
68}
69
70/// Result type alias for application operations.
71///
72/// Convenience type alias for `Result<T, AppError>` used throughout the application.
73pub type AppResult<T> = Result<T, AppError>;
74
75#[cfg(test)]
76mod tests {
77    use super::AppError;
78
79    #[test]
80    fn test_period_validation_error_display() {
81        let err = AppError::PeriodValidationError {
82            period: "202301".to_string(),
83            available: "202302, 202303".to_string(),
84        };
85
86        let error_msg = err.to_string();
87        assert!(error_msg.contains("202301"));
88        assert!(error_msg.contains("202302"));
89        assert!(error_msg.contains("202303"));
90    }
91
92    #[test]
93    fn test_network_error_display() {
94        let err = AppError::NetworkError("Connection timeout".to_string());
95        assert!(err.to_string().contains("Network error"));
96        assert!(err.to_string().contains("Connection timeout"));
97    }
98
99    #[test]
100    fn test_url_error_display() {
101        let err = AppError::UrlError("Invalid URL format".to_string());
102        assert!(err.to_string().contains("Invalid URL"));
103        assert!(err.to_string().contains("Invalid URL format"));
104    }
105
106    #[test]
107    fn test_regex_error_display() {
108        let err = AppError::RegexError("Invalid regex".to_string());
109        assert!(err.to_string().contains("Regex error"));
110    }
111
112    #[test]
113    fn test_selector_error_display() {
114        let err = AppError::SelectorError("Invalid selector".to_string());
115        assert!(err.to_string().contains("CSS selector error"));
116    }
117
118    #[test]
119    fn test_invalid_input_error_display() {
120        let err = AppError::InvalidInput("Not a number".to_string());
121        assert!(err.to_string().contains("Invalid input"));
122    }
123
124    #[test]
125    fn test_app_error_implements_error_trait() {
126        use std::error::Error;
127        let err: Box<dyn Error> = Box::new(AppError::NetworkError("test".to_string()));
128        assert!(!err.to_string().is_empty());
129    }
130}