From 55e7e7e9e9bae5b073ab97162d4633b46d2f2d6b Mon Sep 17 00:00:00 2001 From: Alex van de Sandt Date: Sat, 8 Aug 2026 16:12:40 -0500 Subject: [PATCH] Implement the get_set_command macro --- src/command/bc125at/key_beep.rs | 143 ++++---------------------------- src/command/bc125at/volume.rs | 4 + src/command/mod.rs | 1 + src/lib.rs | 3 + src/macros.rs | 130 +++++++++++++++++++++++++++++ 5 files changed, 152 insertions(+), 129 deletions(-) create mode 100644 src/macros.rs diff --git a/src/command/bc125at/key_beep.rs b/src/command/bc125at/key_beep.rs index cdbbe4d..effe5a7 100644 --- a/src/command/bc125at/key_beep.rs +++ b/src/command/bc125at/key_beep.rs @@ -1,130 +1,15 @@ -use itertools::Itertools; -use tokio_util::bytes::Bytes; - -use crate::{ - Command, OkResponse, - command::{IntoParam, NoParams, Params, Response, ResponseField}, -}; - -#[derive(Clone, Copy, Debug, thiserror::Error)] -pub enum KeyBeepSettingError { - #[error("invalid beep level")] - BeepLevel, - #[error("invalid key lock status")] - KeyLockStatus, - #[error("expected two fields")] - Malformed, -} - -pub enum BeepLevel { - Auto, - Off, -} - -impl IntoParam for BeepLevel { - fn into_param(self) -> Bytes { - match self { - BeepLevel::Auto => b"0".as_ref(), - BeepLevel::Off => b"99".as_ref(), - } - .into() - } -} - -impl ResponseField for BeepLevel { - fn deserialize(raw: &[u8]) -> Option { - match raw { - b"0" => Some(Self::Auto), - b"99" => Some(Self::Off), - _ => None, - } - } -} - -pub enum KeyLockStatus { - Off, - On, -} - -impl IntoParam for KeyLockStatus { - fn into_param(self) -> Bytes { - match self { - KeyLockStatus::Off => b"0".as_ref(), - KeyLockStatus::On => b"1".as_ref(), - } - .into() - } -} - -impl ResponseField for KeyLockStatus { - fn deserialize(raw: &[u8]) -> Option { - match raw { - b"0" => Some(Self::Off), - b"1" => Some(Self::On), - _ => None, - } - } -} - -pub struct KeyBeepSetting(BeepLevel, KeyLockStatus); - -impl IntoIterator for KeyBeepSetting { - type Item = Bytes; - - type IntoIter = std::array::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - [self.0.into_param(), self.1.into_param()].into_iter() - } -} - -impl Params for KeyBeepSetting { - fn size_hint(&self) -> usize { - 1 // commas - + 2 // beep level - + 1 // key lock status - } -} - -impl Response for KeyBeepSetting { - type Error = KeyBeepSettingError; - - fn deserialize<'i, I: Iterator>(raw_values: I) -> Result { - let (bl, kls) = raw_values.collect_tuple().ok_or(Self::Error::Malformed)?; - let bl = ResponseField::deserialize(bl).ok_or(Self::Error::BeepLevel)?; - let kls = ResponseField::deserialize(kls).ok_or(Self::Error::KeyLockStatus)?; - Ok(Self(bl, kls)) - } - - fn expected_field_count() -> usize { - 2 - } -} - -pub struct GetKeyBeepSetting; - -impl Command for GetKeyBeepSetting { - const TEXT: &'static [u8] = b"KBP"; - - type Params = NoParams; - - type Response = KeyBeepSetting; - - fn params(self) -> Self::Params { - NoParams - } -} - -pub struct SetKeyBeep(KeyBeepSetting); - -impl Command for SetKeyBeep { - const TEXT: &'static [u8] = b"KBP"; - - type Params = KeyBeepSetting; - - type Response = OkResponse; - - fn params(self) -> Self::Params { - self.0 - } +get_set_command! { + text: b"KBP", + get: GetKeyBeepSetting, + set: SetKeyBeep, + type: KeyBeepSetting(KeyBeepSettingError) ( + beep_level: enum BeepLevel { + Auto => b"0", + Off => b"99", + }, + key_lock_status: enum KeyLockStatus { + Off => b"0", + On => b"1", + }, + ), } diff --git a/src/command/bc125at/volume.rs b/src/command/bc125at/volume.rs index 6b5f174..1208dae 100644 --- a/src/command/bc125at/volume.rs +++ b/src/command/bc125at/volume.rs @@ -22,6 +22,10 @@ impl IntoParam for Volume { fn into_param(self) -> Bytes { Bytes::from(format!("{}", self.0)) } + + fn size_hint(&self) -> usize { + todo!() + } } impl Response for Volume { diff --git a/src/command/mod.rs b/src/command/mod.rs index dadbc9d..7291d99 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -23,6 +23,7 @@ pub trait Params: IntoIterator { pub trait IntoParam { fn into_param(self) -> Bytes; + fn size_hint(&self) -> usize; } pub trait ResponseField: Sized { diff --git a/src/lib.rs b/src/lib.rs index 4dcb9af..14abb4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +#[macro_use] +mod macros; + mod bytes_split; mod codec; mod command; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..c7abf5d --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,130 @@ +macro_rules! replace_expr { + ($_t:tt $sub:expr) => { + $sub + }; +} + +// https://lukaswirth.dev/tlborm/decl-macros/building-blocks/counting.html#repetition-with-replacement +macro_rules! count_tts { + ($($tts:tt)*) => {0usize $(+ replace_expr!($tts 1usize))*}; +} + +macro_rules! get_set_command { + // Top level + ( + text: $text:literal, + get: $get:ident, + set: $set:ident, + type: $set_name:ident $set_error:tt $set_fields:tt, + ) => { + get_set_command!(get $get $text $set_name); + get_set_command!(set $set $text $set_name); + get_set_command!(params $set_name $set_error $set_fields); + }; + // Param set + ( + params $set_name:ident ($set_error_name:ident) ( + $($field_name:ident: $kind:tt $type_name:ident $body:tt),* $(,)? + ) + ) => { + $(get_set_command!(param $kind $type_name $body);)* + + pub struct $set_name { + $($field_name: $type_name),* + } + + impl IntoIterator for $set_name { + type Item = tokio_util::bytes::Bytes; + type IntoIter = std::array::IntoIter; + fn into_iter(self) -> Self::IntoIter { + [ + $(crate::command::IntoParam::into_param(self.$field_name)),* + ].into_iter() + } + } + + impl crate::command::Params for $set_name { + fn size_hint(&self) -> usize { + let field_count = count_tts!($($field_name)*); + let commas = field_count - 1; + let field_sizes = [ + $(crate::command::IntoParam::size_hint(&self.$field_name)),* + ].into_iter().sum::(); + + commas + field_sizes + } + } + + #[derive(Debug, thiserror::Error)] + pub enum $set_error_name { + #[error("failed to deserialize field {0}")] + BadField(&'static str), + #[error("incorrect number of fields")] + Malformed, + } + + impl crate::command::Response for $set_name { + type Error = $set_error_name; + + fn deserialize<'i, I: Iterator>(raw_values: I) -> Result { + use itertools::Itertools; + let ( + $($field_name,)* + ) = raw_values.collect_tuple().ok_or(Self::Error::Malformed)?; + Ok(Self { + $( + $field_name: crate::command::ResponseField::deserialize($field_name) + .ok_or(Self::Error::BadField(stringify!($field_name)))?, + )* + }) + } + + fn expected_field_count() -> usize { + 2 + } + } + }; + // Generators for param types + (param enum $name:ident { $($variant:ident => $val:literal),* $(,)? }) => { + pub enum $name { $($variant),* } + + impl crate::command::IntoParam for $name { + fn into_param(self) -> tokio_util::bytes::Bytes { + tokio_util::bytes::Bytes::from_static(match self { + $(Self::$variant => $val),* + }) + } + fn size_hint(&self) -> usize { + [$($val.len()),*].into_iter().max().unwrap_or(0) + } + } + + impl crate::command::ResponseField for $name { + fn deserialize(raw: &[u8]) -> Option { + match raw { + $($val => Some(Self::$variant),)* + _ => None + } + } + } + }; + // Commands + (get $name:ident $text:literal $response:ident) => { + pub struct $name; + impl crate::command::Command for $name { + const TEXT: &'static [u8] = $text; + type Params = crate::command::NoParams; + type Response = $response; + fn params(self) -> Self::Params { crate::command::NoParams } + } + }; + (set $name:ident $text:literal $params:ident) => { + pub struct $name($params); + impl crate::command::Command for $name { + const TEXT: &'static [u8] = $text; + type Params = $params; + type Response = crate::command::OkResponse; + fn params(self) -> Self::Params { self.0 } + } + } +} -- 2.51.2