1use std::{error, fmt, io, sync::Arc};
2
3use arcstr::ArcStr;
4
5use crate::{
6 ParsingError,
7 errors::server_error::{ServerError, ServerErrorKind},
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#[derive(Debug, Clone)]
222#[non_exhaustive]
223pub enum RetryMethod {
224 Reconnect,
226 NoRetry,
228 RetryImmediately,
230 WaitAndRetry,
232 AskRedirect,
234 MovedRedirect,
236 ReconnectFromInitialConnections,
238}
239
240impl RedisError {
242 pub fn kind(&self) -> ErrorKind {
244 match &self.repr {
245 ErrorRepr::General(kind, _, _) => *kind,
246 ErrorRepr::Internal { kind, .. } => *kind,
247 ErrorRepr::Parsing(_) => ErrorKind::Parse,
248 ErrorRepr::Server(err) => match err.kind() {
249 Some(kind) => ErrorKind::Server(kind),
250 None => ErrorKind::Extension,
251 },
252 ErrorRepr::Pipeline(items) => items
253 .first()
254 .and_then(|item| item.1.kind().map(|kind| kind.into()))
255 .unwrap_or(ErrorKind::Extension),
256 ErrorRepr::TransactionAborted(..) => ErrorKind::Server(ServerErrorKind::ExecAbort),
257 }
258 }
259
260 pub fn detail(&self) -> Option<&str> {
262 match &self.repr {
263 ErrorRepr::General(_, _, detail) => detail.as_ref().map(|detail| detail.as_str()),
264 ErrorRepr::Parsing(err) => Some(&err.description),
265 ErrorRepr::Server(err) => err.details(),
266 _ => None,
267 }
268 }
269
270 pub fn code(&self) -> Option<&str> {
272 match self.kind() {
273 ErrorKind::Server(kind) => Some(kind.code()),
274 _ => match &self.repr {
275 ErrorRepr::Server(err) => Some(err.code()),
276 _ => None,
277 },
278 }
279 }
280
281 pub fn category(&self) -> &str {
283 match self.kind() {
284 ErrorKind::Server(ServerErrorKind::ResponseError) => "response error",
285 ErrorKind::AuthenticationFailed => "authentication failed",
286 ErrorKind::UnexpectedReturnType => "type error",
287 ErrorKind::Server(ServerErrorKind::ExecAbort) => "script execution aborted",
288 ErrorKind::Server(ServerErrorKind::BusyLoading) => "busy loading",
289 ErrorKind::Server(ServerErrorKind::NoScript) => "no script",
290 ErrorKind::InvalidClientConfig => "invalid client config",
291 ErrorKind::Server(ServerErrorKind::Moved) => "key moved",
292 ErrorKind::Server(ServerErrorKind::Ask) => "key moved (ask)",
293 ErrorKind::Server(ServerErrorKind::TryAgain) => "try again",
294 ErrorKind::Server(ServerErrorKind::ClusterDown) => "cluster down",
295 ErrorKind::Server(ServerErrorKind::CrossSlot) => "cross-slot",
296 ErrorKind::Server(ServerErrorKind::MasterDown) => "master down",
297 ErrorKind::Io => "I/O error",
298 ErrorKind::Extension => "extension error",
299 ErrorKind::Client => "client error",
300 ErrorKind::Server(ServerErrorKind::ReadOnly) => "read-only",
301 ErrorKind::MasterNameNotFoundBySentinel => "master name not found by sentinel",
302 ErrorKind::NoValidReplicasFoundBySentinel => "no valid replicas found by sentinel",
303 ErrorKind::EmptySentinelList => "empty sentinel list",
304 ErrorKind::Server(ServerErrorKind::NotBusy) => "not busy",
305 ErrorKind::ClusterConnectionNotFound => "connection to node in cluster not found",
306 #[cfg(feature = "json")]
307 ErrorKind::Serialize => "serializing",
308 ErrorKind::RESP3NotSupported => "resp3 is not supported by server",
309 ErrorKind::Parse => "parse error",
310 ErrorKind::Server(ServerErrorKind::NoSub) => {
311 "Server declined unsubscribe related command in non-subscribed mode"
312 }
313 ErrorKind::Server(ServerErrorKind::NoPerm) => "",
314 }
315 }
316
317 pub fn is_io_error(&self) -> bool {
319 self.kind() == ErrorKind::Io
320 }
321
322 pub(crate) fn as_io_error(&self) -> Option<&io::Error> {
323 match &self.repr {
324 ErrorRepr::Internal { err, .. } => err.downcast_ref(),
325 _ => None,
326 }
327 }
328
329 pub fn is_cluster_error(&self) -> bool {
331 matches!(
332 self.kind(),
333 ErrorKind::Server(ServerErrorKind::Moved)
334 | ErrorKind::Server(ServerErrorKind::Ask)
335 | ErrorKind::Server(ServerErrorKind::TryAgain)
336 | ErrorKind::Server(ServerErrorKind::ClusterDown)
337 )
338 }
339
340 pub fn is_connection_refusal(&self) -> bool {
345 self.as_io_error().is_some_and(|err| {
346 #[allow(clippy::match_like_matches_macro)]
347 match err.kind() {
348 io::ErrorKind::ConnectionRefused => true,
349 io::ErrorKind::NotFound => cfg!(unix),
353 _ => false,
354 }
355 })
356 }
357
358 pub fn is_timeout(&self) -> bool {
361 self.as_io_error().is_some_and(|err| {
362 matches!(
363 err.kind(),
364 io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock
365 )
366 })
367 }
368
369 pub fn is_connection_dropped(&self) -> bool {
371 match self.repr {
372 ErrorRepr::General(kind, _, _) => kind == ErrorKind::Io,
373 ErrorRepr::Internal { .. } => self.as_io_error().is_some_and(|err| {
374 matches!(
375 err.kind(),
376 io::ErrorKind::BrokenPipe
377 | io::ErrorKind::ConnectionReset
378 | io::ErrorKind::ConnectionRefused
379 | io::ErrorKind::ConnectionAborted
380 | io::ErrorKind::UnexpectedEof
381 | io::ErrorKind::NotConnected
382 | io::ErrorKind::NotFound
383 )
384 }),
385
386 _ => false,
387 }
388 }
389
390 pub fn is_unrecoverable_error(&self) -> bool {
392 let retry_method = self.retry_method();
393 match retry_method {
394 RetryMethod::Reconnect => true,
395 RetryMethod::ReconnectFromInitialConnections => true,
396
397 RetryMethod::NoRetry => false,
398 RetryMethod::RetryImmediately => false,
399 RetryMethod::WaitAndRetry => false,
400 RetryMethod::AskRedirect => false,
401 RetryMethod::MovedRedirect => false,
402 }
403 }
404
405 pub fn redirect_node(&self) -> Option<(&str, u16)> {
409 if !matches!(
410 self.kind(),
411 ErrorKind::Server(ServerErrorKind::Ask) | ErrorKind::Server(ServerErrorKind::Moved),
412 ) {
413 return None;
414 }
415 let mut iter = self.detail()?.split_ascii_whitespace();
416 let slot_id: u16 = iter.next()?.parse().ok()?;
417 let addr = iter.next()?;
418 Some((addr, slot_id))
419 }
420
421 pub fn retry_method(&self) -> RetryMethod {
428 match self.kind() {
429 ErrorKind::Server(server_error) => server_error.retry_method(),
430
431 ErrorKind::MasterNameNotFoundBySentinel => RetryMethod::WaitAndRetry,
432 ErrorKind::NoValidReplicasFoundBySentinel => RetryMethod::WaitAndRetry,
433
434 ErrorKind::Extension => RetryMethod::NoRetry,
435 ErrorKind::UnexpectedReturnType => RetryMethod::NoRetry,
436 ErrorKind::InvalidClientConfig => RetryMethod::NoRetry,
437 ErrorKind::Client => RetryMethod::NoRetry,
438 ErrorKind::EmptySentinelList => RetryMethod::NoRetry,
439 #[cfg(feature = "json")]
440 ErrorKind::Serialize => RetryMethod::NoRetry,
441 ErrorKind::RESP3NotSupported => RetryMethod::NoRetry,
442
443 ErrorKind::Parse => RetryMethod::Reconnect,
444 ErrorKind::AuthenticationFailed => RetryMethod::Reconnect,
445 ErrorKind::ClusterConnectionNotFound => RetryMethod::ReconnectFromInitialConnections,
446
447 ErrorKind::Io => {
448 if self.is_connection_dropped() {
449 RetryMethod::Reconnect
450 } else {
451 self.as_io_error()
452 .map(|err| match err.kind() {
453 io::ErrorKind::PermissionDenied => RetryMethod::NoRetry,
454 io::ErrorKind::Unsupported => RetryMethod::NoRetry,
455
456 _ => RetryMethod::RetryImmediately,
457 })
458 .unwrap_or(RetryMethod::NoRetry)
459 }
460 }
461 }
462 }
463
464 pub fn into_server_errors(self) -> Option<Arc<[(usize, ServerError)]>> {
469 match self.repr {
470 ErrorRepr::Pipeline(items) => Some(items),
471 ErrorRepr::TransactionAborted(errs) => Some(errs),
472 ErrorRepr::Server(err) => Some(Arc::from([(0, err)])),
473 _ => None,
474 }
475 }
476
477 pub(crate) fn pipeline(errors: Vec<(usize, ServerError)>) -> Self {
478 Self {
479 repr: ErrorRepr::Pipeline(Arc::from(errors)),
480 }
481 }
482
483 pub(crate) fn make_aborted_transaction(errs: Vec<(usize, ServerError)>) -> Self {
484 Self {
485 repr: ErrorRepr::TransactionAborted(Arc::from(errs)),
486 }
487 }
488
489 pub(crate) fn make_empty_command() -> Self {
490 Self {
491 repr: ErrorRepr::General(ErrorKind::Client, "empty command", None),
492 }
493 }
494}
495
496pub fn make_extension_error(code: String, detail: Option<String>) -> RedisError {
510 RedisError {
511 repr: ErrorRepr::Server(ServerError(crate::errors::Repr::Extension {
512 code: code.into(),
513 detail: detail.map(|detail| detail.into()),
514 })),
515 }
516}
517
518#[cfg(feature = "tls-native-tls")]
519impl From<native_tls::Error> for RedisError {
520 fn from(err: native_tls::Error) -> RedisError {
521 RedisError {
522 repr: ErrorRepr::Internal {
523 kind: ErrorKind::Client,
524 err: Arc::new(err),
525 },
526 }
527 }
528}
529
530#[cfg(feature = "tls-rustls")]
531impl From<rustls::Error> for RedisError {
532 fn from(err: rustls::Error) -> RedisError {
533 RedisError {
534 repr: ErrorRepr::Internal {
535 kind: ErrorKind::Client,
536 err: Arc::new(err),
537 },
538 }
539 }
540}
541
542impl From<ServerError> for RedisError {
543 fn from(err: ServerError) -> Self {
544 Self {
545 repr: ErrorRepr::Server(err),
546 }
547 }
548}
549
550impl From<ServerErrorKind> for ErrorKind {
551 fn from(kind: ServerErrorKind) -> Self {
552 ErrorKind::Server(kind)
553 }
554}
555
556impl From<ParsingError> for RedisError {
557 fn from(err: ParsingError) -> Self {
558 RedisError {
559 repr: ErrorRepr::Parsing(err),
560 }
561 }
562}
563
564impl TryFrom<RedisError> for ServerError {
565 type Error = RedisError;
566
567 fn try_from(err: RedisError) -> Result<ServerError, RedisError> {
568 match err.repr {
569 ErrorRepr::Server(err) => Ok(err),
570 _ => Err(err),
571 }
572 }
573}
574
575#[cfg(test)]
576mod tests {
577 use crate::parse_redis_value;
578
579 #[test]
580 fn test_redirect_node() {
581 let err = parse_redis_value(b"-ASK 123 foobar:6380\r\n")
582 .unwrap()
583 .extract_error()
584 .unwrap_err();
585 let node = err.redirect_node();
586
587 assert_eq!(node, Some(("foobar:6380", 123)));
588 }
589}