Rocket

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 24ee97575be930fedb45b63521326b18ac246593
parent 74007815a00654748af113641df68977281b9d36
Author: Sergio Benitez <sb@sergio.bz>
Date:   Mon, 24 Sep 2018 19:44:25 -0700

Remove Collider trait.

Diffstat:
Mcore/lib/src/router/collider.rs | 168+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mcore/lib/src/router/mod.rs | 3+--
2 files changed, 89 insertions(+), 82 deletions(-)

diff --git a/core/lib/src/router/collider.rs b/core/lib/src/router/collider.rs @@ -4,11 +4,68 @@ use http::uri::Origin; use http::MediaType; use request::Request; -/// The Collider trait is used to determine if two items that can be routed on -/// can match against a given request. That is, if two items `collide`, they -/// will both match against _some_ request. -pub trait Collider<T: ?Sized = Self> { - fn collides_with(&self, other: &T) -> bool; +impl Route { + /// Determines if two routes can match against some request. That is, if two + /// routes `collide`, there exists a request that can match against both + /// routes. + /// + /// 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)] @@ -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 { - #[inline(always)] - fn collides_with(&self, other: &str) -> bool { - let a_iter = self.as_bytes().iter().cloned(); - let b_iter = other.as_bytes().iter().cloned(); - iters_match_until(b'<', a_iter.clone(), b_iter.clone()) - && iters_match_until(b'>', a_iter.rev(), b_iter.rev()) - } +fn segments_collide(first: &str, other: &str) -> bool { + let a_iter = first.as_bytes().iter().cloned(); + let b_iter = other.as_bytes().iter().cloned(); + 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. -impl<'a, 'b> Collider<Origin<'b>> for Origin<'a> { - fn collides_with(&self, other: &Origin<'b>) -> bool { - for (seg_a, seg_b) in self.segments().zip(other.segments()) { - if seg_a.ends_with("..>") || seg_b.ends_with("..>") { - return true; - } - - if !seg_a.collides_with(seg_b) { - return false; - } +fn paths_collide(first: &Origin, other: &Origin) -> bool { + for (seg_a, seg_b) in first.segments().zip(other.segments()) { + if seg_a.ends_with("..>") || seg_b.ends_with("..>") { + return true; } - if self.segment_count() != other.segment_count() { + if !segments_collide(seg_a, seg_b) { return false; } - - true } -} -impl Collider for MediaType { - #[inline(always)] - 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()) + if first.segment_count() != other.segment_count() { + return false; } + + true } -// 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 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 - } - } +fn queries_collide(route: &Route, req: &Request) -> bool { + route.uri.query().map_or(true, |_| req.uri().query().is_some()) } -// 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 - } - } +fn media_types_collide(first: &MediaType, other: &MediaType) -> bool { + let collide = |a, b| a == "*" || b == "*" || a == b; + collide(first.top(), other.top()) && collide(first.sub(), other.sub()) } #[cfg(test)] mod tests { use std::str::FromStr; - use super::Collider; + use super::*; use rocket::Rocket; use config::Config; use request::Request; @@ -139,8 +146,9 @@ mod tests { } fn s_s_collide(a: &'static str, b: &'static str) -> bool { - Origin::parse_route(a).unwrap() - .collides_with(&Origin::parse_route(b).unwrap()) + let a = Origin::parse_route(a).unwrap(); + let b = Origin::parse_route(b).unwrap(); + paths_collide(&a, &b) } #[test] @@ -306,7 +314,7 @@ mod tests { fn mt_mt_collide(mt1: &str, mt2: &str) -> bool { let mt_a = MediaType::from_str(mt1).expect(mt1); let mt_b = MediaType::from_str(mt2).expect(mt2); - mt_a.collides_with(&mt_b) + media_types_collide(&mt_a, &mt_b) } #[test] @@ -382,7 +390,7 @@ mod tests { route.format = Some(mt_str.parse::<MediaType>().unwrap()); } - route.collides_with(&req) + route.matches(&req) } #[test] @@ -428,7 +436,7 @@ mod tests { let rocket = Rocket::custom(Config::development().unwrap()); let req = Request::new(&rocket, Get, Origin::parse(a).expect("valid URI")); let route = Route::ranked(0, Get, b.to_string(), dummy_handler); - route.collides_with(&req) + route.matches(&req) } #[test] diff --git a/core/lib/src/router/mod.rs b/core/lib/src/router/mod.rs @@ -4,7 +4,6 @@ mod route; use std::collections::hash_map::HashMap; pub use self::route::Route; -use self::collider::Collider; use request::Request; use http::Method; @@ -38,7 +37,7 @@ impl Router { // Note that routes are presorted by rank on each `add`. let matches = self.routes.get(&req.method()).map_or(vec![], |routes| { routes.iter() - .filter(|r| r.collides_with(req)) + .filter(|r| r.matches(req)) .collect() });