mirror of
https://github.com/rwf2/Rocket.git
synced 2025-01-18 07:29:09 +00:00
Remove Collider trait.
This commit is contained in:
parent
74007815a0
commit
24ee97575b
@ -4,11 +4,68 @@ use http::uri::Origin;
|
|||||||
use http::MediaType;
|
use http::MediaType;
|
||||||
use request::Request;
|
use request::Request;
|
||||||
|
|
||||||
/// The Collider trait is used to determine if two items that can be routed on
|
impl Route {
|
||||||
/// can match against a given request. That is, if two items `collide`, they
|
/// Determines if two routes can match against some request. That is, if two
|
||||||
/// will both match against _some_ request.
|
/// routes `collide`, there exists a request that can match against both
|
||||||
pub trait Collider<T: ?Sized = Self> {
|
/// routes.
|
||||||
fn collides_with(&self, other: &T) -> bool;
|
///
|
||||||
|
/// This implementation is used at initialization to check if two user
|
||||||
|
/// routes collide before launching. Format collisions works like this:
|
||||||
|
///
|
||||||
|
/// * If route specifies a format, it only gets requests for that format.
|
||||||
|
/// * If route doesn't specify a format, it gets requests for any format.
|
||||||
|
///
|
||||||
|
/// Query collisions work like this:
|
||||||
|
///
|
||||||
|
/// * If routes specify a query, they only gets request that have queries.
|
||||||
|
/// * If routes don't specify a query, requests with queries also match.
|
||||||
|
///
|
||||||
|
/// As a result, as long as everything else collides, whether a route has a
|
||||||
|
/// query or not is irrelevant: it will collide.
|
||||||
|
pub fn collides_with(&self, other: &Route) -> bool {
|
||||||
|
self.method == other.method
|
||||||
|
&& self.rank == other.rank
|
||||||
|
&& paths_collide(&self.uri, &other.uri)
|
||||||
|
&& match (self.format.as_ref(), other.format.as_ref()) {
|
||||||
|
(Some(a), Some(b)) => media_types_collide(a, b),
|
||||||
|
(Some(_), None) => true,
|
||||||
|
(None, Some(_)) => true,
|
||||||
|
(None, None) => true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines if this route matches against the given request. This means
|
||||||
|
/// that:
|
||||||
|
///
|
||||||
|
/// * The route's method matches that of the incoming request.
|
||||||
|
/// * The route's format (if any) matches that of the incoming request.
|
||||||
|
/// - If route specifies format, it only gets requests for that format.
|
||||||
|
/// - If route doesn't specify format, it gets requests for any format.
|
||||||
|
/// * All static components in the route's path match the corresponding
|
||||||
|
/// components in the same position in the incoming request.
|
||||||
|
/// * If the route specifies a query, the request must have a query as
|
||||||
|
/// well. If the route doesn't specify a query, requests with and
|
||||||
|
/// without queries match.
|
||||||
|
///
|
||||||
|
/// In the future, query handling will work as follows:
|
||||||
|
///
|
||||||
|
/// * All static components in the route's query string are also in the
|
||||||
|
/// request query string, though in any position, and there exists a
|
||||||
|
/// query parameter named exactly like each non-multi dynamic component
|
||||||
|
/// in the route's query that wasn't matched against a static component.
|
||||||
|
/// - If no query in route, requests with/without queries match.
|
||||||
|
pub fn matches(&self, req: &Request) -> bool {
|
||||||
|
self.method == req.method()
|
||||||
|
&& paths_collide(&self.uri, req.uri())
|
||||||
|
&& queries_collide(self, req)
|
||||||
|
&& match self.format {
|
||||||
|
Some(ref a) => match req.format() {
|
||||||
|
Some(ref b) => media_types_collide(a, b),
|
||||||
|
None => false
|
||||||
|
},
|
||||||
|
None => true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -27,95 +84,45 @@ fn iters_match_until<A, B>(break_c: u8, mut a: A, mut b: B) -> bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Collider<str> for &'a str {
|
fn segments_collide(first: &str, other: &str) -> bool {
|
||||||
#[inline(always)]
|
let a_iter = first.as_bytes().iter().cloned();
|
||||||
fn collides_with(&self, other: &str) -> bool {
|
let b_iter = other.as_bytes().iter().cloned();
|
||||||
let a_iter = self.as_bytes().iter().cloned();
|
iters_match_until(b'<', a_iter.clone(), b_iter.clone())
|
||||||
let b_iter = other.as_bytes().iter().cloned();
|
&& iters_match_until(b'>', a_iter.rev(), b_iter.rev())
|
||||||
iters_match_until(b'<', a_iter.clone(), b_iter.clone())
|
|
||||||
&& iters_match_until(b'>', a_iter.rev(), b_iter.rev())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This _only_ checks the `path` component of the URI.
|
fn paths_collide(first: &Origin, other: &Origin) -> bool {
|
||||||
impl<'a, 'b> Collider<Origin<'b>> for Origin<'a> {
|
for (seg_a, seg_b) in first.segments().zip(other.segments()) {
|
||||||
fn collides_with(&self, other: &Origin<'b>) -> bool {
|
if seg_a.ends_with("..>") || seg_b.ends_with("..>") {
|
||||||
for (seg_a, seg_b) in self.segments().zip(other.segments()) {
|
return true;
|
||||||
if seg_a.ends_with("..>") || seg_b.ends_with("..>") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !seg_a.collides_with(seg_b) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.segment_count() != other.segment_count() {
|
if !segments_collide(seg_a, seg_b) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if first.segment_count() != other.segment_count() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Collider for MediaType {
|
fn queries_collide(route: &Route, req: &Request) -> bool {
|
||||||
#[inline(always)]
|
route.uri.query().map_or(true, |_| req.uri().query().is_some())
|
||||||
fn collides_with(&self, other: &MediaType) -> bool {
|
|
||||||
let collide = |a, b| a == "*" || b == "*" || a == b;
|
|
||||||
collide(self.top(), other.top()) && collide(self.sub(), other.sub())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This implementation is used at initialization to check if two user routes
|
fn media_types_collide(first: &MediaType, other: &MediaType) -> bool {
|
||||||
// collide before launching. Format collisions works like this:
|
let collide = |a, b| a == "*" || b == "*" || a == b;
|
||||||
// * If route specifies a format, it only gets requests for that format.
|
collide(first.top(), other.top()) && collide(first.sub(), other.sub())
|
||||||
// * If route doesn't specify a format, it gets requests for any format.
|
|
||||||
// Query collisions work like this:
|
|
||||||
// * If routes specify a query, they only gets request that have queries.
|
|
||||||
// * If routes don't specify a query, requests with and without queries match.
|
|
||||||
// As a result, as long as everything else collides, whether a route has a query
|
|
||||||
// or not is irrelevant: it will collide.
|
|
||||||
impl Collider for Route {
|
|
||||||
fn collides_with(&self, b: &Route) -> bool {
|
|
||||||
self.method == b.method
|
|
||||||
&& self.rank == b.rank
|
|
||||||
&& self.uri.collides_with(&b.uri)
|
|
||||||
&& match (self.format.as_ref(), b.format.as_ref()) {
|
|
||||||
(Some(mt_a), Some(mt_b)) => mt_a.collides_with(mt_b),
|
|
||||||
(Some(_), None) => true,
|
|
||||||
(None, Some(_)) => true,
|
|
||||||
(None, None) => true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This implementation is used at runtime to check if a given request is
|
|
||||||
// intended for this Route. Format collisions works like this:
|
|
||||||
// * If route specifies format, it only gets requests for that format.
|
|
||||||
// * If route doesn't specify format, it gets requests for any format.
|
|
||||||
// Query collisions work like this:
|
|
||||||
// * If route specifies a query, it only gets request that have queries.
|
|
||||||
// * If route doesn't specify query, requests with & without queries collide.
|
|
||||||
impl<'r> Collider<Request<'r>> for Route {
|
|
||||||
fn collides_with(&self, req: &Request<'r>) -> bool {
|
|
||||||
self.method == req.method()
|
|
||||||
&& self.uri.collides_with(req.uri())
|
|
||||||
&& self.uri.query().map_or(true, |_| req.uri().query().is_some())
|
|
||||||
&& match self.format {
|
|
||||||
Some(ref mt_a) => match req.format() {
|
|
||||||
Some(ref mt_b) => mt_a.collides_with(mt_b),
|
|
||||||
None => false
|
|
||||||
},
|
|
||||||
None => true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use super::Collider;
|
use super::*;
|
||||||
use rocket::Rocket;
|
use rocket::Rocket;
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use request::Request;
|
use request::Request;
|
||||||
@ -139,8 +146,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn s_s_collide(a: &'static str, b: &'static str) -> bool {
|
fn s_s_collide(a: &'static str, b: &'static str) -> bool {
|
||||||
Origin::parse_route(a).unwrap()
|
let a = Origin::parse_route(a).unwrap();
|
||||||
.collides_with(&Origin::parse_route(b).unwrap())
|
let b = Origin::parse_route(b).unwrap();
|
||||||
|
paths_collide(&a, &b)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -306,7 +314,7 @@ mod tests {
|
|||||||
fn mt_mt_collide(mt1: &str, mt2: &str) -> bool {
|
fn mt_mt_collide(mt1: &str, mt2: &str) -> bool {
|
||||||
let mt_a = MediaType::from_str(mt1).expect(mt1);
|
let mt_a = MediaType::from_str(mt1).expect(mt1);
|
||||||
let mt_b = MediaType::from_str(mt2).expect(mt2);
|
let mt_b = MediaType::from_str(mt2).expect(mt2);
|
||||||
mt_a.collides_with(&mt_b)
|
media_types_collide(&mt_a, &mt_b)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -382,7 +390,7 @@ mod tests {
|
|||||||
route.format = Some(mt_str.parse::<MediaType>().unwrap());
|
route.format = Some(mt_str.parse::<MediaType>().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
route.collides_with(&req)
|
route.matches(&req)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -428,7 +436,7 @@ mod tests {
|
|||||||
let rocket = Rocket::custom(Config::development().unwrap());
|
let rocket = Rocket::custom(Config::development().unwrap());
|
||||||
let req = Request::new(&rocket, Get, Origin::parse(a).expect("valid URI"));
|
let req = Request::new(&rocket, Get, Origin::parse(a).expect("valid URI"));
|
||||||
let route = Route::ranked(0, Get, b.to_string(), dummy_handler);
|
let route = Route::ranked(0, Get, b.to_string(), dummy_handler);
|
||||||
route.collides_with(&req)
|
route.matches(&req)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -4,7 +4,6 @@ mod route;
|
|||||||
use std::collections::hash_map::HashMap;
|
use std::collections::hash_map::HashMap;
|
||||||
|
|
||||||
pub use self::route::Route;
|
pub use self::route::Route;
|
||||||
use self::collider::Collider;
|
|
||||||
|
|
||||||
use request::Request;
|
use request::Request;
|
||||||
use http::Method;
|
use http::Method;
|
||||||
@ -38,7 +37,7 @@ impl Router {
|
|||||||
// Note that routes are presorted by rank on each `add`.
|
// Note that routes are presorted by rank on each `add`.
|
||||||
let matches = self.routes.get(&req.method()).map_or(vec![], |routes| {
|
let matches = self.routes.get(&req.method()).map_or(vec![], |routes| {
|
||||||
routes.iter()
|
routes.iter()
|
||||||
.filter(|r| r.collides_with(req))
|
.filter(|r| r.matches(req))
|
||||||
.collect()
|
.collect()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user