From 6e8273680818d7df8c9134449574aa23ed964a76 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 31 Jul 2021 22:28:01 -0400 Subject: [PATCH] Generify commands in blocking/tokio (#24) --- nut-client/Cargo.toml | 2 +- nut-client/src/blocking/mod.rs | 85 ++------------ nut-client/src/cmd.rs | 198 ++++++++++++++++++++++++++++++++- nut-client/src/lib.rs | 2 +- nut-client/src/tokio/mod.rs | 90 ++------------- 5 files changed, 211 insertions(+), 166 deletions(-) diff --git a/nut-client/Cargo.toml b/nut-client/Cargo.toml index 88080a1..81469a8 100644 --- a/nut-client/Cargo.toml +++ b/nut-client/Cargo.toml @@ -28,7 +28,7 @@ async = ["tokio"] async-ssl = ["async", "tokio-rustls", "ssl"] # a feature gate for examples -async-rt = ["tokio/rt-multi-thread", "tokio/macros"] +async-rt = ["async", "tokio/rt-multi-thread", "tokio/macros"] [[example]] name = "async" diff --git a/nut-client/src/blocking/mod.rs b/nut-client/src/blocking/mod.rs index 9041edc..3fd810c 100644 --- a/nut-client/src/blocking/mod.rs +++ b/nut-client/src/blocking/mod.rs @@ -3,7 +3,7 @@ use std::net::{SocketAddr, TcpStream}; use crate::blocking::stream::ConnectionStream; use crate::cmd::{Command, Response}; -use crate::{ClientError, Config, Host, NutError, Variable}; +use crate::{ClientError, Config, Host, NutError}; mod stream; @@ -20,41 +20,6 @@ impl Connection { Host::Tcp(host) => Ok(Self::Tcp(TcpConnection::new(config.clone(), &host.addr)?)), } } - - /// Queries a list of UPS devices. - pub fn list_ups(&mut self) -> crate::Result> { - match self { - Self::Tcp(conn) => conn.list_ups(), - } - } - - /// Queries a list of client IP addresses connected to the given device. - pub fn list_clients(&mut self, ups_name: &str) -> crate::Result> { - match self { - Self::Tcp(conn) => conn.list_clients(ups_name), - } - } - - /// Queries the list of variables for a UPS device. - pub fn list_vars(&mut self, ups_name: &str) -> crate::Result> { - match self { - Self::Tcp(conn) => Ok(conn - .list_vars(ups_name)? - .into_iter() - .map(|(key, val)| Variable::parse(key.as_str(), val)) - .collect()), - } - } - - /// Queries one variable for a UPS device. - pub fn get_var(&mut self, ups_name: &str, variable: &str) -> crate::Result { - match self { - Self::Tcp(conn) => { - let var = conn.get_var(ups_name, variable)?; - Ok(Variable::parse(var.0.as_str(), var.1)) - } - } - } } /// A blocking TCP NUT client connection. @@ -129,7 +94,8 @@ impl TcpConnection { self.stream = self.stream.upgrade_ssl(sess)?; // Send a test command - self.get_network_version()?; + self.write_cmd(Command::NetworkVersion)?; + self.read_plain_response()?; } Ok(self) } @@ -154,44 +120,7 @@ impl TcpConnection { Ok(()) } - fn list_ups(&mut self) -> crate::Result> { - let query = &["UPS"]; - self.write_cmd(Command::List(query))?; - - let list = self.read_list(query)?; - list.into_iter().map(|row| row.expect_ups()).collect() - } - - fn list_clients(&mut self, ups_name: &str) -> crate::Result> { - let query = &["CLIENT", ups_name]; - self.write_cmd(Command::List(query))?; - - let list = self.read_list(query)?; - list.into_iter().map(|row| row.expect_client()).collect() - } - - fn list_vars(&mut self, ups_name: &str) -> crate::Result> { - let query = &["VAR", ups_name]; - self.write_cmd(Command::List(query))?; - - let list = self.read_list(query)?; - list.into_iter().map(|row| row.expect_var()).collect() - } - - fn get_var(&mut self, ups_name: &str, variable: &str) -> crate::Result<(String, String)> { - let query = &["VAR", ups_name, variable]; - self.write_cmd(Command::Get(query))?; - - self.read_response()?.expect_var() - } - - #[allow(dead_code)] - fn get_network_version(&mut self) -> crate::Result { - self.write_cmd(Command::NetworkVersion)?; - self.read_plain_response() - } - - fn write_cmd(&mut self, line: Command) -> crate::Result<()> { + pub(crate) fn write_cmd(&mut self, line: Command) -> crate::Result<()> { let line = format!("{}\n", line); if self.config.debug { eprint!("DEBUG -> {}", line); @@ -219,19 +148,19 @@ impl TcpConnection { Ok(args) } - fn read_response(&mut self) -> crate::Result { + pub(crate) fn read_response(&mut self) -> crate::Result { let mut reader = BufReader::new(&mut self.stream); let args = Self::parse_line(&mut reader, self.config.debug)?; Response::from_args(args) } - fn read_plain_response(&mut self) -> crate::Result { + pub(crate) fn read_plain_response(&mut self) -> crate::Result { let mut reader = BufReader::new(&mut self.stream); let args = Self::parse_line(&mut reader, self.config.debug)?; Ok(args.join(" ")) } - fn read_list(&mut self, query: &[&str]) -> crate::Result> { + pub(crate) fn read_list(&mut self, query: &[&str]) -> crate::Result> { let mut reader = BufReader::new(&mut self.stream); let args = Self::parse_line(&mut reader, self.config.debug)?; diff --git a/nut-client/src/cmd.rs b/nut-client/src/cmd.rs index 28dc2e7..1295254 100644 --- a/nut-client/src/cmd.rs +++ b/nut-client/src/cmd.rs @@ -1,6 +1,6 @@ use core::fmt; -use crate::{ClientError, NutError}; +use crate::{ClientError, NutError, Variable}; #[derive(Debug, Clone)] pub enum Command<'a> { @@ -214,9 +214,9 @@ impl Response { } } - pub fn expect_var(&self) -> crate::Result<(String, String)> { + pub fn expect_var(&self) -> crate::Result { if let Self::Var(name, value) = &self { - Ok((name.to_owned(), value.to_owned())) + Ok(Variable::parse(name, value.to_owned())) } else { Err(NutError::UnexpectedResponse.into()) } @@ -238,3 +238,195 @@ impl Response { } } } + +/// A macro for implementing `LIST` commands. +/// +/// Each function should return a 2-tuple with +/// (1) the query to pass to `LIST` +/// (2) a closure for mapping each `Response` row to the return type +macro_rules! implement_list_commands { + ( + $( + $(#[$attr:meta])+ + fn $name:ident($($argname:ident: $argty:ty),*) -> $retty:ty { + ( + $query:block, + $mapper:block, + ) + } + )* + ) => { + impl crate::blocking::Connection { + $( + $(#[$attr])* + pub fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> { + match self { + Self::Tcp(conn) => { + conn.write_cmd(Command::List($query))?; + let list = conn.read_list($query)?; + list.into_iter().map($mapper).collect() + }, + } + } + )* + } + + #[cfg(feature = "async")] + impl crate::tokio::Connection { + $( + $(#[$attr])* + pub async fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> { + match self { + Self::Tcp(conn) => { + conn.write_cmd(Command::List($query)).await?; + let list = conn.read_list($query).await?; + list.into_iter().map($mapper).collect() + }, + } + } + )* + } + }; +} + +/// A macro for implementing `GET` commands. +/// +/// Each function should return a 2-tuple with +/// (1) the query to pass to `GET` +/// (2) a closure for mapping the `Response` row to the return type +macro_rules! implement_get_commands { + ( + $( + $(#[$attr:meta])+ + fn $name:ident($($argname:ident: $argty:ty),*) -> $retty:ty { + ( + $query:block, + $mapper:block, + ) + } + )* + ) => { + impl crate::blocking::Connection { + $( + $(#[$attr])* + pub fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> { + match self { + Self::Tcp(conn) => { + conn.write_cmd(Command::Get($query))?; + ($mapper)(conn.read_response()?) + }, + } + } + )* + } + + #[cfg(feature = "async")] + impl crate::tokio::Connection { + $( + $(#[$attr])* + pub async fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> { + match self { + Self::Tcp(conn) => { + conn.write_cmd(Command::Get($query)).await?; + ($mapper)(conn.read_response().await?) + }, + } + } + )* + } + }; +} + +/// A macro for implementing simple/direct commands. +/// +/// Each function should return a 2-tuple with +/// (1) the command to pass +/// (2) a closure for mapping the `String` row to the return type +macro_rules! implement_simple_commands { + ( + $( + $(#[$attr:meta])+ + fn $name:ident($($argname:ident: $argty:ty),*) -> $retty:ty { + ( + $cmd:block, + $mapper:block, + ) + } + )* + ) => { + impl crate::blocking::Connection { + $( + $(#[$attr])* + pub fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> { + match self { + Self::Tcp(conn) => { + conn.write_cmd($cmd)?; + ($mapper)(conn.read_plain_response()?) + }, + } + } + )* + } + + #[cfg(feature = "async")] + impl crate::tokio::Connection { + $( + $(#[$attr])* + pub async fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> { + match self { + Self::Tcp(conn) => { + conn.write_cmd($cmd).await?; + ($mapper)(conn.read_plain_response().await?) + }, + } + } + )* + } + }; +} + +implement_list_commands! { + /// Queries a list of UPS devices. + fn list_ups() -> Vec<(String, String)> { + ( + { &["UPS"] }, + { |row: Response| row.expect_ups() }, + ) + } + + /// Queries a list of client IP addresses connected to the given device. + fn list_clients(ups_name: &str) -> Vec { + ( + { &["CLIENT", ups_name] }, + { |row: Response| row.expect_client() }, + ) + } + + /// Queries the list of variables for a UPS device. + fn list_vars(ups_name: &str) -> Vec { + ( + { &["VAR", ups_name] }, + { |row: Response| row.expect_var() }, + ) + } +} + +implement_get_commands! { + /// Queries one variable for a UPS device. + fn get_var(ups_name: &str, variable: &str) -> Variable { + ( + { &["VAR", ups_name, variable] }, + { |row: Response| row.expect_var() }, + ) + } +} + +implement_simple_commands! { + /// Queries the network protocol version. + fn get_network_version() -> String { + ( + { Command::NetworkVersion }, + { |row: String| Ok(row) }, + ) + } +} diff --git a/nut-client/src/lib.rs b/nut-client/src/lib.rs index 594cd41..7877811 100644 --- a/nut-client/src/lib.rs +++ b/nut-client/src/lib.rs @@ -12,7 +12,7 @@ pub use var::*; /// Blocking client implementation for NUT. pub mod blocking; /// Async client implementation for NUT, using Tokio. -#[cfg(feature = "tokio")] +#[cfg(feature = "async")] pub mod tokio; mod cmd; diff --git a/nut-client/src/tokio/mod.rs b/nut-client/src/tokio/mod.rs index dc550b9..b8269e7 100644 --- a/nut-client/src/tokio/mod.rs +++ b/nut-client/src/tokio/mod.rs @@ -2,7 +2,7 @@ use std::net::SocketAddr; use crate::cmd::{Command, Response}; use crate::tokio::stream::ConnectionStream; -use crate::{Config, Host, NutError, Variable}; +use crate::{Config, Host, NutError}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::net::TcpStream; @@ -23,42 +23,6 @@ impl Connection { )), } } - - /// Queries a list of UPS devices. - pub async fn list_ups(&mut self) -> crate::Result> { - match self { - Self::Tcp(conn) => conn.list_ups().await, - } - } - - /// Queries a list of client IP addresses connected to the given device. - pub async fn list_clients(&mut self, ups_name: &str) -> crate::Result> { - match self { - Self::Tcp(conn) => conn.list_clients(ups_name).await, - } - } - - /// Queries the list of variables for a UPS device. - pub async fn list_vars(&mut self, ups_name: &str) -> crate::Result> { - match self { - Self::Tcp(conn) => Ok(conn - .list_vars(ups_name) - .await? - .into_iter() - .map(|(key, val)| Variable::parse(key.as_str(), val)) - .collect()), - } - } - - /// Queries one variable for a UPS device. - pub async fn get_var(&mut self, ups_name: &str, variable: &str) -> crate::Result { - match self { - Self::Tcp(conn) => { - let var = conn.get_var(ups_name, variable).await?; - Ok(Variable::parse(var.0.as_str(), var.1)) - } - } - } } /// A blocking TCP NUT client connection. @@ -137,7 +101,8 @@ impl TcpConnection { self.stream = self.stream.upgrade_ssl(config, dns_name.as_ref()).await?; // Send a test command - self.get_network_version().await?; + self.write_cmd(Command::NetworkVersion).await?; + self.read_plain_response().await?; } Ok(self) } @@ -162,48 +127,7 @@ impl TcpConnection { Ok(()) } - async fn list_ups(&mut self) -> crate::Result> { - let query = &["UPS"]; - self.write_cmd(Command::List(query)).await?; - - let list = self.read_list(query).await?; - list.into_iter().map(|row| row.expect_ups()).collect() - } - - async fn list_clients(&mut self, ups_name: &str) -> crate::Result> { - let query = &["CLIENT", ups_name]; - self.write_cmd(Command::List(query)).await?; - - let list = self.read_list(query).await?; - list.into_iter().map(|row| row.expect_client()).collect() - } - - async fn list_vars(&mut self, ups_name: &str) -> crate::Result> { - let query = &["VAR", ups_name]; - self.write_cmd(Command::List(query)).await?; - - let list = self.read_list(query).await?; - list.into_iter().map(|row| row.expect_var()).collect() - } - - async fn get_var<'a>( - &mut self, - ups_name: &'a str, - variable: &'a str, - ) -> crate::Result<(String, String)> { - let query = &["VAR", ups_name, variable]; - self.write_cmd(Command::Get(query)).await?; - - self.read_response().await?.expect_var() - } - - #[allow(dead_code)] - async fn get_network_version(&mut self) -> crate::Result { - self.write_cmd(Command::NetworkVersion).await?; - self.read_plain_response().await - } - - async fn write_cmd(&mut self, line: Command<'_>) -> crate::Result<()> { + pub(crate) async fn write_cmd(&mut self, line: Command<'_>) -> crate::Result<()> { let line = format!("{}\n", line); if self.config.debug { eprint!("DEBUG -> {}", line); @@ -231,19 +155,19 @@ impl TcpConnection { Ok(args) } - async fn read_response(&mut self) -> crate::Result { + pub(crate) async fn read_response(&mut self) -> crate::Result { let mut reader = BufReader::new(&mut self.stream); let args = Self::parse_line(&mut reader, self.config.debug).await?; Response::from_args(args) } - async fn read_plain_response(&mut self) -> crate::Result { + pub(crate) async fn read_plain_response(&mut self) -> crate::Result { let mut reader = BufReader::new(&mut self.stream); let args = Self::parse_line(&mut reader, self.config.debug).await?; Ok(args.join(" ")) } - async fn read_list(&mut self, query: &[&str]) -> crate::Result> { + pub(crate) async fn read_list(&mut self, query: &[&str]) -> crate::Result> { let mut reader = BufReader::new(&mut self.stream); let args = Self::parse_line(&mut reader, self.config.debug).await?;