prax_query/data_cache/
options.rs

1//! Cache options and configuration.
2
3use std::time::Duration;
4
5use super::invalidation::EntityTag;
6
7/// Options for caching a query result.
8#[derive(Debug, Clone)]
9pub struct CacheOptions {
10    /// Time-to-live for the cached entry.
11    pub ttl: Option<Duration>,
12    /// Cache policy to use.
13    pub policy: CachePolicy,
14    /// Write policy (when to write to cache).
15    pub write_policy: WritePolicy,
16    /// Tags for invalidation.
17    pub tags: Vec<EntityTag>,
18    /// Skip caching if result is larger than this size (bytes).
19    pub max_size: Option<usize>,
20    /// Whether to cache empty results.
21    pub cache_empty: bool,
22    /// Whether to bypass cache for this query.
23    pub bypass: bool,
24    /// Stale-while-revalidate duration.
25    pub stale_while_revalidate: Option<Duration>,
26}
27
28impl Default for CacheOptions {
29    fn default() -> Self {
30        Self {
31            ttl: Some(Duration::from_secs(300)), // 5 minutes
32            policy: CachePolicy::CacheFirst,
33            write_policy: WritePolicy::WriteThrough,
34            tags: Vec::new(),
35            max_size: Some(1024 * 1024), // 1MB
36            cache_empty: true,
37            bypass: false,
38            stale_while_revalidate: None,
39        }
40    }
41}
42
43impl CacheOptions {
44    /// Create options with a specific TTL.
45    pub fn ttl(duration: Duration) -> Self {
46        Self {
47            ttl: Some(duration),
48            ..Default::default()
49        }
50    }
51
52    /// Create options that don't expire.
53    pub fn no_expire() -> Self {
54        Self {
55            ttl: None,
56            ..Default::default()
57        }
58    }
59
60    /// Create options that bypass the cache.
61    pub fn bypass() -> Self {
62        Self {
63            bypass: true,
64            ..Default::default()
65        }
66    }
67
68    /// Set the TTL.
69    pub fn with_ttl(mut self, duration: Duration) -> Self {
70        self.ttl = Some(duration);
71        self
72    }
73
74    /// Set the cache policy.
75    pub fn with_policy(mut self, policy: CachePolicy) -> Self {
76        self.policy = policy;
77        self
78    }
79
80    /// Set the write policy.
81    pub fn with_write_policy(mut self, policy: WritePolicy) -> Self {
82        self.write_policy = policy;
83        self
84    }
85
86    /// Add a tag.
87    pub fn with_tag(mut self, tag: impl Into<EntityTag>) -> Self {
88        self.tags.push(tag.into());
89        self
90    }
91
92    /// Add multiple tags.
93    pub fn with_tags(mut self, tags: impl IntoIterator<Item = EntityTag>) -> Self {
94        self.tags.extend(tags);
95        self
96    }
97
98    /// Set max size.
99    pub fn with_max_size(mut self, size: usize) -> Self {
100        self.max_size = Some(size);
101        self
102    }
103
104    /// Don't limit size.
105    pub fn no_size_limit(mut self) -> Self {
106        self.max_size = None;
107        self
108    }
109
110    /// Don't cache empty results.
111    pub fn no_cache_empty(mut self) -> Self {
112        self.cache_empty = false;
113        self
114    }
115
116    /// Enable stale-while-revalidate.
117    pub fn stale_while_revalidate(mut self, duration: Duration) -> Self {
118        self.stale_while_revalidate = Some(duration);
119        self
120    }
121
122    /// Short TTL preset (1 minute).
123    pub fn short() -> Self {
124        Self::ttl(Duration::from_secs(60))
125    }
126
127    /// Medium TTL preset (5 minutes).
128    pub fn medium() -> Self {
129        Self::ttl(Duration::from_secs(300))
130    }
131
132    /// Long TTL preset (1 hour).
133    pub fn long() -> Self {
134        Self::ttl(Duration::from_secs(3600))
135    }
136
137    /// Very long TTL preset (1 day).
138    pub fn daily() -> Self {
139        Self::ttl(Duration::from_secs(86400))
140    }
141}
142
143/// Cache lookup/write policy.
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
145pub enum CachePolicy {
146    /// Check cache first, fetch on miss.
147    #[default]
148    CacheFirst,
149
150    /// Always fetch from source, update cache.
151    NetworkFirst,
152
153    /// Return cached value only, never fetch.
154    CacheOnly,
155
156    /// Fetch from source only, don't use cache.
157    NetworkOnly,
158
159    /// Return stale data while revalidating.
160    StaleWhileRevalidate,
161}
162
163impl CachePolicy {
164    /// Check if this policy should check cache.
165    pub fn should_check_cache(&self) -> bool {
166        matches!(
167            self,
168            Self::CacheFirst | Self::CacheOnly | Self::StaleWhileRevalidate
169        )
170    }
171
172    /// Check if this policy should fetch from source.
173    pub fn should_fetch(&self) -> bool {
174        matches!(
175            self,
176            Self::CacheFirst | Self::NetworkFirst | Self::NetworkOnly | Self::StaleWhileRevalidate
177        )
178    }
179
180    /// Check if this policy should update cache.
181    pub fn should_update_cache(&self) -> bool {
182        matches!(
183            self,
184            Self::CacheFirst | Self::NetworkFirst | Self::StaleWhileRevalidate
185        )
186    }
187}
188
189/// When to write to the cache.
190#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
191pub enum WritePolicy {
192    /// Write to cache immediately.
193    #[default]
194    WriteThrough,
195
196    /// Write to cache in background.
197    WriteBack,
198
199    /// Write to cache after a delay.
200    WriteDelayed,
201}
202
203/// Preset configurations for common use cases.
204pub mod presets {
205    use super::*;
206
207    /// For frequently changing data (news feeds, notifications).
208    pub fn volatile() -> CacheOptions {
209        CacheOptions::ttl(Duration::from_secs(30))
210            .with_policy(CachePolicy::StaleWhileRevalidate)
211            .stale_while_revalidate(Duration::from_secs(60))
212    }
213
214    /// For user profiles and settings.
215    pub fn user_data() -> CacheOptions {
216        CacheOptions::ttl(Duration::from_secs(300))
217            .with_policy(CachePolicy::CacheFirst)
218    }
219
220    /// For reference/lookup data that rarely changes.
221    pub fn reference_data() -> CacheOptions {
222        CacheOptions::ttl(Duration::from_secs(3600))
223            .with_policy(CachePolicy::CacheFirst)
224    }
225
226    /// For static data that almost never changes.
227    pub fn static_data() -> CacheOptions {
228        CacheOptions::ttl(Duration::from_secs(86400))
229            .with_policy(CachePolicy::CacheFirst)
230    }
231
232    /// For session data.
233    pub fn session() -> CacheOptions {
234        CacheOptions::ttl(Duration::from_secs(1800)) // 30 minutes
235            .with_policy(CachePolicy::CacheFirst)
236    }
237
238    /// For real-time data that shouldn't be cached.
239    pub fn realtime() -> CacheOptions {
240        CacheOptions::bypass()
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247
248    #[test]
249    fn test_default_options() {
250        let opts = CacheOptions::default();
251        assert_eq!(opts.ttl, Some(Duration::from_secs(300)));
252        assert_eq!(opts.policy, CachePolicy::CacheFirst);
253        assert!(opts.cache_empty);
254        assert!(!opts.bypass);
255    }
256
257    #[test]
258    fn test_options_builder() {
259        let opts = CacheOptions::ttl(Duration::from_secs(60))
260            .with_policy(CachePolicy::NetworkFirst)
261            .with_tag(EntityTag::new("User"))
262            .no_cache_empty();
263
264        assert_eq!(opts.ttl, Some(Duration::from_secs(60)));
265        assert_eq!(opts.policy, CachePolicy::NetworkFirst);
266        assert_eq!(opts.tags.len(), 1);
267        assert!(!opts.cache_empty);
268    }
269
270    #[test]
271    fn test_cache_policy() {
272        assert!(CachePolicy::CacheFirst.should_check_cache());
273        assert!(CachePolicy::CacheFirst.should_fetch());
274        assert!(CachePolicy::CacheFirst.should_update_cache());
275
276        assert!(!CachePolicy::NetworkOnly.should_check_cache());
277        assert!(CachePolicy::NetworkOnly.should_fetch());
278        assert!(!CachePolicy::NetworkOnly.should_update_cache());
279
280        assert!(CachePolicy::CacheOnly.should_check_cache());
281        assert!(!CachePolicy::CacheOnly.should_fetch());
282        assert!(!CachePolicy::CacheOnly.should_update_cache());
283    }
284
285    #[test]
286    fn test_presets() {
287        let volatile = presets::volatile();
288        assert_eq!(volatile.ttl, Some(Duration::from_secs(30)));
289
290        let reference = presets::reference_data();
291        assert_eq!(reference.ttl, Some(Duration::from_secs(3600)));
292
293        let realtime = presets::realtime();
294        assert!(realtime.bypass);
295    }
296}
297
298