1use std::ops::{Bound, Deref, RangeBounds};
4use std::time;
5
6#[derive(Debug, Clone)]
30#[cfg_attr(test, derive(Eq, PartialEq))]
31pub struct TimerangeBound<T> {
32 obj: T,
35 start: Option<time::SystemTime>,
37 end: Option<time::SystemTime>,
39}
40
41fn unwrap_bound(b: Bound<&'_ time::SystemTime>) -> Option<time::SystemTime> {
48 match b {
49 Bound::Included(x) => Some(*x),
50 Bound::Excluded(x) => Some(*x),
51 _ => None,
52 }
53}
54
55impl<T> TimerangeBound<T> {
56 pub fn new<U>(obj: T, range: U) -> Self
62 where
63 U: RangeBounds<time::SystemTime>,
64 {
65 let start = unwrap_bound(range.start_bound());
66 let end = unwrap_bound(range.end_bound());
67 Self { obj, start, end }
68 }
69
70 pub fn new_from_start_end(
72 obj: T,
73 start: Option<time::SystemTime>,
74 end: Option<time::SystemTime>,
75 ) -> Self {
76 Self { obj, start, end }
77 }
78
79 #[must_use]
82 pub fn extend_tolerance(self, d: time::Duration) -> Self {
83 let end = match self.end {
84 Some(t) => t.checked_add(d),
85 _ => None,
86 };
87 Self { end, ..self }
88 }
89 #[must_use]
92 pub fn extend_pre_tolerance(self, d: time::Duration) -> Self {
93 let start = match self.start {
94 Some(t) => t.checked_sub(d),
95 _ => None,
96 };
97 Self { start, ..self }
98 }
99 #[must_use]
106 pub fn dangerously_map<F, U>(self, f: F) -> TimerangeBound<U>
107 where
108 F: FnOnce(T) -> U,
109 {
110 TimerangeBound {
111 obj: f(self.obj),
112 start: self.start,
113 end: self.end,
114 }
115 }
116
117 pub fn dangerously_into_parts(
123 self,
124 ) -> (T, (Option<time::SystemTime>, Option<time::SystemTime>)) {
125 let bounds = self.bounds();
126
127 (self.obj, bounds)
128 }
129
130 pub fn dangerously_peek(&self) -> &T {
137 &self.obj
138 }
139
140 pub fn as_ref(&self) -> TimerangeBound<&T> {
145 TimerangeBound {
146 obj: &self.obj,
147 start: self.start,
148 end: self.end,
149 }
150 }
151
152 pub fn as_deref(&self) -> TimerangeBound<&T::Target>
154 where
155 T: Deref,
156 {
157 self.as_ref().dangerously_map(|t| &**t)
158 }
159
160 pub fn bounds(&self) -> (Option<time::SystemTime>, Option<time::SystemTime>) {
162 (self.start, self.end)
163 }
164}
165
166impl<T> RangeBounds<time::SystemTime> for TimerangeBound<T> {
167 fn start_bound(&self) -> Bound<&time::SystemTime> {
168 self.start
169 .as_ref()
170 .map(Bound::Included)
171 .unwrap_or(Bound::Unbounded)
172 }
173
174 fn end_bound(&self) -> Bound<&time::SystemTime> {
175 self.end
176 .as_ref()
177 .map(Bound::Included)
178 .unwrap_or(Bound::Unbounded)
179 }
180}
181
182impl<T> crate::Timebound<T> for TimerangeBound<T> {
183 type Error = crate::TimeValidityError;
184
185 fn is_valid_at(&self, t: &time::SystemTime) -> Result<(), Self::Error> {
186 use crate::TimeValidityError;
187 if let Some(start) = self.start {
188 if let Ok(d) = start.duration_since(*t) {
189 return Err(TimeValidityError::NotYetValid(d));
190 }
191 }
192
193 if let Some(end) = self.end {
194 if let Ok(d) = t.duration_since(end) {
195 return Err(TimeValidityError::Expired(d));
196 }
197 }
198
199 Ok(())
200 }
201
202 fn dangerously_assume_timely(self) -> T {
203 self.obj
204 }
205}
206
207#[cfg(test)]
208mod test {
209 #![allow(clippy::bool_assert_comparison)]
211 #![allow(clippy::clone_on_copy)]
212 #![allow(clippy::dbg_macro)]
213 #![allow(clippy::mixed_attributes_style)]
214 #![allow(clippy::print_stderr)]
215 #![allow(clippy::print_stdout)]
216 #![allow(clippy::single_char_pattern)]
217 #![allow(clippy::unwrap_used)]
218 #![allow(clippy::unchecked_duration_subtraction)]
219 #![allow(clippy::useless_vec)]
220 #![allow(clippy::needless_pass_by_value)]
221 use super::*;
223 use crate::{TimeValidityError, Timebound};
224 use humantime::parse_rfc3339;
225 use std::time::{Duration, SystemTime};
226
227 #[test]
228 fn test_bounds() {
229 #![allow(clippy::unwrap_used)]
230 let one_day = Duration::new(86400, 0);
231 let mixminion_v0_0_1 = parse_rfc3339("2003-01-07T00:00:00Z").unwrap();
232 let tor_v0_0_2pre13 = parse_rfc3339("2003-10-19T00:00:00Z").unwrap();
233 let cussed_nougat = parse_rfc3339("2008-08-02T00:00:00Z").unwrap();
234 let tor_v0_4_4_5 = parse_rfc3339("2020-09-15T00:00:00Z").unwrap();
235 let today = parse_rfc3339("2020-09-22T00:00:00Z").unwrap();
236
237 let tr = TimerangeBound::new((), ..tor_v0_4_4_5);
238 assert_eq!(tr.start, None);
239 assert_eq!(tr.end, Some(tor_v0_4_4_5));
240 assert!(tr.is_valid_at(&mixminion_v0_0_1).is_ok());
241 assert!(tr.is_valid_at(&tor_v0_0_2pre13).is_ok());
242 assert_eq!(
243 tr.is_valid_at(&today),
244 Err(TimeValidityError::Expired(7 * one_day))
245 );
246
247 let tr = TimerangeBound::new((), tor_v0_0_2pre13..=tor_v0_4_4_5);
248 assert_eq!(tr.start, Some(tor_v0_0_2pre13));
249 assert_eq!(tr.end, Some(tor_v0_4_4_5));
250 assert_eq!(
251 tr.is_valid_at(&mixminion_v0_0_1),
252 Err(TimeValidityError::NotYetValid(285 * one_day))
253 );
254 assert!(tr.is_valid_at(&cussed_nougat).is_ok());
255 assert_eq!(
256 tr.is_valid_at(&today),
257 Err(TimeValidityError::Expired(7 * one_day))
258 );
259
260 let tr = tr
261 .extend_pre_tolerance(5 * one_day)
262 .extend_tolerance(2 * one_day);
263 assert_eq!(tr.start, Some(tor_v0_0_2pre13 - 5 * one_day));
264 assert_eq!(tr.end, Some(tor_v0_4_4_5 + 2 * one_day));
265
266 let tr = tr
267 .extend_pre_tolerance(Duration::MAX)
268 .extend_tolerance(Duration::MAX);
269 assert_eq!(tr.start, None);
270 assert_eq!(tr.end, None);
271
272 let tr = TimerangeBound::new((), tor_v0_4_4_5..);
273 assert_eq!(tr.start, Some(tor_v0_4_4_5));
274 assert_eq!(tr.end, None);
275 assert_eq!(
276 tr.is_valid_at(&cussed_nougat),
277 Err(TimeValidityError::NotYetValid(4427 * one_day))
278 );
279 assert!(tr.is_valid_at(&today).is_ok());
280 }
281
282 #[test]
283 fn test_checking() {
284 let de = humantime::parse_rfc3339("1990-10-03T00:00:00Z").unwrap();
286 let cz_sk = humantime::parse_rfc3339("1993-01-01T00:00:00Z").unwrap();
288 let eu = humantime::parse_rfc3339("1993-11-01T00:00:00Z").unwrap();
290 let za = humantime::parse_rfc3339("1994-04-27T00:00:00Z").unwrap();
292
293 let tr = TimerangeBound::new("Hello world", cz_sk..eu);
295 assert!(tr.check_valid_at(&za).is_err());
296
297 let tr = TimerangeBound::new("Hello world", cz_sk..za);
298 assert_eq!(tr.check_valid_at(&eu), Ok("Hello world"));
299
300 let tr = TimerangeBound::new("hello world", de..);
302 assert_eq!(tr.check_valid_now(), Ok("hello world"));
303
304 let tr = TimerangeBound::new("hello world", ..za);
305 assert!(tr.check_valid_now().is_err());
306
307 let tr = TimerangeBound::new("hello world", de..);
309 assert_eq!(tr.check_valid_at_opt(None), Ok("hello world"));
310 let tr = TimerangeBound::new("hello world", de..);
311 assert_eq!(
312 tr.check_valid_at_opt(Some(SystemTime::now())),
313 Ok("hello world")
314 );
315 let tr = TimerangeBound::new("hello world", ..za);
316 assert!(tr.check_valid_at_opt(None).is_err());
317 }
318
319 #[test]
320 fn test_dangerous() {
321 let t1 = SystemTime::now();
322 let t2 = t1 + Duration::from_secs(60 * 525600);
323 let tr = TimerangeBound::new("cups of coffee", t1..=t2);
324
325 assert_eq!(tr.dangerously_peek(), &"cups of coffee");
326
327 let (a, b) = tr.dangerously_into_parts();
328 assert_eq!(a, "cups of coffee");
329 assert_eq!(b.0, Some(t1));
330 assert_eq!(b.1, Some(t2));
331 }
332
333 #[test]
334 fn test_map() {
335 let t1 = SystemTime::now();
336 let min = Duration::from_secs(60);
337
338 let tb = TimerangeBound::new(17_u32, t1..t1 + 5 * min);
339 let tb = tb.dangerously_map(|v| v * v);
340 assert!(tb.is_valid_at(&(t1 + 1 * min)).is_ok());
341 assert!(tb.is_valid_at(&(t1 + 10 * min)).is_err());
342
343 let val = tb.check_valid_at(&(t1 + 1 * min)).unwrap();
344 assert_eq!(val, 289);
345 }
346
347 #[test]
348 fn test_as_ref() {
349 let t1 = SystemTime::now();
350 let min = Duration::from_secs(60);
351
352 let tb1: TimerangeBound<String> = TimerangeBound::new("hi".into(), t1..t1 + 5 * min);
353 let tb2: TimerangeBound<&String> = tb1.as_ref();
354 let tb3: TimerangeBound<&str> = tb1.as_deref();
355 assert_eq!(tb1, tb2.dangerously_map(|s| s.clone()));
356 assert_eq!(tb1, tb3.dangerously_map(|s| s.to_owned()));
357 }
358}