version: 0.0.3

feat: list UPS variables
docs: improve docs and enforce
This commit is contained in:
Aram 🍐 2020-11-18 00:14:55 -05:00
parent b36c855b2c
commit 0ba5e4565f
8 changed files with 66 additions and 13 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "nut-client" name = "nut-client"
version = "0.0.2" version = "0.0.3"
authors = ["Aram Peres <aram.peres@wavy.fm>"] authors = ["Aram Peres <aram.peres@wavy.fm>"]
edition = "2018" edition = "2018"
description = "Network UPS Tools (NUT) client library" description = "Network UPS Tools (NUT) client library"

View file

@ -10,6 +10,7 @@ A [Network UPS Tools](https://github.com/networkupstools/nut) (NUT) client libra
- Connect to `upsd`/`nut-server` using TCP - Connect to `upsd`/`nut-server` using TCP
- Login with with username and password - Login with with username and password
- List UPS devices - List UPS devices
- List variables for a UPS device
## ⚠️ Safety Goggles Required ⚠️ ## ⚠️ Safety Goggles Required ⚠️
@ -26,11 +27,10 @@ Check out the `examples` directory for more advanced examples.
use std::env; use std::env;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
use nut_client::{Auth, ConfigBuilder, Host};
use nut_client::blocking::Connection; use nut_client::blocking::Connection;
use nut_client::{Auth, ConfigBuilder, Host};
fn main() -> nut_client::Result<()> { fn main() -> nut_client::Result<()> {
// The TCP host:port for upsd/nut-server
let addr = env::var("NUT_ADDR") let addr = env::var("NUT_ADDR")
.unwrap_or_else(|_| "localhost:3493".into()) .unwrap_or_else(|_| "localhost:3493".into())
.to_socket_addrs() .to_socket_addrs()
@ -38,25 +38,27 @@ fn main() -> nut_client::Result<()> {
.next() .next()
.unwrap(); .unwrap();
// Username and password (optional)
let username = env::var("NUT_USER").ok(); let username = env::var("NUT_USER").ok();
let password = env::var("NUT_PASSWORD").ok(); let password = env::var("NUT_PASSWORD").ok();
let auth = username.map(|username| Auth::new(username, password)); let auth = username.map(|username| Auth::new(username, password));
// Build the config
let config = ConfigBuilder::new() let config = ConfigBuilder::new()
.with_host(Host::Tcp(addr)) .with_host(Host::Tcp(addr))
.with_auth(auth) .with_auth(auth)
.build(); .build();
// Open a connection and login
let mut conn = Connection::new(config)?; let mut conn = Connection::new(config)?;
// Print a list of all UPS devices // Print a list of all UPS devices and their variables
println!("Connected UPS devices:"); println!("Connected UPS devices:");
for (id, description) in conn.list_ups()? { for (name, description) in conn.list_ups()? {
println!("\t- ID: {}", id); println!("\t- Name: {}", name);
println!("\t Description: {}", description); println!("\t Description: {}", description);
println!("\t Variables:");
for (var_name, var_val) in conn.list_vars(&name)? {
println!("\t\t- {} = {}", var_name, var_val);
}
} }
Ok(()) Ok(())

View file

@ -23,11 +23,16 @@ fn main() -> nut_client::Result<()> {
let mut conn = Connection::new(config)?; let mut conn = Connection::new(config)?;
// Print a list of all UPS devices // Print a list of all UPS devices and their variables
println!("Connected UPS devices:"); println!("Connected UPS devices:");
for (id, description) in conn.list_ups()? { for (name, description) in conn.list_ups()? {
println!("\t- ID: {}", id); println!("\t- Name: {}", name);
println!("\t Description: {}", description); println!("\t Description: {}", description);
println!("\t Variables:");
for (var_name, var_val) in conn.list_vars(&name)? {
println!("\t\t- {} = {}", var_name, var_val);
}
} }
Ok(()) Ok(())

View file

@ -7,10 +7,12 @@ use crate::{ClientError, Config, Host, NutError};
/// A blocking NUT client connection. /// A blocking NUT client connection.
pub enum Connection { pub enum Connection {
/// A TCP connection.
Tcp(TcpConnection), Tcp(TcpConnection),
} }
impl Connection { impl Connection {
/// Initializes a connection to a NUT server (upsd).
pub fn new(config: Config) -> crate::Result<Self> { pub fn new(config: Config) -> crate::Result<Self> {
match &config.host { match &config.host {
Host::Tcp(socket_addr) => { Host::Tcp(socket_addr) => {
@ -19,11 +21,19 @@ impl Connection {
} }
} }
/// Queries a list of UPS devices.
pub fn list_ups(&mut self) -> crate::Result<Vec<(String, String)>> { pub fn list_ups(&mut self) -> crate::Result<Vec<(String, String)>> {
match self { match self {
Self::Tcp(conn) => conn.list_ups(), Self::Tcp(conn) => conn.list_ups(),
} }
} }
/// Queries the list of variables for a UPS device.
pub fn list_vars(&mut self, ups_name: &str) -> crate::Result<Vec<(String, String)>> {
match self {
Self::Tcp(conn) => conn.list_vars(ups_name),
}
}
} }
/// A blocking TCP NUT client connection. /// A blocking TCP NUT client connection.
@ -70,6 +80,17 @@ impl TcpConnection {
.collect()) .collect())
} }
fn list_vars(&mut self, ups_name: &str) -> crate::Result<Vec<(String, String)>> {
let query = &["VAR", ups_name];
Self::write_cmd(&mut self.tcp_stream, Command::List(query))?;
let list = Self::read_list(&mut self.tcp_stream, query)?;
Ok(list
.into_iter()
.map(|mut row| (row.remove(0), row.remove(0)))
.collect())
}
fn write_cmd(stream: &mut TcpStream, line: Command) -> crate::Result<()> { fn write_cmd(stream: &mut TcpStream, line: Command) -> crate::Result<()> {
let line = format!("{}\n", line); let line = format!("{}\n", line);
stream.write_all(line.as_bytes())?; stream.write_all(line.as_bytes())?;

View file

@ -67,6 +67,7 @@ impl Response {
let err_type = args.remove(0); let err_type = args.remove(0);
match err_type.as_str() { match err_type.as_str() {
"ACCESS-DENIED" => Err(NutError::AccessDenied.into()), "ACCESS-DENIED" => Err(NutError::AccessDenied.into()),
"UNKNOWN-UPS" => Err(NutError::UnknownUps.into()),
_ => Err(NutError::Generic(format!( _ => Err(NutError::Generic(format!(
"Server error: {} {}", "Server error: {} {}",
err_type, err_type,

View file

@ -31,6 +31,7 @@ pub struct Auth {
} }
impl Auth { impl Auth {
/// Initializes authentication credentials with a username, and optionally a password.
pub fn new(username: String, password: Option<String>) -> Self { pub fn new(username: String, password: Option<String>) -> Self {
Auth { username, password } Auth { username, password }
} }
@ -54,6 +55,7 @@ pub struct Config {
} }
impl Config { impl Config {
/// Creates a connection configuration.
pub fn new(host: Host, auth: Option<Auth>, timeout: Duration) -> Self { pub fn new(host: Host, auth: Option<Auth>, timeout: Duration) -> Self {
Config { Config {
host, host,
@ -77,21 +79,26 @@ impl ConfigBuilder {
ConfigBuilder::default() ConfigBuilder::default()
} }
/// Sets the connection host, such as the TCP address and port.
pub fn with_host(mut self, host: Host) -> Self { pub fn with_host(mut self, host: Host) -> Self {
self.host = Some(host); self.host = Some(host);
self self
} }
/// Sets the optional authentication parameters.
pub fn with_auth(mut self, auth: Option<Auth>) -> Self { pub fn with_auth(mut self, auth: Option<Auth>) -> Self {
self.auth = auth; self.auth = auth;
self self
} }
/// Sets the network connection timeout. This may be ignored by non-network
/// connections, such as Unix domain sockets.
pub fn with_timeout(mut self, timeout: Duration) -> Self { pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout); self.timeout = Some(timeout);
self self
} }
/// Builds the configuration with this builder.
pub fn build(self) -> Config { pub fn build(self) -> Config {
Config::new( Config::new(
self.host.unwrap_or_default(), self.host.unwrap_or_default(),

View file

@ -6,7 +6,11 @@ use std::io;
pub enum NutError { pub enum NutError {
/// Occurs when the username/password combination is rejected. /// Occurs when the username/password combination is rejected.
AccessDenied, AccessDenied,
/// Occurs when the specified UPS device does not exist.
UnknownUps,
/// Occurs when the response type or content wasn't expected at the current stage.
UnexpectedResponse, UnexpectedResponse,
/// Occurs when the response type is not recognized by the client.
UnknownResponseType(String), UnknownResponseType(String),
/// Generic (usually internal) client error. /// Generic (usually internal) client error.
Generic(String), Generic(String),
@ -16,7 +20,8 @@ impl fmt::Display for NutError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::AccessDenied => write!(f, "Authentication failed"), Self::AccessDenied => write!(f, "Authentication failed"),
Self::UnexpectedResponse => write!(f, "Unexpected server response"), Self::UnknownUps => write!(f, "Unknown UPS device name"),
Self::UnexpectedResponse => write!(f, "Unexpected server response content"),
Self::UnknownResponseType(ty) => write!(f, "Unknown response type: {}", ty), Self::UnknownResponseType(ty) => write!(f, "Unknown response type: {}", ty),
Self::Generic(msg) => write!(f, "Internal client error: {}", msg), Self::Generic(msg) => write!(f, "Internal client error: {}", msg),
} }
@ -25,9 +30,12 @@ impl fmt::Display for NutError {
impl std::error::Error for NutError {} impl std::error::Error for NutError {}
/// Encapsulation for errors emitted by the client library.
#[derive(Debug)] #[derive(Debug)]
pub enum ClientError { pub enum ClientError {
/// Encapsulates IO errors.
Io(io::Error), Io(io::Error),
/// Encapsulates NUT and client-specific errors.
Nut(NutError), Nut(NutError),
} }
@ -54,4 +62,5 @@ impl From<NutError> for ClientError {
} }
} }
/// Result type for [`ClientError`]
pub type Result<T> = std::result::Result<T, ClientError>; pub type Result<T> = std::result::Result<T, ClientError>;

View file

@ -1,6 +1,14 @@
#![deny(missing_docs)]
//! # nut-client
//!
//! The `nut-client` crate provides a network client implementation
//! for Network UPS Tools (NUT) servers.
pub use config::*; pub use config::*;
pub use error::*; pub use error::*;
/// Blocking client implementation for NUT.
pub mod blocking; pub mod blocking;
mod cmd; mod cmd;