use std::{collections::HashMap, fmt::Display};
#[cfg_attr(feature = "json", derive(serde::Serialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Url<'a> {
pub path: Vec<&'a str>,
pub search_params: HashMap<&'a str, &'a str>,
}
impl<'a> Url<'a> {
pub fn new(path: Vec<&'a str>, search_params: HashMap<&'a str, &'a str>) -> Self {
Self {
path,
search_params,
}
}
pub fn at(&self, i: usize) -> Option<&'a str> {
self.path.get(i).copied()
}
pub fn search_param(&self, key: &'a str) -> Option<&'a str> {
self.search_params.get(key).copied()
}
pub fn has_search_param(&self, key: &'a str) -> bool {
self.search_params.contains_key(key)
}
}
impl<'a> From<&'a str> for Url<'a> {
fn from(value: &'a str) -> Self {
let (path_part, query_part) = value.split_once('?').unwrap_or((value, ""));
let path: Vec<&'a str> = path_part.split('/').filter(|x| !x.is_empty()).collect();
let mut search_params = HashMap::new();
if !query_part.is_empty() {
for s in query_part.split('&') {
let (key, value) = s.split_once('=').unwrap_or((s, ""));
if key.is_empty() {
continue;
}
search_params.insert(key, value);
}
}
Self::new(path, search_params)
}
}
use std::fmt;
impl Display for Url<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let path_str = self.path.join("/");
let params = self
.search_params
.iter()
.map(|(key, value)| format!("{}={}", key, value))
.collect::<Vec<String>>()
.join("&");
write!(f, "{}?{}", path_str, params)
}
}