
Link: https://docs.rs/urldecode/0.1.1/src/urldecode/lib.rs.html#1-21 Why rob? Well its slightly modified(very smol change) in that it now takes a &str and allocates a string from it Later patches will change this so that we don't _always_ allocate frivolously
102 lines
3.0 KiB
Rust
102 lines
3.0 KiB
Rust
use serde_json::{self, Value};
|
|
use hyper::http::HeaderValue;
|
|
use hyper::Response;
|
|
use hyper::Body;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
const APP_JSON_HEADER: &'static str = "application/json";
|
|
const CONTENT_TYPE: &'static str = "Content-Type";
|
|
|
|
pub fn set_json_body(response: &mut Response<Body>, values: Value) {
|
|
response.headers_mut().insert(
|
|
CONTENT_TYPE,
|
|
HeaderValue::from_static(APP_JSON_HEADER));
|
|
|
|
*response.body_mut() = Body::from(values.to_string());
|
|
}
|
|
|
|
fn percent_decode(url: &str) -> String {
|
|
// completely ripped from: https://docs.rs/urldecode/0.1.1/src/urldecode/lib.rs.html#1-21
|
|
let url = String::from(url);
|
|
let mut decoded = String::from("");
|
|
let mut skip = 0;
|
|
for i in 0..url.len() {
|
|
if skip != 0 {
|
|
skip -= 1;
|
|
continue;
|
|
}
|
|
let c: char = url.chars().nth(i).unwrap();
|
|
if c == '%' {
|
|
let left = url.chars().nth(i + 1).unwrap();
|
|
let right = url.chars().nth(i + 2).unwrap();
|
|
let byte = u8::from_str_radix(&format!("{}{}", left, right), 16).unwrap();
|
|
decoded += &(byte as char).to_string();
|
|
skip = 2;
|
|
} else {
|
|
decoded += &c.to_string();
|
|
}
|
|
}
|
|
decoded
|
|
}
|
|
|
|
pub fn parse_query_string<'qs>(string: &str)
|
|
-> HashMap<String, String> {
|
|
|
|
let mut map: HashMap<String, String> = HashMap::new();
|
|
|
|
// get pairs of [key1=value1, key2=value2, ...]
|
|
for pair in string.split('&') {
|
|
|
|
let kv: Vec<&str> = pair.split('=').collect();
|
|
|
|
match (kv.get(0), kv.get(1)) {
|
|
// only if the format is some_key=some_value do we actually care about it
|
|
(Some(key), Some(value)) => {
|
|
map.insert(percent_decode(&key), percent_decode(&value));
|
|
},
|
|
// ignore all non-pairs
|
|
_ => continue
|
|
};
|
|
}
|
|
return map;
|
|
}
|
|
|
|
// Pulls out Option<type> from a HashMap<&str, &str>
|
|
// NOTE: If you need &str just use the hashmap directly
|
|
#[macro_export]
|
|
macro_rules! qs_param {
|
|
($obj:expr, $id:literal, $type:ty) => {
|
|
match $obj.get($id) {
|
|
Some(val) => {
|
|
if let Ok(pval) = val.to_string().parse::<$type>() {
|
|
Some(pval)
|
|
} else{
|
|
None
|
|
}
|
|
},
|
|
None => None
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::collections::HashMap;
|
|
|
|
#[test]
|
|
fn validate_qs_param() {
|
|
let mut map: HashMap<&str, &str> = HashMap::new();
|
|
map.insert("key", "value");
|
|
map.insert("asdf", "123");
|
|
// It should be noted if you want &str values then just
|
|
// use the hashmap directly, since the macro is a bit clumsy with strings
|
|
// Thust the cast to a String not &str is required
|
|
assert_eq!(qs_param!(map, "key", String).is_some(), true);
|
|
assert_eq!(qs_param!(map, "not-there", String).is_some(), false);
|
|
|
|
assert_eq!(qs_param!(map, "asdf", u64).is_some(), true);
|
|
assert_eq!(qs_param!(map, "asdf", bool).is_some(), false);
|
|
}
|
|
}
|