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    /// Sentinel error
67    #[error("Sentinel error: {0}")]
68    Sentinel(String),
69
70    /// Authentication error
71    #[error("Authentication failed: {0}")]
72    Auth(String),
73
74    /// Pool error
75    #[error("Pool error: {0}")]
76    Pool(String),
77
78    /// Maximum retry attempts exceeded
79    #[error("Maximum retry attempts ({0}) exceeded")]
80    MaxRetriesExceeded(usize),
81
82    /// Unexpected response from server
83    #[error("Unexpected response: {0}")]
84    UnexpectedResponse(String),
85}
86
87impl RedisError {
88    /// Parse a Redis error message to check for MOVED or ASK redirects
89    #[must_use]
90    pub fn parse_redirect(msg: &str) -> Option<Self> {
91        if let Some(moved_str) = msg.strip_prefix("MOVED ") {
92            let parts: Vec<&str> = moved_str.split_whitespace().collect();
93            if parts.len() == 2 {
94                if let Ok(slot) = parts[0].parse::<u16>() {
95                    if let Some((host, port)) = parts[1].rsplit_once(':') {
96                        if let Ok(port) = port.parse::<u16>() {
97                            return Some(Self::Moved {
98                                slot,
99                                host: host.to_string(),
100                                port,
101                            });
102                        }
103                    }
104                }
105            }
106        }
107
108        if let Some(ask_str) = msg.strip_prefix("ASK ") {
109            let parts: Vec<&str> = ask_str.split_whitespace().collect();
110            if parts.len() == 2 {
111                if let Ok(slot) = parts[0].parse::<u16>() {
112                    if let Some((host, port)) = parts[1].rsplit_once(':') {
113                        if let Ok(port) = port.parse::<u16>() {
114                            return Some(Self::Ask {
115                                slot,
116                                host: host.to_string(),
117                                port,
118                            });
119                        }
120                    }
121                }
122            }
123        }
124
125        None
126    }
127
128    /// Check if this error is a redirect (MOVED or ASK)
129    #[must_use]
130    pub const fn is_redirect(&self) -> bool {
131        matches!(self, Self::Moved { .. } | Self::Ask { .. })
132    }
133
134    /// Get the target address from a redirect error
135    #[must_use]
136    pub fn redirect_target(&self) -> Option<(String, u16)> {
137        match self {
138            Self::Moved { host, port, .. } | Self::Ask { host, port, .. } => {
139                Some((host.clone(), *port))
140            }
141            _ => None,
142        }
143    }
144
145    /// Get the slot number from a redirect error
146    #[must_use]
147    pub const fn redirect_slot(&self) -> Option<u16> {
148        match self {
149            Self::Moved { slot, .. } | Self::Ask { slot, .. } => Some(*slot),
150            _ => None,
151        }
152    }
153}