#![warn(clippy::all)]
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
use std::iter::Iterator;
#[derive(Clone, Debug, PartialEq, Default)]
pub struct QString {
pairs: Vec<(String, QValue)>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum QValue {
Empty,
Value(String),
}
impl From<String> for QValue {
fn from(s: String) -> QValue {
QValue::Value(s)
}
}
impl QString {
pub fn new<S, T>(params: Vec<(S, T)>) -> QString
where
S: Into<String>,
T: Into<String>,
{
QString {
pairs: params
.into_iter()
.map(|(k, v)| (k.into(), QValue::Value(v.into())))
.collect(),
}
}
pub fn has(&self, name: &str) -> bool {
self.pairs.iter().any(|p| p.0 == name)
}
pub fn get<'a>(&'a self, name: &str) -> Option<&'a str> {
self.pairs
.iter()
.find(|p| p.0 == name)
.and_then(|p| match p.1 {
QValue::Empty => Some(""),
QValue::Value(ref s) => Some(s),
})
}
pub fn into_pairs(self) -> Vec<(String, String)> {
self.pairs
.into_iter()
.map(|p| {
(
p.0,
match p.1 {
QValue::Empty => "".to_string(),
QValue::Value(s) => s,
},
)
})
.collect()
}
pub fn to_pairs(&self) -> Vec<(&str, &str)> {
self.pairs
.iter()
.map(|p| {
(
p.0.as_str(),
match p.1 {
QValue::Empty => "",
QValue::Value(ref s) => s.as_str(),
},
)
})
.collect()
}
pub fn add_pair<S, T>(&mut self, pair: (S, T))
where
S: Into<String>,
T: Into<String>,
{
self.pairs
.push((pair.0.into(), QValue::Value(pair.1.into())));
}
pub fn add_str(&mut self, origin: &str) {
let mut to_add = str_to_pairs(origin);
self.pairs.append(&mut to_add);
}
pub fn len(&self) -> usize {
self.pairs.len()
}
pub fn is_empty(&self) -> bool {
self.pairs.is_empty()
}
}
impl<'a> From<&'a str> for QString {
fn from(origin: &str) -> Self {
QString {
pairs: str_to_pairs(origin),
}
}
}
fn str_to_pairs(origin: &str) -> Vec<(String, QValue)> {
let mut cur = origin;
if !cur.is_empty() && &cur[0..1] == "?" {
cur = &cur[1..];
}
let mut params = vec![];
while !cur.is_empty() {
if &cur[0..1] == "&" {
cur = &cur[1..];
continue;
}
let (name, rest) = match cur.find('=') {
None => match cur.find('&') {
None => {
params.push((decode(&cur[..]), QValue::Empty));
break;
}
Some(pos) => {
params.push((decode(&cur[..pos]), QValue::Empty));
cur = &cur[(pos + 1)..];
continue;
}
},
Some(pos) => (&cur[..pos], &cur[(pos + 1)..]),
};
if name.is_empty() {
cur = rest;
continue;
}
let (value, newcur) = match rest.find('&') {
None => (rest, ""),
Some(pos) => (&rest[..pos], &rest[(pos + 1)..]),
};
params.push((decode(name), QValue::Value(decode(value))));
cur = newcur;
}
params
}
impl IntoIterator for QString {
type Item = (String, String);
type IntoIter = ::std::vec::IntoIter<(String, String)>;
fn into_iter(self) -> Self::IntoIter {
self.into_pairs().into_iter()
}
}
impl Into<Vec<(String, String)>> for QString {
fn into(self) -> Vec<(String, String)> {
self.into_pairs()
}
}
impl Into<String> for QString {
fn into(self) -> String {
format!("{}", self)
}
}
impl ::std::fmt::Display for QString {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
for (idx, p) in self.pairs.iter().enumerate() {
write!(
f,
"{}{}{}",
(if idx == 0 { "" } else { "&" }),
encode(&p.0),
match p.1 {
QValue::Empty => "".to_string(),
QValue::Value(ref s) => format!("={}", encode(s)),
}
)?;
}
Ok(())
}
}
fn decode(s: &str) -> String {
percent_decode(s.as_bytes())
.decode_utf8()
.map(|cow| cow.into_owned())
.unwrap_or_else(|_| s.to_string())
}
const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
fn encode(s: &str) -> String {
utf8_percent_encode(s, FRAGMENT).to_string()
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! test {
($func_name:ident, $origin:expr, $result:expr) => (
#[test]
fn $func_name() {
let qs = QString::from($origin);
let ps: Vec<(String, String)> = qs.into_pairs();
let cs: Vec<(String, String)> = ($result as Vec<(&str, &str)>)
.into_iter().map(|(k,v)| (k.to_string(), v.to_string()))
.collect();
assert_eq!(ps, cs);
}
)
}
test!(empty_1, "", vec![]);
test!(empty_2, "?", vec![]);
test!(empty_3, "&", vec![]);
test!(empty_4, "=", vec![]);
test!(empty_5, "?=", vec![]);
test!(empty_6, "?&", vec![]);
test!(a_is_1, "a", vec![("a", "")]);
test!(a_is_2, "a=", vec![("a", "")]);
test!(a_is_3, "a=b", vec![("a", "b")]);
test!(a_is_4, "?a", vec![("a", "")]);
test!(a_is_5, "?a=", vec![("a", "")]);
test!(a_is_6, "?a=b", vec![("a", "b")]);
test!(a_is_7, "?&a", vec![("a", "")]);
test!(a_is_8, "?&a=", vec![("a", "")]);
test!(a_is_9, "?&a=b", vec![("a", "b")]);
test!(a_is_10, "?a=&", vec![("a", "")]);
test!(a_is_11, "?=a", vec![("a", "")]);
test!(a_is_eq_1, "a==", vec![("a", "=")]);
test!(is_q_1, "??", vec![("?", "")]);
test!(is_q_2, "&?", vec![("?", "")]);
test!(is_q_3, "??a", vec![("?a", "")]);
test!(is_q_4, "&?a", vec![("?a", "")]);
test!(ac_is_1, "?a&c", vec![("a", ""), ("c", "")]);
test!(ac_is_2, "?a&c&", vec![("a", ""), ("c", "")]);
test!(ac_is_3, "?a=&c", vec![("a", ""), ("c", "")]);
test!(ac_is_4, "?a=&c=", vec![("a", ""), ("c", "")]);
test!(ac_is_5, "?a=b&c=", vec![("a", "b"), ("c", "")]);
test!(ac_is_6, "?a=&c=d", vec![("a", ""), ("c", "d")]);
test!(ac_is_7, "?a=b&c=d", vec![("a", "b"), ("c", "d")]);
}