use core::{
fmt,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
};
use crate::std::{self as std, string::String, vec::Vec};
use super::{Domain, DomainAddress, Host, SocketAddress, parse_utils};
use crate::Protocol;
use rama_core::error::BoxErrorExt as _;
use rama_core::error::{BoxError, ErrorContext};
use rama_utils::macros::generate_set_and_with;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct HostWithPort {
pub host: Host,
pub port: u16,
}
impl HostWithPort {
#[must_use]
#[inline(always)]
pub const fn new(host: Host, port: u16) -> Self {
Self { host, port }
}
#[must_use]
#[inline(always)]
pub const fn local_ipv4(port: u16) -> Self {
Self {
host: Host::LOCALHOST_IPV4,
port,
}
}
#[must_use]
#[inline(always)]
pub const fn local_ipv6(port: u16) -> Self {
Self {
host: Host::LOCALHOST_IPV6,
port,
}
}
#[must_use]
#[inline(always)]
pub const fn default_ipv4(port: u16) -> Self {
Self {
host: Host::DEFAULT_IPV4,
port,
}
}
#[must_use]
#[inline(always)]
pub const fn default_ipv6(port: u16) -> Self {
Self {
host: Host::DEFAULT_IPV6,
port,
}
}
#[must_use]
#[inline(always)]
pub const fn broadcast_ipv4(port: u16) -> Self {
Self {
host: Host::BROADCAST_IPV4,
port,
}
}
#[must_use]
#[inline(always)]
pub const fn example_domain_http() -> Self {
Self::example_domain_with_port(Protocol::HTTP_DEFAULT_PORT)
}
#[must_use]
#[inline(always)]
pub const fn example_domain_https() -> Self {
Self::example_domain_with_port(Protocol::HTTPS_DEFAULT_PORT)
}
#[must_use]
#[inline(always)]
pub const fn example_domain_with_port(port: u16) -> Self {
Self {
host: Host::EXAMPLE_NAME,
port,
}
}
#[must_use]
#[inline(always)]
pub const fn localhost_domain_http() -> Self {
Self::localhost_domain_with_port(Protocol::HTTP_DEFAULT_PORT)
}
#[must_use]
#[inline(always)]
pub const fn localhost_domain_https() -> Self {
Self::localhost_domain_with_port(Protocol::HTTPS_DEFAULT_PORT)
}
#[must_use]
#[inline(always)]
pub const fn localhost_domain_with_port(port: u16) -> Self {
Self {
host: Host::LOCALHOST_NAME,
port,
}
}
generate_set_and_with! {
pub fn host(mut self, host: impl Into<Host>) -> Self {
self.host = host.into();
self
}
}
generate_set_and_with! {
pub fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
}
}
impl From<(Domain, u16)> for HostWithPort {
#[inline]
fn from((domain, port): (Domain, u16)) -> Self {
(Host::Name(domain), port).into()
}
}
impl From<(IpAddr, u16)> for HostWithPort {
#[inline]
fn from((ip, port): (IpAddr, u16)) -> Self {
(Host::Address(ip), port).into()
}
}
impl From<(Ipv4Addr, u16)> for HostWithPort {
#[inline]
fn from((ip, port): (Ipv4Addr, u16)) -> Self {
(Host::Address(IpAddr::V4(ip)), port).into()
}
}
impl From<([u8; 4], u16)> for HostWithPort {
#[inline]
fn from((ip, port): ([u8; 4], u16)) -> Self {
(Host::Address(IpAddr::V4(ip.into())), port).into()
}
}
impl From<(Ipv6Addr, u16)> for HostWithPort {
#[inline]
fn from((ip, port): (Ipv6Addr, u16)) -> Self {
(Host::Address(IpAddr::V6(ip)), port).into()
}
}
impl From<([u8; 16], u16)> for HostWithPort {
#[inline]
fn from((ip, port): ([u8; 16], u16)) -> Self {
(Host::Address(IpAddr::V6(ip.into())), port).into()
}
}
impl From<(Host, u16)> for HostWithPort {
fn from((host, port): (Host, u16)) -> Self {
Self { host, port }
}
}
impl From<HostWithPort> for Host {
fn from(hwp: HostWithPort) -> Self {
hwp.host
}
}
impl From<SocketAddr> for HostWithPort {
fn from(addr: SocketAddr) -> Self {
Self {
host: Host::Address(addr.ip()),
port: addr.port(),
}
}
}
impl From<&SocketAddr> for HostWithPort {
fn from(addr: &SocketAddr) -> Self {
Self {
host: Host::Address(addr.ip()),
port: addr.port(),
}
}
}
impl From<SocketAddress> for HostWithPort {
fn from(addr: SocketAddress) -> Self {
let SocketAddress { ip_addr, port } = addr;
Self {
host: Host::Address(ip_addr),
port,
}
}
}
impl From<&SocketAddress> for HostWithPort {
fn from(addr: &SocketAddress) -> Self {
Self {
host: Host::Address(addr.ip_addr),
port: addr.port,
}
}
}
impl From<DomainAddress> for HostWithPort {
fn from(addr: DomainAddress) -> Self {
let DomainAddress { domain, port } = addr;
Self::from((domain, port))
}
}
impl fmt::Display for HostWithPort {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.host {
Host::Name(domain) => write!(f, "{}:{}", domain, self.port),
Host::Address(ip) => match ip {
IpAddr::V4(ip) => write!(f, "{}:{}", ip, self.port),
IpAddr::V6(ip) => write!(f, "[{}]:{}", ip, self.port),
},
Host::Uninterpreted(host) => write!(f, "{}:{}", host, self.port),
}
}
}
impl core::str::FromStr for HostWithPort {
type Err = BoxError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s)
}
}
impl TryFrom<String> for HostWithPort {
type Error = BoxError;
fn try_from(s: String) -> Result<Self, Self::Error> {
s.as_str().try_into()
}
}
impl TryFrom<&str> for HostWithPort {
type Error = BoxError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let (host, port) = parse_utils::split_port_from_str(s)?;
let host = Host::try_from(host).context("parse host from host-with-port")?;
match host {
Host::Address(IpAddr::V6(_)) if !s.starts_with('[') => Err(BoxError::from_static_str(
"missing brackets for IPv6 address with port (in host-with-port)",
)),
_ => Ok(Self { host, port }),
}
}
}
impl TryFrom<Vec<u8>> for HostWithPort {
type Error = BoxError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let s = String::from_utf8(bytes).context("parse host-with-port from bytes")?;
s.try_into()
}
}
impl TryFrom<&[u8]> for HostWithPort {
type Error = BoxError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let s = core::str::from_utf8(bytes).context("parse host-with-port from bytes")?;
s.try_into()
}
}
use rama_utils::macros::serde_str::impl_serde_str;
impl_serde_str!(display HostWithPort);
#[cfg(test)]
mod tests {
use super::*;
#[expect(clippy::needless_pass_by_value)]
fn assert_eq(s: &str, host_with_port: HostWithPort, host: &str, port: u16) {
assert_eq!(host_with_port.host, host, "parsing: {s}");
assert_eq!(host_with_port.port, port, "parsing: {s}");
}
#[test]
fn test_parse_valid() {
for (s, (expected_host, expected_port)) in [
("example.com:80", ("example.com", 80)),
("[::1]:80", ("::1", 80)),
("127.0.0.1:80", ("127.0.0.1", 80)),
(
"[2001:db8:3333:4444:5555:6666:7777:8888]:80",
("2001:db8:3333:4444:5555:6666:7777:8888", 80),
),
] {
let msg = format!("parsing '{s}'");
assert_eq(s, s.parse().expect(&msg), expected_host, expected_port);
assert_eq(s, s.try_into().expect(&msg), expected_host, expected_port);
assert_eq(
s,
s.to_owned().try_into().expect(&msg),
expected_host,
expected_port,
);
assert_eq(
s,
s.as_bytes().try_into().expect(&msg),
expected_host,
expected_port,
);
assert_eq(
s,
s.as_bytes().to_vec().try_into().expect(&msg),
expected_host,
expected_port,
);
}
}
#[test]
fn test_parse_invalid() {
for s in [
"",
"-",
".",
":",
":80",
"-.",
".-",
"::1",
"127.0.0.1",
"[::1]",
"2001:db8:3333:4444:5555:6666:7777:8888",
"[2001:db8:3333:4444:5555:6666:7777:8888]",
"example.com",
"example.com:",
"example.com:-1",
"example.com:999999",
"example:com",
"[127.0.0.1]:80",
"2001:db8:3333:4444:5555:6666:7777:8888:80",
] {
let msg = format!("parsing '{s}'");
assert!(s.parse::<HostWithPort>().is_err(), "{msg}");
assert!(HostWithPort::try_from(s).is_err(), "{msg}");
assert!(HostWithPort::try_from(s.to_owned()).is_err(), "{msg}");
assert!(HostWithPort::try_from(s.as_bytes()).is_err(), "{msg}");
assert!(
HostWithPort::try_from(s.as_bytes().to_vec()).is_err(),
"{msg}"
);
}
}
#[test]
fn test_parse_display() {
for (s, expected) in [
("example.com:80", "example.com:80"),
("[::1]:80", "[::1]:80"),
("127.0.0.1:80", "127.0.0.1:80"),
] {
let msg = format!("parsing '{s}'");
let host_with_port: HostWithPort = s.parse().expect(&msg);
assert_eq!(host_with_port.to_string(), expected, "{msg}");
}
}
#[test]
fn host_with_port_roundtrips_pct_encoded_reg_name() {
let host = crate::uri::Uri::parse_authority_form("exa%6Dple.com:443")
.unwrap()
.host()
.unwrap()
.into_owned();
let hwp = HostWithPort::new(host, 443);
let round: HostWithPort = hwp.to_string().parse().expect("roundtrip");
assert_eq!(hwp, round);
}
#[test]
fn host_with_port_roundtrips_bracketed_ipvfuture() {
let host = crate::uri::Uri::parse_authority_form("[v1.fe80::a]:443")
.unwrap()
.host()
.unwrap()
.into_owned();
let hwp = HostWithPort::new(host, 443);
let round: HostWithPort = hwp.to_string().parse().expect("roundtrip");
assert_eq!(hwp, round);
}
}