From 482f8db344feb8247a342e27a5ef8745e2f280d5 Mon Sep 17 00:00:00 2001 From: Dinculescu Date: Fri, 26 Jan 2024 22:33:44 +0000 Subject: [PATCH] Improve the ergonomics of the `send()` method --- CHANGELOG.md | 4 + tapo/examples/tapo_l530.rs | 2 +- tapo/examples/tapo_l930.rs | 2 +- tapo/src/api.rs | 2 + tapo/src/api/api_client.rs | 4 +- tapo/src/api/color_light_handler.rs | 39 ++++----- tapo/src/api/color_light_strip_handler.rs | 39 ++++----- tapo/src/api/handler_ext.rs | 7 ++ tapo/src/lib.rs | 2 +- .../requests/set_device_info/color_light.rs | 84 ++++++++++--------- 10 files changed, 102 insertions(+), 83 deletions(-) create mode 100644 tapo/src/api/handler_ext.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 625aee3..776173e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ file. This change log follows the conventions of ## [Rust Unreleased][Unreleased] +### Changed + +- The `send()` method of the `.set()` API now takes a reference to the device handler in order to allow for better ergonomics. + ## [Python Unreleased][Unreleased] ### Added diff --git a/tapo/examples/tapo_l530.rs b/tapo/examples/tapo_l530.rs index 4569a47..c6e52ed 100644 --- a/tapo/examples/tapo_l530.rs +++ b/tapo/examples/tapo_l530.rs @@ -52,7 +52,7 @@ async fn main() -> Result<(), Box> { .set() .brightness(50) .color(Color::HotPink) - .send() + .send(&device) .await?; info!("Waiting 2 seconds..."); diff --git a/tapo/examples/tapo_l930.rs b/tapo/examples/tapo_l930.rs index 128791b..4e77c6e 100644 --- a/tapo/examples/tapo_l930.rs +++ b/tapo/examples/tapo_l930.rs @@ -55,7 +55,7 @@ async fn main() -> Result<(), Box> { .set() .brightness(50) .color(Color::HotPink) - .send() + .send(&device) .await?; info!("Waiting 2 seconds..."); diff --git a/tapo/src/api.rs b/tapo/src/api.rs index 72c40e8..4d5ac51 100644 --- a/tapo/src/api.rs +++ b/tapo/src/api.rs @@ -3,6 +3,7 @@ mod child_devices; mod color_light_handler; mod color_light_strip_handler; mod generic_device_handler; +mod handler_ext; mod hub_handler; mod light_handler; mod plug_energy_monitoring_handler; @@ -14,6 +15,7 @@ pub use child_devices::*; pub use color_light_handler::*; pub use color_light_strip_handler::*; pub use generic_device_handler::*; +pub use handler_ext::*; pub use hub_handler::*; pub use light_handler::*; pub use plug_energy_monitoring_handler::*; diff --git a/tapo/src/api/api_client.rs b/tapo/src/api/api_client.rs index 839512c..1f0f6e6 100644 --- a/tapo/src/api/api_client.rs +++ b/tapo/src/api/api_client.rs @@ -23,8 +23,10 @@ use crate::responses::{ const TERMINAL_UUID: &str = "00-00-00-00-00-00"; +/// Implemented by all ApiClient implementations. #[async_trait] -pub(crate) trait ApiClientExt: std::fmt::Debug + Send + Sync { +pub trait ApiClientExt: std::fmt::Debug + Send + Sync { + /// Sets the device info. async fn set_device_info(&self, device_info_params: serde_json::Value) -> Result<(), Error>; } diff --git a/tapo/src/api/color_light_handler.rs b/tapo/src/api/color_light_handler.rs index b93917f..674fa6c 100644 --- a/tapo/src/api/color_light_handler.rs +++ b/tapo/src/api/color_light_handler.rs @@ -1,8 +1,9 @@ -use crate::api::ApiClient; use crate::error::Error; use crate::requests::{Color, ColorLightSetDeviceInfoParams}; use crate::responses::{DeviceInfoColorLightResult, DeviceUsageEnergyMonitoringResult}; +use super::{ApiClient, ApiClientExt, HandlerExt}; + /// Handler for the [L530](https://www.tapo.com/en/search/?q=L530), [L630](https://www.tapo.com/en/search/?q=L630) and [L900](https://www.tapo.com/en/search/?q=L900) devices. pub struct ColorLightHandler { client: ApiClient, @@ -21,18 +22,12 @@ impl ColorLightHandler { /// Turns *on* the device. pub async fn on(&self) -> Result<(), Error> { - ColorLightSetDeviceInfoParams::new(&self.client) - .on() - .send() - .await + ColorLightSetDeviceInfoParams::new().on().send(self).await } /// Turns *off* the device. pub async fn off(&self) -> Result<(), Error> { - ColorLightSetDeviceInfoParams::new(&self.client) - .off() - .send() - .await + ColorLightSetDeviceInfoParams::new().off().send(self).await } /// *Hardware resets* the device. @@ -82,13 +77,13 @@ impl ColorLightHandler { /// .set() /// .brightness(50) /// .color(Color::HotPink) - /// .send() + /// .send(&device) /// .await?; /// # Ok(()) /// # } /// ``` pub fn set(&self) -> ColorLightSetDeviceInfoParams { - ColorLightSetDeviceInfoParams::new(&self.client) + ColorLightSetDeviceInfoParams::new() } /// Sets the *brightness* and turns *on* the device. @@ -97,9 +92,9 @@ impl ColorLightHandler { /// /// * `brightness` - between 1 and 100 pub async fn set_brightness(&self, brightness: u8) -> Result<(), Error> { - ColorLightSetDeviceInfoParams::new(&self.client) + ColorLightSetDeviceInfoParams::new() .brightness(brightness) - .send() + .send(self) .await } @@ -109,9 +104,9 @@ impl ColorLightHandler { /// /// * `color` - one of [crate::requests::Color] as defined in the Google Home app pub async fn set_color(&self, color: Color) -> Result<(), Error> { - ColorLightSetDeviceInfoParams::new(&self.client) + ColorLightSetDeviceInfoParams::new() .color(color) - .send() + .send(self) .await } @@ -122,9 +117,9 @@ impl ColorLightHandler { /// * `hue` - between 1 and 360 /// * `saturation` - between 1 and 100 pub async fn set_hue_saturation(&self, hue: u16, saturation: u8) -> Result<(), Error> { - ColorLightSetDeviceInfoParams::new(&self.client) + ColorLightSetDeviceInfoParams::new() .hue_saturation(hue, saturation) - .send() + .send(self) .await } @@ -134,9 +129,15 @@ impl ColorLightHandler { /// /// * `color_temperature` - between 2500 and 6500 pub async fn set_color_temperature(&self, color_temperature: u16) -> Result<(), Error> { - ColorLightSetDeviceInfoParams::new(&self.client) + ColorLightSetDeviceInfoParams::new() .color_temperature(color_temperature) - .send() + .send(self) .await } } + +impl HandlerExt for ColorLightHandler { + fn get_client(&self) -> &impl ApiClientExt { + &self.client + } +} diff --git a/tapo/src/api/color_light_strip_handler.rs b/tapo/src/api/color_light_strip_handler.rs index a583e79..8b2e8a8 100644 --- a/tapo/src/api/color_light_strip_handler.rs +++ b/tapo/src/api/color_light_strip_handler.rs @@ -1,8 +1,9 @@ -use crate::api::ApiClient; use crate::error::Error; use crate::requests::{Color, ColorLightSetDeviceInfoParams, LightingEffect}; use crate::responses::{DeviceInfoColorLightStripResult, DeviceUsageEnergyMonitoringResult}; +use super::{ApiClient, ApiClientExt, HandlerExt}; + /// Handler for the [L920](https://www.tapo.com/en/search/?q=L920) and [L930](https://www.tapo.com/en/search/?q=L930) devices. pub struct ColorLightStripHandler { client: ApiClient, @@ -21,18 +22,12 @@ impl ColorLightStripHandler { /// Turns *on* the device. pub async fn on(&self) -> Result<(), Error> { - ColorLightSetDeviceInfoParams::new(&self.client) - .on() - .send() - .await + ColorLightSetDeviceInfoParams::new().on().send(self).await } /// Turns *off* the device. pub async fn off(&self) -> Result<(), Error> { - ColorLightSetDeviceInfoParams::new(&self.client) - .off() - .send() - .await + ColorLightSetDeviceInfoParams::new().off().send(self).await } /// *Hardware resets* the device. @@ -83,13 +78,13 @@ impl ColorLightStripHandler { /// .set() /// .brightness(50) /// .color(Color::HotPink) - /// .send() + /// .send(&device) /// .await?; /// # Ok(()) /// # } /// ``` pub fn set(&self) -> ColorLightSetDeviceInfoParams { - ColorLightSetDeviceInfoParams::new(&self.client) + ColorLightSetDeviceInfoParams::new() } /// Sets the *brightness* and turns *on* the device. @@ -99,9 +94,9 @@ impl ColorLightStripHandler { /// /// * `brightness` - between 1 and 100 pub async fn set_brightness(&self, brightness: u8) -> Result<(), Error> { - ColorLightSetDeviceInfoParams::new(&self.client) + ColorLightSetDeviceInfoParams::new() .brightness(brightness) - .send() + .send(self) .await } @@ -112,9 +107,9 @@ impl ColorLightStripHandler { /// /// * `color` - one of [crate::requests::Color] as defined in the Google Home app pub async fn set_color(&self, color: Color) -> Result<(), Error> { - ColorLightSetDeviceInfoParams::new(&self.client) + ColorLightSetDeviceInfoParams::new() .color(color) - .send() + .send(self) .await } @@ -126,9 +121,9 @@ impl ColorLightStripHandler { /// * `hue` - between 1 and 360 /// * `saturation` - between 1 and 100 pub async fn set_hue_saturation(&self, hue: u16, saturation: u8) -> Result<(), Error> { - ColorLightSetDeviceInfoParams::new(&self.client) + ColorLightSetDeviceInfoParams::new() .hue_saturation(hue, saturation) - .send() + .send(self) .await } @@ -139,9 +134,9 @@ impl ColorLightStripHandler { /// /// * `color_temperature` - between 2500 and 6500 pub async fn set_color_temperature(&self, color_temperature: u16) -> Result<(), Error> { - ColorLightSetDeviceInfoParams::new(&self.client) + ColorLightSetDeviceInfoParams::new() .color_temperature(color_temperature) - .send() + .send(self) .await } @@ -159,3 +154,9 @@ impl ColorLightStripHandler { .await } } + +impl HandlerExt for ColorLightStripHandler { + fn get_client(&self) -> &impl ApiClientExt { + &self.client + } +} diff --git a/tapo/src/api/handler_ext.rs b/tapo/src/api/handler_ext.rs new file mode 100644 index 0000000..72c5c7b --- /dev/null +++ b/tapo/src/api/handler_ext.rs @@ -0,0 +1,7 @@ +use super::ApiClientExt; + +/// Implemented by all device handlers. +pub trait HandlerExt: Send + Sync { + /// Returns the client used by this handler. + fn get_client(&self) -> &impl ApiClientExt; +} diff --git a/tapo/src/lib.rs b/tapo/src/lib.rs index 59ef0cd..5732bfd 100644 --- a/tapo/src/lib.rs +++ b/tapo/src/lib.rs @@ -50,7 +50,7 @@ //! .set() //! .brightness(50) //! .color(Color::HotPink) -//! .send() +//! .send(&device) //! .await?; //! //! println!("Waiting 2 seconds..."); diff --git a/tapo/src/requests/set_device_info/color_light.rs b/tapo/src/requests/set_device_info/color_light.rs index 267229c..8cf8512 100644 --- a/tapo/src/requests/set_device_info/color_light.rs +++ b/tapo/src/requests/set_device_info/color_light.rs @@ -1,14 +1,12 @@ use serde::Serialize; -use crate::api::ApiClientExt; use crate::error::Error; use crate::requests::color::{Color, COLOR_MAP}; +use crate::{ApiClientExt, HandlerExt}; /// Builder that is used by the [`crate::ColorLightHandler::set`] API to set multiple properties in a single request. -#[derive(Debug, Serialize)] -pub struct ColorLightSetDeviceInfoParams<'a> { - #[serde(skip)] - client: &'a dyn ApiClientExt, +#[derive(Debug, Clone, Default, Serialize)] +pub struct ColorLightSetDeviceInfoParams { #[serde(skip_serializing_if = "Option::is_none")] device_on: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -22,7 +20,7 @@ pub struct ColorLightSetDeviceInfoParams<'a> { color_temperature: Option, } -impl<'a> ColorLightSetDeviceInfoParams<'a> { +impl ColorLightSetDeviceInfoParams { /// Turns *on* the device. [`ColorLightSetDeviceInfoParams::send`] must be called at the end to apply the changes. pub fn on(mut self) -> Self { self.device_on = Some(true); @@ -94,23 +92,17 @@ impl<'a> ColorLightSetDeviceInfoParams<'a> { } /// Performs a request to apply the changes to the device. - pub async fn send(self) -> Result<(), Error> { + pub async fn send(self, client: &impl HandlerExt) -> Result<(), Error> { self.validate()?; let json = serde_json::to_value(&self)?; - self.client.set_device_info(json).await + client.get_client().set_device_info(json).await } } -impl<'a> ColorLightSetDeviceInfoParams<'a> { - pub(crate) fn new(client: &'a dyn ApiClientExt) -> Self { - Self { - client, - device_on: None, - brightness: None, - hue: None, - saturation: None, - color_temperature: None, - } +impl ColorLightSetDeviceInfoParams { + /// Creates a new [`ColorLightSetDeviceInfoParams`] builder. + pub fn new() -> Self { + Self::default() } fn validate(&self) -> Result<(), Error> { @@ -173,6 +165,8 @@ impl<'a> ColorLightSetDeviceInfoParams<'a> { mod tests { use async_trait::async_trait; + use crate::ApiClientExt; + use super::*; #[derive(Debug)] @@ -185,9 +179,18 @@ mod tests { } } + #[derive(Debug)] + struct MockHandler; + + impl HandlerExt for MockHandler { + fn get_client(&self) -> &impl ApiClientExt { + &MockApiClient + } + } + #[tokio::test] async fn hue_saturation_overrides_color_temperature() { - let params = ColorLightSetDeviceInfoParams::new(&MockApiClient); + let params = ColorLightSetDeviceInfoParams::new(); let params = params.color_temperature(3000); let params = params.hue_saturation(50, 50); @@ -196,12 +199,12 @@ mod tests { assert_eq!(params.saturation, Some(50)); assert_eq!(params.color_temperature, Some(0)); - assert!(params.send().await.is_ok()) + assert!(params.send(&MockHandler).await.is_ok()) } #[tokio::test] async fn color_temperature_overrides_hue_saturation() { - let params = ColorLightSetDeviceInfoParams::new(&MockApiClient); + let params = ColorLightSetDeviceInfoParams::new(); let params = params.hue_saturation(50, 50); let params = params.color_temperature(3000); @@ -210,13 +213,13 @@ mod tests { assert_eq!(params.saturation, Some(100)); assert_eq!(params.color_temperature, Some(3000)); - assert!(params.send().await.is_ok()) + assert!(params.send(&MockHandler).await.is_ok()) } #[tokio::test] async fn no_property_validation() { - let params = ColorLightSetDeviceInfoParams::new(&MockApiClient); - let result = params.send().await; + let params = ColorLightSetDeviceInfoParams::new(); + let result = params.send(&MockHandler).await; assert!(matches!( result.err(), Some(Error::Validation { field, message }) if field == "DeviceInfoParams" && message == "requires at least one property" @@ -225,15 +228,15 @@ mod tests { #[tokio::test] async fn brightness_validation() { - let params = ColorLightSetDeviceInfoParams::new(&MockApiClient); - let result = params.brightness(0).send().await; + let params = ColorLightSetDeviceInfoParams::new(); + let result = params.brightness(0).send(&MockHandler).await; assert!(matches!( result.err(), Some(Error::Validation { field, message }) if field == "brightness" && message == "must be between 1 and 100" )); - let params = ColorLightSetDeviceInfoParams::new(&MockApiClient); - let result = params.brightness(101).send().await; + let params = ColorLightSetDeviceInfoParams::new(); + let result = params.brightness(101).send(&MockHandler).await; assert!(matches!( result.err(), Some(Error::Validation { field, message }) if field == "brightness" && message == "must be between 1 and 100" @@ -242,15 +245,15 @@ mod tests { #[tokio::test] async fn hue_validation() { - let params = ColorLightSetDeviceInfoParams::new(&MockApiClient); - let result = params.hue_saturation(0, 50).send().await; + let params = ColorLightSetDeviceInfoParams::new(); + let result = params.hue_saturation(0, 50).send(&MockHandler).await; assert!(matches!( result.err(), Some(Error::Validation { field, message }) if field == "hue" && message == "must be between 1 and 360" )); - let params = ColorLightSetDeviceInfoParams::new(&MockApiClient); - let result = params.hue_saturation(361, 50).send().await; + let params = ColorLightSetDeviceInfoParams::new(); + let result = params.hue_saturation(361, 50).send(&MockHandler).await; assert!(matches!( result.err(), Some(Error::Validation { field, message }) if field == "hue" && message == "must be between 1 and 360" @@ -259,15 +262,15 @@ mod tests { #[tokio::test] async fn saturation_validation() { - let params = ColorLightSetDeviceInfoParams::new(&MockApiClient); - let result = params.hue_saturation(1, 0).send().await; + let params = ColorLightSetDeviceInfoParams::new(); + let result = params.hue_saturation(1, 0).send(&MockHandler).await; assert!(matches!( result.err(), Some(Error::Validation { field, message }) if field == "saturation" && message == "must be between 1 and 100" )); - let params = ColorLightSetDeviceInfoParams::new(&MockApiClient); - let result = params.hue_saturation(1, 101).send().await; + let params = ColorLightSetDeviceInfoParams::new(); + let result = params.hue_saturation(1, 101).send(&MockHandler).await; assert!(matches!( result.err(), Some(Error::Validation { field, message }) if field == "saturation" && message == "must be between 1 and 100" @@ -276,16 +279,15 @@ mod tests { #[tokio::test] async fn color_temperature_validation() { - let params: ColorLightSetDeviceInfoParams = - ColorLightSetDeviceInfoParams::new(&MockApiClient); - let result = params.color_temperature(2499).send().await; + let params: ColorLightSetDeviceInfoParams = ColorLightSetDeviceInfoParams::new(); + let result = params.color_temperature(2499).send(&MockHandler).await; assert!(matches!( result.err(), Some(Error::Validation { field, message }) if field == "color_temperature" && message == "must be between 2500 and 6500" )); - let params = ColorLightSetDeviceInfoParams::new(&MockApiClient); - let result = params.color_temperature(6501).send().await; + let params = ColorLightSetDeviceInfoParams::new(); + let result = params.color_temperature(6501).send(&MockHandler).await; assert!(matches!( result.err(), Some(Error::Validation { field, message }) if field == "color_temperature" && message == "must be between 2500 and 6500"