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() => {
89 Self(format!("{namespace}:{}", key.as_ref()))
90 }
91 _ => Self(key.as_ref().to_owned()),
92 }
93 }
94
95 pub fn as_str(&self) -> &str {
97 &self.0
98 }
99
100 pub fn into_string(self) -> String {
102 self.0
103 }
104}
105
106#[cfg(feature = "moka")]
107mod moka_backend {
108 #[cfg(feature = "observability")]
109 use std::time::Instant;
110
111 use nidus_core::{Container, ProviderRegistrant, Result as NidusResult};
112
113 use super::{CacheConfig, CacheKey, Result};
114
115 #[derive(Clone, Debug, Default)]
117 pub struct MokaCacheBuilder {
118 config: CacheConfig,
119 #[cfg(feature = "observability")]
120 observer: Option<nidus_observability::ObservabilityAdapterObserver>,
121 }
122
123 impl MokaCacheBuilder {
124 pub fn new() -> Self {
126 Self::default()
127 }
128
129 pub fn config(mut self, config: CacheConfig) -> Self {
131 self.config = config;
132 self
133 }
134
135 pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
137 self.config = self.config.namespace(namespace);
138 self
139 }
140
141 pub fn time_to_live(mut self, time_to_live: std::time::Duration) -> Self {
143 self.config = self.config.time_to_live(time_to_live);
144 self
145 }
146
147 pub fn max_capacity(mut self, max_capacity: u64) -> Self {
149 self.config = self.config.max_capacity(max_capacity);
150 self
151 }
152
153 #[cfg(feature = "observability")]
155 pub fn observability(
156 mut self,
157 observer: nidus_observability::ObservabilityAdapterObserver,
158 ) -> Self {
159 self.observer = Some(observer);
160 self
161 }
162
163 pub fn build(self) -> MokaCacheProvider {
165 let mut builder = moka::future::Cache::builder();
166 if let Some(time_to_live) = self.config.time_to_live {
167 builder = builder.time_to_live(time_to_live);
168 }
169 if let Some(max_capacity) = self.config.max_capacity {
170 builder = builder.max_capacity(max_capacity);
171 }
172 MokaCacheProvider {
173 namespace: self.config.namespace,
174 cache: builder.build(),
175 #[cfg(feature = "observability")]
176 observer: self.observer,
177 }
178 }
179
180 pub fn register(self, container: &mut Container) -> Result<()> {
182 container.register_singleton(self.build())?;
183 Ok(())
184 }
185 }
186
187 #[derive(Clone, Debug)]
189 pub struct MokaCacheProvider {
190 namespace: Option<String>,
191 cache: moka::future::Cache<String, Vec<u8>>,
192 #[cfg(feature = "observability")]
193 observer: Option<nidus_observability::ObservabilityAdapterObserver>,
194 }
195
196 impl MokaCacheProvider {
197 pub fn builder() -> MokaCacheBuilder {
199 MokaCacheBuilder::new()
200 }
201
202 pub fn from_cache(
204 cache: moka::future::Cache<String, Vec<u8>>,
205 namespace: Option<String>,
206 ) -> Self {
207 Self {
208 namespace,
209 cache,
210 #[cfg(feature = "observability")]
211 observer: None,
212 }
213 }
214
215 pub async fn insert(&self, key: impl AsRef<str>, value: Vec<u8>) {
217 #[cfg(feature = "observability")]
218 let started_at = Instant::now();
219 self.cache
220 .insert(self.cache_key(key).into_string(), value)
221 .await;
222 #[cfg(feature = "observability")]
223 self.record(
224 "insert",
225 nidus_observability::OperationStatus::Success,
226 started_at,
227 );
228 }
229
230 pub async fn get(&self, key: impl AsRef<str>) -> Option<Vec<u8>> {
232 #[cfg(feature = "observability")]
233 let started_at = Instant::now();
234 let result = self.cache.get(self.cache_key(key).as_str()).await;
235 #[cfg(feature = "observability")]
236 self.record(
237 "get",
238 nidus_observability::OperationStatus::Success,
239 started_at,
240 );
241 result
242 }
243
244 pub async fn invalidate(&self, key: impl AsRef<str>) {
246 #[cfg(feature = "observability")]
247 let started_at = Instant::now();
248 self.cache.invalidate(self.cache_key(key).as_str()).await;
249 #[cfg(feature = "observability")]
250 self.record(
251 "invalidate",
252 nidus_observability::OperationStatus::Success,
253 started_at,
254 );
255 }
256
257 pub fn inner(&self) -> &moka::future::Cache<String, Vec<u8>> {
259 &self.cache
260 }
261
262 pub fn namespace(&self) -> Option<&str> {
264 self.namespace.as_deref()
265 }
266
267 #[cfg(feature = "health")]
269 pub fn health_status(&self) -> nidus_http::health::HealthStatus {
270 #[cfg(feature = "observability")]
271 let started_at = Instant::now();
272 #[cfg(feature = "observability")]
273 self.record(
274 "health",
275 nidus_observability::OperationStatus::Success,
276 started_at,
277 );
278 nidus_http::health::HealthStatus::up()
279 }
280
281 #[cfg(feature = "health")]
287 pub fn register_ready_check(
288 self: std::sync::Arc<Self>,
289 registry: nidus_http::health::HealthRegistry,
290 name: impl Into<String>,
291 ) -> nidus_http::health::HealthRegistry {
292 registry.ready_check_sync(name, move || self.health_status())
293 }
294
295 fn cache_key(&self, key: impl AsRef<str>) -> CacheKey {
296 CacheKey::new(self.namespace.as_deref(), key)
297 }
298
299 #[cfg(feature = "observability")]
300 fn record(
301 &self,
302 operation: &'static str,
303 status: nidus_observability::OperationStatus,
304 started_at: Instant,
305 ) {
306 if let Some(observer) = &self.observer {
307 observer.record("nidus-cache", operation, status, started_at.elapsed());
308 }
309 }
310 }
311
312 impl ProviderRegistrant for MokaCacheProvider {
313 fn register_provider(container: &mut Container) -> NidusResult<()> {
314 container.register_singleton(Self::builder().build())?;
315 Ok(())
316 }
317 }
318}
319
320#[cfg(feature = "moka")]
321pub use moka_backend::{MokaCacheBuilder, MokaCacheProvider};