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 DummyStore;
115
116 #[async_trait::async_trait]
117 impl CacheStore for DummyStore {
118 async fn get(&self, _key: &str) -> AppResult<Option<String>> {
119 Ok(None)
120 }
121
122 async fn set(&self, _key: &str, _val: &str, _ttl: Option<Duration>) -> AppResult<()> {
123 Ok(())
124 }
125
126 async fn delete(&self, _key: &str) -> AppResult<bool> {
127 Ok(false)
128 }
129
130 async fn exists(&self, _key: &str) -> AppResult<bool> {
131 Ok(false)
132 }
133 }
134
135 #[derive(Default)]
136 struct DummyFactory;
137
138 #[async_trait::async_trait]
139 impl CacheStoreFactory for DummyFactory {
140 async fn create(&self, _config: &CacheConfig) -> AppResult<Arc<dyn CacheStore>> {
141 Ok(Arc::new(DummyStore))
142 }
143 }
144
145 #[test]
146 fn register_rejects_empty_store_name() {
147 let mut registry = CacheRegistry::new();
148 let err = registry
149 .register(" \t ", Arc::new(DummyFactory))
150 .expect_err("empty store names must be rejected");
151 assert_eq!(err.code(), ErrorCode::InvalidInput);
152 }
153
154 #[test]
155 fn register_rejects_duplicate_store_name() {
156 let mut registry = CacheRegistry::new();
157 registry
158 .register("memory", Arc::new(DummyFactory))
159 .expect("first registration should succeed");
160
161 let err = registry
162 .register("memory", Arc::new(DummyFactory))
163 .expect_err("duplicate store names must be rejected");
164 assert_eq!(err.code(), ErrorCode::AlreadyExists);
165 }
166
167 #[tokio::test]
168 async fn build_rejects_empty_selected_store() {
169 let registry = CacheRegistry::new();
170 let config = CacheConfig {
171 store: " ".into(),
172 ..CacheConfig::default()
173 };
174
175 assert_eq!(
176 registry.build(&config).await.err().map(|err| err.code()),
177 Some(ErrorCode::InvalidInput)
178 );
179 }
180
181 #[tokio::test]
182 async fn build_rejects_unregistered_selected_store() {
183 let registry = CacheRegistry::new();
184 let config = CacheConfig {
185 store: "redis".into(),
186 ..CacheConfig::default()
187 };
188
189 assert_eq!(
190 registry.build(&config).await.err().map(|err| err.code()),
191 Some(ErrorCode::NotFound)
192 );
193 }
194
195 #[tokio::test]
196 async fn build_uses_registered_factory() {
197 let mut registry = CacheRegistry::new();
198 registry
199 .register("memory", Arc::new(DummyFactory))
200 .expect("registration should succeed");
201 assert!(registry.contains("memory"));
202 assert_eq!(registry.len(), 1);
203 assert!(!registry.is_empty());
204
205 let config = CacheConfig {
206 store: " memory ".into(),
207 ..CacheConfig::default()
208 };
209
210 let store = registry
211 .build(&config)
212 .await
213 .expect("registered store should build");
214 assert!(
215 store
216 .get("missing")
217 .await
218 .expect("get should succeed")
219 .is_none()
220 );
221 store.set("k", "v", None).await.expect("set should succeed");
222 assert!(!store.delete("k").await.expect("delete should succeed"));
223 assert!(
224 !store
225 .exists("missing")
226 .await
227 .expect("exists should succeed")
228 );
229 }
230}