xjp_oidc/
cache.rs

1//! Cache abstraction for JWKS and discovery metadata
2
3use std::time::{Duration, Instant};
4
5/// Cache trait for storing and retrieving values with TTL
6pub trait Cache<K, V>: Send + Sync {
7    /// Get a value from the cache
8    fn get(&self, key: &K) -> Option<V>;
9
10    /// Put a value into the cache with TTL in seconds
11    fn put(&self, key: K, value: V, ttl_secs: u64);
12
13    /// Remove a value from the cache
14    fn remove(&self, key: &K) -> Option<V>;
15
16    /// Clear all values from the cache
17    fn clear(&self);
18}
19
20/// No-op cache implementation (always returns None)
21#[derive(Clone)]
22pub struct NoOpCache;
23
24impl<K, V> Cache<K, V> for NoOpCache {
25    fn get(&self, _key: &K) -> Option<V> {
26        None
27    }
28
29    fn put(&self, _key: K, _value: V, _ttl_secs: u64) {}
30
31    fn remove(&self, _key: &K) -> Option<V> {
32        None
33    }
34
35    fn clear(&self) {}
36}
37
38/// Simple in-memory cache implementation (for testing/examples)
39#[derive(Clone)]
40pub struct MemoryCache;
41
42impl<K, V> Cache<K, V> for MemoryCache {
43    fn get(&self, _key: &K) -> Option<V> {
44        // Simple implementation - always returns None for now
45        // In production, use LruCacheImpl or MokaCacheImpl
46        None
47    }
48
49    fn put(&self, _key: K, _value: V, _ttl_secs: u64) {
50        // No-op for simple implementation
51    }
52
53    fn remove(&self, _key: &K) -> Option<V> {
54        None
55    }
56
57    fn clear(&self) {}
58}
59
60/// Simple LRU cache implementation
61#[cfg(feature = "lru")]
62pub struct LruCacheImpl<K: std::hash::Hash + Eq + Clone, V: Clone> {
63    inner: std::sync::Mutex<LruCacheInner<K, V>>,
64}
65
66#[cfg(feature = "lru")]
67struct LruCacheInner<K: std::hash::Hash + Eq + Clone, V: Clone> {
68    cache: lru::LruCache<K, (V, Instant)>,
69}
70
71#[cfg(feature = "lru")]
72impl<K: std::hash::Hash + Eq + Clone, V: Clone> LruCacheImpl<K, V> {
73    /// Create a new LRU cache with the specified capacity
74    pub fn new(capacity: usize) -> Self {
75        Self {
76            inner: std::sync::Mutex::new(LruCacheInner {
77                cache: lru::LruCache::new(capacity.try_into().unwrap()),
78            }),
79        }
80    }
81}
82
83#[cfg(feature = "lru")]
84impl<K: std::hash::Hash + Eq + Clone + Send + Sync + std::fmt::Display, V: Clone + Send + Sync>
85    Cache<K, V> for LruCacheImpl<K, V>
86{
87    fn get(&self, key: &K) -> Option<V> {
88        let mut inner = self.inner.lock().unwrap();
89        let result = if let Some((value, expires_at)) = inner.cache.get(key) {
90            if Instant::now() < *expires_at {
91                Some(value.clone())
92            } else {
93                inner.cache.pop(key);
94                None
95            }
96        } else {
97            None
98        };
99
100        tracing::debug!(
101            target: "xjp_oidc::cache",
102            cache_key = %key,
103            cache_hit = result.is_some(),
104            cache_type = "lru",
105            "缓存查询"
106        );
107
108        result
109    }
110
111    fn put(&self, key: K, value: V, ttl_secs: u64) {
112        let expires_at = Instant::now() + Duration::from_secs(ttl_secs);
113        let mut inner = self.inner.lock().unwrap();
114        inner.cache.put(key, (value, expires_at));
115    }
116
117    fn remove(&self, key: &K) -> Option<V> {
118        let mut inner = self.inner.lock().unwrap();
119        inner.cache.pop(key).map(|(v, _)| v)
120    }
121
122    fn clear(&self) {
123        let mut inner = self.inner.lock().unwrap();
124        inner.cache.clear();
125    }
126}
127
128/// Moka-based async cache implementation
129#[cfg(all(not(target_arch = "wasm32"), feature = "moka"))]
130#[derive(Clone)]
131pub struct MokaCacheImpl<K: std::hash::Hash + Eq + Clone + Send + Sync, V: Clone + Send + Sync> {
132    cache: moka::future::Cache<K, (V, Option<std::time::Instant>)>,
133}
134
135#[cfg(all(not(target_arch = "wasm32"), feature = "moka"))]
136impl<K: std::hash::Hash + Eq + Clone + Send + Sync + 'static, V: Clone + Send + Sync + 'static>
137    MokaCacheImpl<K, V>
138{
139    /// Create a new Moka cache with the specified capacity
140    pub fn new(capacity: u64) -> Self {
141        let cache = moka::future::Cache::builder().max_capacity(capacity).build();
142        Self { cache }
143    }
144
145    /// Create a new Moka cache with custom configuration
146    pub fn with_config(
147        capacity: u64,
148        time_to_live: Option<Duration>,
149        time_to_idle: Option<Duration>,
150    ) -> Self {
151        let mut builder = moka::future::Cache::builder().max_capacity(capacity);
152
153        if let Some(ttl) = time_to_live {
154            builder = builder.time_to_live(ttl);
155        }
156
157        if let Some(tti) = time_to_idle {
158            builder = builder.time_to_idle(tti);
159        }
160
161        Self { cache: builder.build() }
162    }
163}
164
165#[cfg(all(not(target_arch = "wasm32"), feature = "moka"))]
166impl<K: std::hash::Hash + Eq + Clone + Send + Sync + 'static, V: Clone + Send + Sync + 'static>
167    Cache<K, V> for MokaCacheImpl<K, V>
168{
169    fn get(&self, key: &K) -> Option<V> {
170        // Note: This blocks on async operation, which is not ideal
171        // Consider making Cache trait async in future versions
172        futures::executor::block_on(async {
173            if let Some((value, expiry)) = self.cache.get(key).await {
174                // Check if the entry has expired
175                if let Some(exp_time) = expiry {
176                    if std::time::Instant::now() > exp_time {
177                        // Entry has expired, remove it and return None
178                        self.cache.invalidate(key).await;
179                        return None;
180                    }
181                }
182                Some(value)
183            } else {
184                None
185            }
186        })
187    }
188
189    fn put(&self, key: K, value: V, ttl_secs: u64) {
190        // Note: This blocks on async operation, which is not ideal
191        // Consider making Cache trait async in future versions
192        futures::executor::block_on(async {
193            // Calculate expiry time based on ttl_secs
194            let expiry = if ttl_secs > 0 {
195                Some(std::time::Instant::now() + Duration::from_secs(ttl_secs))
196            } else {
197                None // No expiry
198            };
199
200            // Store value with its expiry time
201            self.cache.insert(key, (value, expiry)).await;
202        });
203    }
204
205    fn remove(&self, key: &K) -> Option<V> {
206        futures::executor::block_on(async {
207            self.cache.remove(key).await.map(|(value, _)| value)
208        })
209    }
210
211    fn clear(&self) {
212        // Note: This blocks on async operation, which is not ideal
213        // Consider making Cache trait async in future versions
214        futures::executor::block_on(async { self.cache.invalidate_all() });
215    }
216}