rustuya 0.3.0-rc.2

A fast and concurrent Tuya Local API implementation in Rust
Documentation
//! Error types and result definitions for the Tuya protocol.
//!
//! Defines Tuya-specific error codes and provides conversion from standard IO and JSON errors.

use crate::macros::define_error_codes;
use thiserror::Error;

#[derive(Error, Debug, Clone)]
pub enum TuyaError {
    #[error("IO error ({kind:?}): {message}")]
    Io {
        kind: std::io::ErrorKind,
        message: String,
    },

    #[error("JSON error: {0}")]
    Json(String),

    #[error("Decryption failed")]
    DecryptionFailed,

    #[error("Encryption failed")]
    EncryptionFailed,

    #[error("Invalid payload")]
    InvalidPayload,

    #[error("Timeout waiting for device")]
    Timeout,

    #[error("CRC mismatch")]
    CrcMismatch,

    #[error("HMAC mismatch")]
    HmacMismatch,

    #[error("Socket connection failed")]
    ConnectionFailed,

    #[error("Invalid header")]
    InvalidHeader,

    #[error("Decode error: {0}")]
    DecodeError(String),

    #[error("Device offline")]
    Offline,

    #[error("Handshake failed")]
    HandshakeFailed,

    #[error("Check device key or version (Error 914)")]
    KeyOrVersionError,

    #[error("Device ID '{0}' not found")]
    DeviceNotFound(String),

    /// The broadcast bus skipped messages because the consumer fell behind by
    /// more than the bus capacity (currently 128 messages). The skipped
    /// messages are permanently lost; the caller should re-query device state
    /// or accept the gap. Surfaced by [`Device::receive`] and by the synthetic
    /// `listener_lagged` event emitted by [`Device::listener`].
    ///
    /// [`Device::receive`]: crate::Device::receive
    /// [`Device::listener`]: crate::Device::listener
    #[error("Broadcast bus lagged, {skipped} messages lost")]
    BroadcastLagged { skipped: u64 },
}

pub type Result<T> = std::result::Result<T, TuyaError>;

impl From<std::io::Error> for TuyaError {
    fn from(err: std::io::Error) -> Self {
        TuyaError::Io {
            kind: err.kind(),
            message: err.to_string(),
        }
    }
}

impl From<serde_json::Error> for TuyaError {
    fn from(err: serde_json::Error) -> Self {
        TuyaError::Json(err.to_string())
    }
}

impl TuyaError {
    /// Builds an `Io` error with the given kind and message.
    pub fn io<S: Into<String>>(kind: std::io::ErrorKind, message: S) -> Self {
        Self::Io {
            kind,
            message: message.into(),
        }
    }

    /// Builds an `Io` error with `ErrorKind::Other` and the given message.
    pub fn io_other<S: Into<String>>(message: S) -> Self {
        Self::io(std::io::ErrorKind::Other, message)
    }

    /// Returns the underlying `io::ErrorKind` if this is an `Io` error.
    #[must_use]
    pub fn io_kind(&self) -> Option<std::io::ErrorKind> {
        if let Self::Io { kind, .. } = self {
            Some(*kind)
        } else {
            None
        }
    }

    #[must_use]
    pub fn code(&self) -> u32 {
        match self {
            TuyaError::Io { .. } => ERR_CONNECT,
            TuyaError::Json(_) => ERR_JSON,
            TuyaError::DecryptionFailed => ERR_KEY_OR_VER,
            TuyaError::EncryptionFailed => ERR_KEY_OR_VER,
            TuyaError::InvalidPayload => ERR_PAYLOAD,
            TuyaError::CrcMismatch => ERR_KEY_OR_VER,
            TuyaError::HmacMismatch => ERR_KEY_OR_VER,
            TuyaError::ConnectionFailed => ERR_CONNECT,
            TuyaError::InvalidHeader => ERR_PAYLOAD,
            TuyaError::DecodeError(_) => ERR_PAYLOAD,
            TuyaError::Offline => ERR_OFFLINE,
            TuyaError::HandshakeFailed => ERR_KEY_OR_VER,
            TuyaError::KeyOrVersionError => ERR_KEY_OR_VER,
            TuyaError::DeviceNotFound(_) => ERR_JSON,
            TuyaError::Timeout => ERR_TIMEOUT,
            TuyaError::BroadcastLagged { .. } => ERR_STATE,
        }
    }

    #[must_use]
    pub fn from_code(code: u32) -> Self {
        match code {
            ERR_JSON => TuyaError::Json("Generic JSON error".to_string()),
            ERR_CONNECT => TuyaError::ConnectionFailed,
            ERR_TIMEOUT => TuyaError::Timeout,
            ERR_OFFLINE => TuyaError::Offline,
            ERR_KEY_OR_VER => TuyaError::KeyOrVersionError,
            ERR_PAYLOAD => TuyaError::InvalidPayload,
            _ => TuyaError::io_other(format!("Unknown error code: {code}")),
        }
    }
}

define_error_codes! {
    ERR_SUCCESS = 0 => "Connection Successful",
    ERR_JSON = 900 => "Invalid JSON Response from Device",
    ERR_CONNECT = 901 => "Network Error: Unable to Connect",
    ERR_TIMEOUT = 902 => "Timeout Waiting for Device",
    ERR_RANGE = 903 => "Specified Value Out of Range",
    ERR_PAYLOAD = 904 => "Unexpected Payload from Device",
    ERR_OFFLINE = 905 => "Network Error: Device Unreachable",
    ERR_STATE = 906 => "Device in Unknown State",
    ERR_FUNCTION = 907 => "Function Not Supported by Device",
    ERR_DEVTYPE = 908 => "Device22 Detected: Retry Command",
    ERR_CLOUDKEY = 909 => "Missing Tuya Cloud Key and Secret",
    ERR_CLOUDRESP = 910 => "Invalid JSON Response from Cloud",
    ERR_CLOUDTOKEN = 911 => "Unable to Get Cloud Token",
    ERR_PARAMS = 912 => "Missing Function Parameters",
    ERR_CLOUD = 913 => "Error Response from Tuya Cloud",
    ERR_KEY_OR_VER = 914 => "Check device key or version",
}