use std::{collections::HashMap, error, fmt};
pub struct Query;
impl Query {
#[inline(always)]
pub fn parse<'a, C: QueryCollector<'a>>(query: &'a [u8], limit: usize) -> Result<C, Error> {
let mut result = C::with_capacity(limit);
Self::parse_into(&mut result, query, limit)?;
Ok(result)
}
#[inline]
pub fn parse_into<'a, C: QueryCollector<'a>>(
result: &mut C,
query: &'a [u8],
limit: usize,
) -> Result<(), Error> {
let data = match query.first().ok_or(Error::Empty)? {
b'?' => &query[1..],
_ => query,
};
let mut start = 0;
while start < data.len() {
if result.length() >= limit {
return Err(Error::OverLimit(limit));
}
let end = &data[start..]
.iter()
.position(|&c| c == b'&')
.map(|pos| start + pos)
.unwrap_or(data.len());
let index = &data[start..*end]
.iter()
.position(|&c| c == b'=')
.unwrap_or(end - start);
let split_index = start + index;
let key = &data[start..split_index];
let value = match split_index < *end {
true => &data[split_index + 1..*end], false => b"", };
result.add_param(key, value);
start = end + 1;
}
Ok(())
}
}
pub trait QueryCollector<'a>
where
Self: Sized,
{
fn add_param(&mut self, key: &'a [u8], value: &'a [u8]);
fn length(&self) -> usize;
fn with_capacity(capacity: usize) -> Self;
}
impl<'a> QueryCollector<'a> for Vec<(&'a [u8], &'a [u8])> {
#[inline(always)]
fn add_param(&mut self, key: &'a [u8], value: &'a [u8]) {
self.push((key, value));
}
#[inline(always)]
fn length(&self) -> usize {
self.len()
}
#[inline(always)]
fn with_capacity(capacity: usize) -> Self {
Vec::with_capacity(capacity)
}
}
impl<'a> QueryCollector<'a> for HashMap<&'a [u8], &'a [u8]> {
#[inline(always)]
fn add_param(&mut self, key: &'a [u8], value: &'a [u8]) {
self.insert(key, value);
}
#[inline(always)]
fn length(&self) -> usize {
self.len()
}
#[inline(always)]
fn with_capacity(capacity: usize) -> Self {
HashMap::with_capacity(capacity)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
OverLimit(usize),
Empty,
}
impl error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::OverLimit(limit) => {
write!(f, "Query parameter limit exceeded: limit={}", limit)
}
Error::Empty => {
write!(f, "Query string is empty or contains no parameters")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tools::*;
#[test]
fn basic() {
let cases = ["a=1&b=2", "?a=1&b=2"];
for line in cases {
let params: Vec<(&[u8], &[u8])> = Query::parse(line.as_bytes(), 8).unwrap();
assert_eq!(params.len(), 2);
assert_eq!(str_2(params[0]), ("a", "1"));
assert_eq!(str_2(params[1]), ("b", "2"));
}
}
#[test]
fn full() {
let line = b"flag&empty=&=val&&key=value";
let params: Vec<(&[u8], &[u8])> = Query::parse(line, 10).unwrap();
assert_eq!(params.len(), 5);
assert_eq!(str_2(params[0]), ("flag", ""));
assert_eq!(str_2(params[1]), ("empty", ""));
assert_eq!(str_2(params[2]), ("", "val"));
assert_eq!(str_2(params[3]), ("", ""));
assert_eq!(str_2(params[4]), ("key", "value"));
}
#[test]
fn not_complete() {
let params: Vec<(&[u8], &[u8])> = Query::parse(b"flag&empty=&=val", 10).unwrap();
assert_eq!(params.len(), 3);
assert_eq!(str_2(params[0]), ("flag", ""));
assert_eq!(str_2(params[1]), ("empty", ""));
assert_eq!(str_2(params[2]), ("", "val"));
}
#[test]
fn limit_error() {
assert_eq!(
Query::parse::<Vec<(&[u8], &[u8])>>(b"a&a", 1),
Err(Error::OverLimit(1))
);
}
#[test]
fn empty_error() {
assert_eq!(
Query::parse::<Vec<(&[u8], &[u8])>>(b"", 10),
Err(Error::Empty)
);
}
}