rocket/route/
uri.rs

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