Skip to main content

ntex_router/
path.rs

1use std::ops::Index;
2
3use serde::de;
4
5use crate::de::PathDeserializer;
6use crate::{Resource, ResourcePath};
7
8#[derive(Debug, Clone)]
9pub(super) enum PathItem {
10    Static(&'static str),
11    Segment(String),
12    IdxSegment(u32, u32),
13}
14
15/// Resource path match information
16///
17/// Wraps a path source and stores the values of dynamic segments matched by
18/// a [`Router`](crate::Router).
19#[derive(Debug)]
20pub struct Path<T> {
21    resource: T,
22    pub(super) skip: u32,
23    pub(super) segments: Vec<(&'static str, PathItem)>,
24}
25
26impl<T: Default> Default for Path<T> {
27    fn default() -> Self {
28        Path {
29            resource: T::default(),
30            skip: 0,
31            segments: Vec::new(),
32        }
33    }
34}
35
36impl<T: Clone> Clone for Path<T> {
37    fn clone(&self) -> Self {
38        Path {
39            resource: self.resource.clone(),
40            skip: self.skip,
41            segments: self.segments.clone(),
42        }
43    }
44}
45
46impl<T: ResourcePath> Path<T> {
47    /// Creates a new path state for the path source.
48    pub fn new(resource: T) -> Path<T> {
49        Path {
50            resource,
51            skip: 0,
52            segments: Vec::new(),
53        }
54    }
55
56    #[inline]
57    /// Get reference to inner path instance
58    pub fn get_ref(&self) -> &T {
59        &self.resource
60    }
61
62    #[inline]
63    /// Get mutable reference to inner path instance
64    pub fn get_mut(&mut self) -> &mut T {
65        &mut self.resource
66    }
67
68    #[inline]
69    /// Consume the path state and return the inner path instance
70    pub fn into_inner(self) -> T {
71        self.resource
72    }
73
74    #[inline]
75    /// Path that is not matched yet, e.g. the rest of the path after a prefix
76    /// resource matched.
77    ///
78    /// Unlike [`get_ref().path()`](ResourcePath::path), which is the full path,
79    /// it excludes the skipped part of the path.
80    pub fn path(&self) -> &str {
81        let skip = self.skip as usize;
82        let path = self.resource.path();
83        if skip <= path.len() { &path[skip..] } else { "" }
84    }
85
86    #[inline]
87    /// Set new path source and reset state
88    pub fn set(&mut self, resource: T) {
89        self.skip = 0;
90        self.resource = resource;
91        self.segments.clear();
92    }
93
94    #[inline]
95    /// Reset skipped length and matched segments
96    pub fn reset(&mut self) {
97        self.skip = 0;
98        self.segments.clear();
99    }
100
101    #[inline]
102    /// Skip first `n` bytes in path, matching starts after them
103    ///
104    /// The skipped length is limited to the path length.
105    pub fn skip(&mut self, n: u32) {
106        let len = u32::try_from(self.resource.path().len()).unwrap_or(u32::MAX);
107        self.skip = self.skip.saturating_add(n).min(len);
108    }
109
110    // pub(crate) fn add(&mut self, name: &'static str, value: String) {
111    //     self.segments.push((name, PathItem::Segment(value)))
112    // }
113
114    #[doc(hidden)]
115    pub fn add_static(&mut self, name: &'static str, value: &'static str) {
116        self.segments.push((name, PathItem::Static(value)));
117    }
118
119    #[inline]
120    /// Check if there are any matched segments
121    pub fn is_empty(&self) -> bool {
122        self.segments.is_empty()
123    }
124
125    #[inline]
126    /// Number of matched segments
127    pub fn len(&self) -> usize {
128        self.segments.len()
129    }
130
131    /// Get matched segment by name without type conversion
132    ///
133    /// If no segment is named `tail`, the `tail` key returns the unprocessed
134    /// part of the path, see [`path()`](Self::path).
135    pub fn get(&self, key: &str) -> Option<&str> {
136        for item in &self.segments {
137            if key == item.0 {
138                return match item.1 {
139                    PathItem::Static(s) => Some(s),
140                    PathItem::Segment(ref s) => Some(s),
141                    PathItem::IdxSegment(s, e) => {
142                        Some(&self.resource.path()[(s as usize)..(e as usize)])
143                    }
144                };
145            }
146        }
147        if key == "tail" { Some(self.path()) } else { None }
148    }
149
150    /// Iterator over matched segments as `(name, value)` pairs, in pattern
151    /// order
152    pub fn iter(&self) -> PathIter<'_, T> {
153        PathIter {
154            idx: 0,
155            params: self,
156        }
157    }
158
159    /// Try to deserialize matched segments to a specified type `U`
160    ///
161    /// See [`PathDeserializer`].
162    pub fn load<'de, U: serde::Deserialize<'de>>(&'de self) -> Result<U, de::value::Error> {
163        de::Deserialize::deserialize(PathDeserializer::new(self))
164    }
165}
166
167/// Iterator over matched segments, see [`Path::iter()`]
168#[derive(Debug)]
169pub struct PathIter<'a, T> {
170    idx: usize,
171    params: &'a Path<T>,
172}
173
174impl<'a, T: ResourcePath> Iterator for PathIter<'a, T> {
175    type Item = (&'a str, &'a str);
176
177    #[inline]
178    fn next(&mut self) -> Option<(&'a str, &'a str)> {
179        if self.idx < self.params.len() {
180            let idx = self.idx;
181            let res = match self.params.segments[idx].1 {
182                PathItem::Static(s) => s,
183                PathItem::Segment(ref s) => s.as_str(),
184                PathItem::IdxSegment(s, e) => {
185                    &self.params.resource.path()[(s as usize)..(e as usize)]
186                }
187            };
188            self.idx += 1;
189            return Some((self.params.segments[idx].0, res));
190        }
191        None
192    }
193}
194
195impl<'a, T: ResourcePath> IntoIterator for &'a Path<T> {
196    type Item = (&'a str, &'a str);
197    type IntoIter = PathIter<'a, T>;
198
199    fn into_iter(self) -> PathIter<'a, T> {
200        self.iter()
201    }
202}
203
204impl<'a, T: ResourcePath> Index<&'a str> for Path<T> {
205    type Output = str;
206
207    fn index(&self, name: &'a str) -> &str {
208        self.get(name)
209            .expect("Value for parameter is not available")
210    }
211}
212
213impl<T: ResourcePath> Index<usize> for Path<T> {
214    type Output = str;
215
216    fn index(&self, idx: usize) -> &str {
217        let Some((_, item)) = self.segments.get(idx) else {
218            panic!(
219                "Segment index {idx} is out of range, path has {} segments",
220                self.segments.len()
221            )
222        };
223        match *item {
224            PathItem::Static(s) => s,
225            PathItem::Segment(ref s) => s,
226            PathItem::IdxSegment(s, e) => &self.resource.path()[(s as usize)..(e as usize)],
227        }
228    }
229}
230
231impl<T: ResourcePath> Resource<T> for Path<T> {
232    fn path(&self) -> &str {
233        self.path()
234    }
235
236    fn resource_path(&mut self) -> &mut Path<T> {
237        self
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244
245    #[test]
246    fn test_path() {
247        let mut p: Path<String> = Path::default();
248        assert_eq!(p.get_ref(), &String::new());
249        p.get_mut().push_str("test");
250        assert_eq!(p.get_ref().as_str(), "test");
251        let p2 = p.clone();
252        assert_eq!(p2.get_ref().as_str(), "test");
253
254        p.skip(2);
255        assert_eq!(p.get("tail").unwrap(), "st");
256        assert_eq!(p.get("unknown"), None);
257        assert_eq!(p.path(), "st");
258        assert_eq!(p.get_ref().path(), "test");
259
260        p.reset();
261        assert_eq!(p.path(), "test");
262
263        p.segments.push(("k1", PathItem::IdxSegment(0, 2)));
264        assert_eq!(p.get("k1").unwrap(), "te");
265    }
266
267    #[test]
268    fn test_path_set_and_items() {
269        let mut p = Path::new("/first/path");
270        p.skip(7);
271        p.add_static("st", "static");
272        p.segments
273            .push(("seg", PathItem::Segment("owned".to_string())));
274        assert_eq!(p.path(), "path");
275        assert_eq!(p.len(), 2);
276        assert_eq!(p.get("st"), Some("static"));
277        assert_eq!(p.get("seg"), Some("owned"));
278        assert_eq!(&p[0], "static");
279        assert_eq!(&p[1], "owned");
280        assert_eq!(
281            p.iter().collect::<Vec<_>>(),
282            vec![("st", "static"), ("seg", "owned")]
283        );
284
285        // skip is limited to the path length
286        p.skip(100);
287        assert_eq!(p.path(), "");
288
289        p.set("/second");
290        assert!(p.is_empty());
291        assert_eq!(p.path(), "/second");
292        assert_eq!(*p.get_ref(), "/second");
293    }
294
295    #[test]
296    #[should_panic(expected = "Value for parameter is not available")]
297    fn test_path_index_missing() {
298        let p = Path::new("/");
299        let _ = &p["missing"];
300    }
301
302    #[test]
303    #[should_panic(expected = "Segment index 1 is out of range, path has 1 segments")]
304    fn index_out_of_range() {
305        let mut p = Path::new("/test");
306        p.add_static("st", "static");
307        assert_eq!(&p[0], "static");
308        let _ = &p[1];
309    }
310}