1use core::fmt;
2use std::collections::HashSet;
3use std::convert::TryFrom;
4use std::time::Duration;
5
6pub mod key {
10 pub const DEVICE_MODEL: &str = "device.model";
12 pub const DEVICE_MANUFACTURER: &str = "device.mfr";
14 pub const DEVICE_SERIAL: &str = "device.serial";
16 pub const DEVICE_TYPE: &str = "device.type";
18 pub const DEVICE_DESCRIPTION: &str = "device.description";
20 pub const DEVICE_CONTACT: &str = "device.contact";
22 pub const DEVICE_LOCATION: &str = "device.location";
24 pub const DEVICE_PART: &str = "device.part";
26 pub const DEVICE_MAC_ADDRESS: &str = "device.macaddr";
28 pub const DEVICE_UPTIME: &str = "device.uptime";
30}
31
32#[derive(Debug, Clone, Eq, PartialEq)]
36pub enum Variable {
37 DeviceModel(String),
39 DeviceManufacturer(String),
41 DeviceSerial(String),
43 DeviceType(DeviceType),
45 DeviceDescription(String),
47 DeviceContact(String),
49 DeviceLocation(String),
51 DevicePart(String),
53 DeviceMacAddress(String),
55 DeviceUptime(Duration),
57
58 Other((String, String)),
60}
61
62impl Variable {
63 pub fn parse(name: &str, value: String) -> Variable {
65 use self::key::*;
66
67 match name {
68 DEVICE_MODEL => Self::DeviceModel(value),
69 DEVICE_MANUFACTURER => Self::DeviceManufacturer(value),
70 DEVICE_SERIAL => Self::DeviceSerial(value),
71 DEVICE_TYPE => Self::DeviceType(DeviceType::from(value)),
72 DEVICE_DESCRIPTION => Self::DeviceDescription(value),
73 DEVICE_CONTACT => Self::DeviceContact(value),
74 DEVICE_LOCATION => Self::DeviceLocation(value),
75 DEVICE_PART => Self::DevicePart(value),
76 DEVICE_MAC_ADDRESS => Self::DeviceMacAddress(value),
77 DEVICE_UPTIME => Self::DeviceUptime(Duration::from_secs(
78 value.parse().expect("invalid uptime value"),
79 )),
80
81 _ => Self::Other((name.into(), value)),
82 }
83 }
84
85 pub fn name(&self) -> &str {
87 use self::key::*;
88 match self {
89 Self::DeviceModel(_) => DEVICE_MODEL,
90 Self::DeviceManufacturer(_) => DEVICE_MANUFACTURER,
91 Self::DeviceSerial(_) => DEVICE_SERIAL,
92 Self::DeviceType(_) => DEVICE_TYPE,
93 Self::DeviceDescription(_) => DEVICE_DESCRIPTION,
94 Self::DeviceContact(_) => DEVICE_CONTACT,
95 Self::DeviceLocation(_) => DEVICE_LOCATION,
96 Self::DevicePart(_) => DEVICE_PART,
97 Self::DeviceMacAddress(_) => DEVICE_MAC_ADDRESS,
98 Self::DeviceUptime(_) => DEVICE_UPTIME,
99 Self::Other((name, _)) => name.as_str(),
100 }
101 }
102
103 pub fn value(&self) -> String {
105 match self {
106 Self::DeviceModel(value) => value.clone(),
107 Self::DeviceManufacturer(value) => value.clone(),
108 Self::DeviceSerial(value) => value.clone(),
109 Self::DeviceType(value) => value.to_string(),
110 Self::DeviceDescription(value) => value.clone(),
111 Self::DeviceContact(value) => value.clone(),
112 Self::DeviceLocation(value) => value.clone(),
113 Self::DevicePart(value) => value.clone(),
114 Self::DeviceMacAddress(value) => value.clone(),
115 Self::DeviceUptime(value) => value.as_secs().to_string(),
116 Self::Other((_, value)) => value.clone(),
117 }
118 }
119}
120
121impl fmt::Display for Variable {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 write!(f, "{}: {}", self.name(), self.value())
124 }
125}
126
127#[derive(Debug, Clone, Eq, PartialEq)]
129pub enum DeviceType {
130 Ups,
132 Pdu,
134 Scd,
136 Psu,
138 Ats,
140 Other(String),
142}
143
144impl DeviceType {
145 pub fn from(v: String) -> DeviceType {
147 match v.as_str() {
148 "ups" => Self::Ups,
149 "pdu" => Self::Pdu,
150 "scd" => Self::Scd,
151 "psu" => Self::Psu,
152 "ats" => Self::Ats,
153 _ => Self::Other(v),
154 }
155 }
156}
157
158impl fmt::Display for DeviceType {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 match self {
161 Self::Ups => write!(f, "ups"),
162 Self::Pdu => write!(f, "pdu"),
163 Self::Scd => write!(f, "scd"),
164 Self::Psu => write!(f, "psu"),
165 Self::Ats => write!(f, "ats"),
166 Self::Other(val) => write!(f, "other({})", val),
167 }
168 }
169}
170
171#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
173#[allow(dead_code)]
174pub(crate) enum VariableType {
175 Rw,
177 Enum,
179 String(usize),
181 Range,
183 Number,
185}
186
187impl TryFrom<&str> for VariableType {
188 type Error = crate::ClientError;
189
190 fn try_from(value: &str) -> Result<Self, Self::Error> {
191 match value {
192 "RW" => Ok(Self::Rw),
193 "ENUM" => Ok(Self::Enum),
194 "RANGE" => Ok(Self::Range),
195 "NUMBER" => Ok(Self::Number),
196 other => {
197 if other.starts_with("STRING:") {
198 let size = other
199 .splitn(2, ':')
200 .nth(1)
201 .map(|s| s.parse().ok())
202 .flatten()
203 .ok_or_else(|| {
204 crate::ClientError::Nut(crate::NutError::Generic(
205 "Invalid STRING definition".into(),
206 ))
207 })?;
208 Ok(Self::String(size))
209 } else {
210 Err(crate::ClientError::Nut(crate::NutError::Generic(format!(
211 "Unrecognized variable type: {}",
212 value
213 ))))
214 }
215 }
216 }
217 }
218}
219
220#[derive(Debug, Clone, Eq, PartialEq)]
222pub struct VariableDefinition(String, HashSet<VariableType>);
223
224impl VariableDefinition {
225 pub fn name(&self) -> &str {
227 self.0.as_str()
228 }
229
230 pub fn is_mutable(&self) -> bool {
232 self.1.contains(&VariableType::Rw)
233 }
234
235 pub fn is_enum(&self) -> bool {
237 self.1.contains(&VariableType::Enum)
238 }
239
240 pub fn is_string(&self) -> bool {
242 self.1.iter().any(|t| matches!(t, VariableType::String(_)))
243 }
244
245 pub fn is_range(&self) -> bool {
248 self.1.contains(&VariableType::Range)
249 }
250
251 pub fn is_number(&self) -> bool {
253 self.1.contains(&VariableType::Number)
254 }
255
256 pub fn get_string_length(&self) -> Option<usize> {
258 self.1.iter().find_map(|t| match t {
259 VariableType::String(n) => Some(*n),
260 _ => None,
261 })
262 }
263}
264
265impl<A: ToString> TryFrom<(A, Vec<&str>)> for VariableDefinition {
266 type Error = crate::ClientError;
267
268 fn try_from(value: (A, Vec<&str>)) -> Result<Self, Self::Error> {
269 Ok(VariableDefinition(
270 value.0.to_string(),
271 value
272 .1
273 .iter()
274 .map(|s| VariableType::try_from(*s))
275 .collect::<crate::Result<HashSet<VariableType>>>()?,
276 ))
277 }
278}
279
280#[derive(Debug, Clone, Eq, PartialEq)]
282pub struct VariableRange(pub String, pub String);
283
284#[cfg(test)]
285mod tests {
286 use std::iter::FromIterator;
287
288 use super::*;
289
290 #[test]
291 fn test_parse_variable_definition() {
292 assert_eq!(
293 VariableDefinition::try_from(("var0", vec![])).unwrap(),
294 VariableDefinition("var0".into(), HashSet::new())
295 );
296
297 assert_eq!(
298 VariableDefinition::try_from(("var1", vec!["RW"])).unwrap(),
299 VariableDefinition(
300 "var1".into(),
301 HashSet::from_iter(vec![VariableType::Rw].into_iter())
302 )
303 );
304
305 assert_eq!(
306 VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"])).unwrap(),
307 VariableDefinition(
308 "var1".into(),
309 HashSet::from_iter(vec![VariableType::Rw, VariableType::String(123)].into_iter())
310 )
311 );
312
313 assert!(
314 VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
315 .unwrap()
316 .is_mutable()
317 );
318 assert!(
319 VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
320 .unwrap()
321 .is_string()
322 );
323 assert!(
324 !VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
325 .unwrap()
326 .is_enum()
327 );
328 assert!(
329 !VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
330 .unwrap()
331 .is_number()
332 );
333 assert!(
334 !VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
335 .unwrap()
336 .is_range()
337 );
338 assert_eq!(
339 VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
340 .unwrap()
341 .get_string_length(),
342 Some(123)
343 );
344 }
345}