use smallvec::SmallVec;
use std::borrow::Cow;
use std::fmt;
pub const QUERY_PARAM_INLINE: usize = 8;
pub const PATH_PARAM_INLINE: usize = 4;
pub const MIDDLEWARE_INLINE: usize = 8;
pub const FORM_FIELD_INLINE: usize = 16;
pub const COOKIE_INLINE: usize = 8;
pub const ROUTE_SEGMENT_INLINE: usize = 8;
pub const SMALL_INLINE: usize = 8;
pub const TINY_INLINE: usize = 4;
pub type SmallQueryParams<'a> = SmallVec<[(Cow<'a, str>, Cow<'a, str>); QUERY_PARAM_INLINE]>;
pub type SmallPathParams<'a> = SmallVec<[(Cow<'a, str>, Cow<'a, str>); PATH_PARAM_INLINE]>;
pub type SmallPairs = SmallVec<[(String, String); SMALL_INLINE]>;
pub type SmallStrings = SmallVec<[String; SMALL_INLINE]>;
pub type SmallBytes = SmallVec<[u8; 64]>;
pub type SmallSegments<'a> = SmallVec<[&'a str; ROUTE_SEGMENT_INLINE]>;
pub type Small<T> = SmallVec<[T; SMALL_INLINE]>;
pub type Tiny<T> = SmallVec<[T; TINY_INLINE]>;
#[derive(Clone, Default)]
pub struct QueryParams {
inner: SmallVec<[(String, String); QUERY_PARAM_INLINE]>,
}
impl QueryParams {
#[inline]
pub const fn new() -> Self {
Self {
inner: SmallVec::new_const(),
}
}
#[inline]
pub fn with_capacity(cap: usize) -> Self {
Self {
inner: SmallVec::with_capacity(cap),
}
}
#[inline]
pub fn is_inline(&self) -> bool {
!self.inner.spilled()
}
#[inline]
pub fn len(&self) -> usize {
self.inner.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[inline]
pub fn push(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.inner.push((key.into(), value.into()));
}
#[inline]
pub fn get(&self, key: &str) -> Option<&str> {
self.inner
.iter()
.find(|(k, _)| k == key)
.map(|(_, v)| v.as_str())
}
#[inline]
pub fn get_all<'a>(&'a self, key: &'a str) -> impl Iterator<Item = &'a str> {
self.inner
.iter()
.filter(move |(k, _)| k == key)
.map(|(_, v)| v.as_str())
}
#[inline]
pub fn contains(&self, key: &str) -> bool {
self.inner.iter().any(|(k, _)| k == key)
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
self.inner.iter().map(|(k, v)| (k.as_str(), v.as_str()))
}
pub fn parse(query: &str) -> Self {
let mut params = Self::new();
for part in query.split('&') {
if part.is_empty() {
continue;
}
if let Some((key, value)) = part.split_once('=') {
let key = urlencoding::decode(key).unwrap_or(Cow::Borrowed(key));
let value = urlencoding::decode(value).unwrap_or(Cow::Borrowed(value));
params.push(key.into_owned(), value.into_owned());
} else {
let key = urlencoding::decode(part).unwrap_or(Cow::Borrowed(part));
params.push(key.into_owned(), String::new());
}
}
params
}
#[inline]
pub fn to_vec(&self) -> Vec<(String, String)> {
self.inner.to_vec()
}
}
impl fmt::Debug for QueryParams {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map()
.entries(self.inner.iter().map(|(k, v)| (k, v)))
.finish()
}
}
impl<'a> IntoIterator for &'a QueryParams {
type Item = (&'a str, &'a str);
type IntoIter = std::iter::Map<
std::slice::Iter<'a, (String, String)>,
fn(&'a (String, String)) -> (&'a str, &'a str),
>;
fn into_iter(self) -> Self::IntoIter {
self.inner.iter().map(|(k, v)| (k.as_str(), v.as_str()))
}
}
#[derive(Clone, Default)]
pub struct PathParams {
inner: SmallVec<[(String, String); PATH_PARAM_INLINE]>,
}
impl PathParams {
#[inline]
pub const fn new() -> Self {
Self {
inner: SmallVec::new_const(),
}
}
#[inline]
pub fn is_inline(&self) -> bool {
!self.inner.spilled()
}
#[inline]
pub fn len(&self) -> usize {
self.inner.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[inline]
pub fn push(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.inner.push((key.into(), value.into()));
}
#[inline]
pub fn get(&self, name: &str) -> Option<&str> {
self.inner
.iter()
.find(|(k, _)| k == name)
.map(|(_, v)| v.as_str())
}
#[inline]
pub fn get_index(&self, index: usize) -> Option<&str> {
self.inner.get(index).map(|(_, v)| v.as_str())
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
self.inner.iter().map(|(k, v)| (k.as_str(), v.as_str()))
}
}
impl fmt::Debug for PathParams {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map()
.entries(self.inner.iter().map(|(k, v)| (k, v)))
.finish()
}
}
#[derive(Clone, Default)]
pub struct FormFields {
inner: SmallVec<[(String, String); FORM_FIELD_INLINE]>,
}
impl FormFields {
#[inline]
pub const fn new() -> Self {
Self {
inner: SmallVec::new_const(),
}
}
#[inline]
pub fn is_inline(&self) -> bool {
!self.inner.spilled()
}
#[inline]
pub fn len(&self) -> usize {
self.inner.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[inline]
pub fn push(&mut self, name: impl Into<String>, value: impl Into<String>) {
self.inner.push((name.into(), value.into()));
}
#[inline]
pub fn get(&self, name: &str) -> Option<&str> {
self.inner
.iter()
.find(|(k, _)| k == name)
.map(|(_, v)| v.as_str())
}
#[inline]
pub fn contains(&self, name: &str) -> bool {
self.inner.iter().any(|(k, _)| k == name)
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
self.inner.iter().map(|(k, v)| (k.as_str(), v.as_str()))
}
pub fn parse(body: &str) -> Self {
let mut fields = Self::new();
for part in body.split('&') {
if part.is_empty() {
continue;
}
if let Some((key, value)) = part.split_once('=') {
let key = urlencoding::decode(key).unwrap_or(Cow::Borrowed(key));
let value = urlencoding::decode(value).unwrap_or(Cow::Borrowed(value));
fields.push(key.into_owned(), value.into_owned());
}
}
fields
}
}
impl fmt::Debug for FormFields {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map()
.entries(self.inner.iter().map(|(k, v)| (k, v)))
.finish()
}
}
#[derive(Clone, Default)]
pub struct Cookies {
inner: SmallVec<[(String, String); COOKIE_INLINE]>,
}
impl Cookies {
#[inline]
pub const fn new() -> Self {
Self {
inner: SmallVec::new_const(),
}
}
#[inline]
pub fn is_inline(&self) -> bool {
!self.inner.spilled()
}
#[inline]
pub fn len(&self) -> usize {
self.inner.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[inline]
pub fn push(&mut self, name: impl Into<String>, value: impl Into<String>) {
self.inner.push((name.into(), value.into()));
}
#[inline]
pub fn get(&self, name: &str) -> Option<&str> {
self.inner
.iter()
.find(|(k, _)| k == name)
.map(|(_, v)| v.as_str())
}
#[inline]
pub fn contains(&self, name: &str) -> bool {
self.inner.iter().any(|(k, _)| k == name)
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
self.inner.iter().map(|(k, v)| (k.as_str(), v.as_str()))
}
pub fn parse(cookie_header: &str) -> Self {
let mut cookies = Self::new();
for cookie in cookie_header.split(';') {
let cookie = cookie.trim();
if cookie.is_empty() {
continue;
}
if let Some((name, value)) = cookie.split_once('=') {
cookies.push(name.trim().to_string(), value.trim().to_string());
}
}
cookies
}
}
impl fmt::Debug for Cookies {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map()
.entries(self.inner.iter().map(|(k, v)| (k, v)))
.finish()
}
}
pub trait SmallVecExt<T> {
fn is_inline(&self) -> bool;
fn stack_size() -> usize;
}
impl<T, const N: usize> SmallVecExt<T> for SmallVec<[T; N]> {
#[inline]
fn is_inline(&self) -> bool {
!self.spilled()
}
#[inline]
fn stack_size() -> usize {
std::mem::size_of::<SmallVec<[T; N]>>()
}
}
#[inline]
pub fn vec_to_small<T, const N: usize>(vec: Vec<T>) -> SmallVec<[T; N]> {
SmallVec::from_vec(vec)
}
#[inline]
pub fn collect_small<T, I, const N: usize>(iter: I) -> SmallVec<[T; N]>
where
I: IntoIterator<Item = T>,
{
iter.into_iter().collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_query_params_inline() {
let mut params = QueryParams::new();
for i in 0..QUERY_PARAM_INLINE {
params.push(format!("key{}", i), format!("value{}", i));
}
assert!(params.is_inline());
assert_eq!(params.len(), QUERY_PARAM_INLINE);
params.push("extra", "value");
assert!(!params.is_inline());
}
#[test]
fn test_query_params_parse() {
let params = QueryParams::parse("name=Alice&age=30&city=NYC");
assert_eq!(params.get("name"), Some("Alice"));
assert_eq!(params.get("age"), Some("30"));
assert_eq!(params.get("city"), Some("NYC"));
assert!(params.is_inline());
}
#[test]
fn test_query_params_url_decode() {
let params = QueryParams::parse("name=Hello%20World&emoji=%F0%9F%98%80");
assert_eq!(params.get("name"), Some("Hello World"));
assert_eq!(params.get("emoji"), Some("😀"));
}
#[test]
fn test_path_params_inline() {
let mut params = PathParams::new();
params.push("id", "123");
params.push("name", "test");
assert!(params.is_inline());
assert_eq!(params.get("id"), Some("123"));
assert_eq!(params.get_index(0), Some("123"));
}
#[test]
fn test_form_fields_inline() {
let mut fields = FormFields::new();
for i in 0..FORM_FIELD_INLINE {
fields.push(format!("field{}", i), format!("value{}", i));
}
assert!(fields.is_inline());
}
#[test]
fn test_cookies_parse() {
let cookies = Cookies::parse("session=abc123; user=alice; theme=dark");
assert_eq!(cookies.get("session"), Some("abc123"));
assert_eq!(cookies.get("user"), Some("alice"));
assert_eq!(cookies.get("theme"), Some("dark"));
assert!(cookies.is_inline());
}
#[test]
fn test_small_vec_stack_size() {
assert!(SmallVec::<[(String, String); QUERY_PARAM_INLINE]>::stack_size() < 512);
assert!(SmallVec::<[(String, String); PATH_PARAM_INLINE]>::stack_size() < 256);
}
}