rocket_community/route/uri.rs
1use std::fmt;
2
3use crate::form::ValueField;
4use crate::http::ext::IntoOwned;
5use crate::http::uri::{self, Origin, Path};
6use crate::route::Segment;
7
8/// A route URI which is matched against requests.
9///
10/// A route URI is composed of two components:
11///
12/// * `base`
13///
14/// Otherwise known as the route's "mount point", the `base` is a static
15/// [`Origin`] that prefixes the route URI. All route URIs have a `base`.
16/// When routes are created manually with [`Route::new()`], the base
17/// defaults to `/`. When mounted via [`Rocket::mount()`], the base is
18/// explicitly specified as the first argument.
19///
20/// ```rust
21/// # extern crate rocket_community as rocket;
22/// use rocket::Route;
23/// use rocket::http::Method;
24/// # use rocket::route::dummy_handler as handler;
25///
26/// let route = Route::new(Method::Get, "/foo/<bar>", handler);
27/// assert_eq!(route.uri.base(), "/");
28///
29/// let rocket = rocket::build().mount("/base", vec![route]);
30/// let routes: Vec<_> = rocket.routes().collect();
31/// assert_eq!(routes[0].uri.base(), "/base");
32/// ```
33///
34/// * `origin`
35///
36/// Otherwise known as the "route URI", the `origin` is an [`Origin`] with
37/// potentially dynamic (`<dyn>` or `<dyn..>`) segments. It is prefixed with
38/// the `base`. This is the URI which is matched against incoming requests
39/// for routing.
40///
41/// ```rust
42/// # extern crate rocket_community as rocket;
43/// use rocket::Route;
44/// use rocket::http::Method;
45/// # use rocket::route::dummy_handler as handler;
46///
47/// let route = Route::new(Method::Get, "/foo/<bar>", handler);
48/// assert_eq!(route.uri, "/foo/<bar>");
49///
50/// let rocket = rocket::build().mount("/base", vec![route]);
51/// let routes: Vec<_> = rocket.routes().collect();
52/// assert_eq!(routes[0].uri, "/base/foo/<bar>");
53/// ```
54///
55/// [`Rocket::mount()`]: crate::Rocket::mount()
56/// [`Route::new()`]: crate::Route::new()
57#[derive(Debug, Clone)]
58pub struct RouteUri<'a> {
59 /// The mount point.
60 pub(crate) base: Origin<'a>,
61 /// The URI _without_ the `base` mount point.
62 pub(crate) unmounted_origin: Origin<'a>,
63 /// The URI _with_ the base mount point. This is the canonical route URI.
64 pub(crate) uri: Origin<'a>,
65 /// Cached metadata about this URI.
66 pub(crate) metadata: Metadata,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub(crate) enum Color {
71 /// Fully static: no dynamic components.
72 Static = 3,
73 /// Partially static/dynamic: some, but not all, dynamic components.
74 Partial = 2,
75 /// Fully dynamic: no static components.
76 Wild = 1,
77}
78
79#[derive(Debug, Clone)]
80pub(crate) struct Metadata {
81 /// Segments in the route URI, including base.
82 pub uri_segments: Vec<Segment>,
83 /// Numbers of segments in `uri_segments` that belong to the base.
84 pub base_len: usize,
85 /// `(name, value)` of the query segments that are static.
86 pub static_query_fields: Vec<(String, String)>,
87 /// The "color" of the route path.
88 pub path_color: Color,
89 /// The "color" of the route query, if there is query.
90 pub query_color: Option<Color>,
91 /// Whether the path has a `<trailing..>` parameter.
92 pub dynamic_trail: bool,
93}
94
95type Result<T, E = uri::Error<'static>> = std::result::Result<T, E>;
96
97impl<'a> RouteUri<'a> {
98 /// Create a new `RouteUri`.
99 ///
100 /// Panics if `base` or `uri` cannot be parsed as `Origin`s.
101 #[track_caller]
102 pub(crate) fn new(base: &str, uri: &str) -> RouteUri<'static> {
103 Self::try_new(base, uri).expect("expected valid route URIs")
104 }
105
106 /// Creates a new `RouteUri` from a `base` mount point and a route `uri`.
107 ///
108 /// This is a fallible variant of [`RouteUri::new`] which returns an `Err`
109 /// if `base` or `uri` cannot be parsed as [`Origin`]s.
110 /// INTERNAL!
111 #[doc(hidden)]
112 pub fn try_new(base: &str, uri: &str) -> Result<RouteUri<'static>> {
113 let mut base = Origin::parse(base)
114 .map_err(|e| e.into_owned())?
115 .into_normalized()
116 .into_owned();
117
118 base.clear_query();
119
120 let origin = Origin::parse_route(uri)
121 .map_err(|e| e.into_owned())?
122 .into_normalized()
123 .into_owned();
124
125 // Distinguish for routes `/` with bases of `/foo/` and `/foo`. The
126 // latter base, without a trailing slash, should combine as `/foo`.
127 let route_uri = match origin.path().as_str() {
128 "/" if !base.has_trailing_slash() => match origin.query() {
129 Some(query) => format!("{}?{}", base, query),
130 None => base.to_string(),
131 },
132 _ => format!("{}{}", base, origin),
133 };
134
135 let uri = Origin::parse_route(&route_uri)
136 .map_err(|e| e.into_owned())?
137 .into_normalized()
138 .into_owned();
139
140 let metadata = Metadata::from(&base, &uri);
141
142 Ok(RouteUri {
143 base,
144 unmounted_origin: origin,
145 uri,
146 metadata,
147 })
148 }
149
150 /// Returns the complete route URI.
151 ///
152 /// **Note:** `RouteURI` derefs to the `Origin` returned by this method, so
153 /// this method should rarely be called directly.
154 ///
155 /// # Example
156 ///
157 /// ```rust
158 /// # extern crate rocket_community as rocket;
159 /// use rocket::Route;
160 /// use rocket::http::Method;
161 /// # use rocket::route::dummy_handler as handler;
162 ///
163 /// let route = Route::new(Method::Get, "/foo/bar?a=1", handler);
164 ///
165 /// // Use `inner()` directly:
166 /// assert_eq!(route.uri.inner().query().unwrap(), "a=1");
167 ///
168 /// // Use the deref implementation. This is preferred:
169 /// assert_eq!(route.uri.query().unwrap(), "a=1");
170 /// ```
171 pub fn inner(&self) -> &Origin<'a> {
172 &self.uri
173 }
174
175 /// The base mount point of this route URI.
176 ///
177 /// # Example
178 ///
179 /// ```rust
180 /// # extern crate rocket_community as rocket;
181 /// use rocket::Route;
182 /// use rocket::http::Method;
183 /// # use rocket::route::dummy_handler as handler;
184 /// # use rocket::uri;
185 ///
186 /// let route = Route::new(Method::Get, "/foo/bar?a=1", handler);
187 /// assert_eq!(route.uri.base(), "/");
188 ///
189 /// let route = route.rebase(uri!("/boo"));
190 /// assert_eq!(route.uri.base(), "/boo");
191 ///
192 /// let route = route.rebase(uri!("/foo"));
193 /// assert_eq!(route.uri.base(), "/foo/boo");
194 /// ```
195 #[inline(always)]
196 pub fn base(&self) -> Path<'_> {
197 self.base.path()
198 }
199
200 /// The route URI _without_ the base mount point.
201 ///
202 /// # Example
203 ///
204 /// ```rust
205 /// # extern crate rocket_community as rocket;
206 /// use rocket::Route;
207 /// use rocket::http::Method;
208 /// # use rocket::route::dummy_handler as handler;
209 /// # use rocket::uri;
210 ///
211 /// let route = Route::new(Method::Get, "/foo/bar?a=1", handler);
212 /// let route = route.rebase(uri!("/boo"));
213 ///
214 /// assert_eq!(route.uri, "/boo/foo/bar?a=1");
215 /// assert_eq!(route.uri.base(), "/boo");
216 /// assert_eq!(route.uri.unmounted(), "/foo/bar?a=1");
217 /// ```
218 #[inline(always)]
219 pub fn unmounted(&self) -> &Origin<'a> {
220 &self.unmounted_origin
221 }
222
223 /// Get the default rank of a route with this URI.
224 ///
225 /// The route's default rank is determined based on the presence or absence
226 /// of static and dynamic paths and queries. See the documentation for
227 /// [`Route::new`][`crate::Route::new`] for a table summarizing the exact default ranks.
228 ///
229 /// | path | query | rank |
230 /// |---------|---------|------|
231 /// | static | static | -12 |
232 /// | static | partial | -11 |
233 /// | static | wild | -10 |
234 /// | static | none | -9 |
235 /// | partial | static | -8 |
236 /// | partial | partial | -7 |
237 /// | partial | wild | -6 |
238 /// | partial | none | -5 |
239 /// | wild | static | -4 |
240 /// | wild | partial | -3 |
241 /// | wild | wild | -2 |
242 /// | wild | none | -1 |
243 pub(crate) fn default_rank(&self) -> isize {
244 let raw_path_weight = self.metadata.path_color as u8;
245 let raw_query_weight = self.metadata.query_color.map_or(0, |c| c as u8);
246 let raw_weight = (raw_path_weight << 2) | raw_query_weight;
247
248 // We subtract `3` because `raw_path` is never `0`: 0b0100 = 4 - 3 = 1.
249 -((raw_weight as isize) - 3)
250 }
251}
252
253impl Metadata {
254 fn from(base: &Origin<'_>, uri: &Origin<'_>) -> Self {
255 let uri_segments = uri
256 .path()
257 .raw_segments()
258 .map(Segment::from)
259 .collect::<Vec<_>>();
260
261 let query_segs = uri
262 .query()
263 .map(|q| q.raw_segments().map(Segment::from).collect::<Vec<_>>())
264 .unwrap_or_default();
265
266 let static_query_fields = query_segs
267 .iter()
268 .filter(|s| !s.dynamic)
269 .map(|s| ValueField::parse(&s.value))
270 .map(|f| (f.name.source().to_string(), f.value.to_string()))
271 .collect();
272
273 let static_path = uri_segments.iter().all(|s| !s.dynamic);
274 let wild_path = !uri_segments.is_empty() && uri_segments.iter().all(|s| s.dynamic);
275 let path_color = match (static_path, wild_path) {
276 (true, _) => Color::Static,
277 (_, true) => Color::Wild,
278 (_, _) => Color::Partial,
279 };
280
281 let query_color = (!query_segs.is_empty()).then(|| {
282 let static_query = query_segs.iter().all(|s| !s.dynamic);
283 let wild_query = query_segs.iter().all(|s| s.dynamic);
284 match (static_query, wild_query) {
285 (true, _) => Color::Static,
286 (_, true) => Color::Wild,
287 (_, _) => Color::Partial,
288 }
289 });
290
291 let dynamic_trail = uri_segments.last().is_some_and(|p| p.dynamic_trail);
292 let segments = base.path().segments();
293 let num_empty = segments.clone().filter(|s| s.is_empty()).count();
294 let base_len = segments.num() - num_empty;
295
296 Metadata {
297 uri_segments,
298 base_len,
299 static_query_fields,
300 path_color,
301 query_color,
302 dynamic_trail,
303 }
304 }
305}
306
307impl<'a> std::ops::Deref for RouteUri<'a> {
308 type Target = Origin<'a>;
309
310 fn deref(&self) -> &Self::Target {
311 self.inner()
312 }
313}
314
315impl fmt::Display for RouteUri<'_> {
316 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317 self.uri.fmt(f)
318 }
319}
320
321impl<'a, 'b> PartialEq<Origin<'b>> for RouteUri<'a> {
322 fn eq(&self, other: &Origin<'b>) -> bool {
323 self.inner() == other
324 }
325}
326
327impl PartialEq<str> for RouteUri<'_> {
328 fn eq(&self, other: &str) -> bool {
329 self.inner() == other
330 }
331}
332
333impl PartialEq<&str> for RouteUri<'_> {
334 fn eq(&self, other: &&str) -> bool {
335 self.inner() == *other
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 macro_rules! assert_uri_equality {
342 ($base:expr, $path:expr => $ebase:expr, $epath:expr, $efull:expr) => {
343 let uri = super::RouteUri::new($base, $path);
344 assert_eq!(
345 uri, $efull,
346 "complete URI mismatch. expected {}, got {}",
347 $efull, uri
348 );
349 assert_eq!(
350 uri.base(),
351 $ebase,
352 "expected base {}, got {}",
353 $ebase,
354 uri.base()
355 );
356 assert_eq!(
357 uri.unmounted(),
358 $epath,
359 "expected unmounted {}, got {}",
360 $epath,
361 uri.unmounted()
362 );
363 };
364 }
365
366 #[test]
367 fn test_route_uri_composition() {
368 assert_uri_equality!("/", "/" => "/", "/", "/");
369 assert_uri_equality!("/", "/foo" => "/", "/foo", "/foo");
370 assert_uri_equality!("/", "/foo/bar" => "/", "/foo/bar", "/foo/bar");
371 assert_uri_equality!("/", "/foo/" => "/", "/foo/", "/foo/");
372 assert_uri_equality!("/", "/foo/bar/" => "/", "/foo/bar/", "/foo/bar/");
373
374 assert_uri_equality!("/foo", "/" => "/foo", "/", "/foo");
375 assert_uri_equality!("/foo", "/bar" => "/foo", "/bar", "/foo/bar");
376 assert_uri_equality!("/foo", "/bar/" => "/foo", "/bar/", "/foo/bar/");
377 assert_uri_equality!("/foo", "/?baz" => "/foo", "/?baz", "/foo?baz");
378 assert_uri_equality!("/foo", "/bar?baz" => "/foo", "/bar?baz", "/foo/bar?baz");
379 assert_uri_equality!("/foo", "/bar/?baz" => "/foo", "/bar/?baz", "/foo/bar/?baz");
380
381 assert_uri_equality!("/foo/", "/" => "/foo/", "/", "/foo/");
382 assert_uri_equality!("/foo/", "/bar" => "/foo/", "/bar", "/foo/bar");
383 assert_uri_equality!("/foo/", "/bar/" => "/foo/", "/bar/", "/foo/bar/");
384 assert_uri_equality!("/foo/", "/?baz" => "/foo/", "/?baz", "/foo/?baz");
385 assert_uri_equality!("/foo/", "/bar?baz" => "/foo/", "/bar?baz", "/foo/bar?baz");
386 assert_uri_equality!("/foo/", "/bar/?baz" => "/foo/", "/bar/?baz", "/foo/bar/?baz");
387
388 assert_uri_equality!("/foo?baz", "/" => "/foo", "/", "/foo");
389 assert_uri_equality!("/foo?baz", "/bar" => "/foo", "/bar", "/foo/bar");
390 assert_uri_equality!("/foo?baz", "/bar/" => "/foo", "/bar/", "/foo/bar/");
391 assert_uri_equality!("/foo/?baz", "/" => "/foo/", "/", "/foo/");
392 assert_uri_equality!("/foo/?baz", "/bar" => "/foo/", "/bar", "/foo/bar");
393 assert_uri_equality!("/foo/?baz", "/bar/" => "/foo/", "/bar/", "/foo/bar/");
394 }
395}