via_router/path/
span.rs

1use smallvec::SmallVec;
2
3/// A matched range in the url path.
4///
5#[derive(Debug, PartialEq)]
6pub struct Span {
7    start: usize,
8    end: usize,
9}
10
11pub fn split(path: &'static str) -> Vec<Span> {
12    let mut segments = Vec::new();
13    let mut start = None;
14
15    for (index, byte) in path.bytes().enumerate() {
16        match (start, byte) {
17            // The start index is set and the current byte is a `/`. This is the
18            // end of the current segment.
19            (Some(from), b'/') => {
20                // Push the segment range to the segments vector.
21                segments.push(Span::new(from, index));
22
23                // Reset the start index.
24                start = None;
25            }
26
27            // The start index is set and the current byte is not a terminating
28            // `/`. Continue to the next byte.
29            (Some(_), _) => {}
30
31            // The start index is not set and the current byte is a `/`. Continue
32            // to the next byte.
33            (None, b'/') => {}
34
35            // The current byte is a `/` and the start index is not set. This is
36            // the start of a new segment.
37            (None, _) => {
38                // Set the start index to the current index.
39                start = Some(index);
40            }
41        }
42    }
43
44    // It is likely that the last character in the path is not a `/`. Check if
45    // the start index is set. If so, push the last segment to the segments vec
46    // using the length of the path as the end index.
47    if let Some(from) = start {
48        segments.push(Span::new(from, path.len()));
49    }
50
51    segments
52}
53
54#[inline]
55pub fn split_into(segments: &mut SmallVec<[Span; 5]>, path: &str) {
56    let mut start = None;
57
58    for (index, byte) in path.chars().enumerate() {
59        match (start, byte) {
60            // The start index is set and the current byte is a `/`. This is the
61            // end of the current segment.
62            (Some(from), '/') => {
63                // Push the segment range to the segments vector.
64                segments.push(Span::new(from, index));
65
66                // Reset the start index.
67                start = None;
68            }
69
70            // The start index is set and the current byte is not a terminating
71            // `/`. Continue to the next byte.
72            (Some(_), _) => {}
73
74            // The start index is not set and the current byte is a `/`. Continue
75            // to the next byte.
76            (None, '/') => {}
77
78            // The current byte is a `/` and the start index is not set. This is
79            // the start of a new segment.
80            (None, _) => {
81                // Set the start index to the current index.
82                start = Some(index);
83            }
84        }
85    }
86
87    // It is likely that the last character in the path is not a `/`. Check if
88    // the start index is set. If so, push the last segment to the segments vec
89    // using the length of the path as the end index.
90    if let Some(from) = start {
91        segments.push(Span::new(from, path.len()));
92    }
93}
94
95impl Span {
96    /// Returns the start offset of the matched range.
97    ///
98    pub fn start(&self) -> usize {
99        self.start
100    }
101
102    /// Returns the end offset of the matched range.
103    ///
104    pub fn end(&self) -> usize {
105        self.end
106    }
107}
108
109impl Span {
110    pub(crate) fn new(start: usize, end: usize) -> Self {
111        Self { start, end }
112    }
113}
114
115impl Clone for Span {
116    fn clone(&self) -> Self {
117        Self {
118            start: self.start,
119            end: self.end,
120        }
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use smallvec::SmallVec;
127
128    use super::{split, split_into, Span};
129
130    const PATHS: [&str; 15] = [
131        "/home/about",
132        "/products/item/123",
133        "/blog/posts/2024/june",
134        "/user/profile/settings",
135        "/services/contact",
136        "/search/results?q=books",
137        "/news/latest",
138        "/portfolio/designs",
139        "/faq",
140        "/support/tickets",
141        "//home//about",
142        "/products//item/123",
143        "/blog/posts/2024//june",
144        "/user//profile/settings",
145        "/services/contact//us",
146    ];
147
148    fn get_expected_results() -> [Vec<Span>; 15] {
149        [
150            vec![Span::new(1, 5), Span::new(6, 11)],
151            vec![Span::new(1, 9), Span::new(10, 14), Span::new(15, 18)],
152            vec![
153                Span::new(1, 5),
154                Span::new(6, 11),
155                Span::new(12, 16),
156                Span::new(17, 21),
157            ],
158            vec![Span::new(1, 5), Span::new(6, 13), Span::new(14, 22)],
159            vec![Span::new(1, 9), Span::new(10, 17)],
160            vec![Span::new(1, 7), Span::new(8, 23)],
161            vec![Span::new(1, 5), Span::new(6, 12)],
162            vec![Span::new(1, 10), Span::new(11, 18)],
163            vec![Span::new(1, 4)],
164            vec![Span::new(1, 8), Span::new(9, 16)],
165            vec![Span::new(2, 6), Span::new(8, 13)],
166            vec![Span::new(1, 9), Span::new(11, 15), Span::new(16, 19)],
167            vec![
168                Span::new(1, 5),
169                Span::new(6, 11),
170                Span::new(12, 16),
171                Span::new(18, 22),
172            ],
173            vec![Span::new(1, 5), Span::new(7, 14), Span::new(15, 23)],
174            vec![Span::new(1, 9), Span::new(10, 17), Span::new(19, 21)],
175        ]
176    }
177
178    #[test]
179    fn test_split() {
180        let expected_results = get_expected_results();
181
182        for (path_index, path_value) in PATHS.iter().enumerate() {
183            let segments = split(path_value);
184
185            for (segment_index, segment_value) in segments.into_iter().enumerate() {
186                assert_eq!(segment_value, expected_results[path_index][segment_index]);
187            }
188        }
189    }
190
191    #[test]
192    fn test_split_into() {
193        let expected_results = get_expected_results();
194
195        for (path_index, path_value) in PATHS.iter().enumerate() {
196            let mut segments = SmallVec::new();
197
198            split_into(&mut segments, path_value);
199
200            for (segment_index, segment_value) in segments.into_iter().enumerate() {
201                assert_eq!(segment_value, expected_results[path_index][segment_index]);
202            }
203        }
204    }
205}