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}