use super::{Domain, Host, ParseAsciiHostError, ParseHostError};
use core::{
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
str::FromStr,
};
#[derive(Debug, thiserror::Error)]
pub enum ParseHostAddrError {
#[error(transparent)]
Host(#[from] ParseHostError),
#[error(transparent)]
Port(#[from] core::num::ParseIntError),
}
impl ParseHostAddrError {
#[inline]
const fn host() -> Self {
Self::Host(ParseHostError(()))
}
}
#[derive(Debug, thiserror::Error)]
pub enum ParseAsciiHostAddrError {
#[error(transparent)]
Host(#[from] ParseAsciiHostError),
#[error(transparent)]
Port(#[from] core::num::ParseIntError),
}
impl ParseAsciiHostAddrError {
#[inline]
const fn host() -> Self {
Self::Host(ParseAsciiHostError(()))
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HostAddr<S> {
pub(super) host: Host<S>,
pub(super) port: Option<u16>,
}
#[cfg(feature = "cheap-clone")]
impl<S: cheap_clone::CheapClone> cheap_clone::CheapClone for HostAddr<S> {}
impl<S> core::fmt::Display for HostAddr<S>
where
S: core::fmt::Display,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match (self.host.is_ipv6(), self.port) {
(true, Some(port)) => write!(f, "[{}]:{}", self.host, port),
(_, Some(port)) => write!(f, "{}:{}", self.host, port),
_ => write!(f, "{}", self.host),
}
}
}
impl<S> From<Domain<S>> for HostAddr<S> {
fn from(domain: Domain<S>) -> Self {
Self::from_domain(domain)
}
}
impl<S> From<(Domain<S>, u16)> for HostAddr<S> {
fn from((host, port): (Domain<S>, u16)) -> Self {
Self::from_domain(host).with_port(port)
}
}
impl<S> From<(u16, Domain<S>)> for HostAddr<S> {
fn from((port, host): (u16, Domain<S>)) -> Self {
Self::from_domain(host).with_port(port)
}
}
impl<S> From<(IpAddr, u16)> for HostAddr<S> {
fn from((host, port): (IpAddr, u16)) -> Self {
Self::from_ip_addr(host).with_port(port)
}
}
impl<S> From<(u16, IpAddr)> for HostAddr<S> {
fn from((port, host): (u16, IpAddr)) -> Self {
Self::from_ip_addr(host).with_port(port)
}
}
impl<S> From<(Ipv4Addr, u16)> for HostAddr<S> {
fn from((host, port): (Ipv4Addr, u16)) -> Self {
Self::from((IpAddr::V4(host), port))
}
}
impl<S> From<(Ipv6Addr, u16)> for HostAddr<S> {
fn from((host, port): (Ipv6Addr, u16)) -> Self {
Self::from((port, IpAddr::V6(host)))
}
}
impl<S> From<SocketAddr> for HostAddr<S> {
fn from(addr: SocketAddr) -> Self {
Self::from_sock_addr(addr)
}
}
impl<S> From<SocketAddrV4> for HostAddr<S> {
fn from(addr: SocketAddrV4) -> Self {
Self::from_sock_addr(SocketAddr::V4(addr))
}
}
impl<S> From<SocketAddrV6> for HostAddr<S> {
fn from(addr: SocketAddrV6) -> Self {
Self::from_sock_addr(SocketAddr::V6(addr))
}
}
impl<S> From<IpAddr> for HostAddr<S> {
fn from(ip: IpAddr) -> Self {
Self::from_ip_addr(ip)
}
}
impl<S> From<Ipv4Addr> for HostAddr<S> {
fn from(ip: Ipv4Addr) -> Self {
Self::from(IpAddr::V4(ip))
}
}
impl<S> From<Ipv6Addr> for HostAddr<S> {
fn from(ip: Ipv6Addr) -> Self {
Self::from(IpAddr::V6(ip))
}
}
impl<S> From<HostAddr<S>> for HostAddr<Domain<S>> {
#[inline]
fn from(value: HostAddr<S>) -> Self {
let (host, port) = value.into_components();
match host {
Host::Domain(domain) => Self {
host: Host::Domain(Domain::new_unchecked(domain)),
port,
},
Host::Ip(ip) => Self {
host: Host::Ip(ip),
port,
},
}
}
}
impl<'a, S> From<&'a HostAddr<S>> for HostAddr<&'a Domain<S>> {
#[inline]
fn from(value: &'a HostAddr<S>) -> Self {
let (host, port) = value.as_ref().into_components();
match host {
Host::Domain(domain) => Self {
host: Host::Domain(Domain::from_ref_unchecked(domain)),
port,
},
Host::Ip(ip) => Self {
host: Host::Ip(ip),
port,
},
}
}
}
impl<S> HostAddr<S> {
#[inline]
pub const fn new(host: Host<S>) -> Self {
Self { host, port: None }
}
#[inline]
pub fn from_domain(domain: Domain<S>) -> Self {
Self {
host: Host::Domain(domain.0),
port: None,
}
}
#[inline]
pub const fn from_ip_addr(ip: IpAddr) -> Self {
Self {
host: Host::Ip(ip),
port: None,
}
}
#[inline]
pub const fn from_sock_addr(addr: SocketAddr) -> Self {
Self {
host: Host::Ip(addr.ip()),
port: Some(addr.port()),
}
}
#[inline]
pub const fn host(&self) -> &Host<S> {
&self.host
}
#[inline]
pub const fn ip(&self) -> Option<&IpAddr> {
self.host.ip()
}
#[inline]
pub const fn port(&self) -> Option<u16> {
self.port
}
#[inline]
pub const fn set_port(&mut self, port: u16) -> &mut Self {
self.port = Some(port);
self
}
#[inline]
pub const fn maybe_port(&mut self, port: Option<u16>) -> &mut Self {
self.port = port;
self
}
#[inline]
pub const fn maybe_with_port(mut self, port: Option<u16>) -> Self {
self.port = port;
self
}
#[inline]
pub const fn with_port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
#[inline]
pub const fn with_default_port(mut self, default: u16) -> Self {
if self.port.is_none() {
self.port = Some(default);
}
self
}
#[inline]
pub const fn clear_port(&mut self) -> &mut Self {
self.port = None;
self
}
#[inline]
pub const fn has_port(&self) -> bool {
self.port.is_some()
}
#[inline]
pub fn set_host(&mut self, host: Host<S>) -> &mut Self {
self.host = host;
self
}
#[inline]
pub fn with_host(mut self, host: Host<S>) -> Self {
self.host = host;
self
}
#[inline]
pub const fn is_ip(&self) -> bool {
self.host.is_ip()
}
#[inline]
pub const fn is_ipv4(&self) -> bool {
self.host.is_ipv4()
}
#[inline]
pub const fn is_ipv6(&self) -> bool {
self.host.is_ipv6()
}
#[inline]
pub const fn is_domain(&self) -> bool {
self.host.is_domain()
}
#[inline]
pub fn is_localhost(&self) -> bool
where
S: AsRef<str>,
{
match &self.host {
Host::Ip(IpAddr::V4(ip)) => ip.is_loopback(),
Host::Ip(IpAddr::V6(ip)) => ip.is_loopback(),
Host::Domain(domain) => {
let s = domain.as_ref();
s.eq_ignore_ascii_case("localhost") || s.eq_ignore_ascii_case("localhost.")
}
}
}
#[inline]
pub const fn to_socket_addr(&self) -> Option<SocketAddr> {
match (&self.host, self.port) {
(Host::Ip(ip), Some(port)) => Some(SocketAddr::new(*ip, port)),
_ => None,
}
}
#[inline]
pub const fn as_ref(&self) -> HostAddr<&S> {
HostAddr {
host: self.host.as_ref(),
port: self.port,
}
}
#[inline]
pub fn as_deref(&self) -> HostAddr<&S::Target>
where
S: core::ops::Deref,
{
HostAddr {
host: self.host.as_deref(),
port: self.port,
}
}
#[inline]
pub fn into_components(self) -> (Host<S>, Option<u16>) {
let Self { host, port } = self;
(host, port)
}
#[inline]
pub fn unwrap_domain(self) -> (S, Option<u16>) {
(self.host.unwrap_domain(), self.port)
}
#[inline]
pub fn unwrap_ip(self) -> (IpAddr, Option<u16>) {
(self.host.unwrap_ip(), self.port)
}
}
impl<S> HostAddr<&S> {
#[inline]
pub const fn copied(self) -> HostAddr<S>
where
S: Copy,
{
HostAddr {
host: self.host.copied(),
port: self.port,
}
}
#[inline]
pub fn cloned(self) -> HostAddr<S>
where
S: Clone,
{
HostAddr {
host: self.host.cloned(),
port: self.port,
}
}
}
macro_rules! try_from_str {
($convert:ident($s: ident)) => {{
match try_parse_v6::<S>($s)? {
Some(addr) => Ok(addr),
None => {
let mut parts = $s.splitn(2, ':');
let host = parts.next().ok_or(ParseHostAddrError::host())?.$convert()?;
let port = match parts.next() {
Some(port) => Some(port.parse().map_err(ParseHostAddrError::Port)?),
None => None,
};
Ok(Self { host, port })
}
}
}};
}
impl<'a> HostAddr<&'a str> {
#[inline]
pub fn try_from_ascii_str(input: &'a str) -> Result<Self, ParseAsciiHostAddrError> {
match try_parse_v6(input).map_err(|_| ParseAsciiHostAddrError::host())? {
Some(addr) => Ok(addr),
None => {
if let Ok(ip) = input.parse() {
return Ok(Self::from_ip_addr(ip));
}
let mut parts = input.splitn(2, ':');
let host = Host::try_from_ascii_str(parts.next().ok_or(ParseAsciiHostAddrError::host())?)?;
let port = match parts.next() {
Some(port) => Some(port.parse().map_err(ParseAsciiHostAddrError::Port)?),
None => None,
};
Ok(Self { host, port })
}
}
}
#[inline]
pub const fn as_bytes(&self) -> HostAddr<&'a [u8]> {
HostAddr {
host: self.host.as_bytes(),
port: self.port,
}
}
}
impl<'a> HostAddr<&'a [u8]> {
#[inline]
pub fn try_from_ascii_bytes(input: &'a [u8]) -> Result<Self, ParseAsciiHostAddrError> {
let input_str = simdutf8::basic::from_utf8(input).map_err(|_| ParseAsciiHostError(()))?;
match try_parse_v6(input_str).map_err(|_| ParseAsciiHostAddrError::host())? {
Some(addr) => Ok(addr),
None => {
if let Ok(ip) = IpAddr::from_str(input_str) {
return Ok(Self::from_ip_addr(ip));
}
let mut parts = input_str.splitn(2, ':');
let host = Host::try_from_ascii_bytes(
parts
.next()
.map(|s| s.as_bytes())
.ok_or(ParseAsciiHostAddrError::host())?,
)?;
let port = match parts.next() {
Some(port) => Some(port.parse().map_err(ParseAsciiHostAddrError::Port)?),
None => None,
};
Ok(Self { host, port })
}
}
}
#[inline]
pub const fn as_str(&self) -> HostAddr<&'a str> {
HostAddr {
host: self.host.as_str(),
port: self.port,
}
}
}
impl<S> FromStr for HostAddr<S>
where
Domain<S>: FromStr,
{
type Err = ParseHostAddrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
try_from_str!(parse(s))
}
}
impl<'a, S> TryFrom<&'a str> for HostAddr<S>
where
Domain<S>: TryFrom<&'a str>,
{
type Error = ParseHostAddrError;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
try_from_str!(try_into(s))
}
}
impl<'a, S> TryFrom<(&'a str, u16)> for HostAddr<S>
where
Domain<S>: TryFrom<&'a str>,
{
type Error = ParseHostAddrError;
fn try_from((s, port): (&'a str, u16)) -> Result<Self, Self::Error> {
let host = Host::try_from(s)?;
Ok(Self {
host,
port: Some(port),
})
}
}
fn try_parse_v6<S>(s: &str) -> Result<Option<HostAddr<S>>, ParseHostAddrError> {
if let Some(without_prefix) = s.strip_prefix('[') {
if let Some(ip) = without_prefix.strip_suffix(']') {
return ip
.parse()
.map_err(|_| ParseHostAddrError::host())
.map(|addr| Some(HostAddr::from_ip_addr(addr)));
}
let mut parts = s.rsplitn(2, ':');
let port = parts.next();
let host = parts.next();
match (host, port) {
(Some(host), Some(_)) if host.ends_with("]") => {
return s
.parse()
.map_err(|_| ParseHostAddrError::host())
.map(|addr| Some(HostAddr::from_sock_addr(addr)));
}
_ => return Err(ParseHostAddrError::host()),
}
}
Ok(None)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hostaddr_parsing() {
#[cfg(any(feature = "std", feature = "alloc"))]
{
use std::string::String;
let host: HostAddr<String> = "example.com".parse().unwrap();
assert_eq!("example.com", host.as_ref().host().unwrap_domain());
let host: HostAddr<String> = "example.com:8080".parse().unwrap();
assert_eq!("example.com", host.as_ref().host().unwrap_domain());
assert_eq!(Some(8080), host.port());
let host: HostAddr<String> = "127.0.0.1:8080".parse().unwrap();
assert_eq!(
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
host.as_ref().host().unwrap_ip()
);
assert_eq!(Some(8080), host.port());
let host: HostAddr<String> = "[::1]:8080".parse().unwrap();
assert_eq!(
IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
host.as_ref().host().unwrap_ip()
);
assert_eq!(Some(8080), host.port());
}
let host: HostAddr<&str> = HostAddr::try_from_ascii_str("[::1]").unwrap();
assert_eq!(
IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
host.as_ref().host().unwrap_ip()
);
let host: HostAddr<&[u8]> = HostAddr::try_from_ascii_bytes(b"[::1]").unwrap();
assert_eq!(
IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
host.as_ref().host().unwrap_ip()
);
let host: HostAddr<&[u8]> = HostAddr::try_from_ascii_bytes(b"::1").unwrap();
assert_eq!(
IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
host.as_ref().host().unwrap_ip()
);
let host: HostAddr<&str> = HostAddr::try_from_ascii_str("::1").unwrap();
assert_eq!(
IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
host.as_ref().host().unwrap_ip()
);
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[test]
fn test_hostaddr_try_into() {
use std::string::String;
let host: HostAddr<String> = "example.com".try_into().unwrap();
assert_eq!("example.com", host.as_ref().host().unwrap_domain());
let host: HostAddr<String> = "example.com:8080".try_into().unwrap();
assert_eq!("example.com", host.as_ref().host().unwrap_domain());
assert_eq!(Some(8080), host.port());
let host: HostAddr<String> = "127.0.0.1:8080".try_into().unwrap();
assert_eq!(
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
host.as_ref().host().unwrap_ip()
);
assert_eq!(Some(8080), host.port());
let host: HostAddr<String> = "[::1]:8080".try_into().unwrap();
assert_eq!(
IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
host.as_ref().host().unwrap_ip()
);
assert_eq!(Some(8080), host.port());
}
#[test]
fn negative_try_parse_v6() {
let _ = try_parse_v6::<&str>("[a]").unwrap_err();
let _ = try_parse_v6::<&str>("[a]:8080").unwrap_err();
let _ = try_parse_v6::<&str>("[a:8080").unwrap_err();
}
#[test]
fn negative_try_from_ascii_bytes() {
let err = HostAddr::try_from_ascii_bytes(b"example.com:aaa").unwrap_err();
assert!(matches!(err, ParseAsciiHostAddrError::Port(_)));
}
#[test]
fn negative_try_from_ascii_str() {
let err = HostAddr::try_from_ascii_str("example.com:aaa").unwrap_err();
assert!(matches!(err, ParseAsciiHostAddrError::Port(_)));
}
#[test]
#[cfg(feature = "std")]
fn ipv6_display_roundtrip() {
let addr = HostAddr::<&str>::from_sock_addr("[::1]:8080".parse().unwrap());
assert_eq!(addr.to_string(), "[::1]:8080");
let addr = HostAddr::<&str>::from_ip_addr("::1".parse().unwrap());
assert_eq!(addr.to_string(), "::1");
let addr = HostAddr::<&str>::from_sock_addr("127.0.0.1:3000".parse().unwrap());
assert_eq!(addr.to_string(), "127.0.0.1:3000");
#[cfg(any(feature = "std", feature = "alloc"))]
{
use std::string::String;
let addr = HostAddr::<String>::from_sock_addr("[::1]:443".parse().unwrap());
let displayed = addr.to_string();
let reparsed: HostAddr<String> = displayed.parse().unwrap();
assert_eq!(reparsed.port(), Some(443));
assert!(reparsed.is_ipv6());
}
}
}