1use thiserror::Error;
4
5pub type Result<T> = std::result::Result<T, QrzXmlError>;
7
8#[derive(Error, Debug)]
10pub enum QrzXmlError {
11 #[error("Network error: {0}")]
13 Network(#[from] reqwest::Error),
14
15 #[error("XML parsing error: {0}")]
17 XmlParsing(#[from] quick_xml::DeError),
18
19 #[error("URL parsing error: {0}")]
21 UrlParsing(#[from] url::ParseError),
22
23 #[error("QRZ API error: {message}")]
25 ApiError { message: String },
26
27 #[error("Authentication failed: {reason}")]
29 AuthenticationFailed { reason: String },
30
31 #[error("Session expired or invalid - re-authentication required")]
33 SessionExpired,
34
35 #[error("Callsign not found: {callsign}")]
37 CallsignNotFound { callsign: String },
38
39 #[error("DXCC entity not found: {entity}")]
41 DxccNotFound { entity: String },
42
43 #[error("Invalid input: {message}")]
45 InvalidInput { message: String },
46
47 #[error("QRZ service is refusing connections - try again in 24 hours")]
49 ConnectionRefused,
50
51 #[error("A subscription is required to access this data")]
53 SubscriptionRequired,
54
55 #[error("Rate limit exceeded - too many requests")]
57 RateLimitExceeded,
58
59 #[error("No session key received - authentication may have failed")]
61 NoSessionKey,
62
63 #[error("Invalid API version: {version}")]
65 InvalidApiVersion { version: String },
66
67 #[error("Unexpected API response: {message}")]
69 UnexpectedResponse { message: String },
70}
71
72impl QrzXmlError {
73 pub fn api_error(message: impl Into<String>) -> Self {
75 Self::ApiError {
76 message: message.into(),
77 }
78 }
79
80 pub fn auth_failed(reason: impl Into<String>) -> Self {
82 Self::AuthenticationFailed {
83 reason: reason.into(),
84 }
85 }
86
87 pub fn callsign_not_found(callsign: impl Into<String>) -> Self {
89 Self::CallsignNotFound {
90 callsign: callsign.into(),
91 }
92 }
93
94 pub fn dxcc_not_found(entity: impl Into<String>) -> Self {
96 Self::DxccNotFound {
97 entity: entity.into(),
98 }
99 }
100
101 pub fn invalid_input(message: impl Into<String>) -> Self {
103 Self::InvalidInput {
104 message: message.into(),
105 }
106 }
107
108 pub fn unexpected_response(message: impl Into<String>) -> Self {
110 Self::UnexpectedResponse {
111 message: message.into(),
112 }
113 }
114
115 pub fn should_reauthenticate(&self) -> bool {
117 matches!(
118 self,
119 QrzXmlError::SessionExpired | QrzXmlError::NoSessionKey
120 )
121 }
122
123 pub fn is_retryable(&self) -> bool {
125 matches!(
126 self,
127 QrzXmlError::Network(_) | QrzXmlError::SessionExpired | QrzXmlError::RateLimitExceeded
128 )
129 }
130
131 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}