cache_kit/error.rs
1//! Error types for the cache framework.
2
3use std::fmt;
4
5/// Result type for cache operations.
6pub type Result<T> = std::result::Result<T, Error>;
7
8/// Error types for cache framework.
9///
10/// All cache operations return `Result<T>` where `Result` is defined as `std::result::Result<T, Error>`.
11/// Different error variants represent different failure modes:
12#[derive(Debug, Clone)]
13pub enum Error {
14 /// Serialization failed when converting entity to cache bytes.
15 ///
16 /// This occurs when the entity's `Serde` implementation fails.
17 /// Common causes:
18 /// - Entity contains non-serializable types
19 /// - Serde serialization panic/error
20 /// - Postcard codec error
21 SerializationError(String),
22
23 /// Deserialization failed when converting cache bytes to entity.
24 ///
25 /// This indicates corrupted or malformed data in cache.
26 /// Common causes:
27 /// - Cache was corrupted during transport or storage
28 /// - Invalid Postcard encoding
29 /// - Incomplete data read from backend
30 ///
31 /// **Recovery:** Cache entry should be evicted and recomputed.
32 DeserializationError(String),
33
34 /// Validation failed in feeder or entity.
35 ///
36 /// This is raised when:
37 /// - `CacheFeed::validate()` returns an error
38 /// - `CacheEntity::validate()` returns an error after deserialization
39 ///
40 /// Implement these methods to add custom validation logic.
41 ValidationError(String),
42
43 /// Cache miss: key not found in cache.
44 ///
45 /// Not necessarily an error condition, but indicates cache entry was absent.
46 /// Only returned with `CacheStrategy::Fresh` when cache lookup fails.
47 CacheMiss,
48
49 /// Backend storage error (Redis, Memcached, etc).
50 ///
51 /// This indicates the cache backend is unavailable or returned an error.
52 /// Common causes:
53 /// - Redis/Memcached connection lost
54 /// - Network timeout
55 /// - Backend storage full
56 /// - Backend protocol error
57 ///
58 /// **Recovery:** Retry the operation or fallback to database.
59 BackendError(String),
60
61 /// Data repository error (database, etc).
62 ///
63 /// This indicates the source repository (database) failed to fetch data.
64 /// Common causes:
65 /// - Database connection lost
66 /// - Query syntax error
67 /// - Database server error
68 /// - Row/record not found
69 ///
70 /// **Recovery:** Retry after connection recovery.
71 RepositoryError(String),
72
73 /// Operation exceeded configured timeout threshold.
74 ///
75 /// This occurs when cache or repository operations take too long.
76 /// Common causes:
77 /// - Network latency
78 /// - Slow database query
79 /// - Backend overload
80 ///
81 /// **Recovery:** Retry with exponential backoff.
82 Timeout(String),
83
84 /// Configuration error during crate initialization.
85 ///
86 /// This occurs when creating backends or policies with invalid config.
87 /// Common causes:
88 /// - Invalid connection string
89 /// - Missing required configuration
90 /// - Invalid TTL policy
91 ///
92 /// **Recovery:** Fix configuration and restart.
93 ConfigError(String),
94
95 /// Feature not implemented or not enabled.
96 ///
97 /// This indicates a requested feature is not available.
98 /// Common causes:
99 /// - Cargo feature not enabled (e.g., "redis" for RedisBackend)
100 /// - Backend-specific operation called on wrong backend type
101 ///
102 /// **Recovery:** Enable the required Cargo feature.
103 NotImplemented(String),
104
105 /// Invalid cache entry: corrupted envelope or bad magic.
106 ///
107 /// This indicates the cache entry header is invalid.
108 /// Returned when:
109 /// - Magic header is not `b"CKIT"`
110 /// - Envelope deserialization fails
111 /// - Non-cache-kit data in cache key
112 ///
113 /// **Recovery:** Evict the cache entry and recompute.
114 InvalidCacheEntry(String),
115
116 /// Schema version mismatch between code and cached data.
117 ///
118 /// This indicates the cache entry was created with a different schema version.
119 /// Raised when:
120 /// - `CURRENT_SCHEMA_VERSION` changed
121 /// - Struct fields were added/removed/reordered
122 /// - Enum variants changed
123 ///
124 /// **Recovery:** Cache entry is automatically evicted and recomputed on next access.
125 /// No action needed - this is expected during deployments.
126 VersionMismatch {
127 /// Expected schema version (from compiled code)
128 expected: u32,
129 /// Found schema version (from cached entry)
130 found: u32,
131 },
132
133 /// Generic error with custom message.
134 ///
135 /// Used for errors that don't fit into other variants.
136 Other(String),
137}
138
139impl fmt::Display for Error {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 match self {
142 Error::SerializationError(msg) => write!(f, "Serialization error: {}", msg),
143 Error::DeserializationError(msg) => write!(f, "Deserialization error: {}", msg),
144 Error::ValidationError(msg) => write!(f, "Validation error: {}", msg),
145 Error::CacheMiss => write!(f, "Cache miss"),
146 Error::BackendError(msg) => write!(f, "Backend error: {}", msg),
147 Error::RepositoryError(msg) => write!(f, "Repository error: {}", msg),
148 Error::Timeout(msg) => write!(f, "Timeout: {}", msg),
149 Error::ConfigError(msg) => write!(f, "Config error: {}", msg),
150 Error::NotImplemented(msg) => write!(f, "Not implemented: {}", msg),
151 Error::InvalidCacheEntry(msg) => {
152 write!(f, "Invalid cache entry: {}", msg)
153 }
154 Error::VersionMismatch { expected, found } => {
155 write!(
156 f,
157 "Cache version mismatch: expected {}, found {}",
158 expected, found
159 )
160 }
161 Error::Other(msg) => write!(f, "Error: {}", msg),
162 }
163 }
164}
165
166impl std::error::Error for Error {}
167
168// ============================================================================
169// Conversions from other error types
170// ============================================================================
171
172impl From<serde_json::Error> for Error {
173 fn from(e: serde_json::Error) -> Self {
174 if e.is_io() {
175 Error::BackendError(e.to_string())
176 } else if e.is_syntax() {
177 Error::DeserializationError(e.to_string())
178 } else {
179 Error::SerializationError(e.to_string())
180 }
181 }
182}
183
184impl From<std::io::Error> for Error {
185 fn from(e: std::io::Error) -> Self {
186 Error::BackendError(e.to_string())
187 }
188}
189
190impl From<String> for Error {
191 fn from(e: String) -> Self {
192 Error::Other(e)
193 }
194}
195
196impl From<&str> for Error {
197 fn from(e: &str) -> Self {
198 Error::Other(e.to_string())
199 }
200}
201
202#[cfg(feature = "redis")]
203impl From<redis::RedisError> for Error {
204 fn from(e: redis::RedisError) -> Self {
205 Error::BackendError(format!("Redis error: {}", e))
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn test_error_display() {
215 let err = Error::ValidationError("Test".to_string());
216 assert_eq!(err.to_string(), "Validation error: Test");
217 }
218
219 #[test]
220 fn test_error_from_string() {
221 let err: Error = "test error".into();
222 assert!(matches!(err, Error::Other(_)));
223 }
224}