Skip to main content

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 | Self::MasterDown | Self::ClusterDown | Self::BusyLoading => {
66                RetryMethod::WaitAndRetry
67            }
68
69            // A write that lands on a node demoted to replica during failover returns READONLY.
70            // The slot map is stale, so refresh topology and retry against the new master.
71            Self::ReadOnly => RetryMethod::RefreshSlotsAndRetry,
72
73            Self::ResponseError
74            | Self::ExecAbort
75            | Self::NoScript
76            | Self::CrossSlot
77            | Self::NotBusy
78            | Self::NoSub
79            | Self::NoPerm => RetryMethod::NoRetry,
80        }
81    }
82}
83
84/// An error that was returned from the server
85#[derive(PartialEq, Debug, Clone)]
86pub struct ServerError(pub(crate) Repr);
87
88#[derive(PartialEq, Debug, Clone)]
89pub(crate) enum Repr {
90    Extension {
91        code: ArcStr,
92        detail: Option<ArcStr>,
93    },
94    Known {
95        kind: ServerErrorKind,
96        detail: Option<ArcStr>,
97    },
98}
99
100impl ServerError {
101    /// Returns the kind of error. If `None`, try `crate::Self::code` to get the error code.
102    pub fn kind(&self) -> Option<ServerErrorKind> {
103        match &self.0 {
104            Repr::Extension { .. } => None,
105            Repr::Known { kind, .. } => Some(*kind),
106        }
107    }
108
109    /// The error code returned from the server
110    pub fn code(&self) -> &str {
111        match &self.0 {
112            Repr::Extension { code, .. } => code,
113            Repr::Known { kind, .. } => kind.code(),
114        }
115    }
116
117    /// Additional details about the error, if exist
118    pub fn details(&self) -> Option<&str> {
119        match &self.0 {
120            Repr::Extension { detail, .. } => detail.as_ref().map(|str| str.as_str()),
121            Repr::Known { detail, .. } => detail.as_ref().map(|str| str.as_str()),
122        }
123    }
124
125    #[cfg(feature = "cluster-async")]
126    pub(crate) fn requires_action(&self) -> bool {
127        !matches!(
128            self.kind()
129                .map(|kind| kind.retry_method())
130                .unwrap_or(RetryMethod::NoRetry),
131            RetryMethod::NoRetry
132        )
133    }
134}
135
136impl fmt::Display for ServerError {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        match &self.0 {
139            Repr::Extension { code, detail } => {
140                fmt::Debug::fmt(&code, f)?;
141                if let Some(detail) = detail {
142                    f.write_str(": ")?;
143                    detail.fmt(f)?;
144                }
145                Ok(())
146            }
147            Repr::Known { kind, detail } => {
148                fmt::Debug::fmt(&kind, f)?;
149                if let Some(detail) = detail {
150                    f.write_str(": ")?;
151                    detail.fmt(f)?;
152                }
153                Ok(())
154            }
155        }
156    }
157}
158
159impl std::error::Error for ServerError {}