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();
|
let fn_args = fn_args.into_iter().collect();
|
||||||
|
|
||||||
input.parse::<Token![,]>()?;
|
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 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)
|
let path_params = Parameter::parse_many::<fmt::Path>(route_uri.path().as_str(), span)
|
||||||
|
@ -334,13 +334,7 @@ impl Parse for InternalUriParams {
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}).unwrap_or_default();
|
}).unwrap_or_default();
|
||||||
|
|
||||||
Ok(InternalUriParams {
|
Ok(InternalUriParams { route_uri, path_params, query_params, fn_args, uri_mac })
|
||||||
route_uri,
|
|
||||||
path_params,
|
|
||||||
query_params,
|
|
||||||
fn_args,
|
|
||||||
uri_mac: uri_params
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -189,47 +189,58 @@ fn check_route_prefix_suffix() {
|
||||||
uri!("/") => "/",
|
uri!("/") => "/",
|
||||||
uri!("/", index) => "/",
|
uri!("/", index) => "/",
|
||||||
uri!("/hi", index) => "/hi",
|
uri!("/hi", index) => "/hi",
|
||||||
|
uri!("/foo", index) => "/foo",
|
||||||
|
uri!("/hi/", index) => "/hi/",
|
||||||
|
uri!("/foo/", index) => "/foo/",
|
||||||
uri!("/", simple3(10)) => "/?id=10",
|
uri!("/", simple3(10)) => "/?id=10",
|
||||||
uri!("/hi", simple3(11)) => "/hi?id=11",
|
uri!("/hi", simple3(11)) => "/hi?id=11",
|
||||||
|
uri!("/hi/", simple3(11)) => "/hi/?id=11",
|
||||||
uri!("/mount", simple(100)) => "/mount/100",
|
uri!("/mount", simple(100)) => "/mount/100",
|
||||||
uri!("/mount", simple(id = 23)) => "/mount/23",
|
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(100)) => "/another/100",
|
||||||
uri!("/another", simple(id = 23)) => "/another/23",
|
uri!("/another", simple(id = 23)) => "/another/23",
|
||||||
uri!("/foo") => "/foo",
|
uri!("/foo") => "/foo",
|
||||||
uri!("/foo/") => "/foo/",
|
uri!("/foo/") => "/foo/",
|
||||||
uri!("/foo///") => "/foo/",
|
uri!("/foo///") => "/foo/",
|
||||||
uri!("/foo/bar/") => "/foo/bar/",
|
uri!("/foo/bar/") => "/foo/bar/",
|
||||||
uri!("/foo/", index) => "/foo/",
|
|
||||||
uri!("/foo", index) => "/foo",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_uri_eq! {
|
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/", 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://rocket.rs/foo/", index) => "http://rocket.rs/foo/",
|
uri!("http://rocket.rs/foo/", index) => "http://rocket.rs/foo/",
|
||||||
uri!("http://", index) => "http://",
|
uri!("http://", index) => "http://",
|
||||||
uri!("ftp:", index) => "ftp:/",
|
uri!("http:///", index) => "http:///",
|
||||||
|
uri!("http:////", index) => "http:///",
|
||||||
|
uri!("ftp:/", index) => "ftp:/",
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!("http://rocket.rs", index, "?foo") => "http://rocket.rs?foo",
|
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, "#") => "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, "#") => "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") => "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://rocket.rs/", index, "?bar#baz") => "http://rocket.rs/?bar#baz",
|
||||||
uri!("http://", index, "?foo") => "http://?foo",
|
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") => "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#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",
|
uri!(_, simple3(id = 100), "?foo#bar") => "/?id=100#bar",
|
||||||
}
|
}
|
||||||
|
|
||||||
let dyn_origin = uri!("/a/b/c");
|
let dyn_origin = uri!("/a/b/c");
|
||||||
let dyn_origin2 = uri!("/a/b/c?foo-bar");
|
let dyn_origin2 = uri!("/a/b/c?foo-bar");
|
||||||
|
let dyn_origin_slash = uri!("/a/b/c/");
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(dyn_origin.clone(), index) => "/a/b/c",
|
uri!(dyn_origin.clone(), index) => "/a/b/c",
|
||||||
uri!(dyn_origin2.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_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_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_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 = uri!("http://rocket.rs");
|
||||||
|
let dyn_absolute_slash = uri!("http://rocket.rs/");
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(dyn_absolute.clone(), index) => "http://rocket.rs",
|
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!(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");
|
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",
|
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");
|
let dyn_ref = uri!("?foo#bar");
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(_, index, dyn_ref.clone()) => "/?foo#bar",
|
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://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!("http:///", index, dyn_ref.clone()) => "http:///?foo#bar",
|
||||||
uri!(_, simple3(id = 123), dyn_ref) => "/?id=123#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",
|
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();
|
let mut prefix = self.into_normalized();
|
||||||
prefix.clear_query();
|
prefix.clear_query();
|
||||||
|
|
||||||
|
// Avoid a double `//` to start.
|
||||||
if prefix.path() == "/" {
|
if prefix.path() == "/" {
|
||||||
// Avoid a double `//` to start.
|
|
||||||
return Origin::new(path, query);
|
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);
|
prefix.set_query(query);
|
||||||
return prefix;
|
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)
|
Origin::new(format!("{}{}", prefix.path(), path), query)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -458,12 +466,11 @@ impl<'a> ValidRoutePrefix for Absolute<'a> {
|
||||||
let mut prefix = self.into_normalized();
|
let mut prefix = self.into_normalized();
|
||||||
prefix.clear_query();
|
prefix.clear_query();
|
||||||
|
|
||||||
if prefix.authority().is_some() {
|
// Distinguish for routes `/` with bases of `/foo/` and `/foo`. The
|
||||||
// The prefix is normalized. Appending a `/` is a no-op.
|
// latter base, without a trailing slash, should combine as `/foo`.
|
||||||
if path == "/" {
|
if path == "/" {
|
||||||
prefix.set_query(query);
|
prefix.set_query(query);
|
||||||
return prefix;
|
return prefix;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// In these cases, appending `path` would be a no-op or worse.
|
// In these cases, appending `path` would be a no-op or worse.
|
||||||
|
@ -473,11 +480,7 @@ impl<'a> ValidRoutePrefix for Absolute<'a> {
|
||||||
return prefix;
|
return prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
if path == "/" {
|
// Create the combined URI.
|
||||||
prefix.set_query(query);
|
|
||||||
return prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix.set_path(format!("{}{}", prefix.path(), path));
|
prefix.set_path(format!("{}{}", prefix.path(), path));
|
||||||
prefix.set_query(query);
|
prefix.set_query(query);
|
||||||
prefix
|
prefix
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::fmt;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
use crate::http::uri::Path;
|
use crate::http::uri::Path;
|
||||||
|
use crate::http::ext::IntoOwned;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::http::{Status, ContentType, uri};
|
use crate::http::{Status, ContentType, uri};
|
||||||
|
@ -207,9 +208,58 @@ impl Catcher {
|
||||||
self.base.path()
|
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
|
/// Maps the `base` of this catcher using `mapper`, returning a new
|
||||||
/// `Catcher` with the returned base.
|
/// `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
|
/// `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
|
/// 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
|
/// 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));
|
/// let catcher = catcher.map_base(|base| format!("/foo ? {}", base));
|
||||||
/// assert!(catcher.is_err());
|
/// assert!(catcher.is_err());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn map_base<'a, F>(
|
pub fn map_base<'a, F>(mut self, mapper: F) -> Result<Self, uri::Error<'static>>
|
||||||
mut self,
|
|
||||||
mapper: F
|
|
||||||
) -> std::result::Result<Self, uri::Error<'static>>
|
|
||||||
where F: FnOnce(uri::Origin<'a>) -> String
|
where F: FnOnce(uri::Origin<'a>) -> String
|
||||||
{
|
{
|
||||||
let new_base = uri::Origin::parse_owned(mapper(self.base))?;
|
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::fairing::{Fairing, Fairings};
|
||||||
use crate::phase::{Phase, Build, Building, Ignite, Igniting, Orbit, Orbiting};
|
use crate::phase::{Phase, Build, Building, Ignite, Igniting, Orbit, Orbiting};
|
||||||
use crate::phase::{Stateful, StateRef, State};
|
use crate::phase::{Stateful, StateRef, State};
|
||||||
use crate::http::uri::{self, Origin};
|
use crate::http::uri::Origin;
|
||||||
use crate::http::ext::IntoOwned;
|
use crate::http::ext::IntoOwned;
|
||||||
use crate::error::{Error, ErrorKind};
|
use crate::error::{Error, ErrorKind};
|
||||||
use crate::log::PaintExt;
|
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
|
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,
|
where B: TryInto<Origin<'a>> + Clone + fmt::Display,
|
||||||
B::Error: 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),
|
F: Fn(&mut Self, T),
|
||||||
T: Clone + fmt::Display,
|
T: Clone + fmt::Display,
|
||||||
{
|
{
|
||||||
|
@ -266,42 +266,65 @@ impl Rocket<Build> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for unmounted_item in items {
|
for unmounted_item in items {
|
||||||
let item = match m(&base, unmounted_item.clone()) {
|
f(&mut self, 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mounts all of the routes in the supplied vector at the given `base`
|
/// Mounts all of the `routes` at the given `base` mount point.
|
||||||
/// path. Mounting a route with path `path` at path `base` makes the route
|
///
|
||||||
/// available at `base/path`.
|
/// 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
|
||||||
///
|
///
|
||||||
/// Panics if either:
|
/// 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
|
/// * any route URI is not a valid origin URI. (**Note:** _This kind of
|
||||||
/// were generated using Rocket's code generation._
|
/// panic is guaranteed not to occur if the routes were generated using
|
||||||
|
/// Rocket's code generation._)
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// Use the `routes!` macro to mount routes created using the code
|
/// Use the `routes!` macro to mount routes created using the code
|
||||||
/// generation facilities. Requests to the `/hello/world` URI will be
|
/// generation facilities. Requests to both `/world` and `/hello/world` URI
|
||||||
/// dispatched to the `hi` route.
|
/// will be dispatched to the `hi` route.
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
@ -313,7 +336,9 @@ impl Rocket<Build> {
|
||||||
///
|
///
|
||||||
/// #[launch]
|
/// #[launch]
|
||||||
/// fn rocket() -> _ {
|
/// 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>>
|
R: Into<Vec<Route>>
|
||||||
{
|
{
|
||||||
self.load("route", base, routes.into(),
|
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))
|
|r, route| r.0.routes.push(route))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,7 +408,7 @@ impl Rocket<Build> {
|
||||||
C: Into<Vec<Catcher>>
|
C: Into<Vec<Catcher>>
|
||||||
{
|
{
|
||||||
self.load("catcher", base, catchers.into(),
|
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))
|
|r, catcher| r.0.catchers.push(catcher))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::convert::From;
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use yansi::Paint;
|
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`
|
/// Maps the `base` of this route using `mapper`, returning a new `Route`
|
||||||
/// with the returned base.
|
/// with the returned base.
|
||||||
///
|
///
|
||||||
|
/// **Note:** Prefer to use [`Route::rebase()`] whenever possible!
|
||||||
|
///
|
||||||
/// `mapper` is called with the current base. The returned `String` is used
|
/// `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
|
/// 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
|
/// a query, it is ignored. Returns an error if the base produced by
|
||||||
|
@ -269,18 +307,28 @@ impl Route {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::Route;
|
/// use rocket::Route;
|
||||||
/// use rocket::http::{Method, uri::Origin};
|
/// use rocket::http::Method;
|
||||||
/// # use rocket::route::dummy_handler as handler;
|
/// # use rocket::route::dummy_handler as handler;
|
||||||
|
/// # use rocket::uri;
|
||||||
///
|
///
|
||||||
/// let index = Route::new(Method::Get, "/foo/bar", handler);
|
/// let index = Route::new(Method::Get, "/foo/bar", handler);
|
||||||
/// assert_eq!(index.uri.base(), "/");
|
/// assert_eq!(index.uri.base(), "/");
|
||||||
/// assert_eq!(index.uri.unmounted().path(), "/foo/bar");
|
/// assert_eq!(index.uri.unmounted().path(), "/foo/bar");
|
||||||
/// assert_eq!(index.uri.path(), "/foo/bar");
|
/// assert_eq!(index.uri.path(), "/foo/bar");
|
||||||
///
|
///
|
||||||
/// let index = index.map_base(|base| format!("{}{}", "/boo", base)).unwrap();
|
/// # let old_index = index;
|
||||||
/// assert_eq!(index.uri.base(), "/boo");
|
/// # let index = old_index.clone();
|
||||||
/// assert_eq!(index.uri.unmounted().path(), "/foo/bar");
|
/// let mapped = index.map_base(|base| format!("{}{}", "/boo", base)).unwrap();
|
||||||
/// assert_eq!(index.uri.path(), "/boo/foo/bar");
|
/// 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>>
|
pub fn map_base<'a, F>(mut self, mapper: F) -> Result<Self, uri::Error<'static>>
|
||||||
where F: FnOnce(uri::Origin<'a>) -> String
|
where F: FnOnce(uri::Origin<'a>) -> String
|
||||||
|
@ -298,11 +346,7 @@ impl fmt::Display for Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
write!(f, "{} ", Paint::green(&self.method))?;
|
write!(f, "{} ", Paint::green(&self.method))?;
|
||||||
if self.uri.base() != "/" {
|
self.uri.color_fmt(f)?;
|
||||||
write!(f, "{}", Paint::blue(self.uri.base()).underline())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, "{}", Paint::blue(&self.uri.unmounted()))?;
|
|
||||||
|
|
||||||
if self.rank > 1 {
|
if self.rank > 1 {
|
||||||
write!(f, " [{}]", Paint::default(&self.rank).bold())?;
|
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.
|
/// Panics if `base` or `uri` cannot be parsed as `Origin`s.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub(crate) fn new(base: &str, uri: &str) -> RouteUri<'static> {
|
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`.
|
/// 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>> {
|
pub fn try_new(base: &str, uri: &str) -> Result<RouteUri<'static>> {
|
||||||
let mut base = Origin::parse(base)
|
let mut base = Origin::parse(base)
|
||||||
.map_err(|e| e.into_owned())?
|
.map_err(|e| e.into_owned())?
|
||||||
.into_normalized_nontrailing()
|
.into_normalized()
|
||||||
.into_owned();
|
.into_owned();
|
||||||
|
|
||||||
base.clear_query();
|
base.clear_query();
|
||||||
|
@ -120,16 +120,17 @@ impl<'a> RouteUri<'a> {
|
||||||
.into_normalized()
|
.into_normalized()
|
||||||
.into_owned();
|
.into_owned();
|
||||||
|
|
||||||
let compiled_uri = match base.path().as_str() {
|
// Distinguish for routes `/` with bases of `/foo/` and `/foo`. The
|
||||||
"/" => origin.to_string(),
|
// latter base, without a trailing slash, should combine as `/foo`.
|
||||||
base => match (origin.path().as_str(), origin.query()) {
|
let route_uri = match origin.path().as_str() {
|
||||||
("/", None) => base.to_string(),
|
"/" if !base.has_trailing_slash() => match origin.query() {
|
||||||
("/", Some(q)) => format!("{}?{}", base, q),
|
Some(query) => format!("{}?{}", base, query),
|
||||||
_ => format!("{}{}", base, origin),
|
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())?
|
.map_err(|e| e.into_owned())?
|
||||||
.into_normalized()
|
.into_normalized()
|
||||||
.into_owned();
|
.into_owned();
|
||||||
|
@ -171,12 +172,16 @@ impl<'a> RouteUri<'a> {
|
||||||
/// use rocket::Route;
|
/// use rocket::Route;
|
||||||
/// use rocket::http::Method;
|
/// use rocket::http::Method;
|
||||||
/// # use rocket::route::dummy_handler as handler;
|
/// # use rocket::route::dummy_handler as handler;
|
||||||
|
/// # use rocket::uri;
|
||||||
///
|
///
|
||||||
/// let route = Route::new(Method::Get, "/foo/bar?a=1", handler);
|
/// let route = Route::new(Method::Get, "/foo/bar?a=1", handler);
|
||||||
/// assert_eq!(route.uri.base(), "/");
|
/// 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");
|
/// assert_eq!(route.uri.base(), "/boo");
|
||||||
|
///
|
||||||
|
/// let route = route.rebase(uri!("/foo"));
|
||||||
|
/// assert_eq!(route.uri.base(), "/foo/boo");
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn base(&self) -> Path<'_> {
|
pub fn base(&self) -> Path<'_> {
|
||||||
|
@ -191,9 +196,10 @@ impl<'a> RouteUri<'a> {
|
||||||
/// use rocket::Route;
|
/// use rocket::Route;
|
||||||
/// use rocket::http::Method;
|
/// use rocket::http::Method;
|
||||||
/// # use rocket::route::dummy_handler as handler;
|
/// # use rocket::route::dummy_handler as handler;
|
||||||
|
/// # use rocket::uri;
|
||||||
///
|
///
|
||||||
/// let route = Route::new(Method::Get, "/foo/bar?a=1", handler);
|
/// 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, "/boo/foo/bar?a=1");
|
||||||
/// assert_eq!(route.uri.base(), "/boo");
|
/// 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.
|
// We subtract `3` because `raw_path` is never `0`: 0b0100 = 4 - 3 = 1.
|
||||||
-((raw_weight as isize) - 3)
|
-((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 {
|
impl Metadata {
|
||||||
|
@ -289,7 +312,7 @@ impl<'a> std::ops::Deref for RouteUri<'a> {
|
||||||
|
|
||||||
impl fmt::Display for RouteUri<'_> {
|
impl fmt::Display for RouteUri<'_> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
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<'_> {
|
impl PartialEq<&str> for RouteUri<'_> {
|
||||||
fn eq(&self, other: &&str) -> bool { self.inner() == *other }
|
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