1use std::collections::HashMap;
7
8#[derive(Debug, Clone, Default)]
24pub struct RouteParams {
25 params: HashMap<String, String>,
26}
27
28impl RouteParams {
29 pub fn new() -> Self {
31 Self::default()
32 }
33
34 pub fn from_map(params: HashMap<String, String>) -> Self {
36 Self { params }
37 }
38
39 pub fn get(&self, key: &str) -> Option<&String> {
41 self.params.get(key)
42 }
43
44 pub fn get_as<T>(&self, key: &str) -> Option<T>
48 where
49 T: std::str::FromStr,
50 {
51 self.params.get(key)?.parse().ok()
52 }
53
54 pub fn insert(&mut self, key: String, value: String) {
56 self.params.insert(key, value);
57 }
58
59 pub fn set(&mut self, key: String, value: String) {
61 self.params.insert(key, value);
62 }
63
64 pub fn contains(&self, key: &str) -> bool {
66 self.params.contains_key(key)
67 }
68
69 pub fn all(&self) -> &HashMap<String, String> {
71 &self.params
72 }
73
74 pub fn all_mut(&mut self) -> &mut HashMap<String, String> {
76 &mut self.params
77 }
78
79 pub fn iter(&self) -> impl Iterator<Item = (&String, &String)> {
81 self.params.iter()
82 }
83
84 pub fn is_empty(&self) -> bool {
86 self.params.is_empty()
87 }
88
89 pub fn len(&self) -> usize {
91 self.params.len()
92 }
93}
94
95#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
106 fn test_route_params_basic() {
107 let mut params = RouteParams::new();
108 params.insert("id".to_string(), "123".to_string());
109
110 assert_eq!(params.get("id"), Some(&"123".to_string()));
111 assert!(params.contains("id"));
112 assert!(!params.contains("missing"));
113 }
114
115 #[test]
116 fn test_route_params_get_as() {
117 let mut params = RouteParams::new();
118 params.insert("id".to_string(), "123".to_string());
119 params.insert("active".to_string(), "true".to_string());
120
121 assert_eq!(params.get_as::<i32>("id"), Some(123));
122 assert_eq!(params.get_as::<u32>("id"), Some(123));
123 assert_eq!(params.get_as::<bool>("active"), Some(true));
124 assert_eq!(params.get_as::<i32>("missing"), None);
125 }
126
127 #[test]
128 fn test_route_params_from_map() {
129 let mut map = HashMap::new();
130 map.insert("name".to_string(), "John".to_string());
131 map.insert("age".to_string(), "30".to_string());
132
133 let params = RouteParams::from_map(map);
134
135 assert_eq!(params.get("name"), Some(&"John".to_string()));
136 assert_eq!(params.get_as::<i32>("age"), Some(30));
137 }
138
139 #[test]
140 fn test_route_params_set() {
141 let mut params = RouteParams::new();
142 params.set("key".to_string(), "value".to_string());
143
144 assert_eq!(params.get("key"), Some(&"value".to_string()));
145 }
146
147 #[test]
148 fn test_route_params_all() {
149 let mut params = RouteParams::new();
150 params.insert("a".to_string(), "1".to_string());
151 params.insert("b".to_string(), "2".to_string());
152
153 let all = params.all();
154 assert_eq!(all.len(), 2);
155 assert_eq!(all.get("a"), Some(&"1".to_string()));
156 }
157
158 #[test]
159 fn test_route_params_iter() {
160 let mut params = RouteParams::new();
161 params.insert("x".to_string(), "1".to_string());
162 params.insert("y".to_string(), "2".to_string());
163
164 let count = params.iter().count();
165 assert_eq!(count, 2);
166 }
167
168 #[test]
169 fn test_route_params_empty() {
170 let params = RouteParams::new();
171 assert!(params.is_empty());
172 assert_eq!(params.len(), 0);
173
174 let mut params = RouteParams::new();
175 params.insert("key".to_string(), "value".to_string());
176 assert!(!params.is_empty());
177 assert_eq!(params.len(), 1);
178 }
179}
180
181#[derive(Debug, Clone, Default)]
201pub struct QueryParams {
202 params: HashMap<String, Vec<String>>,
203}
204
205impl QueryParams {
206 pub fn new() -> Self {
208 Self::default()
209 }
210
211 pub fn from_query_string(query: &str) -> Self {
222 let mut params = HashMap::new();
223
224 for pair in query.split('&') {
225 if let Some((key, value)) = pair.split_once('=') {
226 let key = decode_uri_component(key);
228 let value = decode_uri_component(value);
229
230 params.entry(key).or_insert_with(Vec::new).push(value);
231 }
232 }
233
234 Self { params }
235 }
236
237 pub fn get(&self, key: &str) -> Option<&String> {
239 self.params.get(key)?.first()
240 }
241
242 pub fn get_all(&self, key: &str) -> Option<&Vec<String>> {
246 self.params.get(key)
247 }
248
249 pub fn get_as<T>(&self, key: &str) -> Option<T>
253 where
254 T: std::str::FromStr,
255 {
256 self.get(key)?.parse().ok()
257 }
258
259 pub fn insert(&mut self, key: String, value: String) {
263 self.params.entry(key).or_default().push(value);
264 }
265
266 pub fn contains(&self, key: &str) -> bool {
268 self.params.contains_key(key)
269 }
270
271 pub fn to_query_string(&self) -> String {
284 let pairs: Vec<String> = self
285 .params
286 .iter()
287 .flat_map(|(key, values)| {
288 values.iter().map(move |value| {
289 format!(
290 "{}={}",
291 encode_uri_component(key),
292 encode_uri_component(value)
293 )
294 })
295 })
296 .collect();
297
298 pairs.join("&")
299 }
300
301 pub fn is_empty(&self) -> bool {
303 self.params.is_empty()
304 }
305
306 pub fn len(&self) -> usize {
308 self.params.len()
309 }
310}
311
312fn encode_uri_component(s: &str) -> String {
314 s.chars()
315 .map(|c| match c {
316 'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' | '.' | '~' => c.to_string(),
317 ' ' => "%20".to_string(),
318 _ => format!("%{:02X}", c as u8),
319 })
320 .collect()
321}
322
323fn decode_uri_component(s: &str) -> String {
325 let mut result = String::new();
326 let mut chars = s.chars().peekable();
327
328 while let Some(c) = chars.next() {
329 if c == '%' {
330 let hex: String = chars.by_ref().take(2).collect();
332 if let Ok(byte) = u8::from_str_radix(&hex, 16) {
333 result.push(byte as char);
334 } else {
335 result.push('%');
336 result.push_str(&hex);
337 }
338 } else if c == '+' {
339 result.push(' ');
340 } else {
341 result.push(c);
342 }
343 }
344
345 result
346}
347
348#[test]
351fn test_query_params_basic() {
352 let query = QueryParams::from_query_string("page=1&sort=name&filter=active");
353
354 assert_eq!(query.get("page"), Some(&"1".to_string()));
355 assert_eq!(query.get("sort"), Some(&"name".to_string()));
356 assert_eq!(query.get("filter"), Some(&"active".to_string()));
357 assert_eq!(query.get("missing"), None);
358}
359
360#[test]
361fn test_query_params_get_as() {
362 let query = QueryParams::from_query_string("page=1&limit=50&active=true");
363
364 assert_eq!(query.get_as::<i32>("page"), Some(1));
365 assert_eq!(query.get_as::<usize>("limit"), Some(50));
366 assert_eq!(query.get_as::<bool>("active"), Some(true));
367 assert_eq!(query.get_as::<i32>("missing"), None);
368}
369
370#[test]
371fn test_query_params_multiple_values() {
372 let query = QueryParams::from_query_string("tag=rust&tag=gpui&tag=ui");
373
374 let tags = query.get_all("tag").unwrap();
375 assert_eq!(tags.len(), 3);
376 assert!(tags.contains(&"rust".to_string()));
377 assert!(tags.contains(&"gpui".to_string()));
378 assert!(tags.contains(&"ui".to_string()));
379
380 assert_eq!(query.get("tag"), Some(&"rust".to_string()));
382}
383
384#[test]
385fn test_query_params_insert() {
386 let mut query = QueryParams::new();
387 query.insert("key".to_string(), "value1".to_string());
388 query.insert("key".to_string(), "value2".to_string());
389
390 let values = query.get_all("key").unwrap();
391 assert_eq!(values.len(), 2);
392 assert_eq!(values[0], "value1");
393 assert_eq!(values[1], "value2");
394}
395
396#[test]
397fn test_uri_encoding() {
398 let encoded = encode_uri_component("hello world");
399 assert_eq!(encoded, "hello%20world");
400
401 let encoded = encode_uri_component("test@example.com");
402 assert!(encoded.contains("%40")); }
404
405#[test]
406fn test_uri_decoding() {
407 let decoded = decode_uri_component("hello%20world");
408 assert_eq!(decoded, "hello world");
409
410 let decoded = decode_uri_component("hello+world");
411 assert_eq!(decoded, "hello world");
412}
413
414#[test]
415fn test_to_query_string() {
416 let mut query = QueryParams::new();
417 query.insert("page".to_string(), "1".to_string());
418 query.insert("sort".to_string(), "name".to_string());
419
420 let s = query.to_query_string();
421 assert!(s.contains("page=1"));
423 assert!(s.contains("sort=name"));
424}
425
426#[test]
427fn test_query_params_empty() {
428 let query = QueryParams::new();
429 assert!(query.is_empty());
430 assert_eq!(query.len(), 0);
431
432 let mut query = QueryParams::new();
433 query.insert("key".to_string(), "value".to_string());
434 assert!(!query.is_empty());
435 assert_eq!(query.len(), 1);
436}
437
438#[test]
439fn test_empty_query_string() {
440 let query = QueryParams::from_query_string("");
441 assert!(query.is_empty());
442}