Skip to main content

litellm_rs/core/traits/
cache.rs

1//! Cache system trait definitions
2//!
3//! Provides unified cache interface supporting multiple cache backends
4
5use serde::{Deserialize, Serialize};
6use std::time::Duration;
7
8/// Core cache trait
9///
10/// Defines unified cache operation interface
11#[allow(async_fn_in_trait)]
12pub trait Cache<K, V>: Send + Sync
13where
14    K: Send + Sync,
15    V: Send + Sync,
16{
17    /// Error
18    type Error: std::error::Error + Send + Sync + 'static;
19
20    /// Get
21    async fn get(&self, key: &K) -> Result<Option<V>, Self::Error>;
22
23    /// Settings
24    async fn set(&self, key: &K, value: V, ttl: Duration) -> Result<(), Self::Error>;
25
26    /// Delete
27    async fn delete(&self, key: &K) -> Result<bool, Self::Error>;
28
29    /// Check
30    async fn exists(&self, key: &K) -> Result<bool, Self::Error>;
31
32    /// Settings
33    async fn expire(&self, key: &K, ttl: Duration) -> Result<bool, Self::Error>;
34
35    /// Get
36    async fn ttl(&self, key: &K) -> Result<Option<Duration>, Self::Error>;
37
38    /// Clear all cache
39    async fn clear(&self) -> Result<(), Self::Error>;
40
41    /// Get
42    async fn size(&self) -> Result<usize, Self::Error>;
43
44    /// Get
45    async fn get_many(&self, keys: &[K]) -> Result<Vec<Option<V>>, Self::Error> {
46        let mut results = Vec::with_capacity(keys.len());
47        for key in keys {
48            results.push(self.get(key).await?);
49        }
50        Ok(results)
51    }
52
53    /// Settings
54    async fn set_many(&self, items: &[(K, V, Duration)]) -> Result<(), Self::Error>
55    where
56        K: Clone,
57        V: Clone,
58    {
59        for (key, value, ttl) in items {
60            self.set(key, value.clone(), *ttl).await?;
61        }
62        Ok(())
63    }
64}
65
66/// Cache key trait
67///
68/// Defines operations that cache keys must support
69pub trait CacheKey: Send + Sync + Clone + std::fmt::Debug + std::hash::Hash + Eq {
70    /// Serialize key to string
71    fn to_cache_key(&self) -> String;
72
73    /// Deserialize key from string
74    fn from_cache_key(s: &str) -> Result<Self, CacheError>
75    where
76        Self: Sized;
77}
78
79/// Cache value trait
80///
81/// Defines operations that cache values must support
82pub trait CacheValue: Send + Sync + Clone + std::fmt::Debug {
83    /// Serialize to bytes
84    fn to_bytes(&self) -> Result<Vec<u8>, CacheError>;
85
86    /// Deserialize from bytes
87    fn from_bytes(bytes: &[u8]) -> Result<Self, CacheError>
88    where
89        Self: Sized;
90}
91
92/// Implementation of CacheKey for String
93impl CacheKey for String {
94    fn to_cache_key(&self) -> String {
95        self.clone()
96    }
97
98    fn from_cache_key(s: &str) -> Result<Self, CacheError> {
99        Ok(s.to_string())
100    }
101}
102
103/// Implementation of CacheValue for all types that implement Serialize + DeserializeOwned
104impl<T> CacheValue for T
105where
106    T: Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone + std::fmt::Debug,
107{
108    fn to_bytes(&self) -> Result<Vec<u8>, CacheError> {
109        rmp_serde::to_vec(self).map_err(CacheError::Serialization)
110    }
111
112    fn from_bytes(bytes: &[u8]) -> Result<Self, CacheError> {
113        rmp_serde::from_slice(bytes).map_err(CacheError::Deserialization)
114    }
115}
116
117/// Cache statistics
118#[derive(Debug, Clone)]
119pub struct CacheStats {
120    /// Cache hit count
121    pub hits: u64,
122    /// Cache miss count
123    pub misses: u64,
124    /// Current key count
125    pub key_count: usize,
126    /// Used memory amount (bytes)
127    pub memory_usage: usize,
128    /// Hit rate
129    pub hit_rate: f64,
130}
131
132impl CacheStats {
133    pub fn new() -> Self {
134        Self {
135            hits: 0,
136            misses: 0,
137            key_count: 0,
138            memory_usage: 0,
139            hit_rate: 0.0,
140        }
141    }
142
143    pub fn calculate_hit_rate(&mut self) {
144        let total = self.hits + self.misses;
145        if total > 0 {
146            self.hit_rate = self.hits as f64 / total as f64;
147        }
148    }
149}
150
151impl Default for CacheStats {
152    fn default() -> Self {
153        Self::new()
154    }
155}
156
157/// Cache trait with statistics functionality
158#[allow(async_fn_in_trait)]
159pub trait CacheWithStats<K, V>: Cache<K, V>
160where
161    K: Send + Sync,
162    V: Send + Sync,
163{
164    /// Get
165    async fn stats(&self) -> Result<CacheStats, Self::Error>;
166
167    /// Reset statistics
168    async fn reset_stats(&self) -> Result<(), Self::Error>;
169}
170
171/// Cache event types
172#[derive(Debug, Clone)]
173pub enum CacheEvent<K, V> {
174    /// Cache hit
175    Hit { key: K },
176    /// Cache miss
177    Miss { key: K },
178    /// Settings
179    Set { key: K, value: V },
180    /// Delete
181    Delete { key: K },
182    /// Cache expiration
183    Expire { key: K },
184    /// Cache clear
185    Clear,
186}
187
188/// Cache event listener
189#[allow(async_fn_in_trait)]
190pub trait CacheEventListener<K, V>: Send + Sync
191where
192    K: Send + Sync,
193    V: Send + Sync,
194{
195    /// Handle
196    async fn on_event(&self, event: CacheEvent<K, V>);
197}
198
199/// Error
200#[derive(Debug, thiserror::Error)]
201pub enum CacheError {
202    #[error("Connection failed: {0}")]
203    Connection(String),
204
205    #[error("Serialization failed: {0}")]
206    Serialization(#[from] rmp_serde::encode::Error),
207
208    #[error("Deserialization failed: {0}")]
209    Deserialization(rmp_serde::decode::Error),
210
211    #[error("Key not found: {key}")]
212    KeyNotFound { key: String },
213
214    #[error("Cache is full")]
215    CacheFull,
216
217    #[error("Invalid TTL: {ttl_ms}ms")]
218    InvalidTTL { ttl_ms: u64 },
219
220    #[error("Cache operation timeout")]
221    Timeout,
222
223    #[error("Cache backend error: {0}")]
224    Backend(String),
225
226    #[error("Other cache error: {0}")]
227    Other(String),
228}
229
230impl CacheError {
231    pub fn connection(msg: impl Into<String>) -> Self {
232        Self::Connection(msg.into())
233    }
234
235    pub fn key_not_found(key: impl Into<String>) -> Self {
236        Self::KeyNotFound { key: key.into() }
237    }
238
239    pub fn backend(msg: impl Into<String>) -> Self {
240        Self::Backend(msg.into())
241    }
242
243    pub fn other(msg: impl Into<String>) -> Self {
244        Self::Other(msg.into())
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251
252    // ==================== CacheKey Tests ====================
253
254    #[test]
255    fn test_string_cache_key_to_cache_key() {
256        let key = "my-cache-key".to_string();
257        assert_eq!(key.to_cache_key(), "my-cache-key");
258    }
259
260    #[test]
261    fn test_string_cache_key_from_cache_key() {
262        let key = String::from_cache_key("restored-key").unwrap();
263        assert_eq!(key, "restored-key");
264    }
265
266    #[test]
267    fn test_string_cache_key_roundtrip() {
268        let original = "test-key-123".to_string();
269        let serialized = original.to_cache_key();
270        let restored = String::from_cache_key(&serialized).unwrap();
271        assert_eq!(original, restored);
272    }
273
274    // ==================== CacheValue Tests ====================
275
276    #[test]
277    fn test_cache_value_to_bytes_string() {
278        let value = "hello world".to_string();
279        let bytes = value.to_bytes();
280        assert!(bytes.is_ok());
281        assert!(!bytes.unwrap().is_empty());
282    }
283
284    #[test]
285    fn test_cache_value_from_bytes_string() {
286        let value = "test value".to_string();
287        let bytes = value.to_bytes().unwrap();
288        let restored = String::from_bytes(&bytes).unwrap();
289        assert_eq!(value, restored);
290    }
291
292    #[test]
293    fn test_cache_value_roundtrip_integer() {
294        let value: i32 = 42;
295        let bytes = value.to_bytes().unwrap();
296        let restored = i32::from_bytes(&bytes).unwrap();
297        assert_eq!(value, restored);
298    }
299
300    #[test]
301    fn test_cache_value_roundtrip_complex() {
302        #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
303        struct TestData {
304            id: u64,
305            name: String,
306            active: bool,
307        }
308
309        let value = TestData {
310            id: 123,
311            name: "test".to_string(),
312            active: true,
313        };
314        let bytes = value.to_bytes().unwrap();
315        let restored = TestData::from_bytes(&bytes).unwrap();
316        assert_eq!(value, restored);
317    }
318
319    // ==================== CacheStats Tests ====================
320
321    #[test]
322    fn test_cache_stats_new() {
323        let stats = CacheStats::new();
324        assert_eq!(stats.hits, 0);
325        assert_eq!(stats.misses, 0);
326        assert_eq!(stats.key_count, 0);
327        assert_eq!(stats.memory_usage, 0);
328        assert!((stats.hit_rate - 0.0).abs() < f64::EPSILON);
329    }
330
331    #[test]
332    fn test_cache_stats_default() {
333        let stats = CacheStats::default();
334        assert_eq!(stats.hits, 0);
335        assert_eq!(stats.misses, 0);
336    }
337
338    #[test]
339    fn test_cache_stats_calculate_hit_rate_zero_total() {
340        let mut stats = CacheStats::new();
341        stats.calculate_hit_rate();
342        assert!((stats.hit_rate - 0.0).abs() < f64::EPSILON);
343    }
344
345    #[test]
346    fn test_cache_stats_calculate_hit_rate_all_hits() {
347        let mut stats = CacheStats::new();
348        stats.hits = 100;
349        stats.misses = 0;
350        stats.calculate_hit_rate();
351        assert!((stats.hit_rate - 1.0).abs() < f64::EPSILON);
352    }
353
354    #[test]
355    fn test_cache_stats_calculate_hit_rate_all_misses() {
356        let mut stats = CacheStats::new();
357        stats.hits = 0;
358        stats.misses = 100;
359        stats.calculate_hit_rate();
360        assert!((stats.hit_rate - 0.0).abs() < f64::EPSILON);
361    }
362
363    #[test]
364    fn test_cache_stats_calculate_hit_rate_mixed() {
365        let mut stats = CacheStats::new();
366        stats.hits = 75;
367        stats.misses = 25;
368        stats.calculate_hit_rate();
369        assert!((stats.hit_rate - 0.75).abs() < 0.001);
370    }
371
372    #[test]
373    fn test_cache_stats_clone() {
374        let mut stats = CacheStats::new();
375        stats.hits = 10;
376        stats.key_count = 5;
377        let cloned = stats.clone();
378        assert_eq!(stats.hits, cloned.hits);
379        assert_eq!(stats.key_count, cloned.key_count);
380    }
381
382    #[test]
383    fn test_cache_stats_debug() {
384        let stats = CacheStats::new();
385        let debug = format!("{:?}", stats);
386        assert!(debug.contains("CacheStats"));
387    }
388
389    // ==================== CacheEvent Tests ====================
390
391    #[test]
392    fn test_cache_event_hit() {
393        let event: CacheEvent<String, i32> = CacheEvent::Hit {
394            key: "key1".to_string(),
395        };
396        assert!(matches!(event, CacheEvent::Hit { key } if key == "key1"));
397    }
398
399    #[test]
400    fn test_cache_event_miss() {
401        let event: CacheEvent<String, i32> = CacheEvent::Miss {
402            key: "key2".to_string(),
403        };
404        assert!(matches!(event, CacheEvent::Miss { key } if key == "key2"));
405    }
406
407    #[test]
408    fn test_cache_event_set() {
409        let event = CacheEvent::Set {
410            key: "key3".to_string(),
411            value: 42,
412        };
413        assert!(matches!(event, CacheEvent::Set { key, value } if key == "key3" && value == 42));
414    }
415
416    #[test]
417    fn test_cache_event_delete() {
418        let event: CacheEvent<String, i32> = CacheEvent::Delete {
419            key: "key4".to_string(),
420        };
421        assert!(matches!(event, CacheEvent::Delete { key } if key == "key4"));
422    }
423
424    #[test]
425    fn test_cache_event_expire() {
426        let event: CacheEvent<String, i32> = CacheEvent::Expire {
427            key: "key5".to_string(),
428        };
429        assert!(matches!(event, CacheEvent::Expire { key } if key == "key5"));
430    }
431
432    #[test]
433    fn test_cache_event_clear() {
434        let event: CacheEvent<String, i32> = CacheEvent::Clear;
435        assert!(matches!(event, CacheEvent::Clear));
436    }
437
438    #[test]
439    fn test_cache_event_clone() {
440        let event = CacheEvent::Set {
441            key: "key".to_string(),
442            value: 100,
443        };
444        let cloned = event.clone();
445        assert!(matches!(cloned, CacheEvent::Set { key, value } if key == "key" && value == 100));
446    }
447
448    // ==================== CacheError Tests ====================
449
450    #[test]
451    fn test_cache_error_connection() {
452        let err = CacheError::connection("Redis connection failed");
453        assert!(matches!(err, CacheError::Connection(_)));
454        assert!(err.to_string().contains("Connection failed"));
455    }
456
457    #[test]
458    fn test_cache_error_key_not_found() {
459        let err = CacheError::key_not_found("missing-key");
460        assert!(matches!(err, CacheError::KeyNotFound { .. }));
461        assert!(err.to_string().contains("Key not found"));
462        assert!(err.to_string().contains("missing-key"));
463    }
464
465    #[test]
466    fn test_cache_error_cache_full() {
467        let err = CacheError::CacheFull;
468        assert!(err.to_string().contains("Cache is full"));
469    }
470
471    #[test]
472    fn test_cache_error_invalid_ttl() {
473        let err = CacheError::InvalidTTL { ttl_ms: 0 };
474        assert!(err.to_string().contains("Invalid TTL"));
475    }
476
477    #[test]
478    fn test_cache_error_timeout() {
479        let err = CacheError::Timeout;
480        assert!(err.to_string().contains("timeout"));
481    }
482
483    #[test]
484    fn test_cache_error_backend() {
485        let err = CacheError::backend("Backend failure");
486        assert!(matches!(err, CacheError::Backend(_)));
487        assert!(err.to_string().contains("Backend"));
488    }
489
490    #[test]
491    fn test_cache_error_other() {
492        let err = CacheError::other("Some other error");
493        assert!(matches!(err, CacheError::Other(_)));
494    }
495
496    #[test]
497    fn test_cache_error_display() {
498        let err = CacheError::connection("test error");
499        let display = format!("{}", err);
500        assert!(!display.is_empty());
501    }
502
503    #[test]
504    fn test_cache_error_debug() {
505        let err = CacheError::CacheFull;
506        let debug = format!("{:?}", err);
507        assert!(debug.contains("CacheFull"));
508    }
509}