rustapi_core/
path_params.rs

1//! Path parameter types with optimized storage
2//!
3//! This module provides efficient path parameter storage using stack allocation
4//! for the common case of having 4 or fewer parameters.
5
6use smallvec::SmallVec;
7use std::collections::HashMap;
8
9/// Maximum number of path parameters to store on the stack.
10/// Most routes have 1-4 parameters, so this covers the majority of cases
11/// without heap allocation.
12pub const STACK_PARAMS_CAPACITY: usize = 4;
13
14/// Path parameters with stack-optimized storage.
15///
16/// Uses `SmallVec` to store up to 4 key-value pairs on the stack,
17/// avoiding heap allocation for the common case.
18#[derive(Debug, Clone, Default)]
19pub struct PathParams {
20    inner: SmallVec<[(String, String); STACK_PARAMS_CAPACITY]>,
21}
22
23impl PathParams {
24    /// Create a new empty path params collection.
25    #[inline]
26    pub fn new() -> Self {
27        Self {
28            inner: SmallVec::new(),
29        }
30    }
31
32    /// Create path params with pre-allocated capacity.
33    #[inline]
34    pub fn with_capacity(capacity: usize) -> Self {
35        Self {
36            inner: SmallVec::with_capacity(capacity),
37        }
38    }
39
40    /// Insert a key-value pair.
41    #[inline]
42    pub fn insert(&mut self, key: String, value: String) {
43        self.inner.push((key, value));
44    }
45
46    /// Get a value by key.
47    #[inline]
48    pub fn get(&self, key: &str) -> Option<&String> {
49        self.inner.iter().find(|(k, _)| k == key).map(|(_, v)| v)
50    }
51
52    /// Check if a key exists.
53    #[inline]
54    pub fn contains_key(&self, key: &str) -> bool {
55        self.inner.iter().any(|(k, _)| k == key)
56    }
57
58    /// Check if the collection is empty.
59    #[inline]
60    pub fn is_empty(&self) -> bool {
61        self.inner.is_empty()
62    }
63
64    /// Get the number of parameters.
65    #[inline]
66    pub fn len(&self) -> usize {
67        self.inner.len()
68    }
69
70    /// Iterate over key-value pairs.
71    #[inline]
72    pub fn iter(&self) -> impl Iterator<Item = (&String, &String)> {
73        self.inner.iter().map(|(k, v)| (k, v))
74    }
75
76    /// Convert to a HashMap (for backwards compatibility).
77    pub fn to_hashmap(&self) -> HashMap<String, String> {
78        self.inner.iter().cloned().collect()
79    }
80}
81
82impl FromIterator<(String, String)> for PathParams {
83    fn from_iter<I: IntoIterator<Item = (String, String)>>(iter: I) -> Self {
84        Self {
85            inner: iter.into_iter().collect(),
86        }
87    }
88}
89
90impl<'a> FromIterator<(&'a str, &'a str)> for PathParams {
91    fn from_iter<I: IntoIterator<Item = (&'a str, &'a str)>>(iter: I) -> Self {
92        Self {
93            inner: iter
94                .into_iter()
95                .map(|(k, v)| (k.to_string(), v.to_string()))
96                .collect(),
97        }
98    }
99}
100
101impl From<HashMap<String, String>> for PathParams {
102    fn from(map: HashMap<String, String>) -> Self {
103        Self {
104            inner: map.into_iter().collect(),
105        }
106    }
107}
108
109impl From<PathParams> for HashMap<String, String> {
110    fn from(params: PathParams) -> Self {
111        params.inner.into_iter().collect()
112    }
113}
114
115impl<'a> IntoIterator for &'a PathParams {
116    type Item = &'a (String, String);
117    type IntoIter = std::slice::Iter<'a, (String, String)>;
118
119    fn into_iter(self) -> Self::IntoIter {
120        self.inner.iter()
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn test_small_params_on_stack() {
130        let mut params = PathParams::new();
131        params.insert("id".to_string(), "123".to_string());
132        params.insert("name".to_string(), "test".to_string());
133
134        assert_eq!(params.get("id"), Some(&"123".to_string()));
135        assert_eq!(params.get("name"), Some(&"test".to_string()));
136        assert_eq!(params.len(), 2);
137
138        // Should be on stack (not spilled)
139        assert!(!params.inner.spilled());
140    }
141
142    #[test]
143    fn test_many_params_spill_to_heap() {
144        let mut params = PathParams::new();
145        for i in 0..10 {
146            params.insert(format!("key{}", i), format!("value{}", i));
147        }
148
149        assert_eq!(params.len(), 10);
150        // Should have spilled to heap
151        assert!(params.inner.spilled());
152    }
153
154    #[test]
155    fn test_from_iterator() {
156        let params: PathParams = [("a", "1"), ("b", "2"), ("c", "3")].into_iter().collect();
157
158        assert_eq!(params.get("a"), Some(&"1".to_string()));
159        assert_eq!(params.get("b"), Some(&"2".to_string()));
160        assert_eq!(params.get("c"), Some(&"3".to_string()));
161    }
162
163    #[test]
164    fn test_to_hashmap_conversion() {
165        let mut params = PathParams::new();
166        params.insert("id".to_string(), "42".to_string());
167
168        let map = params.to_hashmap();
169        assert_eq!(map.get("id"), Some(&"42".to_string()));
170    }
171}