use super::EndpointError;
use crate::ZmqError;
use std::convert::TryFrom;
use std::fmt;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum Host {
Ipv4(Ipv4Addr),
Ipv6(Ipv6Addr),
Domain(String),
}
impl fmt::Display for Host {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match self {
Host::Ipv4(addr) => write!(f, "{}", addr),
Host::Ipv6(addr) => write!(f, "{}", addr),
Host::Domain(name) => write!(f, "{}", name),
}
}
}
impl TryFrom<Host> for IpAddr {
type Error = ZmqError;
fn try_from(h: Host) -> Result<Self, Self::Error> {
match h {
Host::Ipv4(a) => Ok(IpAddr::V4(a)),
Host::Ipv6(a) => Ok(IpAddr::V6(a)),
Host::Domain(_) => Err(ZmqError::Other("Host was neither Ipv4 nor Ipv6".into())),
}
}
}
impl From<IpAddr> for Host {
fn from(a: IpAddr) -> Self {
match a {
IpAddr::V4(a) => Host::Ipv4(a),
IpAddr::V6(a) => Host::Ipv6(a),
}
}
}
impl From<Ipv4Addr> for Host {
fn from(a: Ipv4Addr) -> Self {
Host::Ipv4(a)
}
}
impl From<Ipv6Addr> for Host {
fn from(a: Ipv6Addr) -> Self {
Host::Ipv6(a)
}
}
impl TryFrom<String> for Host {
type Error = EndpointError;
fn try_from(s: String) -> Result<Self, Self::Error> {
if s.is_empty() {
return Err(EndpointError::Syntax("Host string should not be empty"));
}
if let Ok(addr) = s.parse::<Ipv4Addr>() {
return Ok(Host::Ipv4(addr));
}
let ipv6_substr =
if s.starts_with('[') && s.len() >= 4 && *s.as_bytes().last().unwrap() == b']' {
let substr = &s[1..s.len() - 1];
debug_assert_eq!(substr.len(), s.len() - 2);
substr
} else {
&s
};
if let Ok(addr) = ipv6_substr.parse::<Ipv6Addr>() {
return Ok(Host::Ipv6(addr));
}
Ok(Host::Domain(s))
}
}
impl FromStr for Host {
type Err = EndpointError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_string();
Self::try_from(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn std_ipv6_parse() {
assert_eq!(Ipv6Addr::LOCALHOST, "::1".parse::<Ipv6Addr>().unwrap());
assert!("[::1]".parse::<Ipv6Addr>().is_err());
}
#[test]
fn std_ipv6_display() {
assert_eq!("::1", &Ipv6Addr::LOCALHOST.to_string());
}
#[test]
fn parse_and_display_nobracket_ipv6_same_as_std() {
let valid_addr_strs = vec![
"::1",
"::",
"2001:db8:a::123",
"2001:db8:0:0:0:0:2:1",
"2001:db8::2:1",
];
let invalid_addr_strs = vec!["", "[]", "[:]", ":"];
for valid in valid_addr_strs {
let parsed_std = valid.parse::<Ipv6Addr>().unwrap();
let parsed_host = valid.parse::<Host>().unwrap();
if let Host::Ipv6(parsed_host) = &parsed_host {
assert_eq!(&parsed_std, parsed_host);
} else {
panic!("Did not parse as IPV6!");
}
assert_eq!(parsed_std.to_string(), parsed_host.to_string());
}
for invalid in invalid_addr_strs {
invalid.parse::<Ipv6Addr>().unwrap_err();
let parsed_host = invalid.parse::<Host>();
if parsed_host.is_err() {
continue;
}
let parsed_host = parsed_host.unwrap();
if let Host::Domain(_) = parsed_host {
continue;
}
panic!(
"Expected that \"{}\" would not parse as Ipv6 or Ipv4, but instead it parsed as {:?}",
invalid, parsed_host
);
}
}
#[test]
fn parse_and_display_bracket_ipv6() {
let addr_strs = vec![
"[::1]",
"[::]",
"[2001:db8:a::123]",
"[2001:db8:0:0:0:0:2:1]",
"[2001:db8::2:1]",
];
fn remove_brackets(s: &str) -> &str {
assert!(s.starts_with('['));
assert!(s.ends_with(']'));
let result = &s[1..s.len() - 1];
assert_eq!(result.len(), s.len() - 2);
result
}
for addr_str in addr_strs {
let parsed_host: Host = addr_str.parse().unwrap();
assert!(addr_str.parse::<Ipv6Addr>().is_err());
if let Host::Ipv6(host_ipv6) = parsed_host {
assert_eq!(
host_ipv6,
remove_brackets(addr_str).parse::<Ipv6Addr>().unwrap()
);
assert_eq!(parsed_host.to_string(), host_ipv6.to_string());
} else {
panic!(
"Expected host to parse as Ipv6, but instead got {:?}",
parsed_host
);
}
}
}
}