rocket/route/
uri.rs

1use std::fmt;
2use std::borrow::Cow;
3
4use crate::http::uri::{self, Origin};
5use crate::http::ext::IntoOwned;
6use crate::form::ValueField;
7use crate::route::Segment;
8
9/// A route URI which is matched against requests.
10///
11/// A route URI is composed of two components:
12///
13///   * `base`
14///
15///     Otherwise known as the route's "mount point", the `base` is a static
16///     [`Origin`] that prefixes the route URI. All route URIs have a `base`.
17///     When routes are created manually with [`Route::new()`], the base
18///     defaults to `/`. When mounted via [`Rocket::mount()`], the base is
19///     explicitly specified as the first argument.
20///
21///     ```rust
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///     use rocket::Route;
43///     use rocket::http::Method;
44///     # use rocket::route::dummy_handler as handler;
45///
46///     let route = Route::new(Method::Get, "/foo/<bar>", handler);
47///     assert_eq!(route.uri, "/foo/<bar>");
48///
49///     let rocket = rocket::build().mount("/base", vec![route]);
50///     let routes: Vec<_> = rocket.routes().collect();
51///     assert_eq!(routes[0].uri, "/base/foo/<bar>");
52///     ```
53///
54/// [`Rocket::mount()`]: crate::Rocket::mount()
55/// [`Route::new()`]: crate::Route::new()
56#[derive(Clone)]
57pub struct RouteUri<'a> {
58    /// The source string for this URI.
59    source: Cow<'a, str>,
60    /// The mount point.
61    pub base: Origin<'a>,
62    /// The URI _without_ the `base` mount point.
63    pub unmounted_origin: Origin<'a>,
64    /// The URI _with_ the base mount point. This is the canonical route URI.
65    pub origin: Origin<'a>,
66    /// Cached metadata about this URI.
67    pub(crate) metadata: Metadata,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub(crate) enum Color {
72    /// Fully static: no dynamic components.
73    Static = 3,
74    /// Partially static/dynamic: some, but not all, dynamic components.
75    Partial = 2,
76    /// Fully dynamic: no static components.
77    Wild = 1,
78}
79
80#[derive(Debug, Clone)]
81pub(crate) struct Metadata {
82    /// Segments in the base.
83    pub base_segs: Vec<Segment>,
84    /// Segments in the path, including base.
85    pub path_segs: Vec<Segment>,
86    /// `(name, value)` of the query segments that are static.
87    pub static_query_fields: Vec<(String, String)>,
88    /// The "color" of the route path.
89    pub path_color: Color,
90    /// The "color" of the route query, if there is query.
91    pub query_color: Option<Color>,
92    /// Whether the path has a `<trailing..>` parameter.
93    pub trailing_path: bool,
94}
95
96type Result<T, E = uri::Error<'static>> = std::result::Result<T, E>;
97
98impl<'a> RouteUri<'a> {
99    /// Create a new `RouteUri`.
100    ///
101    /// This is a fallible variant of [`RouteUri::new`] which returns an `Err`
102    /// if `base` or `uri` cannot be parsed as [`Origin`]s.
103    pub(crate) fn try_new(base: &str, uri: &str) -> Result<RouteUri<'static>> {
104        let mut base = Origin::parse(base)
105            .map_err(|e| e.into_owned())?
106            .into_normalized()
107            .into_owned();
108
109        base.clear_query();
110
111        let unmounted_origin = Origin::parse_route(uri)
112            .map_err(|e| e.into_owned())?
113            .into_normalized()
114            .into_owned();
115
116        let origin = Origin::parse_route(&format!("{}/{}", base, unmounted_origin))
117            .map_err(|e| e.into_owned())?
118            .into_normalized()
119            .into_owned();
120
121        let source = origin.to_string().into();
122        let metadata = Metadata::from(&base, &origin);
123
124        Ok(RouteUri { source, base, unmounted_origin, origin, metadata })
125    }
126
127    /// Create a new `RouteUri`.
128    ///
129    /// Panics if  `base` or `uri` cannot be parsed as `Origin`s.
130    #[track_caller]
131    pub(crate) fn new(base: &str, uri: &str) -> RouteUri<'static> {
132        Self::try_new(base, uri).expect("Expected valid URIs")
133    }
134
135    /// The path of the base mount point of this route URI as an `&str`.
136    ///
137    /// # Example
138    ///
139    /// ```rust
140    /// use rocket::Route;
141    /// use rocket::http::Method;
142    /// # use rocket::route::dummy_handler as handler;
143    ///
144    /// let index = Route::new(Method::Get, "/foo/bar?a=1", handler);
145    /// assert_eq!(index.uri.base(), "/");
146    /// let index = index.map_base(|base| format!("{}{}", "/boo", base)).unwrap();
147    /// assert_eq!(index.uri.base(), "/boo");
148    /// ```
149    #[inline(always)]
150    pub fn base(&self) -> &str {
151        self.base.path().as_str()
152    }
153
154    /// The path part of this route URI as an `&str`.
155    ///
156    /// # Example
157    ///
158    /// ```rust
159    /// use rocket::Route;
160    /// use rocket::http::Method;
161    /// # use rocket::route::dummy_handler as handler;
162    ///
163    /// let index = Route::new(Method::Get, "/foo/bar?a=1", handler);
164    /// assert_eq!(index.uri.path(), "/foo/bar");
165    /// let index = index.map_base(|base| format!("{}{}", "/boo", base)).unwrap();
166    /// assert_eq!(index.uri.path(), "/boo/foo/bar");
167    /// ```
168    #[inline(always)]
169    pub fn path(&self) -> &str {
170        self.origin.path().as_str()
171    }
172
173    /// The query part of this route URI, if there is one.
174    ///
175    /// # Example
176    ///
177    /// ```rust
178    /// use rocket::Route;
179    /// use rocket::http::Method;
180    /// # use rocket::route::dummy_handler as handler;
181    ///
182    /// let index = Route::new(Method::Get, "/foo/bar", handler);
183    /// assert!(index.uri.query().is_none());
184    ///
185    /// // Normalization clears the empty '?'.
186    /// let index = Route::new(Method::Get, "/foo/bar?", handler);
187    /// assert!(index.uri.query().is_none());
188    ///
189    /// let index = Route::new(Method::Get, "/foo/bar?a=1", handler);
190    /// assert_eq!(index.uri.query().unwrap(), "a=1");
191    ///
192    /// let index = index.map_base(|base| format!("{}{}", "/boo", base)).unwrap();
193    /// assert_eq!(index.uri.query().unwrap(), "a=1");
194    /// ```
195    #[inline(always)]
196    pub fn query(&self) -> Option<&str> {
197        self.origin.query().map(|q| q.as_str())
198    }
199
200    /// The full URI as an `&str`.
201    ///
202    /// # Example
203    ///
204    /// ```rust
205    /// use rocket::Route;
206    /// use rocket::http::Method;
207    /// # use rocket::route::dummy_handler as handler;
208    ///
209    /// let index = Route::new(Method::Get, "/foo/bar?a=1", handler);
210    /// assert_eq!(index.uri.as_str(), "/foo/bar?a=1");
211    /// let index = index.map_base(|base| format!("{}{}", "/boo", base)).unwrap();
212    /// assert_eq!(index.uri.as_str(), "/boo/foo/bar?a=1");
213    /// ```
214    #[inline(always)]
215    pub fn as_str(&self) -> &str {
216        &self.source
217    }
218
219    /// Get the default rank of a route with this URI.
220    ///
221    /// The route's default rank is determined based on the presence or absence
222    /// of static and dynamic paths and queries. See the documentation for
223    /// [`Route::new`][`crate::Route::new`] for a table summarizing the exact default ranks.
224    ///
225    /// | path    | query   | rank |
226    /// |---------|---------|------|
227    /// | static  | static  | -12  |
228    /// | static  | partial | -11  |
229    /// | static  | wild    | -10  |
230    /// | static  | none    | -9   |
231    /// | partial | static  | -8   |
232    /// | partial | partial | -7   |
233    /// | partial | wild    | -6   |
234    /// | partial | none    | -5   |
235    /// | wild    | static  | -4   |
236    /// | wild    | partial | -3   |
237    /// | wild    | wild    | -2   |
238    /// | wild    | none    | -1   |
239    pub(crate) fn default_rank(&self) -> isize {
240        let raw_path_weight = self.metadata.path_color as u8;
241        let raw_query_weight = self.metadata.query_color.map_or(0, |c| c as u8);
242        let raw_weight = (raw_path_weight << 2) | raw_query_weight;
243
244        // We subtract `3` because `raw_path` is never `0`: 0b0100 = 4 - 3 = 1.
245        -((raw_weight as isize) - 3)
246    }
247}
248
249impl Metadata {
250    fn from(base: &Origin<'_>, origin: &Origin<'_>) -> Self {
251        let base_segs = base.path().raw_segments()
252            .map(Segment::from)
253            .collect::<Vec<_>>();
254
255        let path_segs = origin.path().raw_segments()
256            .map(Segment::from)
257            .collect::<Vec<_>>();
258
259        let query_segs = origin.query()
260            .map(|q| q.raw_segments().map(Segment::from).collect::<Vec<_>>())
261            .unwrap_or_default();
262
263        let static_query_fields = query_segs.iter().filter(|s| !s.dynamic)
264            .map(|s| ValueField::parse(&s.value))
265            .map(|f| (f.name.source().to_string(), f.value.to_string()))
266            .collect();
267
268        let static_path = path_segs.iter().all(|s| !s.dynamic);
269        let wild_path = !path_segs.is_empty() && path_segs.iter().all(|s| s.dynamic);
270        let path_color = match (static_path, wild_path) {
271            (true, _) => Color::Static,
272            (_, true) => Color::Wild,
273            (_, _) => Color::Partial
274        };
275
276        let query_color = (!query_segs.is_empty()).then(|| {
277            let static_query = query_segs.iter().all(|s| !s.dynamic);
278            let wild_query = query_segs.iter().all(|s| s.dynamic);
279            match (static_query, wild_query) {
280                (true, _) => Color::Static,
281                (_, true) => Color::Wild,
282                (_, _) => Color::Partial
283            }
284        });
285
286        let trailing_path = path_segs.last().map_or(false, |p| p.trailing);
287
288        Metadata {
289            base_segs, path_segs, static_query_fields, path_color, query_color,
290            trailing_path,
291        }
292    }
293}
294
295impl<'a> std::ops::Deref for RouteUri<'a> {
296    type Target = Origin<'a>;
297
298    fn deref(&self) -> &Self::Target {
299        &self.origin
300    }
301}
302
303impl fmt::Display for RouteUri<'_> {
304    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305        self.origin.fmt(f)
306    }
307}
308
309impl fmt::Debug for RouteUri<'_> {
310    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
311        f.debug_struct("RouteUri")
312            .field("base", &self.base)
313            .field("unmounted_origin", &self.unmounted_origin)
314            .field("origin", &self.origin)
315            .field("metadata", &self.metadata)
316            .finish()
317    }
318}
319
320impl<'a, 'b> PartialEq<Origin<'b>> for RouteUri<'a> {
321    fn eq(&self, other: &Origin<'b>) -> bool { &self.origin == other }
322}
323
324impl PartialEq<str> for RouteUri<'_> {
325    fn eq(&self, other: &str) -> bool { self.as_str() == other }
326}
327
328impl PartialEq<&str> for RouteUri<'_> {
329    fn eq(&self, other: &&str) -> bool { self.as_str() == *other }
330}