use crate::NetError;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UrlParts {
pub scheme: String,
pub host: String,
pub port: u16,
pub path: String,
}
pub fn parse_url(url: &str) -> Result<UrlParts, NetError> {
let (scheme, rest) = url
.split_once("://")
.ok_or_else(|| NetError::MalformedUrl(url.to_owned()))?;
let (host_port, path) = match rest.split_once('/') {
Some((host_port, suffix)) => {
let trimmed = suffix.trim_end_matches('/');
(host_port, format!("/{trimmed}"))
}
None => (rest, "/".to_owned()),
};
let (host, port) = match host_port.rsplit_once(':') {
Some((host, port)) => (
host.to_owned(),
port.parse::<u16>()
.map_err(|_| NetError::InvalidPort(url.to_owned()))?,
),
None => (host_port.to_owned(), default_port_for_scheme(scheme, url)?),
};
if host.is_empty() {
return Err(NetError::MalformedUrl(url.to_owned()));
}
Ok(UrlParts {
scheme: scheme.to_owned(),
host,
port,
path,
})
}
pub fn parse_url_for_scheme(
url: &str,
scheme: &str,
default_path: &str,
) -> Result<UrlParts, NetError> {
let mut parts = parse_url(url)?;
if parts.scheme != scheme {
return Err(NetError::UnexpectedScheme {
expected: scheme.to_owned(),
found: parts.scheme,
});
}
if parts.path == "/" {
parts.path = default_path.to_owned();
}
Ok(parts)
}
fn default_port_for_scheme(scheme: &str, url: &str) -> Result<u16, NetError> {
match scheme {
"http" => Ok(80),
"https" => Ok(443),
_ => Err(NetError::UnsupportedScheme(format!("{scheme} in {url}"))),
}
}