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 cluster is busy or temporarily unavailable (HTTP 503).
86    #[error("Cluster is busy or unavailable")]
87    ClusterBusy,
88}
89
90impl From<reqwest::Error> for RestError {
91    fn from(err: reqwest::Error) -> Self {
92        RestError::RequestFailed(err.to_string())
93    }
94}
95
96impl From<serde_json::Error> for RestError {
97    fn from(err: serde_json::Error) -> Self {
98        RestError::SerializationError(err.to_string())
99    }
100}
101
102impl RestError {
103    /// Check if this is a not found error
104    pub fn is_not_found(&self) -> bool {
105        matches!(self, RestError::NotFound)
106            || matches!(self, RestError::ApiError { code, .. } if *code == 404)
107    }
108
109    /// Check if this is an authentication error
110    pub fn is_unauthorized(&self) -> bool {
111        matches!(self, RestError::Unauthorized)
112            || matches!(self, RestError::AuthenticationFailed)
113            || matches!(self, RestError::ApiError { code, .. } if *code == 401)
114    }
115
116    /// Check if this is a server error
117    pub fn is_server_error(&self) -> bool {
118        matches!(self, RestError::ServerError(_))
119            || matches!(self, RestError::ApiError { code, .. } if *code >= 500)
120    }
121
122    /// Check if this is a timeout error
123    pub fn is_timeout(&self) -> bool {
124        matches!(self, RestError::Timeout)
125    }
126
127    /// Check if this is a rate limit error
128    pub fn is_rate_limited(&self) -> bool {
129        matches!(self, RestError::RateLimited { .. })
130            || matches!(self, RestError::ApiError { code, .. } if *code == 429)
131    }
132
133    /// Check if this is a conflict/already exists error
134    pub fn is_conflict(&self) -> bool {
135        matches!(self, RestError::AlreadyExists)
136            || matches!(self, RestError::Conflict(_))
137            || matches!(self, RestError::ApiError { code, .. } if *code == 409)
138    }
139
140    /// Check if this is a cluster busy error
141    pub fn is_cluster_busy(&self) -> bool {
142        matches!(self, RestError::ClusterBusy)
143            || matches!(self, RestError::ApiError { code, .. } if *code == 503)
144    }
145
146    /// Check if this error is retryable
147    pub fn is_retryable(&self) -> bool {
148        self.is_timeout()
149            || self.is_rate_limited()
150            || self.is_cluster_busy()
151            || self.is_server_error()
152    }
153
154    /// Check if this is a bad request / validation error
155    pub fn is_bad_request(&self) -> bool {
156        matches!(self, RestError::ValidationError(_))
157            || matches!(self, RestError::ApiError { code, .. } if *code == 400)
158    }
159}
160
161/// Result alias used throughout the crate for Redis Enterprise REST API operations.
162pub type Result<T> = std::result::Result<T, RestError>;