use thiserror::Error;
#[derive(Error, Debug)]
pub enum TreadlyError {
#[error("BLE error: {0}")]
Ble(#[from] btleplug::Error),
#[error("Treadly device not found")]
DeviceNotFound,
#[error("Failed to connect to device: {0}")]
ConnectionFailed(String),
#[error("Device disconnected")]
Disconnected,
#[error("Authentication failed: {0}")]
AuthenticationFailed(String),
#[error("Command timed out after {timeout_ms}ms")]
Timeout {
timeout_ms: u64,
},
#[error("Invalid command parameters: {0}")]
InvalidParameters(String),
#[error("Protocol error: {0}")]
Protocol(String),
#[error("Device error: status code {status:02X}")]
DeviceError {
status: u8,
},
#[error("Emergency stop is active - reset required")]
EmergencyStop,
#[error("Device not ready: {reason}")]
NotReady {
reason: String,
},
#[error("Invalid device state: {state}")]
InvalidState {
state: String,
},
#[error("Temperature safety stop activated - critical temperature reached")]
TemperatureSafetyStop,
#[error("High temperature detected - speed reduction required")]
HighTemperatureSpeedReduction,
#[error("Temperature sensor error - unable to monitor safety")]
TemperatureSensorError,
#[error("Device requires power cycle - safety condition")]
PowerCycleRequired,
#[error("Connection lost - emergency stop activated for safety")]
ConnectionLostEmergencyStop,
#[error("Connection health degraded - monitoring for safety")]
ConnectionHealthDegraded,
#[error("Emergency stop command sent but acknowledgment not received")]
EmergencyStopNotAcknowledged,
#[error("Failed to parse message: {0}")]
ParseError(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Other error: {0}")]
Other(String),
}
pub type Result<T> = std::result::Result<T, TreadlyError>;
impl TreadlyError {
#[must_use]
pub const fn is_connection_error(&self) -> bool {
matches!(
self,
Self::Ble(_) | Self::ConnectionFailed(_) | Self::Disconnected | Self::DeviceNotFound
)
}
#[must_use]
pub const fn is_recoverable(&self) -> bool {
matches!(
self,
Self::Timeout { .. } | Self::NotReady { .. } | Self::InvalidParameters(_)
)
}
#[must_use]
pub const fn requires_reset(&self) -> bool {
matches!(self, Self::EmergencyStop)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_classification() {
let connection_error = TreadlyError::ConnectionFailed("test".to_string());
assert!(connection_error.is_connection_error());
assert!(!connection_error.is_recoverable());
assert!(!connection_error.requires_reset());
let timeout_error = TreadlyError::Timeout { timeout_ms: 5000 };
assert!(!timeout_error.is_connection_error());
assert!(timeout_error.is_recoverable());
assert!(!timeout_error.requires_reset());
let emergency_error = TreadlyError::EmergencyStop;
assert!(!emergency_error.is_connection_error());
assert!(!emergency_error.is_recoverable());
assert!(emergency_error.requires_reset());
}
#[test]
fn test_error_display() {
let error = TreadlyError::InvalidParameters("speed out of range".to_string());
let error_string = format!("{error}");
assert!(error_string.contains("Invalid command parameters"));
assert!(error_string.contains("speed out of range"));
}
}