allscreenshots_sdk/
error.rs

1//! Error types for the Allscreenshots SDK.
2
3use thiserror::Error;
4
5/// Error codes returned by the Allscreenshots API.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum ErrorCode {
8    /// Invalid request parameters
9    ValidationError,
10    /// Authentication failed
11    Unauthorized,
12    /// Resource not found
13    NotFound,
14    /// Rate limit exceeded
15    RateLimitExceeded,
16    /// Internal server error
17    InternalError,
18    /// Job was cancelled
19    Cancelled,
20    /// Request timeout
21    Timeout,
22    /// Network error
23    NetworkError,
24    /// Unknown error code
25    Unknown(String),
26}
27
28impl From<&str> for ErrorCode {
29    fn from(s: &str) -> Self {
30        match s {
31            "VALIDATION_ERROR" => ErrorCode::ValidationError,
32            "UNAUTHORIZED" => ErrorCode::Unauthorized,
33            "NOT_FOUND" => ErrorCode::NotFound,
34            "RATE_LIMIT_EXCEEDED" => ErrorCode::RateLimitExceeded,
35            "INTERNAL_ERROR" => ErrorCode::InternalError,
36            "CANCELLED" => ErrorCode::Cancelled,
37            "TIMEOUT" => ErrorCode::Timeout,
38            "NETWORK_ERROR" => ErrorCode::NetworkError,
39            _ => ErrorCode::Unknown(s.to_string()),
40        }
41    }
42}
43
44impl std::fmt::Display for ErrorCode {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        match self {
47            ErrorCode::ValidationError => write!(f, "VALIDATION_ERROR"),
48            ErrorCode::Unauthorized => write!(f, "UNAUTHORIZED"),
49            ErrorCode::NotFound => write!(f, "NOT_FOUND"),
50            ErrorCode::RateLimitExceeded => write!(f, "RATE_LIMIT_EXCEEDED"),
51            ErrorCode::InternalError => write!(f, "INTERNAL_ERROR"),
52            ErrorCode::Cancelled => write!(f, "CANCELLED"),
53            ErrorCode::Timeout => write!(f, "TIMEOUT"),
54            ErrorCode::NetworkError => write!(f, "NETWORK_ERROR"),
55            ErrorCode::Unknown(s) => write!(f, "{}", s),
56        }
57    }
58}
59
60/// The main error type for the Allscreenshots SDK.
61#[derive(Error, Debug)]
62pub enum AllscreenshotsError {
63    /// API returned an error response
64    #[error("API error ({code}): {message}")]
65    ApiError {
66        /// The error code from the API
67        code: ErrorCode,
68        /// The error message from the API
69        message: String,
70        /// HTTP status code
71        status: u16,
72    },
73
74    /// Validation error for request parameters
75    #[error("Validation error: {0}")]
76    ValidationError(String),
77
78    /// HTTP request failed
79    #[error("HTTP error: {0}")]
80    HttpError(#[from] reqwest::Error),
81
82    /// Failed to parse URL
83    #[error("Invalid URL: {0}")]
84    UrlError(#[from] url::ParseError),
85
86    /// JSON serialization/deserialization error
87    #[error("JSON error: {0}")]
88    JsonError(#[from] serde_json::Error),
89
90    /// Configuration error
91    #[error("Configuration error: {0}")]
92    ConfigError(String),
93
94    /// Environment variable not set
95    #[error("Environment variable '{0}' not set")]
96    EnvVarNotSet(String),
97
98    /// All retries exhausted
99    #[error("All retries exhausted: {0}")]
100    RetriesExhausted(String),
101
102    /// Request timeout
103    #[error("Request timeout")]
104    Timeout,
105}
106
107impl AllscreenshotsError {
108    /// Returns `true` if the error is retryable.
109    pub fn is_retryable(&self) -> bool {
110        match self {
111            AllscreenshotsError::ApiError { code, status, .. } => {
112                matches!(code, ErrorCode::RateLimitExceeded | ErrorCode::InternalError)
113                    || *status >= 500
114            }
115            AllscreenshotsError::HttpError(e) => e.is_timeout() || e.is_connect(),
116            AllscreenshotsError::Timeout => true,
117            _ => false,
118        }
119    }
120
121    /// Creates an API error from response data.
122    pub fn from_api_response(status: u16, code: Option<&str>, message: &str) -> Self {
123        AllscreenshotsError::ApiError {
124            code: code.map(ErrorCode::from).unwrap_or(ErrorCode::Unknown("UNKNOWN".to_string())),
125            message: message.to_string(),
126            status,
127        }
128    }
129}
130
131/// API error response structure for deserialization.
132#[derive(Debug, serde::Deserialize)]
133pub(crate) struct ApiErrorResponse {
134    #[serde(rename = "errorCode")]
135    pub error_code: Option<String>,
136    #[serde(rename = "errorMessage")]
137    pub error_message: Option<String>,
138    pub message: Option<String>,
139    pub error: Option<String>,
140}
141
142impl ApiErrorResponse {
143    /// Get the error message from any available field.
144    pub fn get_message(&self) -> String {
145        self.error_message
146            .as_ref()
147            .or(self.message.as_ref())
148            .or(self.error.as_ref())
149            .cloned()
150            .unwrap_or_else(|| "Unknown error".to_string())
151    }
152}