use std::io;
use thiserror::Error;
pub type RedisResult<T> = Result<T, RedisError>;
#[derive(Error, Debug)]
pub enum RedisError {
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Protocol error: {0}")]
Protocol(String),
#[error("Server error: {0}")]
Server(String),
#[error("MOVED redirect: slot {slot} to {host}:{port}")]
Moved {
slot: u16,
host: String,
port: u16,
},
#[error("ASK redirect: slot {slot} to {host}:{port}")]
Ask {
slot: u16,
host: String,
port: u16,
},
#[error("Connection error: {0}")]
Connection(String),
#[error("Operation timed out")]
Timeout,
#[error("Type conversion error: {0}")]
Type(String),
#[error("Invalid configuration: {0}")]
Config(String),
#[error("Cluster error: {0}")]
Cluster(String),
#[error("Sentinel error: {0}")]
Sentinel(String),
#[error("Authentication failed: {0}")]
Auth(String),
#[error("Pool error: {0}")]
Pool(String),
#[error("Maximum retry attempts ({0}) exceeded")]
MaxRetriesExceeded(usize),
#[error("Unexpected response: {0}")]
UnexpectedResponse(String),
}
impl RedisError {
#[must_use]
pub fn parse_redirect(msg: &str) -> Option<Self> {
if let Some(moved_str) = msg.strip_prefix("MOVED ") {
let parts: Vec<&str> = moved_str.split_whitespace().collect();
if parts.len() == 2 {
if let Ok(slot) = parts[0].parse::<u16>() {
if let Some((host, port)) = parts[1].rsplit_once(':') {
if let Ok(port) = port.parse::<u16>() {
return Some(Self::Moved {
slot,
host: host.to_string(),
port,
});
}
}
}
}
}
if let Some(ask_str) = msg.strip_prefix("ASK ") {
let parts: Vec<&str> = ask_str.split_whitespace().collect();
if parts.len() == 2 {
if let Ok(slot) = parts[0].parse::<u16>() {
if let Some((host, port)) = parts[1].rsplit_once(':') {
if let Ok(port) = port.parse::<u16>() {
return Some(Self::Ask {
slot,
host: host.to_string(),
port,
});
}
}
}
}
}
None
}
#[must_use]
pub const fn is_redirect(&self) -> bool {
matches!(self, Self::Moved { .. } | Self::Ask { .. })
}
#[must_use]
pub fn redirect_target(&self) -> Option<(String, u16)> {
match self {
Self::Moved { host, port, .. } | Self::Ask { host, port, .. } => {
Some((host.clone(), *port))
}
_ => None,
}
}
#[must_use]
pub const fn redirect_slot(&self) -> Option<u16> {
match self {
Self::Moved { slot, .. } | Self::Ask { slot, .. } => Some(*slot),
_ => None,
}
}
}