1use async_trait::async_trait;
2use redis::AsyncCommands;
3use synaptic_core::{ChatResponse, SynapticError};
4
5use crate::connection::{collect_matching_keys, RedisBackend, RedisConn};
6
7#[derive(Debug, Clone)]
9pub struct RedisCacheConfig {
10 pub prefix: String,
12 pub ttl: Option<u64>,
14}
15
16impl Default for RedisCacheConfig {
17 fn default() -> Self {
18 Self {
19 prefix: "synaptic:cache:".to_string(),
20 ttl: None,
21 }
22 }
23}
24
25pub struct RedisCache {
32 backend: RedisBackend,
33 config: RedisCacheConfig,
34}
35
36impl RedisCache {
37 pub fn from_url(url: &str) -> Result<Self, SynapticError> {
39 Ok(Self {
40 backend: RedisBackend::standalone(url)?,
41 config: RedisCacheConfig::default(),
42 })
43 }
44
45 pub fn from_url_with_config(
47 url: &str,
48 config: RedisCacheConfig,
49 ) -> Result<Self, SynapticError> {
50 Ok(Self {
51 backend: RedisBackend::standalone(url)?,
52 config,
53 })
54 }
55
56 #[allow(dead_code)]
58 pub(crate) fn from_backend(backend: RedisBackend, config: RedisCacheConfig) -> Self {
59 Self { backend, config }
60 }
61
62 #[cfg(feature = "cluster")]
64 pub fn from_cluster_nodes(nodes: &[&str]) -> Result<Self, SynapticError> {
65 Ok(Self {
66 backend: RedisBackend::cluster(nodes)?,
67 config: RedisCacheConfig::default(),
68 })
69 }
70
71 #[cfg(feature = "cluster")]
73 pub fn from_cluster_nodes_with_config(
74 nodes: &[&str],
75 config: RedisCacheConfig,
76 ) -> Result<Self, SynapticError> {
77 Ok(Self {
78 backend: RedisBackend::cluster(nodes)?,
79 config,
80 })
81 }
82
83 fn redis_key(&self, key: &str) -> String {
85 format!("{}{key}", self.config.prefix)
86 }
87
88 async fn get_connection(&self) -> Result<RedisConn, SynapticError> {
89 self.backend.get_connection().await
90 }
91}
92
93async fn redis_get_string(con: &mut RedisConn, key: &str) -> Result<Option<String>, SynapticError> {
95 let raw: Option<String> = con
96 .get(key)
97 .await
98 .map_err(|e| SynapticError::Cache(format!("Redis GET error: {e}")))?;
99 Ok(raw)
100}
101
102#[async_trait]
103impl synaptic_core::LlmCache for RedisCache {
104 async fn get(&self, key: &str) -> Result<Option<ChatResponse>, SynapticError> {
105 let mut con = self.get_connection().await?;
106 let redis_key = self.redis_key(key);
107
108 let raw = redis_get_string(&mut con, &redis_key).await?;
109
110 match raw {
111 Some(json_str) => {
112 let response: ChatResponse = serde_json::from_str(&json_str)
113 .map_err(|e| SynapticError::Cache(format!("JSON deserialize error: {e}")))?;
114 Ok(Some(response))
115 }
116 None => Ok(None),
117 }
118 }
119
120 async fn put(&self, key: &str, response: &ChatResponse) -> Result<(), SynapticError> {
121 let mut con = self.get_connection().await?;
122 let redis_key = self.redis_key(key);
123
124 let json_str = serde_json::to_string(response)
125 .map_err(|e| SynapticError::Cache(format!("JSON serialize error: {e}")))?;
126
127 con.set::<_, _, ()>(&redis_key, &json_str)
128 .await
129 .map_err(|e| SynapticError::Cache(format!("Redis SET error: {e}")))?;
130
131 if let Some(ttl_secs) = self.config.ttl {
133 con.expire::<_, ()>(&redis_key, ttl_secs as i64)
134 .await
135 .map_err(|e| SynapticError::Cache(format!("Redis EXPIRE error: {e}")))?;
136 }
137
138 Ok(())
139 }
140
141 async fn clear(&self) -> Result<(), SynapticError> {
142 let mut con = self.get_connection().await?;
143 let pattern = format!("{}*", self.config.prefix);
144
145 let keys = collect_matching_keys(&mut con, &pattern).await?;
147
148 if !keys.is_empty() {
149 for chunk in keys.chunks(100) {
151 con.del::<_, ()>(chunk)
152 .await
153 .map_err(|e| SynapticError::Cache(format!("Redis DEL error: {e}")))?;
154 }
155 }
156
157 Ok(())
158 }
159}