use core::fmt;
use core::hash::Hash;
use crate::std::borrow::Cow;
use crate::std::string::String;
use crate::std::vec::Vec;
use super::component_input::IntoUriComponent;
use super::encode::{
encoded_pair_component, encoded_query, encoded_query_cmp, encoded_query_eq,
extend_encoded_query, hash_encoded_query, write_encoded_query,
};
use rama_core::bytes::{Bytes, BytesMut};
use percent_encoding::percent_decode;
#[derive(Debug, Clone, Default)]
pub struct Query {
pub(crate) bytes: BytesMut,
}
impl Query {
#[must_use]
pub fn as_encoded_str(&self) -> Cow<'_, str> {
encoded_query(&self.bytes)
}
#[must_use]
#[inline]
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
#[must_use]
pub fn as_decoded_str(&self) -> Cow<'_, str> {
percent_decode(&self.bytes).decode_utf8_lossy()
}
#[must_use]
#[inline]
pub fn view(&self) -> QueryRef<'_> {
QueryRef { bytes: &self.bytes }
}
#[must_use]
pub fn pairs(&self) -> QueryPairs<'_> {
QueryPairs::new(&self.bytes)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn deserialize<'de, T>(&'de self) -> Result<T, QueryDeserializeError>
where
T: serde::de::Deserialize<'de>,
{
self.view().deserialize::<T>()
}
#[must_use]
pub fn first_value(&self, name: impl IntoUriComponent) -> Option<Cow<'_, str>> {
self.view().first_value(name)
}
#[must_use]
pub fn values(&self, name: impl IntoUriComponent) -> QueryValues<'_> {
self.view().values(name)
}
#[must_use]
pub fn contains_name(&self, name: impl IntoUriComponent) -> bool {
self.view().contains_name(name)
}
}
impl PartialEq for Query {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.view() == other.view()
}
}
impl Eq for Query {}
impl PartialOrd for Query {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Query {
#[inline(always)]
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.view().cmp(&other.view())
}
}
impl Hash for Query {
#[inline(always)]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.view().hash(state);
}
}
const COLLECT_BYTES_PER_PAIR: usize = 32;
fn collect_pairs<'a, I>(iter: I) -> Query
where
I: IntoIterator,
I::Item: AsRef<[u8]> + 'a,
{
let iter = iter.into_iter();
let mut bytes = BytesMut::with_capacity(iter.size_hint().0 * COLLECT_BYTES_PER_PAIR);
for pair in iter {
if !bytes.is_empty() {
bytes.extend_from_slice(b"&");
}
bytes.extend_from_slice(pair.as_ref());
}
Query { bytes }
}
impl FromIterator<QueryPair> for Query {
fn from_iter<I: IntoIterator<Item = QueryPair>>(iter: I) -> Self {
collect_pairs(iter.into_iter().map(|p| p.raw))
}
}
impl<'a> FromIterator<QueryPairRef<'a>> for Query {
fn from_iter<I: IntoIterator<Item = QueryPairRef<'a>>>(iter: I) -> Self {
collect_pairs(iter.into_iter().map(|p| p.raw))
}
}
#[derive(Debug, Clone, Copy)]
pub struct QueryRef<'a> {
pub(crate) bytes: &'a [u8],
}
impl<'a> QueryRef<'a> {
#[must_use]
#[inline]
pub(crate) const fn new(bytes: &'a [u8]) -> Self {
Self { bytes }
}
#[must_use]
#[inline]
pub fn from_raw_str(query: &'a str) -> Self {
Self::new(query.as_bytes())
}
#[must_use]
pub fn as_encoded_str(self) -> Cow<'a, str> {
encoded_query(self.bytes)
}
#[must_use]
#[inline]
pub fn is_empty(self) -> bool {
self.bytes.is_empty()
}
pub(super) fn write_encoded_to(self, buf: &mut BytesMut) {
extend_encoded_query(buf, self.bytes);
}
#[must_use]
pub fn as_decoded_str(&self) -> Cow<'a, str> {
percent_decode(self.bytes).decode_utf8_lossy()
}
#[must_use]
pub fn into_owned(self) -> Query {
Query {
bytes: BytesMut::from(self.bytes),
}
}
#[must_use]
pub fn pairs(&self) -> QueryPairs<'a> {
QueryPairs::new(self.bytes)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn deserialize<T>(&self) -> Result<T, QueryDeserializeError>
where
T: serde::de::Deserialize<'a>,
{
match self.as_encoded_str() {
Cow::Borrowed(encoded) => {
serde_html_form::from_str(encoded).map_err(QueryDeserializeError)
}
Cow::Owned(_) => Err(QueryDeserializeError(
<serde_html_form::de::Error as serde::de::Error>::custom(
"query contains component text that needs encoding before deserialization",
),
)),
}
}
#[must_use]
pub fn first_value(self, name: impl IntoUriComponent) -> Option<Cow<'a, str>> {
self.values(name).next()
}
#[must_use]
#[expect(
clippy::needless_pass_by_value,
reason = "by-value matches IntoUriComponent's signature on sibling matchers; this impl only borrows the input"
)]
pub fn values(self, name: impl IntoUriComponent) -> QueryValues<'a> {
QueryValues {
pairs: self.pairs(),
name: form_decode_bytes(&name.as_uri_component_bytes()).into_owned(),
}
}
#[must_use]
#[expect(
clippy::needless_pass_by_value,
reason = "by-value matches IntoUriComponent's signature on sibling matchers; this impl only borrows the input"
)]
pub fn contains_name(self, name: impl IntoUriComponent) -> bool {
let name = name.as_uri_component_bytes();
let pattern = form_decode_bytes(&name);
self.pairs()
.any(|p| form_decode_bytes(p.name_bytes()) == pattern)
}
}
impl PartialEq for QueryRef<'_> {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
encoded_query_eq(self.bytes, other.bytes)
}
}
impl Eq for QueryRef<'_> {}
impl PartialEq<str> for QueryRef<'_> {
#[inline(always)]
fn eq(&self, other: &str) -> bool {
self.eq(&QueryRef::from_raw_str(other))
}
}
impl PartialEq<&str> for QueryRef<'_> {
#[inline(always)]
fn eq(&self, other: &&str) -> bool {
self.eq(&QueryRef::from_raw_str(other))
}
}
impl<'a> PartialEq<QueryRef<'a>> for str {
#[inline(always)]
fn eq(&self, other: &QueryRef<'a>) -> bool {
QueryRef::from_raw_str(self).eq(other)
}
}
impl<'a> PartialEq<QueryRef<'a>> for &str {
#[inline(always)]
fn eq(&self, other: &QueryRef<'a>) -> bool {
QueryRef::from_raw_str(self).eq(other)
}
}
impl PartialOrd for QueryRef<'_> {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for QueryRef<'_> {
#[inline(always)]
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
encoded_query_cmp(self.bytes, other.bytes)
}
}
impl Hash for QueryRef<'_> {
#[inline(always)]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
hash_encoded_query(state, self.bytes);
}
}
#[derive(Debug)]
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub struct QueryDeserializeError(serde_html_form::de::Error);
#[cfg(feature = "std")]
impl fmt::Display for QueryDeserializeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "failed to deserialize URI query: {}", self.0)
}
}
#[cfg(feature = "std")]
impl core::error::Error for QueryDeserializeError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(&self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct QueryPair {
raw: Bytes,
eq_at: Option<u32>,
}
impl QueryPair {
#[inline]
#[must_use]
pub(crate) fn from_raw(raw: Bytes) -> Self {
let eq_at = memchr::memchr(b'=', &raw).map(|i| i as u32);
Self { raw, eq_at }
}
#[must_use]
#[inline]
pub fn view(&self) -> QueryPairRef<'_> {
QueryPairRef {
raw: &self.raw,
eq_at: self.eq_at,
}
}
#[must_use]
#[inline]
pub fn name_encoded(&self) -> Cow<'_, str> {
self.view().name_encoded()
}
#[must_use]
#[inline]
pub fn name_decoded(&self) -> Cow<'_, str> {
self.view().name_decoded()
}
#[must_use]
#[inline]
pub fn value_encoded(&self) -> Option<Cow<'_, str>> {
self.view().value_encoded()
}
#[must_use]
#[inline]
pub fn value_decoded(&self) -> Option<Cow<'_, str>> {
self.view().value_decoded()
}
#[must_use]
#[inline]
pub fn has_value(&self) -> bool {
self.view().has_value()
}
}
impl core::fmt::Display for QueryPair {
#[inline(always)]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(&self.view(), f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct QueryPairRef<'a> {
raw: &'a [u8],
eq_at: Option<u32>,
}
impl<'a> QueryPairRef<'a> {
#[inline]
#[must_use]
pub(crate) fn from_raw(raw: &'a [u8]) -> Self {
let eq_at = memchr::memchr(b'=', raw).map(|i| i as u32);
Self { raw, eq_at }
}
#[must_use]
pub fn name_encoded(self) -> Cow<'a, str> {
encoded_pair_component(self.name_bytes())
}
#[must_use]
pub fn name_decoded(self) -> Cow<'a, str> {
form_decode(self.name_bytes())
}
#[must_use]
pub fn value_encoded(self) -> Option<Cow<'a, str>> {
self.value_bytes().map(encoded_pair_component)
}
#[must_use]
pub fn value_decoded(self) -> Option<Cow<'a, str>> {
self.value_bytes().map(form_decode)
}
#[must_use]
pub fn has_value(self) -> bool {
self.eq_at.is_some()
}
#[must_use]
pub fn into_owned(self) -> QueryPair {
QueryPair {
raw: Bytes::copy_from_slice(self.raw),
eq_at: self.eq_at,
}
}
#[inline(always)]
pub(super) fn raw_bytes(self) -> &'a [u8] {
self.raw
}
#[inline(always)]
pub(super) fn name_bytes(self) -> &'a [u8] {
match self.eq_at {
Some(i) => &self.raw[..i as usize],
None => self.raw,
}
}
#[inline(always)]
pub(super) fn value_bytes(self) -> Option<&'a [u8]> {
self.eq_at.map(|i| &self.raw[i as usize + 1..])
}
}
impl core::fmt::Display for QueryPairRef<'_> {
#[inline(always)]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write_encoded_query(f, self.raw)
}
}
#[derive(Debug, Clone)]
pub struct QueryPairs<'a> {
remaining: &'a [u8],
exhausted: bool,
}
impl<'a> QueryPairs<'a> {
#[inline]
fn new(bytes: &'a [u8]) -> Self {
Self {
remaining: bytes,
exhausted: bytes.is_empty(),
}
}
}
impl<'a> Iterator for QueryPairs<'a> {
type Item = QueryPairRef<'a>;
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.exhausted {
return None;
}
let fragment = if let Some(i) = memchr::memchr(b'&', self.remaining) {
let frag = &self.remaining[..i];
self.remaining = &self.remaining[i + 1..];
frag
} else {
let frag = self.remaining;
self.remaining = &[];
self.exhausted = true;
frag
};
if fragment.is_empty() {
continue;
}
return Some(QueryPairRef::from_raw(fragment));
}
}
}
impl core::iter::FusedIterator for QueryPairs<'_> {}
#[derive(Debug, Clone)]
pub struct QueryValues<'a> {
pairs: QueryPairs<'a>,
name: Vec<u8>,
}
impl<'a> Iterator for QueryValues<'a> {
type Item = Cow<'a, str>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let pair = self.pairs.next()?;
if *form_decode_bytes(pair.name_bytes()) == *self.name {
return Some(pair.value_decoded().unwrap_or(Cow::Borrowed("")));
}
}
}
}
impl core::iter::FusedIterator for QueryValues<'_> {}
pub(super) fn form_decode_bytes(input: &[u8]) -> Cow<'_, [u8]> {
let Some(start) = memchr::memchr2(b'+', b'%', input) else {
return Cow::Borrowed(input);
};
let mut out = Vec::with_capacity(input.len());
out.extend_from_slice(&input[..start]);
let mut i = start;
while i < input.len() {
match input[i] {
b'+' => {
out.push(b' ');
i += 1;
}
b'%' if i + 2 < input.len() => {
if let Some(byte) = rama_utils::hex::decode_pair(input[i + 1], input[i + 2]) {
out.push(byte);
i += 3;
} else {
out.push(b'%');
i += 1;
}
}
b => {
out.push(b);
i += 1;
}
}
}
Cow::Owned(out)
}
fn form_decode(input: &[u8]) -> Cow<'_, str> {
match form_decode_bytes(input) {
Cow::Borrowed(bytes) => Cow::Borrowed(unsafe { core::str::from_utf8_unchecked(bytes) }),
Cow::Owned(out) => match String::from_utf8(out) {
Ok(s) => Cow::Owned(s),
Err(e) => Cow::Owned(String::from_utf8_lossy(e.as_bytes()).into_owned()),
},
}
}
impl fmt::Display for Query {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.view(), f)
}
}
impl fmt::Display for QueryRef<'_> {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_encoded_query(f, self.bytes)
}
}
impl core::str::FromStr for Query {
type Err = core::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self {
bytes: super::encode::encode_query(s),
})
}
}
#[cfg(test)]
mod internal_tests {
use crate::std::borrow::Cow;
use super::form_decode;
#[test]
fn form_decode_empty_borrows() {
let out = form_decode(b"");
assert!(matches!(out, Cow::Borrowed(_)));
assert_eq!(&*out, "");
}
#[test]
fn form_decode_borrowed_path_is_zero_copy() {
let input: &[u8] = b"no-escapes-here";
let out = form_decode(input);
match out {
Cow::Borrowed(s) => {
assert_eq!(s.as_ptr(), input.as_ptr(), "borrowed view re-allocated");
}
Cow::Owned(_) => panic!("expected Cow::Borrowed for input without `+` or `%`"),
}
}
#[test]
fn form_decode_pct_2b_is_literal_plus_not_space() {
assert_eq!(form_decode(b"%2B"), Cow::Borrowed("+"));
assert_eq!(form_decode(b"a%2Bb"), Cow::Borrowed("a+b"));
}
#[test]
fn form_decode_pct_delimiter_bytes() {
assert_eq!(form_decode(b"%26"), Cow::Borrowed("&"));
assert_eq!(form_decode(b"%3D"), Cow::Borrowed("="));
assert_eq!(form_decode(b"a%26b%3Dc"), Cow::Borrowed("a&b=c"));
}
#[test]
fn form_decode_pct_00_null_byte() {
let out = form_decode(b"a%00b");
assert_eq!(out.as_bytes(), b"a\x00b");
}
#[test]
fn form_decode_three_byte_utf8() {
assert_eq!(form_decode(b"%E2%82%AC"), Cow::Borrowed("€"));
}
#[test]
fn form_decode_four_byte_utf8() {
assert_eq!(form_decode(b"%F0%9F%98%80"), Cow::Borrowed("\u{1F600}"));
}
#[test]
fn form_decode_truncated_utf8_replacement() {
let out = form_decode(b"%E2%82");
assert!(out.contains('\u{FFFD}'), "got {out:?}");
}
#[test]
fn form_decode_mixed_input() {
assert_eq!(
form_decode(b"hello+world%20%21"),
Cow::Borrowed("hello world !"),
);
}
#[test]
fn form_decode_long_string() {
const N: usize = 1000;
let mut input = Vec::with_capacity(6 * N);
for _ in 0..N {
input.extend_from_slice(b"a+b%20");
}
let out = form_decode(&input);
assert_eq!(out.len(), 4 * N);
assert!(out.starts_with("a b a b "));
assert!(out.ends_with("a b a b "));
}
#[test]
fn form_decode_malformed_pct_literal_passthrough() {
assert_eq!(form_decode(b"%ZZ"), Cow::Borrowed("%ZZ"));
assert_eq!(form_decode(b"%G0"), Cow::Borrowed("%G0"));
assert_eq!(form_decode(b"%-1"), Cow::Borrowed("%-1"));
}
#[test]
fn form_decode_trailing_percent_variants() {
assert_eq!(form_decode(b"%"), Cow::Borrowed("%"));
assert_eq!(form_decode(b"a%"), Cow::Borrowed("a%"));
assert_eq!(form_decode(b"a%A"), Cow::Borrowed("a%A"));
}
}