Skip to main content

armature_cache/
config.rs

1//! Cache configuration types.
2
3use crate::error::CacheResult;
4use std::time::Duration;
5
6/// Cache backend type.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum CacheBackend {
9    /// Redis backend
10    Redis,
11    /// Memcached backend
12    Memcached,
13}
14
15/// Cache configuration.
16#[derive(Debug, Clone)]
17pub struct CacheConfig {
18    /// Cache backend type
19    pub backend: CacheBackend,
20
21    /// Connection URL
22    pub url: String,
23
24    /// Key prefix for all cache keys
25    pub key_prefix: Option<String>,
26
27    /// Default TTL for cache entries
28    pub default_ttl: Option<Duration>,
29
30    /// Connection timeout
31    pub connection_timeout: Duration,
32
33    /// Operation timeout
34    pub operation_timeout: Duration,
35
36    /// Maximum number of connections (for connection pools)
37    pub max_connections: usize,
38}
39
40impl CacheConfig {
41    /// Create a new Redis cache configuration.
42    ///
43    /// # Arguments
44    ///
45    /// * `url` - Redis connection URL (e.g., "redis://localhost:6379")
46    ///
47    /// # Examples
48    ///
49    /// ```
50    /// use armature_cache::CacheConfig;
51    ///
52    /// let config = CacheConfig::redis("redis://localhost:6379").unwrap();
53    /// ```
54    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    /// Create a new Memcached cache configuration.
67    ///
68    /// # Arguments
69    ///
70    /// * `url` - Memcached connection URL (e.g., "memcache://localhost:11211")
71    ///
72    /// # Examples
73    ///
74    /// ```
75    /// use armature_cache::CacheConfig;
76    ///
77    /// let config = CacheConfig::memcached("memcache://localhost:11211").unwrap();
78    /// ```
79    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    /// Set the key prefix.
92    pub fn with_key_prefix(mut self, prefix: impl Into<String>) -> Self {
93        self.key_prefix = Some(prefix.into());
94        self
95    }
96
97    /// Set the default TTL.
98    pub fn with_default_ttl(mut self, ttl: Duration) -> Self {
99        self.default_ttl = Some(ttl);
100        self
101    }
102
103    /// Set the connection timeout.
104    pub fn with_connection_timeout(mut self, timeout: Duration) -> Self {
105        self.connection_timeout = timeout;
106        self
107    }
108
109    /// Set the operation timeout.
110    pub fn with_operation_timeout(mut self, timeout: Duration) -> Self {
111        self.operation_timeout = timeout;
112        self
113    }
114
115    /// Set the maximum number of connections.
116    pub fn with_max_connections(mut self, max: usize) -> Self {
117        self.max_connections = max;
118        self
119    }
120
121    /// Build the final key with prefix if configured.
122    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        // Just verify they can be formatted
244        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}