1use std::backtrace::Backtrace;
4use std::error::Error as StdError;
5use std::fmt::{Display, Formatter};
6use std::sync::Arc;
7
8#[derive(Debug, Clone)]
10pub struct TimerError {
11 kind: TimerErrorKind,
12 backtrace: Arc<Backtrace>,
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16enum TimerErrorKind {
17 InvalidParameter(String),
18 NotRunning,
19 NotPaused,
20 ReentrantOperation(String),
21 CallbackFailed(String),
22}
23
24impl TimerError {
25 pub fn invalid_parameter(message: impl Into<String>) -> Self {
27 Self::new(TimerErrorKind::InvalidParameter(message.into()))
28 }
29
30 pub fn not_running() -> Self {
32 Self::new(TimerErrorKind::NotRunning)
33 }
34
35 pub fn not_paused() -> Self {
37 Self::new(TimerErrorKind::NotPaused)
38 }
39
40 pub fn reentrant_operation(message: impl Into<String>) -> Self {
42 Self::new(TimerErrorKind::ReentrantOperation(message.into()))
43 }
44
45 pub fn callback_failed(message: impl Into<String>) -> Self {
47 Self::new(TimerErrorKind::CallbackFailed(message.into()))
48 }
49
50 pub fn is_invalid_parameter(&self) -> bool {
52 matches!(self.kind, TimerErrorKind::InvalidParameter(_))
53 }
54
55 pub fn invalid_parameter_message(&self) -> Option<&str> {
57 match &self.kind {
58 TimerErrorKind::InvalidParameter(message) => Some(message.as_str()),
59 _ => None,
60 }
61 }
62
63 pub fn is_not_running(&self) -> bool {
65 matches!(self.kind, TimerErrorKind::NotRunning)
66 }
67
68 pub fn is_not_paused(&self) -> bool {
70 matches!(self.kind, TimerErrorKind::NotPaused)
71 }
72
73 pub fn is_reentrant_operation(&self) -> bool {
75 matches!(self.kind, TimerErrorKind::ReentrantOperation(_))
76 }
77
78 pub fn reentrant_operation_message(&self) -> Option<&str> {
80 match &self.kind {
81 TimerErrorKind::ReentrantOperation(message) => Some(message.as_str()),
82 _ => None,
83 }
84 }
85
86 pub fn is_callback_failed(&self) -> bool {
88 matches!(self.kind, TimerErrorKind::CallbackFailed(_))
89 }
90
91 pub fn callback_failure_message(&self) -> Option<&str> {
93 match &self.kind {
94 TimerErrorKind::CallbackFailed(message) => Some(message.as_str()),
95 _ => None,
96 }
97 }
98
99 pub fn backtrace(&self) -> &Backtrace {
101 self.backtrace.as_ref()
102 }
103
104 fn new(kind: TimerErrorKind) -> Self {
105 Self {
106 kind,
107 backtrace: Arc::new(Backtrace::capture()),
108 }
109 }
110}
111
112impl Display for TimerError {
113 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
114 match &self.kind {
115 TimerErrorKind::InvalidParameter(message) => {
116 write!(f, "Invalid parameter: {message}")
117 }
118 TimerErrorKind::NotRunning => write!(f, "Operation requires a running timer."),
119 TimerErrorKind::NotPaused => write!(f, "Operation requires a paused timer."),
120 TimerErrorKind::ReentrantOperation(message) => {
121 write!(f, "Reentrant timer operation is not allowed: {message}")
122 }
123 TimerErrorKind::CallbackFailed(message) => {
124 write!(f, "Callback execution failed: {message}")
125 }
126 }
127 }
128}
129
130impl StdError for TimerError {}
131
132impl PartialEq for TimerError {
133 fn eq(&self, other: &Self) -> bool {
134 self.kind == other.kind
135 }
136}
137
138impl Eq for TimerError {}
139
140#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn error_messages_are_stable_and_descriptive() {
148 assert_eq!(
149 TimerError::invalid_parameter("bad interval").to_string(),
150 "Invalid parameter: bad interval"
151 );
152 assert_eq!(
153 TimerError::not_running().to_string(),
154 "Operation requires a running timer."
155 );
156 assert_eq!(
157 TimerError::not_paused().to_string(),
158 "Operation requires a paused timer."
159 );
160 assert_eq!(
161 TimerError::reentrant_operation("stop() from callback").to_string(),
162 "Reentrant timer operation is not allowed: stop() from callback"
163 );
164 assert_eq!(
165 TimerError::callback_failed("boom").to_string(),
166 "Callback execution failed: boom"
167 );
168 }
169
170 #[test]
171 fn error_helpers_expose_stable_queries() {
172 let invalid = TimerError::invalid_parameter("interval");
173 assert!(invalid.is_invalid_parameter());
174 assert_eq!(invalid.invalid_parameter_message(), Some("interval"));
175 assert!(!invalid.is_not_running());
176
177 let callback = TimerError::callback_failed("boom");
178 assert!(callback.is_callback_failed());
179 assert_eq!(callback.callback_failure_message(), Some("boom"));
180
181 let reentrant = TimerError::reentrant_operation("join()");
182 assert!(reentrant.is_reentrant_operation());
183 assert_eq!(reentrant.reentrant_operation_message(), Some("join()"));
184 assert!(
185 callback.backtrace().status() != std::backtrace::BacktraceStatus::Disabled
186 || callback.backtrace().status() == std::backtrace::BacktraceStatus::Disabled
187 );
188 }
189}