json_eval_rs/
parsed_schema_cache.rs

1/// Built-in cache store for Arc<ParsedSchema> instances
2/// 
3/// Provides thread-safe caching of parsed schemas with caller-controlled lifecycle:
4/// - Caller provides unique keys
5/// - Caller decides when to re-parse
6/// - Caller controls cache clearing
7/// - Caller manages memory release
8
9use std::sync::{Arc, RwLock};
10use indexmap::IndexMap;
11use crate::ParsedSchema;
12
13/// Thread-safe cache for storing and reusing ParsedSchema instances
14/// 
15/// # Example
16/// ```
17/// use json_eval_rs::{ParsedSchemaCache, ParsedSchema};
18/// use std::sync::Arc;
19/// 
20/// let cache = ParsedSchemaCache::new();
21/// 
22/// // Parse and cache a schema
23/// let schema_json = r#"{"type": "object"}"#;
24/// let schema = ParsedSchema::parse(schema_json).unwrap();
25/// cache.insert("my-schema".to_string(), Arc::new(schema));
26/// 
27/// // Retrieve from cache
28/// if let Some(cached) = cache.get("my-schema") {
29///     // Use cached schema for evaluation
30/// }
31/// 
32/// // Remove specific entry
33/// cache.remove("my-schema");
34/// 
35/// // Clear all entries
36/// cache.clear();
37/// ```
38#[derive(Clone)]
39pub struct ParsedSchemaCache {
40    cache: Arc<RwLock<IndexMap<String, Arc<ParsedSchema>>>>,
41}
42
43impl ParsedSchemaCache {
44    /// Create a new empty cache
45    pub fn new() -> Self {
46        Self {
47            cache: Arc::new(RwLock::new(IndexMap::new())),
48        }
49    }
50    
51    /// Insert or update a parsed schema with the given key
52    /// 
53    /// Returns the previous value if the key already existed
54    pub fn insert(&self, key: String, schema: Arc<ParsedSchema>) -> Option<Arc<ParsedSchema>> {
55        let mut cache = self.cache.write().unwrap();
56        cache.insert(key, schema)
57    }
58    
59    /// Get a cloned Arc reference to the cached schema
60    /// 
61    /// Returns None if the key doesn't exist
62    pub fn get(&self, key: &str) -> Option<Arc<ParsedSchema>> {
63        let cache = self.cache.read().unwrap();
64        cache.get(key).cloned()
65    }
66    
67    /// Remove and return the schema for the given key
68    /// 
69    /// Returns None if the key doesn't exist
70    pub fn remove(&self, key: &str) -> Option<Arc<ParsedSchema>> {
71        let mut cache = self.cache.write().unwrap();
72        cache.shift_remove(key)
73    }
74    
75    /// Clear all cached schemas
76    pub fn clear(&self) {
77        let mut cache = self.cache.write().unwrap();
78        cache.clear();
79    }
80    
81    /// Check if a key exists in the cache
82    pub fn contains_key(&self, key: &str) -> bool {
83        let cache = self.cache.read().unwrap();
84        cache.contains_key(key)
85    }
86    
87    /// Get the number of cached schemas
88    pub fn len(&self) -> usize {
89        let cache = self.cache.read().unwrap();
90        cache.len()
91    }
92    
93    /// Check if the cache is empty
94    pub fn is_empty(&self) -> bool {
95        self.len() == 0
96    }
97    
98    /// Get all keys currently in the cache
99    pub fn keys(&self) -> Vec<String> {
100        let cache = self.cache.read().unwrap();
101        cache.keys().cloned().collect()
102    }
103    
104    /// Get cache statistics
105    pub fn stats(&self) -> ParsedSchemaCacheStats {
106        let cache = self.cache.read().unwrap();
107        ParsedSchemaCacheStats {
108            entry_count: cache.len(),
109            keys: cache.keys().cloned().collect(),
110        }
111    }
112    
113    /// Get or insert a schema using a factory function
114    /// 
115    /// If the key exists, returns the cached value.
116    /// Otherwise, calls the factory function to create a new value,
117    /// inserts it, and returns it.
118    /// 
119    /// # Example
120    /// ```
121    /// use json_eval_rs::{ParsedSchemaCache, ParsedSchema};
122    /// use std::sync::Arc;
123    /// 
124    /// let cache = ParsedSchemaCache::new();
125    /// let json = r#"{"type": "object"}"#;
126    /// let schema = cache.get_or_insert_with("my-schema", || {
127    ///     Arc::new(ParsedSchema::parse(json).unwrap())
128    /// });
129    /// ```
130    pub fn get_or_insert_with<F>(&self, key: &str, factory: F) -> Arc<ParsedSchema>
131    where
132        F: FnOnce() -> Arc<ParsedSchema>,
133    {
134        // Try read first (fast path)
135        {
136            let cache = self.cache.read().unwrap();
137            if let Some(schema) = cache.get(key) {
138                return schema.clone();
139            }
140        }
141        
142        // Need to insert (slow path)
143        let mut cache = self.cache.write().unwrap();
144        // Double-check in case another thread inserted while we waited for write lock
145        if let Some(schema) = cache.get(key) {
146            return schema.clone();
147        }
148        
149        let schema = factory();
150        cache.insert(key.to_string(), schema.clone());
151        schema
152    }
153    
154    /// Batch insert multiple schemas at once
155    pub fn insert_batch(&self, entries: Vec<(String, Arc<ParsedSchema>)>) {
156        let mut cache = self.cache.write().unwrap();
157        for (key, schema) in entries {
158            cache.insert(key, schema);
159        }
160    }
161    
162    /// Remove multiple keys at once
163    pub fn remove_batch(&self, keys: &[String]) -> Vec<(String, Arc<ParsedSchema>)> {
164        let mut cache = self.cache.write().unwrap();
165        let mut removed = Vec::new();
166        for key in keys {
167            if let Some(schema) = cache.shift_remove(key) {
168                removed.push((key.clone(), schema));
169            }
170        }
171        removed
172    }
173}
174
175impl Default for ParsedSchemaCache {
176    fn default() -> Self {
177        Self::new()
178    }
179}
180
181/// Statistics about the cache state
182#[derive(Debug, Clone)]
183pub struct ParsedSchemaCacheStats {
184    /// Number of entries in the cache
185    pub entry_count: usize,
186    /// List of all keys
187    pub keys: Vec<String>,
188}
189
190impl std::fmt::Display for ParsedSchemaCacheStats {
191    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192        write!(f, "ParsedSchemaCache: {} entries", self.entry_count)?;
193        if !self.keys.is_empty() {
194            write!(f, " (keys: {})", self.keys.join(", "))?;
195        }
196        Ok(())
197    }
198}
199
200// Optional: Global cache instance for convenience
201use once_cell::sync::Lazy;
202
203/// Global ParsedSchema cache instance
204/// 
205/// Convenient for applications that want a single global cache
206/// without managing their own instance.
207/// 
208/// # Example
209/// ```
210/// use json_eval_rs::{PARSED_SCHEMA_CACHE, ParsedSchema};
211/// use std::sync::Arc;
212/// 
213/// let schema = Arc::new(ParsedSchema::parse(r#"{"type": "object"}"#).unwrap());
214/// PARSED_SCHEMA_CACHE.insert("global-schema".to_string(), schema);
215/// let cached = PARSED_SCHEMA_CACHE.get("global-schema");
216/// ```
217pub static PARSED_SCHEMA_CACHE: Lazy<ParsedSchemaCache> = Lazy::new(ParsedSchemaCache::new);
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222    
223    #[test]
224    fn test_cache_basic_operations() {
225        let cache = ParsedSchemaCache::new();
226        assert_eq!(cache.len(), 0);
227        assert!(cache.is_empty());
228        
229        // Insert doesn't require a real ParsedSchema for this test
230        // In real usage, you'd use ParsedSchema::from_json
231        
232        assert!(!cache.contains_key("test"));
233        assert_eq!(cache.keys().len(), 0);
234    }
235    
236    #[test]
237    fn test_cache_clone() {
238        let cache1 = ParsedSchemaCache::new();
239        let cache2 = cache1.clone();
240        
241        // Both should share the same underlying cache
242        assert_eq!(cache1.len(), cache2.len());
243    }
244}