use crate::error::Error;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Url {
scheme: String,
host: String,
port: u16,
path: String,
query: Option<String>,
}
impl Url {
pub fn parse(source: &str) -> Result<Self, Error> {
let (scheme, rest) = source.split_once("://").ok_or_else(|| Error::InvalidUrl {
source: source.to_owned(),
})?;
if scheme.is_empty() {
Err(Error::InvalidUrl {
source: source.to_owned(),
})
} else {
split_host_and_path(scheme, rest).ok_or_else(|| Error::InvalidUrl {
source: source.to_owned(),
})
}
}
#[must_use]
pub fn scheme(&self) -> &str {
&self.scheme
}
#[must_use]
pub fn host(&self) -> &str {
&self.host
}
#[must_use]
pub fn port(&self) -> u16 {
self.port
}
#[must_use]
pub fn path(&self) -> &str {
&self.path
}
#[must_use]
pub fn query(&self) -> Option<&str> {
self.query.as_deref()
}
#[must_use]
pub fn request_target(&self) -> String {
match &self.query {
Some(q) => format!("{}?{q}", self.path),
None => self.path.clone(),
}
}
}
fn split_host_and_path(scheme: &str, rest: &str) -> Option<Url> {
let (authority, path_and_query) = match rest.find('/') {
Some(idx) => (rest.get(..idx)?, rest.get(idx..)?),
None => (rest, "/"),
};
let (host_str, port) = parse_authority(authority, scheme)?;
let (path, query) = match path_and_query.find('?') {
Some(idx) => (
path_and_query.get(..idx)?.to_owned(),
Some(path_and_query.get(idx + 1..)?.to_owned()),
),
None => (path_and_query.to_owned(), None),
};
Some(Url {
scheme: scheme.to_ascii_lowercase(),
host: host_str,
port,
path,
query,
})
}
fn parse_authority(authority: &str, scheme: &str) -> Option<(String, u16)> {
if authority.is_empty() {
None
} else {
match authority.rfind(':') {
Some(idx) => {
let host = authority.get(..idx)?.to_owned();
let port = authority.get(idx + 1..)?.parse::<u16>().ok()?;
Some((host, port))
}
None => Some((authority.to_owned(), default_port(scheme))),
}
}
}
fn default_port(scheme: &str) -> u16 {
match scheme {
"https" => 443,
_other => 80,
}
}