safe_uri 0.1.0-beta.4

Simple and safe URI types.
Documentation
mod error;
pub(crate) mod query_or_fragment;

pub(crate) use self::error::{InvalidByte, InvalidComponent, InvalidPercentEncodedByte};

use crate::percent_encoded::PercentEncoded;
use shared_bytes::SharedStr;

pub(crate) const fn is_unreserved(b: u8) -> bool {
    b.is_ascii_alphanumeric() || matches!(b, b'-' | b'.' | b'_' | b'~')
}

pub(crate) const fn is_sub_delimiter(b: u8) -> bool {
    matches!(
        b,
        b'!' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b'+' | b',' | b';' | b'='
    )
}

pub(crate) fn with_normalized_percent_encoding(
    string: &str,
    is_valid_byte: fn(u8) -> bool,
) -> Result<Option<SharedStr>, InvalidComponent> {
    let mut normalized = String::new();
    let mut index = 0;
    while index < string.len() {
        let byte = string.as_bytes()[index];
        if byte == b'%' {
            let encoded = PercentEncoded::from_bytes_at_index(string.as_bytes(), index)
                .map_err(InvalidComponent::PercentEncoded)?;
            if is_valid_byte(encoded.byte) {
                if normalized.is_empty() {
                    normalized.reserve(string.len());
                    normalized.push_str(&string[..index]);
                }
                normalized.push(char::from(encoded.byte))
            }
            index += PercentEncoded::BYTES_LENGTH;
        } else if is_valid_byte(byte) {
            if !normalized.is_empty() {
                normalized.push(char::from(byte))
            }
            index += 1;
        } else {
            return Err(InvalidComponent::Byte(InvalidByte { byte }));
        }
    }
    Ok(if normalized.is_empty() {
        None
    } else {
        Some(normalized.into())
    })
}