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