mirror of https://github.com/rwf2/Rocket.git
Introduce more flexible mounting.
Prior to this commit, a route with a URI of `/` could not be mounted in such a way that the resulting effective URI contained a trailing slash. This commit changes the semantics of mounting so that mounting such a route to a mount point with a trailing slash yields an effective URI with a trailing slash. When mounted to points without a trailing slash, the effective URI does not have a trailing slash. This commit also introduces the `Route::rebase()` and `Catcher::rebase()` methods for easier rebasing of existing routes and catchers. Finally, this commit improves logging such that mount points of `/` are underlined in the logs. Tests and docs were added and modified as necessary. Resolves #2533.
This commit is contained in:
parent
dbc43c41a3
commit
56cf905c6e
|
@ -318,7 +318,7 @@ impl Parse for InternalUriParams {
|
|||
let fn_args = fn_args.into_iter().collect();
|
||||
|
||||
input.parse::<Token![,]>()?;
|
||||
let uri_params = input.parse::<RoutedUri>()?;
|
||||
let uri_mac = input.parse::<RoutedUri>()?;
|
||||
|
||||
let span = route_uri_str.subspan(1..route_uri.path().len() + 1);
|
||||
let path_params = Parameter::parse_many::<fmt::Path>(route_uri.path().as_str(), span)
|
||||
|
@ -334,13 +334,7 @@ impl Parse for InternalUriParams {
|
|||
.collect::<Vec<_>>()
|
||||
}).unwrap_or_default();
|
||||
|
||||
Ok(InternalUriParams {
|
||||
route_uri,
|
||||
path_params,
|
||||
query_params,
|
||||
fn_args,
|
||||
uri_mac: uri_params
|
||||
})
|
||||
Ok(InternalUriParams { route_uri, path_params, query_params, fn_args, uri_mac })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -189,47 +189,58 @@ fn check_route_prefix_suffix() {
|
|||
uri!("/") => "/",
|
||||
uri!("/", index) => "/",
|
||||
uri!("/hi", index) => "/hi",
|
||||
uri!("/foo", index) => "/foo",
|
||||
uri!("/hi/", index) => "/hi/",
|
||||
uri!("/foo/", index) => "/foo/",
|
||||
uri!("/", simple3(10)) => "/?id=10",
|
||||
uri!("/hi", simple3(11)) => "/hi?id=11",
|
||||
uri!("/hi/", simple3(11)) => "/hi/?id=11",
|
||||
uri!("/mount", simple(100)) => "/mount/100",
|
||||
uri!("/mount", simple(id = 23)) => "/mount/23",
|
||||
uri!("/mount/", simple(100)) => "/mount/100",
|
||||
uri!("/mount/", simple(id = 23)) => "/mount/23",
|
||||
uri!("/another", simple(100)) => "/another/100",
|
||||
uri!("/another", simple(id = 23)) => "/another/23",
|
||||
uri!("/foo") => "/foo",
|
||||
uri!("/foo/") => "/foo/",
|
||||
uri!("/foo///") => "/foo/",
|
||||
uri!("/foo/bar/") => "/foo/bar/",
|
||||
uri!("/foo/", index) => "/foo/",
|
||||
uri!("/foo", index) => "/foo",
|
||||
}
|
||||
|
||||
assert_uri_eq! {
|
||||
uri!("http://rocket.rs", index) => "http://rocket.rs",
|
||||
uri!("http://rocket.rs/", index) => "http://rocket.rs/",
|
||||
uri!("http://rocket.rs///", index) => "http://rocket.rs/",
|
||||
uri!("http://rocket.rs/foo", index) => "http://rocket.rs/foo",
|
||||
uri!("http://rocket.rs/foo/", index) => "http://rocket.rs/foo/",
|
||||
uri!("http://", index) => "http://",
|
||||
uri!("ftp:", index) => "ftp:/",
|
||||
uri!("http:///", index) => "http:///",
|
||||
uri!("http:////", index) => "http:///",
|
||||
uri!("ftp:/", index) => "ftp:/",
|
||||
}
|
||||
|
||||
assert_uri_eq! {
|
||||
uri!("http://rocket.rs", index, "?foo") => "http://rocket.rs?foo",
|
||||
uri!("http://rocket.rs", index, "?") => "http://rocket.rs?",
|
||||
uri!("http://rocket.rs", index, "#") => "http://rocket.rs#",
|
||||
uri!("http://rocket.rs", index, "#bar") => "http://rocket.rs#bar",
|
||||
uri!("http://rocket.rs", index, "?bar#baz") => "http://rocket.rs?bar#baz",
|
||||
uri!("http://rocket.rs/", index, "?foo") => "http://rocket.rs/?foo",
|
||||
uri!("http://rocket.rs/", index, "?") => "http://rocket.rs/?",
|
||||
uri!("http://rocket.rs/", index, "#") => "http://rocket.rs/#",
|
||||
uri!("http://rocket.rs", index, "#bar") => "http://rocket.rs#bar",
|
||||
uri!("http://rocket.rs/", index, "#bar") => "http://rocket.rs/#bar",
|
||||
uri!("http://rocket.rs", index, "?bar#baz") => "http://rocket.rs?bar#baz",
|
||||
uri!("http://rocket.rs/", index, "?bar#baz") => "http://rocket.rs/?bar#baz",
|
||||
uri!("http://", index, "?foo") => "http://?foo",
|
||||
uri!("http://rocket.rs", simple3(id = 100), "?foo") => "http://rocket.rs?id=100",
|
||||
uri!("http://rocket.rs", simple3(id = 100), "?foo#bar") => "http://rocket.rs?id=100#bar",
|
||||
uri!("http://rocket.rs/", simple3(id = 100), "?foo") => "http://rocket.rs/?id=100",
|
||||
uri!("http://rocket.rs/", simple3(id = 100), "?foo#bar") => "http://rocket.rs/?id=100#bar",
|
||||
uri!(_, simple3(id = 100), "?foo#bar") => "/?id=100#bar",
|
||||
}
|
||||
|
||||
let dyn_origin = uri!("/a/b/c");
|
||||
let dyn_origin2 = uri!("/a/b/c?foo-bar");
|
||||
let dyn_origin_slash = uri!("/a/b/c/");
|
||||
assert_uri_eq! {
|
||||
uri!(dyn_origin.clone(), index) => "/a/b/c",
|
||||
uri!(dyn_origin2.clone(), index) => "/a/b/c",
|
||||
|
@ -241,12 +252,25 @@ fn check_route_prefix_suffix() {
|
|||
uri!(dyn_origin2.clone(), simple2(100, "hey")) => "/a/b/c/100/hey",
|
||||
uri!(dyn_origin.clone(), simple2(id = 23, name = "hey")) => "/a/b/c/23/hey",
|
||||
uri!(dyn_origin2.clone(), simple2(id = 23, name = "hey")) => "/a/b/c/23/hey",
|
||||
|
||||
uri!(dyn_origin_slash.clone(), index) => "/a/b/c/",
|
||||
uri!(dyn_origin_slash.clone(), simple3(10)) => "/a/b/c/?id=10",
|
||||
uri!(dyn_origin_slash.clone(), simple(100)) => "/a/b/c/100",
|
||||
}
|
||||
|
||||
let dyn_absolute = uri!("http://rocket.rs");
|
||||
let dyn_absolute_slash = uri!("http://rocket.rs/");
|
||||
assert_uri_eq! {
|
||||
uri!(dyn_absolute.clone(), index) => "http://rocket.rs",
|
||||
uri!(dyn_absolute.clone(), simple(100)) => "http://rocket.rs/100",
|
||||
uri!(dyn_absolute.clone(), simple3(123)) => "http://rocket.rs?id=123",
|
||||
uri!(dyn_absolute_slash.clone(), index) => "http://rocket.rs/",
|
||||
uri!(dyn_absolute_slash.clone(), simple(100)) => "http://rocket.rs/100",
|
||||
uri!(dyn_absolute_slash.clone(), simple3(123)) => "http://rocket.rs/?id=123",
|
||||
uri!(uri!("http://rocket.rs/a/b"), index) => "http://rocket.rs/a/b",
|
||||
uri!("http://rocket.rs/a/b") => "http://rocket.rs/a/b",
|
||||
uri!(uri!("http://rocket.rs/a/b"), index) => "http://rocket.rs/a/b",
|
||||
uri!("http://rocket.rs/a/b") => "http://rocket.rs/a/b",
|
||||
}
|
||||
|
||||
let dyn_abs = uri!("http://rocket.rs?foo");
|
||||
|
@ -258,12 +282,23 @@ fn check_route_prefix_suffix() {
|
|||
uri!(_, simple3(id = 123), dyn_abs) => "/?id=123",
|
||||
}
|
||||
|
||||
let dyn_abs = uri!("http://rocket.rs/?foo");
|
||||
assert_uri_eq! {
|
||||
uri!(_, index, dyn_abs.clone()) => "/?foo",
|
||||
uri!("http://rocket.rs", index, dyn_abs.clone()) => "http://rocket.rs?foo",
|
||||
uri!("http://rocket.rs/", index, dyn_abs.clone()) => "http://rocket.rs/?foo",
|
||||
uri!("http://", index, dyn_abs.clone()) => "http://?foo",
|
||||
uri!("http:///", index, dyn_abs.clone()) => "http:///?foo",
|
||||
uri!(_, simple3(id = 123), dyn_abs) => "/?id=123",
|
||||
}
|
||||
|
||||
let dyn_ref = uri!("?foo#bar");
|
||||
assert_uri_eq! {
|
||||
uri!(_, index, dyn_ref.clone()) => "/?foo#bar",
|
||||
uri!("http://rocket.rs", index, dyn_ref.clone()) => "http://rocket.rs?foo#bar",
|
||||
uri!("http://rocket.rs/", index, dyn_ref.clone()) => "http://rocket.rs/?foo#bar",
|
||||
uri!("http://", index, dyn_ref.clone()) => "http://?foo#bar",
|
||||
uri!("http:///", index, dyn_ref.clone()) => "http:///?foo#bar",
|
||||
uri!(_, simple3(id = 123), dyn_ref) => "/?id=123#bar",
|
||||
}
|
||||
}
|
||||
|
@ -619,3 +654,22 @@ fn test_json() {
|
|||
uri!(bar(&mut Json(inner))) => "/?json=%7B%22foo%22:%7B%22foo%22:%22hi%22%7D%7D",
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_route_uri_normalization_with_prefix() {
|
||||
#[get("/world")] fn world() {}
|
||||
|
||||
assert_uri_eq! {
|
||||
uri!("/", index()) => "/",
|
||||
uri!("/foo", index()) => "/foo",
|
||||
uri!("/bar/", index()) => "/bar/",
|
||||
uri!("/foo/bar", index()) => "/foo/bar",
|
||||
uri!("/foo/bar/", index()) => "/foo/bar/",
|
||||
|
||||
uri!("/", world()) => "/world",
|
||||
uri!("/foo", world()) => "/foo/world",
|
||||
uri!("/bar/", world()) => "/bar/world",
|
||||
uri!("/foo/bar", world()) => "/foo/bar/world",
|
||||
uri!("/foo/bar/", world()) => "/foo/bar/world",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -437,15 +437,23 @@ impl<'a> ValidRoutePrefix for Origin<'a> {
|
|||
let mut prefix = self.into_normalized();
|
||||
prefix.clear_query();
|
||||
|
||||
if prefix.path() == "/" {
|
||||
// Avoid a double `//` to start.
|
||||
if prefix.path() == "/" {
|
||||
return Origin::new(path, query);
|
||||
} else if path == "/" {
|
||||
// Appending path to `/` is a no-op, but append any query.
|
||||
}
|
||||
|
||||
// Avoid allocating if the `path` would result in just the prefix.
|
||||
if path == "/" {
|
||||
prefix.set_query(query);
|
||||
return prefix;
|
||||
}
|
||||
|
||||
// Avoid a `//` resulting from joining.
|
||||
if prefix.has_trailing_slash() && path.starts_with('/') {
|
||||
return Origin::new(format!("{}{}", prefix.path(), &path[1..]), query);
|
||||
}
|
||||
|
||||
// Join normally.
|
||||
Origin::new(format!("{}{}", prefix.path(), path), query)
|
||||
}
|
||||
}
|
||||
|
@ -458,13 +466,12 @@ impl<'a> ValidRoutePrefix for Absolute<'a> {
|
|||
let mut prefix = self.into_normalized();
|
||||
prefix.clear_query();
|
||||
|
||||
if prefix.authority().is_some() {
|
||||
// The prefix is normalized. Appending a `/` is a no-op.
|
||||
// Distinguish for routes `/` with bases of `/foo/` and `/foo`. The
|
||||
// latter base, without a trailing slash, should combine as `/foo`.
|
||||
if path == "/" {
|
||||
prefix.set_query(query);
|
||||
return prefix;
|
||||
}
|
||||
}
|
||||
|
||||
// In these cases, appending `path` would be a no-op or worse.
|
||||
if prefix.path().is_empty() || prefix.path() == "/" {
|
||||
|
@ -473,11 +480,7 @@ impl<'a> ValidRoutePrefix for Absolute<'a> {
|
|||
return prefix;
|
||||
}
|
||||
|
||||
if path == "/" {
|
||||
prefix.set_query(query);
|
||||
return prefix;
|
||||
}
|
||||
|
||||
// Create the combined URI.
|
||||
prefix.set_path(format!("{}{}", prefix.path(), path));
|
||||
prefix.set_query(query);
|
||||
prefix
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::fmt;
|
|||
use std::io::Cursor;
|
||||
|
||||
use crate::http::uri::Path;
|
||||
use crate::http::ext::IntoOwned;
|
||||
use crate::response::Response;
|
||||
use crate::request::Request;
|
||||
use crate::http::{Status, ContentType, uri};
|
||||
|
@ -207,9 +208,58 @@ impl Catcher {
|
|||
self.base.path()
|
||||
}
|
||||
|
||||
/// Prefix `base` to the current `base` in `self.`
|
||||
///
|
||||
/// If the the current base is `/`, then the base is replaced by `base`.
|
||||
/// Otherwise, `base` is prefixed to the existing `base`.
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::request::Request;
|
||||
/// use rocket::catcher::{Catcher, BoxFuture};
|
||||
/// use rocket::response::Responder;
|
||||
/// use rocket::http::Status;
|
||||
/// # use rocket::uri;
|
||||
///
|
||||
/// fn handle_404<'r>(status: Status, req: &'r Request<'_>) -> BoxFuture<'r> {
|
||||
/// let res = (status, format!("404: {}", req.uri()));
|
||||
/// Box::pin(async move { res.respond_to(req) })
|
||||
/// }
|
||||
///
|
||||
/// let catcher = Catcher::new(404, handle_404);
|
||||
/// assert_eq!(catcher.base(), "/");
|
||||
///
|
||||
/// // Since the base is `/`, rebasing replaces the base.
|
||||
/// let rebased = catcher.rebase(uri!("/boo"));
|
||||
/// assert_eq!(rebased.base(), "/boo");
|
||||
///
|
||||
/// // Now every rebase prefixes.
|
||||
/// let rebased = rebased.rebase(uri!("/base"));
|
||||
/// assert_eq!(rebased.base(), "/base/boo");
|
||||
///
|
||||
/// // Note that trailing slashes have no effect and are thus removed:
|
||||
/// let catcher = Catcher::new(404, handle_404);
|
||||
/// let rebased = catcher.rebase(uri!("/boo/"));
|
||||
/// assert_eq!(rebased.base(), "/boo");
|
||||
/// ```
|
||||
pub fn rebase(mut self, mut base: uri::Origin<'_>) -> Self {
|
||||
self.base = if self.base.path() == "/" {
|
||||
base.clear_query();
|
||||
base.into_normalized_nontrailing().into_owned()
|
||||
} else {
|
||||
uri::Origin::parse_owned(format!("{}{}", base.path(), self.base))
|
||||
.expect("catcher rebase: {new}{old} is valid origin URI")
|
||||
.into_normalized_nontrailing()
|
||||
};
|
||||
|
||||
self.rank = -1 * (self.base().segments().filter(|s| !s.is_empty()).count() as isize);
|
||||
self
|
||||
}
|
||||
|
||||
/// Maps the `base` of this catcher using `mapper`, returning a new
|
||||
/// `Catcher` with the returned base.
|
||||
///
|
||||
/// **Note:** Prefer to use [`Catcher::rebase()`] whenever possible!
|
||||
///
|
||||
/// `mapper` is called with the current base. The returned `String` is used
|
||||
/// as the new base if it is a valid URI. If the returned base URI contains
|
||||
/// a query, it is ignored. Returns an error if the base produced by
|
||||
|
@ -240,10 +290,7 @@ impl Catcher {
|
|||
/// let catcher = catcher.map_base(|base| format!("/foo ? {}", base));
|
||||
/// assert!(catcher.is_err());
|
||||
/// ```
|
||||
pub fn map_base<'a, F>(
|
||||
mut self,
|
||||
mapper: F
|
||||
) -> std::result::Result<Self, uri::Error<'static>>
|
||||
pub fn map_base<'a, F>(mut self, mapper: F) -> Result<Self, uri::Error<'static>>
|
||||
where F: FnOnce(uri::Origin<'a>) -> String
|
||||
{
|
||||
let new_base = uri::Origin::parse_owned(mapper(self.base))?;
|
||||
|
|
|
@ -12,7 +12,7 @@ use crate::trip_wire::TripWire;
|
|||
use crate::fairing::{Fairing, Fairings};
|
||||
use crate::phase::{Phase, Build, Building, Ignite, Igniting, Orbit, Orbiting};
|
||||
use crate::phase::{Stateful, StateRef, State};
|
||||
use crate::http::uri::{self, Origin};
|
||||
use crate::http::uri::Origin;
|
||||
use crate::http::ext::IntoOwned;
|
||||
use crate::error::{Error, ErrorKind};
|
||||
use crate::log::PaintExt;
|
||||
|
@ -246,7 +246,7 @@ impl Rocket<Build> {
|
|||
fn load<'a, B, T, F, M>(mut self, kind: &str, base: B, items: Vec<T>, m: M, f: F) -> Self
|
||||
where B: TryInto<Origin<'a>> + Clone + fmt::Display,
|
||||
B::Error: fmt::Display,
|
||||
M: Fn(&Origin<'a>, T) -> Result<T, uri::Error<'static>>,
|
||||
M: Fn(&Origin<'a>, T) -> T,
|
||||
F: Fn(&mut Self, T),
|
||||
T: Clone + fmt::Display,
|
||||
{
|
||||
|
@ -266,42 +266,65 @@ impl Rocket<Build> {
|
|||
}
|
||||
|
||||
for unmounted_item in items {
|
||||
let item = match m(&base, unmounted_item.clone()) {
|
||||
Ok(item) => item,
|
||||
Err(e) => {
|
||||
error!("malformed URI in {} {}", kind, unmounted_item);
|
||||
error_!("{}", e);
|
||||
info_!("{} {}", Paint::white("in"), std::panic::Location::caller());
|
||||
panic!("aborting due to invalid {} URI", kind);
|
||||
}
|
||||
};
|
||||
|
||||
f(&mut self, item)
|
||||
f(&mut self, m(&base, unmounted_item.clone()))
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Mounts all of the routes in the supplied vector at the given `base`
|
||||
/// path. Mounting a route with path `path` at path `base` makes the route
|
||||
/// available at `base/path`.
|
||||
/// Mounts all of the `routes` at the given `base` mount point.
|
||||
///
|
||||
/// A route _mounted_ at `base` has an effective URI of `base/route`, where
|
||||
/// `route` is the route URI. In other words, `base` is added as a prefix to
|
||||
/// the route's URI. The URI resulting from joining the `base` URI and the
|
||||
/// route URI is called the route's _effective URI_, as this is the URI used
|
||||
/// for request matching during routing.
|
||||
///
|
||||
/// A `base` URI is not allowed to have a query part. If a `base` _does_
|
||||
/// have a query part, it is ignored when producing the effective URI.
|
||||
///
|
||||
/// A `base` may have an optional trailing slash. A route with a URI path of
|
||||
/// `/` (and any optional query) mounted at a `base` has an effective URI
|
||||
/// equal to the `base` (plus any optional query). That is, if the base has
|
||||
/// a trailing slash, the effective URI path has a trailing slash, and
|
||||
/// otherwise it does not. Routes with URI paths other than `/` are not
|
||||
/// effected by trailing slashes in their corresponding mount point.
|
||||
///
|
||||
/// As concrete examples, consider the following table:
|
||||
///
|
||||
/// | mount point | route URI | effective URI |
|
||||
/// |-------------|-----------|---------------|
|
||||
/// | `/` | `/foo` | `/foo` |
|
||||
/// | `/` | `/foo/` | `/foo/` |
|
||||
/// | `/foo` | `/` | `/foo` |
|
||||
/// | `/foo` | `/?bar` | `/foo?bar` |
|
||||
/// | `/foo` | `/bar` | `/foo/bar` |
|
||||
/// | `/foo` | `/bar/` | `/foo/bar/` |
|
||||
/// | `/foo/` | `/` | `/foo/` |
|
||||
/// | `/foo/` | `/bar` | `/foo/bar` |
|
||||
/// | `/foo/` | `/?bar` | `/foo/?bar` |
|
||||
/// | `/foo/bar` | `/` | `/foo/bar` |
|
||||
/// | `/foo/bar/` | `/` | `/foo/bar/` |
|
||||
/// | `/foo/?bar` | `/` | `/foo/` |
|
||||
/// | `/foo/?bar` | `/baz` | `/foo/baz` |
|
||||
/// | `/foo/?bar` | `/baz/` | `/foo/baz/` |
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if either:
|
||||
/// * the `base` mount point is not a valid static path: a valid origin
|
||||
/// URI without dynamic parameters.
|
||||
///
|
||||
/// * any route's URI is not a valid origin URI.
|
||||
/// * the `base` mount point is not a valid origin URI without dynamic
|
||||
/// parameters
|
||||
///
|
||||
/// **Note:** _This kind of panic is guaranteed not to occur if the routes
|
||||
/// were generated using Rocket's code generation._
|
||||
/// * any route URI is not a valid origin URI. (**Note:** _This kind of
|
||||
/// panic is guaranteed not to occur if the routes were generated using
|
||||
/// Rocket's code generation._)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Use the `routes!` macro to mount routes created using the code
|
||||
/// generation facilities. Requests to the `/hello/world` URI will be
|
||||
/// dispatched to the `hi` route.
|
||||
/// generation facilities. Requests to both `/world` and `/hello/world` URI
|
||||
/// will be dispatched to the `hi` route.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
|
@ -313,7 +336,9 @@ impl Rocket<Build> {
|
|||
///
|
||||
/// #[launch]
|
||||
/// fn rocket() -> _ {
|
||||
/// rocket::build().mount("/hello", routes![hi])
|
||||
/// rocket::build()
|
||||
/// .mount("/", routes![hi])
|
||||
/// .mount("/hello", routes![hi])
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
|
@ -344,7 +369,7 @@ impl Rocket<Build> {
|
|||
R: Into<Vec<Route>>
|
||||
{
|
||||
self.load("route", base, routes.into(),
|
||||
|base, route| route.map_base(|old| format!("{}{}", base, old)),
|
||||
|base, route| route.rebase(base.clone()),
|
||||
|r, route| r.0.routes.push(route))
|
||||
}
|
||||
|
||||
|
@ -383,7 +408,7 @@ impl Rocket<Build> {
|
|||
C: Into<Vec<Catcher>>
|
||||
{
|
||||
self.load("catcher", base, catchers.into(),
|
||||
|base, catcher| catcher.map_base(|old| format!("{}{}", base, old)),
|
||||
|base, catcher| catcher.rebase(base.clone()),
|
||||
|r, catcher| r.0.catchers.push(catcher))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use std::fmt;
|
||||
use std::convert::From;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use yansi::Paint;
|
||||
|
@ -257,9 +256,48 @@ impl Route {
|
|||
}
|
||||
}
|
||||
|
||||
/// Prefix `base` to any existing mount point base in `self`.
|
||||
///
|
||||
/// If the the current mount point base is `/`, then the base is replaced by
|
||||
/// `base`. Otherwise, `base` is prefixed to the existing `base`.
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::Route;
|
||||
/// use rocket::http::Method;
|
||||
/// # use rocket::route::dummy_handler as handler;
|
||||
/// # use rocket::uri;
|
||||
///
|
||||
/// // The default base is `/`.
|
||||
/// let index = Route::new(Method::Get, "/foo/bar", handler);
|
||||
///
|
||||
/// // Since the base is `/`, rebasing replaces the base.
|
||||
/// let rebased = index.rebase(uri!("/boo"));
|
||||
/// assert_eq!(rebased.uri.base(), "/boo");
|
||||
///
|
||||
/// // Now every rebase prefixes.
|
||||
/// let rebased = rebased.rebase(uri!("/base"));
|
||||
/// assert_eq!(rebased.uri.base(), "/base/boo");
|
||||
///
|
||||
/// // Note that trailing slashes are preserved:
|
||||
/// let index = Route::new(Method::Get, "/foo", handler);
|
||||
/// let rebased = index.rebase(uri!("/boo/"));
|
||||
/// assert_eq!(rebased.uri.base(), "/boo/");
|
||||
/// ```
|
||||
pub fn rebase(mut self, base: uri::Origin<'_>) -> Self {
|
||||
let new_base = match self.uri.base().as_str() {
|
||||
"/" => base.path().to_string(),
|
||||
_ => format!("{}{}", base.path(), self.uri.base()),
|
||||
};
|
||||
|
||||
self.uri = RouteUri::new(&new_base, &self.uri.unmounted_origin.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Maps the `base` of this route using `mapper`, returning a new `Route`
|
||||
/// with the returned base.
|
||||
///
|
||||
/// **Note:** Prefer to use [`Route::rebase()`] whenever possible!
|
||||
///
|
||||
/// `mapper` is called with the current base. The returned `String` is used
|
||||
/// as the new base if it is a valid URI. If the returned base URI contains
|
||||
/// a query, it is ignored. Returns an error if the base produced by
|
||||
|
@ -269,18 +307,28 @@ impl Route {
|
|||
///
|
||||
/// ```rust
|
||||
/// use rocket::Route;
|
||||
/// use rocket::http::{Method, uri::Origin};
|
||||
/// use rocket::http::Method;
|
||||
/// # use rocket::route::dummy_handler as handler;
|
||||
/// # use rocket::uri;
|
||||
///
|
||||
/// let index = Route::new(Method::Get, "/foo/bar", handler);
|
||||
/// assert_eq!(index.uri.base(), "/");
|
||||
/// assert_eq!(index.uri.unmounted().path(), "/foo/bar");
|
||||
/// assert_eq!(index.uri.path(), "/foo/bar");
|
||||
///
|
||||
/// let index = index.map_base(|base| format!("{}{}", "/boo", base)).unwrap();
|
||||
/// assert_eq!(index.uri.base(), "/boo");
|
||||
/// assert_eq!(index.uri.unmounted().path(), "/foo/bar");
|
||||
/// assert_eq!(index.uri.path(), "/boo/foo/bar");
|
||||
/// # let old_index = index;
|
||||
/// # let index = old_index.clone();
|
||||
/// let mapped = index.map_base(|base| format!("{}{}", "/boo", base)).unwrap();
|
||||
/// assert_eq!(mapped.uri.base(), "/boo/");
|
||||
/// assert_eq!(mapped.uri.unmounted().path(), "/foo/bar");
|
||||
/// assert_eq!(mapped.uri.path(), "/boo/foo/bar");
|
||||
///
|
||||
/// // Note that this produces different `base` results than `rebase`!
|
||||
/// # let index = old_index.clone();
|
||||
/// let rebased = index.rebase(uri!("/boo"));
|
||||
/// assert_eq!(rebased.uri.base(), "/boo");
|
||||
/// assert_eq!(rebased.uri.unmounted().path(), "/foo/bar");
|
||||
/// assert_eq!(rebased.uri.path(), "/boo/foo/bar");
|
||||
/// ```
|
||||
pub fn map_base<'a, F>(mut self, mapper: F) -> Result<Self, uri::Error<'static>>
|
||||
where F: FnOnce(uri::Origin<'a>) -> String
|
||||
|
@ -298,11 +346,7 @@ impl fmt::Display for Route {
|
|||
}
|
||||
|
||||
write!(f, "{} ", Paint::green(&self.method))?;
|
||||
if self.uri.base() != "/" {
|
||||
write!(f, "{}", Paint::blue(self.uri.base()).underline())?;
|
||||
}
|
||||
|
||||
write!(f, "{}", Paint::blue(&self.uri.unmounted()))?;
|
||||
self.uri.color_fmt(f)?;
|
||||
|
||||
if self.rank > 1 {
|
||||
write!(f, " [{}]", Paint::default(&self.rank).bold())?;
|
||||
|
|
|
@ -98,7 +98,7 @@ impl<'a> RouteUri<'a> {
|
|||
/// Panics if `base` or `uri` cannot be parsed as `Origin`s.
|
||||
#[track_caller]
|
||||
pub(crate) fn new(base: &str, uri: &str) -> RouteUri<'static> {
|
||||
Self::try_new(base, uri).expect("Expected valid URIs")
|
||||
Self::try_new(base, uri).expect("expected valid route URIs")
|
||||
}
|
||||
|
||||
/// Creates a new `RouteUri` from a `base` mount point and a route `uri`.
|
||||
|
@ -110,7 +110,7 @@ impl<'a> RouteUri<'a> {
|
|||
pub fn try_new(base: &str, uri: &str) -> Result<RouteUri<'static>> {
|
||||
let mut base = Origin::parse(base)
|
||||
.map_err(|e| e.into_owned())?
|
||||
.into_normalized_nontrailing()
|
||||
.into_normalized()
|
||||
.into_owned();
|
||||
|
||||
base.clear_query();
|
||||
|
@ -120,16 +120,17 @@ impl<'a> RouteUri<'a> {
|
|||
.into_normalized()
|
||||
.into_owned();
|
||||
|
||||
let compiled_uri = match base.path().as_str() {
|
||||
"/" => origin.to_string(),
|
||||
base => match (origin.path().as_str(), origin.query()) {
|
||||
("/", None) => base.to_string(),
|
||||
("/", Some(q)) => format!("{}?{}", base, q),
|
||||
_ => format!("{}{}", base, origin),
|
||||
// Distinguish for routes `/` with bases of `/foo/` and `/foo`. The
|
||||
// latter base, without a trailing slash, should combine as `/foo`.
|
||||
let route_uri = match origin.path().as_str() {
|
||||
"/" if !base.has_trailing_slash() => match origin.query() {
|
||||
Some(query) => format!("{}?{}", base, query),
|
||||
None => base.to_string(),
|
||||
}
|
||||
_ => format!("{}{}", base, origin),
|
||||
};
|
||||
|
||||
let uri = Origin::parse_route(&compiled_uri)
|
||||
let uri = Origin::parse_route(&route_uri)
|
||||
.map_err(|e| e.into_owned())?
|
||||
.into_normalized()
|
||||
.into_owned();
|
||||
|
@ -171,12 +172,16 @@ impl<'a> RouteUri<'a> {
|
|||
/// use rocket::Route;
|
||||
/// use rocket::http::Method;
|
||||
/// # use rocket::route::dummy_handler as handler;
|
||||
/// # use rocket::uri;
|
||||
///
|
||||
/// let route = Route::new(Method::Get, "/foo/bar?a=1", handler);
|
||||
/// assert_eq!(route.uri.base(), "/");
|
||||
///
|
||||
/// let route = route.map_base(|base| format!("{}{}", "/boo", base)).unwrap();
|
||||
/// let route = route.rebase(uri!("/boo"));
|
||||
/// assert_eq!(route.uri.base(), "/boo");
|
||||
///
|
||||
/// let route = route.rebase(uri!("/foo"));
|
||||
/// assert_eq!(route.uri.base(), "/foo/boo");
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn base(&self) -> Path<'_> {
|
||||
|
@ -191,9 +196,10 @@ impl<'a> RouteUri<'a> {
|
|||
/// use rocket::Route;
|
||||
/// use rocket::http::Method;
|
||||
/// # use rocket::route::dummy_handler as handler;
|
||||
/// # use rocket::uri;
|
||||
///
|
||||
/// let route = Route::new(Method::Get, "/foo/bar?a=1", handler);
|
||||
/// let route = route.map_base(|base| format!("{}{}", "/boo", base)).unwrap();
|
||||
/// let route = route.rebase(uri!("/boo"));
|
||||
///
|
||||
/// assert_eq!(route.uri, "/boo/foo/bar?a=1");
|
||||
/// assert_eq!(route.uri.base(), "/boo");
|
||||
|
@ -232,6 +238,23 @@ impl<'a> RouteUri<'a> {
|
|||
// We subtract `3` because `raw_path` is never `0`: 0b0100 = 4 - 3 = 1.
|
||||
-((raw_weight as isize) - 3)
|
||||
}
|
||||
|
||||
pub(crate) fn color_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use yansi::Paint;
|
||||
|
||||
let (path, base, unmounted) = (self.uri.path(), self.base(), self.unmounted().path());
|
||||
let unmounted_part = path.strip_prefix(base.as_str())
|
||||
.map(|raw| raw.as_str())
|
||||
.unwrap_or(unmounted.as_str());
|
||||
|
||||
write!(f, "{}", Paint::blue(self.base()).underline())?;
|
||||
write!(f, "{}", Paint::blue(unmounted_part))?;
|
||||
if let Some(q) = self.unmounted().query() {
|
||||
write!(f, "?{}", Paint::green(q))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
|
@ -289,7 +312,7 @@ impl<'a> std::ops::Deref for RouteUri<'a> {
|
|||
|
||||
impl fmt::Display for RouteUri<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.inner().fmt(f)
|
||||
self.uri.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -304,3 +327,46 @@ impl PartialEq<str> for RouteUri<'_> {
|
|||
impl PartialEq<&str> for RouteUri<'_> {
|
||||
fn eq(&self, other: &&str) -> bool { self.inner() == *other }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
macro_rules! assert_uri_equality {
|
||||
($base:expr, $path:expr => $ebase:expr, $epath:expr, $efull:expr) => {
|
||||
let uri = super::RouteUri::new($base, $path);
|
||||
assert_eq!(uri, $efull, "complete URI mismatch. expected {}, got {}", $efull, uri);
|
||||
assert_eq!(uri.base(), $ebase, "expected base {}, got {}", $ebase, uri.base());
|
||||
assert_eq!(uri.unmounted(), $epath, "expected unmounted {}, got {}", $epath,
|
||||
uri.unmounted());
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_route_uri_composition() {
|
||||
assert_uri_equality!("/", "/" => "/", "/", "/");
|
||||
assert_uri_equality!("/", "/foo" => "/", "/foo", "/foo");
|
||||
assert_uri_equality!("/", "/foo/bar" => "/", "/foo/bar", "/foo/bar");
|
||||
assert_uri_equality!("/", "/foo/" => "/", "/foo/", "/foo/");
|
||||
assert_uri_equality!("/", "/foo/bar/" => "/", "/foo/bar/", "/foo/bar/");
|
||||
|
||||
assert_uri_equality!("/foo", "/" => "/foo", "/", "/foo");
|
||||
assert_uri_equality!("/foo", "/bar" => "/foo", "/bar", "/foo/bar");
|
||||
assert_uri_equality!("/foo", "/bar/" => "/foo", "/bar/", "/foo/bar/");
|
||||
assert_uri_equality!("/foo", "/?baz" => "/foo", "/?baz", "/foo?baz");
|
||||
assert_uri_equality!("/foo", "/bar?baz" => "/foo", "/bar?baz", "/foo/bar?baz");
|
||||
assert_uri_equality!("/foo", "/bar/?baz" => "/foo", "/bar/?baz", "/foo/bar/?baz");
|
||||
|
||||
assert_uri_equality!("/foo/", "/" => "/foo/", "/", "/foo/");
|
||||
assert_uri_equality!("/foo/", "/bar" => "/foo/", "/bar", "/foo/bar");
|
||||
assert_uri_equality!("/foo/", "/bar/" => "/foo/", "/bar/", "/foo/bar/");
|
||||
assert_uri_equality!("/foo/", "/?baz" => "/foo/", "/?baz", "/foo/?baz");
|
||||
assert_uri_equality!("/foo/", "/bar?baz" => "/foo/", "/bar?baz", "/foo/bar?baz");
|
||||
assert_uri_equality!("/foo/", "/bar/?baz" => "/foo/", "/bar/?baz", "/foo/bar/?baz");
|
||||
|
||||
assert_uri_equality!("/foo?baz", "/" => "/foo", "/", "/foo");
|
||||
assert_uri_equality!("/foo?baz", "/bar" => "/foo", "/bar", "/foo/bar");
|
||||
assert_uri_equality!("/foo?baz", "/bar/" => "/foo", "/bar/", "/foo/bar/");
|
||||
assert_uri_equality!("/foo/?baz", "/" => "/foo/", "/", "/foo/");
|
||||
assert_uri_equality!("/foo/?baz", "/bar" => "/foo/", "/bar", "/foo/bar");
|
||||
assert_uri_equality!("/foo/?baz", "/bar/" => "/foo/", "/bar/", "/foo/bar/");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue