Skip to main content

qrz_xml/
error.rs

1//! Error types for the QRZ client library.
2
3use thiserror::Error;
4
5/// Result type alias for convenience
6pub type Result<T> = std::result::Result<T, QrzXmlError>;
7
8/// Comprehensive error type for all QRZ API operations
9#[derive(Error, Debug)]
10pub enum QrzXmlError {
11    /// Network or HTTP-related errors
12    #[error("Network error: {0}")]
13    Network(#[from] reqwest::Error),
14
15    /// XML parsing errors
16    #[error("XML parsing error: {0}")]
17    XmlParsing(#[from] quick_xml::DeError),
18
19    /// URL parsing errors
20    #[error("URL parsing error: {0}")]
21    UrlParsing(#[from] url::ParseError),
22
23    /// QRZ API returned an error message
24    #[error("QRZ API error: {message}")]
25    ApiError { message: String },
26
27    /// Authentication failed
28    #[error("Authentication failed: {reason}")]
29    AuthenticationFailed { reason: String },
30
31    /// Session expired or invalid
32    #[error("Session expired or invalid - re-authentication required")]
33    SessionExpired,
34
35    /// Callsign not found
36    #[error("Callsign not found: {callsign}")]
37    CallsignNotFound { callsign: String },
38
39    /// DXCC entity not found
40    #[error("DXCC entity not found: {entity}")]
41    DxccNotFound { entity: String },
42
43    /// Invalid input provided
44    #[error("Invalid input: {message}")]
45    InvalidInput { message: String },
46
47    /// QRZ service is refusing connections
48    #[error("QRZ service is refusing connections - try again in 24 hours")]
49    ConnectionRefused,
50
51    /// Subscription required for this operation
52    #[error("A subscription is required to access this data")]
53    SubscriptionRequired,
54
55    /// Rate limit exceeded
56    #[error("Rate limit exceeded - too many requests")]
57    RateLimitExceeded,
58
59    /// No session key present in response
60    #[error("No session key received - authentication may have failed")]
61    NoSessionKey,
62
63    /// Invalid API version specified
64    #[error("Invalid API version: {version}")]
65    InvalidApiVersion { version: String },
66
67    /// Generic API error for unexpected responses
68    #[error("Unexpected API response: {message}")]
69    UnexpectedResponse { message: String },
70}
71
72impl QrzXmlError {
73    /// Create a new API error
74    pub fn api_error(message: impl Into<String>) -> Self {
75        Self::ApiError {
76            message: message.into(),
77        }
78    }
79
80    /// Create a new authentication error
81    pub fn auth_failed(reason: impl Into<String>) -> Self {
82        Self::AuthenticationFailed {
83            reason: reason.into(),
84        }
85    }
86
87    /// Create a new callsign not found error
88    pub fn callsign_not_found(callsign: impl Into<String>) -> Self {
89        Self::CallsignNotFound {
90            callsign: callsign.into(),
91        }
92    }
93
94    /// Create a new DXCC not found error
95    pub fn dxcc_not_found(entity: impl Into<String>) -> Self {
96        Self::DxccNotFound {
97            entity: entity.into(),
98        }
99    }
100
101    /// Create a new invalid input error
102    pub fn invalid_input(message: impl Into<String>) -> Self {
103        Self::InvalidInput {
104            message: message.into(),
105        }
106    }
107
108    /// Create a new unexpected response error
109    pub fn unexpected_response(message: impl Into<String>) -> Self {
110        Self::UnexpectedResponse {
111            message: message.into(),
112        }
113    }
114
115    /// Check if this error indicates we should retry with authentication
116    pub fn should_reauthenticate(&self) -> bool {
117        matches!(
118            self,
119            QrzXmlError::SessionExpired | QrzXmlError::NoSessionKey
120        )
121    }
122
123    /// Check if this error is retryable (temporary)
124    pub fn is_retryable(&self) -> bool {
125        matches!(
126            self,
127            QrzXmlError::Network(_) | QrzXmlError::SessionExpired | QrzXmlError::RateLimitExceeded
128        )
129    }
130
131    /// Check if this error is due to insufficient permissions/subscription
132    pub fn is_permission_error(&self) -> bool {
133        matches!(
134            self,
135            QrzXmlError::SubscriptionRequired | QrzXmlError::ConnectionRefused
136        )
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_error_construction() {
146        let error = QrzXmlError::api_error("test message");
147        assert!(error.to_string().contains("test message"));
148
149        let error = QrzXmlError::callsign_not_found("TEST");
150        assert!(error.to_string().contains("TEST"));
151    }
152
153    #[test]
154    fn test_error_properties() {
155        assert!(QrzXmlError::SessionExpired.should_reauthenticate());
156        assert!(QrzXmlError::RateLimitExceeded.is_retryable());
157        assert!(QrzXmlError::SubscriptionRequired.is_permission_error());
158        assert!(!QrzXmlError::CallsignNotFound {
159            callsign: "TEST".to_string()
160        }
161        .is_retryable());
162    }
163}