use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs};
use std::path::PathBuf;
use crate::core::types::DynError;
use crate::util::atoi::valid_port;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SockInfo {
Inet(SocketAddr),
Inet6(SocketAddr),
Unix(PathBuf),
}
impl SockInfo {
pub fn is_inet(&self) -> bool {
matches!(self, Self::Inet(_) | Self::Inet6(_))
}
pub fn as_socket_addr(&self) -> Option<SocketAddr> {
match self {
Self::Inet(s) | Self::Inet6(s) => Some(*s),
Self::Unix(_) => None,
}
}
pub fn resolve(name: &str, port: u16) -> Result<Self, DynError> {
if name.starts_with('/') {
return Ok(Self::Unix(PathBuf::from(name)));
}
if !valid_port(i32::from(port)) {
return Err(DynError::generic(format!("invalid port: {port}")));
}
let mut addrs = (name, port).to_socket_addrs().map_err(DynError::Io)?;
let addr = addrs
.next()
.ok_or_else(|| DynError::generic(format!("no address for {name}:{port}")))?;
Ok(match addr.ip() {
IpAddr::V4(_) => Self::Inet(addr),
IpAddr::V6(_) => Self::Inet6(addr),
})
}
pub fn from_v4(addr: Ipv4Addr, port: u16) -> Self {
Self::Inet(SocketAddr::new(IpAddr::V4(addr), port))
}
pub fn from_v6(addr: Ipv6Addr, port: u16) -> Self {
Self::Inet6(SocketAddr::new(IpAddr::V6(addr), port))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolve_loopback_v4() {
let s = SockInfo::resolve("127.0.0.1", 6379).unwrap();
assert_eq!(
s.as_socket_addr().unwrap().ip(),
IpAddr::V4(Ipv4Addr::LOCALHOST)
);
assert!(matches!(s, SockInfo::Inet(_)));
}
#[test]
fn resolve_loopback_v6() {
let s = SockInfo::resolve("::1", 6379).unwrap();
assert!(matches!(s, SockInfo::Inet6(_)));
}
#[test]
fn unix_socket_passthrough() {
let s = SockInfo::resolve("/tmp/x.sock", 0).unwrap();
assert!(matches!(s, SockInfo::Unix(_)));
assert!(!s.is_inet());
}
#[test]
fn invalid_port_is_rejected() {
let err = SockInfo::resolve("127.0.0.1", 0).unwrap_err();
assert!(err.to_string().contains("invalid port"));
}
}