Skip to main content

uxc/cache/
mod.rs

1//! Schema caching module for improved performance
2//!
3//! Provides filesystem-based caching for schemas across all protocols (OpenAPI, gRPC, GraphQL, MCP).
4//! Cache is stored in ~/.uxc/cache/schemas/ with TTL-based expiration.
5
6mod config;
7mod stats;
8mod storage;
9
10pub use config::CacheConfig;
11#[allow(unused_imports)]
12pub use config::CacheOptions;
13pub use stats::CacheStats;
14pub use storage::SchemaCache;
15#[allow(unused_imports)]
16pub use storage::{CacheEntry, CacheStorage};
17
18use anyhow::Result;
19use serde_json::Value;
20use std::sync::Arc;
21
22/// Default cache TTL in seconds (24 hours)
23pub const DEFAULT_CACHE_TTL: u64 = 86400;
24
25/// Default cache directory relative to home directory
26pub const DEFAULT_CACHE_DIR: &str = ".uxc/cache/schemas";
27
28/// Cache result indicating whether the value was retrieved from cache
29#[derive(Debug, Clone)]
30pub enum CacheResult {
31    /// Value was retrieved from cache
32    Hit(Value),
33    /// Value was not in cache or was expired
34    Miss,
35    /// Cache was bypassed (e.g., --no-cache flag)
36    Bypassed,
37}
38
39impl CacheResult {
40    #[allow(dead_code)]
41    pub fn is_hit(&self) -> bool {
42        matches!(self, CacheResult::Hit(_))
43    }
44
45    #[allow(dead_code)]
46    pub fn is_miss(&self) -> bool {
47        matches!(self, CacheResult::Miss)
48    }
49
50    #[allow(dead_code)]
51    pub fn is_bypassed(&self) -> bool {
52        matches!(self, CacheResult::Bypassed)
53    }
54}
55
56/// Main cache interface for adapters
57///
58/// This trait provides a simple interface for protocol adapters to interact
59/// with the cache system.
60pub trait Cache: Send + Sync {
61    /// Get a schema from cache
62    ///
63    /// Returns `CacheResult::Hit` if the schema is found and valid,
64    /// `CacheResult::Miss` if not found or expired, or `CacheResult::Bypassed`
65    /// if caching is disabled.
66    fn get(&self, url: &str) -> Result<CacheResult>;
67
68    /// Put a schema into cache
69    ///
70    /// Stores the schema with metadata including timestamp and TTL.
71    /// If caching is disabled, this is a no-op.
72    fn put(&self, url: &str, schema: &Value) -> Result<()>;
73
74    /// Invalidate a specific cache entry
75    fn invalidate(&self, url: &str) -> Result<()>;
76
77    /// Clear all cache entries
78    fn clear(&self) -> Result<()>;
79
80    /// Get cache statistics
81    fn stats(&self) -> Result<CacheStats>;
82
83    /// Check if caching is enabled
84    #[allow(dead_code)]
85    fn is_enabled(&self) -> bool;
86}
87
88/// Create a new schema cache instance with the given configuration
89pub fn create_cache(config: CacheConfig) -> Result<Arc<dyn Cache>> {
90    Ok(Arc::new(SchemaCache::new(config)?))
91}
92
93/// Create a cache with default settings
94#[allow(dead_code)]
95pub fn create_default_cache() -> Result<Arc<dyn Cache>> {
96    Ok(Arc::new(SchemaCache::with_default_config()?))
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_cache_result() {
105        let hit = CacheResult::Hit(serde_json::json!( {"test": "data"} ));
106        assert!(hit.is_hit());
107        assert!(!hit.is_miss());
108        assert!(!hit.is_bypassed());
109
110        let miss = CacheResult::Miss;
111        assert!(!miss.is_hit());
112        assert!(miss.is_miss());
113        assert!(!miss.is_bypassed());
114
115        let bypassed = CacheResult::Bypassed;
116        assert!(!bypassed.is_hit());
117        assert!(!bypassed.is_miss());
118        assert!(bypassed.is_bypassed());
119    }
120}