1use std::time::Duration;
2
3use quick_cache::sync::Cache;
4use tokio::time::Instant;
5
6#[derive(Debug)]
7pub struct QuickCache<T: Clone> {
8 cache: Cache<String, (T, Instant)>,
9}
10
11impl<T: Clone> Default for QuickCache<T> {
12 fn default() -> Self {
13 Self::new()
14 }
15}
16
17impl<T: Clone> QuickCache<T> {
18 pub fn new() -> Self {
19 let cache = Cache::new(10);
20 Self { cache } }
22
23 pub fn get(&self, key: &str) -> Option<T> {
24 match self.cache.get(key) {
25 None => None,
26 Some((value, expire_time)) => {
27 if expire_time > Instant::now() {
28 Some(value)
29 } else {
30 self.cache.remove(key);
31 None
32 }
33 }
34 }
35 }
36
37 pub fn set(&mut self, key: &str, value: T, expire_time: i32) {
39 let expire_time = Instant::now() + Duration::from_secs(expire_time as u64);
40 self.cache.insert(key.to_string(), (value, expire_time));
41 }
42
43 pub fn get_with_expiry(&self, key: &str) -> Option<CacheEntry<T>> {
45 match self.cache.get(key) {
46 None => None,
47 Some((value, expire_time)) => {
48 let now = Instant::now();
49 if expire_time > now {
50 Some(CacheEntry {
51 value,
52 expires_at: expire_time,
53 current_time: now,
54 })
55 } else {
56 self.cache.remove(key);
57 None
58 }
59 }
60 }
61 }
62}
63
64#[derive(Debug, Clone)]
66pub struct CacheEntry<T> {
67 pub value: T,
68 pub expires_at: Instant,
69 pub current_time: Instant,
70}
71
72impl<T> CacheEntry<T> {
73 pub fn expiry_seconds(&self) -> u64 {
75 if self.expires_at > self.current_time {
76 (self.expires_at - self.current_time).as_secs()
77 } else {
78 0
79 }
80 }
81
82 pub fn expires_within(&self, seconds: u64) -> bool {
84 self.expiry_seconds() <= seconds
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use std::thread::sleep;
92 use std::time::Duration;
93
94 #[test]
95 fn test_cache_creation() {
96 let cache: QuickCache<String> = QuickCache::new();
97
98 assert!(cache.get("test_key").is_none());
100 }
101
102 #[test]
103 fn test_cache_default() {
104 let cache: QuickCache<i32> = QuickCache::default();
105
106 assert!(cache.get("test_key").is_none());
108 }
109
110 #[test]
111 fn test_cache_set_and_get() {
112 let mut cache = QuickCache::new();
113
114 cache.set("key1", "value1".to_string(), 10);
116
117 let result = cache.get("key1");
119 assert_eq!(result, Some("value1".to_string()));
120 }
121
122 #[test]
123 fn test_cache_expiration() {
124 let mut cache = QuickCache::new();
125
126 cache.set("short_key", "short_value".to_string(), 1);
128
129 assert_eq!(cache.get("short_key"), Some("short_value".to_string()));
131
132 sleep(Duration::from_secs(2));
134
135 assert!(cache.get("short_key").is_none());
137 }
138
139 #[test]
140 fn test_cache_nonexistent_key() {
141 let cache: QuickCache<String> = QuickCache::new();
142
143 assert!(cache.get("nonexistent").is_none());
145 }
146
147 #[test]
148 fn test_cache_with_expiry_info() {
149 let mut cache = QuickCache::new();
150
151 cache.set("expiry_key", 42, 60);
153
154 let entry = cache.get_with_expiry("expiry_key");
156 assert!(entry.is_some());
157
158 let entry = entry.unwrap();
159 assert_eq!(entry.value, 42);
160 assert!(entry.expiry_seconds() > 0);
161 assert!(entry.expiry_seconds() <= 60);
162 }
163
164 #[test]
165 fn test_cache_entry_expiry_seconds() {
166 let mut cache = QuickCache::new();
167
168 cache.set("timing_key", "timing_value".to_string(), 30);
170
171 let entry = cache.get_with_expiry("timing_key").unwrap();
172 let remaining = entry.expiry_seconds();
173
174 assert!(remaining > 25 && remaining <= 30);
176 }
177
178 #[test]
179 fn test_cache_entry_expires_within() {
180 let mut cache = QuickCache::new();
181
182 cache.set("soon_key", "soon_value".to_string(), 5);
184
185 let entry = cache.get_with_expiry("soon_key").unwrap();
186
187 assert!(entry.expires_within(10));
189
190 assert!(!entry.expires_within(1));
192 }
193
194 #[test]
195 fn test_cache_with_different_types() {
196 let mut string_cache = QuickCache::new();
197 let mut int_cache = QuickCache::new();
198 let mut vec_cache = QuickCache::new();
199
200 string_cache.set("str_key", "hello".to_string(), 30);
202 assert_eq!(string_cache.get("str_key"), Some("hello".to_string()));
203
204 int_cache.set("int_key", 123, 30);
206 assert_eq!(int_cache.get("int_key"), Some(123));
207
208 vec_cache.set("vec_key", vec![1, 2, 3], 30);
210 assert_eq!(vec_cache.get("vec_key"), Some(vec![1, 2, 3]));
211 }
212
213 #[test]
214 fn test_cache_overwrite_key() {
215 let mut cache = QuickCache::new();
216
217 cache.set("overwrite_key", "first_value".to_string(), 30);
219 assert_eq!(cache.get("overwrite_key"), Some("first_value".to_string()));
220
221 cache.set("overwrite_key", "second_value".to_string(), 30);
223 assert_eq!(cache.get("overwrite_key"), Some("second_value".to_string()));
224 }
225
226 #[test]
227 fn test_cache_multiple_keys() {
228 let mut cache = QuickCache::new();
229
230 cache.set("key1", "value1".to_string(), 60);
232 cache.set("key2", "value2".to_string(), 60);
233 cache.set("key3", "value3".to_string(), 60);
234
235 assert_eq!(cache.get("key1"), Some("value1".to_string()));
237 assert_eq!(cache.get("key2"), Some("value2".to_string()));
238 assert_eq!(cache.get("key3"), Some("value3".to_string()));
239 }
240
241 #[test]
242 fn test_cache_expired_entry_cleanup() {
243 let mut cache = QuickCache::new();
244
245 cache.set("cleanup_key", "cleanup_value".to_string(), 1);
247
248 assert!(cache.get("cleanup_key").is_some());
250
251 sleep(Duration::from_secs(2));
253
254 assert!(cache.get("cleanup_key").is_none());
256
257 assert!(cache.get("cleanup_key").is_none());
259 }
260
261 #[test]
262 fn test_cache_entry_debug_trait() {
263 let mut cache = QuickCache::new();
264 cache.set("debug_key", "debug_value".to_string(), 60);
265
266 let entry = cache.get_with_expiry("debug_key").unwrap();
267 let debug_string = format!("{:?}", entry);
268
269 assert!(debug_string.contains("CacheEntry"));
270 assert!(debug_string.contains("debug_value"));
271 }
272
273 #[test]
274 fn test_cache_debug_trait() {
275 let cache: QuickCache<String> = QuickCache::new();
276 let debug_string = format!("{:?}", cache);
277
278 assert!(debug_string.contains("QuickCache"));
279 }
280
281 #[test]
282 fn test_zero_expiry_seconds() {
283 let now = Instant::now();
284 let past_time = now - Duration::from_secs(10);
285
286 let entry = CacheEntry {
287 value: "test".to_string(),
288 expires_at: past_time,
289 current_time: now,
290 };
291
292 assert_eq!(entry.expiry_seconds(), 0);
294 assert!(entry.expires_within(100));
295 }
296
297 #[test]
298 fn test_cache_entry_clone() {
299 let mut cache = QuickCache::new();
300 cache.set("clone_key", vec![1, 2, 3], 60);
301
302 let entry = cache.get_with_expiry("clone_key").unwrap();
303 let cloned_entry = entry.clone();
304
305 assert_eq!(entry.value, cloned_entry.value);
306 assert_eq!(entry.expires_at, cloned_entry.expires_at);
307 assert_eq!(entry.current_time, cloned_entry.current_time);
308 }
309
310 #[test]
311 fn test_cache_with_zero_expiry() {
312 let mut cache = QuickCache::new();
313
314 cache.set("zero_key", "zero_value".to_string(), 0);
316
317 assert!(cache.get("zero_key").is_none());
319 }
320
321 #[test]
322 fn test_cache_with_negative_expiry() {
323 let mut cache = QuickCache::new();
324
325 cache.set("zero_expiry_key", "zero_value".to_string(), 0);
329
330 assert!(cache.get("zero_expiry_key").is_none());
332 }
333
334 #[test]
335 fn test_cache_large_expiry_time() {
336 let mut cache = QuickCache::new();
337
338 cache.set("large_key", "large_value".to_string(), i32::MAX);
340
341 let entry = cache.get_with_expiry("large_key").unwrap();
342 assert_eq!(entry.value, "large_value");
343 assert!(entry.expiry_seconds() > 1_000_000);
344 }
345
346 #[test]
347 fn test_get_with_expiry_nonexistent_key() {
348 let cache: QuickCache<String> = QuickCache::new();
349
350 assert!(cache.get_with_expiry("nonexistent").is_none());
352 }
353
354 #[test]
355 fn test_cache_with_empty_strings() {
356 let mut cache = QuickCache::new();
357
358 cache.set("", "empty_key_value".to_string(), 30);
360 assert_eq!(cache.get(""), Some("empty_key_value".to_string()));
361
362 cache.set("empty_value_key", "".to_string(), 30);
364 assert_eq!(cache.get("empty_value_key"), Some("".to_string()));
365
366 cache.set("", "".to_string(), 30);
368 assert_eq!(cache.get(""), Some("".to_string()));
369 }
370
371 #[test]
372 fn test_cache_with_unicode_strings() {
373 let mut cache = QuickCache::new();
374
375 let unicode_key = "键名_🔑";
376 let unicode_value = "值_🎯_测试";
377
378 cache.set(unicode_key, unicode_value.to_string(), 30);
379 assert_eq!(cache.get(unicode_key), Some(unicode_value.to_string()));
380 }
381
382 #[test]
383 fn test_cache_with_long_strings() {
384 let mut cache = QuickCache::new();
385
386 let long_key = "a".repeat(1000);
387 let long_value = "b".repeat(10000);
388
389 cache.set(&long_key, long_value.clone(), 30);
390 assert_eq!(cache.get(&long_key), Some(long_value));
391 }
392
393 #[test]
394 fn test_cache_memory_efficiency() {
395 let mut cache = QuickCache::new();
396
397 for i in 0..8 {
399 cache.set(&format!("key_{}", i), format!("value_{}", i), 60);
400 }
401
402 for i in 0..8 {
404 let key = format!("key_{}", i);
405 let expected_value = format!("value_{}", i);
406 assert_eq!(cache.get(&key), Some(expected_value));
407 }
408 }
409
410 #[test]
411 fn test_cache_concurrent_style_simulation() {
412 let mut cache = QuickCache::new();
414
415 for thread_id in 0..2 {
417 for operation in 0..3 {
418 let key = format!("thread_{}_op_{}", thread_id, operation);
419 let value = format!("data_{}", thread_id * 3 + operation);
420
421 cache.set(&key, value.clone(), 30);
422 assert_eq!(cache.get(&key), Some(value));
423 }
424 }
425
426 for thread_id in 0..2 {
428 for operation in 0..3 {
429 let key = format!("thread_{}_op_{}", thread_id, operation);
430 let expected_value = format!("data_{}", thread_id * 3 + operation);
431 assert_eq!(cache.get(&key), Some(expected_value));
432 }
433 }
434 }
435
436 #[test]
437 fn test_cache_entry_exact_timing() {
438 let now = Instant::now();
440 let expires_in_5_seconds = now + Duration::from_secs(5);
441
442 let entry = CacheEntry {
443 value: "timing_test".to_string(),
444 expires_at: expires_in_5_seconds,
445 current_time: now,
446 };
447
448 assert_eq!(entry.expiry_seconds(), 5);
449 assert!(entry.expires_within(5)); assert!(entry.expires_within(6)); assert!(!entry.expires_within(4)); }
453
454 #[test]
455 fn test_cache_entry_boundary_conditions() {
456 let now = Instant::now();
457
458 let exactly_now = CacheEntry {
460 value: "now".to_string(),
461 expires_at: now,
462 current_time: now,
463 };
464 assert_eq!(exactly_now.expiry_seconds(), 0);
465 assert!(exactly_now.expires_within(0));
466 assert!(exactly_now.expires_within(1));
467
468 let one_nano_future = CacheEntry {
470 value: "nano".to_string(),
471 expires_at: now + Duration::from_nanos(1),
472 current_time: now,
473 };
474 assert_eq!(one_nano_future.expiry_seconds(), 0); assert!(one_nano_future.expires_within(1));
476 }
477
478 #[test]
479 fn test_cache_with_complex_types() {
480 use std::collections::HashMap;
481
482 let mut map_cache = QuickCache::new();
484 let mut map = HashMap::new();
485 map.insert("nested_key".to_string(), "nested_value".to_string());
486
487 map_cache.set("map_key", map.clone(), 30);
488 assert_eq!(map_cache.get("map_key"), Some(map));
489
490 let mut option_cache = QuickCache::new();
492 option_cache.set("some_key", Some(42), 30);
493 assert_eq!(option_cache.get("some_key"), Some(Some(42)));
494
495 option_cache.set("none_key", None::<i32>, 30);
496 assert_eq!(option_cache.get("none_key"), Some(None));
497 }
498
499 #[test]
500 fn test_get_with_expiry_expired_cleanup() {
501 let mut cache = QuickCache::new();
502
503 cache.set("expiry_cleanup", "will_expire".to_string(), 1);
505
506 assert!(cache.get_with_expiry("expiry_cleanup").is_some());
508
509 sleep(Duration::from_secs(2));
511
512 assert!(cache.get_with_expiry("expiry_cleanup").is_none());
514
515 assert!(cache.get_with_expiry("expiry_cleanup").is_none());
517 }
518}