From f1700739f647dfc5b0550fccb0b003363520f5ef Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sun, 1 Aug 2021 02:36:56 -0400 Subject: [PATCH] Implement GET TYPE Fixes #14 --- nut-client/examples/async.rs | 2 + nut-client/examples/blocking.rs | 2 + nut-client/src/cmd.rs | 46 ++++++++- nut-client/src/var.rs | 174 ++++++++++++++++++++++++++++++++ 4 files changed, 222 insertions(+), 2 deletions(-) diff --git a/nut-client/examples/async.rs b/nut-client/examples/async.rs index 16a95e4..ce02f89 100644 --- a/nut-client/examples/async.rs +++ b/nut-client/examples/async.rs @@ -47,6 +47,7 @@ async fn main() -> nut_client::Result<()> { println!("\t Mutable Variables:"); for var in mutable_vars.iter() { println!("\t\t- {}", var); + println!("\t\t {:?}", conn.get_var_type(&name, var.name()).await?); } // List UPS immutable properties (key = val) @@ -56,6 +57,7 @@ async fn main() -> nut_client::Result<()> { continue; } println!("\t\t- {}", var); + println!("\t\t {:?}", conn.get_var_type(&name, var.name()).await?); } // List UPS commands diff --git a/nut-client/examples/blocking.rs b/nut-client/examples/blocking.rs index c8924a6..2dba977 100644 --- a/nut-client/examples/blocking.rs +++ b/nut-client/examples/blocking.rs @@ -43,6 +43,7 @@ fn main() -> nut_client::Result<()> { println!("\t Mutable Variables:"); for var in mutable_vars.iter() { println!("\t\t- {}", var); + println!("\t\t {:?}", conn.get_var_type(&name, var.name())?); } // List UPS immutable properties (key = val) @@ -52,6 +53,7 @@ fn main() -> nut_client::Result<()> { continue; } println!("\t\t- {}", var); + println!("\t\t {:?}", conn.get_var_type(&name, var.name())?); } // List UPS commands diff --git a/nut-client/src/cmd.rs b/nut-client/src/cmd.rs index 879d397..44dc13b 100644 --- a/nut-client/src/cmd.rs +++ b/nut-client/src/cmd.rs @@ -1,6 +1,7 @@ use core::fmt; -use crate::{ClientError, NutError, Variable}; +use crate::{ClientError, NutError, Variable, VariableDefinition}; +use std::convert::TryFrom; #[derive(Debug, Clone)] pub enum Command<'a> { @@ -98,8 +99,12 @@ pub enum Response { Desc(String), /// A NUMLOGINS response. /// - /// Params (number of logins) + /// Params: (number of logins) NumLogins(i32), + /// A variable type (TYPE) response. + /// + /// Params: (variable name, variable types) + Type(String, Vec), } impl Response { @@ -348,6 +353,24 @@ impl Response { })?; Ok(Response::NumLogins(num)) } + "TYPE" => { + let _device = if args.is_empty() { + Err(ClientError::from(NutError::Generic( + "Unspecified TYPE device in response".into(), + ))) + } else { + Ok(args.remove(0)) + }?; + let name = if args.is_empty() { + Err(ClientError::from(NutError::Generic( + "Unspecified TYPE name in response".into(), + ))) + } else { + Ok(args.remove(0)) + }?; + let types = args; + Ok(Response::Type(name, types)) + } _ => Err(NutError::UnknownResponseType(cmd_name).into()), } } @@ -443,6 +466,17 @@ impl Response { Err(NutError::UnexpectedResponse.into()) } } + + pub fn expect_type(&self) -> crate::Result { + if let Self::Type(name, types) = &self { + VariableDefinition::try_from(( + name.to_owned(), + types.iter().map(String::as_str).collect(), + )) + } else { + Err(NutError::UnexpectedResponse.into()) + } + } } /// A macro for implementing `LIST` commands. @@ -701,6 +735,14 @@ implement_get_commands! { ) } + /// Queries the type of a UPS variable. + pub fn get_var_type(ups_name: &str, variable: &str) -> VariableDefinition { + ( + { &["TYPE", ups_name, variable] }, + { |row: Response| row.expect_type() }, + ) + } + /// Queries the description of a UPS command. pub fn get_command_description(ups_name: &str, variable: &str) -> String { ( diff --git a/nut-client/src/var.rs b/nut-client/src/var.rs index 7401dda..734a2da 100644 --- a/nut-client/src/var.rs +++ b/nut-client/src/var.rs @@ -1,4 +1,6 @@ use core::fmt; +use std::collections::HashSet; +use std::convert::TryFrom; use std::time::Duration; /// Well-known variable keys for NUT UPS devices. @@ -165,3 +167,175 @@ impl fmt::Display for DeviceType { } } } + +/// NUT Variable type +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[allow(dead_code)] +pub(crate) enum VariableType { + /// A mutable variable (`RW`). + Rw, + /// An enumerated type, which supports a few specific values (`ENUM`). + Enum, + /// A string with a maximum size (`STRING:n`). + String(usize), + /// A numeric type, either integer or float, comprised in the range defined by `LIST RANGE`. + Range, + /// A simple numeric value, either integer or float. + Number, +} + +impl TryFrom<&str> for VariableType { + type Error = crate::ClientError; + + fn try_from(value: &str) -> Result { + match value { + "RW" => Ok(Self::Rw), + "ENUM" => Ok(Self::Enum), + "RANGE" => Ok(Self::Range), + "NUMBER" => Ok(Self::Number), + other => { + if other.starts_with("STRING:") { + let size = other + .splitn(2, ':') + .nth(1) + .map(|s| s.parse().ok()) + .flatten() + .ok_or_else(|| { + crate::ClientError::Nut(crate::NutError::Generic( + "Invalid STRING definition".into(), + )) + })?; + Ok(Self::String(size)) + } else { + Err(crate::ClientError::Nut(crate::NutError::Generic(format!( + "Unrecognized variable type: {}", + value + )))) + } + } + } + } +} + +/// NUT Variable definition. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct VariableDefinition(String, HashSet); + +impl VariableDefinition { + /// The name of this variable. + pub fn name(&self) -> &str { + self.0.as_str() + } + + /// Whether this variable is mutable. + pub fn is_mutable(&self) -> bool { + self.1.contains(&VariableType::Rw) + } + + /// Whether this variable is an enumerated type. + pub fn is_enum(&self) -> bool { + self.1.contains(&VariableType::Enum) + } + + /// Whether this variable is a String type + pub fn is_string(&self) -> bool { + self.1.iter().any(|t| matches!(t, VariableType::String(_))) + } + + /// Whether this variable is a numeric type, + /// either integer or float, comprised in a range + pub fn is_range(&self) -> bool { + self.1.contains(&VariableType::Range) + } + + /// Whether this variable is a numeric type, either integer or float. + pub fn is_number(&self) -> bool { + self.1.contains(&VariableType::Number) + } + + /// Returns the max string length, if applicable. + pub fn get_string_length(&self) -> Option { + self.1.iter().find_map(|t| match t { + VariableType::String(n) => Some(*n), + _ => None, + }) + } +} + +impl TryFrom<(A, Vec<&str>)> for VariableDefinition { + type Error = crate::ClientError; + + fn try_from(value: (A, Vec<&str>)) -> Result { + Ok(VariableDefinition( + value.0.to_string(), + value + .1 + .iter() + .map(|s| VariableType::try_from(*s)) + .collect::>>()?, + )) + } +} + +#[cfg(test)] +mod tests { + use std::iter::FromIterator; + + use super::*; + + #[test] + fn test_parse_variable_definition() { + assert_eq!( + VariableDefinition::try_from(("var0", vec![])).unwrap(), + VariableDefinition("var0".into(), HashSet::new()) + ); + + assert_eq!( + VariableDefinition::try_from(("var1", vec!["RW"])).unwrap(), + VariableDefinition( + "var1".into(), + HashSet::from_iter(vec![VariableType::Rw].into_iter()) + ) + ); + + assert_eq!( + VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"])).unwrap(), + VariableDefinition( + "var1".into(), + HashSet::from_iter(vec![VariableType::Rw, VariableType::String(123)].into_iter()) + ) + ); + + assert!( + VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"])) + .unwrap() + .is_mutable() + ); + assert!( + VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"])) + .unwrap() + .is_string() + ); + assert!( + !VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"])) + .unwrap() + .is_enum() + ); + assert!( + !VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"])) + .unwrap() + .is_number() + ); + assert!( + !VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"])) + .unwrap() + .is_range() + ); + assert_eq!( + VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"])) + .unwrap() + .get_string_length(), + Some(123) + ); + } +}