use std::num::NonZeroUsize;
use lru::LruCache;
use crate::checker::Diagnostic;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct ResultKey {
engine: &'static str,
language: String,
content: u64,
}
pub struct ResultCache {
cache: Option<LruCache<ResultKey, Vec<Diagnostic>>>,
}
impl ResultCache {
#[must_use]
pub fn new(capacity: usize) -> Self {
Self {
cache: NonZeroUsize::new(capacity).map(LruCache::new),
}
}
fn key(engine: &'static str, language: &str, text: &str) -> ResultKey {
ResultKey {
engine,
language: language.to_string(),
content: crate::hashing::content_hash(text),
}
}
#[must_use]
pub fn get(
&mut self,
engine: &'static str,
language: &str,
text: &str,
) -> Option<Vec<Diagnostic>> {
self.cache
.as_mut()?
.get(&Self::key(engine, language, text))
.cloned()
}
pub fn put(
&mut self,
engine: &'static str,
language: &str,
text: &str,
diagnostics: Vec<Diagnostic>,
) {
if let Some(cache) = self.cache.as_mut() {
cache.put(Self::key(engine, language, text), diagnostics);
}
}
#[must_use]
pub fn len(&self) -> usize {
self.cache.as_ref().map_or(0, LruCache::len)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn clear(&mut self) {
if let Some(cache) = self.cache.as_mut() {
cache.clear();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn diagnostic(rule: &str) -> Diagnostic {
Diagnostic {
start_byte: 0,
end_byte: 4,
message: String::new(),
suggestions: Vec::new(),
rule_id: rule.to_string(),
severity: 2,
unified_id: String::new(),
confidence: 1.0,
language: String::new(),
pack_installable: false,
}
}
#[test]
fn an_answer_comes_back_for_the_same_prose() {
let mut cache = ResultCache::new(10);
assert!(cache.get("harper", "en-US", "some prose").is_none());
cache.put(
"harper",
"en-US",
"some prose",
vec![diagnostic("harper.Spelling")],
);
let hit = cache.get("harper", "en-US", "some prose").expect("hit");
assert_eq!(hit[0].rule_id, "harper.Spelling");
}
#[test]
fn changed_prose_misses() {
let mut cache = ResultCache::new(10);
cache.put(
"harper",
"en-US",
"some prose",
vec![diagnostic("harper.Spelling")],
);
assert!(cache.get("harper", "en-US", "some prose edited").is_none());
}
#[test]
fn another_engine_or_language_misses() {
let mut cache = ResultCache::new(10);
cache.put(
"harper",
"en-US",
"some prose",
vec![diagnostic("harper.Spelling")],
);
assert!(cache.get("languagetool", "en-US", "some prose").is_none());
assert!(cache.get("harper", "fr", "some prose").is_none());
}
#[test]
fn the_oldest_answer_is_evicted_at_capacity() {
let mut cache = ResultCache::new(2);
cache.put("harper", "en-US", "one", Vec::new());
cache.put("harper", "en-US", "two", Vec::new());
cache.put("harper", "en-US", "three", Vec::new());
assert_eq!(cache.len(), 2);
assert!(cache.get("harper", "en-US", "one").is_none());
assert!(cache.get("harper", "en-US", "three").is_some());
}
#[test]
fn a_zero_capacity_cache_is_switched_off() {
let mut cache = ResultCache::new(0);
cache.put("harper", "en-US", "one", Vec::new());
assert!(cache.get("harper", "en-US", "one").is_none());
assert!(cache.is_empty());
}
}