redis_oxide/core/
error.rs

1//! Error types for Redis operations
2
3use std::io;
4use thiserror::Error;
5
6/// Result type for Redis operations
7pub type RedisResult<T> = Result<T, RedisError>;
8
9/// Comprehensive error type for Redis operations
10#[derive(Error, Debug)]
11pub enum RedisError {
12    /// IO error during network operations
13    #[error("IO error: {0}")]
14    Io(#[from] io::Error),
15
16    /// Protocol parsing error
17    #[error("Protocol error: {0}")]
18    Protocol(String),
19
20    /// Server returned an error
21    #[error("Server error: {0}")]
22    Server(String),
23
24    /// MOVED redirect in cluster mode
25    #[error("MOVED redirect: slot {slot} to {host}:{port}")]
26    Moved {
27        /// Slot number that was moved
28        slot: u16,
29        /// Target host
30        host: String,
31        /// Target port
32        port: u16,
33    },
34
35    /// ASK redirect in cluster mode
36    #[error("ASK redirect: slot {slot} to {host}:{port}")]
37    Ask {
38        /// Slot number for temporary redirect
39        slot: u16,
40        /// Target host
41        host: String,
42        /// Target port
43        port: u16,
44    },
45
46    /// Connection error
47    #[error("Connection error: {0}")]
48    Connection(String),
49
50    /// Timeout error
51    #[error("Operation timed out")]
52    Timeout,
53
54    /// Type conversion error
55    #[error("Type conversion error: {0}")]
56    Type(String),
57
58    /// Invalid configuration
59    #[error("Invalid configuration: {0}")]
60    Config(String),
61
62    /// Cluster error
63    #[error("Cluster error: {0}")]
64    Cluster(String),
65
66    /// Authentication error
67    #[error("Authentication failed: {0}")]
68    Auth(String),
69
70    /// Pool error
71    #[error("Pool error: {0}")]
72    Pool(String),
73
74    /// Maximum retry attempts exceeded
75    #[error("Maximum retry attempts ({0}) exceeded")]
76    MaxRetriesExceeded(usize),
77
78    /// Unexpected response from server
79    #[error("Unexpected response: {0}")]
80    UnexpectedResponse(String),
81}
82
83impl RedisError {
84    /// Parse a Redis error message to check for MOVED or ASK redirects
85    #[must_use]
86    pub fn parse_redirect(msg: &str) -> Option<Self> {
87        if let Some(moved_str) = msg.strip_prefix("MOVED ") {
88            let parts: Vec<&str> = moved_str.split_whitespace().collect();
89            if parts.len() == 2 {
90                if let Ok(slot) = parts[0].parse::<u16>() {
91                    if let Some((host, port)) = parts[1].rsplit_once(':') {
92                        if let Ok(port) = port.parse::<u16>() {
93                            return Some(Self::Moved {
94                                slot,
95                                host: host.to_string(),
96                                port,
97                            });
98                        }
99                    }
100                }
101            }
102        }
103
104        if let Some(ask_str) = msg.strip_prefix("ASK ") {
105            let parts: Vec<&str> = ask_str.split_whitespace().collect();
106            if parts.len() == 2 {
107                if let Ok(slot) = parts[0].parse::<u16>() {
108                    if let Some((host, port)) = parts[1].rsplit_once(':') {
109                        if let Ok(port) = port.parse::<u16>() {
110                            return Some(Self::Ask {
111                                slot,
112                                host: host.to_string(),
113                                port,
114                            });
115                        }
116                    }
117                }
118            }
119        }
120
121        None
122    }
123
124    /// Check if this error is a redirect (MOVED or ASK)
125    #[must_use]
126    pub const fn is_redirect(&self) -> bool {
127        matches!(self, Self::Moved { .. } | Self::Ask { .. })
128    }
129
130    /// Get the target address from a redirect error
131    #[must_use]
132    pub fn redirect_target(&self) -> Option<(String, u16)> {
133        match self {
134            Self::Moved { host, port, .. } | Self::Ask { host, port, .. } => {
135                Some((host.clone(), *port))
136            }
137            _ => None,
138        }
139    }
140
141    /// Get the slot number from a redirect error
142    #[must_use]
143    pub const fn redirect_slot(&self) -> Option<u16> {
144        match self {
145            Self::Moved { slot, .. } | Self::Ask { slot, .. } => Some(*slot),
146            _ => None,
147        }
148    }
149}