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}