1#![deny(missing_docs)]
2
3use nidus_core::NidusError;
9use thiserror::Error;
10
11pub type Result<T> = std::result::Result<T, CacheError>;
13
14#[derive(Debug, Error)]
16pub enum CacheError {
17 #[error(transparent)]
19 Nidus(#[from] NidusError),
20}
21
22#[derive(Clone, Debug, Eq, PartialEq)]
24pub struct CacheConfig {
25 namespace: Option<String>,
26 time_to_live: Option<std::time::Duration>,
27 max_capacity: Option<u64>,
28}
29
30impl CacheConfig {
31 pub fn new() -> Self {
33 Self {
34 namespace: None,
35 time_to_live: None,
36 max_capacity: None,
37 }
38 }
39
40 pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
42 self.namespace = Some(namespace.into());
43 self
44 }
45
46 pub fn time_to_live(mut self, time_to_live: std::time::Duration) -> Self {
48 self.time_to_live = Some(time_to_live);
49 self
50 }
51
52 pub fn max_capacity(mut self, max_capacity: u64) -> Self {
54 self.max_capacity = Some(max_capacity);
55 self
56 }
57
58 pub fn namespace_value(&self) -> Option<&str> {
60 self.namespace.as_deref()
61 }
62
63 pub fn time_to_live_value(&self) -> Option<std::time::Duration> {
65 self.time_to_live
66 }
67
68 pub fn max_capacity_value(&self) -> Option<u64> {
70 self.max_capacity
71 }
72}
73
74impl Default for CacheConfig {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80#[derive(Clone, Debug, Eq, PartialEq)]
82pub struct CacheKey(String);
83
84impl CacheKey {
85 pub fn new(namespace: Option<&str>, key: impl AsRef<str>) -> Self {
87 match namespace {
88 Some(namespace) if !namespace.is_empty() => Self::namespaced(namespace, key.as_ref()),
89 _ => Self(key.as_ref().to_owned()),
90 }
91 }
92
93 fn namespaced(namespace: &str, key: &str) -> Self {
94 let mut backend_key = String::with_capacity(namespace.len() + 1 + key.len());
95 backend_key.push_str(namespace);
96 backend_key.push(':');
97 backend_key.push_str(key);
98 Self(backend_key)
99 }
100
101 pub fn as_str(&self) -> &str {
103 &self.0
104 }
105
106 pub fn into_string(self) -> String {
108 self.0
109 }
110}
111
112#[cfg(feature = "moka")]
113mod moka_backend {
114 #[cfg(feature = "observability")]
115 use std::time::Instant;
116
117 use nidus_core::{Container, ProviderRegistrant, Result as NidusResult};
118
119 use super::{CacheConfig, CacheKey, Result};
120
121 #[derive(Clone, Debug, Default)]
123 pub struct MokaCacheBuilder {
124 config: CacheConfig,
125 #[cfg(feature = "observability")]
126 observer: Option<nidus_observability::ObservabilityAdapterObserver>,
127 }
128
129 impl MokaCacheBuilder {
130 pub fn new() -> Self {
132 Self::default()
133 }
134
135 pub fn config(mut self, config: CacheConfig) -> Self {
137 self.config = config;
138 self
139 }
140
141 pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
143 self.config = self.config.namespace(namespace);
144 self
145 }
146
147 pub fn time_to_live(mut self, time_to_live: std::time::Duration) -> Self {
149 self.config = self.config.time_to_live(time_to_live);
150 self
151 }
152
153 pub fn max_capacity(mut self, max_capacity: u64) -> Self {
155 self.config = self.config.max_capacity(max_capacity);
156 self
157 }
158
159 #[cfg(feature = "observability")]
161 pub fn observability(
162 mut self,
163 observer: nidus_observability::ObservabilityAdapterObserver,
164 ) -> Self {
165 self.observer = Some(observer);
166 self
167 }
168
169 pub fn build(self) -> MokaCacheProvider {
171 let mut builder = moka::future::Cache::builder();
172 if let Some(time_to_live) = self.config.time_to_live {
173 builder = builder.time_to_live(time_to_live);
174 }
175 if let Some(max_capacity) = self.config.max_capacity {
176 builder = builder.max_capacity(max_capacity);
177 }
178 MokaCacheProvider {
179 namespace: self.config.namespace,
180 cache: builder.build(),
181 #[cfg(feature = "observability")]
182 observer: self.observer,
183 }
184 }
185
186 pub fn register(self, container: &mut Container) -> Result<()> {
188 container.register_singleton(self.build())?;
189 Ok(())
190 }
191 }
192
193 #[derive(Clone, Debug)]
195 pub struct MokaCacheProvider {
196 namespace: Option<String>,
197 cache: moka::future::Cache<String, Vec<u8>>,
198 #[cfg(feature = "observability")]
199 observer: Option<nidus_observability::ObservabilityAdapterObserver>,
200 }
201
202 impl MokaCacheProvider {
203 pub fn builder() -> MokaCacheBuilder {
205 MokaCacheBuilder::new()
206 }
207
208 pub fn from_cache(
210 cache: moka::future::Cache<String, Vec<u8>>,
211 namespace: Option<String>,
212 ) -> Self {
213 Self {
214 namespace,
215 cache,
216 #[cfg(feature = "observability")]
217 observer: None,
218 }
219 }
220
221 pub async fn insert(&self, key: impl AsRef<str>, value: Vec<u8>) {
223 #[cfg(feature = "observability")]
224 let started_at = Instant::now();
225 self.cache
226 .insert(self.cache_key(key).into_string(), value)
227 .await;
228 #[cfg(feature = "observability")]
229 self.record(
230 "insert",
231 nidus_observability::OperationStatus::Success,
232 started_at,
233 );
234 }
235
236 pub async fn get(&self, key: impl AsRef<str>) -> Option<Vec<u8>> {
238 #[cfg(feature = "observability")]
239 let started_at = Instant::now();
240 let key = key.as_ref();
241 let result = match self.namespace.as_deref() {
242 Some(namespace) if !namespace.is_empty() => {
243 let key = CacheKey::namespaced(namespace, key);
244 self.cache.get(key.as_str()).await
245 }
246 _ => self.cache.get(key).await,
247 };
248 #[cfg(feature = "observability")]
249 self.record(
250 "get",
251 nidus_observability::OperationStatus::Success,
252 started_at,
253 );
254 result
255 }
256
257 pub async fn invalidate(&self, key: impl AsRef<str>) {
259 #[cfg(feature = "observability")]
260 let started_at = Instant::now();
261 let key = key.as_ref();
262 match self.namespace.as_deref() {
263 Some(namespace) if !namespace.is_empty() => {
264 let key = CacheKey::namespaced(namespace, key);
265 self.cache.invalidate(key.as_str()).await;
266 }
267 _ => self.cache.invalidate(key).await,
268 }
269 #[cfg(feature = "observability")]
270 self.record(
271 "invalidate",
272 nidus_observability::OperationStatus::Success,
273 started_at,
274 );
275 }
276
277 pub fn inner(&self) -> &moka::future::Cache<String, Vec<u8>> {
279 &self.cache
280 }
281
282 pub fn namespace(&self) -> Option<&str> {
284 self.namespace.as_deref()
285 }
286
287 #[cfg(feature = "health")]
289 pub fn health_status(&self) -> nidus_http::health::HealthStatus {
290 #[cfg(feature = "observability")]
291 let started_at = Instant::now();
292 #[cfg(feature = "observability")]
293 self.record(
294 "health",
295 nidus_observability::OperationStatus::Success,
296 started_at,
297 );
298 nidus_http::health::HealthStatus::up()
299 }
300
301 #[cfg(feature = "health")]
307 pub fn register_ready_check(
308 self: std::sync::Arc<Self>,
309 registry: nidus_http::health::HealthRegistry,
310 name: impl Into<String>,
311 ) -> nidus_http::health::HealthRegistry {
312 registry.ready_check_sync(name, move || self.health_status())
313 }
314
315 fn cache_key(&self, key: impl AsRef<str>) -> CacheKey {
316 CacheKey::new(self.namespace.as_deref(), key)
317 }
318
319 #[cfg(feature = "observability")]
320 fn record(
321 &self,
322 operation: &'static str,
323 status: nidus_observability::OperationStatus,
324 started_at: Instant,
325 ) {
326 if let Some(observer) = &self.observer {
327 observer.record("nidus-cache", operation, status, started_at.elapsed());
328 }
329 }
330 }
331
332 impl ProviderRegistrant for MokaCacheProvider {
333 fn register_provider(container: &mut Container) -> NidusResult<()> {
334 container.register_singleton(Self::builder().build())?;
335 Ok(())
336 }
337 }
338}
339
340#[cfg(feature = "moka")]
341pub use moka_backend::{MokaCacheBuilder, MokaCacheProvider};