Skip to main content

redis_enterprise/
error.rs

1//! Error types for REST API operations
2
3use std::time::Duration;
4use thiserror::Error;
5
6/// Errors returned by Redis Enterprise REST API operations.
7#[derive(Error, Debug, Clone)]
8pub enum RestError {
9    /// The provided base URL could not be parsed.
10    #[error("Invalid URL: {0}")]
11    InvalidUrl(String),
12
13    /// The underlying HTTP request failed (network or transport error).
14    #[error("HTTP request failed: {0}")]
15    RequestFailed(String),
16
17    /// Authentication failed (invalid credentials).
18    #[error("Authentication failed")]
19    AuthenticationFailed,
20
21    /// The server returned a structured API error with HTTP status code.
22    #[error("API error: {message} (code: {code})")]
23    ApiError {
24        /// HTTP status code returned by the server.
25        code: u16,
26        /// Server-provided error message.
27        message: String,
28    },
29
30    /// Request or response could not be serialized/deserialized.
31    #[error("Serialization error: {0}")]
32    SerializationError(String),
33
34    /// Response could not be parsed into the expected shape.
35    #[error("Parse error: {0}")]
36    ParseError(String),
37
38    /// Could not establish a connection to the REST API.
39    #[error("Connection error: {0}")]
40    ConnectionError(String),
41
42    /// TLS handshake or certificate validation failed.
43    #[error("TLS certificate error: {0}")]
44    TlsError(String),
45
46    /// Client has not been connected to the REST API yet.
47    #[error("Not connected to REST API")]
48    NotConnected,
49
50    /// Client-side validation of input parameters failed.
51    #[error("Validation error: {0}")]
52    ValidationError(String),
53
54    /// The requested resource was not found (HTTP 404).
55    #[error("Resource not found")]
56    NotFound,
57
58    /// The request was unauthorized (HTTP 401).
59    #[error("Unauthorized")]
60    Unauthorized,
61
62    /// The server returned a 5xx error.
63    #[error("Server error: {0}")]
64    ServerError(String),
65
66    /// The request timed out.
67    #[error("Request timed out")]
68    Timeout,
69
70    /// The client has been rate limited by the server (HTTP 429).
71    #[error("Rate limited{}", .retry_after.map(|d| format!(" (retry after {:?})", d)).unwrap_or_default())]
72    RateLimited {
73        /// Optional retry-after duration suggested by the server.
74        retry_after: Option<Duration>,
75    },
76
77    /// The resource already exists (HTTP 409).
78    #[error("Resource already exists")]
79    AlreadyExists,
80
81    /// A conflict occurred while processing the request (HTTP 409).
82    #[error("Conflict: {0}")]
83    Conflict(String),
84
85    /// The SDK method represents an operation that supported Redis Software
86    /// versions do not register.
87    #[error("Unsupported operation: {0}")]
88    UnsupportedOperation(String),
89
90    /// The cluster is busy or temporarily unavailable (HTTP 503).
91    #[error("Cluster is busy or unavailable")]
92    ClusterBusy,
93}
94
95impl From<reqwest::Error> for RestError {
96    fn from(err: reqwest::Error) -> Self {
97        RestError::RequestFailed(err.to_string())
98    }
99}
100
101impl From<serde_json::Error> for RestError {
102    fn from(err: serde_json::Error) -> Self {
103        RestError::SerializationError(err.to_string())
104    }
105}
106
107impl RestError {
108    /// Check if this is a not found error
109    pub fn is_not_found(&self) -> bool {
110        matches!(self, RestError::NotFound)
111            || matches!(self, RestError::ApiError { code, .. } if *code == 404)
112    }
113
114    /// Check if this is an authentication error
115    pub fn is_unauthorized(&self) -> bool {
116        matches!(self, RestError::Unauthorized)
117            || matches!(self, RestError::AuthenticationFailed)
118            || matches!(self, RestError::ApiError { code, .. } if *code == 401)
119    }
120
121    /// Check if this is a server error
122    pub fn is_server_error(&self) -> bool {
123        matches!(self, RestError::ServerError(_))
124            || matches!(self, RestError::ApiError { code, .. } if *code >= 500)
125    }
126
127    /// Check if this is a timeout error
128    pub fn is_timeout(&self) -> bool {
129        matches!(self, RestError::Timeout)
130    }
131
132    /// Check if this is a rate limit error
133    pub fn is_rate_limited(&self) -> bool {
134        matches!(self, RestError::RateLimited { .. })
135            || matches!(self, RestError::ApiError { code, .. } if *code == 429)
136    }
137
138    /// Check if this is a conflict/already exists error
139    pub fn is_conflict(&self) -> bool {
140        matches!(self, RestError::AlreadyExists)
141            || matches!(self, RestError::Conflict(_))
142            || matches!(self, RestError::ApiError { code, .. } if *code == 409)
143    }
144
145    /// Check if this is a cluster busy error
146    pub fn is_cluster_busy(&self) -> bool {
147        matches!(self, RestError::ClusterBusy)
148            || matches!(self, RestError::ApiError { code, .. } if *code == 503)
149    }
150
151    /// Check if this error is retryable
152    pub fn is_retryable(&self) -> bool {
153        self.is_timeout()
154            || self.is_rate_limited()
155            || self.is_cluster_busy()
156            || self.is_server_error()
157    }
158
159    /// Check if this is a bad request / validation error
160    pub fn is_bad_request(&self) -> bool {
161        matches!(self, RestError::ValidationError(_))
162            || matches!(self, RestError::ApiError { code, .. } if *code == 400)
163    }
164}
165
166/// Result alias used throughout the crate for Redis Enterprise REST API operations.
167pub type Result<T> = std::result::Result<T, RestError>;
168
169/// Return a consistent local error for a retired SDK operation.
170pub(crate) fn unsupported_operation<T>(operation: &str) -> Result<T> {
171    Err(RestError::UnsupportedOperation(operation.to_string()))
172}