use {
crate::proto::Error,
bytes::Bytes,
http::{
Uri,
uri::{Authority, Scheme},
},
std::{
error, fmt,
io::{self, ErrorKind},
net::{IpAddr, Ipv4Addr, Ipv6Addr},
},
url::Host,
};
#[derive(Clone, Debug)]
pub struct Address {
pub host: Host,
pub port: u16,
pub secure: bool,
}
impl Address {
pub fn new(scheme: &Scheme, authority: &Authority) -> Result<Self, InvalidUri> {
if scheme != &Scheme::HTTP && scheme != &Scheme::HTTPS {
return Err(InvalidUri::NonHttpScheme);
}
let host = Host::parse(authority.host()).map_err(|_| InvalidUri::InvalidHost)?;
let secure = scheme == &Scheme::HTTPS;
let port = authority
.port()
.map_or(default_port(secure), |port| port.as_u16());
Ok(Self { host, port, secure })
}
pub fn http<H>(host: H) -> Self
where
H: IntoHost,
{
Self {
host: host.into_host(),
port: default_port(false),
secure: false,
}
}
pub fn https<H>(host: H) -> Self
where
H: IntoHost,
{
Self {
host: host.into_host(),
port: default_port(true),
secure: true,
}
}
pub fn from_uri(uri: &Uri) -> Result<Self, InvalidUri> {
let scheme = uri.scheme().ok_or(InvalidUri::NoScheme)?;
let authority = uri.authority().ok_or(InvalidUri::InvalidHost)?;
Self::new(scheme, authority)
}
pub fn host_value(&self) -> Bytes {
if self.port == default_port(self.secure) {
match &self.host {
Host::Domain(domain) => Bytes::copy_from_slice(domain.as_bytes()),
Host::Ipv4(ip) => Bytes::from(ip.to_string()),
Host::Ipv6(ip) => Bytes::from(ip.to_string()),
}
} else {
let host = &self.host;
let port = self.port;
Bytes::from(format!("{host}:{port}"))
}
}
}
impl TryFrom<&Uri> for Address {
type Error = InvalidUri;
fn try_from(uri: &Uri) -> Result<Self, Self::Error> {
Self::from_uri(uri)
}
}
impl TryFrom<Uri> for Address {
type Error = InvalidUri;
fn try_from(uri: Uri) -> Result<Self, Self::Error> {
Self::from_uri(&uri)
}
}
fn default_port(secure: bool) -> u16 {
const HTTP: u16 = 80;
const HTTPS: u16 = 443;
if secure { HTTPS } else { HTTP }
}
#[derive(Debug)]
pub enum InvalidUri {
NoScheme,
NonHttpScheme,
InvalidHost,
}
impl From<InvalidUri> for io::Error {
fn from(e: InvalidUri) -> Self {
Self::new(ErrorKind::InvalidInput, e)
}
}
impl From<InvalidUri> for Error {
fn from(e: InvalidUri) -> Self {
Self::Io(e.into())
}
}
impl fmt::Display for InvalidUri {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoScheme => write!(f, "no scheme"),
Self::NonHttpScheme => write!(f, "non http(s) scheme"),
Self::InvalidHost => write!(f, "invalid host"),
}
}
}
impl error::Error for InvalidUri {}
pub trait IntoHost {
fn into_host(self) -> Host;
}
impl IntoHost for Host {
fn into_host(self) -> Self {
self
}
}
impl IntoHost for String {
fn into_host(self) -> Host {
Host::Domain(self)
}
}
impl IntoHost for &str {
fn into_host(self) -> Host {
Host::Domain(self.to_owned())
}
}
impl IntoHost for Ipv4Addr {
fn into_host(self) -> Host {
Host::Ipv4(self)
}
}
impl IntoHost for Ipv6Addr {
fn into_host(self) -> Host {
Host::Ipv6(self)
}
}
impl IntoHost for IpAddr {
fn into_host(self) -> Host {
match self {
Self::V4(ip4) => ip4.into_host(),
Self::V6(ip6) => ip6.into_host(),
}
}
}