Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make ParamsMap have multiple values per key #2966

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions router/src/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,9 @@ where
let get = Memo::new({
let key = key.clone_inplace();
move |_| {
query_map
.with(|map| map.get(&key).and_then(|value| value.parse().ok()))
query_map.with(|map| {
map.get_str(&key).and_then(|value| value.parse().ok())
})
}
});

Expand Down
74 changes: 53 additions & 21 deletions router/src/params.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::location::{unescape, Url};
use std::{borrow::Cow, mem, str::FromStr, sync::Arc};
use std::{borrow::Cow, str::FromStr, sync::Arc};
use thiserror::Error;

type ParamsMapInner = Vec<(Cow<'static, str>, String)>;
type ParamsMapInner = Vec<(Cow<'static, str>, Vec<String>)>;

#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
pub struct ParamsMap(ParamsMapInner);
Expand All @@ -21,34 +21,39 @@ impl ParamsMap {
}

/// Inserts a value into the map.
pub fn insert(&mut self, key: String, value: String) -> Option<String> {
pub fn insert(&mut self, key: String, value: String) {
let value = unescape(&value);

if let Some(prev) = self.0.iter().position(|(k, _)| k == &key) {
return Some(mem::replace(&mut self.0[prev].1, value));
self.0[prev].1.push(value);
} else {
self.0.push((key.into(), vec![value]));
}

self.0.push((key.into(), value));
None
}

/// Gets an owned value from the map.
pub fn get(&self, key: &str) -> Option<String> {
pub fn get(&self, key: &str) -> Option<Vec<String>> {
self.0
.iter()
.find_map(|(k, v)| (k == key).then_some(v.to_owned()))
}

/// Gets a referenc to a value from the map.
/// Gets a reference to a value from the map.
pub fn get_str(&self, key: &str) -> Option<&str> {
self.0
.iter()
.find_map(|(k, v)| (k == key).then_some(v.as_str()))
self.0.iter().find_map(|(k, v)| {
if k == key {
// NOTE: gets the last value which should behave like it did when it was map
// that replaced values
v.last().map(|i| i.as_str())
} else {
None
}
})
}

/// Removes a value from the map.
#[inline(always)]
pub fn remove(&mut self, key: &str) -> Option<String> {
pub fn remove(&mut self, key: &str) -> Option<Vec<String>> {
for i in 0..self.0.len() {
if self.0[i].0 == key {
return Some(self.0.swap_remove(i).1);
Expand All @@ -62,11 +67,13 @@ impl ParamsMap {
let mut buf = String::new();
if !self.0.is_empty() {
buf.push('?');
for (k, v) in &self.0 {
buf.push_str(&Url::escape(k));
buf.push('=');
buf.push_str(&Url::escape(v));
buf.push('&');
for (k, vs) in &self.0 {
for v in vs {
buf.push_str(&Url::escape(k));
buf.push('=');
buf.push_str(&Url::escape(v));
buf.push('&');
}
}
if buf.len() > 1 {
buf.pop();
Expand All @@ -84,7 +91,7 @@ where
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
Self(
iter.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.map(|(k, v)| (k.into(), vec![v.into()]))
.collect(),
)
}
Expand All @@ -95,13 +102,21 @@ impl IntoIterator for ParamsMap {
type IntoIter = ParamsMapIter;

fn into_iter(self) -> Self::IntoIter {
ParamsMapIter(self.0.into_iter())
let inner = self.0.into_iter().fold(vec![], |mut c, (k, vs)| {
for v in vs {
c.push((k.clone(), v));
}
c
});
ParamsMapIter(inner.into_iter())
}
}

/// An iterator over the keys and values of a [`ParamsMap`].
#[derive(Debug)]
pub struct ParamsMapIter(<ParamsMapInner as IntoIterator>::IntoIter);
pub struct ParamsMapIter(
<Vec<(Cow<'static, str>, String)> as IntoIterator>::IntoIter,
);

impl Iterator for ParamsMapIter {
type Item = (Cow<'static, str>, String);
Expand Down Expand Up @@ -201,3 +216,20 @@ impl PartialEq for ParamsError {
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn paramsmap_to_query_string() {
let mut map = ParamsMap::new();
let key = "param".to_string();
let value1 = "a".to_string();
let value2 = "b".to_string();
map.insert(key.clone(), value1);
map.insert(key, value2);
let query_string = map.to_query_string();
assert_eq!(&query_string, "?param=a&param=b")
}
}
Loading