pub mod decode;
use core::fmt::{self, Write as _};
use core::marker::PhantomData;
use crate::parser::char;
use crate::spec::{IriSpec, Spec, UriSpec};
pub type PercentEncodedForUri<T> = PercentEncoded<T, UriSpec>;
pub type PercentEncodedForIri<T> = PercentEncoded<T, IriSpec>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
enum Context {
RegName,
UserOrPassword,
PathSegment,
Path,
Query,
Fragment,
Unreserve,
Character,
}
#[derive(Debug, Clone, Copy)]
pub struct PercentEncoded<T, S> {
context: Context,
raw: T,
_spec: PhantomData<fn() -> S>,
}
impl<T: fmt::Display, S: Spec> PercentEncoded<T, S> {
pub fn from_reg_name(raw: T) -> Self {
Self {
context: Context::RegName,
raw,
_spec: PhantomData,
}
}
pub fn from_user(raw: T) -> Self {
Self {
context: Context::UserOrPassword,
raw,
_spec: PhantomData,
}
}
pub fn from_password(raw: T) -> Self {
Self {
context: Context::UserOrPassword,
raw,
_spec: PhantomData,
}
}
pub fn from_path_segment(raw: T) -> Self {
Self {
context: Context::PathSegment,
raw,
_spec: PhantomData,
}
}
pub fn from_path(raw: T) -> Self {
Self {
context: Context::Path,
raw,
_spec: PhantomData,
}
}
pub fn from_query(raw: T) -> Self {
Self {
context: Context::Query,
raw,
_spec: PhantomData,
}
}
pub fn from_fragment(raw: T) -> Self {
Self {
context: Context::Fragment,
raw,
_spec: PhantomData,
}
}
#[inline]
#[must_use]
pub fn unreserve(raw: T) -> Self {
Self {
context: Context::Unreserve,
raw,
_spec: PhantomData,
}
}
#[inline]
#[must_use]
pub fn characters(raw: T) -> Self {
Self {
context: Context::Character,
raw,
_spec: PhantomData,
}
}
}
impl<T: fmt::Display, S: Spec> fmt::Display for PercentEncoded<T, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
struct Filter<'a, 'b, S> {
context: Context,
writer: &'a mut fmt::Formatter<'b>,
_spec: PhantomData<fn() -> S>,
}
impl<S: Spec> fmt::Write for Filter<'_, '_, S> {
fn write_str(&mut self, s: &str) -> fmt::Result {
s.chars().try_for_each(|c| self.write_char(c))
}
fn write_char(&mut self, c: char) -> fmt::Result {
let is_valid_char = match (self.context, c.is_ascii()) {
(Context::RegName, true) => char::is_ascii_regname(c as u8),
(Context::RegName, false) => char::is_nonascii_regname::<S>(c),
(Context::UserOrPassword, true) => {
c != ':' && char::is_ascii_userinfo_ipvfutureaddr(c as u8)
}
(Context::UserOrPassword, false) => char::is_nonascii_userinfo::<S>(c),
(Context::PathSegment, true) => char::is_ascii_pchar(c as u8),
(Context::PathSegment, false) => S::is_nonascii_char_unreserved(c),
(Context::Path, true) => c == '/' || char::is_ascii_pchar(c as u8),
(Context::Path, false) => S::is_nonascii_char_unreserved(c),
(Context::Query, true) => c == '/' || char::is_ascii_frag_query(c as u8),
(Context::Query, false) => char::is_nonascii_query::<S>(c),
(Context::Fragment, true) => c == '/' || char::is_ascii_frag_query(c as u8),
(Context::Fragment, false) => char::is_nonascii_fragment::<S>(c),
(Context::Unreserve, true) => char::is_ascii_unreserved(c as u8),
(Context::Unreserve, false) => S::is_nonascii_char_unreserved(c),
(Context::Character, true) => char::is_ascii_unreserved_or_reserved(c as u8),
(Context::Character, false) => {
S::is_nonascii_char_unreserved(c) || S::is_nonascii_char_private(c)
}
};
if is_valid_char {
self.writer.write_char(c)
} else {
write_pct_encoded_char(&mut self.writer, c)
}
}
}
let mut filter = Filter {
context: self.context,
writer: f,
_spec: PhantomData::<fn() -> S>,
};
write!(filter, "{}", self.raw)
}
}
#[inline]
fn write_pct_encoded_char<W: fmt::Write>(writer: &mut W, c: char) -> fmt::Result {
let mut buf = [0_u8; 4];
let buf = c.encode_utf8(&mut buf);
buf.bytes().try_for_each(|b| write!(writer, "%{:02X}", b))
}