use core::{
fmt,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
};
use crate::std::{
self as std,
borrow::{Cow, ToOwned},
string::String,
vec::Vec,
};
use super::{Domain, DomainAddress, Host, SocketAddress};
use crate::address::{HostRef, HostWithOptPort, HostWithPort, OptPort, UserInfo, UserInfoRef};
use rama_core::error::{BoxError, BoxErrorExt as _, ErrorContext};
use rama_utils::macros::generate_set_and_with;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Authority {
pub user_info: Option<UserInfo>,
pub address: HostWithOptPort,
}
impl Authority {
#[must_use]
#[inline(always)]
pub const fn new(addr: HostWithOptPort) -> Self {
Self {
address: addr,
user_info: None,
}
}
#[must_use]
#[inline(always)]
pub fn new_with_user_info(addr: HostWithOptPort, user_info: UserInfo) -> Self {
Self {
address: addr,
user_info: Some(user_info),
}
}
#[must_use]
pub const fn from_static(s: &'static str) -> Self {
Self::new(HostWithOptPort::new(Host::from_static(s)))
}
#[must_use]
#[inline(always)]
pub const fn local_ipv4() -> Self {
Self::new(HostWithOptPort::local_ipv4())
}
#[must_use]
#[inline(always)]
pub const fn local_ipv4_with_port(port: u16) -> Self {
Self::new(HostWithOptPort::local_ipv4_with_port(port))
}
#[must_use]
#[inline(always)]
pub const fn local_ipv6() -> Self {
Self::new(HostWithOptPort::local_ipv6())
}
#[must_use]
#[inline(always)]
pub const fn local_ipv6_with_port(port: u16) -> Self {
Self::new(HostWithOptPort::local_ipv6_with_port(port))
}
#[must_use]
#[inline(always)]
pub const fn default_ipv4() -> Self {
Self::new(HostWithOptPort::default_ipv4())
}
#[must_use]
#[inline(always)]
pub const fn default_ipv4_with_port(port: u16) -> Self {
Self::new(HostWithOptPort::default_ipv4_with_port(port))
}
#[must_use]
#[inline(always)]
pub const fn default_ipv6() -> Self {
Self::new(HostWithOptPort::default_ipv6())
}
#[must_use]
#[inline(always)]
pub const fn default_ipv6_with_port(port: u16) -> Self {
Self::new(HostWithOptPort::default_ipv6_with_port(port))
}
#[must_use]
#[inline(always)]
pub const fn broadcast_ipv4() -> Self {
Self::new(HostWithOptPort::broadcast_ipv4())
}
#[must_use]
#[inline(always)]
pub const fn broadcast_ipv4_with_port(port: u16) -> Self {
Self::new(HostWithOptPort::broadcast_ipv4_with_port(port))
}
#[must_use]
#[inline(always)]
pub const fn example_domain() -> Self {
Self::new(HostWithOptPort::example_domain())
}
#[must_use]
#[inline(always)]
pub const fn example_domain_http() -> Self {
Self::new(HostWithOptPort::example_domain_http())
}
#[must_use]
#[inline(always)]
pub const fn example_domain_https() -> Self {
Self::new(HostWithOptPort::example_domain_https())
}
#[must_use]
#[inline(always)]
pub const fn example_domain_with_port(port: u16) -> Self {
Self::new(HostWithOptPort::example_domain_with_port(port))
}
#[must_use]
#[inline(always)]
pub const fn localhost_domain() -> Self {
Self::new(HostWithOptPort::localhost_domain())
}
#[must_use]
#[inline(always)]
pub const fn localhost_domain_http() -> Self {
Self::new(HostWithOptPort::localhost_domain_http())
}
#[must_use]
#[inline(always)]
pub const fn localhost_domain_https() -> Self {
Self::new(HostWithOptPort::localhost_domain_https())
}
#[must_use]
#[inline(always)]
pub const fn localhost_domain_with_port(port: u16) -> Self {
Self::new(HostWithOptPort::localhost_domain_with_port(port))
}
generate_set_and_with! {
pub fn host(mut self, host: impl Into<Host>) -> Self {
self.address.set_host(host.into());
self
}
}
generate_set_and_with! {
pub fn port(mut self, port: impl Into<OptPort>) -> Self {
self.address.port = port.into();
self
}
}
#[must_use]
#[inline]
pub const fn port_u16(&self) -> Option<u16> {
self.address.port.as_u16()
}
generate_set_and_with! {
pub fn user_info(mut self, user_info: Option<UserInfo>) -> Self {
self.user_info = user_info;
self
}
}
#[must_use]
#[inline]
pub fn view(&self) -> AuthorityRef<'_> {
AuthorityRef::from(self)
}
}
impl<'a> From<&'a Authority> for AuthorityRef<'a> {
fn from(a: &'a Authority) -> Self {
Self::new(
a.user_info.as_ref().map(UserInfoRef::from),
HostRef::from(&a.address.host),
a.address.port,
)
}
}
impl From<(Domain, u16)> for Authority {
#[inline(always)]
fn from((domain, port): (Domain, u16)) -> Self {
(Host::Name(domain), port).into()
}
}
impl From<(IpAddr, u16)> for Authority {
#[inline(always)]
fn from((ip, port): (IpAddr, u16)) -> Self {
(Host::Address(ip), port).into()
}
}
impl From<(Ipv4Addr, u16)> for Authority {
#[inline(always)]
fn from((ip, port): (Ipv4Addr, u16)) -> Self {
(Host::Address(IpAddr::V4(ip)), port).into()
}
}
impl From<([u8; 4], u16)> for Authority {
#[inline(always)]
fn from((ip, port): ([u8; 4], u16)) -> Self {
(Host::Address(IpAddr::V4(ip.into())), port).into()
}
}
impl From<(Ipv6Addr, u16)> for Authority {
#[inline(always)]
fn from((ip, port): (Ipv6Addr, u16)) -> Self {
(Host::Address(IpAddr::V6(ip)), port).into()
}
}
impl From<([u8; 16], u16)> for Authority {
#[inline(always)]
fn from((ip, port): ([u8; 16], u16)) -> Self {
(Host::Address(IpAddr::V6(ip.into())), port).into()
}
}
impl From<Host> for Authority {
#[inline(always)]
fn from(host: Host) -> Self {
Self::new(HostWithOptPort::new(host))
}
}
impl From<Domain> for Authority {
#[inline(always)]
fn from(domain: Domain) -> Self {
Host::Name(domain).into()
}
}
impl From<IpAddr> for Authority {
#[inline(always)]
fn from(ip: IpAddr) -> Self {
Host::Address(ip).into()
}
}
impl From<Ipv4Addr> for Authority {
#[inline(always)]
fn from(ip: Ipv4Addr) -> Self {
Host::Address(IpAddr::V4(ip)).into()
}
}
impl From<Ipv6Addr> for Authority {
#[inline(always)]
fn from(ip: Ipv6Addr) -> Self {
Host::Address(IpAddr::V6(ip)).into()
}
}
impl From<(Host, u16)> for Authority {
#[inline(always)]
fn from((host, port): (Host, u16)) -> Self {
Self::new(HostWithOptPort::new_with_port(host, port))
}
}
impl From<Authority> for Host {
#[inline(always)]
fn from(authority: Authority) -> Self {
authority.address.host
}
}
impl From<SocketAddr> for Authority {
#[inline(always)]
fn from(addr: SocketAddr) -> Self {
Self::new(HostWithOptPort::new_with_port(
Host::Address(addr.ip()),
addr.port(),
))
}
}
impl From<&SocketAddr> for Authority {
#[inline(always)]
fn from(addr: &SocketAddr) -> Self {
Self::new(HostWithOptPort::new_with_port(
Host::Address(addr.ip()),
addr.port(),
))
}
}
impl From<HostWithOptPort> for Authority {
#[inline(always)]
fn from(addr: HostWithOptPort) -> Self {
Self {
user_info: None,
address: addr,
}
}
}
impl From<Authority> for HostWithOptPort {
#[inline(always)]
fn from(addr: Authority) -> Self {
addr.address
}
}
impl From<HostWithPort> for Authority {
#[inline(always)]
fn from(addr: HostWithPort) -> Self {
Self {
user_info: None,
address: addr.into(),
}
}
}
impl From<SocketAddress> for Authority {
#[inline(always)]
fn from(addr: SocketAddress) -> Self {
let SocketAddress { ip_addr, port } = addr;
Self::new(HostWithOptPort::new_with_port(Host::Address(ip_addr), port))
}
}
impl From<&SocketAddress> for Authority {
#[inline(always)]
fn from(addr: &SocketAddress) -> Self {
Self::new(HostWithOptPort::new_with_port(
Host::Address(addr.ip_addr),
addr.port,
))
}
}
impl From<DomainAddress> for Authority {
#[inline(always)]
fn from(addr: DomainAddress) -> Self {
let DomainAddress { domain, port } = addr;
Self::from((domain, port))
}
}
impl fmt::Display for Authority {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.view(), f)
}
}
impl core::str::FromStr for Authority {
type Err = BoxError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s)
}
}
impl TryFrom<String> for Authority {
type Error = BoxError;
#[inline(always)]
fn try_from(s: String) -> Result<Self, Self::Error> {
try_from_maybe_borrowed_str(s.into())
}
}
impl TryFrom<&str> for Authority {
type Error = BoxError;
#[inline(always)]
fn try_from(s: &str) -> Result<Self, Self::Error> {
try_from_maybe_borrowed_str(s.into())
}
}
fn try_as_uninterpreted_host(host_str: &str) -> Result<Host, BoxError> {
let host = super::UninterpretedHost::try_from_reg_name_str(host_str)
.context("parse authority host as reg-name")?;
Ok(Host::Uninterpreted(host))
}
fn try_from_maybe_borrowed_str(maybe_borrowed: Cow<'_, str>) -> Result<Authority, BoxError> {
let mut s = maybe_borrowed.as_ref();
if s.is_empty() {
return Err(BoxError::from_static_str(
"empty string is invalid authority",
));
}
let mut user_info = None;
if let Some(idx) = crate::address::parse_utils::find_userinfo_split(s.as_bytes()) {
let ui_bytes = &s.as_bytes()[..idx];
if ui_bytes.iter().any(|&b| b < 0x20 || b == 0x7F) {
return Err(BoxError::from_static_str(
"userinfo contains control character",
));
}
user_info = Some(UserInfo::from_bytes_unchecked(
rama_core::bytes::Bytes::copy_from_slice(ui_bytes),
));
s = &s[idx + 1..];
}
let host;
let mut port = OptPort::Unset;
if s.starts_with('[') && s.ends_with(']') {
let inside = &s[1..s.len() - 1];
if inside.is_empty() {
return Err(BoxError::from_static_str("empty bracketed IP-literal"));
}
if matches!(inside.as_bytes().first(), Some(b'v' | b'V')) {
crate::uri::parser::authority::validate_ipvfuture(inside.as_bytes())
.map_err(BoxError::from)
.context("parse bracketed IPvFuture")?;
return Ok(Authority {
user_info,
address: HostWithOptPort {
host: Host::Uninterpreted(super::UninterpretedHost::from_validated_bytes(
rama_core::bytes::Bytes::copy_from_slice(inside.as_bytes()),
true,
)),
port: OptPort::Unset,
},
});
}
if crate::address::parse_utils::ipv6_bracket_has_zone(inside.as_bytes()) {
return Err(BoxError::from_static_str(
"ipv6 zone identifiers (RFC 6874) are not supported",
));
}
let addr = inside
.parse::<Ipv6Addr>()
.context("parse bracketed ipv6 authority host without port")?;
return Ok(Authority {
user_info,
address: HostWithOptPort {
host: Host::Address(IpAddr::V6(addr)),
port: OptPort::Unset,
},
});
}
if let Some(last_colon) = s.as_bytes().iter().rposition(|c| *c == b':') {
let first_part = &s[..last_colon];
if first_part.contains(':') {
let (addr, parsed_port) =
crate::address::parse_utils::parse_bracketed_ipv6_with_port(s, last_colon)
.context("authority: parse ipv6 host")?;
host = Host::Address(IpAddr::V6(addr));
port = parsed_port;
} else {
if first_part.is_empty() {
return Err(BoxError::from_static_str(
"empty host before ':port' is invalid",
));
}
let port_bytes = &s.as_bytes()[last_colon + 1..];
port = if port_bytes.is_empty() {
OptPort::Empty
} else {
OptPort::Set(
crate::address::parse_utils::parse_port_bytes(port_bytes)
.context("parse authority port string as u16")?,
)
};
host = if let Ok(ipv4) = first_part.parse::<Ipv4Addr>() {
Host::Address(IpAddr::V4(ipv4))
} else {
let mut owned_vec = if user_info.is_some() {
s.as_bytes().to_vec()
} else {
maybe_borrowed.into_owned().into_bytes()
};
owned_vec.truncate(last_colon);
let owned_str = String::from_utf8(owned_vec)
.context("interpret authority host as utf-8 str")?;
match Domain::try_from(owned_str.as_str()) {
Ok(domain) => Host::Name(domain),
Err(_) => try_as_uninterpreted_host(&owned_str)?,
}
};
};
} else {
host = if let Ok(ip) = s.parse::<IpAddr>() {
Host::Address(ip)
} else {
let owned_str = if user_info.is_some() {
s.to_owned()
} else {
maybe_borrowed.into_owned()
};
match Domain::try_from(owned_str.as_str()) {
Ok(domain) => Host::Name(domain),
Err(_) => try_as_uninterpreted_host(&owned_str)?,
}
};
}
Ok(Authority {
user_info,
address: HostWithOptPort { host, port },
})
}
impl TryFrom<Vec<u8>> for Authority {
type Error = BoxError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
let s = String::from_utf8(bytes).context("parse authority from bytes")?;
s.try_into()
}
}
impl TryFrom<&[u8]> for Authority {
type Error = BoxError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let s = core::str::from_utf8(bytes).context("parse authority from bytes")?;
s.try_into()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AuthorityRef<'a> {
pub(crate) userinfo: Option<UserInfoRef<'a>>,
pub(crate) host: HostRef<'a>,
pub(crate) port: OptPort,
}
impl<'a> AuthorityRef<'a> {
#[must_use]
#[inline]
pub(crate) const fn new(
userinfo: Option<UserInfoRef<'a>>,
host: HostRef<'a>,
port: OptPort,
) -> Self {
Self {
userinfo,
host,
port,
}
}
#[must_use]
pub fn userinfo(&self) -> Option<UserInfoRef<'a>> {
self.userinfo
}
#[must_use]
pub fn host(&self) -> HostRef<'a> {
self.host
}
#[must_use]
pub const fn port(&self) -> OptPort {
self.port
}
#[must_use]
#[inline]
pub const fn port_u16(&self) -> Option<u16> {
self.port.as_u16()
}
#[must_use]
pub fn into_owned(self) -> Authority {
Authority {
user_info: self.userinfo.map(|u| u.into_owned()),
address: HostWithOptPort {
host: self.host.into_owned(),
port: self.port,
},
}
}
}
impl fmt::Display for AuthorityRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ui) = self.userinfo {
write!(f, "{ui}@")?;
}
match self.host {
HostRef::Address(IpAddr::V6(ip)) => write!(f, "[{ip}]")?,
_ => self.host.fmt(f)?,
}
self.port.fmt(f)
}
}
use rama_utils::macros::serde_str::impl_serde_str;
impl_serde_str!(display Authority);
#[cfg(test)]
mod tests {
use super::*;
#[expect(clippy::needless_pass_by_value)]
fn assert_eq(
s: &str,
authority: Authority,
user_info: Option<UserInfo>,
host: &str,
port: Option<u16>,
) {
assert_eq!(authority.user_info, user_info, "parsing: {s}");
assert_eq!(authority.address.host, host, "parsing: {s}");
assert_eq!(authority.address.port.as_u16(), port, "parsing: {s}");
}
#[test]
fn test_parse_valid() {
for (s, (expected_user_info, expected_host, expected_port)) in [
("example.com", (None, "example.com", None)),
(
"user@example.com",
(Some(UserInfo::from_static("user")), "example.com", None),
),
(
"user:password@example.com",
(
Some(UserInfo::from_static("user:password")),
"example.com",
None,
),
),
("example.com:80", (None, "example.com", Some(80))),
("example.com:", (None, "example.com", None)),
(
"user@example.com:80",
(Some(UserInfo::from_static("user")), "example.com", Some(80)),
),
(
"user:secret@example.com:80",
(
Some(UserInfo::from_static("user:secret")),
"example.com",
Some(80),
),
),
(
"user@::1",
(Some(UserInfo::from_static("user")), "::1", None),
),
(
"user:password@::1",
(Some(UserInfo::from_static("user:password")), "::1", None),
),
("::1", (None, "::1", None)),
("[::1]:80", (None, "::1", Some(80))),
(
"user@[::1]:80",
(Some(UserInfo::from_static("user")), "::1", Some(80)),
),
(
"user:password@[::1]:80",
(
Some(UserInfo::from_static("user:password")),
"::1",
Some(80),
),
),
("127.0.0.1", (None, "127.0.0.1", None)),
(
"user@127.0.0.1",
(Some(UserInfo::from_static("user")), "127.0.0.1", None),
),
(
"user:password@127.0.0.1",
(
Some(UserInfo::from_static("user:password")),
"127.0.0.1",
None,
),
),
("127.0.0.1:80", (None, "127.0.0.1", Some(80))),
(
"user@127.0.0.1:80",
(Some(UserInfo::from_static("user")), "127.0.0.1", Some(80)),
),
(
"user:secret@127.0.0.1:80",
(
Some(UserInfo::from_static("user:secret")),
"127.0.0.1",
Some(80),
),
),
(
"2001:db8:3333:4444:5555:6666:7777:8888",
(None, "2001:db8:3333:4444:5555:6666:7777:8888", None),
),
(
"user@2001:db8:3333:4444:5555:6666:7777:8888",
(
Some(UserInfo::from_static("user")),
"2001:db8:3333:4444:5555:6666:7777:8888",
None,
),
),
(
"user:secret@2001:db8:3333:4444:5555:6666:7777:8888",
(
Some(UserInfo::from_static("user:secret")),
"2001:db8:3333:4444:5555:6666:7777:8888",
None,
),
),
(
"[2001:db8:3333:4444:5555:6666:7777:8888]:80",
(None, "2001:db8:3333:4444:5555:6666:7777:8888", Some(80)),
),
(
"user@[2001:db8:3333:4444:5555:6666:7777:8888]:80",
(
Some(UserInfo::from_static("user")),
"2001:db8:3333:4444:5555:6666:7777:8888",
Some(80),
),
),
(
"user:secret@[2001:db8:3333:4444:5555:6666:7777:8888]:80",
(
Some(UserInfo::from_static("user:secret")),
"2001:db8:3333:4444:5555:6666:7777:8888",
Some(80),
),
),
] {
let msg = format!("parsing '{s}'");
assert_eq(
s,
s.parse().expect(&msg),
expected_user_info.clone(),
expected_host,
expected_port,
);
assert_eq(
s,
s.try_into().expect(&msg),
expected_user_info.clone(),
expected_host,
expected_port,
);
assert_eq(
s,
s.to_owned().try_into().expect(&msg),
expected_user_info.clone(),
expected_host,
expected_port,
);
assert_eq(
s,
s.as_bytes().try_into().expect(&msg),
expected_user_info.clone(),
expected_host,
expected_port,
);
assert_eq(
s,
s.as_bytes().to_vec().try_into().expect(&msg),
expected_user_info.clone(),
expected_host,
expected_port,
);
}
}
#[test]
fn test_parse_invalid() {
for s in [
"",
":80",
":foo@:80",
"[]",
"[2001:db8:3333:4444:5555:6666:7777:8888",
"2001:db8:3333:4444:5555:6666:7777:8888]",
"example.com:-1",
"example.com:999999",
"[127.0.0.1]:80",
"2001:db8:3333:4444:5555:6666:7777:8888:80",
"[fe80::1%25en0]",
"[fe80::1%25en0]:8080",
"user@[fe80::1%25en0]:8080",
] {
let msg = format!("parsing '{s}'");
assert!(s.parse::<Authority>().is_err(), "{msg}");
assert!(Authority::try_from(s).is_err(), "{msg}");
assert!(Authority::try_from(s.to_owned()).is_err(), "{msg}");
assert!(Authority::try_from(s.as_bytes()).is_err(), "{msg}");
assert!(Authority::try_from(s.as_bytes().to_vec()).is_err(), "{msg}");
}
}
#[test]
fn ipv6_zone_rejection_has_clear_message() {
let err = Authority::try_from("[fe80::1%25en0]:8080").unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("zone identifier") || msg.contains("zone identifiers"),
"expected zone-identifier message, got: {msg}"
);
}
#[test]
fn test_parse_display() {
for (s, expected) in [
("example.com", "example.com"),
("user@example.com", "user@example.com"),
("user:secret@example.com", "user:secret@example.com"),
("example.com:80", "example.com:80"),
("user@example.com:80", "user@example.com:80"),
("user:secret@example.com:80", "user:secret@example.com:80"),
("[::1]:80", "[::1]:80"),
("user@[::1]:80", "user@[::1]:80"),
("secret:user@[::1]:80", "secret:user@[::1]:80"),
("::1", "[::1]"),
("user@::1", "user@[::1]"),
("user:secret@::1", "user:secret@[::1]"),
("127.0.0.1:80", "127.0.0.1:80"),
("user@127.0.0.1:80", "user@127.0.0.1:80"),
("user:secret@127.0.0.1:80", "user:secret@127.0.0.1:80"),
("127.0.0.1", "127.0.0.1"),
("user@127.0.0.1", "user@127.0.0.1"),
("user:secret@127.0.0.1", "user:secret@127.0.0.1"),
] {
let msg = format!("parsing '{s}'");
let authority: Authority = s.parse().expect(&msg);
assert_eq!(authority.to_string(), expected, "{msg}");
}
}
#[test]
fn regression_authority_userinfo_splits_on_last_at() {
let auth = Authority::try_from("user@name:pass@example.com:80").unwrap();
assert_eq!(auth.address.host, "example.com");
assert_eq!(auth.address.port, OptPort::Set(80));
let ui = auth.user_info.as_ref().expect("userinfo present");
let (user, pass) = ui.split_user_password();
assert_eq!(user, b"user@name");
assert_eq!(pass, Some(&b"pass"[..]));
}
#[test]
fn authority_try_from_accepts_pct_encoded_reg_name() {
let from_uri = crate::uri::Uri::parse_authority_form("exa%6Dple.com")
.unwrap()
.authority()
.unwrap()
.into_owned();
let direct = Authority::try_from("exa%6Dple.com").unwrap();
assert_eq!(direct, from_uri);
let from_uri_p = crate::uri::Uri::parse_authority_form("exa%6Dple.com:443")
.unwrap()
.authority()
.unwrap()
.into_owned();
let direct_p = Authority::try_from("exa%6Dple.com:443").unwrap();
assert_eq!(direct_p, from_uri_p);
}
#[test]
fn authority_try_from_rejects_empty_host_with_port() {
Authority::try_from(":80").unwrap_err();
Authority::try_from(":foo@:80").unwrap_err();
crate::uri::Uri::parse_authority_form(":80").unwrap_err();
}
#[test]
fn authority_try_from_bracketed_ipvfuture() {
let direct = Authority::try_from("[v1.fe80::a]").unwrap();
let from_uri = crate::uri::Uri::parse_authority_form("[v1.fe80::a]")
.unwrap()
.authority()
.unwrap()
.into_owned();
assert_eq!(direct, from_uri);
assert!(matches!(direct.address.host, Host::Uninterpreted(_)));
}
#[test]
fn authority_try_from_bracketed_ipv6_no_port_is_typed_address() {
let auth = Authority::try_from("[::1]").unwrap();
assert!(
matches!(auth.address.host, Host::Address(IpAddr::V6(_))),
"expected typed IPv6 Address, got {:?}",
auth.address.host
);
assert_eq!(auth.address.port, OptPort::Unset);
assert_eq!(auth.to_string(), "[::1]");
}
#[test]
fn regression_authority_rejects_ipv6_zone_id() {
for input in [
"[fe80::1%eth0]:80",
"[fe80::1%25eth0]:80",
"user@[fe80::1%eth0]:80",
] {
assert!(
Authority::try_from(input).is_err(),
"authority should reject zone-id input {input:?}",
);
}
}
#[test]
fn authority_ref_display_matches_owned() {
for input in [
"example.com",
"example.com:443",
"user@example.com:80",
"user:secret@example.com:8080",
"127.0.0.1:8080",
"[2001:db8::1]:443",
] {
let owned: Authority = input.parse().unwrap();
let ref_view = AuthorityRef::new(
owned.user_info.as_ref().map(UserInfoRef::from),
HostRef::from(&owned.address.host),
owned.address.port,
);
assert_eq!(
ref_view.to_string(),
owned.to_string(),
"AuthorityRef Display must match Authority for {input:?}"
);
}
}
}