use std::{collections::HashMap, sync::Arc};
use smallvec::SmallVec;
const INLINE_PARAM_CAPACITY: usize = 4;
const INLINE_VALUE_CAPACITY: usize = 32;
type PathParamNames = SmallVec<[String; INLINE_PARAM_CAPACITY]>;
type PathParamValues = SmallVec<[PathParamValue; INLINE_PARAM_CAPACITY]>;
type PathParamValueBytes = SmallVec<[u8; INLINE_VALUE_CAPACITY]>;
#[derive(Debug, Clone, PartialEq, Eq)]
struct PathParamValue {
inner: PathParamValueBytes,
}
impl PathParamValue {
fn as_str(&self) -> &str {
std::str::from_utf8(&self.inner)
.expect("path parameter values are created from valid UTF-8 strings")
}
fn into_string(self) -> String {
String::from_utf8(self.inner.into_vec())
.expect("path parameter values are created from valid UTF-8 strings")
}
}
impl From<&str> for PathParamValue {
fn from(value: &str) -> Self {
Self {
inner: PathParamValueBytes::from_slice(value.as_bytes()),
}
}
}
impl From<String> for PathParamValue {
fn from(value: String) -> Self {
Self {
inner: value.into_bytes().into_iter().collect(),
}
}
}
impl From<&String> for PathParamValue {
fn from(value: &String) -> Self {
value.as_str().into()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum PathParamNameStorage {
Owned(PathParamNames),
Shared(Arc<[String]>),
}
impl Default for PathParamNameStorage {
fn default() -> Self {
Self::Owned(PathParamNames::new())
}
}
impl PathParamNameStorage {
fn with_capacity(capacity: usize) -> Self {
Self::Owned(PathParamNames::with_capacity(capacity))
}
fn get(&self, index: usize) -> Option<&String> {
match self {
Self::Owned(names) => names.get(index),
Self::Shared(names) => names.get(index),
}
}
fn position(&self, key: &str) -> Option<usize> {
match self {
Self::Owned(names) => names.iter().position(|name| name == key),
Self::Shared(names) => names.iter().position(|name| name == key),
}
}
fn push(&mut self, key: String) {
self.ensure_owned().push(key);
}
fn ensure_owned(&mut self) -> &mut PathParamNames {
if let Self::Shared(names) = self {
*self = Self::Owned(names.iter().cloned().collect());
}
match self {
Self::Owned(names) => names,
Self::Shared(_) => unreachable!("shared names are materialized before mutation"),
}
}
}
pub struct PathParamsIter<'a> {
params: &'a PathParams,
index: usize,
}
impl<'a> Iterator for PathParamsIter<'a> {
type Item = (&'a String, &'a str);
fn next(&mut self) -> Option<Self::Item> {
let index = self.index;
self.index += 1;
Some((
self.params.names.get(index)?,
self.params.values.get(index)?.as_str(),
))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.params.len().saturating_sub(self.index);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for PathParamsIter<'_> {}
#[derive(Debug, Clone, Default)]
pub struct PathParams {
names: PathParamNameStorage,
values: PathParamValues,
}
impl PartialEq for PathParams {
fn eq(&self, other: &Self) -> bool {
self.iter().eq(other.iter())
}
}
impl Eq for PathParams {}
impl PathParams {
pub fn new() -> Self {
Self {
names: PathParamNameStorage::default(),
values: PathParamValues::new(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
names: PathParamNameStorage::with_capacity(capacity),
values: PathParamValues::with_capacity(capacity),
}
}
pub fn from_shared_names<I, V>(names: Arc<[String]>, values: I) -> Self
where
I: IntoIterator<Item = V>,
V: AsRef<str>,
{
let mut path_values = PathParamValues::with_capacity(names.len());
for value in values {
path_values.push(PathParamValue::from(value.as_ref()));
}
assert_eq!(
names.len(),
path_values.len(),
"shared path parameter names and values must have the same length"
);
Self {
names: PathParamNameStorage::Shared(names),
values: path_values,
}
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn get(&self, key: &str) -> Option<&str> {
let index = self.names.position(key)?;
self.values.get(index).map(PathParamValue::as_str)
}
pub fn insert(&mut self, key: impl Into<String>, value: impl AsRef<str>) {
let key = key.into();
let value = PathParamValue::from(value.as_ref());
if let Some(index) = self.names.position(&key) {
self.values[index] = value;
} else {
self.names.push(key);
self.values.push(value);
}
}
pub fn iter(&self) -> PathParamsIter<'_> {
PathParamsIter {
params: self,
index: 0,
}
}
pub fn values(&self) -> impl Iterator<Item = &str> {
self.values.iter().map(PathParamValue::as_str)
}
pub fn to_vec(&self) -> Vec<(String, String)> {
self.iter()
.map(|(key, value)| (key.clone(), value.to_string()))
.collect()
}
pub fn into_vec(self) -> Vec<(String, String)> {
match self.names {
PathParamNameStorage::Owned(names) => names
.into_iter()
.zip(self.values.into_iter().map(PathParamValue::into_string))
.collect(),
PathParamNameStorage::Shared(names) => names
.iter()
.cloned()
.zip(self.values.into_iter().map(PathParamValue::into_string))
.collect(),
}
}
}
impl<K, V> FromIterator<(K, V)> for PathParams
where
K: Into<String>,
V: AsRef<str>,
{
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.into_vec().into_iter()
}
}
impl<'a> IntoIterator for &'a PathParams {
type Item = (&'a String, &'a str);
type IntoIter = PathParamsIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl From<Vec<(String, String)>> for PathParams {
fn from(inner: Vec<(String, String)>) -> Self {
let mut params = Self::with_capacity(inner.len());
for (key, value) in inner {
params.insert(key, value);
}
params
}
}
impl From<HashMap<String, String>> for PathParams {
fn from(map: HashMap<String, String>) -> Self {
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, Some("myslug"));
assert_eq!(cluster_id, 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)).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"]);
}
#[rstest]
fn consuming_iterator_keeps_public_vec_iterator_type() {
let params = PathParams::from(vec![("org".to_string(), "myslug".to_string())]);
let mut iter: std::vec::IntoIter<(String, String)> = params.into_iter();
assert_eq!(iter.next(), Some(("org".to_string(), "myslug".to_string())));
assert_eq!(iter.next(), None);
}
}