use std::fmt;
use std::io;
use std::net::{SocketAddr, ToSocketAddrs};
use std::vec::IntoIter;
use crate::errors::{Error, Result};
use crate::net::parser::Parser;
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Host {
pub name: String,
pub tls_name: Option<String>,
pub port: u16,
}
impl Host {
pub fn new(name: &str, port: u16) -> Self {
Host {
name: name.to_string(),
tls_name: None,
port,
}
}
pub fn new_tls(name: &str, tls_name: &str, port: u16) -> Self {
let tls_name = match tls_name.trim().len() {
0 => None,
_ => Some(tls_name.into()),
};
Host {
name: name.to_string(),
tls_name,
port,
}
}
pub fn address(&self) -> String {
format!("{}:{}", self.name, self.port)
}
}
impl ToSocketAddrs for Host {
type Iter = IntoIter<SocketAddr>;
fn to_socket_addrs(&self) -> io::Result<IntoIter<SocketAddr>> {
(self.name.as_str(), self.port).to_socket_addrs()
}
}
impl fmt::Display for Host {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.name, self.port)
}
}
pub trait ToHosts {
fn to_hosts(&self) -> Result<Vec<Host>>;
}
impl ToHosts for Vec<Host> {
fn to_hosts(&self) -> Result<Vec<Host>> {
Ok(self.clone())
}
}
impl ToHosts for String {
fn to_hosts(&self) -> Result<Vec<Host>> {
let parser = Parser::new(self, 3000);
parser.read_hosts().map_err(|e| {
e.wrap(Error::InvalidArgument(format!(
"Invalid hosts list: '{self}'"
)))
})
}
}
impl ToHosts for &str {
fn to_hosts(&self) -> Result<Vec<Host>> {
(*self).to_string().to_hosts()
}
}
#[cfg(test)]
mod tests {
use super::{Host, ToHosts};
const CLOUD_TLS_NAME: &str = "6072bb5c-b902-4cbd-888c-cc63e71192f7.amstest.internal";
fn assert_tls_host(seed: &str, expected_name: &str, expected_tls: &str, expected_port: u16) {
let hosts = seed.to_hosts().unwrap_or_else(|e| {
panic!(
"expected `{}` to parse as host:tls_name:port, got: {:?}",
seed, e
)
});
assert_eq!(
hosts,
vec![Host::new_tls(expected_name, expected_tls, expected_port)],
"unexpected parse for `{seed}`"
);
}
#[test]
fn tls_name_letter_first_parses_host_tls_port() {
assert_tls_host(
"node.example.com:abc.example.com:4000",
"node.example.com",
"abc.example.com",
4000,
);
}
#[test]
fn tls_name_digit_first_parses_host_tls_port() {
assert_tls_host(
"node.example.com:6abc.example.com:4000",
"node.example.com",
"6abc.example.com",
4000,
);
}
#[test]
fn tls_name_cloud_uuid_hostname_parses_host_tls_port() {
let seed = format!("{CLOUD_TLS_NAME}:{CLOUD_TLS_NAME}:4000");
assert_tls_host(&seed, CLOUD_TLS_NAME, CLOUD_TLS_NAME, 4000);
}
#[test]
fn host_port_without_tls_name_still_parses() {
let hosts = "node.example.com:4000".to_hosts().unwrap();
assert_eq!(hosts, vec![Host::new("node.example.com", 4000)]);
}
#[test]
fn to_hosts() {
assert_eq!(
vec![Host::new("foo", 3000)],
String::from("foo").to_hosts().unwrap()
);
assert_eq!(vec![Host::new("foo", 3000)], "foo".to_hosts().unwrap());
assert_eq!(vec![Host::new("foo", 1234)], "foo:1234".to_hosts().unwrap());
assert_eq!(
vec![Host::new("foo", 1234), Host::new("bar", 1234)],
"foo:1234,bar:1234".to_hosts().unwrap()
);
}
}