1#![warn(clippy::all)]
2
3use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
4use std::iter::Iterator;
5
6#[derive(Clone, Debug, PartialEq, Default)]
37pub struct QString {
38 pairs: Vec<(String, QValue)>,
39}
40
41#[derive(Clone, Debug, PartialEq)]
42pub enum QValue {
43 Empty,
44 Value(String),
45}
46
47impl From<String> for QValue {
48 fn from(s: String) -> QValue {
49 QValue::Value(s)
50 }
51}
52
53impl QString {
54 pub fn new<S, T>(params: Vec<(S, T)>) -> QString
64 where
65 S: Into<String>,
66 T: Into<String>,
67 {
68 QString {
69 pairs: params
70 .into_iter()
71 .map(|(k, v)| (k.into(), QValue::Value(v.into())))
72 .collect(),
73 }
74 }
75
76 pub fn has(&self, name: &str) -> bool {
84 self.pairs.iter().any(|p| p.0 == name)
85 }
86
87 pub fn get<'a>(&'a self, name: &str) -> Option<&'a str> {
97 self.pairs
98 .iter()
99 .find(|p| p.0 == name)
100 .and_then(|p| match p.1 {
101 QValue::Empty => Some(""),
102 QValue::Value(ref s) => Some(s),
103 })
104 }
105
106 pub fn into_pairs(self) -> Vec<(String, String)> {
117 self.pairs
118 .into_iter()
119 .map(|p| {
120 (
121 p.0,
122 match p.1 {
123 QValue::Empty => "".to_string(),
124 QValue::Value(s) => s,
125 },
126 )
127 })
128 .collect()
129 }
130
131 pub fn to_pairs(&self) -> Vec<(&str, &str)> {
142 self.pairs
143 .iter()
144 .map(|p| {
145 (
146 p.0.as_str(),
147 match p.1 {
148 QValue::Empty => "",
149 QValue::Value(ref s) => s.as_str(),
150 },
151 )
152 })
153 .collect()
154 }
155
156 pub fn add_pair<S, T>(&mut self, pair: (S, T))
166 where
167 S: Into<String>,
168 T: Into<String>,
169 {
170 self.pairs
171 .push((pair.0.into(), QValue::Value(pair.1.into())));
172 }
173
174 pub fn add_str(&mut self, origin: &str) {
184 let mut to_add = str_to_pairs(origin);
185 self.pairs.append(&mut to_add);
186 }
187
188 pub fn len(&self) -> usize {
190 self.pairs.len()
191 }
192
193 pub fn is_empty(&self) -> bool {
195 self.pairs.is_empty()
196 }
197}
198
199impl<'a> From<&'a str> for QString {
200 fn from(origin: &str) -> Self {
211 QString {
212 pairs: str_to_pairs(origin),
213 }
214 }
215}
216
217fn str_to_pairs(origin: &str) -> Vec<(String, QValue)> {
218 let mut cur = origin;
220
221 if !cur.is_empty() && &cur[0..1] == "?" {
223 cur = &cur[1..];
224 }
225
226 let mut params = vec![];
228
229 while !cur.is_empty() {
230 if &cur[0..1] == "&" {
232 cur = &cur[1..];
233 continue;
234 }
235 let (name, rest) = match cur.find('=') {
237 None => match cur.find('&') {
239 None => {
241 params.push((decode(&cur[..]), QValue::Empty));
242 break;
243 }
244 Some(pos) => {
247 params.push((decode(&cur[..pos]), QValue::Empty));
248 cur = &cur[(pos + 1)..];
249 continue;
250 }
251 },
252 Some(pos) => {
253 if let Some(apos) = cur.find('&') {
254 if apos < pos {
255 params.push((decode(&cur[..apos]), QValue::Empty));
256 cur = &cur[(apos + 1)..];
257 continue;
258 }
259 }
260 (&cur[..pos], &cur[(pos + 1)..])
261 }
262 };
263 if name.is_empty() {
265 cur = rest;
266 continue;
267 }
268 let (value, newcur) = match rest.find('&') {
270 None => (rest, ""),
272 Some(pos) => (&rest[..pos], &rest[(pos + 1)..]),
274 };
275 params.push((decode(name), QValue::Value(decode(value))));
277 cur = newcur;
278 }
279 params
280}
281
282impl IntoIterator for QString {
283 type Item = (String, String);
284 type IntoIter = ::std::vec::IntoIter<(String, String)>;
285 fn into_iter(self) -> Self::IntoIter {
286 self.into_pairs().into_iter()
287 }
288}
289
290impl Into<Vec<(String, String)>> for QString {
291 fn into(self) -> Vec<(String, String)> {
292 self.into_pairs()
293 }
294}
295
296impl Into<String> for QString {
297 fn into(self) -> String {
298 format!("{}", self)
299 }
300}
301
302impl ::std::fmt::Display for QString {
303 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
304 for (idx, p) in self.pairs.iter().enumerate() {
305 write!(
306 f,
307 "{}{}{}",
308 (if idx == 0 { "" } else { "&" }),
309 encode(&p.0),
310 match p.1 {
311 QValue::Empty => "".to_string(),
312 QValue::Value(ref s) => format!("={}", encode(s)),
313 }
314 )?;
315 }
316 Ok(())
317 }
318}
319
320fn decode(s: &str) -> String {
321 percent_decode(s.as_bytes())
322 .decode_utf8()
323 .map(|cow| cow.into_owned())
324 .unwrap_or_else(|_| s.to_string())
325}
326
327const FRAGMENT: &AsciiSet = &CONTROLS
328 .add(b' ')
329 .add(b'"')
330 .add(b'<')
331 .add(b'>')
332 .add(b'`')
333 .add(b'&')
334 .add(b'?')
335 .add(b'=');
336
337fn encode(s: &str) -> String {
338 utf8_percent_encode(s, FRAGMENT).to_string()
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344
345 macro_rules! test {
346 ($func_name:ident, $origin:expr, $result:expr) => {
347 #[test]
348 fn $func_name() {
349 let qs = QString::from($origin);
350 let ps: Vec<(String, String)> = qs.into_pairs();
351 let cs: Vec<(String, String)> = ($result as Vec<(&str, &str)>)
352 .into_iter()
353 .map(|(k, v)| (k.to_string(), v.to_string()))
354 .collect();
355 assert_eq!(ps, cs);
356 }
357 };
358 }
359
360 #[test]
361 fn encode_amp() {
362 let x = QString::new(vec![("foo", "b&?=ar")]);
363 assert_eq!("foo=b%26%3F%3Dar", x.to_string());
364 }
365
366 #[test]
367 fn amps_in_a_row() {
368 assert_eq!(
369 QString::from("&bar=baz&pooch&panda=bear").to_pairs(),
370 vec![("bar", "baz"), ("pooch", ""), ("panda", "bear")]
371 );
372 }
373
374 test!(empty_1, "", vec![]);
375 test!(empty_2, "?", vec![]);
376 test!(empty_3, "&", vec![]);
377 test!(empty_4, "=", vec![]);
378 test!(empty_5, "?=", vec![]);
379 test!(empty_6, "?&", vec![]);
380
381 test!(a_is_1, "a", vec![("a", "")]);
382 test!(a_is_2, "a=", vec![("a", "")]);
383 test!(a_is_3, "a=b", vec![("a", "b")]);
384 test!(a_is_4, "?a", vec![("a", "")]);
385 test!(a_is_5, "?a=", vec![("a", "")]);
386 test!(a_is_6, "?a=b", vec![("a", "b")]);
387 test!(a_is_7, "?&a", vec![("a", "")]);
388 test!(a_is_8, "?&a=", vec![("a", "")]);
389 test!(a_is_9, "?&a=b", vec![("a", "b")]);
390 test!(a_is_10, "?a=&", vec![("a", "")]);
391 test!(a_is_11, "?=a", vec![("a", "")]);
392
393 test!(a_is_eq_1, "a==", vec![("a", "=")]);
394
395 test!(is_q_1, "??", vec![("?", "")]);
396 test!(is_q_2, "&?", vec![("?", "")]);
397 test!(is_q_3, "??a", vec![("?a", "")]);
398 test!(is_q_4, "&?a", vec![("?a", "")]);
399
400 test!(ac_is_1, "?a&c", vec![("a", ""), ("c", "")]);
401 test!(ac_is_2, "?a&c&", vec![("a", ""), ("c", "")]);
402 test!(ac_is_3, "?a=&c", vec![("a", ""), ("c", "")]);
403 test!(ac_is_4, "?a=&c=", vec![("a", ""), ("c", "")]);
404 test!(ac_is_5, "?a=b&c=", vec![("a", "b"), ("c", "")]);
405 test!(ac_is_6, "?a=&c=d", vec![("a", ""), ("c", "d")]);
406 test!(ac_is_7, "?a=b&c=d", vec![("a", "b"), ("c", "d")]);
407}