use std::time::{Duration, Instant};
pub trait Cache<K, V>: Send + Sync {
fn get(&self, key: &K) -> Option<V>;
fn put(&self, key: K, value: V, ttl_secs: u64);
fn remove(&self, key: &K) -> Option<V>;
fn clear(&self);
}
#[derive(Clone)]
pub struct NoOpCache;
impl<K, V> Cache<K, V> for NoOpCache {
fn get(&self, _key: &K) -> Option<V> {
None
}
fn put(&self, _key: K, _value: V, _ttl_secs: u64) {}
fn remove(&self, _key: &K) -> Option<V> {
None
}
fn clear(&self) {}
}
#[derive(Clone)]
pub struct MemoryCache;
impl<K, V> Cache<K, V> for MemoryCache {
fn get(&self, _key: &K) -> Option<V> {
None
}
fn put(&self, _key: K, _value: V, _ttl_secs: u64) {
}
fn remove(&self, _key: &K) -> Option<V> {
None
}
fn clear(&self) {}
}
#[cfg(feature = "lru")]
pub struct LruCacheImpl<K: std::hash::Hash + Eq + Clone, V: Clone> {
inner: std::sync::Mutex<LruCacheInner<K, V>>,
}
#[cfg(feature = "lru")]
struct LruCacheInner<K: std::hash::Hash + Eq + Clone, V: Clone> {
cache: lru::LruCache<K, (V, Instant)>,
}
#[cfg(feature = "lru")]
impl<K: std::hash::Hash + Eq + Clone, V: Clone> LruCacheImpl<K, V> {
pub fn new(capacity: usize) -> Self {
Self {
inner: std::sync::Mutex::new(LruCacheInner {
cache: lru::LruCache::new(capacity.try_into().unwrap()),
}),
}
}
}
#[cfg(feature = "lru")]
impl<K: std::hash::Hash + Eq + Clone + Send + Sync + std::fmt::Display, V: Clone + Send + Sync>
Cache<K, V> for LruCacheImpl<K, V>
{
fn get(&self, key: &K) -> Option<V> {
let mut inner = self.inner.lock().unwrap();
let result = if let Some((value, expires_at)) = inner.cache.get(key) {
if Instant::now() < *expires_at {
Some(value.clone())
} else {
inner.cache.pop(key);
None
}
} else {
None
};
tracing::debug!(
target: "xjp_oidc::cache",
cache_key = %key,
cache_hit = result.is_some(),
cache_type = "lru",
"ç¼“å˜æŸ¥è¯¢"
);
result
}
fn put(&self, key: K, value: V, ttl_secs: u64) {
let expires_at = Instant::now() + Duration::from_secs(ttl_secs);
let mut inner = self.inner.lock().unwrap();
inner.cache.put(key, (value, expires_at));
}
fn remove(&self, key: &K) -> Option<V> {
let mut inner = self.inner.lock().unwrap();
inner.cache.pop(key).map(|(v, _)| v)
}
fn clear(&self) {
let mut inner = self.inner.lock().unwrap();
inner.cache.clear();
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "moka"))]
#[derive(Clone)]
pub struct MokaCacheImpl<K: std::hash::Hash + Eq + Clone + Send + Sync, V: Clone + Send + Sync> {
cache: moka::future::Cache<K, (V, Option<std::time::Instant>)>,
}
#[cfg(all(not(target_arch = "wasm32"), feature = "moka"))]
impl<K: std::hash::Hash + Eq + Clone + Send + Sync + 'static, V: Clone + Send + Sync + 'static>
MokaCacheImpl<K, V>
{
pub fn new(capacity: u64) -> Self {
let cache = moka::future::Cache::builder().max_capacity(capacity).build();
Self { cache }
}
pub fn with_config(
capacity: u64,
time_to_live: Option<Duration>,
time_to_idle: Option<Duration>,
) -> Self {
let mut builder = moka::future::Cache::builder().max_capacity(capacity);
if let Some(ttl) = time_to_live {
builder = builder.time_to_live(ttl);
}
if let Some(tti) = time_to_idle {
builder = builder.time_to_idle(tti);
}
Self { cache: builder.build() }
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "moka"))]
impl<K: std::hash::Hash + Eq + Clone + Send + Sync + 'static, V: Clone + Send + Sync + 'static>
Cache<K, V> for MokaCacheImpl<K, V>
{
fn get(&self, key: &K) -> Option<V> {
futures::executor::block_on(async {
if let Some((value, expiry)) = self.cache.get(key).await {
if let Some(exp_time) = expiry {
if std::time::Instant::now() > exp_time {
self.cache.invalidate(key).await;
return None;
}
}
Some(value)
} else {
None
}
})
}
fn put(&self, key: K, value: V, ttl_secs: u64) {
futures::executor::block_on(async {
let expiry = if ttl_secs > 0 {
Some(std::time::Instant::now() + Duration::from_secs(ttl_secs))
} else {
None };
self.cache.insert(key, (value, expiry)).await;
});
}
fn remove(&self, key: &K) -> Option<V> {
futures::executor::block_on(async {
self.cache.remove(key).await.map(|(value, _)| value)
})
}
fn clear(&self) {
futures::executor::block_on(async { self.cache.invalidate_all() });
}
}