use std::fmt;
#[cfg(unix)]
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ListenAddr {
Tcp {
host: String,
port: u16,
},
#[cfg(unix)]
Uds {
path: PathBuf,
},
}
impl ListenAddr {
pub fn loopback_tcp(port: u16) -> Self {
Self::Tcp {
host: "127.0.0.1".to_string(),
port,
}
}
pub fn is_loopback_or_uds(&self) -> bool {
match self {
Self::Tcp { host, .. } => host
.parse::<std::net::IpAddr>()
.is_ok_and(|ip| ip.is_loopback()),
#[cfg(unix)]
Self::Uds { .. } => true,
}
}
}
impl fmt::Display for ListenAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Tcp { host, port } if host.contains(':') => {
write!(f, "http://[{host}]:{port}")
}
Self::Tcp { host, port } => write!(f, "http://{host}:{port}"),
#[cfg(unix)]
Self::Uds { path } => write!(f, "unix://{}", path.display()),
}
}
}
#[cfg(windows)]
pub(crate) const WINDOWS_UDS_REJECTION: &str =
"--unix-socket PATH listeners are not supported on Windows. \
Use --port PORT instead.";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_tcp_ipv4_emits_http_url() {
let addr = ListenAddr::Tcp {
host: "127.0.0.1".to_string(),
port: 3000,
};
assert_eq!(addr.to_string(), "http://127.0.0.1:3000");
}
#[test]
fn display_tcp_ipv6_emits_bracketed_authority() {
let addr = ListenAddr::Tcp {
host: "::1".to_string(),
port: 8080,
};
assert_eq!(addr.to_string(), "http://[::1]:8080");
}
#[test]
fn display_tcp_hostname_preserves_literal() {
let addr = ListenAddr::Tcp {
host: "my.host".to_string(),
port: 80,
};
assert_eq!(addr.to_string(), "http://my.host:80");
}
#[cfg(unix)]
#[test]
fn display_uds_emits_unix_url() {
let addr = ListenAddr::Uds {
path: PathBuf::from("/tmp/vlc.sock"),
};
assert_eq!(addr.to_string(), "unix:///tmp/vlc.sock");
}
#[test]
fn loopback_tcp_helper_is_127_0_0_1() {
let addr = ListenAddr::loopback_tcp(9000);
assert!(matches!(
addr,
ListenAddr::Tcp { ref host, port: 9000 } if host == "127.0.0.1"
));
}
#[test]
fn is_loopback_or_uds_tcp_table() {
for (host, expected) in [
("127.0.0.1", true),
("::1", true),
("0.0.0.0", false),
("localhost", false),
] {
let addr = ListenAddr::Tcp {
host: host.to_string(),
port: 0,
};
assert_eq!(addr.is_loopback_or_uds(), expected, "host = {host:?}");
}
}
#[cfg(unix)]
#[test]
fn is_loopback_or_uds_treats_uds_as_local() {
let addr = ListenAddr::Uds {
path: PathBuf::from("/tmp/x.sock"),
};
assert!(addr.is_loopback_or_uds());
}
}