use std::{ borrow::Cow, fmt, net::{IpAddr, SocketAddr}, }; use arrayvec::ArrayString; #[derive(Clone, Debug, PartialEq, Eq)] pub enum FedDest { Literal(SocketAddr), Named(String, PortString), } /// numeric or service-name pub type PortString = ArrayString<16>; const DEFAULT_PORT: &str = ":8448"; pub(crate) fn get_ip_with_port(dest_str: &str) -> Option { if let Ok(dest) = dest_str.parse::() { Some(FedDest::Literal(dest)) } else if let Ok(ip_addr) = dest_str.parse::() { Some(FedDest::Literal(SocketAddr::new(ip_addr, 8448))) } else { None } } pub(crate) fn add_port_to_hostname(dest: &str) -> FedDest { let (host, port) = match dest.find(':') { None => (dest, DEFAULT_PORT), Some(pos) => dest.split_at(pos), }; FedDest::Named( host.to_owned(), PortString::from(port).unwrap_or_else(|_| FedDest::default_port()), ) } impl FedDest { pub(crate) fn https_string(&self) -> String { match self { Self::Literal(addr) => format!("https://{addr}"), Self::Named(host, port) => format!("https://{host}{port}"), } } pub(crate) fn uri_string(&self) -> String { match self { Self::Literal(addr) => addr.to_string(), Self::Named(host, port) => format!("{host}{port}"), } } #[inline] pub(crate) fn hostname(&self) -> Cow<'_, str> { match &self { Self::Literal(addr) => addr.ip().to_string().into(), Self::Named(host, _) => host.into(), } } #[inline] #[allow(clippy::string_slice)] pub(crate) fn port(&self) -> Option { match &self { Self::Literal(addr) => Some(addr.port()), Self::Named(_, port) => port[1..].parse().ok(), } } #[inline] #[must_use] pub fn default_port() -> PortString { PortString::from(DEFAULT_PORT).expect("default port string") } } impl fmt::Display for FedDest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.uri_string().as_str()) } }