use crate::error::BuildError;
use crate::optimized_strings::OptimizedString;
use blake3::Hasher as Blake3Hasher;
use indexmap::IndexMap;
use once_cell::sync::Lazy;
use std::hash::{Hash, Hasher};
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant};
static GLOBAL_CACHE: Lazy<Arc<RwLock<GlobalCache>>> =
Lazy::new(|| Arc::new(RwLock::new(GlobalCache::new())));
#[derive(Debug)]
pub struct GlobalCache {
schemas: SchemaCache,
validation_cache: ValidationCache,
hash_cache: HashCache,
template_cache: TemplateCache,
stats: CacheStats,
}
impl GlobalCache {
pub fn new() -> Self {
Self {
schemas: SchemaCache::new(),
validation_cache: ValidationCache::new(),
hash_cache: HashCache::new(),
template_cache: TemplateCache::new(),
stats: CacheStats::default(),
}
}
pub fn clear_all(&mut self) {
self.schemas.clear();
self.validation_cache.clear();
self.hash_cache.clear();
self.template_cache.clear();
self.stats = CacheStats::default();
}
pub fn stats(&self) -> &CacheStats {
&self.stats
}
pub fn prune_expired(&mut self) {
self.validation_cache.prune_expired();
self.hash_cache.prune_expired();
self.template_cache.prune_expired();
}
}
#[derive(Debug)]
pub struct SchemaCache {
schemas: IndexMap<SchemaKey, CachedSchema>,
metadata: IndexMap<SchemaKey, SchemaMetadata>,
}
impl SchemaCache {
pub fn new() -> Self {
Self {
schemas: IndexMap::new(),
metadata: IndexMap::new(),
}
}
pub fn get_or_compile(
&mut self,
version: &str,
profile: Option<&str>,
compiler: impl FnOnce() -> Result<CompiledSchema, BuildError>,
) -> Result<&CompiledSchema, BuildError> {
let key = SchemaKey {
version: version.to_string(),
profile: profile.map(|p| p.to_string()),
};
if !self.schemas.contains_key(&key) {
let start_time = Instant::now();
let schema = compiler()?;
let compile_time = start_time.elapsed();
self.metadata.insert(
key.clone(),
SchemaMetadata {
compile_time,
last_used: Instant::now(),
use_count: 0,
},
);
self.schemas.insert(
key.clone(),
CachedSchema {
schema,
created_at: Instant::now(),
},
);
}
if let Some(metadata) = self.metadata.get_mut(&key) {
metadata.last_used = Instant::now();
metadata.use_count += 1;
}
Ok(&self.schemas.get(&key).unwrap().schema)
}
pub fn contains(&self, version: &str, profile: Option<&str>) -> bool {
let key = SchemaKey {
version: version.to_string(),
profile: profile.map(|p| p.to_string()),
};
self.schemas.contains_key(&key)
}
pub fn clear(&mut self) {
self.schemas.clear();
self.metadata.clear();
}
pub fn memory_usage(&self) -> usize {
self.schemas
.values()
.map(|cached| cached.schema.memory_footprint())
.sum()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct SchemaKey {
version: String,
profile: Option<String>,
}
#[derive(Debug)]
#[allow(dead_code)]
struct CachedSchema {
schema: CompiledSchema,
created_at: Instant,
}
#[derive(Debug)]
#[allow(dead_code)]
struct SchemaMetadata {
compile_time: Duration,
last_used: Instant,
use_count: usize,
}
#[derive(Debug, Clone)]
pub struct CompiledSchema {
pub version: String,
pub profile: Option<String>,
pub rules: Vec<ValidationRule>,
pub required_elements: Vec<String>,
pub element_constraints: IndexMap<String, ElementConstraint>,
}
impl CompiledSchema {
pub fn memory_footprint(&self) -> usize {
std::mem::size_of::<Self>()
+ self.version.len()
+ self.profile.as_ref().map_or(0, |p| p.len())
+ self.rules.len() * std::mem::size_of::<ValidationRule>()
+ self
.required_elements
.iter()
.map(|e| e.len())
.sum::<usize>()
+ self
.element_constraints
.keys()
.map(|k| k.len())
.sum::<usize>()
}
}
#[derive(Debug, Clone)]
pub struct ValidationRule {
pub element_path: String,
pub rule_type: RuleType,
pub parameters: Vec<String>,
}
#[derive(Debug, Clone)]
pub enum RuleType {
Required,
Pattern(String),
Range(f64, f64),
Length(usize, usize),
Custom(String),
}
#[derive(Debug, Clone)]
pub struct ElementConstraint {
pub min_occurs: usize,
pub max_occurs: Option<usize>,
pub data_type: String,
}
#[derive(Debug)]
pub struct ValidationCache {
results: IndexMap<String, CachedValidationResult>,
config: ValidationCacheConfig,
}
impl ValidationCache {
pub fn new() -> Self {
Self {
results: IndexMap::new(),
config: ValidationCacheConfig::default(),
}
}
pub fn get(&mut self, content_hash: &str) -> Option<ValidationResult> {
if let Some(cached) = self.results.get_mut(content_hash) {
if cached.created_at.elapsed() > self.config.ttl {
self.results.shift_remove(content_hash);
return None;
}
cached.last_accessed = Instant::now();
cached.access_count += 1;
Some(cached.result.clone())
} else {
None
}
}
pub fn insert(&mut self, content_hash: String, result: ValidationResult) {
if self.results.len() >= self.config.max_entries {
self.evict_lru();
}
self.results.insert(
content_hash,
CachedValidationResult {
result,
created_at: Instant::now(),
last_accessed: Instant::now(),
access_count: 0,
},
);
}
fn evict_lru(&mut self) {
if let Some((key, _)) = self
.results
.iter()
.min_by_key(|(_, cached)| cached.last_accessed)
.map(|(k, v)| (k.clone(), v.last_accessed))
{
self.results.shift_remove(&key);
}
}
pub fn prune_expired(&mut self) {
let ttl = self.config.ttl;
self.results
.retain(|_, cached| cached.created_at.elapsed() <= ttl);
}
pub fn clear(&mut self) {
self.results.clear();
}
pub fn hit_rate(&self) -> f64 {
if self.results.is_empty() {
0.0
} else {
let total_accesses: usize = self
.results
.values()
.map(|cached| cached.access_count)
.sum();
if total_accesses == 0 {
0.0
} else {
self.results.len() as f64 / total_accesses as f64
}
}
}
}
#[derive(Debug)]
struct CachedValidationResult {
result: ValidationResult,
created_at: Instant,
last_accessed: Instant,
access_count: usize,
}
#[derive(Debug)]
struct ValidationCacheConfig {
max_entries: usize,
ttl: Duration,
}
impl Default for ValidationCacheConfig {
fn default() -> Self {
Self {
max_entries: 1000,
ttl: Duration::from_secs(300), }
}
}
#[derive(Debug, Clone)]
pub struct ValidationResult {
pub is_valid: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub validation_time: Duration,
}
#[derive(Debug)]
pub struct HashCache {
hashes: IndexMap<HashKey, CachedHash>,
config: HashCacheConfig,
}
impl HashCache {
pub fn new() -> Self {
Self {
hashes: IndexMap::new(),
config: HashCacheConfig::default(),
}
}
pub fn get_or_compute<T: Hash>(
&mut self,
key: &HashKey,
value: &T,
hasher_fn: impl FnOnce(&T) -> String,
) -> String {
if let Some(cached) = self.hashes.get_mut(key) {
if cached.created_at.elapsed() <= self.config.ttl {
cached.access_count += 1;
return cached.hash.clone();
} else {
self.hashes.shift_remove(key);
}
}
let hash = hasher_fn(value);
if self.hashes.len() >= self.config.max_entries {
self.evict_random();
}
self.hashes.insert(
key.clone(),
CachedHash {
hash: hash.clone(),
created_at: Instant::now(),
access_count: 1,
},
);
hash
}
fn evict_random(&mut self) {
if let Some(key) = self.hashes.keys().next().cloned() {
self.hashes.shift_remove(&key);
}
}
pub fn prune_expired(&mut self) {
let ttl = self.config.ttl;
self.hashes
.retain(|_, cached| cached.created_at.elapsed() <= ttl);
}
pub fn clear(&mut self) {
self.hashes.clear();
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct HashKey {
pub algorithm: String,
pub content_type: String,
pub content_id: String,
}
#[derive(Debug)]
struct CachedHash {
hash: String,
created_at: Instant,
access_count: usize,
}
#[derive(Debug)]
struct HashCacheConfig {
max_entries: usize,
ttl: Duration,
}
impl Default for HashCacheConfig {
fn default() -> Self {
Self {
max_entries: 500,
ttl: Duration::from_secs(600), }
}
}
#[derive(Debug)]
pub struct TemplateCache {
templates: IndexMap<TemplateKey, CachedTemplate>,
config: TemplateCacheConfig,
}
impl TemplateCache {
pub fn new() -> Self {
Self {
templates: IndexMap::new(),
config: TemplateCacheConfig::default(),
}
}
pub fn get_or_compile(
&mut self,
key: &TemplateKey,
compiler: impl FnOnce() -> CompiledTemplate,
) -> &CompiledTemplate {
if !self.templates.contains_key(key) {
if self.templates.len() >= self.config.max_entries {
self.evict_lru();
}
let template = compiler();
self.templates.insert(
key.clone(),
CachedTemplate {
template,
created_at: Instant::now(),
last_used: Instant::now(),
use_count: 0,
},
);
}
if let Some(cached) = self.templates.get_mut(key) {
cached.last_used = Instant::now();
cached.use_count += 1;
}
&self.templates.get(key).unwrap().template
}
fn evict_lru(&mut self) {
if let Some((key, _)) = self
.templates
.iter()
.min_by_key(|(_, cached)| cached.last_used)
.map(|(k, v)| (k.clone(), v.last_used))
{
self.templates.shift_remove(&key);
}
}
pub fn prune_expired(&mut self) {
let ttl = self.config.ttl;
self.templates
.retain(|_, cached| cached.created_at.elapsed() <= ttl);
}
pub fn clear(&mut self) {
self.templates.clear();
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TemplateKey {
pub element_type: String,
pub version: String,
pub variant: Option<String>,
}
#[derive(Debug)]
struct CachedTemplate {
template: CompiledTemplate,
created_at: Instant,
last_used: Instant,
use_count: usize,
}
#[derive(Debug, Clone)]
pub struct CompiledTemplate {
pub parts: Vec<TemplatePart>,
pub required_fields: Vec<String>,
pub estimated_size: usize,
}
#[derive(Debug, Clone)]
pub enum TemplatePart {
Static(OptimizedString),
Placeholder(String),
}
#[derive(Debug)]
struct TemplateCacheConfig {
max_entries: usize,
ttl: Duration,
}
impl Default for TemplateCacheConfig {
fn default() -> Self {
Self {
max_entries: 100,
ttl: Duration::from_secs(1800), }
}
}
#[derive(Debug, Default, Clone)]
pub struct CacheStats {
pub schema_hits: usize,
pub schema_misses: usize,
pub validation_hits: usize,
pub validation_misses: usize,
pub hash_hits: usize,
pub hash_misses: usize,
pub template_hits: usize,
pub template_misses: usize,
}
impl CacheStats {
pub fn overall_hit_rate(&self) -> f64 {
let total_hits =
self.schema_hits + self.validation_hits + self.hash_hits + self.template_hits;
let total_requests = total_hits
+ self.schema_misses
+ self.validation_misses
+ self.hash_misses
+ self.template_misses;
if total_requests == 0 {
0.0
} else {
total_hits as f64 / total_requests as f64
}
}
pub fn summary(&self) -> String {
format!(
"Cache Hit Rate: {:.1}% (Schema: {:.1}%, Validation: {:.1}%, Hash: {:.1}%, Template: {:.1}%)",
self.overall_hit_rate() * 100.0,
self.schema_hit_rate() * 100.0,
self.validation_hit_rate() * 100.0,
self.hash_hit_rate() * 100.0,
self.template_hit_rate() * 100.0,
)
}
fn schema_hit_rate(&self) -> f64 {
let total = self.schema_hits + self.schema_misses;
if total == 0 {
0.0
} else {
self.schema_hits as f64 / total as f64
}
}
fn validation_hit_rate(&self) -> f64 {
let total = self.validation_hits + self.validation_misses;
if total == 0 {
0.0
} else {
self.validation_hits as f64 / total as f64
}
}
fn hash_hit_rate(&self) -> f64 {
let total = self.hash_hits + self.hash_misses;
if total == 0 {
0.0
} else {
self.hash_hits as f64 / total as f64
}
}
fn template_hit_rate(&self) -> f64 {
let total = self.template_hits + self.template_misses;
if total == 0 {
0.0
} else {
self.template_hits as f64 / total as f64
}
}
}
pub struct CacheManager;
impl CacheManager {
pub fn stats() -> CacheStats {
GLOBAL_CACHE.read().unwrap().stats().clone()
}
pub fn clear_all() {
GLOBAL_CACHE.write().unwrap().clear_all();
}
pub fn prune_expired() {
GLOBAL_CACHE.write().unwrap().prune_expired();
}
pub fn get_schema(
version: &str,
profile: Option<&str>,
compiler: impl FnOnce() -> Result<CompiledSchema, BuildError>,
) -> Result<CompiledSchema, BuildError> {
let mut cache = GLOBAL_CACHE.write().unwrap();
let schema = cache.schemas.get_or_compile(version, profile, compiler)?;
Ok(schema.clone())
}
pub fn fast_hash<T: Hash + std::fmt::Debug>(
algorithm: &str,
content_type: &str,
content_id: &str,
value: &T,
) -> String {
let key = HashKey {
algorithm: algorithm.to_string(),
content_type: content_type.to_string(),
content_id: content_id.to_string(),
};
let mut cache = GLOBAL_CACHE.write().unwrap();
cache.hash_cache.get_or_compute(&key, value, |v| {
match algorithm {
"blake3" => {
let mut hasher = Blake3Hasher::new();
let bytes = format!("{:?}", v); hasher.update(bytes.as_bytes());
hasher.finalize().to_hex().to_string()
}
_ => {
let mut hasher = std::hash::DefaultHasher::new();
v.hash(&mut hasher);
format!("{:016x}", hasher.finish())
}
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_schema_cache() {
let mut cache = SchemaCache::new();
let schema = cache
.get_or_compile("4.3", None, || {
Ok(CompiledSchema {
version: "4.3".to_string(),
profile: None,
rules: vec![],
required_elements: vec!["MessageHeader".to_string()],
element_constraints: IndexMap::new(),
})
})
.unwrap();
assert_eq!(schema.version, "4.3");
assert!(cache.contains("4.3", None));
let schema2 = cache
.get_or_compile("4.3", None, || panic!("Should not compile again"))
.unwrap();
assert_eq!(schema2.version, "4.3");
}
#[test]
fn test_validation_cache() {
let mut cache = ValidationCache::new();
let hash = "test_hash".to_string();
assert!(cache.get(&hash).is_none());
let result = ValidationResult {
is_valid: true,
errors: vec![],
warnings: vec![],
validation_time: Duration::from_millis(10),
};
cache.insert(hash.clone(), result);
let cached = cache.get(&hash).unwrap();
assert!(cached.is_valid);
}
#[test]
fn test_hash_cache() {
let mut cache = HashCache::new();
let key = HashKey {
algorithm: "blake3".to_string(),
content_type: "track".to_string(),
content_id: "T001".to_string(),
};
let test_value = "test content";
let hash1 = cache.get_or_compute(&key, &test_value, |v| format!("hash_{}", v));
let hash2 = cache.get_or_compute(&key, &test_value, |_| panic!("Should not compute again"));
assert_eq!(hash1, hash2);
assert_eq!(hash1, "hash_test content");
}
#[test]
fn test_cache_manager() {
CacheManager::clear_all();
let stats = CacheManager::stats();
assert_eq!(stats.overall_hit_rate(), 0.0);
let hash1 = CacheManager::fast_hash("blake3", "test", "item1", &"content");
let hash2 = CacheManager::fast_hash("blake3", "test", "item1", &"content");
assert_eq!(hash1, hash2); }
#[test]
fn test_template_cache() {
let mut cache = TemplateCache::new();
let key = TemplateKey {
element_type: "SoundRecording".to_string(),
version: "4.3".to_string(),
variant: None,
};
let template = cache.get_or_compile(&key, || CompiledTemplate {
parts: vec![
TemplatePart::Static(OptimizedString::new("<SoundRecording>")),
TemplatePart::Placeholder("title".to_string()),
TemplatePart::Static(OptimizedString::new("</SoundRecording>")),
],
required_fields: vec!["title".to_string()],
estimated_size: 100,
});
assert_eq!(template.required_fields.len(), 1);
let template2 = cache.get_or_compile(&key, || panic!("Should not compile again"));
assert_eq!(template2.required_fields.len(), 1);
}
}