1use std::cmp::Ordering;
2use std::hash::{Hash, Hasher};
3use std::ops::{Deref, DerefMut};
4use std::time::Duration;
5use std::time::Instant;
6
7#[derive(Debug)]
9pub struct TtlValue<T> {
10 pub value: T,
11 last_touched: Instant,
12 ttl: Duration,
13}
14
15pub type EmptyTtlValue = TtlValue<()>;
17
18impl EmptyTtlValue {
19 pub fn empty(ttl: Duration) -> Self {
20 Self::new((), ttl)
21 }
22}
23
24impl<T> TtlValue<T> {
25 pub fn new(value: T, ttl: Duration) -> Self {
26 Self {
27 value,
28 last_touched: Instant::now(),
29 ttl,
30 }
31 }
32
33 pub fn touch(&mut self) {
34 self.last_touched = Instant::now();
35 }
36
37 pub fn last_touched(&self) -> &Instant {
38 &self.last_touched
39 }
40
41 pub fn ttl(&self) -> &Duration {
42 &self.ttl
43 }
44
45 pub fn has_expired(&self) -> bool {
46 self.last_touched.elapsed().checked_sub(self.ttl).is_some()
47 }
48}
49
50impl<T: Hash> Hash for TtlValue<T> {
51 fn hash<H: Hasher>(&self, state: &mut H) {
52 self.value.hash(state);
53 }
54}
55
56impl<T: Eq> Eq for TtlValue<T> {}
57
58impl<T: Eq> PartialEq for TtlValue<T> {
59 fn eq(&self, other: &Self) -> bool {
60 self.value == other.value
61 }
62}
63
64impl<T: PartialOrd + Eq> PartialOrd for TtlValue<T> {
65 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
66 self.value.partial_cmp(&other.value)
67 }
68}
69
70impl<T: Ord> Ord for TtlValue<T> {
71 fn cmp(&self, other: &Self) -> Ordering {
72 self.value.cmp(&other.value)
73 }
74}
75
76impl<T: Clone> Clone for TtlValue<T> {
77 fn clone(&self) -> Self {
78 Self {
79 value: self.value.clone(),
80 last_touched: self.last_touched,
81 ttl: self.ttl,
82 }
83 }
84}
85
86impl<T: Copy> Copy for TtlValue<T> {}
87
88impl<T> From<T> for TtlValue<T> {
89 fn from(value: T) -> Self {
90 Self::new(value, Duration::new(0, 0))
91 }
92}
93
94impl<T> Into<Instant> for TtlValue<T> {
95 fn into(self) -> Instant {
96 self.last_touched
97 }
98}
99
100impl<T> Into<Duration> for TtlValue<T> {
101 fn into(self) -> Duration {
102 self.ttl
103 }
104}
105
106impl<T> Deref for TtlValue<T> {
107 type Target = T;
108
109 fn deref(&self) -> &Self::Target {
110 &self.value
111 }
112}
113
114impl<T> DerefMut for TtlValue<T> {
115 fn deref_mut(&mut self) -> &mut Self::Target {
116 &mut self.value
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use std::collections::HashSet;
124
125 #[test]
126 fn touch_should_renew_lifetime_of_value() {
127 let mut ttl_value = TtlValue::new(0, Duration::from_millis(100));
128
129 ttl_value.last_touched = Instant::now()
131 .checked_sub(Duration::from_millis(1000))
132 .unwrap();
133
134 ttl_value.touch();
136
137 assert!(!ttl_value.has_expired(), "Value expired unexpectedly");
138 }
139
140 #[test]
141 fn has_expired_should_return_false_if_value_has_not_expired() {
142 let ttl_value = TtlValue::new(0, Duration::from_millis(100));
143
144 assert!(!ttl_value.has_expired(), "Value expired unexpectedly");
145 }
146
147 #[test]
148 fn has_expired_should_return_true_if_value_has_expired() {
149 let mut ttl_value = TtlValue::new(0, Duration::from_millis(100));
150
151 ttl_value.last_touched = Instant::now()
153 .checked_sub(Duration::from_millis(1000))
154 .unwrap();
155
156 assert!(ttl_value.has_expired(), "Value not expired when should be");
157 }
158
159 #[test]
160 fn hash_should_use_underlying_value() {
161 let mut set = HashSet::new();
163 let v1 = TtlValue::new(0, Duration::from_millis(5));
164 let v2 = TtlValue::new(1, Duration::from_millis(5));
165
166 set.insert(v1);
167 set.insert(v2);
168
169 assert_eq!(set.len(), 2);
170 assert!(set.get(&0.into()).is_some());
171 assert!(set.get(&1.into()).is_some());
172
173 let mut set = HashSet::new();
175 let v1 = TtlValue::new(2, Duration::from_millis(5));
176 let v2 = TtlValue::new(2, Duration::from_millis(10));
177
178 set.insert(v1);
179 set.insert(v2);
180
181 assert_eq!(set.len(), 1);
182 assert!(set.get(&2.into()).is_some());
183 }
184
185 #[test]
186 fn partial_eq_should_use_underlying_value() {
187 let v1 = TtlValue::new(5, Duration::from_millis(1));
188 let v2 = TtlValue::new(5, Duration::from_millis(2));
189
190 assert_eq!(v1, v2);
191 }
192
193 #[test]
194 fn partial_ord_should_use_underlying_value() {
195 let v1 = TtlValue::new(3, Duration::from_millis(1));
196 let v2 = TtlValue::new(5, Duration::from_millis(1));
197
198 assert!(v1 < v2);
199 }
200
201 #[test]
202 fn clone_should_use_underlying_value() {
203 let v1 = TtlValue::new(3, Duration::from_millis(1));
204
205 assert_eq!(v1, v1.clone());
206 }
207
208 #[test]
209 fn copy_should_use_underlying_value() {
210 let v1 = TtlValue::new(3, Duration::from_millis(1));
211 let v2 = v1;
212
213 assert_eq!(v1, v2);
214 }
215
216 #[test]
217 fn from_should_use_underlying_value_and_produce_a_duration_of_zero() {
218 let v1 = TtlValue::from(3);
219
220 assert_eq!(v1.value, 3);
221 assert_eq!(v1.ttl, Duration::new(0, 0));
222 }
223
224 #[test]
225 fn into_should_return_last_touched_if_type_is_instant() {
226 let v1 = TtlValue::new(3, Duration::from_millis(5));
227 let v2 = v1.clone();
228
229 let i: Instant = v1.into();
230 assert_eq!(i, v2.last_touched);
231 }
232
233 #[test]
234 fn into_should_return_ttl_if_type_is_duration() {
235 let v1 = TtlValue::new(3, Duration::from_millis(5));
236
237 let d: Duration = v1.into();
238 assert_eq!(d, Duration::from_millis(5));
239 }
240
241 #[test]
242 fn deref_should_yield_underlying_value() {
243 let v1 = TtlValue::new(5, Duration::from_millis(1));
244
245 assert_eq!(*v1, 5);
246 }
247
248 #[test]
249 fn deref_mut_should_yield_underlying_value() {
250 let mut v1 = TtlValue::new(5, Duration::from_millis(1));
251
252 *v1 = 10;
253
254 assert_eq!(*v1, 10);
255 }
256}