1use std::time::{Duration, SystemTime};
18
19use crate::engine::ns;
20use crate::{Result, Store, from_ms, to_ms};
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct CacheEntry {
24 pub value: Vec<u8>,
25 pub stored_at: SystemTime,
26}
27
28impl CacheEntry {
29 pub fn age_at(&self, now: SystemTime) -> Duration {
31 now.duration_since(self.stored_at).unwrap_or_default()
32 }
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct CachePolicy {
38 pub max_age: Duration,
39 pub min_age: Option<Duration>,
40 pub store_in_cache: bool,
41}
42
43impl Default for CachePolicy {
44 fn default() -> Self {
45 Self {
46 max_age: Duration::from_millis(172_800_000),
47 min_age: None,
48 store_in_cache: true,
49 }
50 }
51}
52
53impl CachePolicy {
54 pub fn from_firecrawl(
56 max_age_ms: Option<u64>,
57 min_age_ms: Option<u64>,
58 store_in_cache: Option<bool>,
59 ) -> Self {
60 let d = Self::default();
61 Self {
62 max_age: max_age_ms.map_or(d.max_age, Duration::from_millis),
63 min_age: min_age_ms.map(Duration::from_millis),
64 store_in_cache: store_in_cache.unwrap_or(d.store_in_cache),
65 }
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub enum CacheLookup {
71 Hit(CacheEntry),
73 Miss,
75 NoCachedData,
77}
78
79pub struct Cache<'a> {
80 store: &'a Store,
81}
82
83impl<'a> Cache<'a> {
84 pub(crate) fn new(store: &'a Store) -> Self {
85 Self { store }
86 }
87
88 pub fn entry(&self, key: impl AsRef<[u8]>) -> Result<Option<CacheEntry>> {
90 Ok(self
91 .store
92 .get(ns::CACHE, key.as_ref())?
93 .map(|(ts, value)| CacheEntry {
94 value,
95 stored_at: from_ms(ts),
96 }))
97 }
98
99 pub fn get(&self, key: impl AsRef<[u8]>, max_age: Duration) -> Result<Option<CacheEntry>> {
101 self.get_at(key, max_age, SystemTime::now())
102 }
103
104 pub fn get_at(
105 &self,
106 key: impl AsRef<[u8]>,
107 max_age: Duration,
108 now: SystemTime,
109 ) -> Result<Option<CacheEntry>> {
110 if max_age.is_zero() {
111 return Ok(None);
112 }
113 Ok(self.entry(key)?.filter(|e| e.age_at(now) < max_age))
114 }
115
116 pub fn get_min_age(
118 &self,
119 key: impl AsRef<[u8]>,
120 min_age: Duration,
121 now: SystemTime,
122 ) -> Result<Option<CacheEntry>> {
123 Ok(self.entry(key)?.filter(|e| e.age_at(now) >= min_age))
124 }
125
126 pub fn lookup(&self, key: impl AsRef<[u8]>, policy: &CachePolicy) -> Result<CacheLookup> {
129 let now = SystemTime::now();
130 if let Some(min) = policy.min_age {
131 return Ok(match self.get_min_age(key, min, now)? {
132 Some(e) => CacheLookup::Hit(e),
133 None => CacheLookup::NoCachedData,
134 });
135 }
136 Ok(match self.get_at(key, policy.max_age, now)? {
137 Some(e) => CacheLookup::Hit(e),
138 None => CacheLookup::Miss,
139 })
140 }
141
142 pub fn put(&self, key: impl AsRef<[u8]>, value: &[u8]) -> Result<()> {
143 self.put_at(key, value, SystemTime::now())
144 }
145
146 pub fn put_at(&self, key: impl AsRef<[u8]>, value: &[u8], stored_at: SystemTime) -> Result<()> {
148 self.store
149 .put(ns::CACHE, key.as_ref(), value, to_ms(stored_at))
150 .map(drop)
151 }
152
153 pub fn store(&self, key: impl AsRef<[u8]>, value: &[u8], policy: &CachePolicy) -> Result<bool> {
155 if policy.store_in_cache {
156 self.put(key, value)?;
157 }
158 Ok(policy.store_in_cache)
159 }
160
161 pub fn remove(&self, key: impl AsRef<[u8]>) -> Result<bool> {
162 self.store.delete(ns::CACHE, key.as_ref())
163 }
164}