use crate::webserver::single_or_vec::SingleOrVec;
use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
use serde::{Deserialize, Deserializer};
use serde_json::Value;
use std::borrow::Cow;
use std::fmt;
#[derive(Default)]
pub struct URLParameters(String);
impl URLParameters {
pub fn new() -> Self {
Self(String::new())
}
fn encode_and_push(&mut self, v: &str) {
let val: Cow<str> = percent_encode(v.as_bytes(), NON_ALPHANUMERIC).into();
self.0.push_str(&val);
}
fn start_new_pair(&mut self) {
let char = if self.0.is_empty() { '?' } else { '&' };
self.0.push(char);
}
fn push_kv(&mut self, key: &str, value: &str) {
self.start_new_pair();
self.encode_and_push(key);
self.0.push('=');
self.encode_and_push(value);
}
fn push_array_entry(&mut self, key: &str, value: &str) {
self.start_new_pair();
self.encode_and_push(key);
self.0.push_str("[]=");
self.encode_and_push(value);
}
fn push_array(&mut self, key: &str, values: Vec<Value>) {
for val in values {
let val_str = match val {
Value::String(s) => s,
other => other.to_string(),
};
self.push_array_entry(key, &val_str);
}
}
pub fn push_single_or_vec(&mut self, key: &str, val: SingleOrVec) {
match val {
SingleOrVec::Single(v) => self.push_kv(key, &v),
SingleOrVec::Vec(v) => {
for s in v {
self.push_array_entry(key, &s);
}
}
}
}
fn add_from_json(&mut self, key: &str, raw_json_value: &str) {
if let Ok(str_val) = serde_json::from_str::<Option<Cow<str>>>(raw_json_value) {
if let Some(str_val) = str_val {
self.push_kv(key, &str_val);
}
} else if let Ok(vec_val) = serde_json::from_str::<Vec<Value>>(raw_json_value) {
self.push_array(key, vec_val);
} else {
self.push_kv(key, raw_json_value);
}
}
pub fn with_empty_path(self) -> String {
if self.0.is_empty() {
"?".to_string() } else {
self.0 }
}
pub fn append_to_path(self, url: &mut String) {
if url.is_empty() {
*url = self.with_empty_path();
} else {
url.push_str(&self.0);
}
}
}
impl<'de> Deserialize<'de> for URLParameters {
fn deserialize<D>(deserializer: D) -> Result<URLParameters, D::Error>
where
D: Deserializer<'de>,
{
struct URLParametersVisitor;
impl<'de> serde::de::Visitor<'de> for URLParametersVisitor {
type Value = URLParameters;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a sequence")
}
fn visit_map<A>(self, mut map: A) -> Result<URLParameters, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut out = URLParameters(String::new());
while let Some((key, value)) =
map.next_entry::<Cow<str>, Cow<serde_json::value::RawValue>>()?
{
out.add_from_json(&key, value.get());
}
Ok(out)
}
}
deserializer.deserialize_map(URLParametersVisitor)
}
}
impl std::fmt::Display for URLParameters {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<URLParameters> for String {
fn from(value: URLParameters) -> Self {
value.0
}
}
#[test]
fn test_url_parameters_deserializer() {
use serde_json::json;
let json = json!({
"x": "hello world",
"num": 123,
"arr": [1, 2, 3],
});
let url_parameters: URLParameters = serde_json::from_value(json).unwrap();
assert_eq!(
url_parameters.0,
"?x=hello%20world&num=123&arr[]=1&arr[]=2&arr[]=3"
);
}
#[test]
fn test_url_parameters_null() {
use serde_json::json;
let json = json!({
"null_should_be_omitted": null,
"x": "hello",
});
let url_parameters: URLParameters = serde_json::from_value(json).unwrap();
assert_eq!(url_parameters.0, "?x=hello");
}
#[test]
fn test_url_parameters_deserializer_special_chars() {
use serde_json::json;
let json = json!({
"chars": ["\n", " ", "\""],
});
let url_parameters: URLParameters = serde_json::from_value(json).unwrap();
assert_eq!(url_parameters.0, "?chars[]=%0A&chars[]=%20&chars[]=%22");
}
#[test]
fn test_url_parameters_deserializer_issue_879() {
use serde_json::json;
let json = json!({
"name": "John Doe & Son's",
"items": [1, "item 2 & 3", true],
"special_char": "%&=+ ",
});
let url_parameters: URLParameters = serde_json::from_value(json).unwrap();
assert_eq!(
url_parameters.0,
"?name=John%20Doe%20%26%20Son%27s&items[]=1&items[]=item%202%20%26%203&items[]=true&special%5Fchar=%25%26%3D%2B%20"
);
}
#[test]
fn test_push_single_or_vec() {
let mut params = URLParameters(String::new());
params.push_single_or_vec("k", SingleOrVec::Single("v".to_string()));
assert_eq!(params.to_string(), "?k=v");
let mut params = URLParameters(String::new());
params.push_single_or_vec(
"arr",
SingleOrVec::Vec(vec!["a".to_string(), "b".to_string()]),
);
assert_eq!(params.to_string(), "?arr[]=a&arr[]=b");
}