use crate::decision::Decision;
use crate::{action::Action, resource::ResourceRef, subject::Subject};
use lru::LruCache;
use std::num::NonZeroUsize;
use std::sync::Mutex;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CacheKey {
pub actor_id: String,
pub action: String,
pub resource_kind: String,
pub resource_id: String,
pub policy_version: u64,
pub tenant_id: Option<String>,
}
impl CacheKey {
#[must_use]
pub fn for_request(
subject: &Subject,
action: &Action,
resource: &ResourceRef,
policy_version: u64,
) -> Self {
Self {
actor_id: subject.actor_id.clone(),
action: action.to_string(),
resource_kind: resource.kind.clone(),
resource_id: resource
.resource_id
.clone()
.unwrap_or_else(|| "*".to_string()),
policy_version,
tenant_id: subject.tenant_id.clone(),
}
}
}
struct CachedEntry {
decision: Decision,
cached_at: Instant,
}
pub struct DecisionCache {
inner: Mutex<LruCache<CacheKey, CachedEntry>>,
ttl: Duration,
}
impl DecisionCache {
pub fn new(max_size: usize, ttl: Duration) -> Self {
let capacity = NonZeroUsize::new(max_size).unwrap_or_else(|| NonZeroUsize::new(1).unwrap());
Self {
inner: Mutex::new(LruCache::new(capacity)),
ttl,
}
}
pub fn get(&self, key: &CacheKey) -> Option<Decision> {
let mut inner = self.inner.lock().unwrap();
if let Some(entry) = inner.get(key) {
if entry.cached_at.elapsed() < self.ttl {
return Some(entry.decision.clone());
}
}
None
}
pub fn insert(&self, key: CacheKey, decision: Decision) {
let mut inner = self.inner.lock().unwrap();
inner.put(
key,
CachedEntry {
decision,
cached_at: Instant::now(),
},
);
}
}