use crate::encoding::{decode_query_key, decode_query_value, encode_query_key, encode_query_value};
use indexmap::IndexMap;
use serde_json::Value;
pub type QueryValue = Value;
pub type QueryObject = IndexMap<String, QueryValue>;
pub fn parse_query(raw_query: &str) -> QueryObject {
let mut object = QueryObject::new();
let params_pair = raw_query.strip_prefix('?').unwrap_or(raw_query);
for parameter in params_pair.split('&') {
if let Some((key, value)) = parameter.split_once('=') {
let key = decode_query_key(key);
let value = decode_query_value(value);
match object.get_mut(&key) {
None => {
object.insert(key, Value::String(value));
}
Some(existing) => {
let existing_str = existing.as_str().unwrap_or("");
*existing = Value::String(format!("{},{}", existing_str, value));
}
}
}
}
object
}
pub fn encode_query_item(key: &str, value: &QueryValue) -> String {
if value.is_null() || value.is_boolean() || value.is_number() {
return encode_query_key(key);
}
if let Some(arr) = value.as_array() {
return arr
.iter()
.map(|v| {
format!(
"{}={}",
encode_query_key(key),
encode_query_value(v.as_str().unwrap_or(""))
)
})
.collect::<Vec<String>>()
.join("&");
}
format!(
"{}={}",
encode_query_key(key),
encode_query_value(value.as_str().unwrap_or(""))
)
}
pub fn stringify_query(query: &QueryObject) -> String {
query
.iter()
.filter(|(_, v)| !v.is_null())
.map(|(k, v)| encode_query_item(k, v))
.filter(|s| !s.is_empty())
.collect::<Vec<String>>()
.join("&")
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_parse_query_basic() {
let query = parse_query("foo=bar&baz=qux");
assert_eq!(query.get("foo").unwrap(), "bar");
assert_eq!(query.get("baz").unwrap(), "qux");
}
#[test]
fn test_parse_query_with_question_mark() {
let query = parse_query("?foo=bar&baz=qux");
assert_eq!(query.get("foo").unwrap(), "bar");
assert_eq!(query.get("baz").unwrap(), "qux");
}
#[test]
fn test_parse_query_empty() {
let query = parse_query("");
assert!(query.is_empty());
}
#[test]
fn test_parse_query_encoded() {
let query = parse_query("user=john%20doe&email=test%40example.com");
assert_eq!(query.get("user").unwrap(), "john doe");
assert_eq!(query.get("email").unwrap(), "test@example.com");
}
#[test]
fn test_parse_query_multiple_values() {
let query = parse_query("color=red&color=blue&color=green");
assert_eq!(query.get("color").unwrap(), "red,blue,green");
}
#[test]
fn test_encode_query_item_basic() {
let key = "test";
let value = json!("hello world");
assert_eq!(encode_query_item(key, &value), "test=hello+world");
}
#[test]
fn test_encode_query_item_array() {
let key = "test";
let array_value = json!(["a", "b", "c"]);
assert_eq!(encode_query_item(key, &array_value), "test=a&test=b&test=c");
}
#[test]
fn test_encode_query_item_special_chars() {
let key = "email";
let value = json!("test@example.com");
assert_eq!(encode_query_item(key, &value), "email=test%40example.com");
}
#[test]
fn test_encode_query_item_null() {
let key = "empty";
let value = json!(null);
assert_eq!(encode_query_item(key, &value), "empty");
}
#[test]
fn test_stringify_query_basic() {
let mut query = QueryObject::new();
query.insert("foo".to_string(), json!("bar"));
query.insert("baz".to_string(), json!("qux"));
let result = stringify_query(&query);
assert!(result.contains("foo=bar"));
assert!(result.contains("baz=qux"));
}
#[test]
fn test_stringify_query_complex() {
let mut query = QueryObject::new();
query.insert("user".to_string(), json!("john doe"));
query.insert("tags".to_string(), json!(["rust", "coding"]));
query.insert("active".to_string(), json!(true));
query.insert("empty".to_string(), json!(null));
let result = stringify_query(&query);
assert!(result.contains("user=john+doe"));
assert!(result.contains("tags=rust&tags=coding"));
assert!(result.contains("active"));
assert!(!result.contains("empty"));
}
#[test]
fn test_encode_query_item_emoji() {
let key = "message";
let value = json!("Hello 👋 World 🌍");
assert_eq!(
encode_query_item(key, &value),
"message=Hello+%F0%9F%91%8B+World+%F0%9F%8C%8D"
);
let key = "reaction";
let value = json!("❤️");
assert_eq!(
encode_query_item(key, &value),
"reaction=%E2%9D%A4%EF%B8%8F"
);
let key = "faces";
let value = json!(["😀", "😎", "🤔"]);
assert_eq!(
encode_query_item(key, &value),
"faces=%F0%9F%98%80&faces=%F0%9F%98%8E&faces=%F0%9F%A4%94"
);
}
#[test]
fn test_decode_query_item_emoji() {
let encoded = "http://example.com?message=Hello+%F0%9F%91%8B+World+%F0%9F%8C%8D";
let url = url::Url::parse(encoded).unwrap();
let pairs: Vec<(String, String)> = url.query_pairs().into_owned().collect();
assert_eq!(
pairs[0],
("message".to_string(), "Hello 👋 World 🌍".to_string())
);
let encoded = "http://example.com?reaction=%E2%9D%A4%EF%B8%8F";
let url = url::Url::parse(encoded).unwrap();
let pairs: Vec<(String, String)> = url.query_pairs().into_owned().collect();
assert_eq!(pairs[0], ("reaction".to_string(), "❤️".to_string()));
let encoded = "http://example.com?faces=%F0%9F%98%80";
let url = url::Url::parse(encoded).unwrap();
let pairs: Vec<(String, String)> = url.query_pairs().into_owned().collect();
assert_eq!(pairs[0], ("faces".to_string(), "😀".to_string()));
}
}