use crate::errors::{Error, Result};
use crate::Host;
use regex::Regex;
use std::sync::LazyLock;
#[derive(Debug)]
pub struct Parser<'a> {
s: &'a str,
default_port: u16,
}
impl<'a> Parser<'a> {
pub const fn new(s: &'a str, default_port: u16) -> Self {
Parser { s, default_port }
}
pub fn read_hosts(&self) -> Result<Vec<Host>> {
static RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^(((\[(?P<ipv6>(\S+))\])|(?P<ipv4>((\d{1,3})(\.\d{1,3}){3}))|(?P<host>([\S--:]+)))(:(?P<tls>[\S--[\d:]][\S--:]*))?(:(?P<port>(\d{1,5})))?)$")
.unwrap()
});
let mut hosts = Vec::new();
let sne = self.s.split(',');
for s in sne {
if !RE.is_match(s) {
return Err(Error::ClientError(format!("Could not parse `{s}`")));
}
let addrs: (String, Option<String>, String) = RE
.captures(s)
.map(|caps| {
let host = match (caps.name("ipv6"), caps.name("ipv4"), caps.name("host")) {
(Some(ipv6), None, None) => ipv6,
(None, Some(ipv4), None) => ipv4,
(None, None, Some(host)) => host,
_ => unreachable!(),
};
let host = host.as_str().into();
let tls = caps.name("tls").map(|tls| tls.as_str().into());
let port = caps
.name("port")
.map_or_else(|| format!("{}", self.default_port), |p| p.as_str().into());
(host, tls, port)
})
.unwrap();
let (host, tls_name, port) = addrs;
let (host, tls_name, port) = (host, tls_name, port.parse::<u16>()?);
match tls_name {
Some(tls_name) => hosts.push(Host::new_tls(&host, &tls_name, port)),
_ => hosts.push(Host::new(&host, port)),
}
}
Ok(hosts)
}
}
#[cfg(test)]
mod tests {
use super::{Host, Parser};
#[test]
fn read_hosts() {
assert_eq!(
vec![Host::new("foo", 3000)],
Parser::new("foo", 3000).read_hosts().unwrap()
);
assert_eq!(
vec![Host::new_tls("foo", "bar", 3000)],
Parser::new("foo:bar", 3000).read_hosts().unwrap()
);
assert_eq!(
vec![Host::new("foo", 1234)],
Parser::new("foo:1234", 3000).read_hosts().unwrap()
);
assert_eq!(
vec![Host::new_tls("foo", "bar", 1234)],
Parser::new("foo:bar:1234", 3000).read_hosts().unwrap()
);
assert_eq!(
vec![Host::new("foo", 1234), Host::new("bar", 1234)],
Parser::new("foo:1234,bar:1234", 3000).read_hosts().unwrap()
);
assert!(Parser::new("", 3000).read_hosts().is_err());
assert!(Parser::new(",", 3000).read_hosts().is_err());
assert!(Parser::new("foo,", 3000).read_hosts().is_err());
assert!(Parser::new(":", 3000).read_hosts().is_err());
assert!(Parser::new("foo:", 3000).read_hosts().is_err());
assert!(Parser::new("foo:bar:bar", 3000).read_hosts().is_err());
assert!(Parser::new("foo:bar:1234:1234", 3000).read_hosts().is_err());
}
}