use std::collections::HashSet;
use std::hash::Hash;
use std::sync::Arc;
use super::ICacheEntryValidityChecker;
pub trait ICache<K, V: ?Sized>: Send + Sync
where
K: Clone + Eq + Hash + Send + Sync,
V: Send + Sync,
{
fn put(&self, key: K, value: Arc<V>);
fn get(&self, key: &K) -> Option<Arc<V>>;
fn get_with_validity_checker(
&self,
key: &K,
validity_checker: &dyn ICacheEntryValidityChecker<K, V>,
) -> Option<Arc<V>>;
fn clear(&self);
fn clear_key(&self, key: &K);
fn key_set(&self) -> HashSet<K>;
}
#[cfg(test)]
mod tests {
use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex};
use super::ICache;
use crate::cache::ICacheEntryValidityChecker;
struct Entry<V> {
value: Arc<V>,
creation_timestamp: i64,
}
#[derive(Default)]
struct ContractCache {
entries: Mutex<HashMap<String, Entry<String>>>,
}
impl ICache<String, String> for ContractCache {
fn put(&self, key: String, value: Arc<String>) {
self.entries.lock().expect("cache lock").insert(
key,
Entry {
value,
creation_timestamp: 7,
},
);
}
fn get(&self, key: &String) -> Option<Arc<String>> {
self.entries
.lock()
.expect("cache lock")
.get(key)
.map(|entry| Arc::clone(&entry.value))
}
fn get_with_validity_checker(
&self,
key: &String,
validity_checker: &dyn ICacheEntryValidityChecker<String, String>,
) -> Option<Arc<String>> {
let mut entries = self.entries.lock().expect("cache lock");
let is_valid = entries.get(key).is_some_and(|entry| {
validity_checker.check_is_value_still_valid(
key,
&entry.value,
entry.creation_timestamp,
)
});
if !is_valid {
entries.remove(key);
return None;
}
entries.get(key).map(|entry| Arc::clone(&entry.value))
}
fn clear(&self) {
self.entries.lock().expect("cache lock").clear();
}
fn clear_key(&self, key: &String) {
self.entries.lock().expect("cache lock").remove(key);
}
fn key_set(&self) -> HashSet<String> {
self.entries
.lock()
.expect("cache lock")
.keys()
.cloned()
.collect()
}
}
struct Checker {
expected_timestamp: i64,
valid: bool,
}
impl ICacheEntryValidityChecker<String, String> for Checker {
fn check_is_value_still_valid(
&self,
key: &String,
value: &String,
entry_creation_timestamp: i64,
) -> bool {
key == "key"
&& value == "value"
&& entry_creation_timestamp == self.expected_timestamp
&& self.valid
}
}
#[test]
fn preserves_shared_value_identity_and_all_cache_operations() {
let cache: &dyn ICache<String, String> = &ContractCache::default();
let key = "key".to_owned();
let value = Arc::new("value".to_owned());
assert!(cache.get(&key).is_none());
cache.put(key.clone(), Arc::clone(&value));
let found = cache.get(&key).expect("cache hit");
assert!(Arc::ptr_eq(&found, &value));
assert_eq!(cache.key_set(), HashSet::from([key.clone()]));
cache.clear_key(&"missing".to_owned());
assert!(cache.get(&key).is_some());
cache.clear_key(&key);
assert!(cache.get(&key).is_none());
cache.put(key.clone(), value);
cache.put("second".to_owned(), Arc::new("two".to_owned()));
cache.clear();
assert!(cache.key_set().is_empty());
}
#[test]
fn explicit_checker_overrides_read_and_removes_invalid_entries() {
let cache = ContractCache::default();
let key = "key".to_owned();
let value = Arc::new("value".to_owned());
cache.put(key.clone(), Arc::clone(&value));
let valid = Checker {
expected_timestamp: 7,
valid: true,
};
let found = cache
.get_with_validity_checker(&key, &valid)
.expect("valid entry");
assert!(Arc::ptr_eq(&found, &value));
let invalid = Checker {
expected_timestamp: 7,
valid: false,
};
assert!(cache.get_with_validity_checker(&key, &invalid).is_none());
assert!(!cache.key_set().contains(&key));
assert!(
cache
.get_with_validity_checker(&"missing".to_owned(), &valid)
.is_none()
);
}
}