1use std::time::Duration;
4use thiserror::Error;
5
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub enum TimerDirective {
9 Stop,
11 ContinueImmediately,
13 RetryAfter(Duration),
15 ScheduleAt(u64),
17 RecurAfter(Duration),
19}
20
21#[non_exhaustive]
23#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
24pub enum ScheduleError {
25 #[error("timer delay exceeds the supported nanosecond range")]
27 DelayOutOfRange,
28 #[error("timer deadline exceeds the supported timestamp range")]
30 DeadlineOverflow,
31}
32
33impl TimerDirective {
34 pub fn deadline_ns(self, now_ns: u64) -> Result<Option<u64>, ScheduleError> {
39 match self {
40 Self::Stop => Ok(None),
41 Self::ContinueImmediately => Ok(Some(now_ns)),
42 Self::ScheduleAt(deadline_ns) => Ok(Some(deadline_ns)),
43 Self::RetryAfter(delay) | Self::RecurAfter(delay) => {
44 deadline_after(now_ns, delay).map(Some)
45 }
46 }
47 }
48}
49
50fn deadline_after(now_ns: u64, delay: Duration) -> Result<u64, ScheduleError> {
51 let delay_ns = u64::try_from(delay.as_nanos()).map_err(|_| ScheduleError::DelayOutOfRange)?;
52 now_ns
53 .checked_add(delay_ns)
54 .ok_or(ScheduleError::DeadlineOverflow)
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[test]
62 fn resolves_all_directive_deadlines() {
63 assert_eq!(TimerDirective::Stop.deadline_ns(10), Ok(None));
64 assert_eq!(
65 TimerDirective::ContinueImmediately.deadline_ns(10),
66 Ok(Some(10))
67 );
68 assert_eq!(TimerDirective::ScheduleAt(50).deadline_ns(10), Ok(Some(50)));
69 assert_eq!(
70 TimerDirective::RetryAfter(Duration::from_nanos(5)).deadline_ns(10),
71 Ok(Some(15))
72 );
73 assert_eq!(
74 TimerDirective::RecurAfter(Duration::from_nanos(7)).deadline_ns(10),
75 Ok(Some(17))
76 );
77 }
78
79 #[test]
80 fn rejects_overflowing_deadline() {
81 assert_eq!(
82 TimerDirective::RetryAfter(Duration::from_nanos(1)).deadline_ns(u64::MAX),
83 Err(ScheduleError::DeadlineOverflow)
84 );
85 }
86}