mockforge_registry_server/
cache.rs1use anyhow::Result;
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9
10use crate::redis::RedisPool;
11
12pub mod keys {
14 pub const ORG: &str = "cache:org:";
15 pub const USER: &str = "cache:user:";
16 pub const ORG_SETTING: &str = "cache:org_setting:";
17 pub const USER_SETTING: &str = "cache:user_setting:";
18 pub const SUBSCRIPTION: &str = "cache:subscription:";
19 pub const PLUGIN: &str = "cache:plugin:";
20 pub const TEMPLATE: &str = "cache:template:";
21 pub const SCENARIO: &str = "cache:scenario:";
22 pub const ORG_MEMBERS: &str = "cache:org_members:";
23}
24
25pub mod ttl {
27
28 pub const SHORT: u64 = 60;
30
31 pub const MEDIUM: u64 = 300;
33
34 pub const LONG: u64 = 900;
36
37 pub const VERY_LONG: u64 = 3600;
39
40 pub const ORG: u64 = MEDIUM;
42
43 pub const USER: u64 = MEDIUM;
45
46 pub const SETTINGS: u64 = LONG;
48
49 pub const SUBSCRIPTION: u64 = MEDIUM;
51
52 pub const MARKETPLACE: u64 = LONG;
54
55 pub const ORG_MEMBERS: u64 = MEDIUM;
57}
58
59pub struct Cache {
61 redis: RedisPool,
62}
63
64impl Cache {
65 pub fn new(redis: RedisPool) -> Self {
67 Self { redis }
68 }
69
70 pub async fn get<T>(&self, key: &str) -> Result<Option<T>>
72 where
73 T: for<'de> Deserialize<'de>,
74 {
75 match self.redis.get(key).await? {
76 Some(value) => {
77 let deserialized: T = serde_json::from_str(&value)?;
78 Ok(Some(deserialized))
79 }
80 None => Ok(None),
81 }
82 }
83
84 pub async fn set<T>(&self, key: &str, value: &T, ttl: u64) -> Result<()>
86 where
87 T: Serialize,
88 {
89 let serialized = serde_json::to_string(value)?;
90 self.redis.set_with_expiry(key, &serialized, ttl).await?;
91 Ok(())
92 }
93
94 pub async fn delete(&self, key: &str) -> Result<()> {
96 self.redis.delete(key).await?;
97 Ok(())
98 }
99
100 pub async fn delete_pattern(&self, pattern: &str) -> Result<()> {
102 let keys = self.redis.scan_keys(pattern).await?;
103 for key in keys {
104 if let Err(e) = self.redis.delete(&key).await {
105 tracing::warn!("Failed to delete cache key {}: {}", key, e);
106 }
107 }
108 Ok(())
109 }
110
111 pub async fn invalidate_org(&self, org_id: &Uuid) -> Result<()> {
113 if let Err(e) = self.delete(&format!("{}:{}", keys::ORG, org_id)).await {
114 tracing::warn!("Failed to invalidate org cache for {}: {}", org_id, e);
115 }
116 if let Err(e) = self.delete(&format!("{}:{}", keys::ORG_MEMBERS, org_id)).await {
117 tracing::warn!("Failed to invalidate org members cache for {}: {}", org_id, e);
118 }
119 if let Err(e) = self.delete_pattern(&format!("{}:{}:*", keys::ORG_SETTING, org_id)).await {
120 tracing::warn!("Failed to invalidate org settings cache for {}: {}", org_id, e);
121 }
122 Ok(())
123 }
124
125 pub async fn invalidate_user(&self, user_id: &Uuid) -> Result<()> {
127 if let Err(e) = self.delete(&format!("{}:{}", keys::USER, user_id)).await {
128 tracing::warn!("Failed to invalidate user cache for {}: {}", user_id, e);
129 }
130 if let Err(e) = self.delete_pattern(&format!("{}:{}:*", keys::USER_SETTING, user_id)).await
131 {
132 tracing::warn!("Failed to invalidate user settings cache for {}: {}", user_id, e);
133 }
134 Ok(())
135 }
136
137 pub async fn invalidate_subscription(&self, org_id: &Uuid) -> Result<()> {
139 let key = format!("{}:{}", keys::SUBSCRIPTION, org_id);
140 self.delete(&key).await?;
141 Ok(())
142 }
143
144 pub async fn invalidate_marketplace(
146 &self,
147 content_type: &str,
148 content_id: &Uuid,
149 ) -> Result<()> {
150 let key = match content_type {
151 "plugin" => format!("{}:{}", keys::PLUGIN, content_id),
152 "template" => format!("{}:{}", keys::TEMPLATE, content_id),
153 "scenario" => format!("{}:{}", keys::SCENARIO, content_id),
154 _ => return Ok(()),
155 };
156 self.delete(&key).await?;
157 Ok(())
158 }
159
160 pub async fn get_or_set<F, Fut, T>(&self, key: &str, ttl: u64, f: F) -> Result<T>
162 where
163 F: FnOnce() -> Fut,
164 Fut: std::future::Future<Output = Result<T>>,
165 T: Serialize + for<'de> Deserialize<'de>,
166 {
167 if let Some(cached) = self.get::<T>(key).await? {
169 return Ok(cached);
170 }
171
172 let value = f().await?;
174
175 if let Err(e) = self.set(key, &value, ttl).await {
177 tracing::warn!("Failed to cache value for key {}: {}", key, e);
178 }
179
180 Ok(value)
181 }
182}
183
184pub fn org_cache_key(org_id: &Uuid) -> String {
186 format!("{}:{}", keys::ORG, org_id)
187}
188
189pub fn user_cache_key(user_id: &Uuid) -> String {
191 format!("{}:{}", keys::USER, user_id)
192}
193
194pub fn org_setting_cache_key(org_id: &Uuid, setting_key: &str) -> String {
196 format!("{}:{}:{}", keys::ORG_SETTING, org_id, setting_key)
197}
198
199pub fn user_setting_cache_key(user_id: &Uuid, setting_key: &str) -> String {
201 format!("{}:{}:{}", keys::USER_SETTING, user_id, setting_key)
202}
203
204pub fn subscription_cache_key(org_id: &Uuid) -> String {
206 format!("{}:{}", keys::SUBSCRIPTION, org_id)
207}
208
209pub fn org_members_cache_key(org_id: &Uuid) -> String {
211 format!("{}:{}", keys::ORG_MEMBERS, org_id)
212}
213
214pub fn plugin_cache_key(plugin_id: &Uuid) -> String {
216 format!("{}:{}", keys::PLUGIN, plugin_id)
217}
218
219pub fn template_cache_key(template_id: &Uuid) -> String {
221 format!("{}:{}", keys::TEMPLATE, template_id)
222}
223
224pub fn scenario_cache_key(scenario_id: &Uuid) -> String {
226 format!("{}:{}", keys::SCENARIO, scenario_id)
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 #[test]
235 fn test_key_constants() {
236 assert_eq!(keys::ORG, "cache:org:");
237 assert_eq!(keys::USER, "cache:user:");
238 assert_eq!(keys::ORG_SETTING, "cache:org_setting:");
239 assert_eq!(keys::USER_SETTING, "cache:user_setting:");
240 assert_eq!(keys::SUBSCRIPTION, "cache:subscription:");
241 assert_eq!(keys::PLUGIN, "cache:plugin:");
242 assert_eq!(keys::TEMPLATE, "cache:template:");
243 assert_eq!(keys::SCENARIO, "cache:scenario:");
244 assert_eq!(keys::ORG_MEMBERS, "cache:org_members:");
245 }
246
247 #[test]
249 fn test_ttl_short() {
250 assert_eq!(ttl::SHORT, 60);
251 }
252
253 #[test]
254 fn test_ttl_medium() {
255 assert_eq!(ttl::MEDIUM, 300);
256 }
257
258 #[test]
259 fn test_ttl_long() {
260 assert_eq!(ttl::LONG, 900);
261 }
262
263 #[test]
264 fn test_ttl_very_long() {
265 assert_eq!(ttl::VERY_LONG, 3600);
266 }
267
268 #[test]
269 fn test_ttl_org() {
270 assert_eq!(ttl::ORG, ttl::MEDIUM);
271 }
272
273 #[test]
274 fn test_ttl_user() {
275 assert_eq!(ttl::USER, ttl::MEDIUM);
276 }
277
278 #[test]
279 fn test_ttl_settings() {
280 assert_eq!(ttl::SETTINGS, ttl::LONG);
281 }
282
283 #[test]
284 fn test_ttl_subscription() {
285 assert_eq!(ttl::SUBSCRIPTION, ttl::MEDIUM);
286 }
287
288 #[test]
289 fn test_ttl_marketplace() {
290 assert_eq!(ttl::MARKETPLACE, ttl::LONG);
291 }
292
293 #[test]
294 fn test_ttl_org_members() {
295 assert_eq!(ttl::ORG_MEMBERS, ttl::MEDIUM);
296 }
297
298 #[test]
300 fn test_org_cache_key() {
301 let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
302 let key = org_cache_key(&id);
303 assert_eq!(key, "cache:org::550e8400-e29b-41d4-a716-446655440000");
304 }
305
306 #[test]
307 fn test_user_cache_key() {
308 let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440001").unwrap();
309 let key = user_cache_key(&id);
310 assert_eq!(key, "cache:user::550e8400-e29b-41d4-a716-446655440001");
311 }
312
313 #[test]
314 fn test_org_setting_cache_key() {
315 let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440002").unwrap();
316 let key = org_setting_cache_key(&id, "theme");
317 assert_eq!(key, "cache:org_setting::550e8400-e29b-41d4-a716-446655440002:theme");
318 }
319
320 #[test]
321 fn test_user_setting_cache_key() {
322 let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440003").unwrap();
323 let key = user_setting_cache_key(&id, "notifications");
324 assert_eq!(key, "cache:user_setting::550e8400-e29b-41d4-a716-446655440003:notifications");
325 }
326
327 #[test]
328 fn test_subscription_cache_key() {
329 let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440004").unwrap();
330 let key = subscription_cache_key(&id);
331 assert_eq!(key, "cache:subscription::550e8400-e29b-41d4-a716-446655440004");
332 }
333
334 #[test]
335 fn test_org_members_cache_key() {
336 let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440005").unwrap();
337 let key = org_members_cache_key(&id);
338 assert_eq!(key, "cache:org_members::550e8400-e29b-41d4-a716-446655440005");
339 }
340
341 #[test]
342 fn test_plugin_cache_key() {
343 let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440006").unwrap();
344 let key = plugin_cache_key(&id);
345 assert_eq!(key, "cache:plugin::550e8400-e29b-41d4-a716-446655440006");
346 }
347
348 #[test]
349 fn test_template_cache_key() {
350 let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440007").unwrap();
351 let key = template_cache_key(&id);
352 assert_eq!(key, "cache:template::550e8400-e29b-41d4-a716-446655440007");
353 }
354
355 #[test]
356 fn test_scenario_cache_key() {
357 let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440008").unwrap();
358 let key = scenario_cache_key(&id);
359 assert_eq!(key, "cache:scenario::550e8400-e29b-41d4-a716-446655440008");
360 }
361
362 #[test]
364 fn test_cache_keys_are_unique() {
365 let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
366
367 let org_key = org_cache_key(&id);
368 let user_key = user_cache_key(&id);
369 let plugin_key = plugin_cache_key(&id);
370 let template_key = template_cache_key(&id);
371 let scenario_key = scenario_cache_key(&id);
372 let subscription_key = subscription_cache_key(&id);
373
374 assert_ne!(org_key, user_key);
376 assert_ne!(org_key, plugin_key);
377 assert_ne!(plugin_key, template_key);
378 assert_ne!(template_key, scenario_key);
379 assert_ne!(subscription_key, org_key);
380 }
381
382 #[test]
384 fn test_setting_keys_with_different_settings() {
385 let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
386
387 let theme_key = org_setting_cache_key(&id, "theme");
388 let lang_key = org_setting_cache_key(&id, "language");
389
390 assert_ne!(theme_key, lang_key);
391 assert!(theme_key.contains("theme"));
392 assert!(lang_key.contains("language"));
393 }
394}