1use super::store;
2use crate::store::StoreErr;
3use serde::{Deserialize, Serialize};
4use std::{fmt::Debug, time::Duration};
5#[cfg(feature = "tracing")]
6use tracing::{debug, warn};
7
8#[derive(Clone)]
10pub struct TinyRef {
11 cache_name: String,
12 max_cache_age: Option<Duration>,
13 ignore_cache: bool,
14}
15
16const CACHE_NAME: &str = ".tiny_cache";
17
18impl TinyRef {
19 pub fn with_name(cache_name: impl ToString) -> Self {
20 Self {
21 cache_name: cache_name.to_string(),
22 max_cache_age: None,
23 ignore_cache: false,
24 }
25 }
26 pub fn new() -> Self {
27 Self {
28 cache_name: CACHE_NAME.to_string(),
29 max_cache_age: None,
30 ignore_cache: false,
31 }
32 }
33 pub fn max_age(mut self, max_duration: Duration) -> Self {
35 self.max_cache_age = Some(max_duration);
36 self
37 }
38
39 pub fn no_cache(&self) -> Self {
40 let mut inner = self.clone();
41 inner.ignore_cache = true;
42 inner
43 }
44}
45
46impl TinyRef {
47 pub fn get_cached_or_fetch<T, Fun>(&self, item_key: impl ToString, fetch_with: Fun) -> T
49 where
50 for<'a> T: Deserialize<'a> + Serialize + Debug,
51 Fun: FnOnce() -> T,
52 {
53 let k = item_key.to_string();
54 if !self.ignore_cache {
55 if let Some(res) = self.read(k.clone()) {
56 return res;
57 }
58 } else {
59 #[cfg(feature = "tracing")]
60 debug!("Ignoring cache on {:?}", k);
61 }
62 let item_from_fut = fetch_with();
63 self.write(item_key, &item_from_fut);
64 item_from_fut
65 }
66 pub fn write<T>(&self, item_key: impl ToString, v: &T)
67 where
68 for<'a> T: Deserialize<'a> + Serialize + Debug,
69 {
70 let item_key = item_key.to_string();
71 if let Err(e) = store::write(self.cache_name.clone(), item_key.clone(), v) {
72 #[cfg(feature = "tracing")]
73 warn!(
74 "failed to write to cache `{}` -> `{}` {:?}",
75 item_key, self.cache_name, e
76 );
77 } else {
78 #[cfg(feature = "tracing")]
79 debug!("SAVE `{}` -> `{}`", item_key, self.cache_name);
80 }
81 }
82 pub fn item_age(&self, item_key: impl ToString) -> Option<Duration> {
83 store::item_age(self.cache_name.clone(), item_key.to_string()).ok()
84 }
85 pub fn read<T>(&self, item_key: impl ToString) -> Option<T>
86 where
87 for<'a> T: Deserialize<'a> + Serialize + Debug,
88 {
89 let item_key = item_key.to_string();
90 if let Some(max_age) = self.max_cache_age {
92 if let Some(age) = self.item_age(item_key.clone()) {
93 if age > max_age {
94 #[cfg(feature = "tracing")]
95 debug!("CACHE `{}` -> TOO OLD. AGE: {:?}", item_key, age);
96 self.invalidate(item_key);
97 return None;
98 }
99 }
100 }
101 match store::read::<T>(self.cache_name.clone(), item_key.clone()) {
102 Ok(v) => Some(v),
103 Err(e) => {
104 if let StoreErr::Ser(e) = e {
105 #[cfg(feature = "tracing")]
107 warn!(
108 "Failed to deserialize {:?}, invalidating cache -> `{}`",
109 e, item_key
110 );
111 self.invalidate(item_key);
112 }
113 None
114 }
115 }
116 }
117 pub fn invalidate(&self, item_key: impl ToString) {
118 if let Err(e) = store::remove(self.cache_name.clone(), item_key.to_string()) {
119 #[cfg(feature = "tracing")]
120 warn!("Failed to invalidate cache {:?}", e);
121 }
122 }
123}
124
125#[cfg(test)]
126mod test {
127 use super::*;
128
129 #[test]
130 fn test_cache() {
131 let tiny = TinyRef::new();
132 #[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
133 pub struct TestStruct {
134 a: String,
135 }
136 let test_struct = TestStruct { a: "hello".into() };
137 let key = "testval";
138
139 tiny.write(key, &test_struct);
141 let stored_struct: TestStruct = tiny.read(key).unwrap();
142 assert_eq!(stored_struct, test_struct);
143
144 std::thread::sleep(Duration::from_millis(100));
146 let age = tiny.item_age(key).unwrap();
147 println!("age {:?}", age);
148 assert!(age > Duration::from_millis(50));
149
150 tiny.invalidate(key);
152 assert_eq!(tiny.read::<TestStruct>(key), None);
153 }
154}