use std::time::Duration;
use thiserror::Error;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TimerDirective {
Stop,
ContinueImmediately,
RetryAfter(Duration),
ScheduleAt(u64),
RecurAfter(Duration),
}
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub enum ScheduleError {
#[error("timer delay exceeds the supported nanosecond range")]
DelayOutOfRange,
#[error("timer deadline exceeds the supported timestamp range")]
DeadlineOverflow,
}
impl TimerDirective {
pub fn deadline_ns(self, now_ns: u64) -> Result<Option<u64>, ScheduleError> {
match self {
Self::Stop => Ok(None),
Self::ContinueImmediately => Ok(Some(now_ns)),
Self::ScheduleAt(deadline_ns) => Ok(Some(deadline_ns)),
Self::RetryAfter(delay) | Self::RecurAfter(delay) => {
deadline_after(now_ns, delay).map(Some)
}
}
}
}
fn deadline_after(now_ns: u64, delay: Duration) -> Result<u64, ScheduleError> {
let delay_ns = u64::try_from(delay.as_nanos()).map_err(|_| ScheduleError::DelayOutOfRange)?;
now_ns
.checked_add(delay_ns)
.ok_or(ScheduleError::DeadlineOverflow)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolves_all_directive_deadlines() {
assert_eq!(TimerDirective::Stop.deadline_ns(10), Ok(None));
assert_eq!(
TimerDirective::ContinueImmediately.deadline_ns(10),
Ok(Some(10))
);
assert_eq!(TimerDirective::ScheduleAt(50).deadline_ns(10), Ok(Some(50)));
assert_eq!(
TimerDirective::RetryAfter(Duration::from_nanos(5)).deadline_ns(10),
Ok(Some(15))
);
assert_eq!(
TimerDirective::RecurAfter(Duration::from_nanos(7)).deadline_ns(10),
Ok(Some(17))
);
}
#[test]
fn rejects_overflowing_deadline() {
assert_eq!(
TimerDirective::RetryAfter(Duration::from_nanos(1)).deadline_ns(u64::MAX),
Err(ScheduleError::DeadlineOverflow)
);
}
}