reinhardt_http/
path_params.rs1use std::collections::HashMap;
21
22use smallvec::SmallVec;
23
24const INLINE_PARAM_CAPACITY: usize = 4;
25type PathParamStorage = SmallVec<[(String, String); INLINE_PARAM_CAPACITY]>;
26
27#[derive(Debug, Clone, Default, PartialEq, Eq)]
49pub struct PathParams {
50 inner: PathParamStorage,
51}
52
53impl PathParams {
54 pub fn new() -> Self {
56 Self {
57 inner: PathParamStorage::new(),
58 }
59 }
60
61 pub fn with_capacity(capacity: usize) -> Self {
63 Self {
64 inner: PathParamStorage::with_capacity(capacity),
65 }
66 }
67
68 pub fn len(&self) -> usize {
70 self.inner.len()
71 }
72
73 pub fn is_empty(&self) -> bool {
75 self.inner.is_empty()
76 }
77
78 pub fn get(&self, key: &str) -> Option<&String> {
83 self.inner.iter().find(|(k, _)| k == key).map(|(_, v)| v)
84 }
85
86 pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
91 let key = key.into();
92 let value = value.into();
93 if let Some(slot) = self.inner.iter_mut().find(|(k, _)| *k == key) {
94 slot.1 = value;
95 } else {
96 self.inner.push((key, value));
97 }
98 }
99
100 pub fn iter(&self) -> std::slice::Iter<'_, (String, String)> {
102 self.inner.iter()
103 }
104
105 pub fn values(&self) -> impl Iterator<Item = &String> {
107 self.inner.iter().map(|(_, v)| v)
108 }
109
110 pub fn as_slice(&self) -> &[(String, String)] {
112 &self.inner
113 }
114
115 pub fn into_vec(self) -> Vec<(String, String)> {
117 self.inner.into_vec()
118 }
119}
120
121impl<K, V> FromIterator<(K, V)> for PathParams
122where
123 K: Into<String>,
124 V: Into<String>,
125{
126 fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
127 let mut params = PathParams::new();
128 for (k, v) in iter {
129 params.insert(k, v);
130 }
131 params
132 }
133}
134
135impl IntoIterator for PathParams {
136 type Item = (String, String);
137 type IntoIter = std::vec::IntoIter<(String, String)>;
138
139 fn into_iter(self) -> Self::IntoIter {
140 self.inner.into_vec().into_iter()
141 }
142}
143
144impl<'a> IntoIterator for &'a PathParams {
145 type Item = &'a (String, String);
146 type IntoIter = std::slice::Iter<'a, (String, String)>;
147
148 fn into_iter(self) -> Self::IntoIter {
149 self.inner.iter()
150 }
151}
152
153impl From<Vec<(String, String)>> for PathParams {
154 fn from(inner: Vec<(String, String)>) -> Self {
155 Self {
157 inner: PathParamStorage::from_vec(inner),
158 }
159 }
160}
161
162impl From<HashMap<String, String>> for PathParams {
163 fn from(map: HashMap<String, String>) -> Self {
167 Self {
168 inner: map.into_iter().collect(),
169 }
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176 use rstest::rstest;
177
178 #[rstest]
179 fn insert_preserves_order() {
180 let mut params = PathParams::new();
182
183 params.insert("z", "first");
185 params.insert("a", "second");
186 params.insert("m", "third");
187
188 let order: Vec<&str> = params.iter().map(|(k, _)| k.as_str()).collect();
190 assert_eq!(order, vec!["z", "a", "m"]);
191 }
192
193 #[rstest]
194 fn get_finds_by_name() {
195 let mut params = PathParams::new();
197 params.insert("org", "myslug");
198 params.insert("cluster_id", "5");
199
200 let org = params.get("org");
202 let cluster_id = params.get("cluster_id");
203 let missing = params.get("missing");
204
205 assert_eq!(org.map(String::as_str), Some("myslug"));
207 assert_eq!(cluster_id.map(String::as_str), Some("5"));
208 assert_eq!(missing, None);
209 }
210
211 #[rstest]
212 fn insert_replaces_existing_in_place() {
213 let mut params = PathParams::new();
215 params.insert("a", "1");
216 params.insert("b", "2");
217
218 params.insert("a", "updated");
220
221 let collected: Vec<_> = params
223 .iter()
224 .map(|(k, v)| (k.as_str(), v.as_str()))
225 .collect();
226 assert_eq!(collected, vec![("a", "updated"), ("b", "2")]);
227 }
228
229 #[rstest]
230 fn from_vec_preserves_caller_order() {
231 let vec = vec![
233 ("org".to_string(), "myslug".to_string()),
234 ("cluster_id".to_string(), "5".to_string()),
235 ];
236
237 let params = PathParams::from(vec);
239
240 let order: Vec<&str> = params.iter().map(|(k, _)| k.as_str()).collect();
242 assert_eq!(order, vec!["org", "cluster_id"]);
243 }
244
245 #[rstest]
246 fn from_iter_collects_in_order() {
247 let pairs = vec![("z", "1"), ("a", "2")];
249
250 let params: PathParams = pairs.into_iter().collect();
252
253 let order: Vec<&str> = params.iter().map(|(k, _)| k.as_str()).collect();
255 assert_eq!(order, vec!["z", "a"]);
256 }
257
258 #[rstest]
259 fn consuming_iterator_keeps_public_vec_iterator_type() {
260 let params = PathParams::from(vec![("org".to_string(), "myslug".to_string())]);
262
263 let mut iter: std::vec::IntoIter<(String, String)> = params.into_iter();
265
266 assert_eq!(iter.next(), Some(("org".to_string(), "myslug".to_string())));
268 assert_eq!(iter.next(), None);
269 }
270}