Skip to main content

authz_core/
cache.rs

1//! Generic cache abstraction for authz resolution caches.
2//!
3//! Provides `AuthzCache<V>` trait and a `NoopCache` implementation that never
4//! stores entries.  The trait is intentionally minimal so that `authz-core`
5//! stays dependency-light — concrete backends (e.g. moka) live in downstream
6//! crates.
7
8use std::sync::Arc;
9
10/// Cache performance metrics.
11pub trait CacheMetrics: Send + Sync {
12    /// Total number of cache hits.
13    fn hits(&self) -> u64;
14
15    /// Total number of cache misses.
16    fn misses(&self) -> u64;
17
18    /// Hit rate as a percentage (0.0 to 100.0).
19    fn hit_rate(&self) -> f64 {
20        let total = self.hits() + self.misses();
21        if total == 0 {
22            0.0
23        } else {
24            (self.hits() as f64 / total as f64) * 100.0
25        }
26    }
27}
28
29/// Generic cache abstraction for authz resolution caches.
30///
31/// Implementations must be `Send + Sync` so they can be shared across threads
32/// and stored behind `Arc`.
33pub trait AuthzCache<V: Clone + Send + Sync>: Send + Sync {
34    /// Look up a cached value by key.  Returns `None` on miss.
35    fn get(&self, key: &str) -> Option<V>;
36
37    /// Insert a value into the cache.
38    fn insert(&self, key: &str, value: V);
39
40    /// Remove a single entry by key.
41    fn invalidate(&self, key: &str);
42
43    /// Remove all entries from the cache.
44    fn invalidate_all(&self);
45
46    /// Get cache performance metrics.
47    fn metrics(&self) -> Box<dyn CacheMetrics>;
48}
49
50/// A cache that never stores anything — every `get` returns `None`.
51///
52/// Used as the default when caching is disabled (TTL = 0) and in unit tests.
53pub struct NoopCache;
54
55/// Metrics for NoopCache (always returns zeros).
56struct NoopMetrics;
57
58impl CacheMetrics for NoopMetrics {
59    fn hits(&self) -> u64 {
60        0
61    }
62
63    fn misses(&self) -> u64 {
64        0
65    }
66}
67
68impl<V: Clone + Send + Sync> AuthzCache<V> for NoopCache {
69    fn get(&self, _key: &str) -> Option<V> {
70        None
71    }
72
73    fn insert(&self, _key: &str, _value: V) {}
74
75    fn invalidate(&self, _key: &str) {}
76
77    fn invalidate_all(&self) {}
78
79    fn metrics(&self) -> Box<dyn CacheMetrics> {
80        Box::new(NoopMetrics)
81    }
82}
83
84/// Convenience helper: create a `NoopCache` wrapped in an `Arc`.
85pub fn noop_cache<V: Clone + Send + Sync + 'static>() -> Arc<dyn AuthzCache<V>> {
86    Arc::new(NoopCache)
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn noop_cache_always_misses() {
95        let cache: Arc<dyn AuthzCache<String>> = noop_cache();
96        cache.insert("key", "value".to_string());
97        assert_eq!(cache.get("key"), None);
98    }
99
100    #[test]
101    fn noop_cache_invalidate_is_safe() {
102        let cache: Arc<dyn AuthzCache<i32>> = noop_cache();
103        cache.invalidate_all(); // should not panic
104    }
105}