1use std::backtrace::Backtrace;
4use std::error::Error as StdError;
5use std::fmt::{Display, Formatter};
6use std::sync::Arc;
7use std::time::Duration;
8
9#[derive(Debug, Clone)]
11pub struct TimerError {
12 kind: TimerErrorKind,
13 backtrace: Arc<Backtrace>,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17enum TimerErrorKind {
18 InvalidParameter(String),
19 NotRunning,
20 NotPaused,
21 ReentrantOperation(String),
22 CallbackTimedOut(Duration),
23 CallbackFailed(String),
24}
25
26impl TimerError {
27 pub fn invalid_parameter(message: impl Into<String>) -> Self {
29 Self::new(TimerErrorKind::InvalidParameter(message.into()))
30 }
31
32 pub fn not_running() -> Self {
34 Self::new(TimerErrorKind::NotRunning)
35 }
36
37 pub fn not_paused() -> Self {
39 Self::new(TimerErrorKind::NotPaused)
40 }
41
42 pub fn reentrant_operation(message: impl Into<String>) -> Self {
44 Self::new(TimerErrorKind::ReentrantOperation(message.into()))
45 }
46
47 pub fn callback_timed_out(timeout: Duration) -> Self {
49 Self::new(TimerErrorKind::CallbackTimedOut(timeout))
50 }
51
52 pub fn callback_failed(message: impl Into<String>) -> Self {
54 Self::new(TimerErrorKind::CallbackFailed(message.into()))
55 }
56
57 pub fn is_invalid_parameter(&self) -> bool {
59 matches!(self.kind, TimerErrorKind::InvalidParameter(_))
60 }
61
62 pub fn invalid_parameter_message(&self) -> Option<&str> {
64 match &self.kind {
65 TimerErrorKind::InvalidParameter(message) => Some(message.as_str()),
66 _ => None,
67 }
68 }
69
70 pub fn is_not_running(&self) -> bool {
72 matches!(self.kind, TimerErrorKind::NotRunning)
73 }
74
75 pub fn is_not_paused(&self) -> bool {
77 matches!(self.kind, TimerErrorKind::NotPaused)
78 }
79
80 pub fn is_reentrant_operation(&self) -> bool {
82 matches!(self.kind, TimerErrorKind::ReentrantOperation(_))
83 }
84
85 pub fn reentrant_operation_message(&self) -> Option<&str> {
87 match &self.kind {
88 TimerErrorKind::ReentrantOperation(message) => Some(message.as_str()),
89 _ => None,
90 }
91 }
92
93 pub fn is_callback_failed(&self) -> bool {
95 matches!(self.kind, TimerErrorKind::CallbackFailed(_))
96 }
97
98 pub fn is_callback_timed_out(&self) -> bool {
100 matches!(self.kind, TimerErrorKind::CallbackTimedOut(_))
101 }
102
103 pub fn callback_timeout(&self) -> Option<Duration> {
105 match &self.kind {
106 TimerErrorKind::CallbackTimedOut(timeout) => Some(*timeout),
107 _ => None,
108 }
109 }
110
111 pub fn callback_failure_message(&self) -> Option<&str> {
113 match &self.kind {
114 TimerErrorKind::CallbackFailed(message) => Some(message.as_str()),
115 _ => None,
116 }
117 }
118
119 pub fn backtrace(&self) -> &Backtrace {
121 self.backtrace.as_ref()
122 }
123
124 fn new(kind: TimerErrorKind) -> Self {
125 Self {
126 kind,
127 backtrace: Arc::new(Backtrace::capture()),
128 }
129 }
130}
131
132impl Display for TimerError {
133 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
134 match &self.kind {
135 TimerErrorKind::InvalidParameter(message) => {
136 write!(f, "Invalid parameter: {message}")
137 }
138 TimerErrorKind::NotRunning => write!(f, "Operation requires a running timer."),
139 TimerErrorKind::NotPaused => write!(f, "Operation requires a paused timer."),
140 TimerErrorKind::ReentrantOperation(message) => {
141 write!(f, "Reentrant timer operation is not allowed: {message}")
142 }
143 TimerErrorKind::CallbackTimedOut(timeout) => {
144 write!(f, "Callback execution timed out after {timeout:?}")
145 }
146 TimerErrorKind::CallbackFailed(message) => {
147 write!(f, "Callback execution failed: {message}")
148 }
149 }
150 }
151}
152
153impl StdError for TimerError {}
154
155impl PartialEq for TimerError {
156 fn eq(&self, other: &Self) -> bool {
157 self.kind == other.kind
158 }
159}
160
161impl Eq for TimerError {}
162
163#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn error_messages_are_stable_and_descriptive() {
171 assert_eq!(
172 TimerError::invalid_parameter("bad interval").to_string(),
173 "Invalid parameter: bad interval"
174 );
175 assert_eq!(
176 TimerError::not_running().to_string(),
177 "Operation requires a running timer."
178 );
179 assert_eq!(
180 TimerError::not_paused().to_string(),
181 "Operation requires a paused timer."
182 );
183 assert_eq!(
184 TimerError::reentrant_operation("stop() from callback").to_string(),
185 "Reentrant timer operation is not allowed: stop() from callback"
186 );
187 assert_eq!(
188 TimerError::callback_timed_out(Duration::from_secs(2)).to_string(),
189 "Callback execution timed out after 2s"
190 );
191 assert_eq!(
192 TimerError::callback_failed("boom").to_string(),
193 "Callback execution failed: boom"
194 );
195 }
196
197 #[test]
198 fn error_helpers_expose_stable_queries() {
199 let invalid = TimerError::invalid_parameter("interval");
200 assert!(invalid.is_invalid_parameter());
201 assert_eq!(invalid.invalid_parameter_message(), Some("interval"));
202 assert!(!invalid.is_not_running());
203
204 let callback = TimerError::callback_failed("boom");
205 assert!(callback.is_callback_failed());
206 assert_eq!(callback.callback_failure_message(), Some("boom"));
207
208 let timed_out = TimerError::callback_timed_out(Duration::from_millis(250));
209 assert!(timed_out.is_callback_timed_out());
210 assert_eq!(
211 timed_out.callback_timeout(),
212 Some(Duration::from_millis(250))
213 );
214
215 let reentrant = TimerError::reentrant_operation("join()");
216 assert!(reentrant.is_reentrant_operation());
217 assert_eq!(reentrant.reentrant_operation_message(), Some("join()"));
218 assert!(
219 callback.backtrace().status() != std::backtrace::BacktraceStatus::Disabled
220 || callback.backtrace().status() == std::backtrace::BacktraceStatus::Disabled
221 );
222 }
223}