mod generated_struct;
use shared_bytes::SharedStr;
pub use self::generated_struct::Scheme;
use crate::validation::InvalidByte;
use std::{error::Error, fmt};
impl Scheme {
pub const HTTP: Self = Self::from_static("http");
pub const HTTPS: Self = Self::new();
pub const WS: Self = Self::from_static("ws");
pub const WSS: Self = Self::from_static("wss");
}
#[derive(Debug, Clone)]
pub struct InvalidScheme(Invalid);
impl InvalidScheme {
pub fn empty() -> Self {
Self(Invalid::Empty)
}
}
#[derive(Debug, Clone)]
enum Invalid {
Byte(InvalidByte),
Empty,
}
impl fmt::Display for InvalidScheme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
Invalid::Byte(e) => write!(f, "URI scheme is invalid: {}", e),
Invalid::Empty => write!(f, "URI scheme is empty"),
}
}
}
impl Error for InvalidScheme {}
const fn validate_static(bytes: &[u8]) -> Result<(), InvalidScheme> {
validate(bytes)
}
fn validate_with_normalized_percent_encoding(
string: &str,
) -> Result<Option<SharedStr>, InvalidScheme> {
validate(string.as_bytes()).map(|()| None)
}
const fn validate(bytes: &[u8]) -> Result<(), InvalidScheme> {
match inner_validate(bytes) {
Ok(x) => Ok(x),
Err(e) => Err(InvalidScheme(e)),
}
}
const fn inner_validate(bytes: &[u8]) -> Result<(), Invalid> {
if bytes.is_empty() {
return Err(Invalid::Empty);
}
let first_byte = bytes[0];
if !first_byte.is_ascii_alphabetic() {
return Err(Invalid::Byte(InvalidByte { byte: first_byte }));
}
let mut index = 1;
while index < bytes.len() {
let byte = bytes[index];
if !is_scheme_char(byte) {
return Err(Invalid::Byte(InvalidByte { byte }));
}
index += 1;
}
Ok(())
}
const fn is_scheme_char(b: u8) -> bool {
b.is_ascii_alphanumeric() || matches!(b, b'+' | b'-' | b'.')
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
#[test]
fn one_ok_char() {
let string = "a";
assert_eq!(Scheme::try_from(string).unwrap(), string);
}
#[test]
fn one_digit() {
let string = "1";
assert_matches!(
Scheme::try_from(string),
Err(InvalidScheme(Invalid::Byte(InvalidByte { byte: b'1' })))
)
}
#[test]
fn empty() {
let string = "";
assert_matches!(Scheme::try_from(string), Err(InvalidScheme(Invalid::Empty)))
}
#[test]
fn default() {
assert_matches!(Scheme::try_from(Scheme::default().into_shared_str()), Ok(_));
assert_eq!(Scheme::default().as_str(), "https");
}
#[test]
fn https_is_https() {
assert_eq!(Scheme::HTTPS, "https");
}
}