use std::collections::HashMap;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct PathParams {
inner: Vec<(String, String)>,
}
impl PathParams {
pub fn new() -> Self {
Self { inner: Vec::new() }
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn get(&self, key: &str) -> Option<&String> {
self.inner.iter().find(|(k, _)| k == key).map(|(_, v)| v)
}
pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
let key = key.into();
let value = value.into();
if let Some(slot) = self.inner.iter_mut().find(|(k, _)| *k == key) {
slot.1 = value;
} else {
self.inner.push((key, value));
}
}
pub fn iter(&self) -> std::slice::Iter<'_, (String, String)> {
self.inner.iter()
}
pub fn values(&self) -> impl Iterator<Item = &String> {
self.inner.iter().map(|(_, v)| v)
}
pub fn as_slice(&self) -> &[(String, String)] {
&self.inner
}
pub fn into_vec(self) -> Vec<(String, String)> {
self.inner
}
}
impl<K, V> FromIterator<(K, V)> for PathParams
where
K: Into<String>,
V: Into<String>,
{
fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
let mut params = PathParams::new();
for (k, v) in iter {
params.insert(k, v);
}
params
}
}
impl IntoIterator for PathParams {
type Item = (String, String);
type IntoIter = std::vec::IntoIter<(String, String)>;
fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter()
}
}
impl<'a> IntoIterator for &'a PathParams {
type Item = &'a (String, String);
type IntoIter = std::slice::Iter<'a, (String, String)>;
fn into_iter(self) -> Self::IntoIter {
self.inner.iter()
}
}
impl From<Vec<(String, String)>> for PathParams {
fn from(inner: Vec<(String, String)>) -> Self {
Self { inner }
}
}
impl From<HashMap<String, String>> for PathParams {
fn from(map: HashMap<String, String>) -> Self {
Self {
inner: map.into_iter().collect(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
fn insert_preserves_order() {
let mut params = PathParams::new();
params.insert("z", "first");
params.insert("a", "second");
params.insert("m", "third");
let order: Vec<&str> = params.iter().map(|(k, _)| k.as_str()).collect();
assert_eq!(order, vec!["z", "a", "m"]);
}
#[rstest]
fn get_finds_by_name() {
let mut params = PathParams::new();
params.insert("org", "myslug");
params.insert("cluster_id", "5");
let org = params.get("org");
let cluster_id = params.get("cluster_id");
let missing = params.get("missing");
assert_eq!(org.map(String::as_str), Some("myslug"));
assert_eq!(cluster_id.map(String::as_str), Some("5"));
assert_eq!(missing, None);
}
#[rstest]
fn insert_replaces_existing_in_place() {
let mut params = PathParams::new();
params.insert("a", "1");
params.insert("b", "2");
params.insert("a", "updated");
let collected: Vec<_> = params
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect();
assert_eq!(collected, vec![("a", "updated"), ("b", "2")]);
}
#[rstest]
fn from_vec_preserves_caller_order() {
let vec = vec![
("org".to_string(), "myslug".to_string()),
("cluster_id".to_string(), "5".to_string()),
];
let params = PathParams::from(vec);
let order: Vec<&str> = params.iter().map(|(k, _)| k.as_str()).collect();
assert_eq!(order, vec!["org", "cluster_id"]);
}
#[rstest]
fn from_iter_collects_in_order() {
let pairs = vec![("z", "1"), ("a", "2")];
let params: PathParams = pairs.into_iter().collect();
let order: Vec<&str> = params.iter().map(|(k, _)| k.as_str()).collect();
assert_eq!(order, vec!["z", "a"]);
}
}