use std::fmt;
#[derive(Clone, Debug, Default)]
pub struct Query {
pairs: Vec<(String, String)>,
raw: Option<String>,
}
impl PartialEq for Query {
fn eq(&self, other: &Self) -> bool {
self.pairs == other.pairs
}
}
impl Eq for Query {}
impl Query {
#[must_use]
pub fn new() -> Self {
Self {
pairs: Vec::new(),
raw: None,
}
}
pub fn parse(s: &str) -> Result<Self, QueryError> {
if s.is_empty() {
return Ok(Self::new());
}
let raw = Some(s.to_string());
let mut pairs = Vec::new();
for pair_str in s.split('&') {
if pair_str.is_empty() {
continue;
}
if let Some(eq_pos) = pair_str.find('=') {
let key = &pair_str[..eq_pos];
let value = &pair_str[eq_pos + 1..];
let decoded_key = percent_decode(key)?;
let decoded_value = percent_decode(value)?;
pairs.push((decoded_key, decoded_value));
} else {
let decoded_key = percent_decode(pair_str)?;
pairs.push((decoded_key, String::new()));
}
}
Ok(Self { pairs, raw })
}
pub fn append(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.pairs.push((key.into(), value.into()));
self.raw = None; }
#[must_use]
pub fn get(&self, key: &str) -> Option<&str> {
self.pairs
.iter()
.find(|(k, _)| k == key)
.map(|(_, v)| v.as_str())
}
#[must_use]
pub fn get_all(&self, key: &str) -> Vec<&str> {
self.pairs
.iter()
.filter(|(k, _)| k == key)
.map(|(_, v)| v.as_str())
.collect()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.pairs.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.pairs.len()
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
self.pairs.iter().map(|(k, v)| (k.as_str(), v.as_str()))
}
pub fn retain(&mut self, mut f: impl FnMut(&str, &str) -> bool) {
self.pairs.retain(|(k, v)| f(k.as_str(), v.as_str()));
self.raw = None; }
}
impl fmt::Display for Query {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(raw) = &self.raw {
return write!(f, "{raw}");
}
for (i, (key, value)) in self.pairs.iter().enumerate() {
if i > 0 {
write!(f, "&")?;
}
write!(f, "{}={}", percent_encode(key), percent_encode(value))?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QueryError {
message: String,
}
impl QueryError {
fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
}
impl fmt::Display for QueryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "query parse error: {}", self.message)
}
}
impl std::error::Error for QueryError {}
pub fn percent_decode(s: &str) -> Result<String, QueryError> {
let mut result = String::with_capacity(s.len());
let mut chars = s.chars();
while let Some(c) = chars.next() {
match c {
'+' => result.push(' '),
'%' => {
let hex1 = chars.next().ok_or_else(|| {
QueryError::new("incomplete percent-encoding (missing first hex digit)")
})?;
let hex2 = chars.next().ok_or_else(|| {
QueryError::new("incomplete percent-encoding (missing second hex digit)")
})?;
let hex_str = format!("{hex1}{hex2}");
let byte = u8::from_str_radix(&hex_str, 16).map_err(|_| {
QueryError::new(format!("invalid percent-encoding: %{hex_str}"))
})?;
result.push(byte as char);
}
_ => result.push(c),
}
}
Ok(result)
}
#[must_use]
pub fn percent_encode(s: &str) -> String {
let mut result = String::with_capacity(s.len() * 3);
for byte in s.bytes() {
match byte {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
result.push(byte as char);
}
b' ' => result.push('+'),
_ => {
result.push('%');
result.push_str(&format!("{byte:02X}"));
}
}
}
result
}