1use std::collections::BTreeMap;
4use std::sync::Arc;
5use std::time::Duration;
6
7use rskit_errors::{AppError, AppResult, ErrorCode};
8
9use crate::config::CacheConfig;
10
11pub use crate::adapters::memory::register_memory;
12
13#[async_trait::async_trait]
15pub trait CacheStore: Send + Sync {
16 async fn get(&self, key: &str) -> AppResult<Option<String>>;
18 async fn set(&self, key: &str, val: &str, ttl: Option<Duration>) -> AppResult<()>;
23 async fn delete(&self, key: &str) -> AppResult<bool>;
25 async fn exists(&self, key: &str) -> AppResult<bool>;
27}
28
29#[async_trait::async_trait]
31pub trait CacheStoreFactory: Send + Sync {
32 async fn create(&self, config: &CacheConfig) -> AppResult<Arc<dyn CacheStore>>;
34}
35
36#[derive(Default)]
38pub struct CacheRegistry {
39 factories: BTreeMap<String, Arc<dyn CacheStoreFactory>>,
40}
41
42impl CacheRegistry {
43 #[must_use]
45 pub fn new() -> Self {
46 Self::default()
47 }
48
49 pub fn register(
51 &mut self,
52 name: impl Into<String>,
53 factory: Arc<dyn CacheStoreFactory>,
54 ) -> AppResult<()> {
55 let name = name.into().trim().to_owned();
56 if name.is_empty() {
57 return Err(AppError::new(
58 ErrorCode::InvalidInput,
59 "cache store name is required",
60 ));
61 }
62 if self.factories.contains_key(&name) {
63 return Err(AppError::new(
64 ErrorCode::AlreadyExists,
65 format!("cache store '{name}' is already registered"),
66 ));
67 }
68 self.factories.insert(name, factory);
69 Ok(())
70 }
71
72 #[must_use]
74 pub fn contains(&self, name: &str) -> bool {
75 self.factories.contains_key(name)
76 }
77
78 #[must_use]
80 pub fn len(&self) -> usize {
81 self.factories.len()
82 }
83
84 #[must_use]
86 pub fn is_empty(&self) -> bool {
87 self.factories.is_empty()
88 }
89
90 pub async fn build(&self, config: &CacheConfig) -> AppResult<Arc<dyn CacheStore>> {
92 let store = config.store.trim();
93 if store.is_empty() {
94 return Err(AppError::new(
95 ErrorCode::InvalidInput,
96 "cache store name is required",
97 ));
98 }
99 let factory = self.factories.get(store).ok_or_else(|| {
100 AppError::new(
101 ErrorCode::NotFound,
102 format!("cache store '{store}' is not registered"),
103 )
104 })?;
105 factory.create(config).await
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[derive(Default)]
114 struct DummyFactory;
115
116 #[async_trait::async_trait]
117 impl CacheStoreFactory for DummyFactory {
118 async fn create(&self, _config: &CacheConfig) -> AppResult<Arc<dyn CacheStore>> {
119 Err(AppError::new(ErrorCode::Internal, "not used"))
120 }
121 }
122
123 #[test]
124 fn register_rejects_empty_store_name() {
125 let mut registry = CacheRegistry::new();
126 let err = registry
127 .register(" \t ", Arc::new(DummyFactory))
128 .expect_err("empty store names must be rejected");
129 assert_eq!(err.code(), ErrorCode::InvalidInput);
130 }
131
132 #[test]
133 fn register_rejects_duplicate_store_name() {
134 let mut registry = CacheRegistry::new();
135 registry
136 .register("memory", Arc::new(DummyFactory))
137 .expect("first registration should succeed");
138
139 let err = registry
140 .register("memory", Arc::new(DummyFactory))
141 .expect_err("duplicate store names must be rejected");
142 assert_eq!(err.code(), ErrorCode::AlreadyExists);
143 }
144
145 #[tokio::test]
146 async fn build_rejects_empty_selected_store() {
147 let registry = CacheRegistry::new();
148 let config = CacheConfig {
149 store: " ".into(),
150 ..CacheConfig::default()
151 };
152
153 let Err(err) = registry.build(&config).await else {
154 panic!("empty selected stores must be rejected");
155 };
156 assert_eq!(err.code(), ErrorCode::InvalidInput);
157 }
158
159 #[tokio::test]
160 async fn build_rejects_unregistered_selected_store() {
161 let registry = CacheRegistry::new();
162 let config = CacheConfig {
163 store: "redis".into(),
164 ..CacheConfig::default()
165 };
166
167 let Err(err) = registry.build(&config).await else {
168 panic!("unregistered stores must be rejected");
169 };
170 assert_eq!(err.code(), ErrorCode::NotFound);
171 }
172}