1use crate::error::CacheResult;
4use std::time::Duration;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum CacheBackend {
9 Redis,
11 Memcached,
13}
14
15#[derive(Debug, Clone)]
17pub struct CacheConfig {
18 pub backend: CacheBackend,
20
21 pub url: String,
23
24 pub key_prefix: Option<String>,
26
27 pub default_ttl: Option<Duration>,
29
30 pub connection_timeout: Duration,
32
33 pub operation_timeout: Duration,
35
36 pub max_connections: usize,
38}
39
40impl CacheConfig {
41 pub fn redis(url: impl Into<String>) -> CacheResult<Self> {
55 Ok(Self {
56 backend: CacheBackend::Redis,
57 url: url.into(),
58 key_prefix: None,
59 default_ttl: None,
60 connection_timeout: Duration::from_secs(5),
61 operation_timeout: Duration::from_secs(3),
62 max_connections: 10,
63 })
64 }
65
66 pub fn memcached(url: impl Into<String>) -> CacheResult<Self> {
80 Ok(Self {
81 backend: CacheBackend::Memcached,
82 url: url.into(),
83 key_prefix: None,
84 default_ttl: None,
85 connection_timeout: Duration::from_secs(5),
86 operation_timeout: Duration::from_secs(3),
87 max_connections: 10,
88 })
89 }
90
91 pub fn with_key_prefix(mut self, prefix: impl Into<String>) -> Self {
93 self.key_prefix = Some(prefix.into());
94 self
95 }
96
97 pub fn with_default_ttl(mut self, ttl: Duration) -> Self {
99 self.default_ttl = Some(ttl);
100 self
101 }
102
103 pub fn with_connection_timeout(mut self, timeout: Duration) -> Self {
105 self.connection_timeout = timeout;
106 self
107 }
108
109 pub fn with_operation_timeout(mut self, timeout: Duration) -> Self {
111 self.operation_timeout = timeout;
112 self
113 }
114
115 pub fn with_max_connections(mut self, max: usize) -> Self {
117 self.max_connections = max;
118 self
119 }
120
121 pub fn build_key(&self, key: &str) -> String {
123 match &self.key_prefix {
124 Some(prefix) => format!("{}:{}", prefix, key),
125 None => key.to_string(),
126 }
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn test_redis_config() {
136 let config = CacheConfig::redis("redis://localhost:6379").unwrap();
137 assert_eq!(config.backend, CacheBackend::Redis);
138 assert_eq!(config.url, "redis://localhost:6379");
139 }
140
141 #[test]
142 fn test_memcached_config() {
143 let config = CacheConfig::memcached("memcache://localhost:11211").unwrap();
144 assert_eq!(config.backend, CacheBackend::Memcached);
145 assert_eq!(config.url, "memcache://localhost:11211");
146 }
147
148 #[test]
149 fn test_config_builder() {
150 let config = CacheConfig::redis("redis://localhost:6379")
151 .unwrap()
152 .with_key_prefix("app")
153 .with_default_ttl(Duration::from_secs(300))
154 .with_max_connections(20);
155
156 assert_eq!(config.key_prefix, Some("app".to_string()));
157 assert_eq!(config.default_ttl, Some(Duration::from_secs(300)));
158 assert_eq!(config.max_connections, 20);
159 }
160
161 #[test]
162 fn test_build_key_with_prefix() {
163 let config = CacheConfig::redis("redis://localhost:6379")
164 .unwrap()
165 .with_key_prefix("myapp");
166
167 assert_eq!(config.build_key("user:123"), "myapp:user:123");
168 }
169
170 #[test]
171 fn test_build_key_without_prefix() {
172 let config = CacheConfig::redis("redis://localhost:6379").unwrap();
173 assert_eq!(config.build_key("user:123"), "user:123");
174 }
175
176 #[test]
177 fn test_redis_config_with_auth() {
178 let config = CacheConfig::redis("redis://:password@localhost:6379/1").unwrap();
179 assert_eq!(config.backend, CacheBackend::Redis);
180 assert!(config.url.contains("password"));
181 }
182
183 #[test]
184 fn test_memcached_config_with_multiple_servers() {
185 let config = CacheConfig::memcached("memcache://server1:11211,server2:11211").unwrap();
186 assert_eq!(config.backend, CacheBackend::Memcached);
187 }
188
189 #[test]
190 fn test_config_default_values() {
191 let config = CacheConfig::redis("redis://localhost:6379").unwrap();
192 assert_eq!(config.max_connections, 10);
193 assert_eq!(config.key_prefix, None);
194 assert_eq!(config.default_ttl, None);
195 }
196
197 #[test]
198 fn test_config_with_custom_max_connections() {
199 let config = CacheConfig::redis("redis://localhost:6379")
200 .unwrap()
201 .with_max_connections(50);
202 assert_eq!(config.max_connections, 50);
203 }
204
205 #[test]
206 fn test_config_with_ttl_zero() {
207 let config = CacheConfig::redis("redis://localhost:6379")
208 .unwrap()
209 .with_default_ttl(Duration::from_secs(0));
210 assert_eq!(config.default_ttl, Some(Duration::from_secs(0)));
211 }
212
213 #[test]
214 fn test_config_with_long_ttl() {
215 let one_day = Duration::from_secs(86400);
216 let config = CacheConfig::redis("redis://localhost:6379")
217 .unwrap()
218 .with_default_ttl(one_day);
219 assert_eq!(config.default_ttl, Some(one_day));
220 }
221
222 #[test]
223 fn test_build_key_with_empty_key() {
224 let config = CacheConfig::redis("redis://localhost:6379")
225 .unwrap()
226 .with_key_prefix("app");
227 assert_eq!(config.build_key(""), "app:");
228 }
229
230 #[test]
231 fn test_build_key_with_special_characters() {
232 let config = CacheConfig::redis("redis://localhost:6379")
233 .unwrap()
234 .with_key_prefix("app");
235 assert_eq!(config.build_key("user:123:token"), "app:user:123:token");
236 }
237
238 #[test]
239 fn test_config_backend_display() {
240 let redis = CacheBackend::Redis;
241 let memcached = CacheBackend::Memcached;
242
243 let _ = format!("{:?}", redis);
245 let _ = format!("{:?}", memcached);
246 }
247
248 #[test]
249 fn test_config_chaining() {
250 let config = CacheConfig::redis("redis://localhost:6379")
251 .unwrap()
252 .with_key_prefix("test")
253 .with_default_ttl(Duration::from_secs(100))
254 .with_max_connections(15);
255
256 assert_eq!(config.key_prefix, Some("test".to_string()));
257 assert_eq!(config.default_ttl, Some(Duration::from_secs(100)));
258 assert_eq!(config.max_connections, 15);
259 }
260
261 #[test]
262 fn test_config_clone() {
263 let config1 = CacheConfig::redis("redis://localhost:6379")
264 .unwrap()
265 .with_key_prefix("app");
266 let config2 = config1.clone();
267
268 assert_eq!(config1.url, config2.url);
269 assert_eq!(config1.key_prefix, config2.key_prefix);
270 }
271
272 #[test]
273 fn test_redis_config_with_db_number() {
274 let config = CacheConfig::redis("redis://localhost:6379/5").unwrap();
275 assert!(config.url.ends_with("/5"));
276 }
277
278 #[test]
279 fn test_build_key_consistency() {
280 let config = CacheConfig::redis("redis://localhost:6379")
281 .unwrap()
282 .with_key_prefix("app");
283
284 let key1 = config.build_key("test");
285 let key2 = config.build_key("test");
286 assert_eq!(key1, key2);
287 }
288}