use std::ops::{Bound, Deref, RangeBounds};
use std::time;
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct TimerangeBound<T> {
obj: T,
start: Option<time::SystemTime>,
end: Option<time::SystemTime>,
}
fn unwrap_bound(b: Bound<&'_ time::SystemTime>) -> Option<time::SystemTime> {
match b {
Bound::Included(x) => Some(*x),
Bound::Excluded(x) => Some(*x),
_ => None,
}
}
impl<T> TimerangeBound<T> {
pub fn new<U>(obj: T, range: U) -> Self
where
U: RangeBounds<time::SystemTime>,
{
let start = unwrap_bound(range.start_bound());
let end = unwrap_bound(range.end_bound());
Self { obj, start, end }
}
#[must_use]
pub fn extend_tolerance(self, d: time::Duration) -> Self {
let end = match self.end {
Some(t) => t.checked_add(d),
_ => None,
};
Self { end, ..self }
}
#[must_use]
pub fn extend_pre_tolerance(self, d: time::Duration) -> Self {
let start = match self.start {
Some(t) => t.checked_sub(d),
_ => None,
};
Self { start, ..self }
}
#[must_use]
pub fn dangerously_map<F, U>(self, f: F) -> TimerangeBound<U>
where
F: FnOnce(T) -> U,
{
TimerangeBound {
obj: f(self.obj),
start: self.start,
end: self.end,
}
}
pub fn dangerously_into_parts(self) -> (T, (Bound<time::SystemTime>, Bound<time::SystemTime>)) {
let bounds = self.bounds();
(self.obj, bounds)
}
pub fn dangerously_peek(&self) -> &T {
&self.obj
}
pub fn as_ref(&self) -> TimerangeBound<&T> {
TimerangeBound {
obj: &self.obj,
start: self.start,
end: self.end,
}
}
pub fn as_deref(&self) -> TimerangeBound<&T::Target>
where
T: Deref,
{
self.as_ref().dangerously_map(|t| &**t)
}
pub fn bounds(&self) -> (Bound<time::SystemTime>, Bound<time::SystemTime>) {
(self.start_bound().cloned(), self.end_bound().cloned())
}
}
impl<T> RangeBounds<time::SystemTime> for TimerangeBound<T> {
fn start_bound(&self) -> Bound<&time::SystemTime> {
self.start
.as_ref()
.map(Bound::Included)
.unwrap_or(Bound::Unbounded)
}
fn end_bound(&self) -> Bound<&time::SystemTime> {
self.end
.as_ref()
.map(Bound::Included)
.unwrap_or(Bound::Unbounded)
}
}
impl<T> crate::Timebound<T> for TimerangeBound<T> {
type Error = crate::TimeValidityError;
fn is_valid_at(&self, t: &time::SystemTime) -> Result<(), Self::Error> {
use crate::TimeValidityError;
if let Some(start) = self.start {
if let Ok(d) = start.duration_since(*t) {
return Err(TimeValidityError::NotYetValid(d));
}
}
if let Some(end) = self.end {
if let Ok(d) = t.duration_since(end) {
return Err(TimeValidityError::Expired(d));
}
}
Ok(())
}
fn dangerously_assume_timely(self) -> T {
self.obj
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_duration_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
use crate::{TimeValidityError, Timebound};
use humantime::parse_rfc3339;
use std::time::{Duration, SystemTime};
#[test]
fn test_bounds() {
#![allow(clippy::unwrap_used)]
let one_day = Duration::new(86400, 0);
let mixminion_v0_0_1 = parse_rfc3339("2003-01-07T00:00:00Z").unwrap();
let tor_v0_0_2pre13 = parse_rfc3339("2003-10-19T00:00:00Z").unwrap();
let cussed_nougat = parse_rfc3339("2008-08-02T00:00:00Z").unwrap();
let tor_v0_4_4_5 = parse_rfc3339("2020-09-15T00:00:00Z").unwrap();
let today = parse_rfc3339("2020-09-22T00:00:00Z").unwrap();
let tr = TimerangeBound::new((), ..tor_v0_4_4_5);
assert_eq!(tr.start, None);
assert_eq!(tr.end, Some(tor_v0_4_4_5));
assert!(tr.is_valid_at(&mixminion_v0_0_1).is_ok());
assert!(tr.is_valid_at(&tor_v0_0_2pre13).is_ok());
assert_eq!(
tr.is_valid_at(&today),
Err(TimeValidityError::Expired(7 * one_day))
);
let tr = TimerangeBound::new((), tor_v0_0_2pre13..=tor_v0_4_4_5);
assert_eq!(tr.start, Some(tor_v0_0_2pre13));
assert_eq!(tr.end, Some(tor_v0_4_4_5));
assert_eq!(
tr.is_valid_at(&mixminion_v0_0_1),
Err(TimeValidityError::NotYetValid(285 * one_day))
);
assert!(tr.is_valid_at(&cussed_nougat).is_ok());
assert_eq!(
tr.is_valid_at(&today),
Err(TimeValidityError::Expired(7 * one_day))
);
let tr = tr
.extend_pre_tolerance(5 * one_day)
.extend_tolerance(2 * one_day);
assert_eq!(tr.start, Some(tor_v0_0_2pre13 - 5 * one_day));
assert_eq!(tr.end, Some(tor_v0_4_4_5 + 2 * one_day));
let tr = tr
.extend_pre_tolerance(Duration::MAX)
.extend_tolerance(Duration::MAX);
assert_eq!(tr.start, None);
assert_eq!(tr.end, None);
let tr = TimerangeBound::new((), tor_v0_4_4_5..);
assert_eq!(tr.start, Some(tor_v0_4_4_5));
assert_eq!(tr.end, None);
assert_eq!(
tr.is_valid_at(&cussed_nougat),
Err(TimeValidityError::NotYetValid(4427 * one_day))
);
assert!(tr.is_valid_at(&today).is_ok());
}
#[test]
fn test_checking() {
let de = humantime::parse_rfc3339("1990-10-03T00:00:00Z").unwrap();
let cz_sk = humantime::parse_rfc3339("1993-01-01T00:00:00Z").unwrap();
let eu = humantime::parse_rfc3339("1993-11-01T00:00:00Z").unwrap();
let za = humantime::parse_rfc3339("1994-04-27T00:00:00Z").unwrap();
let tr = TimerangeBound::new("Hello world", cz_sk..eu);
assert!(tr.check_valid_at(&za).is_err());
let tr = TimerangeBound::new("Hello world", cz_sk..za);
assert_eq!(tr.check_valid_at(&eu), Ok("Hello world"));
let tr = TimerangeBound::new("hello world", de..);
assert_eq!(tr.check_valid_now(), Ok("hello world"));
let tr = TimerangeBound::new("hello world", ..za);
assert!(tr.check_valid_now().is_err());
let tr = TimerangeBound::new("hello world", de..);
assert_eq!(tr.check_valid_at_opt(None), Ok("hello world"));
let tr = TimerangeBound::new("hello world", de..);
assert_eq!(
tr.check_valid_at_opt(Some(SystemTime::now())),
Ok("hello world")
);
let tr = TimerangeBound::new("hello world", ..za);
assert!(tr.check_valid_at_opt(None).is_err());
}
#[test]
fn test_dangerous() {
let t1 = SystemTime::now();
let t2 = t1 + Duration::from_secs(60 * 525600);
let tr = TimerangeBound::new("cups of coffee", t1..=t2);
assert_eq!(tr.dangerously_peek(), &"cups of coffee");
let (a, b) = tr.dangerously_into_parts();
assert_eq!(a, "cups of coffee");
assert_eq!(b.0, Bound::Included(t1));
assert_eq!(b.1, Bound::Included(t2));
}
#[test]
fn test_map() {
let t1 = SystemTime::now();
let min = Duration::from_secs(60);
let tb = TimerangeBound::new(17_u32, t1..t1 + 5 * min);
let tb = tb.dangerously_map(|v| v * v);
assert!(tb.is_valid_at(&(t1 + 1 * min)).is_ok());
assert!(tb.is_valid_at(&(t1 + 10 * min)).is_err());
let val = tb.check_valid_at(&(t1 + 1 * min)).unwrap();
assert_eq!(val, 289);
}
#[test]
fn test_as_ref() {
let t1 = SystemTime::now();
let min = Duration::from_secs(60);
let tb1: TimerangeBound<String> = TimerangeBound::new("hi".into(), t1..t1 + 5 * min);
let tb2: TimerangeBound<&String> = tb1.as_ref();
let tb3: TimerangeBound<&str> = tb1.as_deref();
assert_eq!(tb1, tb2.dangerously_map(|s| s.clone()));
assert_eq!(tb1, tb3.dangerously_map(|s| s.to_owned()));
}
}