use ohkami_lib::{Slice, percent_decode_utf8};
use std::{borrow::Cow, mem::MaybeUninit};
pub struct Path(MaybeUninit<PathInner>);
pub(crate) struct PathInner {
raw: Slice,
params: Params,
}
struct Params {
next: usize,
list: [MaybeUninit<Slice>; Self::LIMIT],
}
impl Params {
const LIMIT: usize = 2;
}
const _: () = {
impl Params {
fn iter(&self) -> impl Iterator<Item = &Slice> {
(0..self.next).map(|i| unsafe { self.list.get_unchecked(i).assume_init_ref() })
}
}
impl Path {
pub fn params(&self) -> impl Iterator<Item = Cow<'_, str>> {
(unsafe { self.0.assume_init_ref() })
.params
.iter()
.map(|slice| {
percent_decode_utf8(unsafe { slice.as_bytes() })
.unwrap_or_else(|_| String::from_utf8_lossy(unsafe { slice.as_bytes() }))
})
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
let bytes = unsafe { self.0.assume_init_ref().raw.as_bytes() };
if bytes.is_empty() { b"/" } else { bytes }
}
#[inline]
pub fn str(&self) -> Cow<'_, str> {
let bytes = unsafe { self.0.assume_init_ref().raw.as_bytes() };
if bytes.is_empty() {
return Cow::Borrowed("/");
}
percent_decode_utf8(bytes).unwrap_or_else(|_| String::from_utf8_lossy(bytes))
}
#[inline]
pub(crate) unsafe fn assume_one_param<'p>(&self) -> &'p [u8] {
unsafe {
self.0
.assume_init_ref()
.params
.list
.get_unchecked(0)
.assume_init_ref()
.as_bytes()
}
}
#[inline]
pub(crate) unsafe fn assume_two_params<'p>(&self) -> (&'p [u8], &'p [u8]) {
unsafe {
(
self.0
.assume_init_ref()
.params
.list
.get_unchecked(0)
.assume_init_ref()
.as_bytes(),
self.0
.assume_init_ref()
.params
.list
.get_unchecked(1)
.assume_init_ref()
.as_bytes(),
)
}
}
}
impl std::fmt::Debug for Path {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
<Cow<str> as std::fmt::Debug>::fmt(&self.str(), f)
}
}
impl std::fmt::Display for Path {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
<Cow<str> as std::fmt::Display>::fmt(&self.str(), f)
}
}
};
#[cfg(feature = "__rt__")]
const _: () = {
impl Params {
const fn init() -> Self {
Params {
next: 0,
list: [const { MaybeUninit::uninit() }; Params::LIMIT],
}
}
#[inline(always)]
fn push(&mut self, param: Slice) {
#[cfg(debug_assertions)]
{
assert!(self.next < Self::LIMIT);
}
unsafe {
self.list.get_unchecked_mut(self.next).write(param);
}
self.next += 1;
}
}
impl Path {
pub(crate) const fn uninit() -> Self {
Self(MaybeUninit::uninit())
}
#[inline(always)]
pub(crate) fn init_with_request_bytes(
&mut self,
bytes: &[u8],
) -> Result<(), crate::Response> {
(bytes.first() == Some(&b'/'))
.then_some(())
.ok_or_else(crate::Response::NotImplemented)?;
let mut len = bytes.len();
if unsafe { *bytes.get_unchecked(len - 1) == b'/' } {
len -= 1
};
#[allow(unused_unsafe/* I don't know why but rustc sometimes put warnings to this unsafe as unnecessary */)]
self.0.write(PathInner {
raw: unsafe { Slice::new_unchecked(bytes.as_ptr(), len) },
params: Params::init(),
});
Ok(())
}
#[inline]
pub(crate) unsafe fn push_param(&mut self, param: Slice) {
unsafe { self.0.assume_init_mut().params.push(param) }
}
#[inline]
pub(crate) unsafe fn normalized_bytes<'req>(&self) -> &'req [u8] {
unsafe { self.0.assume_init_ref().raw.as_bytes() }
}
}
impl Path {
#[cfg(all(feature = "__rt_native__", feature = "DEBUG", test))]
pub(crate) fn from_literal(literal: &'static str) -> Self {
unsafe { Self::from_str_unchecked(literal) }
}
#[allow(unused)]
pub(crate) unsafe fn from_str_unchecked(s: &str) -> Self {
Self(MaybeUninit::new(PathInner {
raw: Slice::from_bytes(s.trim_end_matches('/').as_bytes()),
params: Params::init(),
}))
}
}
};