nut_client/
cmd.rs

1use core::fmt;
2use std::convert::TryFrom;
3
4use crate::{ClientError, NutError, Variable, VariableDefinition, VariableRange};
5
6#[derive(Debug, Clone)]
7pub enum Command<'a> {
8    Get(&'a [&'a str]),
9    /// Passes the login username.
10    SetUsername(&'a str),
11    /// Passes the login password.
12    SetPassword(&'a str),
13    /// Queries for a list. Allows for any number of arguments, which forms a single query.
14    List(&'a [&'a str]),
15    /// Tells upsd to switch to TLS, so all future communications will be encrypted.
16    StartTLS,
17    /// Queries the network version.
18    NetworkVersion,
19    /// Queries the server version.
20    Version,
21    /// Gracefully shuts down the connection.
22    Logout,
23}
24
25impl<'a> Command<'a> {
26    /// The network identifier of the command.
27    pub fn name(&self) -> &'static str {
28        match self {
29            Self::Get(_) => "GET",
30            Self::SetUsername(_) => "USERNAME",
31            Self::SetPassword(_) => "PASSWORD",
32            Self::List(_) => "LIST",
33            Self::StartTLS => "STARTTLS",
34            Self::NetworkVersion => "NETVER",
35            Self::Version => "VER",
36            Self::Logout => "LOGOUT",
37        }
38    }
39
40    /// The arguments of the command to serialize.
41    pub fn args(&self) -> Vec<&str> {
42        match self {
43            Self::Get(cmd) => cmd.to_vec(),
44            Self::SetUsername(username) => vec![username],
45            Self::SetPassword(password) => vec![password],
46            Self::List(query) => query.to_vec(),
47            _ => Vec::new(),
48        }
49    }
50}
51
52impl<'a> fmt::Display for Command<'a> {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        let mut args = self.args();
55        args.insert(0, self.name());
56        write!(f, "{}", shell_words::join(args))
57    }
58}
59
60#[derive(Debug, Clone)]
61pub enum Response {
62    /// A successful response.
63    Ok,
64    /// Marks the beginning of a list response.
65    BeginList(String),
66    /// Marks the end of a list response.
67    EndList(String),
68    /// A variable (VAR) response.
69    ///
70    /// Params: (var name, var value)
71    Var(String, String),
72    /// A UPS (UPS) response.
73    ///
74    /// Params: (device name, device description)
75    Ups(String, String),
76    /// A client (CLIENT) response.
77    ///
78    /// Params: (client IP)
79    Client(String),
80    /// A command (CMD) response.
81    ///
82    /// Params: (command name)
83    Cmd(String),
84    /// A command description (CMDDESC) response.
85    ///
86    /// Params: (command description)
87    CmdDesc(String),
88    /// A UPS description (UPSDESC) response.
89    ///
90    /// Params: (UPS description)
91    UpsDesc(String),
92    /// A mutable variable (RW) response.
93    ///
94    /// Params: (var name, var value)
95    Rw(String, String),
96    /// A variable description (DESC) response.
97    ///
98    /// Params: (variable description)
99    Desc(String),
100    /// A NUMLOGINS response.
101    ///
102    /// Params: (number of logins)
103    NumLogins(i32),
104    /// A variable type (TYPE) response.
105    ///
106    /// Params: (variable name, variable types)
107    Type(String, Vec<String>),
108    /// A variable range (RANGE) response.
109    ///
110    /// Params: (variable range)
111    Range(VariableRange),
112    /// A variable enum (ENUM) response.
113    ///
114    /// Params: (enum value)
115    Enum(String),
116}
117
118impl Response {
119    pub fn from_args(mut args: Vec<String>) -> crate::Result<Response> {
120        if args.is_empty() {
121            return Err(
122                NutError::Generic("Parsing server response failed: empty line".into()).into(),
123            );
124        }
125        let cmd_name = args.remove(0);
126        match cmd_name.as_str() {
127            "OK" => Ok(Self::Ok),
128            "ERR" => {
129                if args.is_empty() {
130                    Err(NutError::Generic("Unspecified server error".into()).into())
131                } else {
132                    let err_type = args.remove(0);
133                    match err_type.as_str() {
134                        "ACCESS-DENIED" => Err(NutError::AccessDenied.into()),
135                        "UNKNOWN-UPS" => Err(NutError::UnknownUps.into()),
136                        "FEATURE-NOT-CONFIGURED" => Err(NutError::FeatureNotConfigured.into()),
137                        _ => Err(NutError::Generic(format!(
138                            "Server error: {} {}",
139                            err_type,
140                            args.join(" ")
141                        ))
142                        .into()),
143                    }
144                }
145            }
146            "BEGIN" => {
147                if args.is_empty() {
148                    Err(NutError::Generic("Unspecified BEGIN type".into()).into())
149                } else {
150                    let begin_type = args.remove(0);
151                    if &begin_type != "LIST" {
152                        Err(
153                            NutError::Generic(format!("Unexpected BEGIN type: {}", begin_type))
154                                .into(),
155                        )
156                    } else {
157                        let args = shell_words::join(args);
158                        Ok(Response::BeginList(args))
159                    }
160                }
161            }
162            "END" => {
163                if args.is_empty() {
164                    Err(NutError::Generic("Unspecified END type".into()).into())
165                } else {
166                    let begin_type = args.remove(0);
167                    if &begin_type != "LIST" {
168                        Err(
169                            NutError::Generic(format!("Unexpected END type: {}", begin_type))
170                                .into(),
171                        )
172                    } else {
173                        let args = shell_words::join(args);
174                        Ok(Response::EndList(args))
175                    }
176                }
177            }
178            "VAR" => {
179                let _var_device = if args.is_empty() {
180                    Err(ClientError::from(NutError::Generic(
181                        "Unspecified VAR device name in response".into(),
182                    )))
183                } else {
184                    Ok(args.remove(0))
185                }?;
186                let var_name = if args.is_empty() {
187                    Err(ClientError::from(NutError::Generic(
188                        "Unspecified VAR name in response".into(),
189                    )))
190                } else {
191                    Ok(args.remove(0))
192                }?;
193                let var_value = if args.is_empty() {
194                    Err(ClientError::from(NutError::Generic(
195                        "Unspecified VAR value in response".into(),
196                    )))
197                } else {
198                    Ok(args.remove(0))
199                }?;
200                Ok(Response::Var(var_name, var_value))
201            }
202            "RW" => {
203                let _var_device = if args.is_empty() {
204                    Err(ClientError::from(NutError::Generic(
205                        "Unspecified RW device name in response".into(),
206                    )))
207                } else {
208                    Ok(args.remove(0))
209                }?;
210                let var_name = if args.is_empty() {
211                    Err(ClientError::from(NutError::Generic(
212                        "Unspecified RW name in response".into(),
213                    )))
214                } else {
215                    Ok(args.remove(0))
216                }?;
217                let var_value = if args.is_empty() {
218                    Err(ClientError::from(NutError::Generic(
219                        "Unspecified RW value in response".into(),
220                    )))
221                } else {
222                    Ok(args.remove(0))
223                }?;
224                Ok(Response::Rw(var_name, var_value))
225            }
226            "UPS" => {
227                let name = if args.is_empty() {
228                    Err(ClientError::from(NutError::Generic(
229                        "Unspecified UPS name in response".into(),
230                    )))
231                } else {
232                    Ok(args.remove(0))
233                }?;
234                let description = if args.is_empty() {
235                    Err(ClientError::from(NutError::Generic(
236                        "Unspecified UPS description in response".into(),
237                    )))
238                } else {
239                    Ok(args.remove(0))
240                }?;
241                Ok(Response::Ups(name, description))
242            }
243            "CLIENT" => {
244                let _device = if args.is_empty() {
245                    Err(ClientError::from(NutError::Generic(
246                        "Unspecified CLIENT device in response".into(),
247                    )))
248                } else {
249                    Ok(args.remove(0))
250                }?;
251                let ip_address = if args.is_empty() {
252                    Err(ClientError::from(NutError::Generic(
253                        "Unspecified CLIENT IP in response".into(),
254                    )))
255                } else {
256                    Ok(args.remove(0))
257                }?;
258                Ok(Response::Client(ip_address))
259            }
260            "CMD" => {
261                let _device = if args.is_empty() {
262                    Err(ClientError::from(NutError::Generic(
263                        "Unspecified CMD device in response".into(),
264                    )))
265                } else {
266                    Ok(args.remove(0))
267                }?;
268                let name = if args.is_empty() {
269                    Err(ClientError::from(NutError::Generic(
270                        "Unspecified CMD name in response".into(),
271                    )))
272                } else {
273                    Ok(args.remove(0))
274                }?;
275                Ok(Response::Cmd(name))
276            }
277            "CMDDESC" => {
278                let _device = if args.is_empty() {
279                    Err(ClientError::from(NutError::Generic(
280                        "Unspecified CMDDESC device in response".into(),
281                    )))
282                } else {
283                    Ok(args.remove(0))
284                }?;
285                let _name = if args.is_empty() {
286                    Err(ClientError::from(NutError::Generic(
287                        "Unspecified CMDDESC name in response".into(),
288                    )))
289                } else {
290                    Ok(args.remove(0))
291                }?;
292                let desc = if args.is_empty() {
293                    Err(ClientError::from(NutError::Generic(
294                        "Unspecified CMDDESC description in response".into(),
295                    )))
296                } else {
297                    Ok(args.remove(0))
298                }?;
299                Ok(Response::CmdDesc(desc))
300            }
301            "UPSDESC" => {
302                let _device = if args.is_empty() {
303                    Err(ClientError::from(NutError::Generic(
304                        "Unspecified UPSDESC device in response".into(),
305                    )))
306                } else {
307                    Ok(args.remove(0))
308                }?;
309                let desc = if args.is_empty() {
310                    Err(ClientError::from(NutError::Generic(
311                        "Unspecified UPSDESC description in response".into(),
312                    )))
313                } else {
314                    Ok(args.remove(0))
315                }?;
316                Ok(Response::UpsDesc(desc))
317            }
318            "DESC" => {
319                let _device = if args.is_empty() {
320                    Err(ClientError::from(NutError::Generic(
321                        "Unspecified DESC device in response".into(),
322                    )))
323                } else {
324                    Ok(args.remove(0))
325                }?;
326                let _name = if args.is_empty() {
327                    Err(ClientError::from(NutError::Generic(
328                        "Unspecified DESC name in response".into(),
329                    )))
330                } else {
331                    Ok(args.remove(0))
332                }?;
333                let desc = if args.is_empty() {
334                    Err(ClientError::from(NutError::Generic(
335                        "Unspecified DESC description in response".into(),
336                    )))
337                } else {
338                    Ok(args.remove(0))
339                }?;
340                Ok(Response::Desc(desc))
341            }
342            "NUMLOGINS" => {
343                let _device = if args.is_empty() {
344                    Err(ClientError::from(NutError::Generic(
345                        "Unspecified NUMLOGINS device in response".into(),
346                    )))
347                } else {
348                    Ok(args.remove(0))
349                }?;
350                let num = if args.is_empty() {
351                    Err(ClientError::from(NutError::Generic(
352                        "Unspecified NUMLOGINS number in response".into(),
353                    )))
354                } else {
355                    Ok(args.remove(0))
356                }?;
357                let num = num.parse::<i32>().map_err(|_| {
358                    ClientError::from(NutError::Generic(
359                        "Invalid NUMLOGINS number in response".into(),
360                    ))
361                })?;
362                Ok(Response::NumLogins(num))
363            }
364            "TYPE" => {
365                let _device = if args.is_empty() {
366                    Err(ClientError::from(NutError::Generic(
367                        "Unspecified TYPE device in response".into(),
368                    )))
369                } else {
370                    Ok(args.remove(0))
371                }?;
372                let name = if args.is_empty() {
373                    Err(ClientError::from(NutError::Generic(
374                        "Unspecified TYPE name in response".into(),
375                    )))
376                } else {
377                    Ok(args.remove(0))
378                }?;
379                let types = args;
380                Ok(Response::Type(name, types))
381            }
382            "RANGE" => {
383                let _device = if args.is_empty() {
384                    Err(ClientError::from(NutError::Generic(
385                        "Unspecified RANGE device in response".into(),
386                    )))
387                } else {
388                    Ok(args.remove(0))
389                }?;
390                let _name = if args.is_empty() {
391                    Err(ClientError::from(NutError::Generic(
392                        "Unspecified RANGE name in response".into(),
393                    )))
394                } else {
395                    Ok(args.remove(0))
396                }?;
397                let min = if args.is_empty() {
398                    Err(ClientError::from(NutError::Generic(
399                        "Unspecified RANGE min in response".into(),
400                    )))
401                } else {
402                    Ok(args.remove(0))
403                }?;
404                let max = if args.is_empty() {
405                    Err(ClientError::from(NutError::Generic(
406                        "Unspecified RANGE max in response".into(),
407                    )))
408                } else {
409                    Ok(args.remove(0))
410                }?;
411                Ok(Response::Range(VariableRange(min, max)))
412            }
413            "ENUM" => {
414                let _device = if args.is_empty() {
415                    Err(ClientError::from(NutError::Generic(
416                        "Unspecified ENUM device in response".into(),
417                    )))
418                } else {
419                    Ok(args.remove(0))
420                }?;
421                let _name = if args.is_empty() {
422                    Err(ClientError::from(NutError::Generic(
423                        "Unspecified ENUM name in response".into(),
424                    )))
425                } else {
426                    Ok(args.remove(0))
427                }?;
428                let val = if args.is_empty() {
429                    Err(ClientError::from(NutError::Generic(
430                        "Unspecified ENUM value in response".into(),
431                    )))
432                } else {
433                    Ok(args.remove(0))
434                }?;
435                Ok(Response::Enum(val))
436            }
437            _ => Err(NutError::UnknownResponseType(cmd_name).into()),
438        }
439    }
440
441    pub fn expect_ok(&self) -> crate::Result<&Response> {
442        match self {
443            Self::Ok => Ok(self),
444            _ => Err(NutError::UnexpectedResponse.into()),
445        }
446    }
447
448    pub fn expect_begin_list(self, expected_args: &[&str]) -> crate::Result<Response> {
449        let expected_args = shell_words::join(expected_args);
450        if let Self::BeginList(args) = &self {
451            if &expected_args == args {
452                Ok(self)
453            } else {
454                Err(NutError::UnexpectedResponse.into())
455            }
456        } else {
457            Err(NutError::UnexpectedResponse.into())
458        }
459    }
460
461    pub fn expect_var(&self) -> crate::Result<Variable> {
462        if let Self::Var(name, value) = &self {
463            Ok(Variable::parse(name, value.to_owned()))
464        } else {
465            Err(NutError::UnexpectedResponse.into())
466        }
467    }
468
469    pub fn expect_rw(&self) -> crate::Result<Variable> {
470        if let Self::Rw(name, value) = &self {
471            Ok(Variable::parse(name, value.to_owned()))
472        } else {
473            Err(NutError::UnexpectedResponse.into())
474        }
475    }
476
477    pub fn expect_ups(&self) -> crate::Result<(String, String)> {
478        if let Self::Ups(name, description) = &self {
479            Ok((name.to_owned(), description.to_owned()))
480        } else {
481            Err(NutError::UnexpectedResponse.into())
482        }
483    }
484
485    pub fn expect_client(&self) -> crate::Result<String> {
486        if let Self::Client(client_ip) = &self {
487            Ok(client_ip.to_owned())
488        } else {
489            Err(NutError::UnexpectedResponse.into())
490        }
491    }
492
493    pub fn expect_cmd(&self) -> crate::Result<String> {
494        if let Self::Cmd(name) = &self {
495            Ok(name.to_owned())
496        } else {
497            Err(NutError::UnexpectedResponse.into())
498        }
499    }
500
501    pub fn expect_cmddesc(&self) -> crate::Result<String> {
502        if let Self::CmdDesc(description) = &self {
503            Ok(description.to_owned())
504        } else {
505            Err(NutError::UnexpectedResponse.into())
506        }
507    }
508
509    pub fn expect_upsdesc(&self) -> crate::Result<String> {
510        if let Self::UpsDesc(description) = &self {
511            Ok(description.to_owned())
512        } else {
513            Err(NutError::UnexpectedResponse.into())
514        }
515    }
516
517    pub fn expect_desc(&self) -> crate::Result<String> {
518        if let Self::Desc(description) = &self {
519            Ok(description.to_owned())
520        } else {
521            Err(NutError::UnexpectedResponse.into())
522        }
523    }
524
525    pub fn expect_numlogins(&self) -> crate::Result<i32> {
526        if let Self::NumLogins(num) = &self {
527            Ok(*num)
528        } else {
529            Err(NutError::UnexpectedResponse.into())
530        }
531    }
532
533    pub fn expect_type(&self) -> crate::Result<VariableDefinition> {
534        if let Self::Type(name, types) = &self {
535            VariableDefinition::try_from((
536                name.to_owned(),
537                types.iter().map(String::as_str).collect(),
538            ))
539        } else {
540            Err(NutError::UnexpectedResponse.into())
541        }
542    }
543
544    pub fn expect_range(&self) -> crate::Result<VariableRange> {
545        if let Self::Range(range) = &self {
546            Ok(range.to_owned())
547        } else {
548            Err(NutError::UnexpectedResponse.into())
549        }
550    }
551
552    pub fn expect_enum(&self) -> crate::Result<String> {
553        if let Self::Enum(value) = &self {
554            Ok(value.to_owned())
555        } else {
556            Err(NutError::UnexpectedResponse.into())
557        }
558    }
559}
560
561/// A macro for implementing `LIST` commands.
562///
563/// Each function should return a 2-tuple with
564///     (1) the query to pass to `LIST`
565///     (2) a closure for mapping each `Response` row to the return type
566macro_rules! implement_list_commands {
567    (
568        $(
569            $(#[$attr:meta])+
570            $vis:vis fn $name:ident($($argname:ident: $argty:ty),*) -> $retty:ty {
571                (
572                    $query:block,
573                    $mapper:block,
574                )
575            }
576        )*
577    ) => {
578        impl crate::blocking::Connection {
579            $(
580                $(#[$attr])*
581                #[allow(dead_code)]
582                $vis fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
583                    match self {
584                        Self::Tcp(conn) => {
585                            conn.write_cmd(Command::List($query))?;
586                            let list = conn.read_list($query)?;
587                            list.into_iter().map($mapper).collect()
588                        },
589                    }
590                }
591            )*
592        }
593
594        #[cfg(feature = "async")]
595        impl crate::tokio::Connection {
596            $(
597                $(#[$attr])*
598                #[allow(dead_code)]
599                $vis async fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
600                    match self {
601                        Self::Tcp(conn) => {
602                            conn.write_cmd(Command::List($query)).await?;
603                            let list = conn.read_list($query).await?;
604                            list.into_iter().map($mapper).collect()
605                        },
606                    }
607                }
608            )*
609        }
610    };
611}
612
613/// A macro for implementing `GET` commands.
614///
615/// Each function should return a 2-tuple with
616///     (1) the query to pass to `GET`
617///     (2) a closure for mapping the `Response` row to the return type
618macro_rules! implement_get_commands {
619    (
620        $(
621            $(#[$attr:meta])+
622            $vis:vis fn $name:ident($($argname:ident: $argty:ty),*) -> $retty:ty {
623                (
624                    $query:block,
625                    $mapper:block,
626                )
627            }
628        )*
629    ) => {
630        impl crate::blocking::Connection {
631            $(
632                $(#[$attr])*
633                #[allow(dead_code)]
634                $vis fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
635                    match self {
636                        Self::Tcp(conn) => {
637                            conn.write_cmd(Command::Get($query))?;
638                            ($mapper)(conn.read_response()?)
639                        },
640                    }
641                }
642            )*
643        }
644
645        #[cfg(feature = "async")]
646        impl crate::tokio::Connection {
647            $(
648                $(#[$attr])*
649                #[allow(dead_code)]
650                $vis async fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
651                    match self {
652                        Self::Tcp(conn) => {
653                            conn.write_cmd(Command::Get($query)).await?;
654                            ($mapper)(conn.read_response().await?)
655                        },
656                    }
657                }
658            )*
659        }
660    };
661}
662
663/// A macro for implementing simple/direct commands.
664///
665/// Each function should return a 2-tuple with
666///     (1) the command to pass
667///     (2) a closure for mapping the `String` row to the return type
668macro_rules! implement_simple_commands {
669    (
670        $(
671            $(#[$attr:meta])+
672            $vis:vis fn $name:ident($($argname:ident: $argty:ty),*) -> $retty:ty {
673                (
674                    $cmd:block,
675                    $mapper:block,
676                )
677            }
678        )*
679    ) => {
680        impl crate::blocking::Connection {
681            $(
682                $(#[$attr])*
683                #[allow(dead_code)]
684                $vis fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
685                    match self {
686                        Self::Tcp(conn) => {
687                            conn.write_cmd($cmd)?;
688                            ($mapper)(conn.read_plain_response()?)
689                        },
690                    }
691                }
692            )*
693        }
694
695        #[cfg(feature = "async")]
696        impl crate::tokio::Connection {
697            $(
698                $(#[$attr])*
699                #[allow(dead_code)]
700                $vis async fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
701                    match self {
702                        Self::Tcp(conn) => {
703                            conn.write_cmd($cmd).await?;
704                            ($mapper)(conn.read_plain_response().await?)
705                        },
706                    }
707                }
708            )*
709        }
710    };
711}
712
713/// A macro for implementing action commands that return `OK`.
714///
715/// Each function should return the command to pass.
716macro_rules! implement_action_commands {
717    (
718        $(
719            $(#[$attr:meta])+
720            $vis:vis fn $name:ident($($argname:ident: $argty:ty),*) $cmd:block
721        )*
722    ) => {
723        impl crate::blocking::Connection {
724            $(
725                $(#[$attr])*
726                #[allow(dead_code)]
727                $vis fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<()> {
728                    match self {
729                        Self::Tcp(conn) => {
730                            conn.write_cmd($cmd)?;
731                            conn.read_response()?.expect_ok()?;
732                            Ok(())
733                        },
734                    }
735                }
736            )*
737        }
738
739        #[cfg(feature = "async")]
740        impl crate::tokio::Connection {
741            $(
742                $(#[$attr])*
743                #[allow(dead_code)]
744                $vis async fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<()> {
745                    match self {
746                        Self::Tcp(conn) => {
747                            conn.write_cmd($cmd).await?;
748                            conn.read_response().await?.expect_ok()?;
749                            Ok(())
750                        },
751                    }
752                }
753            )*
754        }
755    };
756}
757
758implement_list_commands! {
759    /// Queries a list of UPS devices.
760    pub fn list_ups() -> Vec<(String, String)> {
761        (
762            { &["UPS"] },
763            { |row: Response| row.expect_ups() },
764        )
765    }
766
767    /// Queries the list of client IP addresses connected to the given device.
768    pub fn list_clients(ups_name: &str) -> Vec<String> {
769        (
770            { &["CLIENT", ups_name] },
771            { |row: Response| row.expect_client() },
772        )
773    }
774
775    /// Queries the list of variables for a UPS device.
776    pub fn list_vars(ups_name: &str) -> Vec<Variable> {
777        (
778            { &["VAR", ups_name] },
779            { |row: Response| row.expect_var() },
780        )
781    }
782
783    /// Queries the list of mutable variables for a UPS device.
784    pub fn list_mutable_vars(ups_name: &str) -> Vec<Variable> {
785        (
786            { &["RW", ups_name] },
787            { |row: Response| row.expect_rw() },
788        )
789    }
790
791    /// Queries the list of commands available for the given device.
792    pub fn list_commands(ups_name: &str) -> Vec<String> {
793        (
794            { &["CMD", ups_name] },
795            { |row: Response| row.expect_cmd() },
796        )
797    }
798
799    /// Queries the possible ranges of a UPS variable.
800    pub fn list_var_range(ups_name: &str, variable: &str) -> Vec<VariableRange> {
801        (
802            { &["RANGE", ups_name, variable] },
803            { |row: Response| row.expect_range() },
804        )
805    }
806
807    /// Queries the possible enum values of a UPS variable.
808    pub fn list_var_enum(ups_name: &str, variable: &str) -> Vec<String> {
809        (
810            { &["ENUM", ups_name, variable] },
811            { |row: Response| row.expect_enum() },
812        )
813    }
814}
815
816implement_get_commands! {
817    /// Queries one variable for a UPS device.
818    pub fn get_var(ups_name: &str, variable: &str) -> Variable {
819        (
820            { &["VAR", ups_name, variable] },
821            { |row: Response| row.expect_var() },
822        )
823    }
824
825    /// Queries the description of a UPS variable.
826    pub fn get_var_description(ups_name: &str, variable: &str) -> String {
827        (
828            { &["DESC", ups_name, variable] },
829            { |row: Response| row.expect_desc() },
830        )
831    }
832
833    /// Queries the type of a UPS variable.
834    pub fn get_var_type(ups_name: &str, variable: &str) -> VariableDefinition {
835        (
836            { &["TYPE", ups_name, variable] },
837            { |row: Response| row.expect_type() },
838        )
839    }
840
841    /// Queries the description of a UPS command.
842    pub fn get_command_description(ups_name: &str, variable: &str) -> String {
843        (
844            { &["CMDDESC", ups_name, variable] },
845            { |row: Response| row.expect_cmddesc() },
846        )
847    }
848
849    /// Queries the description of a UPS device.
850    pub fn get_ups_description(ups_name: &str) -> String {
851        (
852            { &["UPSDESC", ups_name] },
853            { |row: Response| row.expect_upsdesc() },
854        )
855    }
856
857    /// Queries the number of logins to the specified UPS.
858    pub fn get_num_logins(ups_name: &str) -> i32 {
859        (
860            { &["NUMLOGINS", ups_name] },
861            { |row: Response| row.expect_numlogins() },
862        )
863    }
864}
865
866implement_simple_commands! {
867    /// Queries the network protocol version.
868    pub fn get_network_version() -> String {
869        (
870            { Command::NetworkVersion },
871            { |row: String| Ok(row) },
872        )
873    }
874
875    /// Queries the server NUT version.
876    pub fn get_server_version() -> String {
877        (
878            { Command::Version },
879            { |row: String| Ok(row) },
880        )
881    }
882}
883
884implement_action_commands! {
885    /// Sends the login username.
886    pub(crate) fn set_username(username: &str) {
887        Command::SetUsername(username)
888    }
889
890    /// Sends the login password.
891    pub(crate) fn set_password(password: &str) {
892        Command::SetPassword(password)
893    }
894
895    /// Gracefully shuts down the connection.
896    pub(crate) fn logout() {
897        Command::Logout
898    }
899}