use crate::events::CacheEvent;
use std::hash::Hash;
use std::sync::Arc;
use std::time::Duration;
use tower_resilience_core::{EventListeners, FnListener};
pub type KeyExtractor<Req, K> = Arc<dyn Fn(&Req) -> K + Send + Sync>;
pub struct CacheConfig<Req, K> {
pub(crate) max_size: usize,
pub(crate) ttl: Option<Duration>,
pub(crate) key_extractor: KeyExtractor<Req, K>,
pub(crate) event_listeners: EventListeners<CacheEvent>,
pub(crate) name: String,
}
pub struct CacheConfigBuilder<Req, K> {
max_size: usize,
ttl: Option<Duration>,
key_extractor: Option<KeyExtractor<Req, K>>,
event_listeners: EventListeners<CacheEvent>,
name: String,
}
impl<Req, K> CacheConfigBuilder<Req, K>
where
K: Hash + Eq + Clone + Send + 'static,
{
pub fn new() -> Self {
Self {
max_size: 100,
ttl: None,
key_extractor: None,
event_listeners: EventListeners::new(),
name: String::from("<unnamed>"),
}
}
pub fn max_size(mut self, size: usize) -> Self {
self.max_size = size;
self
}
pub fn ttl(mut self, ttl: Duration) -> Self {
self.ttl = Some(ttl);
self
}
pub fn key_extractor<F>(mut self, f: F) -> Self
where
F: Fn(&Req) -> K + Send + Sync + 'static,
{
self.key_extractor = Some(Arc::new(f));
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
}
pub fn on_hit<F>(mut self, f: F) -> Self
where
F: Fn() + Send + Sync + 'static,
{
self.event_listeners.add(FnListener::new(move |event| {
if matches!(event, CacheEvent::Hit { .. }) {
f();
}
}));
self
}
pub fn on_miss<F>(mut self, f: F) -> Self
where
F: Fn() + Send + Sync + 'static,
{
self.event_listeners.add(FnListener::new(move |event| {
if matches!(event, CacheEvent::Miss { .. }) {
f();
}
}));
self
}
pub fn on_eviction<F>(mut self, f: F) -> Self
where
F: Fn() + Send + Sync + 'static,
{
self.event_listeners.add(FnListener::new(move |event| {
if matches!(event, CacheEvent::Eviction { .. }) {
f();
}
}));
self
}
pub fn build(self) -> crate::CacheLayer<Req, K> {
let key_extractor = self
.key_extractor
.expect("key_extractor must be set before building");
let config = CacheConfig {
max_size: self.max_size,
ttl: self.ttl,
key_extractor,
event_listeners: self.event_listeners,
name: self.name,
};
crate::CacheLayer::new(config)
}
}
impl<Req, K> Default for CacheConfigBuilder<Req, K>
where
K: Hash + Eq + Clone + Send + 'static,
{
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::CacheLayer;
#[derive(Clone, Hash, Eq, PartialEq)]
struct TestRequest {
id: String,
}
#[test]
fn test_builder_defaults() {
let _layer = CacheLayer::<TestRequest, String>::builder()
.key_extractor(|req| req.id.clone())
.build();
}
#[test]
fn test_builder_custom_values() {
let _layer = CacheLayer::<TestRequest, String>::builder()
.max_size(500)
.ttl(Duration::from_secs(60))
.key_extractor(|req| req.id.clone())
.name("my-cache")
.build();
}
#[test]
fn test_event_listeners() {
let _layer = CacheLayer::<TestRequest, String>::builder()
.key_extractor(|req| req.id.clone())
.on_hit(|| {})
.on_miss(|| {})
.on_eviction(|| {})
.build();
}
#[test]
#[should_panic(expected = "key_extractor must be set")]
fn test_builder_panics_without_key_extractor() {
let _config = CacheLayer::<TestRequest, String>::builder().build();
}
}