Simplify errors

This commit is contained in:
Aram 🍐 2021-08-01 15:10:27 -04:00
parent c935d88496
commit 96fbfeaeab
8 changed files with 109 additions and 135 deletions

View file

@ -142,7 +142,7 @@ impl TcpConnection {
// Parse args by splitting whitespace, minding quotes for args with multiple words // Parse args by splitting whitespace, minding quotes for args with multiple words
let args = shell_words::split(&raw) let args = shell_words::split(&raw)
.map_err(|e| NutError::Generic(format!("Parsing server response failed: {}", e)))?; .map_err(|e| NutError::generic(format!("Parsing server response failed: {}", e)))?;
Ok(args) Ok(args)
} }

View file

@ -118,23 +118,23 @@ pub enum Response {
impl Response { impl Response {
pub fn from_args(mut args: Vec<String>) -> crate::Result<Response> { pub fn from_args(mut args: Vec<String>) -> crate::Result<Response> {
if args.is_empty() { if args.is_empty() {
return Err( return Err(ClientError::generic(
NutError::Generic("Parsing server response failed: empty line".into()).into(), "Parsing server response failed: empty line",
); ));
} }
let cmd_name = args.remove(0); let cmd_name = args.remove(0);
match cmd_name.as_str() { match cmd_name.as_str() {
"OK" => Ok(Self::Ok), "OK" => Ok(Self::Ok),
"ERR" => { "ERR" => {
if args.is_empty() { if args.is_empty() {
Err(NutError::Generic("Unspecified server error".into()).into()) Err(ClientError::generic("Unspecified server error"))
} else { } else {
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()), "UNKNOWN-UPS" => Err(NutError::UnknownUps.into()),
"FEATURE-NOT-CONFIGURED" => Err(NutError::FeatureNotConfigured.into()), "FEATURE-NOT-CONFIGURED" => Err(NutError::FeatureNotConfigured.into()),
_ => Err(NutError::Generic(format!( _ => Err(NutError::generic(format!(
"Server error: {} {}", "Server error: {} {}",
err_type, err_type,
args.join(" ") args.join(" ")
@ -145,14 +145,14 @@ impl Response {
} }
"BEGIN" => { "BEGIN" => {
if args.is_empty() { if args.is_empty() {
Err(NutError::Generic("Unspecified BEGIN type".into()).into()) Err(ClientError::generic("Unspecified BEGIN type"))
} else { } else {
let begin_type = args.remove(0); let begin_type = args.remove(0);
if &begin_type != "LIST" { if &begin_type != "LIST" {
Err( Err(ClientError::generic(format!(
NutError::Generic(format!("Unexpected BEGIN type: {}", begin_type)) "Unexpected BEGIN type: {}",
.into(), begin_type
) )))
} else { } else {
let args = shell_words::join(args); let args = shell_words::join(args);
Ok(Response::BeginList(args)) Ok(Response::BeginList(args))
@ -161,14 +161,14 @@ impl Response {
} }
"END" => { "END" => {
if args.is_empty() { if args.is_empty() {
Err(NutError::Generic("Unspecified END type".into()).into()) Err(ClientError::generic("Unspecified END type"))
} else { } else {
let begin_type = args.remove(0); let begin_type = args.remove(0);
if &begin_type != "LIST" { if &begin_type != "LIST" {
Err( Err(ClientError::generic(format!(
NutError::Generic(format!("Unexpected END type: {}", begin_type)) "Unexpected END type: {}",
.into(), begin_type
) )))
} else { } else {
let args = shell_words::join(args); let args = shell_words::join(args);
Ok(Response::EndList(args)) Ok(Response::EndList(args))
@ -177,23 +177,19 @@ impl Response {
} }
"VAR" => { "VAR" => {
let _var_device = if args.is_empty() { let _var_device = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic(
"Unspecified VAR device name in response".into(), "Unspecified VAR device name in response",
))) ))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let var_name = if args.is_empty() { let var_name = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified VAR name in response"))
"Unspecified VAR name in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let var_value = if args.is_empty() { let var_value = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified VAR value in response"))
"Unspecified VAR value in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
@ -201,23 +197,19 @@ impl Response {
} }
"RW" => { "RW" => {
let _var_device = if args.is_empty() { let _var_device = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic(
"Unspecified RW device name in response".into(), "Unspecified RW device name in response",
))) ))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let var_name = if args.is_empty() { let var_name = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified RW name in response"))
"Unspecified RW name in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let var_value = if args.is_empty() { let var_value = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified RW value in response"))
"Unspecified RW value in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
@ -225,16 +217,14 @@ impl Response {
} }
"UPS" => { "UPS" => {
let name = if args.is_empty() { let name = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified UPS name in response"))
"Unspecified UPS name in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let description = if args.is_empty() { let description = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic(
"Unspecified UPS description in response".into(), "Unspecified UPS description in response",
))) ))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
@ -242,16 +232,14 @@ impl Response {
} }
"CLIENT" => { "CLIENT" => {
let _device = if args.is_empty() { let _device = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic(
"Unspecified CLIENT device in response".into(), "Unspecified CLIENT device in response",
))) ))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let ip_address = if args.is_empty() { let ip_address = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified CLIENT IP in response"))
"Unspecified CLIENT IP in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
@ -259,16 +247,12 @@ impl Response {
} }
"CMD" => { "CMD" => {
let _device = if args.is_empty() { let _device = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified CMD device in response"))
"Unspecified CMD device in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let name = if args.is_empty() { let name = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified CMD name in response"))
"Unspecified CMD name in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
@ -276,23 +260,21 @@ impl Response {
} }
"CMDDESC" => { "CMDDESC" => {
let _device = if args.is_empty() { let _device = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic(
"Unspecified CMDDESC device in response".into(), "Unspecified CMDDESC device in response",
))) ))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let _name = if args.is_empty() { let _name = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified CMDDESC name in response"))
"Unspecified CMDDESC name in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let desc = if args.is_empty() { let desc = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic(
"Unspecified CMDDESC description in response".into(), "Unspecified CMDDESC description in response",
))) ))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
@ -300,16 +282,16 @@ impl Response {
} }
"UPSDESC" => { "UPSDESC" => {
let _device = if args.is_empty() { let _device = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic(
"Unspecified UPSDESC device in response".into(), "Unspecified UPSDESC device in response",
))) ))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let desc = if args.is_empty() { let desc = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic(
"Unspecified UPSDESC description in response".into(), "Unspecified UPSDESC description in response",
))) ))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
@ -317,23 +299,19 @@ impl Response {
} }
"DESC" => { "DESC" => {
let _device = if args.is_empty() { let _device = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified DESC device in response"))
"Unspecified DESC device in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let _name = if args.is_empty() { let _name = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified DESC name in response"))
"Unspecified DESC name in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let desc = if args.is_empty() { let desc = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic(
"Unspecified DESC description in response".into(), "Unspecified DESC description in response",
))) ))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
@ -341,38 +319,32 @@ impl Response {
} }
"NUMLOGINS" => { "NUMLOGINS" => {
let _device = if args.is_empty() { let _device = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic(
"Unspecified NUMLOGINS device in response".into(), "Unspecified NUMLOGINS device in response",
))) ))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let num = if args.is_empty() { let num = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic(
"Unspecified NUMLOGINS number in response".into(), "Unspecified NUMLOGINS number in response",
))) ))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let num = num.parse::<i32>().map_err(|_| { let num = num
ClientError::from(NutError::Generic( .parse::<i32>()
"Invalid NUMLOGINS number in response".into(), .map_err(|_| ClientError::generic("Invalid NUMLOGINS number in response"))?;
))
})?;
Ok(Response::NumLogins(num)) Ok(Response::NumLogins(num))
} }
"TYPE" => { "TYPE" => {
let _device = if args.is_empty() { let _device = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified TYPE device in response"))
"Unspecified TYPE device in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let name = if args.is_empty() { let name = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified TYPE name in response"))
"Unspecified TYPE name in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
@ -381,30 +353,22 @@ impl Response {
} }
"RANGE" => { "RANGE" => {
let _device = if args.is_empty() { let _device = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified RANGE device in response"))
"Unspecified RANGE device in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let _name = if args.is_empty() { let _name = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified RANGE name in response"))
"Unspecified RANGE name in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let min = if args.is_empty() { let min = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified RANGE min in response"))
"Unspecified RANGE min in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let max = if args.is_empty() { let max = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified RANGE max in response"))
"Unspecified RANGE max in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
@ -412,23 +376,17 @@ impl Response {
} }
"ENUM" => { "ENUM" => {
let _device = if args.is_empty() { let _device = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified ENUM device in response"))
"Unspecified ENUM device in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let _name = if args.is_empty() { let _name = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified ENUM name in response"))
"Unspecified ENUM name in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;
let val = if args.is_empty() { let val = if args.is_empty() {
Err(ClientError::from(NutError::Generic( Err(ClientError::generic("Unspecified ENUM value in response"))
"Unspecified ENUM value in response".into(),
)))
} else { } else {
Ok(args.remove(0)) Ok(args.remove(0))
}?; }?;

View file

@ -36,11 +36,18 @@ impl fmt::Display for NutError {
"Given hostname cannot be used for a strict SSL connection" "Given hostname cannot be used for a strict SSL connection"
), ),
Self::FeatureNotConfigured => write!(f, "Feature not configured by server"), Self::FeatureNotConfigured => write!(f, "Feature not configured by server"),
Self::Generic(msg) => write!(f, "Internal client error: {}", msg), Self::Generic(msg) => write!(f, "Client error: {}", msg),
} }
} }
} }
impl NutError {
/// Constructs a generic rups error.
pub fn generic<T: ToString>(message: T) -> Self {
Self::Generic(message.to_string())
}
}
impl std::error::Error for NutError {} impl std::error::Error for NutError {}
/// Encapsulation for errors emitted by the client library. /// Encapsulation for errors emitted by the client library.
@ -52,6 +59,13 @@ pub enum ClientError {
Nut(NutError), Nut(NutError),
} }
impl ClientError {
/// Constructs a generic rups error.
pub fn generic<T: ToString>(message: T) -> Self {
NutError::generic(message.to_string()).into()
}
}
impl fmt::Display for ClientError { impl fmt::Display for ClientError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {

View file

@ -7,6 +7,7 @@
pub use config::*; pub use config::*;
pub use error::*; pub use error::*;
pub use util::*;
pub use var::*; pub use var::*;
/// Blocking client implementation for NUT. /// Blocking client implementation for NUT.
@ -20,4 +21,5 @@ mod config;
mod error; mod error;
#[cfg(feature = "ssl")] #[cfg(feature = "ssl")]
mod ssl; mod ssl;
mod util;
mod var; mod var;

View file

@ -148,7 +148,7 @@ impl TcpConnection {
// Parse args by splitting whitespace, minding quotes for args with multiple words // Parse args by splitting whitespace, minding quotes for args with multiple words
let args = shell_words::split(&raw) let args = shell_words::split(&raw)
.map_err(|e| NutError::Generic(format!("Parsing server response failed: {}", e)))?; .map_err(|e| NutError::generic(format!("Parsing server response failed: {}", e)))?;
Ok(args) Ok(args)
} }

View file

@ -1,17 +1,21 @@
use anyhow::Context;
use std::convert::{TryFrom, TryInto}; use std::convert::{TryFrom, TryInto};
use std::fmt; use std::fmt;
/// The default upsd hostname.
pub const DEFAULT_HOSTNAME: &str = "localhost"; pub const DEFAULT_HOSTNAME: &str = "localhost";
/// The default upsd port.
pub const DEFAULT_PORT: u16 = 3493; pub const DEFAULT_PORT: u16 = 3493;
/// Connection information for a upsd server. /// TCP connection information for a upsd server.
/// ///
/// The upsname is optional depending on context. /// The upsname is optional depending on context.
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct UpsdName<'a> { pub struct UpsdName<'a> {
/// The name of the ups device, if specified.
pub upsname: Option<&'a str>, pub upsname: Option<&'a str>,
/// The hostname of the upsd server.
pub hostname: &'a str, pub hostname: &'a str,
/// The port of the upsd server.
pub port: u16, pub port: u16,
} }
@ -26,9 +30,9 @@ impl<'a> Default for UpsdName<'a> {
} }
impl<'a> TryFrom<&'a str> for UpsdName<'a> { impl<'a> TryFrom<&'a str> for UpsdName<'a> {
type Error = anyhow::Error; type Error = crate::ClientError;
fn try_from(value: &'a str) -> anyhow::Result<UpsdName<'a>> { fn try_from(value: &'a str) -> crate::Result<UpsdName<'a>> {
let mut upsname: Option<&str> = None; let mut upsname: Option<&str> = None;
let mut hostname = DEFAULT_HOSTNAME; let mut hostname = DEFAULT_HOSTNAME;
let mut port = DEFAULT_PORT; let mut port = DEFAULT_PORT;
@ -40,7 +44,7 @@ impl<'a> TryFrom<&'a str> for UpsdName<'a> {
.next() .next()
.unwrap() .unwrap()
.parse::<u16>() .parse::<u16>()
.with_context(|| "invalid port number")?; .map_err(|_| crate::ClientError::generic("Invalid port number"))?;
if prefix.contains('@') { if prefix.contains('@') {
let mut split = prefix.splitn(2, '@'); let mut split = prefix.splitn(2, '@');
upsname = Some(split.next().unwrap()); upsname = Some(split.next().unwrap());
@ -64,13 +68,13 @@ impl<'a> TryFrom<&'a str> for UpsdName<'a> {
} }
} }
impl<'a> TryInto<rups::Host> for UpsdName<'a> { impl<'a> TryInto<crate::Host> for UpsdName<'a> {
type Error = anyhow::Error; type Error = crate::ClientError;
fn try_into(self) -> anyhow::Result<rups::Host> { fn try_into(self) -> crate::Result<crate::Host> {
(self.hostname.to_owned(), self.port) (self.hostname.to_owned(), self.port)
.try_into() .try_into()
.with_context(|| "Invalid hostname/port") .map_err(|_| crate::ClientError::generic("Invalid hostname/port"))
} }
} }

View file

@ -200,17 +200,13 @@ impl TryFrom<&str> for VariableType {
.nth(1) .nth(1)
.map(|s| s.parse().ok()) .map(|s| s.parse().ok())
.flatten() .flatten()
.ok_or_else(|| { .ok_or_else(|| crate::ClientError::generic("Invalid STRING definition"))?;
crate::ClientError::Nut(crate::NutError::Generic(
"Invalid STRING definition".into(),
))
})?;
Ok(Self::String(size)) Ok(Self::String(size))
} else { } else {
Err(crate::ClientError::Nut(crate::NutError::Generic(format!( Err(crate::ClientError::generic(format!(
"Unrecognized variable type: {}", "Unrecognized variable type: {}",
value value
)))) )))
} }
} }
} }

View file

@ -8,9 +8,9 @@ use core::convert::TryInto;
use anyhow::Context; use anyhow::Context;
use clap::{App, Arg}; use clap::{App, Arg};
use crate::parser::UpsdName; use rups::UpsdName;
mod cmd; mod cmd;
mod parser;
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
let args = App::new(clap::crate_name!()) let args = App::new(clap::crate_name!())
@ -72,7 +72,7 @@ fn main() -> anyhow::Result<()> {
) )
.get_matches(); .get_matches();
let server: parser::UpsdName = args.value_of("upsd-server").map_or_else( let server: UpsdName = args.value_of("upsd-server").map_or_else(
|| Ok(UpsdName::default()), || Ok(UpsdName::default()),
|s| s.try_into().with_context(|| "Invalid upsd server name"), |s| s.try_into().with_context(|| "Invalid upsd server name"),
)?; )?;