1use std::{error, fmt, io, sync::Arc};
2
3use arcstr::ArcStr;
4
5use crate::{
6 errors::server_error::{ServerError, ServerErrorKind},
7 ParsingError,
8};
9
10#[derive(PartialEq, Eq, Copy, Clone, Debug)]
12#[non_exhaustive]
13pub enum ErrorKind {
14 Parse,
16 AuthenticationFailed,
18 UnexpectedReturnType,
20 InvalidClientConfig,
23 Io,
27 Client,
29 Extension,
32 MasterNameNotFoundBySentinel,
34 NoValidReplicasFoundBySentinel,
36 EmptySentinelList,
38 ClusterConnectionNotFound,
40 Server(ServerErrorKind),
42
43 #[cfg(feature = "json")]
44 Serialize,
46
47 RESP3NotSupported,
50}
51
52#[derive(Clone)]
57pub struct RedisError {
58 repr: ErrorRepr,
59}
60
61#[cfg(feature = "json")]
62impl From<serde_json::Error> for RedisError {
63 fn from(serde_err: serde_json::Error) -> RedisError {
64 RedisError {
65 repr: ErrorRepr::Internal {
66 kind: ErrorKind::Serialize,
67 err: Arc::new(serde_err),
68 },
69 }
70 }
71}
72
73#[derive(Debug, Clone)]
74enum ErrorRepr {
75 General(ErrorKind, &'static str, Option<ArcStr>),
76 Internal {
77 kind: ErrorKind,
78 err: Arc<dyn error::Error + Send + Sync>,
79 },
80 Parsing(ParsingError),
81 Server(ServerError),
82 Pipeline(Arc<[(usize, ServerError)]>),
83 TransactionAborted(Arc<[(usize, ServerError)]>),
84}
85
86impl PartialEq for RedisError {
87 fn eq(&self, other: &RedisError) -> bool {
88 match (&self.repr, &other.repr) {
89 (&ErrorRepr::General(kind_a, _, _), &ErrorRepr::General(kind_b, _, _)) => {
90 kind_a == kind_b
91 }
92 (ErrorRepr::Parsing(a), ErrorRepr::Parsing(b)) => *a == *b,
93 (ErrorRepr::Server(a), ErrorRepr::Server(b)) => *a == *b,
94 (ErrorRepr::Pipeline(a), ErrorRepr::Pipeline(b)) => *a == *b,
95 _ => false,
96 }
97 }
98}
99
100impl From<io::Error> for RedisError {
101 fn from(err: io::Error) -> RedisError {
102 RedisError {
103 repr: ErrorRepr::Internal {
104 kind: ErrorKind::Io,
105 err: Arc::new(err),
106 },
107 }
108 }
109}
110
111#[cfg(feature = "tls-rustls")]
112impl From<rustls::pki_types::InvalidDnsNameError> for RedisError {
113 fn from(err: rustls::pki_types::InvalidDnsNameError) -> RedisError {
114 RedisError {
115 repr: ErrorRepr::Internal {
116 kind: ErrorKind::Io,
117 err: Arc::new(err),
118 },
119 }
120 }
121}
122
123#[cfg(feature = "tls-rustls")]
124impl From<rustls_native_certs::Error> for RedisError {
125 fn from(err: rustls_native_certs::Error) -> RedisError {
126 RedisError {
127 repr: ErrorRepr::Internal {
128 kind: ErrorKind::Io,
129 err: Arc::new(err),
130 },
131 }
132 }
133}
134
135impl From<(ErrorKind, &'static str)> for RedisError {
136 fn from((kind, desc): (ErrorKind, &'static str)) -> RedisError {
137 RedisError {
138 repr: ErrorRepr::General(kind, desc, None),
139 }
140 }
141}
142
143impl From<(ErrorKind, &'static str, String)> for RedisError {
144 fn from((kind, desc, detail): (ErrorKind, &'static str, String)) -> RedisError {
145 RedisError {
146 repr: ErrorRepr::General(kind, desc, Some(detail.into())),
147 }
148 }
149}
150
151impl error::Error for RedisError {
152 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
153 match &self.repr {
154 ErrorRepr::Internal { err, .. } => Some(err),
155 ErrorRepr::Server(err) => Some(err),
156 ErrorRepr::Parsing(err) => Some(err),
157 _ => None,
158 }
159 }
160}
161
162impl fmt::Debug for RedisError {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
164 fmt::Display::fmt(self, f)
165 }
166}
167
168impl fmt::Display for RedisError {
169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
170 match &self.repr {
171 ErrorRepr::General(kind, desc, detail) => {
172 desc.fmt(f)?;
173 f.write_str(" - ")?;
174 fmt::Debug::fmt(&kind, f)?;
175 if let Some(detail) = detail {
176 f.write_str(": ")?;
177 detail.fmt(f)
178 } else {
179 Ok(())
180 }
181 }
182 ErrorRepr::Internal { err, .. } => err.fmt(f),
183 ErrorRepr::Parsing(err) => err.fmt(f),
184 ErrorRepr::Server(err) => err.fmt(f),
185 ErrorRepr::Pipeline(items) => {
186 if items.len() > 1 {
187 f.write_str("Pipeline failures: [")?;
188 } else {
189 f.write_str("Pipeline failure: [")?;
190 }
191 let mut first = true;
192 for (index, error) in items.iter() {
193 if first {
194 write!(f, "(Index {index}, error: {error})")?;
195 first = false;
196 } else {
197 write!(f, ", (Index {index}, error: {error})")?;
198 }
199 }
200 f.write_str("]")
201 }
202 ErrorRepr::TransactionAborted(items) => {
203 f.write_str("Transaction aborted: [")?;
204
205 let mut first = true;
206 for (index, error) in items.iter() {
207 if first {
208 write!(f, "(Index {index}, error: {error})")?;
209 first = false;
210 } else {
211 write!(f, ", (Index {index}, error: {error})")?;
212 }
213 }
214 f.write_str("]")
215 }
216 }
217 }
218}
219
220#[non_exhaustive]
222pub enum RetryMethod {
223 Reconnect,
225 NoRetry,
227 RetryImmediately,
229 WaitAndRetry,
231 AskRedirect,
233 MovedRedirect,
235 ReconnectFromInitialConnections,
237}
238
239impl RedisError {
241 pub fn kind(&self) -> ErrorKind {
243 match &self.repr {
244 ErrorRepr::General(kind, _, _) => *kind,
245 ErrorRepr::Internal { kind, .. } => *kind,
246 ErrorRepr::Parsing(_) => ErrorKind::Parse,
247 ErrorRepr::Server(err) => match err.kind() {
248 Some(kind) => ErrorKind::Server(kind),
249 None => ErrorKind::Extension,
250 },
251 ErrorRepr::Pipeline(items) => items
252 .first()
253 .and_then(|item| item.1.kind().map(|kind| kind.into()))
254 .unwrap_or(ErrorKind::Extension),
255 ErrorRepr::TransactionAborted(..) => ErrorKind::Server(ServerErrorKind::ExecAbort),
256 }
257 }
258
259 pub fn detail(&self) -> Option<&str> {
261 match &self.repr {
262 ErrorRepr::General(_, _, detail) => detail.as_ref().map(|detail| detail.as_str()),
263 ErrorRepr::Parsing(err) => Some(&err.description),
264 ErrorRepr::Server(err) => err.details(),
265 _ => None,
266 }
267 }
268
269 pub fn code(&self) -> Option<&str> {
271 match self.kind() {
272 ErrorKind::Server(kind) => Some(kind.code()),
273 _ => match &self.repr {
274 ErrorRepr::Server(err) => Some(err.code()),
275 _ => None,
276 },
277 }
278 }
279
280 pub fn category(&self) -> &str {
282 match self.kind() {
283 ErrorKind::Server(ServerErrorKind::ResponseError) => "response error",
284 ErrorKind::AuthenticationFailed => "authentication failed",
285 ErrorKind::UnexpectedReturnType => "type error",
286 ErrorKind::Server(ServerErrorKind::ExecAbort) => "script execution aborted",
287 ErrorKind::Server(ServerErrorKind::BusyLoading) => "busy loading",
288 ErrorKind::Server(ServerErrorKind::NoScript) => "no script",
289 ErrorKind::InvalidClientConfig => "invalid client config",
290 ErrorKind::Server(ServerErrorKind::Moved) => "key moved",
291 ErrorKind::Server(ServerErrorKind::Ask) => "key moved (ask)",
292 ErrorKind::Server(ServerErrorKind::TryAgain) => "try again",
293 ErrorKind::Server(ServerErrorKind::ClusterDown) => "cluster down",
294 ErrorKind::Server(ServerErrorKind::CrossSlot) => "cross-slot",
295 ErrorKind::Server(ServerErrorKind::MasterDown) => "master down",
296 ErrorKind::Io => "I/O error",
297 ErrorKind::Extension => "extension error",
298 ErrorKind::Client => "client error",
299 ErrorKind::Server(ServerErrorKind::ReadOnly) => "read-only",
300 ErrorKind::MasterNameNotFoundBySentinel => "master name not found by sentinel",
301 ErrorKind::NoValidReplicasFoundBySentinel => "no valid replicas found by sentinel",
302 ErrorKind::EmptySentinelList => "empty sentinel list",
303 ErrorKind::Server(ServerErrorKind::NotBusy) => "not busy",
304 ErrorKind::ClusterConnectionNotFound => "connection to node in cluster not found",
305 #[cfg(feature = "json")]
306 ErrorKind::Serialize => "serializing",
307 ErrorKind::RESP3NotSupported => "resp3 is not supported by server",
308 ErrorKind::Parse => "parse error",
309 ErrorKind::Server(ServerErrorKind::NoSub) => {
310 "Server declined unsubscribe related command in non-subscribed mode"
311 }
312 ErrorKind::Server(ServerErrorKind::NoPerm) => "",
313 }
314 }
315
316 pub fn is_io_error(&self) -> bool {
318 self.kind() == ErrorKind::Io
319 }
320
321 pub(crate) fn as_io_error(&self) -> Option<&io::Error> {
322 match &self.repr {
323 ErrorRepr::Internal { err, .. } => err.downcast_ref(),
324 _ => None,
325 }
326 }
327
328 pub fn is_cluster_error(&self) -> bool {
330 matches!(
331 self.kind(),
332 ErrorKind::Server(ServerErrorKind::Moved)
333 | ErrorKind::Server(ServerErrorKind::Ask)
334 | ErrorKind::Server(ServerErrorKind::TryAgain)
335 | ErrorKind::Server(ServerErrorKind::ClusterDown)
336 )
337 }
338
339 pub fn is_connection_refusal(&self) -> bool {
344 self.as_io_error().is_some_and(|err| {
345 #[allow(clippy::match_like_matches_macro)]
346 match err.kind() {
347 io::ErrorKind::ConnectionRefused => true,
348 io::ErrorKind::NotFound => cfg!(unix),
352 _ => false,
353 }
354 })
355 }
356
357 pub fn is_timeout(&self) -> bool {
360 self.as_io_error().is_some_and(|err| {
361 matches!(
362 err.kind(),
363 io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock
364 )
365 })
366 }
367
368 pub fn is_connection_dropped(&self) -> bool {
370 self.as_io_error().is_some_and(|err| {
371 matches!(
372 err.kind(),
373 io::ErrorKind::BrokenPipe
374 | io::ErrorKind::ConnectionReset
375 | io::ErrorKind::UnexpectedEof
376 )
377 })
378 }
379
380 pub fn is_unrecoverable_error(&self) -> bool {
382 match self.retry_method() {
383 RetryMethod::Reconnect => true,
384 RetryMethod::ReconnectFromInitialConnections => true,
385
386 RetryMethod::NoRetry => false,
387 RetryMethod::RetryImmediately => false,
388 RetryMethod::WaitAndRetry => false,
389 RetryMethod::AskRedirect => false,
390 RetryMethod::MovedRedirect => false,
391 }
392 }
393
394 pub fn redirect_node(&self) -> Option<(&str, u16)> {
398 if !matches!(
399 self.kind(),
400 ErrorKind::Server(ServerErrorKind::Ask) | ErrorKind::Server(ServerErrorKind::Moved),
401 ) {
402 return None;
403 }
404 let mut iter = self.detail()?.split_ascii_whitespace();
405 let slot_id: u16 = iter.next()?.parse().ok()?;
406 let addr = iter.next()?;
407 Some((addr, slot_id))
408 }
409
410 pub fn retry_method(&self) -> RetryMethod {
417 match self.kind() {
418 ErrorKind::Server(server_error) => server_error.retry_method(),
419
420 ErrorKind::MasterNameNotFoundBySentinel => RetryMethod::WaitAndRetry,
421 ErrorKind::NoValidReplicasFoundBySentinel => RetryMethod::WaitAndRetry,
422
423 ErrorKind::Extension => RetryMethod::NoRetry,
424 ErrorKind::UnexpectedReturnType => RetryMethod::NoRetry,
425 ErrorKind::InvalidClientConfig => RetryMethod::NoRetry,
426 ErrorKind::Client => RetryMethod::NoRetry,
427 ErrorKind::EmptySentinelList => RetryMethod::NoRetry,
428 #[cfg(feature = "json")]
429 ErrorKind::Serialize => RetryMethod::NoRetry,
430 ErrorKind::RESP3NotSupported => RetryMethod::NoRetry,
431
432 ErrorKind::Parse => RetryMethod::Reconnect,
433 ErrorKind::AuthenticationFailed => RetryMethod::Reconnect,
434 ErrorKind::ClusterConnectionNotFound => RetryMethod::ReconnectFromInitialConnections,
435
436 ErrorKind::Io => self
437 .as_io_error()
438 .map(|err| match err.kind() {
439 io::ErrorKind::ConnectionRefused => RetryMethod::Reconnect,
440 io::ErrorKind::NotFound => RetryMethod::Reconnect,
441 io::ErrorKind::ConnectionReset => RetryMethod::Reconnect,
442 io::ErrorKind::ConnectionAborted => RetryMethod::Reconnect,
443 io::ErrorKind::NotConnected => RetryMethod::Reconnect,
444 io::ErrorKind::BrokenPipe => RetryMethod::Reconnect,
445 io::ErrorKind::UnexpectedEof => RetryMethod::Reconnect,
446
447 io::ErrorKind::PermissionDenied => RetryMethod::NoRetry,
448 io::ErrorKind::Unsupported => RetryMethod::NoRetry,
449
450 _ => RetryMethod::RetryImmediately,
451 })
452 .unwrap_or(RetryMethod::NoRetry),
453 }
454 }
455
456 pub fn into_server_errors(self) -> Option<Arc<[(usize, ServerError)]>> {
461 match self.repr {
462 ErrorRepr::Pipeline(items) => Some(items),
463 ErrorRepr::TransactionAborted(errs) => Some(errs),
464 ErrorRepr::Server(err) => Some(Arc::from([(0, err)])),
465 _ => None,
466 }
467 }
468
469 pub(crate) fn pipeline(errors: Vec<(usize, ServerError)>) -> Self {
470 Self {
471 repr: ErrorRepr::Pipeline(Arc::from(errors)),
472 }
473 }
474
475 pub(crate) fn make_aborted_transaction(errs: Vec<(usize, ServerError)>) -> Self {
476 Self {
477 repr: ErrorRepr::TransactionAborted(Arc::from(errs)),
478 }
479 }
480
481 pub(crate) fn make_empty_command() -> Self {
482 Self {
483 repr: ErrorRepr::General(ErrorKind::Client, "empty command", None),
484 }
485 }
486}
487
488pub fn make_extension_error(code: String, detail: Option<String>) -> RedisError {
502 RedisError {
503 repr: ErrorRepr::Server(ServerError(crate::errors::Repr::Extension {
504 code: code.into(),
505 detail: detail.map(|detail| detail.into()),
506 })),
507 }
508}
509
510#[cfg(feature = "tls-native-tls")]
511impl From<native_tls::Error> for RedisError {
512 fn from(err: native_tls::Error) -> RedisError {
513 RedisError {
514 repr: ErrorRepr::Internal {
515 kind: ErrorKind::Client,
516 err: Arc::new(err),
517 },
518 }
519 }
520}
521
522#[cfg(feature = "tls-rustls")]
523impl From<rustls::Error> for RedisError {
524 fn from(err: rustls::Error) -> RedisError {
525 RedisError {
526 repr: ErrorRepr::Internal {
527 kind: ErrorKind::Client,
528 err: Arc::new(err),
529 },
530 }
531 }
532}
533
534impl From<ServerError> for RedisError {
535 fn from(err: ServerError) -> Self {
536 Self {
537 repr: ErrorRepr::Server(err),
538 }
539 }
540}
541
542impl From<ServerErrorKind> for ErrorKind {
543 fn from(kind: ServerErrorKind) -> Self {
544 ErrorKind::Server(kind)
545 }
546}
547
548impl From<ParsingError> for RedisError {
549 fn from(err: ParsingError) -> Self {
550 RedisError {
551 repr: ErrorRepr::Parsing(err),
552 }
553 }
554}
555
556impl TryFrom<RedisError> for ServerError {
557 type Error = RedisError;
558
559 fn try_from(err: RedisError) -> Result<ServerError, RedisError> {
560 match err.repr {
561 ErrorRepr::Server(err) => Ok(err),
562 _ => Err(err),
563 }
564 }
565}
566
567#[cfg(test)]
568mod tests {
569 use crate::parse_redis_value;
570
571 #[test]
572 fn test_redirect_node() {
573 let err = parse_redis_value(b"-ASK 123 foobar:6380\r\n")
574 .unwrap()
575 .extract_error()
576 .unwrap_err();
577 let node = err.redirect_node();
578
579 assert_eq!(node, Some(("foobar:6380", 123)));
580 }
581}