nut_client/
config.rs

1use core::fmt;
2use std::convert::{TryFrom, TryInto};
3use std::net::{SocketAddr, ToSocketAddrs};
4use std::time::Duration;
5
6use crate::ClientError;
7
8/// A host specification.
9#[derive(Clone, Debug)]
10pub enum Host {
11    /// A TCP hostname, and address (IP + port).
12    Tcp(TcpHost),
13    // TODO: Support Unix socket streams.
14}
15
16impl Host {
17    /// Returns the hostname as given, if any.
18    pub fn hostname(&self) -> Option<String> {
19        match self {
20            Host::Tcp(host) => Some(host.hostname.to_owned()),
21            // _ => None,
22        }
23    }
24}
25
26impl Default for Host {
27    fn default() -> Self {
28        (String::from("localhost"), 3493)
29            .try_into()
30            .expect("Failed to parse local hostname; this is a bug.")
31    }
32}
33
34impl From<SocketAddr> for Host {
35    fn from(addr: SocketAddr) -> Self {
36        let hostname = addr.ip().to_string();
37        Self::Tcp(TcpHost { hostname, addr })
38    }
39}
40
41/// A TCP address, preserving the original DNS hostname if any.
42#[derive(Clone, Debug)]
43pub struct TcpHost {
44    pub(crate) hostname: String,
45    pub(crate) addr: SocketAddr,
46}
47
48impl TryFrom<(String, u16)> for Host {
49    type Error = ClientError;
50
51    fn try_from(hostname_port: (String, u16)) -> Result<Self, Self::Error> {
52        let (hostname, _) = hostname_port.clone();
53        let addr = hostname_port
54            .to_socket_addrs()
55            .map_err(ClientError::Io)?
56            .next()
57            .ok_or_else(|| {
58                ClientError::Io(std::io::Error::new(
59                    std::io::ErrorKind::AddrNotAvailable,
60                    "no address given",
61                ))
62            })?;
63        Ok(Host::Tcp(TcpHost { hostname, addr }))
64    }
65}
66
67/// An authentication mechanism.
68#[derive(Clone)]
69pub struct Auth {
70    /// The username of the user to login as.
71    pub(crate) username: String,
72    /// Optional password assigned to the remote user.
73    pub(crate) password: Option<String>,
74}
75
76impl Auth {
77    /// Initializes authentication credentials with a username, and optionally a password.
78    pub fn new(username: String, password: Option<String>) -> Self {
79        Auth { username, password }
80    }
81}
82
83impl fmt::Debug for Auth {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        f.debug_struct("Auth")
86            .field("username", &self.username)
87            .field("password", &self.password.as_ref().map(|_| "(redacted)"))
88            .finish()
89    }
90}
91
92/// Configuration for connecting to a remote NUT server.
93#[derive(Clone, Debug)]
94pub struct Config {
95    pub(crate) host: Host,
96    pub(crate) auth: Option<Auth>,
97    pub(crate) timeout: Duration,
98    pub(crate) ssl: bool,
99    pub(crate) ssl_insecure: bool,
100    pub(crate) debug: bool,
101}
102
103impl Config {
104    /// Creates a connection configuration.
105    pub fn new(
106        host: Host,
107        auth: Option<Auth>,
108        timeout: Duration,
109        ssl: bool,
110        ssl_insecure: bool,
111        debug: bool,
112    ) -> Self {
113        Config {
114            host,
115            auth,
116            timeout,
117            ssl,
118            ssl_insecure,
119            debug,
120        }
121    }
122}
123
124/// A builder for [`Config`].
125#[derive(Clone, Debug, Default)]
126pub struct ConfigBuilder {
127    host: Option<Host>,
128    auth: Option<Auth>,
129    timeout: Option<Duration>,
130    ssl: Option<bool>,
131    ssl_insecure: Option<bool>,
132    debug: Option<bool>,
133}
134
135impl ConfigBuilder {
136    /// Initializes an empty builder for [`Config`].
137    pub fn new() -> Self {
138        ConfigBuilder::default()
139    }
140
141    /// Sets the connection host, such as the TCP address and port.
142    pub fn with_host(mut self, host: Host) -> Self {
143        self.host = Some(host);
144        self
145    }
146
147    /// Sets the optional authentication parameters.
148    pub fn with_auth(mut self, auth: Option<Auth>) -> Self {
149        self.auth = auth;
150        self
151    }
152
153    /// Sets the network connection timeout. This may be ignored by non-network
154    /// connections, such as Unix domain sockets.
155    pub fn with_timeout(mut self, timeout: Duration) -> Self {
156        self.timeout = Some(timeout);
157        self
158    }
159
160    /// Enables SSL on the connection.
161    ///
162    /// This will enable strict SSL verification (including hostname),
163    /// unless `.with_insecure_ssl` is also set to `true`.
164    #[cfg(feature = "ssl")]
165    pub fn with_ssl(mut self, ssl: bool) -> Self {
166        self.ssl = Some(ssl);
167        self
168    }
169
170    /// Turns off SSL verification.
171    ///
172    /// Note: you must still use `.with_ssl(true)` to turn on SSL.
173    #[cfg(feature = "ssl")]
174    pub fn with_insecure_ssl(mut self, ssl_insecure: bool) -> Self {
175        self.ssl_insecure = Some(ssl_insecure);
176        self
177    }
178
179    /// Enables debugging network calls by printing to stderr.
180    pub fn with_debug(mut self, debug: bool) -> Self {
181        self.debug = Some(debug);
182        self
183    }
184
185    /// Builds the configuration with this builder.
186    pub fn build(self) -> Config {
187        Config::new(
188            self.host.unwrap_or_default(),
189            self.auth,
190            self.timeout.unwrap_or_else(|| Duration::from_secs(5)),
191            self.ssl.unwrap_or(false),
192            self.ssl_insecure.unwrap_or(false),
193            self.debug.unwrap_or(false),
194        )
195    }
196}