use std::collections::HashMap;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::sync::Mutex;
use serde_json::Value;
use crate::SchemaAdapter;
#[derive(Debug, Default)]
pub struct SchemaCache {
entries: Mutex<HashMap<u64, Value>>,
}
impl SchemaCache {
pub fn new() -> Self {
Self { entries: Mutex::new(HashMap::new()) }
}
pub fn get_or_normalize(&self, schema: &Value, adapter: &dyn SchemaAdapter) -> Value {
let hash = Self::hash_schema(schema);
let mut cache = self.entries.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
cache.entry(hash).or_insert_with(|| adapter.normalize_schema(schema.clone())).clone()
}
pub fn clear(&self) {
let mut cache = self.entries.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
cache.clear();
}
pub fn len(&self) -> usize {
let cache = self.entries.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
cache.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn hash_schema(schema: &Value) -> u64 {
let bytes = serde_json::to_vec(schema).unwrap_or_default();
let mut hasher = DefaultHasher::new();
bytes.hash(&mut hasher);
hasher.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use crate::GenericSchemaAdapter;
#[test]
fn test_cache_returns_normalized_schema() {
let cache = SchemaCache::new();
let adapter = GenericSchemaAdapter;
let schema = json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": { "name": { "type": "string" } }
});
let result = cache.get_or_normalize(&schema, &adapter);
assert!(result.get("$schema").is_none());
assert_eq!(result["type"], "object");
}
#[test]
fn test_cache_returns_same_result_on_repeated_calls() {
let cache = SchemaCache::new();
let adapter = GenericSchemaAdapter;
let schema = json!({
"type": "object",
"properties": { "x": { "type": "integer", "const": 42 } }
});
let first = cache.get_or_normalize(&schema, &adapter);
let second = cache.get_or_normalize(&schema, &adapter);
assert_eq!(first, second);
}
#[test]
fn test_cache_stores_entries() {
let cache = SchemaCache::new();
let adapter = GenericSchemaAdapter;
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
let schema1 = json!({"type": "string"});
let schema2 = json!({"type": "number"});
cache.get_or_normalize(&schema1, &adapter);
assert_eq!(cache.len(), 1);
cache.get_or_normalize(&schema2, &adapter);
assert_eq!(cache.len(), 2);
cache.get_or_normalize(&schema1, &adapter);
assert_eq!(cache.len(), 2);
}
#[test]
fn test_cache_clear_removes_all_entries() {
let cache = SchemaCache::new();
let adapter = GenericSchemaAdapter;
cache.get_or_normalize(&json!({"type": "string"}), &adapter);
cache.get_or_normalize(&json!({"type": "number"}), &adapter);
assert_eq!(cache.len(), 2);
cache.clear();
assert!(cache.is_empty());
}
#[test]
fn test_cache_different_schemas_produce_different_entries() {
let cache = SchemaCache::new();
let adapter = GenericSchemaAdapter;
let schema_a = json!({"type": "string", "format": "hostname"});
let schema_b = json!({"type": "string", "format": "email"});
let result_a = cache.get_or_normalize(&schema_a, &adapter);
let result_b = cache.get_or_normalize(&schema_b, &adapter);
assert!(result_a.get("format").is_none());
assert_eq!(result_b["format"], "email");
assert_eq!(cache.len(), 2);
}
#[test]
fn test_cache_new_is_empty() {
let cache = SchemaCache::new();
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
}
#[test]
fn test_cache_default_is_empty() {
let cache = SchemaCache::default();
assert!(cache.is_empty());
}
#[test]
fn test_cache_handles_empty_schema() {
let cache = SchemaCache::new();
let adapter = GenericSchemaAdapter;
let schema = json!({});
let result = cache.get_or_normalize(&schema, &adapter);
assert_eq!(result, json!({}));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_cache_handles_null_schema() {
let cache = SchemaCache::new();
let adapter = GenericSchemaAdapter;
let schema = Value::Null;
let result = cache.get_or_normalize(&schema, &adapter);
assert_eq!(result, Value::Null);
assert_eq!(cache.len(), 1);
}
}