1#[cfg(feature = "cache-memory")]
6pub mod memory;
7
8#[cfg(feature = "cache-redis")]
9pub mod redis;
10
11use std::time::Duration;
12
13#[async_trait::async_trait]
15pub trait Cache: Send + Sync {
16 async fn get(&self, key: &str) -> Option<String>;
18
19 async fn set(
25 &self,
26 key: String,
27 value: String,
28 ttl: Option<Duration>,
29 ) -> crate::error::Result<()>;
30
31 async fn delete(&self, key: &str) -> crate::error::Result<()>;
37
38 async fn clear(&self) -> crate::error::Result<()>;
44
45 async fn exists(&self, key: &str) -> bool;
47}
48
49#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
51pub struct CacheConfig {
52 pub cache_type: String,
54
55 pub memory_size: Option<usize>,
57
58 pub redis_url: Option<String>,
60
61 #[serde(default = "default_key_prefix")]
63 pub key_prefix: String,
64
65 pub default_ttl: Option<u64>,
67
68 #[serde(default = "default_crate_docs_ttl")]
70 pub crate_docs_ttl_secs: Option<u64>,
71
72 #[serde(default = "default_item_docs_ttl")]
74 pub item_docs_ttl_secs: Option<u64>,
75
76 #[serde(default = "default_search_results_ttl")]
78 pub search_results_ttl_secs: Option<u64>,
79}
80
81#[must_use]
83pub fn default_crate_docs_ttl() -> Option<u64> {
84 Some(3600)
85}
86
87#[must_use]
89pub fn default_item_docs_ttl() -> Option<u64> {
90 Some(1800)
91}
92
93#[must_use]
95pub fn default_search_results_ttl() -> Option<u64> {
96 Some(300)
97}
98
99#[must_use]
101pub fn default_key_prefix() -> String {
102 String::new()
103}
104
105impl Default for CacheConfig {
106 fn default() -> Self {
107 Self {
108 cache_type: "memory".to_string(),
109 memory_size: Some(1000),
110 redis_url: None,
111 key_prefix: String::new(),
112 default_ttl: Some(3600), crate_docs_ttl_secs: default_crate_docs_ttl(),
114 item_docs_ttl_secs: default_item_docs_ttl(),
115 search_results_ttl_secs: default_search_results_ttl(),
116 }
117 }
118}
119
120pub fn create_cache(config: &CacheConfig) -> Result<Box<dyn Cache>, crate::error::Error> {
126 match config.cache_type.as_str() {
127 "memory" => {
128 #[cfg(feature = "cache-memory")]
129 {
130 let size = config.memory_size.unwrap_or(1000);
131 Ok(Box::new(memory::MemoryCache::new(size)))
132 }
133 #[cfg(not(feature = "cache-memory"))]
134 {
135 Err(crate::error::Error::Config(
136 "memory cache feature is not enabled".to_string(),
137 ))
138 }
139 }
140 "redis" => {
141 #[cfg(feature = "cache-redis")]
142 {
143 Err(crate::error::Error::Config(
146 "Redis cache requires async initialization. Use create_cache_async instead."
147 .to_string(),
148 ))
149 }
150 #[cfg(not(feature = "cache-redis"))]
151 {
152 Err(crate::error::Error::Config(
153 "redis cache feature is not enabled".to_string(),
154 ))
155 }
156 }
157 _ => Err(crate::error::Error::Config(format!(
158 "unsupported cache type: {}",
159 config.cache_type
160 ))),
161 }
162}
163
164#[cfg(feature = "cache-redis")]
170pub async fn create_cache_async(
171 config: &CacheConfig,
172) -> Result<Box<dyn Cache>, crate::error::Error> {
173 match config.cache_type.as_str() {
174 "memory" => {
175 let size = config.memory_size.unwrap_or(1000);
176 Ok(Box::new(memory::MemoryCache::new(size)))
177 }
178 "redis" => {
179 let url = config
180 .redis_url
181 .as_ref()
182 .ok_or_else(|| crate::error::Error::Config("redis_url is required".to_string()))?;
183 Ok(Box::new(
184 redis::RedisCache::new(url, config.key_prefix.clone()).await?,
185 ))
186 }
187 _ => Err(crate::error::Error::Config(format!(
188 "unsupported cache type: {}",
189 config.cache_type
190 ))),
191 }
192}