use redis::{ErrorKind, RedisError};
use thiserror::Error;
pub type Result<T> = std::result::Result<T, GlideError>;
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum GlideError {
#[error("ClosingError: {0}")]
Closing(String),
#[error("ConfigurationError: {0}")]
Configuration(String),
#[error("ConnectionError: {0}")]
Connection(String),
#[error("ExecAbortError: {0}")]
ExecAbort(String),
#[error("RequestError: {0}")]
Request(String),
#[error("TimeoutError: {0}")]
Timeout(String),
#[error("CircuitBreakerError: {0}")]
CircuitBreaker(String),
}
impl GlideError {
pub fn class_name(&self) -> &'static str {
match self {
GlideError::Closing(_) => "ClosingError",
GlideError::Configuration(_) => "ConfigurationError",
GlideError::Connection(_) => "ConnectionError",
GlideError::ExecAbort(_) => "ExecAbortError",
GlideError::Request(_) => "RequestError",
GlideError::Timeout(_) => "TimeoutError",
GlideError::CircuitBreaker(_) => "CircuitBreakerError",
}
}
pub fn message(&self) -> &str {
match self {
GlideError::Closing(m)
| GlideError::Configuration(m)
| GlideError::Connection(m)
| GlideError::ExecAbort(m)
| GlideError::Request(m)
| GlideError::Timeout(m)
| GlideError::CircuitBreaker(m) => m,
}
}
}
impl From<RedisError> for GlideError {
fn from(err: RedisError) -> Self {
let msg = err.to_string();
match err.kind() {
ErrorKind::IoError | ErrorKind::ClientError => {
if err.is_timeout() {
GlideError::Timeout(msg)
} else {
GlideError::Connection(msg)
}
}
ErrorKind::ExecAbortError => GlideError::ExecAbort(msg),
ErrorKind::CircuitBreakerOpen => GlideError::CircuitBreaker(msg),
ErrorKind::InvalidClientConfig => GlideError::Configuration(msg),
_ => {
if err.is_timeout() {
GlideError::Timeout(msg)
} else {
GlideError::Request(msg)
}
}
}
}
}
impl From<glide_core::client::ConnectionError> for GlideError {
fn from(err: glide_core::client::ConnectionError) -> Self {
use glide_core::client::ConnectionError as CE;
match err {
CE::Timeout => GlideError::Timeout("connection attempt timed out".to_string()),
CE::Configuration(msg) => GlideError::Configuration(msg),
other => GlideError::Connection(other.to_string()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use glide_core::client::ConnectionError as CE;
#[test]
fn redis_exec_abort_maps_to_exec_abort() {
let err = RedisError::from((ErrorKind::ExecAbortError, "aborted"));
let g = GlideError::from(err);
assert!(matches!(g, GlideError::ExecAbort(_)));
assert_eq!(g.class_name(), "ExecAbortError");
}
#[test]
fn redis_circuit_breaker_maps_to_circuit_breaker() {
let err = RedisError::from((ErrorKind::CircuitBreakerOpen, "open"));
let g = GlideError::from(err);
assert!(matches!(g, GlideError::CircuitBreaker(_)));
assert_eq!(g.class_name(), "CircuitBreakerError");
}
#[test]
fn redis_invalid_client_config_maps_to_configuration() {
let err = RedisError::from((ErrorKind::InvalidClientConfig, "bad config"));
let g = GlideError::from(err);
assert!(matches!(g, GlideError::Configuration(_)));
assert_eq!(g.class_name(), "ConfigurationError");
}
#[test]
fn redis_response_error_maps_to_request() {
let err = RedisError::from((ErrorKind::ResponseError, "wrong type"));
let g = GlideError::from(err);
assert!(matches!(g, GlideError::Request(_)));
assert_eq!(g.class_name(), "RequestError");
}
#[test]
fn redis_unknown_kind_maps_to_request() {
let err = RedisError::from((ErrorKind::TypeError, "type mismatch"));
let g = GlideError::from(err);
assert!(matches!(g, GlideError::Request(_)));
}
#[test]
fn redis_io_error_maps_to_connection() {
let err = RedisError::from((ErrorKind::IoError, "socket error"));
let g = GlideError::from(err);
assert!(matches!(g, GlideError::Connection(_)));
assert_eq!(g.class_name(), "ConnectionError");
}
#[test]
fn redis_real_io_timeout_maps_to_timeout() {
let io_err = std::io::Error::new(std::io::ErrorKind::TimedOut, "slow");
let err: RedisError = io_err.into();
assert!(err.is_timeout());
assert!(matches!(GlideError::from(err), GlideError::Timeout(_)));
}
#[test]
fn redis_error_preserves_message() {
let err = RedisError::from((ErrorKind::ResponseError, "custom detail"));
let g = GlideError::from(err);
assert!(g.message().contains("custom detail"));
}
#[test]
fn connection_timeout_maps_to_timeout() {
let g = GlideError::from(CE::Timeout);
assert!(matches!(g, GlideError::Timeout(_)));
assert_eq!(g.class_name(), "TimeoutError");
}
#[test]
fn connection_configuration_maps_to_configuration() {
let g = GlideError::from(CE::Configuration("nope".to_string()));
assert!(matches!(g, GlideError::Configuration(_)));
assert_eq!(g.message(), "nope");
}
#[test]
fn connection_io_error_maps_to_connection() {
let io_err = std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "refused");
let g = GlideError::from(CE::IoError(io_err));
assert!(matches!(g, GlideError::Connection(_)));
assert_eq!(g.class_name(), "ConnectionError");
}
#[test]
fn connection_cluster_error_maps_to_connection() {
let redis_err = RedisError::from((ErrorKind::ResponseError, "cluster boom"));
let g = GlideError::from(CE::Cluster(redis_err));
assert!(matches!(g, GlideError::Connection(_)));
}
#[test]
fn class_names_for_all_variants() {
assert_eq!(GlideError::Closing("x".into()).class_name(), "ClosingError");
assert_eq!(
GlideError::Configuration("x".into()).class_name(),
"ConfigurationError"
);
assert_eq!(
GlideError::Connection("x".into()).class_name(),
"ConnectionError"
);
assert_eq!(
GlideError::ExecAbort("x".into()).class_name(),
"ExecAbortError"
);
assert_eq!(GlideError::Request("x".into()).class_name(), "RequestError");
assert_eq!(GlideError::Timeout("x".into()).class_name(), "TimeoutError");
assert_eq!(
GlideError::CircuitBreaker("x".into()).class_name(),
"CircuitBreakerError"
);
}
#[test]
fn message_for_all_variants() {
assert_eq!(GlideError::Closing("a".into()).message(), "a");
assert_eq!(GlideError::Configuration("b".into()).message(), "b");
assert_eq!(GlideError::Connection("c".into()).message(), "c");
assert_eq!(GlideError::ExecAbort("d".into()).message(), "d");
assert_eq!(GlideError::Request("e".into()).message(), "e");
assert_eq!(GlideError::Timeout("f".into()).message(), "f");
assert_eq!(GlideError::CircuitBreaker("g".into()).message(), "g");
}
#[test]
fn display_includes_class_and_message() {
let e = GlideError::Timeout("slow".into());
assert_eq!(e.to_string(), "TimeoutError: slow");
}
#[test]
fn variants_are_clonable_and_comparable() {
let a = GlideError::Request("same".into());
let b = a.clone();
assert_eq!(a, b);
assert_ne!(a, GlideError::Request("different".into()));
}
}