use thiserror::Error;
#[cfg(feature = "redis")]
#[derive(Debug, Error)]
pub enum CacheConfigError {
#[error("Missing required field: {0}")]
MissingField(String),
#[error("Invalid value for field '{field}': {reason}")]
InvalidValue { field: String, reason: String },
#[error("Unsupported backend combination: {0}")]
UnsupportedBackend(String),
#[error("Connection failed during initialization: {0}")]
ConnectionFailed(String),
}
pub type ConfigResult<T> = std::result::Result<T, CacheConfigError>;
#[derive(Error, Debug)]
pub enum CacheError {
#[error("Serialization error: {0}. Please check the data format and ensure the serializer is compatible.")]
Serialization(String),
#[error("Operation failed: {0}. Please retry or check your request.")]
Operation(String),
#[error("Connection error: {0}. Please check network connectivity and server availability.")]
Connection(String),
#[error("Key not found: {0}. The requested key does not exist in the cache.")]
NotFound(String),
#[error("Cache degraded: {0}. The cache is operating in degraded mode with limited functionality.")]
Degraded(String),
#[error("L1 cache operation failed: {0}. This may indicate memory pressure or configuration issues.")]
L1Error(String),
#[error("L2 cache operation failed: {0}. Please check Redis connection and server status.")]
L2Error(String),
#[error("Operation not supported: {0}. This feature may not be available for the current cache type.")]
NotSupported(String),
#[error("WAL (Write-Ahead Log) operation failed: {0}. Check disk space and file permissions.")]
WalError(String),
#[error("Database error: {0}. Please check database connectivity and query syntax.")]
DatabaseError(String),
#[cfg(feature = "redis")]
#[error("Redis connection failed: {0}")]
RedisError(#[from] redis::RedisError),
#[cfg(not(feature = "redis"))]
#[error("Redis connection failed: {0}. Please enable redis feature and ensure Redis server is running.")]
RedisError(String),
#[error("I/O error: {0}. Check file permissions and disk space.")]
IoError(std::io::Error),
#[error("Backend error: {0}. This may be a transient issue, please retry.")]
BackendError(String),
#[error("Operation timed out: {0}. Consider increasing the timeout value or check system performance.")]
Timeout(String),
#[error("Shutdown error: {0}. Some resources may not have been properly released.")]
ShutdownError(String),
#[error("Key too long: {0}. Maximum key length is {1} bytes.")]
KeyTooLong(usize, usize),
#[error("Value too large: {0}. Maximum value size is {1} bytes.")]
ValueTooLarge(usize, usize),
#[error(
"Buffer full: {0}. The batch write buffer has reached capacity. Please retry later or increase buffer size."
)]
BufferFull(String),
#[error("Invalid input: {0}. The provided input does not meet the required format or constraints.")]
InvalidInput(String),
#[error("Invalid key: {0}. The provided key does not meet the required format or contains forbidden characters.")]
InvalidKey(String),
#[error("Lock error: {0}. The lock may have been poisoned by a previous panic.")]
LockError(String),
#[error("Service not found: {0}. The requested service configuration does not exist in the UnifiedConfig.")]
ServiceNotFound(String),
#[error("Internal error: {0}")]
Internal(String),
}
pub type Result<T> = std::result::Result<T, CacheError>;
impl From<std::io::Error> for CacheError {
fn from(e: std::io::Error) -> Self {
CacheError::IoError(e)
}
}
impl From<serde_json::Error> for CacheError {
fn from(e: serde_json::Error) -> Self {
CacheError::Serialization(e.to_string())
}
}
impl CacheError {
pub fn code(&self) -> &'static str {
match self {
CacheError::NotFound(_) => "CACHE_001",
CacheError::Connection(_) => "CACHE_002",
CacheError::Serialization(_) => "CACHE_003",
CacheError::Operation(_) => "CACHE_004",
CacheError::Degraded(_) => "CACHE_005",
CacheError::L1Error(_) => "CACHE_006",
CacheError::L2Error(_) => "CACHE_007",
CacheError::NotSupported(_) => "CACHE_009",
CacheError::WalError(_) => "CACHE_010",
CacheError::DatabaseError(_) => "CACHE_011",
CacheError::RedisError(_) => "CACHE_012",
CacheError::IoError(_) => "CACHE_013",
CacheError::BackendError(_) => "CACHE_014",
CacheError::Timeout(_) => "CACHE_015",
CacheError::ShutdownError(_) => "CACHE_016",
CacheError::KeyTooLong(_, _) => "CACHE_017",
CacheError::ValueTooLarge(_, _) => "CACHE_018",
CacheError::BufferFull(_) => "CACHE_019",
CacheError::InvalidInput(_) => "CACHE_020",
CacheError::InvalidKey(_) => "CACHE_021",
CacheError::LockError(_) => "CACHE_022",
CacheError::ServiceNotFound(_) => "CACHE_023",
CacheError::Internal(_) => "CACHE_024",
}
}
pub fn is_recoverable(&self) -> bool {
matches!(
self,
CacheError::Connection(_)
| CacheError::Timeout(_)
| CacheError::L2Error(_)
| CacheError::BackendError(_)
| CacheError::BufferFull(_)
)
}
pub fn is_not_found(&self) -> bool {
matches!(self, CacheError::NotFound(_))
}
pub fn is_connection_error(&self) -> bool {
matches!(
self,
CacheError::Connection(_) | CacheError::RedisError(_) | CacheError::L2Error(_)
)
}
pub fn is_degraded(&self) -> bool {
matches!(self, CacheError::Degraded(_))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_config_error_missing_field_display() {
let err = CacheConfigError::MissingField("host".to_string());
assert_eq!(err.to_string(), "Missing required field: host");
}
#[test]
fn test_cache_config_error_invalid_value_display() {
let err = CacheConfigError::InvalidValue {
field: "capacity".to_string(),
reason: "must be > 0".to_string(),
};
assert_eq!(err.to_string(), "Invalid value for field 'capacity': must be > 0");
}
#[test]
fn test_cache_config_error_unsupported_backend_display() {
let err = CacheConfigError::UnsupportedBackend("unknown".to_string());
assert_eq!(err.to_string(), "Unsupported backend combination: unknown");
}
#[test]
fn test_cache_config_error_connection_failed_display() {
let err = CacheConfigError::ConnectionFailed("timeout".to_string());
assert_eq!(err.to_string(), "Connection failed during initialization: timeout");
}
#[test]
fn test_cache_error_serialization_display() {
let err = CacheError::Serialization("bad data".to_string());
let s = err.to_string();
assert!(s.contains("Serialization error: bad data"));
}
#[test]
fn test_cache_error_operation_display() {
let err = CacheError::Operation("fail".to_string());
let s = err.to_string();
assert!(s.contains("Operation failed: fail"));
}
#[test]
fn test_cache_error_connection_display() {
let err = CacheError::Connection("refused".to_string());
let s = err.to_string();
assert!(s.contains("Connection error: refused"));
}
#[test]
fn test_cache_error_not_found_display() {
let err = CacheError::NotFound("key1".to_string());
let s = err.to_string();
assert!(s.contains("Key not found: key1"));
}
#[test]
fn test_cache_error_degraded_display() {
let err = CacheError::Degraded("L2 down".to_string());
let s = err.to_string();
assert!(s.contains("Cache degraded: L2 down"));
}
#[test]
fn test_cache_error_l1_error_display() {
let err = CacheError::L1Error("oom".to_string());
let s = err.to_string();
assert!(s.contains("L1 cache operation failed: oom"));
}
#[test]
fn test_cache_error_l2_error_display() {
let err = CacheError::L2Error("redis down".to_string());
let s = err.to_string();
assert!(s.contains("L2 cache operation failed: redis down"));
}
#[test]
fn test_cache_error_not_supported_display() {
let err = CacheError::NotSupported("scan".to_string());
let s = err.to_string();
assert!(s.contains("Operation not supported: scan"));
}
#[test]
fn test_cache_error_wal_error_display() {
let err = CacheError::WalError("disk full".to_string());
let s = err.to_string();
assert!(s.contains("WAL (Write-Ahead Log) operation failed: disk full"));
}
#[test]
fn test_cache_error_database_error_display() {
let err = CacheError::DatabaseError("query failed".to_string());
let s = err.to_string();
assert!(s.contains("Database error: query failed"));
}
#[test]
fn test_cache_error_redis_error_display() {
#[cfg(feature = "redis")]
{
let err = CacheError::RedisError(redis::RedisError::from(std::io::Error::new(
std::io::ErrorKind::Other,
"auth failed",
)));
let s = err.to_string();
assert!(s.contains("Redis connection failed"));
}
#[cfg(not(feature = "redis"))]
{
let err = CacheError::RedisError("auth failed".to_string());
let s = err.to_string();
assert!(s.contains("Redis connection failed: auth failed"));
}
}
#[test]
fn test_cache_error_io_error_display() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
let err = CacheError::IoError(io_err);
let s = err.to_string();
assert!(s.contains("I/O error:"));
}
#[test]
fn test_cache_error_backend_error_display() {
let err = CacheError::BackendError("transient".to_string());
let s = err.to_string();
assert!(s.contains("Backend error: transient"));
}
#[test]
fn test_cache_error_timeout_display() {
let err = CacheError::Timeout("5s".to_string());
let s = err.to_string();
assert!(s.contains("Operation timed out: 5s"));
}
#[test]
fn test_cache_error_shutdown_error_display() {
let err = CacheError::ShutdownError("leak".to_string());
let s = err.to_string();
assert!(s.contains("Shutdown error: leak"));
}
#[test]
fn test_cache_error_key_too_long_display() {
let err = CacheError::KeyTooLong(600, 512);
let s = err.to_string();
assert!(s.contains("Key too long: 600. Maximum key length is 512 bytes."));
}
#[test]
fn test_cache_error_value_too_large_display() {
let err = CacheError::ValueTooLarge(2048, 1024);
let s = err.to_string();
assert!(s.contains("Value too large: 2048. Maximum value size is 1024 bytes."));
}
#[test]
fn test_cache_error_buffer_full_display() {
let err = CacheError::BufferFull("batch".to_string());
let s = err.to_string();
assert!(s.contains("Buffer full: batch"));
}
#[test]
fn test_cache_error_invalid_input_display() {
let err = CacheError::InvalidInput("bad".to_string());
let s = err.to_string();
assert!(s.contains("Invalid input: bad"));
}
#[test]
fn test_cache_error_invalid_key_display() {
let err = CacheError::InvalidKey("bad key".to_string());
let s = err.to_string();
assert!(s.contains("Invalid key: bad key"));
}
#[test]
fn test_cache_error_lock_error_display() {
let err = CacheError::LockError("poisoned".to_string());
let s = err.to_string();
assert!(s.contains("Lock error: poisoned"));
}
#[test]
fn test_cache_error_service_not_found_display() {
let err = CacheError::ServiceNotFound("svc".to_string());
let s = err.to_string();
assert!(s.contains("Service not found: svc"));
}
#[test]
fn test_cache_error_internal_display() {
let err = CacheError::Internal("boom".to_string());
let s = err.to_string();
assert_eq!(s, "Internal error: boom");
}
#[test]
fn test_from_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
let cache_err: CacheError = io_err.into();
assert!(matches!(cache_err, CacheError::IoError(_)));
}
#[test]
fn test_from_serde_json_error() {
let serde_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
let cache_err: CacheError = serde_err.into();
assert!(matches!(cache_err, CacheError::Serialization(_)));
}
#[test]
fn test_error_code_not_found() {
assert_eq!(CacheError::NotFound("k".to_string()).code(), "CACHE_001");
}
#[test]
fn test_error_code_connection() {
assert_eq!(CacheError::Connection("c".to_string()).code(), "CACHE_002");
}
#[test]
fn test_error_code_serialization() {
assert_eq!(CacheError::Serialization("s".to_string()).code(), "CACHE_003");
}
#[test]
fn test_error_code_operation() {
assert_eq!(CacheError::Operation("o".to_string()).code(), "CACHE_004");
}
#[test]
fn test_error_code_degraded() {
assert_eq!(CacheError::Degraded("d".to_string()).code(), "CACHE_005");
}
#[test]
fn test_error_code_l1() {
assert_eq!(CacheError::L1Error("l1".to_string()).code(), "CACHE_006");
}
#[test]
fn test_error_code_l2() {
assert_eq!(CacheError::L2Error("l2".to_string()).code(), "CACHE_007");
}
#[test]
fn test_error_code_not_supported() {
assert_eq!(CacheError::NotSupported("ns".to_string()).code(), "CACHE_009");
}
#[test]
fn test_error_code_wal() {
assert_eq!(CacheError::WalError("w".to_string()).code(), "CACHE_010");
}
#[test]
fn test_error_code_database() {
assert_eq!(CacheError::DatabaseError("db".to_string()).code(), "CACHE_011");
}
#[test]
fn test_error_code_redis() {
#[cfg(feature = "redis")]
{
let err = CacheError::RedisError(redis::RedisError::from(std::io::Error::new(
std::io::ErrorKind::Other,
"r",
)));
assert_eq!(err.code(), "CACHE_012");
}
#[cfg(not(feature = "redis"))]
{
assert_eq!(CacheError::RedisError("r".to_string()).code(), "CACHE_012");
}
}
#[test]
fn test_error_code_io() {
let io_err = std::io::Error::new(std::io::ErrorKind::Other, "x");
assert_eq!(CacheError::IoError(io_err).code(), "CACHE_013");
}
#[test]
fn test_error_code_backend() {
assert_eq!(CacheError::BackendError("b".to_string()).code(), "CACHE_014");
}
#[test]
fn test_error_code_timeout() {
assert_eq!(CacheError::Timeout("t".to_string()).code(), "CACHE_015");
}
#[test]
fn test_error_code_shutdown() {
assert_eq!(CacheError::ShutdownError("s".to_string()).code(), "CACHE_016");
}
#[test]
fn test_error_code_key_too_long() {
assert_eq!(CacheError::KeyTooLong(1, 2).code(), "CACHE_017");
}
#[test]
fn test_error_code_value_too_large() {
assert_eq!(CacheError::ValueTooLarge(1, 2).code(), "CACHE_018");
}
#[test]
fn test_error_code_buffer_full() {
assert_eq!(CacheError::BufferFull("b".to_string()).code(), "CACHE_019");
}
#[test]
fn test_error_code_invalid_input() {
assert_eq!(CacheError::InvalidInput("i".to_string()).code(), "CACHE_020");
}
#[test]
fn test_error_code_invalid_key() {
assert_eq!(CacheError::InvalidKey("k".to_string()).code(), "CACHE_021");
}
#[test]
fn test_error_code_lock_error() {
assert_eq!(CacheError::LockError("l".to_string()).code(), "CACHE_022");
}
#[test]
fn test_error_code_service_not_found() {
assert_eq!(CacheError::ServiceNotFound("s".to_string()).code(), "CACHE_023");
}
#[test]
fn test_error_code_internal() {
assert_eq!(CacheError::Internal("i".to_string()).code(), "CACHE_024");
}
#[test]
fn test_is_recoverable_connection() {
assert!(CacheError::Connection("c".to_string()).is_recoverable());
}
#[test]
fn test_is_recoverable_timeout() {
assert!(CacheError::Timeout("t".to_string()).is_recoverable());
}
#[test]
fn test_is_recoverable_l2() {
assert!(CacheError::L2Error("l2".to_string()).is_recoverable());
}
#[test]
fn test_is_recoverable_backend() {
assert!(CacheError::BackendError("b".to_string()).is_recoverable());
}
#[test]
fn test_is_recoverable_buffer_full() {
assert!(CacheError::BufferFull("b".to_string()).is_recoverable());
}
#[test]
fn test_is_not_recoverable_not_found() {
assert!(!CacheError::NotFound("k".to_string()).is_recoverable());
}
#[test]
fn test_is_not_recoverable_internal() {
assert!(!CacheError::Internal("i".to_string()).is_recoverable());
}
#[test]
fn test_is_not_recoverable_serialization() {
assert!(!CacheError::Serialization("s".to_string()).is_recoverable());
}
#[test]
fn test_is_not_found_true() {
assert!(CacheError::NotFound("key".to_string()).is_not_found());
}
#[test]
fn test_is_not_found_false() {
assert!(!CacheError::Connection("c".to_string()).is_not_found());
}
#[test]
fn test_is_connection_error_connection() {
assert!(CacheError::Connection("c".to_string()).is_connection_error());
}
#[test]
fn test_is_connection_error_redis() {
#[cfg(feature = "redis")]
{
let err = CacheError::RedisError(redis::RedisError::from(std::io::Error::new(
std::io::ErrorKind::ConnectionRefused,
"r",
)));
assert!(err.is_connection_error());
}
#[cfg(not(feature = "redis"))]
{
assert!(CacheError::RedisError("r".to_string()).is_connection_error());
}
}
#[test]
fn test_is_connection_error_l2() {
assert!(CacheError::L2Error("l2".to_string()).is_connection_error());
}
#[test]
fn test_is_connection_error_false() {
assert!(!CacheError::NotFound("k".to_string()).is_connection_error());
}
#[test]
fn test_is_degraded_true() {
assert!(CacheError::Degraded("d".to_string()).is_degraded());
}
#[test]
fn test_is_degraded_false() {
assert!(!CacheError::NotFound("k".to_string()).is_degraded());
}
#[test]
fn test_cache_error_debug() {
let err = CacheError::NotFound("key".to_string());
let debug_str = format!("{:?}", err);
assert!(debug_str.contains("NotFound"));
}
#[test]
fn test_cache_config_error_debug() {
let err = CacheConfigError::MissingField("f".to_string());
let debug_str = format!("{:?}", err);
assert!(debug_str.contains("MissingField"));
}
#[test]
fn test_cache_error_is_std_error() {
let err = CacheError::NotFound("key".to_string());
let _: &dyn std::error::Error = &err;
}
#[test]
fn test_cache_config_error_is_std_error() {
let err = CacheConfigError::MissingField("f".to_string());
let _: &dyn std::error::Error = &err;
}
}