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}