use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("value error: {0}")]
Value(#[from] ValueError),
#[error("protocol error: {0}")]
Protocol(#[from] ProtocolError),
#[error("parse error: {0}")]
Parse(#[from] ParseError),
#[error("device error: {0}")]
Device(#[from] DeviceError),
#[error("device not found")]
DeviceNotFound,
#[error("device is not connected")]
NotConnected,
#[error("device does not support this capability")]
CapabilityNotSupported,
}
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum ValueError {
#[error("value {actual} is out of range [{min}, {max}]")]
OutOfRange {
min: u16,
max: u16,
actual: u16,
},
#[error("invalid power state: {0}")]
InvalidPowerState(String),
#[error("hue value {0} is out of range [0, 360]")]
InvalidHue(u16),
#[error("saturation value {0} is out of range [0, 100]")]
InvalidSaturation(u8),
#[error("brightness value {0} is out of range [0, 100]")]
InvalidBrightness(u8),
#[error("invalid hex color: {0}")]
InvalidHexColor(String),
}
#[derive(Debug, Error)]
pub enum ProtocolError {
#[cfg(feature = "http")]
#[error("HTTP request failed: {0}")]
Http(#[from] reqwest::Error),
#[cfg(feature = "mqtt")]
#[error("MQTT error: {0}")]
Mqtt(#[from] rumqttc::ClientError),
#[error("connection failed: {0}")]
ConnectionFailed(String),
#[error("request timed out after {0} ms")]
Timeout(u64),
#[error("invalid address: {0}")]
InvalidAddress(String),
#[error("authentication failed")]
AuthenticationFailed,
#[error("channel closed: {0}")]
ChannelClosed(String),
}
#[derive(Debug, Error)]
pub enum ParseError {
#[error("JSON parse error: {0}")]
Json(#[from] serde_json::Error),
#[error("missing field in response: {0}")]
MissingField(String),
#[error("unexpected response format: {0}")]
UnexpectedFormat(String),
#[error("failed to parse {field}: {message}")]
InvalidValue {
field: String,
message: String,
},
}
#[derive(Debug, Error)]
pub enum DeviceError {
#[error("device does not support {capability}")]
UnsupportedCapability {
capability: String,
},
#[error("device is not connected")]
NotConnected,
#[error("command rejected: {0}")]
CommandRejected(String),
#[error("invalid device configuration: {0}")]
InvalidConfiguration(String),
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn value_error_display() {
let err = ValueError::OutOfRange {
min: 0,
max: 100,
actual: 150,
};
assert_eq!(err.to_string(), "value 150 is out of range [0, 100]");
}
#[test]
fn error_from_value_error() {
let value_err = ValueError::InvalidHue(400);
let err: Error = value_err.into();
assert!(matches!(err, Error::Value(ValueError::InvalidHue(400))));
}
#[test]
fn parse_error_display() {
let err = ParseError::MissingField("POWER".to_string());
assert_eq!(err.to_string(), "missing field in response: POWER");
}
#[test]
fn device_error_display() {
let err = DeviceError::UnsupportedCapability {
capability: "energy monitoring".to_string(),
};
assert_eq!(err.to_string(), "device does not support energy monitoring");
}
}