use std::backtrace::Backtrace;
use std::error::Error as StdError;
use std::fmt::{Display, Formatter};
use std::sync::Arc;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct TimerError {
kind: TimerErrorKind,
backtrace: Arc<Backtrace>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum TimerErrorKind {
InvalidParameter(String),
NotRunning,
NotPaused,
ReentrantOperation(String),
CallbackTimedOut(Duration),
CallbackFailed(String),
}
impl TimerError {
pub fn invalid_parameter(message: impl Into<String>) -> Self {
Self::new(TimerErrorKind::InvalidParameter(message.into()))
}
pub fn not_running() -> Self {
Self::new(TimerErrorKind::NotRunning)
}
pub fn not_paused() -> Self {
Self::new(TimerErrorKind::NotPaused)
}
pub fn reentrant_operation(message: impl Into<String>) -> Self {
Self::new(TimerErrorKind::ReentrantOperation(message.into()))
}
pub fn callback_timed_out(timeout: Duration) -> Self {
Self::new(TimerErrorKind::CallbackTimedOut(timeout))
}
pub fn callback_failed(message: impl Into<String>) -> Self {
Self::new(TimerErrorKind::CallbackFailed(message.into()))
}
pub fn is_invalid_parameter(&self) -> bool {
matches!(self.kind, TimerErrorKind::InvalidParameter(_))
}
pub fn invalid_parameter_message(&self) -> Option<&str> {
match &self.kind {
TimerErrorKind::InvalidParameter(message) => Some(message.as_str()),
_ => None,
}
}
pub fn is_not_running(&self) -> bool {
matches!(self.kind, TimerErrorKind::NotRunning)
}
pub fn is_not_paused(&self) -> bool {
matches!(self.kind, TimerErrorKind::NotPaused)
}
pub fn is_reentrant_operation(&self) -> bool {
matches!(self.kind, TimerErrorKind::ReentrantOperation(_))
}
pub fn reentrant_operation_message(&self) -> Option<&str> {
match &self.kind {
TimerErrorKind::ReentrantOperation(message) => Some(message.as_str()),
_ => None,
}
}
pub fn is_callback_failed(&self) -> bool {
matches!(self.kind, TimerErrorKind::CallbackFailed(_))
}
pub fn is_callback_timed_out(&self) -> bool {
matches!(self.kind, TimerErrorKind::CallbackTimedOut(_))
}
pub fn callback_timeout(&self) -> Option<Duration> {
match &self.kind {
TimerErrorKind::CallbackTimedOut(timeout) => Some(*timeout),
_ => None,
}
}
pub fn callback_failure_message(&self) -> Option<&str> {
match &self.kind {
TimerErrorKind::CallbackFailed(message) => Some(message.as_str()),
_ => None,
}
}
pub fn backtrace(&self) -> &Backtrace {
self.backtrace.as_ref()
}
fn new(kind: TimerErrorKind) -> Self {
Self {
kind,
backtrace: Arc::new(Backtrace::capture()),
}
}
}
impl Display for TimerError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self.kind {
TimerErrorKind::InvalidParameter(message) => {
write!(f, "Invalid parameter: {message}")
}
TimerErrorKind::NotRunning => write!(f, "Operation requires a running timer."),
TimerErrorKind::NotPaused => write!(f, "Operation requires a paused timer."),
TimerErrorKind::ReentrantOperation(message) => {
write!(f, "Reentrant timer operation is not allowed: {message}")
}
TimerErrorKind::CallbackTimedOut(timeout) => {
write!(f, "Callback execution timed out after {timeout:?}")
}
TimerErrorKind::CallbackFailed(message) => {
write!(f, "Callback execution failed: {message}")
}
}
}
}
impl StdError for TimerError {}
impl PartialEq for TimerError {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind
}
}
impl Eq for TimerError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_messages_are_stable_and_descriptive() {
assert_eq!(
TimerError::invalid_parameter("bad interval").to_string(),
"Invalid parameter: bad interval"
);
assert_eq!(
TimerError::not_running().to_string(),
"Operation requires a running timer."
);
assert_eq!(
TimerError::not_paused().to_string(),
"Operation requires a paused timer."
);
assert_eq!(
TimerError::reentrant_operation("stop() from callback").to_string(),
"Reentrant timer operation is not allowed: stop() from callback"
);
assert_eq!(
TimerError::callback_timed_out(Duration::from_secs(2)).to_string(),
"Callback execution timed out after 2s"
);
assert_eq!(
TimerError::callback_failed("boom").to_string(),
"Callback execution failed: boom"
);
}
#[test]
fn error_helpers_expose_stable_queries() {
let invalid = TimerError::invalid_parameter("interval");
assert!(invalid.is_invalid_parameter());
assert_eq!(invalid.invalid_parameter_message(), Some("interval"));
assert!(!invalid.is_not_running());
let callback = TimerError::callback_failed("boom");
assert!(callback.is_callback_failed());
assert_eq!(callback.callback_failure_message(), Some("boom"));
let timed_out = TimerError::callback_timed_out(Duration::from_millis(250));
assert!(timed_out.is_callback_timed_out());
assert_eq!(
timed_out.callback_timeout(),
Some(Duration::from_millis(250))
);
let reentrant = TimerError::reentrant_operation("join()");
assert!(reentrant.is_reentrant_operation());
assert_eq!(reentrant.reentrant_operation_message(), Some("join()"));
assert!(
callback.backtrace().status() != std::backtrace::BacktraceStatus::Disabled
|| callback.backtrace().status() == std::backtrace::BacktraceStatus::Disabled
);
}
}