redis/errors/
server_error.rs

1use arcstr::ArcStr;
2use std::fmt;
3
4use crate::RetryMethod;
5
6/// Kinds of errors returned from the server
7#[derive(PartialEq, Debug, Clone, Copy, Eq)]
8#[non_exhaustive]
9pub enum ServerErrorKind {
10    /// The server generated an invalid response, or returned a general error.
11    ResponseError,
12    /// A script execution was aborted.
13    ExecAbort,
14    /// The server cannot response because it's loading a dump.
15    BusyLoading,
16    /// A script that was requested does not actually exist.
17    NoScript,
18    /// Raised if a key moved to a different node.
19    Moved,
20    /// Raised if a key moved to a different node but we need to ask.
21    Ask,
22    /// Raised if a request needs to be retried.
23    TryAgain,
24    /// Raised if a redis cluster is down.
25    ClusterDown,
26    /// A request spans multiple slots
27    CrossSlot,
28    /// A cluster master is unavailable.
29    MasterDown,
30    /// Attempt to write to a read-only server
31    ReadOnly,
32    /// Attempted to kill a script/function while they werent' executing
33    NotBusy,
34    /// Attempted to unsubscribe on a connection that is not in subscribed mode.
35    NoSub,
36    /// Attempted to use a command without ACL permission.
37    NoPerm,
38}
39
40impl ServerErrorKind {
41    pub(crate) fn code(&self) -> &'static str {
42        match self {
43            Self::ResponseError => "ERR",
44            Self::ExecAbort => "EXECABORT",
45            Self::BusyLoading => "LOADING",
46            Self::NoScript => "NOSCRIPT",
47            Self::Moved => "MOVED",
48            Self::Ask => "ASK",
49            Self::TryAgain => "TRYAGAIN",
50            Self::ClusterDown => "CLUSTERDOWN",
51            Self::CrossSlot => "CROSSSLOT",
52            Self::MasterDown => "MASTERDOWN",
53            Self::ReadOnly => "READONLY",
54            Self::NotBusy => "NOTBUSY",
55            Self::NoSub => "NOSUB",
56            Self::NoPerm => "NOPERM",
57        }
58    }
59
60    pub(crate) fn retry_method(&self) -> RetryMethod {
61        match self {
62            Self::Moved => RetryMethod::MovedRedirect,
63            Self::Ask => RetryMethod::AskRedirect,
64
65            Self::TryAgain => RetryMethod::WaitAndRetry,
66            Self::MasterDown => RetryMethod::WaitAndRetry,
67            Self::ClusterDown => RetryMethod::WaitAndRetry,
68            Self::BusyLoading => RetryMethod::WaitAndRetry,
69
70            Self::ResponseError => RetryMethod::NoRetry,
71            Self::ReadOnly => RetryMethod::NoRetry,
72            Self::ExecAbort => RetryMethod::NoRetry,
73            Self::NoScript => RetryMethod::NoRetry,
74            Self::CrossSlot => RetryMethod::NoRetry,
75            Self::NotBusy => RetryMethod::NoRetry,
76            Self::NoSub => RetryMethod::NoRetry,
77            Self::NoPerm => RetryMethod::NoRetry,
78        }
79    }
80}
81
82/// An error that was returned from the server
83#[derive(PartialEq, Debug, Clone)]
84pub struct ServerError(pub(crate) Repr);
85
86#[derive(PartialEq, Debug, Clone)]
87pub(crate) enum Repr {
88    Extension {
89        code: ArcStr,
90        detail: Option<ArcStr>,
91    },
92    Known {
93        kind: ServerErrorKind,
94        detail: Option<ArcStr>,
95    },
96}
97
98impl ServerError {
99    /// Returns the kind of error. If `None`, try `crate::Self::code` to get the error code.
100    pub fn kind(&self) -> Option<ServerErrorKind> {
101        match &self.0 {
102            Repr::Extension { .. } => None,
103            Repr::Known { kind, .. } => Some(*kind),
104        }
105    }
106
107    /// The error code returned from the server
108    pub fn code(&self) -> &str {
109        match &self.0 {
110            Repr::Extension { code, .. } => code,
111            Repr::Known { kind, .. } => kind.code(),
112        }
113    }
114
115    /// Additional details about the error, if exist
116    pub fn details(&self) -> Option<&str> {
117        match &self.0 {
118            Repr::Extension { detail, .. } => detail.as_ref().map(|str| str.as_str()),
119            Repr::Known { detail, .. } => detail.as_ref().map(|str| str.as_str()),
120        }
121    }
122
123    #[cfg(feature = "cluster-async")]
124    pub(crate) fn requires_action(&self) -> bool {
125        !matches!(
126            self.kind()
127                .map(|kind| kind.retry_method())
128                .unwrap_or(RetryMethod::NoRetry),
129            RetryMethod::NoRetry
130        )
131    }
132}
133
134impl fmt::Display for ServerError {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        match &self.0 {
137            Repr::Extension { code, detail } => {
138                fmt::Debug::fmt(&code, f)?;
139                if let Some(detail) = detail {
140                    f.write_str(": ")?;
141                    detail.fmt(f)?;
142                }
143                Ok(())
144            }
145            Repr::Known { kind, detail } => {
146                fmt::Debug::fmt(&kind, f)?;
147                if let Some(detail) = detail {
148                    f.write_str(": ")?;
149                    detail.fmt(f)?;
150                }
151                Ok(())
152            }
153        }
154    }
155}
156
157impl std::error::Error for ServerError {}