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<()>;
24 async fn delete(&self, key: &str) -> AppResult<bool>;
26 async fn exists(&self, key: &str) -> AppResult<bool>;
28}
29
30#[async_trait::async_trait]
32pub trait CacheStoreFactory: Send + Sync {
33 async fn create(&self, config: &CacheConfig) -> AppResult<Arc<dyn CacheStore>>;
35}
36
37#[derive(Default)]
39pub struct CacheRegistry {
40 factories: BTreeMap<String, Arc<dyn CacheStoreFactory>>,
41}
42
43impl CacheRegistry {
44 #[must_use]
46 pub fn new() -> Self {
47 Self::default()
48 }
49
50 pub fn register(
52 &mut self,
53 name: impl Into<String>,
54 factory: Arc<dyn CacheStoreFactory>,
55 ) -> AppResult<()> {
56 let name = name.into().trim().to_owned();
57 if name.is_empty() {
58 return Err(AppError::new(
59 ErrorCode::InvalidInput,
60 "cache store name is required",
61 ));
62 }
63 if self.factories.contains_key(&name) {
64 return Err(AppError::new(
65 ErrorCode::AlreadyExists,
66 format!("cache store '{name}' is already registered"),
67 ));
68 }
69 self.factories.insert(name, factory);
70 Ok(())
71 }
72
73 #[must_use]
75 pub fn contains(&self, name: &str) -> bool {
76 self.factories.contains_key(name)
77 }
78
79 #[must_use]
81 pub fn len(&self) -> usize {
82 self.factories.len()
83 }
84
85 #[must_use]
87 pub fn is_empty(&self) -> bool {
88 self.factories.is_empty()
89 }
90
91 pub async fn build(&self, config: &CacheConfig) -> AppResult<Arc<dyn CacheStore>> {
93 let store = config.store.trim();
94 if store.is_empty() {
95 return Err(AppError::new(
96 ErrorCode::InvalidInput,
97 "cache store name is required",
98 ));
99 }
100 let factory = self.factories.get(store).ok_or_else(|| {
101 AppError::new(
102 ErrorCode::NotFound,
103 format!("cache store '{store}' is not registered"),
104 )
105 })?;
106 factory.create(config).await
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[derive(Default)]
115 struct DummyStore;
116
117 #[async_trait::async_trait]
118 impl CacheStore for DummyStore {
119 async fn get(&self, _key: &str) -> AppResult<Option<String>> {
120 Ok(None)
121 }
122
123 async fn set(&self, _key: &str, _val: &str, _ttl: Option<Duration>) -> AppResult<()> {
124 Ok(())
125 }
126
127 async fn delete(&self, _key: &str) -> AppResult<bool> {
128 Ok(false)
129 }
130
131 async fn exists(&self, _key: &str) -> AppResult<bool> {
132 Ok(false)
133 }
134 }
135
136 #[derive(Default)]
137 struct DummyFactory;
138
139 #[async_trait::async_trait]
140 impl CacheStoreFactory for DummyFactory {
141 async fn create(&self, _config: &CacheConfig) -> AppResult<Arc<dyn CacheStore>> {
142 Ok(Arc::new(DummyStore))
143 }
144 }
145
146 #[test]
147 fn register_rejects_empty_store_name() {
148 let mut registry = CacheRegistry::new();
149 let err = registry
150 .register(" \t ", Arc::new(DummyFactory))
151 .expect_err("empty store names must be rejected");
152 assert_eq!(err.code(), ErrorCode::InvalidInput);
153 }
154
155 #[test]
156 fn register_rejects_duplicate_store_name() {
157 let mut registry = CacheRegistry::new();
158 registry
159 .register("memory", Arc::new(DummyFactory))
160 .expect("first registration should succeed");
161
162 let err = registry
163 .register("memory", Arc::new(DummyFactory))
164 .expect_err("duplicate store names must be rejected");
165 assert_eq!(err.code(), ErrorCode::AlreadyExists);
166 }
167
168 #[tokio::test]
169 async fn build_rejects_empty_selected_store() {
170 let registry = CacheRegistry::new();
171 let config = CacheConfig {
172 store: " ".into(),
173 ..CacheConfig::default()
174 };
175
176 assert_eq!(
177 registry.build(&config).await.err().map(|err| err.code()),
178 Some(ErrorCode::InvalidInput)
179 );
180 }
181
182 #[tokio::test]
183 async fn build_rejects_unregistered_selected_store() {
184 let registry = CacheRegistry::new();
185 let config = CacheConfig {
186 store: "redis".into(),
187 ..CacheConfig::default()
188 };
189
190 assert_eq!(
191 registry.build(&config).await.err().map(|err| err.code()),
192 Some(ErrorCode::NotFound)
193 );
194 }
195
196 #[tokio::test]
197 async fn build_uses_registered_factory() {
198 let mut registry = CacheRegistry::new();
199 registry
200 .register("memory", Arc::new(DummyFactory))
201 .expect("registration should succeed");
202 assert!(registry.contains("memory"));
203 assert_eq!(registry.len(), 1);
204 assert!(!registry.is_empty());
205
206 let config = CacheConfig {
207 store: " memory ".into(),
208 ..CacheConfig::default()
209 };
210
211 let store = registry
212 .build(&config)
213 .await
214 .expect("registered store should build");
215 assert!(
216 store
217 .get("missing")
218 .await
219 .expect("get should succeed")
220 .is_none()
221 );
222 store.set("k", "v", None).await.expect("set should succeed");
223 assert!(!store.delete("k").await.expect("delete should succeed"));
224 assert!(
225 !store
226 .exists("missing")
227 .await
228 .expect("exists should succeed")
229 );
230 }
231}