use thiserror::Error;
pub type Result<T> = std::result::Result<T, QrzXmlError>;
#[derive(Error, Debug)]
pub enum QrzXmlError {
#[error("Network error: {0}")]
Network(#[from] reqwest::Error),
#[error("XML parsing error: {0}")]
XmlParsing(#[from] quick_xml::DeError),
#[error("URL parsing error: {0}")]
UrlParsing(#[from] url::ParseError),
#[error("QRZ API error: {message}")]
ApiError { message: String },
#[error("Authentication failed: {reason}")]
AuthenticationFailed { reason: String },
#[error("Session expired or invalid - re-authentication required")]
SessionExpired,
#[error("Callsign not found: {callsign}")]
CallsignNotFound { callsign: String },
#[error("DXCC entity not found: {entity}")]
DxccNotFound { entity: String },
#[error("Invalid input: {message}")]
InvalidInput { message: String },
#[error("QRZ service is refusing connections - try again in 24 hours")]
ConnectionRefused,
#[error("A subscription is required to access this data")]
SubscriptionRequired,
#[error("Rate limit exceeded - too many requests")]
RateLimitExceeded,
#[error("No session key received - authentication may have failed")]
NoSessionKey,
#[error("Invalid API version: {version}")]
InvalidApiVersion { version: String },
#[error("Unexpected API response: {message}")]
UnexpectedResponse { message: String },
}
impl QrzXmlError {
pub fn api_error(message: impl Into<String>) -> Self {
Self::ApiError {
message: message.into(),
}
}
pub fn auth_failed(reason: impl Into<String>) -> Self {
Self::AuthenticationFailed {
reason: reason.into(),
}
}
pub fn callsign_not_found(callsign: impl Into<String>) -> Self {
Self::CallsignNotFound {
callsign: callsign.into(),
}
}
pub fn dxcc_not_found(entity: impl Into<String>) -> Self {
Self::DxccNotFound {
entity: entity.into(),
}
}
pub fn invalid_input(message: impl Into<String>) -> Self {
Self::InvalidInput {
message: message.into(),
}
}
pub fn unexpected_response(message: impl Into<String>) -> Self {
Self::UnexpectedResponse {
message: message.into(),
}
}
pub fn should_reauthenticate(&self) -> bool {
matches!(
self,
QrzXmlError::SessionExpired | QrzXmlError::NoSessionKey
)
}
pub fn is_retryable(&self) -> bool {
matches!(
self,
QrzXmlError::Network(_) | QrzXmlError::SessionExpired | QrzXmlError::RateLimitExceeded
)
}
pub fn is_permission_error(&self) -> bool {
matches!(
self,
QrzXmlError::SubscriptionRequired | QrzXmlError::ConnectionRefused
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_construction() {
let error = QrzXmlError::api_error("test message");
assert!(error.to_string().contains("test message"));
let error = QrzXmlError::callsign_not_found("TEST");
assert!(error.to_string().contains("TEST"));
}
#[test]
fn test_error_properties() {
assert!(QrzXmlError::SessionExpired.should_reauthenticate());
assert!(QrzXmlError::RateLimitExceeded.is_retryable());
assert!(QrzXmlError::SubscriptionRequired.is_permission_error());
assert!(!QrzXmlError::CallsignNotFound {
callsign: "TEST".to_string()
}
.is_retryable());
}
}