1pub const MAX_DURATION_MS: u64 = 365 * 100 * 24 * 60 * 60 * 1000;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum Timing {
13 Immediate,
15 Deadline { by_ms: u64 },
17 Delayed { after_ms: u64 },
19 Window { earliest_ms: u64, latest_ms: u64 },
21 Exact { at_ms: u64 },
23}
24
25pub fn immediate() -> Timing {
29 Timing::Immediate
30}
31
32pub fn deadline(by_ms: u64) -> Timing {
37 assert!(by_ms > 0, "Deadline must be positive: {by_ms}");
38 Timing::Deadline { by_ms }
39}
40
41pub fn delayed(after_ms: u64) -> Timing {
43 Timing::Delayed { after_ms }
44}
45
46pub fn window(earliest_ms: u64, latest_ms: u64) -> Timing {
51 assert!(
52 latest_ms >= earliest_ms,
53 "Latest ({latest_ms}) must be >= earliest ({earliest_ms})"
54 );
55 Timing::Window {
56 earliest_ms,
57 latest_ms,
58 }
59}
60
61pub fn exact(at_ms: u64) -> Timing {
63 Timing::Exact { at_ms }
64}
65
66impl Timing {
69 pub fn earliest(&self) -> u64 {
71 match self {
72 Timing::Immediate => 0,
73 Timing::Deadline { .. } => 0,
74 Timing::Delayed { after_ms } => *after_ms,
75 Timing::Window { earliest_ms, .. } => *earliest_ms,
76 Timing::Exact { at_ms } => *at_ms,
77 }
78 }
79
80 pub fn latest(&self) -> u64 {
82 match self {
83 Timing::Immediate => MAX_DURATION_MS,
84 Timing::Deadline { by_ms } => *by_ms,
85 Timing::Delayed { .. } => MAX_DURATION_MS,
86 Timing::Window { latest_ms, .. } => *latest_ms,
87 Timing::Exact { at_ms } => *at_ms,
88 }
89 }
90
91 pub fn has_deadline(&self) -> bool {
93 matches!(
94 self,
95 Timing::Deadline { .. } | Timing::Window { .. } | Timing::Exact { .. }
96 )
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn immediate_bounds() {
106 let t = immediate();
107 assert_eq!(t.earliest(), 0);
108 assert_eq!(t.latest(), MAX_DURATION_MS);
109 assert!(!t.has_deadline());
110 }
111
112 #[test]
113 fn deadline_bounds() {
114 let t = deadline(5000);
115 assert_eq!(t.earliest(), 0);
116 assert_eq!(t.latest(), 5000);
117 assert!(t.has_deadline());
118 }
119
120 #[test]
121 fn delayed_bounds() {
122 let t = delayed(100);
123 assert_eq!(t.earliest(), 100);
124 assert_eq!(t.latest(), MAX_DURATION_MS);
125 assert!(!t.has_deadline());
126 }
127
128 #[test]
129 fn window_bounds() {
130 let t = window(100, 500);
131 assert_eq!(t.earliest(), 100);
132 assert_eq!(t.latest(), 500);
133 assert!(t.has_deadline());
134 }
135
136 #[test]
137 fn exact_bounds() {
138 let t = exact(250);
139 assert_eq!(t.earliest(), 250);
140 assert_eq!(t.latest(), 250);
141 assert!(t.has_deadline());
142 }
143
144 #[test]
145 #[should_panic(expected = "Deadline must be positive")]
146 fn deadline_zero_panics() {
147 deadline(0);
148 }
149
150 #[test]
151 #[should_panic(expected = "Latest")]
152 fn window_inverted_panics() {
153 window(500, 100);
154 }
155}