via_router/
visitor.rs

1use smallvec::SmallVec;
2
3use crate::path::{Param, Pattern, Span};
4use crate::routes::Node;
5
6/// A matched node in the route tree.
7///
8/// Contains a reference to the route associated with the node and additional
9/// metadata about the match.
10///
11#[derive(Debug)]
12pub struct Found {
13    /// True if there were no more segments to match against the children of the
14    /// matched node. Otherwise, false.
15    ///
16    pub is_leaf: bool,
17
18    /// The key of the route associated with the node that matched the path
19    /// segment.
20    ///
21    pub route: Option<usize>,
22
23    /// The name of the dynamic parameter that matched the path segment.
24    ///
25    pub param: Option<Param>,
26
27    /// The range of the path segment that matched the node.
28    ///
29    pub at: Span,
30}
31
32pub fn visit(path: &str, nodes: &[Node], segments: &SmallVec<[Span; 5]>) -> Vec<Found> {
33    let mut results = Vec::new();
34    let root = match nodes.first() {
35        Some(node) => node,
36        None => {
37            // This is an edge-case that can be caused by corrupt memory or a bug
38            // in the router. We should log the error and not match any routes.
39            // Placeholder for tracing...
40            return results;
41        }
42    };
43
44    match segments.first() {
45        Some(range) => {
46            // Append the root match to the results vector.
47            results.push(Found::new(root.route, None, Span::new(0, 0)));
48
49            // Begin the search for matches recursively starting with descendants of
50            // the root node.
51            visit_node(&mut results, nodes, root, path, segments, range, 0);
52        }
53        None => {
54            // Append the root match to the results vector.
55            results.push(Found::leaf(root.route, None, Span::new(0, 0)));
56
57            // Perform a shallow search for descendants of the root node that have a
58            // `CatchAll` pattern.
59            visit_index(&mut results, nodes, root);
60        }
61    }
62
63    results
64}
65
66impl Found {
67    fn new(route: Option<usize>, param: Option<Param>, at: Span) -> Self {
68        Self {
69            is_leaf: false,
70            route,
71            param,
72            at,
73        }
74    }
75
76    fn leaf(route: Option<usize>, param: Option<Param>, at: Span) -> Self {
77        Self {
78            is_leaf: true,
79            route,
80            param,
81            at,
82        }
83    }
84}
85
86/// Recursively search for descendants of the node at `key` that have a
87/// pattern that matches the path segment at `index`. If a match is found,
88/// we'll add it to `results` and continue our search with the descendants
89/// of matched node against the path segment at next index.
90fn visit_node(
91    // A mutable reference to a vector that contains the matches that we
92    // have found so far.
93    results: &mut Vec<Found>,
94
95    // A reference to the route store that contains the route tree.
96    nodes: &[Node],
97
98    // A reference to the most recently matched node.
99    node: &Node,
100
101    // The url path that we are attempting to match against the route tree.
102    path: &str,
103
104    segments: &SmallVec<[Span; 5]>,
105
106    // The start and end offset of the path segment at `index` in
107    // `self.path_value`.
108    range: &Span,
109
110    // The index of the path segment in `self.segments` that we are matching
111    // against the node at `key`.
112    index: usize,
113) {
114    // Get the value of the path segment at `range`. We'll eagerly borrow
115    // and cache this slice from `path` to avoid having to build the ref
116    // for each descendant with a static pattern.
117    let segment = match path.get(range.start()..range.end()) {
118        Some(slice) => slice,
119        None => {
120            // Placeholder for tracing...
121            return;
122        }
123    };
124
125    // Search for descendant nodes that match `segment`.
126    for key in node.entries() {
127        let entry = match nodes.get(*key) {
128            Some(entry) => entry,
129            None => {
130                // Placeholder for tracing...
131                continue;
132            }
133        };
134
135        // Check if `descendant` has a pattern that matches `path_segment`.
136        match &entry.pattern {
137            // The next node has a `Static` pattern that matches the value
138            // of the path segment.
139            Pattern::Static(param) if segment == param.as_str() => {
140                // Calculate the index of the next path segment.
141                let index = index + 1;
142                let at = range.clone();
143
144                match segments.get(index) {
145                    Some(range) => {
146                        // Append the match to the results vector.
147                        results.push(Found::new(entry.route, None, at));
148
149                        // Continue searching for descendants of the current node
150                        // that match the the next path segment.
151                        visit_node(results, nodes, entry, path, segments, range, index);
152                    }
153                    None => {
154                        // Append the match to the results vector.
155                        results.push(Found::leaf(entry.route, None, at));
156
157                        // Perform a shallow search for descendants of the
158                        // current node that have a `CatchAll` pattern.
159                        visit_index(results, nodes, entry);
160                    }
161                }
162            }
163
164            // The next node has a `Dynamic` pattern. Therefore, we consider
165            // it a match regardless of the value of the path segment.
166            Pattern::Dynamic(param) => {
167                // Calculate the index of the next path segment.
168                let index = index + 1;
169                let at = range.clone();
170
171                match segments.get(index) {
172                    Some(range) => {
173                        // Append the match to the results vector.
174                        results.push(Found::new(entry.route, Some(param.clone()), at));
175
176                        // Continue searching for descendants of the current node
177                        // that match the the next path segment.
178                        visit_node(results, nodes, entry, path, segments, range, index);
179                    }
180                    None => {
181                        // Append the match to the results vector.
182                        results.push(Found::leaf(entry.route, Some(param.clone()), at));
183
184                        // Perform a shallow search for descendants of the
185                        // current node that have a `CatchAll` pattern.
186                        visit_index(results, nodes, entry);
187                    }
188                }
189            }
190
191            // The next node has a `CatchAll` pattern and will be considered
192            // an exact match. Due to the nature of `CatchAll` patterns, we
193            // do not have to continue searching for descendants of this
194            // node that match the remaining path segments.
195            Pattern::Wildcard(param) => {
196                results.push(Found::leaf(
197                    entry.route,
198                    Some(param.clone()),
199                    Span::new(range.start(), path.len()),
200                ));
201            }
202
203            // We don't have to check and see if the pattern is `Pattern::Root`
204            // since we already added our root node to the matches vector.
205            _ => {}
206        }
207    }
208}
209
210/// Perform a shallow search for descendants of the `node` that have a `CatchAll`
211/// pattern.
212///
213fn visit_index(
214    // A mutable reference to a vector that contains the matches that we
215    // have found so far.
216    results: &mut Vec<Found>,
217
218    // A reference to the route store that contains the route tree.
219    nodes: &[Node],
220
221    // A reference to the most recently matched node.
222    node: &Node,
223) {
224    // Perform a shallow search for descendants of the current node that
225    // have a `CatchAll` pattern. This is required to support matching the
226    // "index" path of a descendant node with a `CatchAll` pattern.
227    for key in node.entries() {
228        let entry = match nodes.get(*key) {
229            Some(entry) => entry,
230            None => {
231                // Placeholder for tracing...
232                continue;
233            }
234        };
235
236        // Check if `descendant` has a `CatchAll` pattern.
237        if let Pattern::Wildcard(param) = &entry.pattern {
238            // Append the match as a leaf to the results vector.
239            results.push(Found::leaf(
240                entry.route,
241                Some(param.clone()),
242                Span::new(0, 0),
243            ));
244        }
245    }
246}