use std::collections::HashMap;
use crate::core::ResourceType;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct RenderCacheKey {
pub(crate) resource_path: String,
pub(crate) resource_type: ResourceType,
pub(crate) tool: Option<String>,
pub(crate) variant_inputs_hash: String,
pub(crate) resolved_commit: Option<String>,
pub(crate) dependency_hash: String,
}
impl RenderCacheKey {
#[must_use]
pub(crate) fn new(
resource_path: String,
resource_type: ResourceType,
tool: Option<String>,
variant_inputs_hash: String,
resolved_commit: Option<String>,
dependency_hash: String,
) -> Self {
Self {
resource_path,
resource_type,
tool,
variant_inputs_hash,
resolved_commit,
dependency_hash,
}
}
}
#[derive(Debug, Default)]
pub(crate) struct RenderCache {
cache: HashMap<RenderCacheKey, String>,
hits: usize,
misses: usize,
}
impl RenderCache {
#[must_use]
pub(crate) fn new() -> Self {
Self {
cache: HashMap::new(),
hits: 0,
misses: 0,
}
}
pub(crate) fn get(&mut self, key: &RenderCacheKey) -> Option<&String> {
if let Some(content) = self.cache.get(key) {
self.hits += 1;
Some(content)
} else {
self.misses += 1;
None
}
}
pub(crate) fn insert(&mut self, key: RenderCacheKey, content: String) {
self.cache.insert(key, content);
}
pub(crate) fn clear(&mut self) {
self.cache.clear();
self.hits = 0;
self.misses = 0;
}
#[must_use]
pub(crate) fn stats(&self) -> (usize, usize) {
(self.hits, self.misses)
}
#[must_use]
pub(crate) fn hit_rate(&self) -> f64 {
let total = self.hits + self.misses;
if total == 0 {
0.0
} else {
(self.hits as f64 / total as f64) * 100.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_render_cache_key_includes_commit() {
let key1 = RenderCacheKey::new(
"agents/helper.md".to_string(),
ResourceType::Agent,
Some("claude-code".to_string()),
"hash123".to_string(),
Some("abc123def".to_string()),
"dep_hash_123".to_string(),
);
let key2 = RenderCacheKey::new(
"agents/helper.md".to_string(),
ResourceType::Agent,
Some("claude-code".to_string()),
"hash123".to_string(),
Some("def456ghi".to_string()),
"dep_hash_456".to_string(),
);
assert_ne!(key1, key2, "Different commits should have different cache keys");
let key3 = RenderCacheKey::new(
"agents/helper.md".to_string(),
ResourceType::Agent,
Some("claude-code".to_string()),
"hash123".to_string(),
Some("abc123def".to_string()),
"dep_hash_123".to_string(),
);
assert_eq!(key1, key3, "Same commits should have identical cache keys");
let key4 = RenderCacheKey::new(
"agents/helper.md".to_string(),
ResourceType::Agent,
Some("claude-code".to_string()),
"hash123".to_string(),
None,
"dep_hash_none".to_string(),
);
assert_ne!(key1, key4, "Some(commit) vs None should have different cache keys");
}
#[test]
fn test_render_cache_basic_operations() {
let mut cache = RenderCache::new();
let key = RenderCacheKey::new(
"test.md".to_string(),
ResourceType::Snippet,
Some("claude-code".to_string()),
"hash789".to_string(),
Some("commit123".to_string()),
"dep_hash_test".to_string(),
);
assert_eq!(cache.stats(), (0, 0));
assert_eq!(cache.hit_rate(), 0.0);
assert!(cache.get(&key).is_none());
assert_eq!(cache.stats(), (0, 1));
cache.insert(key.clone(), "rendered content".to_string());
assert_eq!(cache.get(&key), Some(&"rendered content".to_string()));
assert_eq!(cache.stats(), (1, 1));
assert_eq!(cache.hit_rate(), 50.0);
cache.clear();
assert_eq!(cache.stats(), (0, 0));
assert!(cache.get(&key).is_none());
}
}