use core::fmt;
use std::collections::HashSet;
use std::convert::TryFrom;
use std::time::Duration;
pub mod key {
pub const DEVICE_MODEL: &str = "device.model";
pub const DEVICE_MANUFACTURER: &str = "device.mfr";
pub const DEVICE_SERIAL: &str = "device.serial";
pub const DEVICE_TYPE: &str = "device.type";
pub const DEVICE_DESCRIPTION: &str = "device.description";
pub const DEVICE_CONTACT: &str = "device.contact";
pub const DEVICE_LOCATION: &str = "device.location";
pub const DEVICE_PART: &str = "device.part";
pub const DEVICE_MAC_ADDRESS: &str = "device.macaddr";
pub const DEVICE_UPTIME: &str = "device.uptime";
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Variable {
DeviceModel(String),
DeviceManufacturer(String),
DeviceSerial(String),
DeviceType(DeviceType),
DeviceDescription(String),
DeviceContact(String),
DeviceLocation(String),
DevicePart(String),
DeviceMacAddress(String),
DeviceUptime(Duration),
Other((String, String)),
}
impl Variable {
pub fn parse(name: &str, value: String) -> Variable {
use self::key::*;
match name {
DEVICE_MODEL => Self::DeviceModel(value),
DEVICE_MANUFACTURER => Self::DeviceManufacturer(value),
DEVICE_SERIAL => Self::DeviceSerial(value),
DEVICE_TYPE => Self::DeviceType(DeviceType::from(value)),
DEVICE_DESCRIPTION => Self::DeviceDescription(value),
DEVICE_CONTACT => Self::DeviceContact(value),
DEVICE_LOCATION => Self::DeviceLocation(value),
DEVICE_PART => Self::DevicePart(value),
DEVICE_MAC_ADDRESS => Self::DeviceMacAddress(value),
DEVICE_UPTIME => Self::DeviceUptime(Duration::from_secs(
value.parse().expect("invalid uptime value"),
)),
_ => Self::Other((name.into(), value)),
}
}
pub fn name(&self) -> &str {
use self::key::*;
match self {
Self::DeviceModel(_) => DEVICE_MODEL,
Self::DeviceManufacturer(_) => DEVICE_MANUFACTURER,
Self::DeviceSerial(_) => DEVICE_SERIAL,
Self::DeviceType(_) => DEVICE_TYPE,
Self::DeviceDescription(_) => DEVICE_DESCRIPTION,
Self::DeviceContact(_) => DEVICE_CONTACT,
Self::DeviceLocation(_) => DEVICE_LOCATION,
Self::DevicePart(_) => DEVICE_PART,
Self::DeviceMacAddress(_) => DEVICE_MAC_ADDRESS,
Self::DeviceUptime(_) => DEVICE_UPTIME,
Self::Other((name, _)) => name.as_str(),
}
}
pub fn value(&self) -> String {
match self {
Self::DeviceModel(value) => value.clone(),
Self::DeviceManufacturer(value) => value.clone(),
Self::DeviceSerial(value) => value.clone(),
Self::DeviceType(value) => value.to_string(),
Self::DeviceDescription(value) => value.clone(),
Self::DeviceContact(value) => value.clone(),
Self::DeviceLocation(value) => value.clone(),
Self::DevicePart(value) => value.clone(),
Self::DeviceMacAddress(value) => value.clone(),
Self::DeviceUptime(value) => value.as_secs().to_string(),
Self::Other((_, value)) => value.clone(),
}
}
}
impl fmt::Display for Variable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.name(), self.value())
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum DeviceType {
Ups,
Pdu,
Scd,
Psu,
Ats,
Other(String),
}
impl DeviceType {
pub fn from(v: String) -> DeviceType {
match v.as_str() {
"ups" => Self::Ups,
"pdu" => Self::Pdu,
"scd" => Self::Scd,
"psu" => Self::Psu,
"ats" => Self::Ats,
_ => Self::Other(v),
}
}
}
impl fmt::Display for DeviceType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Ups => write!(f, "ups"),
Self::Pdu => write!(f, "pdu"),
Self::Scd => write!(f, "scd"),
Self::Psu => write!(f, "psu"),
Self::Ats => write!(f, "ats"),
Self::Other(val) => write!(f, "other({})", val),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[allow(dead_code)]
pub(crate) enum VariableType {
Rw,
Enum,
String(usize),
Range,
Number,
}
impl TryFrom<&str> for VariableType {
type Error = crate::ClientError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
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
.split_once(':')
.and_then(|(_, s)| s.parse().ok())
.ok_or_else(|| crate::ClientError::generic("Invalid STRING definition"))?;
Ok(Self::String(size))
} else {
Err(crate::ClientError::generic(format!(
"Unrecognized variable type: {}",
value
)))
}
}
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct VariableDefinition(String, HashSet<VariableType>);
impl VariableDefinition {
pub fn name(&self) -> &str {
self.0.as_str()
}
pub fn is_mutable(&self) -> bool {
self.1.contains(&VariableType::Rw)
}
pub fn is_enum(&self) -> bool {
self.1.contains(&VariableType::Enum)
}
pub fn is_string(&self) -> bool {
self.1.iter().any(|t| matches!(t, VariableType::String(_)))
}
pub fn is_range(&self) -> bool {
self.1.contains(&VariableType::Range)
}
pub fn is_number(&self) -> bool {
self.1.contains(&VariableType::Number)
}
pub fn get_string_length(&self) -> Option<usize> {
self.1.iter().find_map(|t| match t {
VariableType::String(n) => Some(*n),
_ => None,
})
}
}
impl<A: ToString> TryFrom<(A, Vec<&str>)> for VariableDefinition {
type Error = crate::ClientError;
fn try_from(value: (A, Vec<&str>)) -> Result<Self, Self::Error> {
Ok(VariableDefinition(
value.0.to_string(),
value
.1
.iter()
.map(|s| VariableType::try_from(*s))
.collect::<crate::Result<HashSet<VariableType>>>()?,
))
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct VariableRange(pub String, pub String);
#[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)
);
}
}