Skip to main content

timer_lib/
errors.rs

1//! Error handling module for TimerLib.
2
3use std::backtrace::Backtrace;
4use std::error::Error as StdError;
5use std::fmt::{Display, Formatter};
6use std::sync::Arc;
7use std::time::Duration;
8
9/// Represents timer-related failures.
10#[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    /// Creates an invalid parameter error.
28    pub fn invalid_parameter(message: impl Into<String>) -> Self {
29        Self::new(TimerErrorKind::InvalidParameter(message.into()))
30    }
31
32    /// Creates an error for operations that require a running timer.
33    pub fn not_running() -> Self {
34        Self::new(TimerErrorKind::NotRunning)
35    }
36
37    /// Creates an error for operations that require a paused timer.
38    pub fn not_paused() -> Self {
39        Self::new(TimerErrorKind::NotPaused)
40    }
41
42    /// Creates an error for operations that cannot safely run from the active callback.
43    pub fn reentrant_operation(message: impl Into<String>) -> Self {
44        Self::new(TimerErrorKind::ReentrantOperation(message.into()))
45    }
46
47    /// Creates an error for callback timeout expiration.
48    pub fn callback_timed_out(timeout: Duration) -> Self {
49        Self::new(TimerErrorKind::CallbackTimedOut(timeout))
50    }
51
52    /// Creates an error for callback execution failures.
53    pub fn callback_failed(message: impl Into<String>) -> Self {
54        Self::new(TimerErrorKind::CallbackFailed(message.into()))
55    }
56
57    /// Returns true when the error is an invalid parameter error.
58    pub fn is_invalid_parameter(&self) -> bool {
59        matches!(self.kind, TimerErrorKind::InvalidParameter(_))
60    }
61
62    /// Returns the invalid parameter message when available.
63    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    /// Returns true when the error indicates the timer was not running.
71    pub fn is_not_running(&self) -> bool {
72        matches!(self.kind, TimerErrorKind::NotRunning)
73    }
74
75    /// Returns true when the error indicates the timer was not paused.
76    pub fn is_not_paused(&self) -> bool {
77        matches!(self.kind, TimerErrorKind::NotPaused)
78    }
79
80    /// Returns true when the error indicates a reentrant callback operation.
81    pub fn is_reentrant_operation(&self) -> bool {
82        matches!(self.kind, TimerErrorKind::ReentrantOperation(_))
83    }
84
85    /// Returns the reentrant-operation message when available.
86    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    /// Returns true when the error indicates callback execution failed.
94    pub fn is_callback_failed(&self) -> bool {
95        matches!(self.kind, TimerErrorKind::CallbackFailed(_))
96    }
97
98    /// Returns true when the error indicates callback execution timed out.
99    pub fn is_callback_timed_out(&self) -> bool {
100        matches!(self.kind, TimerErrorKind::CallbackTimedOut(_))
101    }
102
103    /// Returns the callback timeout when available.
104    pub fn callback_timeout(&self) -> Option<Duration> {
105        match &self.kind {
106            TimerErrorKind::CallbackTimedOut(timeout) => Some(*timeout),
107            _ => None,
108        }
109    }
110
111    /// Returns the callback failure message when available.
112    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    /// Returns the captured backtrace.
120    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// Rust guideline compliant 2026-02-21
164
165#[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}