use std::fmt;
use crate::url::errors::InvalidUri;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Scheme {
inner: SchemeInner,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
enum SchemeInner {
Http,
Https,
Ws,
Wss,
Custom(String),
}
impl Scheme {
pub const HTTP: Scheme = Scheme {
inner: SchemeInner::Http,
};
pub const HTTPS: Scheme = Scheme {
inner: SchemeInner::Https,
};
pub const WS: Scheme = Scheme {
inner: SchemeInner::Ws,
};
pub const WSS: Scheme = Scheme {
inner: SchemeInner::Wss,
};
pub fn parse_from_uri(uri: &str) -> Result<(Self, &str), InvalidUri> {
let colon_pos = uri
.find(':')
.ok_or_else(|| InvalidUri::new("missing scheme (no ':' found)"))?;
if colon_pos == 0 {
return Err(InvalidUri::new("empty scheme"));
}
let scheme_str = &uri[..colon_pos];
if !Self::is_valid_scheme(scheme_str) {
return Err(InvalidUri::new(format!(
"invalid scheme characters: {scheme_str}"
)));
}
let scheme = Self::from_str(scheme_str);
let remainder = &uri[colon_pos + 1..];
Ok((scheme, remainder))
}
fn from_str(s: &str) -> Self {
let lower = s.to_lowercase();
match lower.as_str() {
"http" => Self::HTTP,
"https" => Self::HTTPS,
"ws" => Self::WS,
"wss" => Self::WSS,
_ => Scheme {
inner: SchemeInner::Custom(lower),
},
}
}
fn is_valid_scheme(s: &str) -> bool {
if s.is_empty() {
return false;
}
let mut chars = s.chars();
if !chars.next().is_some_and(|c| c.is_ascii_alphabetic()) {
return false;
}
chars.all(|c| c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.')
}
#[must_use]
pub fn as_str(&self) -> &str {
match &self.inner {
SchemeInner::Http => "http",
SchemeInner::Https => "https",
SchemeInner::Ws => "ws",
SchemeInner::Wss => "wss",
SchemeInner::Custom(s) => s,
}
}
#[must_use]
pub fn default_port(&self) -> u16 {
match &self.inner {
SchemeInner::Http => 80,
SchemeInner::Https => 443,
SchemeInner::Ws => 80,
SchemeInner::Wss => 443,
SchemeInner::Custom(_) => 0, }
}
#[must_use]
pub fn is_http(&self) -> bool {
matches!(self.inner, SchemeInner::Http)
}
#[must_use]
pub fn is_https(&self) -> bool {
matches!(self.inner, SchemeInner::Https)
}
#[must_use]
pub fn is_ws(&self) -> bool {
matches!(self.inner, SchemeInner::Ws)
}
#[must_use]
pub fn is_wss(&self) -> bool {
matches!(self.inner, SchemeInner::Wss)
}
}
impl fmt::Display for Scheme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}