Skip to content

Commit

Permalink
Generify commands in blocking/tokio (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
aramperes committed Aug 1, 2021
1 parent 65f25fb commit 6e82736
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 166 deletions.
2 changes: 1 addition & 1 deletion nut-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
85 changes: 7 additions & 78 deletions nut-client/src/blocking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<Vec<(String, String)>> {
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<Vec<String>> {
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<Vec<Variable>> {
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<Variable> {
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.
Expand Down Expand Up @@ -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)
}
Expand All @@ -154,44 +120,7 @@ impl TcpConnection {
Ok(())
}

fn list_ups(&mut self) -> crate::Result<Vec<(String, String)>> {
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<Vec<String>> {
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<Vec<(String, String)>> {
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<String> {
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);
Expand Down Expand Up @@ -219,19 +148,19 @@ impl TcpConnection {
Ok(args)
}

fn read_response(&mut self) -> crate::Result<Response> {
pub(crate) fn read_response(&mut self) -> crate::Result<Response> {
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<String> {
pub(crate) fn read_plain_response(&mut self) -> crate::Result<String> {
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<Vec<Response>> {
pub(crate) fn read_list(&mut self, query: &[&str]) -> crate::Result<Vec<Response>> {
let mut reader = BufReader::new(&mut self.stream);
let args = Self::parse_line(&mut reader, self.config.debug)?;

Expand Down
198 changes: 195 additions & 3 deletions nut-client/src/cmd.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use core::fmt;

use crate::{ClientError, NutError};
use crate::{ClientError, NutError, Variable};

#[derive(Debug, Clone)]
pub enum Command<'a> {
Expand Down Expand Up @@ -214,9 +214,9 @@ impl Response {
}
}

pub fn expect_var(&self) -> crate::Result<(String, String)> {
pub fn expect_var(&self) -> crate::Result<Variable> {
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())
}
Expand All @@ -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<String> {
(
{ &["CLIENT", ups_name] },
{ |row: Response| row.expect_client() },
)
}

/// Queries the list of variables for a UPS device.
fn list_vars(ups_name: &str) -> Vec<Variable> {
(
{ &["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) },
)
}
}
2 changes: 1 addition & 1 deletion nut-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 6e82736

Please sign in to comment.