1use async_trait::async_trait;
2use redis::{Client, AsyncCommands};
3use std::time::Duration;
4use serde::{Deserialize, Serialize};
5use crate::{validate_cache_key, validate_ttl, Cache, Result};
6
7pub struct RedisCache {
9 client: Client,
10 default_ttl: Option<Duration>,
11}
12
13impl RedisCache {
14 pub fn new(url: &str) -> Result<Self> {
15 let client = Client::open(url)?;
16
17 Ok(Self {
18 client,
19 default_ttl: Some(Duration::from_secs(3600)),
20 })
21 }
22
23 pub fn with_default_ttl(url: &str, ttl: Duration) -> Result<Self> {
24 validate_ttl(Some(ttl))?;
25 let client = Client::open(url)?;
26
27 Ok(Self {
28 client,
29 default_ttl: Some(ttl),
30 })
31 }
32}
33
34#[async_trait]
35impl Cache for RedisCache {
36 async fn get<T>(&self, key: &str) -> Result<Option<T>>
37 where
38 T: for<'de> Deserialize<'de> + Send,
39 {
40 validate_cache_key(key)?;
41 let mut conn = self.client.get_multiplexed_async_connection().await?;
42
43 let result: Option<String> = conn.get(key).await?;
44
45 if let Some(data) = result {
46 let value: T = serde_json::from_str(&data)?;
47 Ok(Some(value))
48 } else {
49 Ok(None)
50 }
51 }
52
53 async fn set<T>(&self, key: &str, value: &T, ttl: Option<Duration>) -> Result<()>
54 where
55 T: Serialize + Send + Sync,
56 {
57 validate_cache_key(key)?;
58 validate_ttl(ttl)?;
59 let mut conn = self.client.get_multiplexed_async_connection().await?;
60
61 let data = serde_json::to_string(value)?;
62
63 let ttl = ttl.or(self.default_ttl);
64
65 if let Some(duration) = ttl {
66 let seconds = duration.as_secs().max(1);
67 let _: () = conn.set_ex(key, data, seconds).await?;
68 } else {
69 let _: () = conn.set(key, data).await?;
70 }
71
72 Ok(())
73 }
74
75 async fn delete(&self, key: &str) -> Result<()> {
76 validate_cache_key(key)?;
77 let mut conn = self.client.get_multiplexed_async_connection().await?;
78
79 let _: () = conn.del(key).await?;
80
81 Ok(())
82 }
83
84 async fn exists(&self, key: &str) -> Result<bool> {
85 validate_cache_key(key)?;
86 let mut conn = self.client.get_multiplexed_async_connection().await?;
87
88 let exists: bool = conn.exists(key).await?;
89
90 Ok(exists)
91 }
92
93 async fn flush(&self) -> Result<()> {
94 let mut conn = self.client.get_multiplexed_async_connection().await?;
95
96 let _: () = redis::cmd("FLUSHDB")
97 .query_async(&mut conn)
98 .await?;
99
100 Ok(())
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::RedisCache;
107 use std::time::Duration;
108
109 #[test]
110 fn rejects_zero_default_ttl() {
111 let result = RedisCache::with_default_ttl("redis://127.0.0.1/", Duration::from_secs(0));
112 assert!(result.is_err());
113 }
114}