From 6ee47782c721e05ed7ac7e6651ea562a86fbf0b6 Mon Sep 17 00:00:00 2001 From: Alexandros Katechis Date: Thu, 25 Jul 2019 10:22:42 -0400 Subject: [PATCH 1/7] Implement parameter capturing. Remove builders. Add some more tests. --- Cargo.lock | 59 --------- Cargo.toml | 1 - src/builder.rs | 43 ------- src/lib.rs | 256 ++++++++++++++-------------------------- src/parameters.rs | 9 ++ src/path.rs | 213 +++++++++++++++++++++++++++++---- src/route.rs | 104 ++++++++++++++++ src/route/builder.rs | 20 ---- src/route/mod.rs | 5 - src/route/route_impl.rs | 100 ---------------- src/router.rs | 54 +++++++++ test-server/main.rs | 21 +++- tests/basic.rs | 150 +++++++++++------------ tests/parameters.rs | 254 +++++++++++++++++++++++++++++++++++++++ 14 files changed, 797 insertions(+), 492 deletions(-) delete mode 100644 src/builder.rs create mode 100644 src/parameters.rs create mode 100644 src/route.rs delete mode 100644 src/route/builder.rs delete mode 100644 src/route/mod.rs delete mode 100644 src/route/route_impl.rs create mode 100644 src/router.rs create mode 100644 tests/parameters.rs diff --git a/Cargo.lock b/Cargo.lock index 812ca6d4..e1663807 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,13 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "aho-corasick" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "arrayvec" version = "0.4.10" @@ -210,7 +202,6 @@ version = "0.5.0" dependencies = [ "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.12.24 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -273,11 +264,6 @@ dependencies = [ "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "memchr" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "memoffset" version = "0.2.1" @@ -473,26 +459,6 @@ name = "redox_syscall" version = "0.1.51" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "regex" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "regex-syntax" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "rustc_version" version = "0.2.3" @@ -539,14 +505,6 @@ name = "string" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "thread_local" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "time" version = "0.1.42" @@ -665,16 +623,6 @@ name = "try-lock" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "ucd-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "utf8-ranges" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "want" version = "0.0.6" @@ -724,7 +672,6 @@ dependencies = [ ] [metadata] -"checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" "checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" "checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" @@ -756,7 +703,6 @@ dependencies = [ "checksum libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)" = "413f3dfc802c5dc91dc570b05125b6cda9855edfaa9825c9849807876376e70e" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" -"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" "checksum mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "71646331f2619b1026cc302f87a2b8b648d5c6dd6937846a16cc8ce0f347f432" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" @@ -778,8 +724,6 @@ dependencies = [ "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85" -"checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" -"checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" @@ -788,7 +732,6 @@ dependencies = [ "checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" "checksum string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b639411d0b9c738748b5397d5ceba08e648f4f1992231aa859af1a017f31f60b" -"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum tokio 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "e0500b88064f08bebddd0c0bed39e19f5c567a5f30975bee52b0c0d3e2eeb38c" "checksum tokio-current-thread 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "331c8acc267855ec06eb0c94618dcbbfea45bed2d20b77252940095273fb58f6" @@ -799,8 +742,6 @@ dependencies = [ "checksum tokio-threadpool 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c3fd86cb15547d02daa2b21aadaf4e37dee3368df38a526178a5afa3c034d2fb" "checksum tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2910970404ba6fa78c5539126a9ae2045d62e3713041e447f695f41405a120c6" "checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" -"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" -"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" "checksum want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "797464475f30ddb8830cc529aaaae648d581f99e2036a928877dfde027ddf6b3" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" diff --git a/Cargo.toml b/Cargo.toml index 82351ca7..2ffff273 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,4 +20,3 @@ path = "src/lib.rs" [dependencies] futures = "^0.1" hyper = "^0.12" -regex = "^0.2" diff --git a/src/builder.rs b/src/builder.rs deleted file mode 100644 index c15507ad..00000000 --- a/src/builder.rs +++ /dev/null @@ -1,43 +0,0 @@ -use super::Route; -use super::Router; - -/// Builder for a router -/// -/// Example usage: -/// -#[derive(Debug, Default)] -pub struct RouterBuilder { - routes: Vec, -} - -impl RouterBuilder { - pub fn new() -> RouterBuilder { - RouterBuilder { routes: vec![] } - } - - /// Adds new `Route` for `Router` that is being built. - /// - /// Example: - /// - /// ```ignore - /// use hyper::server::{Request, Response}; - /// use hyper_router::{Route, RouterBuilder}; - /// - /// fn some_handler(_: Request) -> Response { - /// // do something - /// } - /// - /// RouterBuilder::new().add(Route::get(r"/person/\d+").using(some_handler)); - /// ``` - #[allow(clippy::should_implement_trait)] - pub fn add(mut self, route: Route) -> RouterBuilder { - self.routes.push(route); - self - } - - pub fn build(self) -> Router { - Router { - routes: self.routes, - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 02331e36..2105f149 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,185 +1,33 @@ #![doc(html_root_url = "https://marad.github.io/hyper-router/doc/hyper_router")] -//! # Hyper Router -//! -//! This cargo is a small extension to the great Hyper HTTP library. It basically is -//! adds the ability to define routes to request handlers and then query for the handlers -//! by request path. -//! -//! ## Usage -//! -//! To use the library just add: -//! -//! ```text -//! hyper = "^0.12" -//! hyper-router = "^0.5" -//! ``` -//! -//! to your dependencies. -//! -//! ```no_run -//! extern crate hyper; -//! extern crate hyper_router; -//! -//! use hyper::header::{CONTENT_LENGTH, CONTENT_TYPE}; -//! use hyper::{Request, Response, Body, Method}; -//! use hyper::server::Server; -//! use hyper::rt::Future; -//! use hyper_router::{Route, RouterBuilder, RouterService}; -//! -//! fn basic_handler(_: Request) -> Response { -//! let body = "Hello World"; -//! Response::builder() -//! .header(CONTENT_LENGTH, body.len() as u64) -//! .header(CONTENT_TYPE, "text/plain") -//! .body(Body::from(body)) -//! .expect("Failed to construct the response") -//! } -//! -//! fn router_service() -> Result { -//! let router = RouterBuilder::new() -//! .add(Route::get("/greet").using(basic_handler)) -//! .add(Route::from(Method::PATCH, "/asd").using(basic_handler)) -//! .build(); -//! -//! Ok(RouterService::new(router)) -//! } -//! -//! fn main() { -//! let addr = "0.0.0.0:8080".parse().unwrap(); -//! let server = Server::bind(&addr) -//! .serve(router_service) -//! .map_err(|e| eprintln!("server error: {}", e)); -//! -//! hyper::rt::run(server) -//! } -//! ``` -//! -//! This code will start Hyper server and add use router to find handlers for request. -//! We create the `Route` so that when we visit path `/greet` the `basic_handler` handler -//! will be called. -//! -//! ## Things to note -//! -//! * `Path::new` method accepts regular expressions so you can match every path you please. -//! * If you have request matching multiple paths the one that was first `add`ed will be chosen. -//! * This library is in an early stage of development so there may be breaking changes comming -//! (but I'll try as hard as I can not to break backwards compatibility or break it just a little - -//! I promise I'll try!). -//! -//! # Waiting for your feedback -//! -//! I've created this little tool to help myself learn Rust and to avoid using big frameworks -//! like Iron or rustful. I just want to keep things simple. -//! -//! Obviously I could make some errors or bad design choices so I'm waiting for your feedback! -//! You may create an issue at [project's bug tracker](https://github.com/marad/hyper-router/issues). - -extern crate futures; -extern crate hyper; - use futures::future::FutureResult; -use hyper::header::CONTENT_LENGTH; use hyper::service::Service; -use hyper::{Body, Request, Response}; - -use hyper::Method; use hyper::StatusCode; +use hyper::{Body, Request, Response}; -mod builder; pub mod handlers; +mod parameters; mod path; -pub mod route; +mod route; +mod router; -pub use self::builder::RouterBuilder; -pub use self::path::Path; +pub use self::parameters::RouteParameters; +use self::path::Path; pub use self::route::Route; -pub use self::route::RouteBuilder; +pub use self::router::Router; pub type Handler = fn(Request) -> Response; pub type HttpResult = Result; -/// This is the one. The router. -#[derive(Debug)] -pub struct Router { - routes: Vec, -} - -impl Router { - /// Finds handler for given Hyper request. - /// - /// This method uses default error handlers. - /// If the request does not match any route than default 404 handler is returned. - /// If the request match some routes but http method does not match (used GET but routes are - /// defined for POST) than default method not supported handler is returned. - pub fn find_handler_with_defaults(&self, request: &Request) -> Handler { - let matching_routes = self.find_matching_routes(request.uri().path()); - match matching_routes.len() { - x if x == 0 => handlers::default_404_handler, - _ => self - .find_for_method(&matching_routes, request.method()) - .unwrap_or(handlers::method_not_supported_handler), - } - } - - /// Finds handler for given Hyper request. - /// - /// It returns handler if it's found or `StatusCode` for error. - /// This method may return `NotFound`, `MethodNotAllowed` or `NotImplemented` - /// status codes. - pub fn find_handler(&self, request: &Request) -> HttpResult { - let matching_routes = self.find_matching_routes(request.uri().path()); - match matching_routes.len() { - x if x == 0 => Err(StatusCode::NOT_FOUND), - _ => self - .find_for_method(&matching_routes, request.method()) - .map(Ok) - .unwrap_or(Err(StatusCode::METHOD_NOT_ALLOWED)), - } - } - - /// Returns vector of `Route`s that match to given path. - pub fn find_matching_routes(&self, request_path: &str) -> Vec<&Route> { - self.routes - .iter() - .filter(|route| route.path.matcher.is_match(&request_path)) - .collect() - } - - fn find_for_method(&self, routes: &[&Route], method: &Method) -> Option { - let method = method.clone(); - routes - .iter() - .find(|route| route.method == method) - .map(|route| route.handler) - } -} - /// The default simple router service. #[derive(Debug)] pub struct RouterService { pub router: Router, - pub error_handler: fn(StatusCode) -> Response, } impl RouterService { pub fn new(router: Router) -> RouterService { - RouterService { - router, - error_handler: Self::default_error_handler, - } - } - - fn default_error_handler(status_code: StatusCode) -> Response { - let error = "Routing error: page not found"; - Response::builder() - .header(CONTENT_LENGTH, error.len() as u64) - .status(match status_code { - StatusCode::NOT_FOUND => StatusCode::NOT_FOUND, - _ => StatusCode::INTERNAL_SERVER_ERROR, - }) - .body(Body::from(error)) - .expect("Failed to construct a response") + RouterService { router } } } @@ -189,10 +37,88 @@ impl Service for RouterService { type Error = hyper::Error; type Future = FutureResult, hyper::Error>; - fn call(&mut self, request: Request) -> Self::Future { - futures::future::ok(match self.router.find_handler(&request) { - Ok(handler) => handler(request), - Err(status_code) => (self.error_handler)(status_code), - }) + fn call(&mut self, mut request: Request) -> Self::Future { + let (handler, parameters) = self.router.find_handler(&request); + let extensions = request.extensions_mut(); + extensions.insert(parameters); + futures::future::ok(handler(request)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use futures::executor::spawn; + + use hyper::header::*; + use hyper::*; + use std::str::FromStr; + + #[test] + fn test_router_service_calls_correct_handler_for_static_path() { + fn get_foo_handler(_: Request) -> Response { + let body = "Hello World"; + Response::builder() + .header(CONTENT_LENGTH, body.len() as u64) + .header(CONTENT_TYPE, "text/plain") + .body(Body::from(body)) + .expect("Failed to construct the response") + } + + let router = Router::new().add(Route::get("/foo", get_foo_handler)); + + let mut svc = RouterService::new(router); + + let request = Request::builder() + .method(Method::GET) + .uri(Uri::from_str("http://www.example.com/foo").unwrap()) + .body(Body::empty()) + .unwrap(); + + let response = spawn(svc.call(request)).wait_future().unwrap(); + let (parts, body) = response.into_parts(); + let headers = parts.headers; + assert_eq!(parts.version, Version::HTTP_11); + assert_eq!(parts.status, 200); + assert_eq!(headers.get(CONTENT_TYPE).unwrap(), "text/plain"); + assert_eq!(headers.get(CONTENT_LENGTH).unwrap(), "11"); + + let bytes = spawn(body).wait_stream().unwrap().unwrap().into_bytes(); + assert_eq!(bytes[..], b"Hello World"[..]); + } + + #[test] + fn test_router_service_passes_captured_parameters_to_handler_via_extensions() { + fn get_foo_handler(req: Request) -> Response { + let route_params: &RouteParameters = req.extensions().get().unwrap(); + let id = route_params.parameters.iter().nth(0).unwrap(); + let body = format!("FOO-{}", id); + Response::builder() + .header(CONTENT_LENGTH, body.len() as u64) + .header(CONTENT_TYPE, "text/plain") + .body(Body::from(body)) + .expect("Failed to construct the response") + } + + let router = Router::new().add(Route::get("/foo/:id", get_foo_handler)); + + let mut svc = RouterService::new(router); + let uri = Uri::from_str("http://www.example.com/foo/75bd8cfc-e421-48f8-93a1-57f423d254e6"); + let request = Request::builder() + .method(Method::GET) + .uri(uri.unwrap()) + .body(Body::empty()) + .unwrap(); + + let response = spawn(svc.call(request)).wait_future().unwrap(); + let (parts, body) = response.into_parts(); + let headers = parts.headers; + assert_eq!(parts.version, Version::HTTP_11); + assert_eq!(parts.status, 200); + assert_eq!(headers.get(CONTENT_TYPE).unwrap(), "text/plain"); + assert_eq!(headers.get(CONTENT_LENGTH).unwrap(), "40"); + + let bytes = spawn(body).wait_stream().unwrap().unwrap().into_bytes(); + assert_eq!(bytes[..], b"FOO-75bd8cfc-e421-48f8-93a1-57f423d254e6"[..]); } } diff --git a/src/parameters.rs b/src/parameters.rs new file mode 100644 index 00000000..db492889 --- /dev/null +++ b/src/parameters.rs @@ -0,0 +1,9 @@ +pub struct RouteParameters { + pub parameters: Vec, +} + +impl RouteParameters { + pub fn new(params: Vec) -> RouteParameters { + RouteParameters { parameters: params } + } +} diff --git a/src/path.rs b/src/path.rs index 221e0829..ed0293d7 100644 --- a/src/path.rs +++ b/src/path.rs @@ -1,31 +1,202 @@ -extern crate regex; -use self::regex::Regex; +use crate::parameters::RouteParameters; /// Represents a path in HTTP sense (starting from `/`) +/// This path is internal to the crate, and encapsulates the path matching +/// logic of a route. #[derive(Debug)] -pub struct Path { - pub matcher: Regex, +pub(crate) enum Path { + Static(String), + Parametric(Vec), } impl Path { - /// Creates a new path. - /// - /// This method accepts regular expressions so you can - /// write something like this: - /// - /// ```no_run - /// use hyper_router::Path; - /// Path::new(r"/person/\d+"); - /// ``` - /// - /// Note that you don't have to match beggining and end of the - /// path using `^` and `$` - those are inserted for you automatically. pub fn new(path: &str) -> Path { - let mut regex = "^".to_string(); - regex.push_str(path); - regex.push_str("$"); - Path { - matcher: Regex::new(®ex).unwrap(), + if is_parametric_path(path) { + Path::Parametric(path.split('/').map(|s| s.to_owned()).collect()) + } else { + Path::Static(path.to_owned()) + } + } + + pub fn matches(&self, other_path: &str) -> Option { + match self { + Path::Static(me) => { + if me == other_path { + Some(RouteParameters::new(vec![])) + } else { + None + } + } + Path::Parametric(self_path) => { + let mut params = vec![]; + let mut self_segments = self_path.iter(); + let mut other_segments = other_path.split('/'); + + loop { + let self_seg = self_segments.next(); + let other_seg = other_segments.next(); + + match (self_seg, other_seg) { + // We have two segments to compare. + (Some(left), Some(right)) => { + let first_self_char = left.chars().nth(0); + match first_self_char { + Some(ch) if ch == ':' => { + params.push(right.to_string()); + } + _ => { + if left != right { + return None; + } + } + }; + } + + // We're out of segments to compare, so break. + (None, None) => { + break; + } + + // We have 1 Some and 1 None, meaning the route can't be a match + _ => { + return None; + } + } + } + + Some(RouteParameters::new(params)) + } + } + } +} + +fn is_parametric_path(path: &str) -> bool { + for ch in path.chars() { + if ch == ':' { + return true; + } + } + return false; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_parametric_path_on_static_paths() { + assert_eq!(is_parametric_path("/foo"), false); + assert_eq!(is_parametric_path("/foo/bar"), false); + assert_eq!(is_parametric_path("/foo/bar/baz"), false); + } + + #[test] + fn test_is_parametric_path_on_parametric_paths() { + assert_eq!(is_parametric_path("/foo/:id"), true); + assert_eq!(is_parametric_path("/foo/:id/bar/baz"), true); + assert_eq!(is_parametric_path("/foo/:foo_id/bar/:bar_id"), true); + } + + #[test] + fn test_static_path_matches_path() { + let path = Path::new("/foo/bar"); + { + let matches = path.matches("/foo"); + assert!(matches.is_none()); + } + { + let matches = path.matches("/foo/bar"); + let empty_vec: Vec = vec![]; + assert!(matches.is_some()); + assert_eq!(matches.unwrap().parameters, empty_vec); + } + { + let matches = path.matches("/foo/bar/baz"); + assert!(matches.is_none()); + } + } + + #[test] + fn test_parametric_path_with_long_variable_matches_path() { + let path = Path::new("/foo/:foooooooooooooooooooooooooooooid"); + { + let matches = path.matches("/foo"); + assert!(matches.is_none()); + } + { + let matches = path.matches("/foo/bar"); + assert!(matches.is_some()); + assert_eq!(matches.unwrap().parameters, vec!["bar"]); + } + { + let matches = path.matches("/foo/barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr"); + assert!(matches.is_some()); + assert_eq!( + matches.unwrap().parameters, + vec!["barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr"] + ); + } + { + let matches = path.matches("/foo/bar/baz"); + assert!(matches.is_none()); + } + } + + #[test] + fn test_parametric_path_with_short_variable_matches_path() { + let path = Path::new("/foo/:a"); + { + let matches = path.matches("/foo"); + assert!(matches.is_none()); + } + { + let matches = path.matches("/foo/bar"); + assert!(matches.is_some()); + assert_eq!(matches.unwrap().parameters, vec!["bar"]); + } + { + let matches = path.matches("/foo/barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr"); + assert!(matches.is_some()); + assert_eq!( + matches.unwrap().parameters, + vec!["barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr"] + ); + } + { + let matches = path.matches("/foo/bar/baz"); + assert!(matches.is_none()); + } + } + + #[test] + fn test_parametric_path_with_multiple_variables_matches_path() { + let path = Path::new("/users/:user_id/friend/:friend_id/group/:group_id"); + let matches = path.matches("/users/1/friend/2/group/77").unwrap(); + assert_eq!(matches.parameters, vec!["1", "2", "77"]); + } + + #[test] + fn test_parametric_path_with_only_variables() { + let path = Path::new("/:one/:two/:three"); + { + let matches = path.matches("/1/2/3").unwrap(); + assert_eq!(matches.parameters, vec!["1", "2", "3"]); + } + { + let matches = path.matches("/hello/howdie/hey").unwrap(); + assert_eq!(matches.parameters, vec!["hello", "howdie", "hey"]); + } + { + let matches = path.matches("/hello"); + assert_eq!(matches.is_none(), true); + } + { + let matches = path.matches("/hello/hello"); + assert_eq!(matches.is_none(), true); + } + { + let matches = path.matches("/hello/hello/h/e/l/l/o"); + assert_eq!(matches.is_none(), true); } } } diff --git a/src/route.rs b/src/route.rs new file mode 100644 index 00000000..864db741 --- /dev/null +++ b/src/route.rs @@ -0,0 +1,104 @@ +use crate::Handler; +use crate::Path; +use hyper::Method; +use std::fmt; + +/// Holds route information +pub struct Route { + /// HTTP method to match + pub method: Method, + + /// Path to match + pub(crate) path: Path, + + /// Request handler + /// + /// This should be method that accepts Hyper's Request and Response: + /// + /// ```rust + /// use hyper::*; + /// use hyper::header::*; + /// + /// fn hello_handler(_: Request) -> Response { + /// let body = "Hello World"; + /// Response::builder() + /// .header(CONTENT_LENGTH, body.len() as u64) + /// .header(CONTENT_TYPE, "text/plain") + /// .body(Body::from(body)) + /// .expect("Failed to construct the response") + /// } + /// ``` + pub handler: Handler, +} + +impl Route { + pub fn options(path: &str, handler: Handler) -> Route { + Route::from(Method::OPTIONS, path, handler) + } + + pub fn get(path: &str, handler: Handler) -> Route { + Route::from(Method::GET, path, handler) + } + + pub fn post(path: &str, handler: Handler) -> Route { + Route::from(Method::POST, path, handler) + } + + pub fn put(path: &str, handler: Handler) -> Route { + Route::from(Method::PUT, path, handler) + } + + pub fn delete(path: &str, handler: Handler) -> Route { + Route::from(Method::DELETE, path, handler) + } + + pub fn head(path: &str, handler: Handler) -> Route { + Route::from(Method::HEAD, path, handler) + } + + pub fn trace(path: &str, handler: Handler) -> Route { + Route::from(Method::TRACE, path, handler) + } + + pub fn connect(path: &str, handler: Handler) -> Route { + Route::from(Method::CONNECT, path, handler) + } + + pub fn patch(path: &str, handler: Handler) -> Route { + Route::from(Method::PATCH, path, handler) + } + + pub fn from(method: Method, path: &str, handler: Handler) -> Route { + Route { + method, + path: Path::new(path), + handler: handler, + } + } +} + +impl fmt::Debug for Route { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Route {{method: {:?}, path: {:?}}}", + self.method, self.path + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use hyper::*; + + fn some_handler(_: Request) -> Response { + unimplemented!() + } + + #[test] + fn test_construct_get_route() { + let r = Route::get("/foo", some_handler); + assert_eq!(r.method, Method::GET); + } +} diff --git a/src/route/builder.rs b/src/route/builder.rs deleted file mode 100644 index db4c2137..00000000 --- a/src/route/builder.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::Handler; -use crate::Route; - -pub struct RouteBuilder { - route: Route, -} - -impl RouteBuilder { - pub fn new(route: Route) -> RouteBuilder { - RouteBuilder { route } - } - - /// Completes the building process by taking the handler to process the request. - /// - /// Returns created route. - pub fn using(mut self, handler: Handler) -> Route { - self.route.handler = handler; - self.route - } -} diff --git a/src/route/mod.rs b/src/route/mod.rs deleted file mode 100644 index 795f2271..00000000 --- a/src/route/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod builder; -mod route_impl; - -pub use builder::RouteBuilder; -pub use route_impl::Route; diff --git a/src/route/route_impl.rs b/src/route/route_impl.rs deleted file mode 100644 index 618c3379..00000000 --- a/src/route/route_impl.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::handlers; -use hyper::Method; -use std::fmt; - -use super::RouteBuilder; -use crate::Handler; -use crate::Path; - -/// Holds route information -pub struct Route { - /// HTTP method to match - pub method: Method, - - /// Path to match - pub path: Path, - - /// Request handler - /// - /// This should be method that accepts Hyper's Request and Response: - /// - /// ```ignore - /// use hyper::server::{Request, Response}; - /// use hyper::header::{ContentLength, ContentType}; - /// - /// fn hello_handler(_: Request) -> Response { - /// let body = "Hello World"; - /// Response::new() - /// .with_header(ContentLength(body.len() as u64)) - /// .with_header(ContentType::plaintext()) - /// .with_body(body) - /// } - /// ``` - pub handler: Handler, -} - -impl Route { - pub fn options(path: &str) -> RouteBuilder { - Route::from(Method::OPTIONS, path) - } - - pub fn get(path: &str) -> RouteBuilder { - Route::from(Method::GET, path) - } - - pub fn post(path: &str) -> RouteBuilder { - Route::from(Method::POST, path) - } - - pub fn put(path: &str) -> RouteBuilder { - Route::from(Method::PUT, path) - } - - pub fn delete(path: &str) -> RouteBuilder { - Route::from(Method::DELETE, path) - } - - pub fn head(path: &str) -> RouteBuilder { - Route::from(Method::HEAD, path) - } - - pub fn trace(path: &str) -> RouteBuilder { - Route::from(Method::TRACE, path) - } - - pub fn connect(path: &str) -> RouteBuilder { - Route::from(Method::CONNECT, path) - } - - pub fn patch(path: &str) -> RouteBuilder { - Route::from(Method::PATCH, path) - } - - pub fn from(method: Method, path: &str) -> RouteBuilder { - RouteBuilder::new(Route { - method, - path: Path::new(path), - ..Route::default() - }) - } -} - -impl Default for Route { - fn default() -> Route { - Route { - method: Method::GET, - path: Path::new("/"), - handler: handlers::not_implemented_handler, - } - } -} - -impl fmt::Debug for Route { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "Route {{method: {:?}, path: {:?}}}", - self.method, self.path - ) - } -} diff --git a/src/router.rs b/src/router.rs new file mode 100644 index 00000000..eaebbf09 --- /dev/null +++ b/src/router.rs @@ -0,0 +1,54 @@ +use crate::handlers; +use crate::parameters::RouteParameters; +use crate::route::Route; +use crate::Handler; +use hyper::Body; +use hyper::Request; + +#[derive(Debug)] +pub struct Router { + routes: Vec, + not_found_handler: Handler, + method_not_supported_handler: Handler, +} + +impl Router { + pub fn new() -> Router { + Router { + routes: vec![], + not_found_handler: handlers::default_404_handler, + method_not_supported_handler: handlers::method_not_supported_handler, + } + } + + pub fn not_found(mut self, handler: Handler) -> Router { + self.not_found_handler = handler; + self + } + + pub fn method_not_supported(mut self, handler: Handler) -> Router { + self.method_not_supported_handler = handler; + self + } + + pub fn add(mut self, route: Route) -> Router { + self.routes.push(route); + self + } + + pub fn find_handler(&self, request: &Request) -> (Handler, RouteParameters) { + let req_path = request.uri().path(); + let req_method = request.method(); + for route in &self.routes { + let matches = route.path.matches(req_path); + if matches.is_some() { + if route.method == req_method { + return (route.handler, matches.unwrap()); + } else { + return (self.method_not_supported_handler, matches.unwrap()); + } + } + } + (self.not_found_handler, RouteParameters::new(vec![])) + } +} diff --git a/test-server/main.rs b/test-server/main.rs index 34cfca03..c921263b 100644 --- a/test-server/main.rs +++ b/test-server/main.rs @@ -5,7 +5,7 @@ use hyper::header::{CONTENT_LENGTH, CONTENT_TYPE}; use hyper::rt::Future; use hyper::server::Server; use hyper::{Body, Method, Request, Response}; -use hyper_router::{Route, RouterBuilder, RouterService}; +use hyper_router::{Route, RouteParameters, Router, RouterService}; fn request_handler(_: Request) -> Response { let body = "Hello World"; @@ -16,11 +16,22 @@ fn request_handler(_: Request) -> Response { .expect("Failed to construct the response") } +fn greeting_handler(req: Request) -> Response { + let params: &RouteParameters = req.extensions().get().unwrap(); + let name = params.parameters.get(0).unwrap(); + let body = format!("Hello, {}!", name); + Response::builder() + .header(CONTENT_LENGTH, body.len() as u64) + .header(CONTENT_TYPE, "text/plain") + .body(Body::from(body)) + .expect("Failed to construct the response") +} + fn router_service() -> Result { - let router = RouterBuilder::new() - .add(Route::get("/hello").using(request_handler)) - .add(Route::from(Method::PATCH, "/world").using(request_handler)) - .build(); + let router = Router::new() + .add(Route::get("/hello", request_handler)) + .add(Route::get("/greeting/:name", greeting_handler)) + .add(Route::from(Method::PATCH, "/world", request_handler)); Ok(RouterService::new(router)) } diff --git a/tests/basic.rs b/tests/basic.rs index 0ac2c6b2..d1344da8 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -6,7 +6,7 @@ use hyper_router::*; use std::str::FromStr; #[test] -fn test_get_route() { +fn test_get_route_with_static_path() { let request = Request::builder() .method(Method::GET) .uri(Uri::from_str("http://www.example.com/hello").unwrap()) @@ -26,19 +26,19 @@ fn test_get_route() { unimplemented!() }; - let router = RouterBuilder::new() - .add(Route::get("/hello").using(handle_get_hello)) - .add(Route::get("/").using(handle_get_root)) - .add(Route::get("/foo").using(handle_get_foo)) - .add(Route::post("/hello").using(handle_post_hello)) - .build(); + let router = Router::new() + .add(Route::get("/hello", handle_get_hello)) + .add(Route::get("/", handle_get_root)) + .add(Route::get("/foo", handle_get_foo)) + .add(Route::post("/hello", handle_post_hello)); - let handler = router.find_handler(&request).unwrap(); + let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_get_hello as fn(_) -> _); + assert_eq!(params.parameters, vec![] as Vec); } #[test] -fn test_post_route() { +fn test_post_route_with_static_path() { let request = Request::builder() .method(Method::POST) .uri(Uri::from_str("http://www.example.com/hello").unwrap()) @@ -58,19 +58,19 @@ fn test_post_route() { unimplemented!() }; - let router = RouterBuilder::new() - .add(Route::post("/hello").using(handle_post_hello)) - .add(Route::get("/").using(handle_post_root)) - .add(Route::get("/foo").using(handle_post_foo)) - .add(Route::get("/hello").using(handle_get_hello)) - .build(); + let router = Router::new() + .add(Route::post("/hello", handle_post_hello)) + .add(Route::get("/", handle_post_root)) + .add(Route::get("/foo", handle_post_foo)) + .add(Route::get("/hello", handle_get_hello)); - let handler = router.find_handler(&request).unwrap(); + let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_post_hello as fn(_) -> _); + assert_eq!(params.parameters, vec![] as Vec); } #[test] -fn test_delete_route() { +fn test_delete_route_with_static_path() { let request = Request::builder() .method(Method::DELETE) .uri(Uri::from_str("http://www.example.com/hello").unwrap()) @@ -84,17 +84,17 @@ fn test_delete_route() { unimplemented!() }; - let router = RouterBuilder::new() - .add(Route::delete("/hello").using(handle_delete_hello)) - .add(Route::post("/hello").using(handle_post_hello)) - .build(); + let router = Router::new() + .add(Route::delete("/hello", handle_delete_hello)) + .add(Route::post("/hello", handle_post_hello)); - let handler = router.find_handler(&request).unwrap(); + let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_delete_hello as fn(_) -> _); + assert_eq!(params.parameters, vec![] as Vec); } #[test] -fn test_options_route() { +fn test_options_route_with_static_path() { let request = Request::builder() .method(Method::OPTIONS) .uri(Uri::from_str("http://www.example.com/hello").unwrap()) @@ -108,17 +108,17 @@ fn test_options_route() { unimplemented!() }; - let router = RouterBuilder::new() - .add(Route::options("/hello").using(handle_options_hello)) - .add(Route::post("/hello").using(handle_post_hello)) - .build(); + let router = Router::new() + .add(Route::options("/hello", handle_options_hello)) + .add(Route::post("/hello", handle_post_hello)); - let handler = router.find_handler(&request).unwrap(); + let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_options_hello as fn(_) -> _); + assert_eq!(params.parameters, vec![] as Vec); } #[test] -fn test_put_route() { +fn test_put_route_with_static_path() { let request = Request::builder() .method(Method::PUT) .uri(Uri::from_str("http://www.example.com/hello").unwrap()) @@ -132,17 +132,17 @@ fn test_put_route() { unimplemented!() }; - let router = RouterBuilder::new() - .add(Route::put("/hello").using(handle_put_hello)) - .add(Route::post("/hello").using(handle_post_hello)) - .build(); + let router = Router::new() + .add(Route::put("/hello", handle_put_hello)) + .add(Route::post("/hello", handle_post_hello)); - let handler = router.find_handler(&request).unwrap(); + let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_put_hello as fn(_) -> _); + assert_eq!(params.parameters, vec![] as Vec); } #[test] -fn test_head_route() { +fn test_head_route_with_static_path() { let request = Request::builder() .method(Method::HEAD) .uri(Uri::from_str("http://www.example.com/hello").unwrap()) @@ -156,17 +156,17 @@ fn test_head_route() { unimplemented!() }; - let router = RouterBuilder::new() - .add(Route::head("/hello").using(handle_head_hello)) - .add(Route::post("/hello").using(handle_post_hello)) - .build(); + let router = Router::new() + .add(Route::head("/hello", handle_head_hello)) + .add(Route::post("/hello", handle_post_hello)); - let handler = router.find_handler(&request).unwrap(); + let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_head_hello as fn(_) -> _); + assert_eq!(params.parameters, vec![] as Vec); } #[test] -fn test_trace_route() { +fn test_trace_route_with_static_path() { let request = Request::builder() .method(Method::TRACE) .uri(Uri::from_str("http://www.example.com/hello").unwrap()) @@ -180,17 +180,17 @@ fn test_trace_route() { unimplemented!() }; - let router = RouterBuilder::new() - .add(Route::trace("/hello").using(handle_trace_hello)) - .add(Route::post("/hello").using(handle_post_hello)) - .build(); + let router = Router::new() + .add(Route::trace("/hello", handle_trace_hello)) + .add(Route::post("/hello", handle_post_hello)); - let handler = router.find_handler(&request).unwrap(); + let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_trace_hello as fn(_) -> _); + assert_eq!(params.parameters, vec![] as Vec); } #[test] -fn test_patch_route() { +fn test_patch_route_with_static_path() { let request = Request::builder() .method(Method::PATCH) .uri(Uri::from_str("http://www.example.com/hello").unwrap()) @@ -204,23 +204,26 @@ fn test_patch_route() { unimplemented!() }; - let router = RouterBuilder::new() - .add(Route::patch("/hello").using(handle_patch_hello)) - .add(Route::post("/hello").using(handle_post_hello)) - .build(); + let router = Router::new() + .add(Route::patch("/hello", handle_patch_hello)) + .add(Route::post("/hello", handle_post_hello)); - let handler = router.find_handler(&request).unwrap(); + let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_patch_hello as fn(_) -> _); + assert_eq!(params.parameters, vec![] as Vec); } #[test] -fn test_no_route() { +fn test_route_not_found() { let request = Request::builder() .method(Method::GET) .uri(Uri::from_str("http://www.example.com/notfound").unwrap()) .body(Body::empty()) .unwrap(); + fn handle_not_found(_: Request) -> Response { + unimplemented!() + } fn handle_get_foo(_: Request) -> Response { unimplemented!() }; @@ -228,39 +231,40 @@ fn test_no_route() { unimplemented!() }; - let router = RouterBuilder::new() - .add(Route::patch("/foo").using(handle_get_foo)) - .add(Route::patch("/bar").using(handle_get_bar)) - .build(); + let router = Router::new() + .not_found(handle_not_found) + .add(Route::patch("/foo", handle_get_foo)) + .add(Route::patch("/bar", handle_get_bar)); - let handler = router.find_handler(&request); - - match handler { - Ok(_) => panic!("Expected an error, but got a handler instead"), - Err(e) => assert_eq!(e, hyper::StatusCode::NOT_FOUND), - } + let (handler, params) = router.find_handler(&request); + assert!(handler as fn(_) -> _ == handle_not_found as fn(_) -> _); + assert_eq!(params.parameters, vec![] as Vec); } #[test] -fn test_regex_path() { +fn test_method_not_supported_with_static_path() { let request = Request::builder() .method(Method::GET) - .uri(Uri::from_str("http://www.example.com/foo/bar").unwrap()) + .uri(Uri::from_str("http://www.example.com/foo").unwrap()) .body(Body::empty()) .unwrap(); - fn handle_regex_foo(_: Request) -> Response { + fn handle_method_not_supported(_: Request) -> Response { + unimplemented!() + } + fn handle_get_foo(_: Request) -> Response { unimplemented!() }; - fn handle_regex_bar(_: Request) -> Response { + fn handle_get_bar(_: Request) -> Response { unimplemented!() }; - let router = RouterBuilder::new() - .add(Route::get(r"/foo/.*?").using(handle_regex_foo)) - .add(Route::get(r"/bar/.*?").using(handle_regex_bar)) - .build(); + let router = Router::new() + .method_not_supported(handle_method_not_supported) + .add(Route::patch("/foo", handle_get_foo)) + .add(Route::patch("/bar", handle_get_bar)); - let handler = router.find_handler(&request).unwrap(); - assert!(handler as fn(_) -> _ == handle_regex_foo as fn(_) -> _); + let (handler, params) = router.find_handler(&request); + assert!(handler as fn(_) -> _ == handle_method_not_supported as fn(_) -> _); + assert_eq!(params.parameters, vec![] as Vec); } diff --git a/tests/parameters.rs b/tests/parameters.rs new file mode 100644 index 00000000..fddd2958 --- /dev/null +++ b/tests/parameters.rs @@ -0,0 +1,254 @@ +extern crate hyper; +extern crate hyper_router; + +use hyper::{Body, Method, Request, Response, Uri}; +use hyper_router::*; +use std::str::FromStr; + +#[test] +fn test_get_route_with_parametric_path() { + let request = Request::builder() + .method(Method::GET) + .uri(Uri::from_str("http://www.example.com/hello/123").unwrap()) + .body(Body::empty()) + .unwrap(); + + fn handle_get_hello(_: Request) -> Response { + unimplemented!() + }; + fn handle_get_root(_: Request) -> Response { + unimplemented!() + }; + fn handle_get_foo(_: Request) -> Response { + unimplemented!() + }; + fn handle_post_hello(_: Request) -> Response { + unimplemented!() + }; + + let router = Router::new() + .add(Route::get("/hello/:id", handle_get_hello)) + .add(Route::get("/foo/:id", handle_get_root)) + .add(Route::get("/foo", handle_get_foo)) + .add(Route::post("/hello", handle_post_hello)); + + let (handler, params) = router.find_handler(&request); + assert!(handler as fn(_) -> _ == handle_get_hello as fn(_) -> _); + assert_eq!(params.parameters, vec!["123"]); +} + +#[test] +fn test_post_route_with_parametric_path() { + let request = Request::builder() + .method(Method::POST) + .uri( + Uri::from_str("http://www.example.com/hello/5ea67884-245e-43f8-80f7-91d00971a562") + .unwrap(), + ) + .body(Body::empty()) + .unwrap(); + + fn handle_post_hello(_: Request) -> Response { + unimplemented!() + }; + fn handle_post_root(_: Request) -> Response { + unimplemented!() + }; + fn handle_post_foo(_: Request) -> Response { + unimplemented!() + }; + fn handle_get_hello(_: Request) -> Response { + unimplemented!() + }; + + let router = Router::new() + .add(Route::post("/hello/:id", handle_post_hello)) + .add(Route::get("/", handle_post_root)) + .add(Route::get("/foo", handle_post_foo)) + .add(Route::get("/hello", handle_get_hello)); + + let (handler, params) = router.find_handler(&request); + assert!(handler as fn(_) -> _ == handle_post_hello as fn(_) -> _); + assert_eq!( + params.parameters, + vec!["5ea67884-245e-43f8-80f7-91d00971a562"] + ); +} + +#[test] +fn test_delete_route_with_parametric_path() { + let request = Request::builder() + .method(Method::DELETE) + .uri(Uri::from_str("http://www.example.com/hello/my-hello").unwrap()) + .body(Body::empty()) + .unwrap(); + + fn handle_delete_hello(_: Request) -> Response { + unimplemented!() + }; + fn handle_post_hello(_: Request) -> Response { + unimplemented!() + }; + + let router = Router::new() + .add(Route::delete("/hello/:id", handle_delete_hello)) + .add(Route::post("/hello", handle_post_hello)); + + let (handler, params) = router.find_handler(&request); + assert!(handler as fn(_) -> _ == handle_delete_hello as fn(_) -> _); + assert_eq!(params.parameters, vec!["my-hello"]); +} + +#[test] +fn test_options_route_with_parametric_path() { + let request = Request::builder() + .method(Method::OPTIONS) + .uri(Uri::from_str("http://www.example.com/hello/7777").unwrap()) + .body(Body::empty()) + .unwrap(); + + fn handle_options_hello(_: Request) -> Response { + unimplemented!() + }; + fn handle_post_hello(_: Request) -> Response { + unimplemented!() + }; + + let router = Router::new() + .add(Route::options("/hello/:id", handle_options_hello)) + .add(Route::post("/hello/:id", handle_post_hello)); + + let (handler, params) = router.find_handler(&request); + assert!(handler as fn(_) -> _ == handle_options_hello as fn(_) -> _); + assert_eq!(params.parameters, vec!["7777"]); +} + +#[test] +fn test_put_route_with_parametric_path() { + let request = Request::builder() + .method(Method::PUT) + .uri(Uri::from_str("http://www.example.com/hello/deadbeef").unwrap()) + .body(Body::empty()) + .unwrap(); + + fn handle_put_hello(_: Request) -> Response { + unimplemented!() + }; + fn handle_post_hello(_: Request) -> Response { + unimplemented!() + }; + + let router = Router::new() + .add(Route::put("/hello/:id", handle_put_hello)) + .add(Route::post("/hello/:id", handle_post_hello)); + + let (handler, params) = router.find_handler(&request); + assert!(handler as fn(_) -> _ == handle_put_hello as fn(_) -> _); + assert_eq!(params.parameters, vec!["deadbeef"]); +} + +#[test] +fn test_head_route_with_parametric_path() { + let request = Request::builder() + .method(Method::HEAD) + .uri(Uri::from_str("http://www.example.com/hello/goodbye").unwrap()) + .body(Body::empty()) + .unwrap(); + + fn handle_head_hello(_: Request) -> Response { + unimplemented!() + }; + fn handle_post_hello(_: Request) -> Response { + unimplemented!() + }; + + let router = Router::new() + .add(Route::head("/hello/:id", handle_head_hello)) + .add(Route::post("/hello/:id", handle_post_hello)); + + let (handler, params) = router.find_handler(&request); + assert!(handler as fn(_) -> _ == handle_head_hello as fn(_) -> _); + assert_eq!(params.parameters, vec!["goodbye"]); +} + +#[test] +fn test_trace_route_with_parametric_path() { + let request = Request::builder() + .method(Method::TRACE) + .uri(Uri::from_str("http://www.example.com/hello/hi").unwrap()) + .body(Body::empty()) + .unwrap(); + + fn handle_trace_hello(_: Request) -> Response { + unimplemented!() + }; + fn handle_post_hello(_: Request) -> Response { + unimplemented!() + }; + + let router = Router::new() + .add(Route::trace( + "/hello/:something-very-long", + handle_trace_hello, + )) + .add(Route::post( + "/hello/:something-very-long", + handle_post_hello, + )); + + let (handler, params) = router.find_handler(&request); + assert!(handler as fn(_) -> _ == handle_trace_hello as fn(_) -> _); + assert_eq!(params.parameters, vec!["hi"]); +} + +#[test] +fn test_patch_route_with_parametric_path() { + let request = Request::builder() + .method(Method::PATCH) + .uri(Uri::from_str("http://www.example.com/hello/hello").unwrap()) + .body(Body::empty()) + .unwrap(); + + fn handle_patch_hello(_: Request) -> Response { + unimplemented!() + }; + fn handle_post_hello(_: Request) -> Response { + unimplemented!() + }; + + let router = Router::new() + .add(Route::patch("/hello/:hello", handle_patch_hello)) + .add(Route::post("/hello/:hello", handle_post_hello)); + + let (handler, params) = router.find_handler(&request); + assert!(handler as fn(_) -> _ == handle_patch_hello as fn(_) -> _); + assert_eq!(params.parameters, vec!["hello"]); +} + +#[test] +fn test_route_not_found() { + let request = Request::builder() + .method(Method::GET) + .uri(Uri::from_str("http://www.example.com/notfound/77").unwrap()) + .body(Body::empty()) + .unwrap(); + + fn handle_not_found(_: Request) -> Response { + unimplemented!() + } + fn handle_get_foo(_: Request) -> Response { + unimplemented!() + }; + fn handle_get_bar(_: Request) -> Response { + unimplemented!() + }; + + let router = Router::new() + .not_found(handle_not_found) + .add(Route::patch("/foo/:id", handle_get_foo)) + .add(Route::patch("/bar/:id", handle_get_bar)); + + let (handler, params) = router.find_handler(&request); + assert!(handler as fn(_) -> _ == handle_not_found as fn(_) -> _); + assert_eq!(params.parameters, vec![] as Vec); +} From cf3a46a46ded8f8a009cafe923de39e32d095240 Mon Sep 17 00:00:00 2001 From: Alexandros Katechis Date: Sun, 28 Jul 2019 13:36:31 -0400 Subject: [PATCH 2/7] Simplify Path::matches() method. Remove Cargo.lock since we're a library. --- .gitignore | 3 +- Cargo.lock | 751 ---------------------------------------------------- src/path.rs | 13 +- 3 files changed, 11 insertions(+), 756 deletions(-) delete mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index d91b6a27..a6a7e183 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ *.iml target/ -**/*.rs.bk \ No newline at end of file +**/*.rs.bk +Cargo.lock diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index e1663807..00000000 --- a/Cargo.lock +++ /dev/null @@ -1,751 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "arrayvec" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "autocfg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "bitflags" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "byteorder" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "bytes" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cfg-if" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-deque 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-channel" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-deque" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-utils" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fnv" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures-cpupool" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "h2" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "http" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "httparse" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "hyper" -version = "0.12.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "h2 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-threadpool 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "hyper-router" -version = "0.5.0" -dependencies = [ - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.12.24 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "indexmap" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "iovec" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "itoa" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "lazy_static" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "lazycell" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "libc" -version = "0.2.49" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "lock_api" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "log" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "memoffset" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "mio" -version = "0.6.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "miow" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "net2" -version = "0.2.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "nodrop" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "num_cpus" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "owning_ref" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "parking_lot" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "parking_lot_core" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_os 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_jitter" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_os" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "redox_syscall" -version = "0.1.51" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "scopeguard" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "slab" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "smallvec" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "stable_deref_trait" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "string" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "time" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-current-thread 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-threadpool 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-current-thread" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-executor" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-io" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-reactor" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-tcp" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-threadpool" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-deque 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-timer" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "try-lock" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "want" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[metadata] -"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" -"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" -"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" -"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" -"checksum bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "40ade3d27603c2cb345eb0912aec461a6dec7e06a4ae48589904e808335c7afa" -"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" -"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum crossbeam 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad4c7ea749d9fb09e23c5cb17e3b70650860553a0e2744e38446b1803bf7db94" -"checksum crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0f0ed1a4de2235cabda8558ff5840bffb97fcb64c97827f354a451307df5f72b" -"checksum crossbeam-deque 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "05e44b8cf3e1a625844d1750e1f7820da46044ff6d28f4d43e455ba3e5bb2c13" -"checksum crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "04c9e3102cc2d69cd681412141b390abd55a362afc1540965dad0ad4d34280b4" -"checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" -"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" -"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b" -"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" -"checksum h2 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "ddb2b25a33e231484694267af28fec74ac63b5ccf51ee2065a5e313b834d836e" -"checksum http 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "fe67e3678f2827030e89cc4b9e7ecd16d52f132c0b940ab5005f88e821500f6a" -"checksum httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83" -"checksum hyper 0.12.24 (registry+https://github.com/rust-lang/crates.io-index)" = "fdfa9b401ef6c4229745bb6e9b2529192d07b920eed624cdee2a82348cd550af" -"checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" -"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" -"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" -"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" -"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" -"checksum libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)" = "413f3dfc802c5dc91dc570b05125b6cda9855edfaa9825c9849807876376e70e" -"checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" -"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" -"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" -"checksum mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "71646331f2619b1026cc302f87a2b8b648d5c6dd6937846a16cc8ce0f347f432" -"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" -"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" -"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" -"checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba" -"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" -"checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" -"checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" -"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" -"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -"checksum rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b9ea758282efe12823e0d952ddb269d2e1897227e464919a554f2a03ef1b832" -"checksum rand_os 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b7c690732391ae0abafced5015ffb53656abfaec61b342290e5eb56b286a679d" -"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85" -"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" -"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -"checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be" -"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" -"checksum string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b639411d0b9c738748b5397d5ceba08e648f4f1992231aa859af1a017f31f60b" -"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" -"checksum tokio 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "e0500b88064f08bebddd0c0bed39e19f5c567a5f30975bee52b0c0d3e2eeb38c" -"checksum tokio-current-thread 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "331c8acc267855ec06eb0c94618dcbbfea45bed2d20b77252940095273fb58f6" -"checksum tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "30c6dbf2d1ad1de300b393910e8a3aa272b724a400b6531da03eed99e329fbf0" -"checksum tokio-io 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b53aeb9d3f5ccf2ebb29e19788f96987fa1355f8fe45ea193928eaaaf3ae820f" -"checksum tokio-reactor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "afbcdb0f0d2a1e4c440af82d7bbf0bf91a8a8c0575bcd20c05d15be7e9d3a02f" -"checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" -"checksum tokio-threadpool 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c3fd86cb15547d02daa2b21aadaf4e37dee3368df38a526178a5afa3c034d2fb" -"checksum tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2910970404ba6fa78c5539126a9ae2045d62e3713041e447f695f41405a120c6" -"checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" -"checksum want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "797464475f30ddb8830cc529aaaae648d581f99e2036a928877dfde027ddf6b3" -"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" -"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" diff --git a/src/path.rs b/src/path.rs index ed0293d7..94454981 100644 --- a/src/path.rs +++ b/src/path.rs @@ -52,9 +52,9 @@ impl Path { }; } - // We're out of segments to compare, so break. + // We're out of segments to compare, so it's a match. (None, None) => { - break; + return Some(RouteParameters::new(params)); } // We have 1 Some and 1 None, meaning the route can't be a match @@ -63,8 +63,6 @@ impl Path { } } } - - Some(RouteParameters::new(params)) } } } @@ -199,4 +197,11 @@ mod tests { assert_eq!(matches.is_none(), true); } } + + #[test] + fn test_parametric_path_with_variable_in_last_position_matches_multiple_segments_as_one() { + let path = Path::new("/files/:path"); + let matches = path.matches("/files/home/user/file.text").unwrap(); + assert_eq!(matches.parameters, vec!["home/user/file.text"]); + } } From 6ed872894448d06af7ae4f1d7b27ec2ee29e270a Mon Sep 17 00:00:00 2001 From: Alexandros Katechis Date: Mon, 29 Jul 2019 13:46:20 -0400 Subject: [PATCH 3/7] Add more comprehensive tests for Route struct. --- src/path.rs | 2 +- src/route.rs | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 223 insertions(+), 4 deletions(-) diff --git a/src/path.rs b/src/path.rs index 94454981..4f3437d2 100644 --- a/src/path.rs +++ b/src/path.rs @@ -3,7 +3,7 @@ use crate::parameters::RouteParameters; /// Represents a path in HTTP sense (starting from `/`) /// This path is internal to the crate, and encapsulates the path matching /// logic of a route. -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub(crate) enum Path { Static(String), Parametric(Vec), diff --git a/src/route.rs b/src/route.rs index 864db741..9b3d4a7e 100644 --- a/src/route.rs +++ b/src/route.rs @@ -92,13 +92,232 @@ mod tests { use super::*; use hyper::*; + fn expected_static_path() -> Path { + Path::Static("/foo".to_string()) + } + + fn expected_parametric_path() -> Path { + Path::Parametric(vec!["".to_string(), "foo".to_string(), ":id".to_string()]) + } + fn some_handler(_: Request) -> Response { unimplemented!() } #[test] - fn test_construct_get_route() { - let r = Route::get("/foo", some_handler); - assert_eq!(r.method, Method::GET); + fn test_construct_static_get_route() { + let r1 = Route::options("/foo", some_handler); + assert_eq!(r1.method, Method::OPTIONS); + assert_eq!(r1.path, expected_static_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::OPTIONS, "/foo", some_handler); + assert_eq!(r2.method, Method::OPTIONS); + assert_eq!(r2.path, expected_static_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); + } + + #[test] + fn test_construct_static_options_route() { + let r1 = Route::get("/foo", some_handler); + assert_eq!(r1.method, Method::GET); + assert_eq!(r1.path, expected_static_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::GET, "/foo", some_handler); + assert_eq!(r2.method, Method::GET); + assert_eq!(r2.path, expected_static_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); + } + + #[test] + fn test_construct_static_post_route() { + let r1 = Route::post("/foo", some_handler); + assert_eq!(r1.method, Method::POST); + assert_eq!(r1.path, expected_static_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::POST, "/foo", some_handler); + assert_eq!(r2.method, Method::POST); + assert_eq!(r2.path, expected_static_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); + } + + #[test] + fn test_construct_static_put_route() { + let r1 = Route::put("/foo", some_handler); + assert_eq!(r1.method, Method::PUT); + assert_eq!(r1.path, expected_static_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::PUT, "/foo", some_handler); + assert_eq!(r2.method, Method::PUT); + assert_eq!(r2.path, expected_static_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); + } + + #[test] + fn test_construct_static_delete_route() { + let r1 = Route::delete("/foo", some_handler); + assert_eq!(r1.method, Method::DELETE); + assert_eq!(r1.path, expected_static_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::DELETE, "/foo", some_handler); + assert_eq!(r2.method, Method::DELETE); + assert_eq!(r2.path, expected_static_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); + } + + #[test] + fn test_construct_static_head_route() { + let r1 = Route::head("/foo", some_handler); + assert_eq!(r1.method, Method::HEAD); + assert_eq!(r1.path, expected_static_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::HEAD, "/foo", some_handler); + assert_eq!(r2.method, Method::HEAD); + assert_eq!(r2.path, expected_static_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); + } + + #[test] + fn test_construct_static_trace_route() { + let r1 = Route::trace("/foo", some_handler); + assert_eq!(r1.method, Method::TRACE); + assert_eq!(r1.path, expected_static_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::TRACE, "/foo", some_handler); + assert_eq!(r2.method, Method::TRACE); + assert_eq!(r2.path, expected_static_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); + } + + #[test] + fn test_construct_static_connect_route() { + let r1 = Route::connect("/foo", some_handler); + assert_eq!(r1.method, Method::CONNECT); + assert_eq!(r1.path, expected_static_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::CONNECT, "/foo", some_handler); + assert_eq!(r2.method, Method::CONNECT); + assert_eq!(r2.path, expected_static_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); + } + + #[test] + fn test_construct_static_patch_route() { + let r1 = Route::patch("/foo", some_handler); + assert_eq!(r1.method, Method::PATCH); + assert_eq!(r1.path, expected_static_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::PATCH, "/foo", some_handler); + assert_eq!(r2.method, Method::PATCH); + assert_eq!(r2.path, expected_static_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); } + + #[test] + fn test_construct_parametric_get_route() { + let r1 = Route::options("/foo/:id", some_handler); + assert_eq!(r1.method, Method::OPTIONS); + assert_eq!(r1.path, expected_parametric_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::OPTIONS, "/foo/:id", some_handler); + assert_eq!(r2.method, Method::OPTIONS); + assert_eq!(r2.path, expected_parametric_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); + } + + #[test] + fn test_construct_parametric_options_route() { + let r1 = Route::get("/foo/:id", some_handler); + assert_eq!(r1.method, Method::GET); + assert_eq!(r1.path, expected_parametric_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::GET, "/foo/:id", some_handler); + assert_eq!(r2.method, Method::GET); + assert_eq!(r2.path, expected_parametric_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); + } + + #[test] + fn test_construct_parametric_post_route() { + let r1 = Route::post("/foo/:id", some_handler); + assert_eq!(r1.method, Method::POST); + assert_eq!(r1.path, expected_parametric_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::POST, "/foo/:id", some_handler); + assert_eq!(r2.method, Method::POST); + assert_eq!(r2.path, expected_parametric_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); + } + + #[test] + fn test_construct_parametric_put_route() { + let r1 = Route::put("/foo/:id", some_handler); + assert_eq!(r1.method, Method::PUT); + assert_eq!(r1.path, expected_parametric_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::PUT, "/foo/:id", some_handler); + assert_eq!(r2.method, Method::PUT); + assert_eq!(r2.path, expected_parametric_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); + } + + #[test] + fn test_construct_parametric_delete_route() { + let r1 = Route::delete("/foo/:id", some_handler); + assert_eq!(r1.method, Method::DELETE); + assert_eq!(r1.path, expected_parametric_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::DELETE, "/foo/:id", some_handler); + assert_eq!(r2.method, Method::DELETE); + assert_eq!(r2.path, expected_parametric_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); + } + + #[test] + fn test_construct_parametric_head_route() { + let r1 = Route::head("/foo/:id", some_handler); + assert_eq!(r1.method, Method::HEAD); + assert_eq!(r1.path, expected_parametric_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::HEAD, "/foo/:id", some_handler); + assert_eq!(r2.method, Method::HEAD); + assert_eq!(r2.path, expected_parametric_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); + } + + #[test] + fn test_construct_parametric_trace_route() { + let r1 = Route::trace("/foo/:id", some_handler); + assert_eq!(r1.method, Method::TRACE); + assert_eq!(r1.path, expected_parametric_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::TRACE, "/foo/:id", some_handler); + assert_eq!(r2.method, Method::TRACE); + assert_eq!(r2.path, expected_parametric_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); + } + + #[test] + fn test_construct_parametric_connect_route() { + let r1 = Route::connect("/foo/:id", some_handler); + assert_eq!(r1.method, Method::CONNECT); + assert_eq!(r1.path, expected_parametric_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::CONNECT, "/foo/:id", some_handler); + assert_eq!(r2.method, Method::CONNECT); + assert_eq!(r2.path, expected_parametric_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); + } + + #[test] + fn test_construct_parametric_patch_route() { + let r1 = Route::patch("/foo/:id", some_handler); + assert_eq!(r1.method, Method::PATCH); + assert_eq!(r1.path, expected_parametric_path()); + assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); + let r2 = Route::from(Method::PATCH, "/foo/:id", some_handler); + assert_eq!(r2.method, Method::PATCH); + assert_eq!(r2.path, expected_parametric_path()); + assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); + } + } From 1db85ce2b470d9f57c0a92d9ef33ce698d4fe3e5 Mon Sep 17 00:00:00 2001 From: Alexandros Katechis Date: Mon, 29 Jul 2019 13:53:41 -0400 Subject: [PATCH 4/7] Remove derived PartialEq impl for Path, and move it just to the test module that needs it. --- src/path.rs | 2 +- src/route.rs | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/path.rs b/src/path.rs index 4f3437d2..94454981 100644 --- a/src/path.rs +++ b/src/path.rs @@ -3,7 +3,7 @@ use crate::parameters::RouteParameters; /// Represents a path in HTTP sense (starting from `/`) /// This path is internal to the crate, and encapsulates the path matching /// logic of a route. -#[derive(Debug, PartialEq)] +#[derive(Debug)] pub(crate) enum Path { Static(String), Parametric(Vec), diff --git a/src/route.rs b/src/route.rs index 9b3d4a7e..aca4ea7b 100644 --- a/src/route.rs +++ b/src/route.rs @@ -92,6 +92,28 @@ mod tests { use super::*; use hyper::*; + impl PartialEq for Path { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Path::Static(self_str), Path::Static(other_str)) => { + return self_str == other_str; + } + (Path::Parametric(self_vec), Path::Parametric(other_vec)) => { + if self_vec.len() != other_vec.len() { + return false; + } + for (a, b) in self_vec.iter().zip(other_vec.iter()) { + if a != b { + return false; + } + } + true + } + _ => false, + } + } + } + fn expected_static_path() -> Path { Path::Static("/foo".to_string()) } From 5be5c4f3e7d6f18449c4fca9c4f76bcca8a1f2cf Mon Sep 17 00:00:00 2001 From: Alexandros Katechis Date: Sat, 3 Aug 2019 23:19:23 -0400 Subject: [PATCH 5/7] Revert API revisions. Add benchmark for parameter capturing --- Cargo.toml | 7 ++ benches/parameter_capture.rs | 45 ++++++++++ src/lib.rs | 16 ++-- src/parameters.rs | 53 ++++++++++- src/path.rs | 106 ++++++++++++++++------ src/route.rs | 165 ++++++++++++++++++----------------- src/router.rs | 83 ++++++++++++++++-- test-server/main.rs | 13 +-- tests/basic.rs | 130 ++++++++++++++++----------- tests/parameters.rs | 145 +++++++++++++++++++----------- 10 files changed, 528 insertions(+), 235 deletions(-) create mode 100644 benches/parameter_capture.rs diff --git a/Cargo.toml b/Cargo.toml index 2ffff273..52a570b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,10 @@ path = "src/lib.rs" [dependencies] futures = "^0.1" hyper = "^0.12" + +[dev-dependencies] +criterion = "0.2" + +[[bench]] +name = "parameter_capture" +harness = false diff --git a/benches/parameter_capture.rs b/benches/parameter_capture.rs new file mode 100644 index 00000000..c0b3f2b1 --- /dev/null +++ b/benches/parameter_capture.rs @@ -0,0 +1,45 @@ +#[macro_use] +extern crate criterion; + +use criterion::Criterion; +use hyper::*; +use hyper_router::*; +use std::str::FromStr; + +fn handler(_: Request) -> Response { + unimplemented!() +} + +fn create_router() -> Router { + RouterBuilder::new() + .add(Route::get("/a/:id").using(handler)) + .add(Route::get("/b/:id").using(handler)) + .add(Route::get("/c/:id").using(handler)) + .add(Route::get("/d/:id").using(handler)) + .add(Route::get("/e/:id").using(handler)) + .add(Route::get("/f/:id").using(handler)) + .add(Route::get("/a/:b/:c/:d/:e/:f/:g/:h/:i/:g/:h/:i/:j/k/l/:m").using(handler)) + .add(Route::get("/g/:id").using(handler)) + .add(Route::get("/h/:id").using(handler)) + .add(Route::get("/a/:id").using(handler)) + .build() +} + +fn run_benchmark(c: &mut Criterion) { + c.bench_function("capture many parameters", |bench| { + let router = create_router(); + bench.iter(move || { + let url = "http://a.com/a/b/c/d/e/f/g/h/i/g/h/i/j/k/l/m"; + let req_uri = Uri::from_str(url).unwrap(); + let request = Request::builder() + .method(Method::GET) + .uri(req_uri) + .body(Body::empty()) + .unwrap(); + router.find_handler(&request) + }) + }); +} + +criterion_group!(benches, run_benchmark); +criterion_main!(benches); diff --git a/src/lib.rs b/src/lib.rs index 2105f149..5d0a70a1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,13 +8,13 @@ use hyper::{Body, Request, Response}; pub mod handlers; mod parameters; mod path; -mod route; +pub mod route; mod router; pub use self::parameters::RouteParameters; use self::path::Path; -pub use self::route::Route; -pub use self::router::Router; +pub use self::route::{Route, RouteBuilder}; +pub use self::router::{Router, RouterBuilder}; pub type Handler = fn(Request) -> Response; pub type HttpResult = Result; @@ -65,7 +65,9 @@ mod tests { .expect("Failed to construct the response") } - let router = Router::new().add(Route::get("/foo", get_foo_handler)); + let router = RouterBuilder::new() + .add(Route::get("/foo").using(get_foo_handler)) + .build(); let mut svc = RouterService::new(router); @@ -91,7 +93,7 @@ mod tests { fn test_router_service_passes_captured_parameters_to_handler_via_extensions() { fn get_foo_handler(req: Request) -> Response { let route_params: &RouteParameters = req.extensions().get().unwrap(); - let id = route_params.parameters.iter().nth(0).unwrap(); + let id = route_params.get("id").unwrap(); let body = format!("FOO-{}", id); Response::builder() .header(CONTENT_LENGTH, body.len() as u64) @@ -100,7 +102,9 @@ mod tests { .expect("Failed to construct the response") } - let router = Router::new().add(Route::get("/foo/:id", get_foo_handler)); + let router = RouterBuilder::new() + .add(Route::get("/foo/:id").using(get_foo_handler)) + .build(); let mut svc = RouterService::new(router); let uri = Uri::from_str("http://www.example.com/foo/75bd8cfc-e421-48f8-93a1-57f423d254e6"); diff --git a/src/parameters.rs b/src/parameters.rs index db492889..42806f68 100644 --- a/src/parameters.rs +++ b/src/parameters.rs @@ -1,9 +1,56 @@ +use std::collections::HashMap; + pub struct RouteParameters { - pub parameters: Vec, + params: HashMap, } impl RouteParameters { - pub fn new(params: Vec) -> RouteParameters { - RouteParameters { parameters: params } + pub(crate) fn none() -> RouteParameters { + RouteParameters::with_capacity(0) + } + pub(crate) fn with_capacity(capacity: usize) -> RouteParameters { + let map = HashMap::with_capacity(capacity); + RouteParameters::new(map) + } + pub fn new(params: HashMap) -> RouteParameters { + RouteParameters { params } + } + pub fn len(&self) -> usize { + self.params.len() + } + pub fn get(&self, param_name: &str) -> Option<&String> { + self.params.get(param_name) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_creating_route_parameters() { + RouteParameters::none(); + + let params = HashMap::new(); + RouteParameters::new(params); + } + + #[test] + fn test_length_of_parameters() { + let mut map = HashMap::with_capacity(2); + map.insert("foo".to_string(), "bar".to_string()); + map.insert("hello".to_string(), "world".to_string()); + let params = RouteParameters::new(map); + assert_eq!(params.len(), 2); + } + + #[test] + fn test_get_parameter() { + let mut map = HashMap::with_capacity(2); + map.insert("foo".to_string(), "bar".to_string()); + map.insert("hello".to_string(), "world".to_string()); + let params = RouteParameters::new(map); + assert_eq!(params.get("foo").unwrap(), &"bar".to_string()); + assert_eq!(params.get("hello").unwrap(), &"world".to_string()); } } diff --git a/src/path.rs b/src/path.rs index 94454981..033305b8 100644 --- a/src/path.rs +++ b/src/path.rs @@ -1,4 +1,5 @@ use crate::parameters::RouteParameters; +use std::collections::HashMap; /// Represents a path in HTTP sense (starting from `/`) /// This path is internal to the crate, and encapsulates the path matching @@ -22,13 +23,13 @@ impl Path { match self { Path::Static(me) => { if me == other_path { - Some(RouteParameters::new(vec![])) + Some(RouteParameters::none()) } else { None } } Path::Parametric(self_path) => { - let mut params = vec![]; + let mut params = HashMap::new(); let mut self_segments = self_path.iter(); let mut other_segments = other_path.split('/'); @@ -39,10 +40,12 @@ impl Path { match (self_seg, other_seg) { // We have two segments to compare. (Some(left), Some(right)) => { - let first_self_char = left.chars().nth(0); + let mut self_chars = left.chars(); + let first_self_char = self_chars.nth(0); match first_self_char { Some(ch) if ch == ':' => { - params.push(right.to_string()); + let key: String = self_chars.collect(); + params.insert(key, right.to_string()); } _ => { if left != right { @@ -77,6 +80,31 @@ fn is_parametric_path(path: &str) -> bool { return false; } +// We only impl PartialEq for Path when we're compiling in test configuration so +// we can use assert_eq!(path_1, path_2) +#[cfg(test)] +impl PartialEq for Path { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Path::Static(self_str), Path::Static(other_str)) => { + return self_str == other_str; + } + (Path::Parametric(self_vec), Path::Parametric(other_vec)) => { + if self_vec.len() != other_vec.len() { + return false; + } + for (a, b) in self_vec.iter().zip(other_vec.iter()) { + if a != b { + return false; + } + } + true + } + _ => false, + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -103,10 +131,8 @@ mod tests { assert!(matches.is_none()); } { - let matches = path.matches("/foo/bar"); - let empty_vec: Vec = vec![]; - assert!(matches.is_some()); - assert_eq!(matches.unwrap().parameters, empty_vec); + let matches = path.matches("/foo/bar").unwrap(); + assert_eq!(matches.len(), 0); } { let matches = path.matches("/foo/bar/baz"); @@ -122,16 +148,19 @@ mod tests { assert!(matches.is_none()); } { - let matches = path.matches("/foo/bar"); - assert!(matches.is_some()); - assert_eq!(matches.unwrap().parameters, vec!["bar"]); + let params = path.matches("/foo/bar").unwrap(); + assert_eq!( + params.get("foooooooooooooooooooooooooooooid").unwrap(), + &"bar".to_string() + ); } { - let matches = path.matches("/foo/barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr"); - assert!(matches.is_some()); + let params = path + .matches("/foo/barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr") + .unwrap(); assert_eq!( - matches.unwrap().parameters, - vec!["barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr"] + params.get("foooooooooooooooooooooooooooooid").unwrap(), + &"barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr".to_string() ); } { @@ -148,16 +177,16 @@ mod tests { assert!(matches.is_none()); } { - let matches = path.matches("/foo/bar"); - assert!(matches.is_some()); - assert_eq!(matches.unwrap().parameters, vec!["bar"]); + let params = path.matches("/foo/bar").unwrap(); + assert_eq!(params.get("a").unwrap(), &"bar".to_string()); } { - let matches = path.matches("/foo/barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr"); - assert!(matches.is_some()); + let params = path + .matches("/foo/barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr") + .unwrap(); assert_eq!( - matches.unwrap().parameters, - vec!["barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr"] + params.get("a").unwrap(), + &"barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr".to_string() ); } { @@ -169,8 +198,10 @@ mod tests { #[test] fn test_parametric_path_with_multiple_variables_matches_path() { let path = Path::new("/users/:user_id/friend/:friend_id/group/:group_id"); - let matches = path.matches("/users/1/friend/2/group/77").unwrap(); - assert_eq!(matches.parameters, vec!["1", "2", "77"]); + let params = path.matches("/users/1/friend/2/group/77").unwrap(); + assert_eq!(params.get("user_id").unwrap(), &"1".to_string()); + assert_eq!(params.get("friend_id").unwrap(), &"2".to_string()); + assert_eq!(params.get("group_id").unwrap(), &"77".to_string()); } #[test] @@ -178,11 +209,21 @@ mod tests { let path = Path::new("/:one/:two/:three"); { let matches = path.matches("/1/2/3").unwrap(); - assert_eq!(matches.parameters, vec!["1", "2", "3"]); + assert_eq!(matches.get("one").unwrap(), &"1".to_string()); + assert_eq!(matches.get("two").unwrap(), &"2".to_string()); + assert_eq!(matches.get("three").unwrap(), &"3".to_string()); } { let matches = path.matches("/hello/howdie/hey").unwrap(); - assert_eq!(matches.parameters, vec!["hello", "howdie", "hey"]); + assert_eq!(matches.get("one").unwrap(), &"hello".to_string()); + assert_eq!(matches.get("two").unwrap(), &"howdie".to_string()); + assert_eq!(matches.get("three").unwrap(), &"hey".to_string()); + } + { + let matches = path.matches("/hello/hello/hello").unwrap(); + assert_eq!(matches.get("one").unwrap(), &"hello".to_string()); + assert_eq!(matches.get("two").unwrap(), &"hello".to_string()); + assert_eq!(matches.get("three").unwrap(), &"hello".to_string()); } { let matches = path.matches("/hello"); @@ -199,9 +240,16 @@ mod tests { } #[test] - fn test_parametric_path_with_variable_in_last_position_matches_multiple_segments_as_one() { + fn test_parametric_path_with_variable_in_last_position_doesnt_glob_match() { let path = Path::new("/files/:path"); - let matches = path.matches("/files/home/user/file.text").unwrap(); - assert_eq!(matches.parameters, vec!["home/user/file.text"]); + let matches = path.matches("/files/home/user/file.text"); + assert_eq!(matches.is_none(), true); + } + + #[test] + fn test_parametric_path_with_variable_in_first_position_doesnt_glob_match() { + let path = Path::new("/:path"); + let matches = path.matches("/home/user/file.text"); + assert_eq!(matches.is_none(), true); } } diff --git a/src/route.rs b/src/route.rs index aca4ea7b..f6454114 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,3 +1,4 @@ +use crate::handlers; use crate::Handler; use crate::Path; use hyper::Method; @@ -32,47 +33,57 @@ pub struct Route { } impl Route { - pub fn options(path: &str, handler: Handler) -> Route { - Route::from(Method::OPTIONS, path, handler) + pub fn options(path: &str) -> RouteBuilder { + Route::from(Method::OPTIONS, path) } - pub fn get(path: &str, handler: Handler) -> Route { - Route::from(Method::GET, path, handler) + pub fn get(path: &str) -> RouteBuilder { + Route::from(Method::GET, path) } - pub fn post(path: &str, handler: Handler) -> Route { - Route::from(Method::POST, path, handler) + pub fn post(path: &str) -> RouteBuilder { + Route::from(Method::POST, path) } - pub fn put(path: &str, handler: Handler) -> Route { - Route::from(Method::PUT, path, handler) + pub fn put(path: &str) -> RouteBuilder { + Route::from(Method::PUT, path) } - pub fn delete(path: &str, handler: Handler) -> Route { - Route::from(Method::DELETE, path, handler) + pub fn delete(path: &str) -> RouteBuilder { + Route::from(Method::DELETE, path) } - pub fn head(path: &str, handler: Handler) -> Route { - Route::from(Method::HEAD, path, handler) + pub fn head(path: &str) -> RouteBuilder { + Route::from(Method::HEAD, path) } - pub fn trace(path: &str, handler: Handler) -> Route { - Route::from(Method::TRACE, path, handler) + pub fn trace(path: &str) -> RouteBuilder { + Route::from(Method::TRACE, path) } - pub fn connect(path: &str, handler: Handler) -> Route { - Route::from(Method::CONNECT, path, handler) + pub fn connect(path: &str) -> RouteBuilder { + Route::from(Method::CONNECT, path) } - pub fn patch(path: &str, handler: Handler) -> Route { - Route::from(Method::PATCH, path, handler) + pub fn patch(path: &str) -> RouteBuilder { + Route::from(Method::PATCH, path) } - pub fn from(method: Method, path: &str, handler: Handler) -> Route { - Route { + pub fn from(method: Method, path: &str) -> RouteBuilder { + RouteBuilder::new(Route { method, path: Path::new(path), - handler: handler, + ..Route::default() + }) + } +} + +impl Default for Route { + fn default() -> Route { + Route { + method: Method::GET, + path: Path::new("/"), + handler: handlers::not_implemented_handler, } } } @@ -87,33 +98,29 @@ impl fmt::Debug for Route { } } +pub struct RouteBuilder { + route: Route, +} + +impl RouteBuilder { + pub fn new(route: Route) -> RouteBuilder { + RouteBuilder { route } + } + + /// Completes the building process by taking the handler to process the request. + /// + /// Returns created route. + pub fn using(mut self, handler: Handler) -> Route { + self.route.handler = handler; + self.route + } +} + #[cfg(test)] mod tests { use super::*; use hyper::*; - impl PartialEq for Path { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Path::Static(self_str), Path::Static(other_str)) => { - return self_str == other_str; - } - (Path::Parametric(self_vec), Path::Parametric(other_vec)) => { - if self_vec.len() != other_vec.len() { - return false; - } - for (a, b) in self_vec.iter().zip(other_vec.iter()) { - if a != b { - return false; - } - } - true - } - _ => false, - } - } - } - fn expected_static_path() -> Path { Path::Static("/foo".to_string()) } @@ -128,11 +135,11 @@ mod tests { #[test] fn test_construct_static_get_route() { - let r1 = Route::options("/foo", some_handler); + let r1 = Route::options("/foo").using(some_handler); assert_eq!(r1.method, Method::OPTIONS); assert_eq!(r1.path, expected_static_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::OPTIONS, "/foo", some_handler); + let r2 = Route::from(Method::OPTIONS, "/foo").using(some_handler); assert_eq!(r2.method, Method::OPTIONS); assert_eq!(r2.path, expected_static_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); @@ -140,11 +147,11 @@ mod tests { #[test] fn test_construct_static_options_route() { - let r1 = Route::get("/foo", some_handler); + let r1 = Route::get("/foo").using(some_handler); assert_eq!(r1.method, Method::GET); assert_eq!(r1.path, expected_static_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::GET, "/foo", some_handler); + let r2 = Route::from(Method::GET, "/foo").using(some_handler); assert_eq!(r2.method, Method::GET); assert_eq!(r2.path, expected_static_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); @@ -152,11 +159,11 @@ mod tests { #[test] fn test_construct_static_post_route() { - let r1 = Route::post("/foo", some_handler); + let r1 = Route::post("/foo").using(some_handler); assert_eq!(r1.method, Method::POST); assert_eq!(r1.path, expected_static_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::POST, "/foo", some_handler); + let r2 = Route::from(Method::POST, "/foo").using(some_handler); assert_eq!(r2.method, Method::POST); assert_eq!(r2.path, expected_static_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); @@ -164,11 +171,11 @@ mod tests { #[test] fn test_construct_static_put_route() { - let r1 = Route::put("/foo", some_handler); + let r1 = Route::put("/foo").using(some_handler); assert_eq!(r1.method, Method::PUT); assert_eq!(r1.path, expected_static_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::PUT, "/foo", some_handler); + let r2 = Route::from(Method::PUT, "/foo").using(some_handler); assert_eq!(r2.method, Method::PUT); assert_eq!(r2.path, expected_static_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); @@ -176,11 +183,11 @@ mod tests { #[test] fn test_construct_static_delete_route() { - let r1 = Route::delete("/foo", some_handler); + let r1 = Route::delete("/foo").using(some_handler); assert_eq!(r1.method, Method::DELETE); assert_eq!(r1.path, expected_static_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::DELETE, "/foo", some_handler); + let r2 = Route::from(Method::DELETE, "/foo").using(some_handler); assert_eq!(r2.method, Method::DELETE); assert_eq!(r2.path, expected_static_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); @@ -188,11 +195,11 @@ mod tests { #[test] fn test_construct_static_head_route() { - let r1 = Route::head("/foo", some_handler); + let r1 = Route::head("/foo").using(some_handler); assert_eq!(r1.method, Method::HEAD); assert_eq!(r1.path, expected_static_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::HEAD, "/foo", some_handler); + let r2 = Route::from(Method::HEAD, "/foo").using(some_handler); assert_eq!(r2.method, Method::HEAD); assert_eq!(r2.path, expected_static_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); @@ -200,11 +207,11 @@ mod tests { #[test] fn test_construct_static_trace_route() { - let r1 = Route::trace("/foo", some_handler); + let r1 = Route::trace("/foo").using(some_handler); assert_eq!(r1.method, Method::TRACE); assert_eq!(r1.path, expected_static_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::TRACE, "/foo", some_handler); + let r2 = Route::from(Method::TRACE, "/foo").using(some_handler); assert_eq!(r2.method, Method::TRACE); assert_eq!(r2.path, expected_static_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); @@ -212,11 +219,11 @@ mod tests { #[test] fn test_construct_static_connect_route() { - let r1 = Route::connect("/foo", some_handler); + let r1 = Route::connect("/foo").using(some_handler); assert_eq!(r1.method, Method::CONNECT); assert_eq!(r1.path, expected_static_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::CONNECT, "/foo", some_handler); + let r2 = Route::from(Method::CONNECT, "/foo").using(some_handler); assert_eq!(r2.method, Method::CONNECT); assert_eq!(r2.path, expected_static_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); @@ -224,11 +231,11 @@ mod tests { #[test] fn test_construct_static_patch_route() { - let r1 = Route::patch("/foo", some_handler); + let r1 = Route::patch("/foo").using(some_handler); assert_eq!(r1.method, Method::PATCH); assert_eq!(r1.path, expected_static_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::PATCH, "/foo", some_handler); + let r2 = Route::from(Method::PATCH, "/foo").using(some_handler); assert_eq!(r2.method, Method::PATCH); assert_eq!(r2.path, expected_static_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); @@ -236,11 +243,11 @@ mod tests { #[test] fn test_construct_parametric_get_route() { - let r1 = Route::options("/foo/:id", some_handler); + let r1 = Route::options("/foo/:id").using(some_handler); assert_eq!(r1.method, Method::OPTIONS); assert_eq!(r1.path, expected_parametric_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::OPTIONS, "/foo/:id", some_handler); + let r2 = Route::from(Method::OPTIONS, "/foo/:id").using(some_handler); assert_eq!(r2.method, Method::OPTIONS); assert_eq!(r2.path, expected_parametric_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); @@ -248,11 +255,11 @@ mod tests { #[test] fn test_construct_parametric_options_route() { - let r1 = Route::get("/foo/:id", some_handler); + let r1 = Route::get("/foo/:id").using(some_handler); assert_eq!(r1.method, Method::GET); assert_eq!(r1.path, expected_parametric_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::GET, "/foo/:id", some_handler); + let r2 = Route::from(Method::GET, "/foo/:id").using(some_handler); assert_eq!(r2.method, Method::GET); assert_eq!(r2.path, expected_parametric_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); @@ -260,11 +267,11 @@ mod tests { #[test] fn test_construct_parametric_post_route() { - let r1 = Route::post("/foo/:id", some_handler); + let r1 = Route::post("/foo/:id").using(some_handler); assert_eq!(r1.method, Method::POST); assert_eq!(r1.path, expected_parametric_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::POST, "/foo/:id", some_handler); + let r2 = Route::from(Method::POST, "/foo/:id").using(some_handler); assert_eq!(r2.method, Method::POST); assert_eq!(r2.path, expected_parametric_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); @@ -272,11 +279,11 @@ mod tests { #[test] fn test_construct_parametric_put_route() { - let r1 = Route::put("/foo/:id", some_handler); + let r1 = Route::put("/foo/:id").using(some_handler); assert_eq!(r1.method, Method::PUT); assert_eq!(r1.path, expected_parametric_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::PUT, "/foo/:id", some_handler); + let r2 = Route::from(Method::PUT, "/foo/:id").using(some_handler); assert_eq!(r2.method, Method::PUT); assert_eq!(r2.path, expected_parametric_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); @@ -284,11 +291,11 @@ mod tests { #[test] fn test_construct_parametric_delete_route() { - let r1 = Route::delete("/foo/:id", some_handler); + let r1 = Route::delete("/foo/:id").using(some_handler); assert_eq!(r1.method, Method::DELETE); assert_eq!(r1.path, expected_parametric_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::DELETE, "/foo/:id", some_handler); + let r2 = Route::from(Method::DELETE, "/foo/:id").using(some_handler); assert_eq!(r2.method, Method::DELETE); assert_eq!(r2.path, expected_parametric_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); @@ -296,11 +303,11 @@ mod tests { #[test] fn test_construct_parametric_head_route() { - let r1 = Route::head("/foo/:id", some_handler); + let r1 = Route::head("/foo/:id").using(some_handler); assert_eq!(r1.method, Method::HEAD); assert_eq!(r1.path, expected_parametric_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::HEAD, "/foo/:id", some_handler); + let r2 = Route::from(Method::HEAD, "/foo/:id").using(some_handler); assert_eq!(r2.method, Method::HEAD); assert_eq!(r2.path, expected_parametric_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); @@ -308,11 +315,11 @@ mod tests { #[test] fn test_construct_parametric_trace_route() { - let r1 = Route::trace("/foo/:id", some_handler); + let r1 = Route::trace("/foo/:id").using(some_handler); assert_eq!(r1.method, Method::TRACE); assert_eq!(r1.path, expected_parametric_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::TRACE, "/foo/:id", some_handler); + let r2 = Route::from(Method::TRACE, "/foo/:id").using(some_handler); assert_eq!(r2.method, Method::TRACE); assert_eq!(r2.path, expected_parametric_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); @@ -320,11 +327,11 @@ mod tests { #[test] fn test_construct_parametric_connect_route() { - let r1 = Route::connect("/foo/:id", some_handler); + let r1 = Route::connect("/foo/:id").using(some_handler); assert_eq!(r1.method, Method::CONNECT); assert_eq!(r1.path, expected_parametric_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::CONNECT, "/foo/:id", some_handler); + let r2 = Route::from(Method::CONNECT, "/foo/:id").using(some_handler); assert_eq!(r2.method, Method::CONNECT); assert_eq!(r2.path, expected_parametric_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); @@ -332,11 +339,11 @@ mod tests { #[test] fn test_construct_parametric_patch_route() { - let r1 = Route::patch("/foo/:id", some_handler); + let r1 = Route::patch("/foo/:id").using(some_handler); assert_eq!(r1.method, Method::PATCH); assert_eq!(r1.path, expected_parametric_path()); assert_eq!(r1.handler as fn(_) -> _, some_handler as fn(_) -> _); - let r2 = Route::from(Method::PATCH, "/foo/:id", some_handler); + let r2 = Route::from(Method::PATCH, "/foo/:id").using(some_handler); assert_eq!(r2.method, Method::PATCH); assert_eq!(r2.path, expected_parametric_path()); assert_eq!(r2.handler as fn(_) -> _, some_handler as fn(_) -> _); diff --git a/src/router.rs b/src/router.rs index eaebbf09..5b7e49b5 100644 --- a/src/router.rs +++ b/src/router.rs @@ -13,30 +13,35 @@ pub struct Router { } impl Router { - pub fn new() -> Router { + pub fn empty() -> Self { + Router::new(vec![]) + } + + pub fn new(routes: Vec) -> Router { Router { - routes: vec![], + routes, not_found_handler: handlers::default_404_handler, method_not_supported_handler: handlers::method_not_supported_handler, } } - pub fn not_found(mut self, handler: Handler) -> Router { + pub fn not_found(mut self, handler: Handler) -> Self { self.not_found_handler = handler; self } - pub fn method_not_supported(mut self, handler: Handler) -> Router { + pub fn method_not_supported(mut self, handler: Handler) -> Self { self.method_not_supported_handler = handler; self } - pub fn add(mut self, route: Route) -> Router { + pub fn add(mut self, route: Route) -> Self { self.routes.push(route); self } pub fn find_handler(&self, request: &Request) -> (Handler, RouteParameters) { + let mut path_match = None; let req_path = request.uri().path(); let req_method = request.method(); for route in &self.routes { @@ -45,10 +50,74 @@ impl Router { if route.method == req_method { return (route.handler, matches.unwrap()); } else { - return (self.method_not_supported_handler, matches.unwrap()); + path_match = matches; } } } - (self.not_found_handler, RouteParameters::new(vec![])) + // At this point, no route matched both path AND method. + // if path_match is true, we return method_not_supported, otherwise, + // not found + return if path_match.is_some() { + (self.method_not_supported_handler, path_match.unwrap()) + } else { + (self.not_found_handler, RouteParameters::none()) + }; + } +} + +/// Builder for a router +/// +/// Example usage: +/// +#[derive(Debug)] +pub struct RouterBuilder { + routes: Vec, + not_found_handler: Handler, + method_not_supported_handler: Handler, +} + +impl RouterBuilder { + pub fn new() -> Self { + RouterBuilder { + routes: vec![], + not_found_handler: handlers::default_404_handler, + method_not_supported_handler: handlers::method_not_supported_handler, + } + } + + /// Adds new `Route` for `Router` that is being built. + /// + /// Example: + /// + /// ```ignore + /// use hyper::server::{Request, Response}; + /// use hyper_router::{Route, RouterBuilder}; + /// + /// fn some_handler(_: Request) -> Response { + /// // do something + /// } + /// + /// RouterBuilder::new().add(Route::get("/person/:id").using(some_handler)); + /// ``` + #[allow(clippy::should_implement_trait)] + pub fn add(mut self, route: Route) -> Self { + self.routes.push(route); + self + } + + pub fn not_found(mut self, handler: Handler) -> Self { + self.not_found_handler = handler; + self + } + + pub fn method_not_supported(mut self, handler: Handler) -> Self { + self.method_not_supported_handler = handler; + self + } + + pub fn build(self) -> Router { + Router::new(self.routes) + .method_not_supported(self.method_not_supported_handler) + .not_found(self.not_found_handler) } } diff --git a/test-server/main.rs b/test-server/main.rs index c921263b..35100272 100644 --- a/test-server/main.rs +++ b/test-server/main.rs @@ -5,7 +5,7 @@ use hyper::header::{CONTENT_LENGTH, CONTENT_TYPE}; use hyper::rt::Future; use hyper::server::Server; use hyper::{Body, Method, Request, Response}; -use hyper_router::{Route, RouteParameters, Router, RouterService}; +use hyper_router::{Route, RouteParameters, RouterBuilder, RouterService}; fn request_handler(_: Request) -> Response { let body = "Hello World"; @@ -18,7 +18,7 @@ fn request_handler(_: Request) -> Response { fn greeting_handler(req: Request) -> Response { let params: &RouteParameters = req.extensions().get().unwrap(); - let name = params.parameters.get(0).unwrap(); + let name = params.get("name").unwrap(); let body = format!("Hello, {}!", name); Response::builder() .header(CONTENT_LENGTH, body.len() as u64) @@ -28,10 +28,11 @@ fn greeting_handler(req: Request) -> Response { } fn router_service() -> Result { - let router = Router::new() - .add(Route::get("/hello", request_handler)) - .add(Route::get("/greeting/:name", greeting_handler)) - .add(Route::from(Method::PATCH, "/world", request_handler)); + let router = RouterBuilder::new() + .add(Route::get("/hello").using(request_handler)) + .add(Route::get("/greeting/:name").using(greeting_handler)) + .add(Route::from(Method::PATCH, "/world").using(request_handler)) + .build(); Ok(RouterService::new(router)) } diff --git a/tests/basic.rs b/tests/basic.rs index d1344da8..abc3dbd4 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -26,15 +26,16 @@ fn test_get_route_with_static_path() { unimplemented!() }; - let router = Router::new() - .add(Route::get("/hello", handle_get_hello)) - .add(Route::get("/", handle_get_root)) - .add(Route::get("/foo", handle_get_foo)) - .add(Route::post("/hello", handle_post_hello)); + let router = RouterBuilder::new() + .add(Route::get("/hello").using(handle_get_hello)) + .add(Route::get("/").using(handle_get_root)) + .add(Route::get("/foo").using(handle_get_foo)) + .add(Route::post("/hello").using(handle_post_hello)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_get_hello as fn(_) -> _); - assert_eq!(params.parameters, vec![] as Vec); + assert_eq!(params.len(), 0); } #[test] @@ -58,15 +59,16 @@ fn test_post_route_with_static_path() { unimplemented!() }; - let router = Router::new() - .add(Route::post("/hello", handle_post_hello)) - .add(Route::get("/", handle_post_root)) - .add(Route::get("/foo", handle_post_foo)) - .add(Route::get("/hello", handle_get_hello)); + let router = RouterBuilder::new() + .add(Route::post("/hello").using(handle_post_hello)) + .add(Route::get("/").using(handle_post_root)) + .add(Route::get("/foo").using(handle_post_foo)) + .add(Route::get("/hello").using(handle_get_hello)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_post_hello as fn(_) -> _); - assert_eq!(params.parameters, vec![] as Vec); + assert_eq!(params.len(), 0); } #[test] @@ -84,13 +86,14 @@ fn test_delete_route_with_static_path() { unimplemented!() }; - let router = Router::new() - .add(Route::delete("/hello", handle_delete_hello)) - .add(Route::post("/hello", handle_post_hello)); + let router = RouterBuilder::new() + .add(Route::delete("/hello").using(handle_delete_hello)) + .add(Route::post("/hello").using(handle_post_hello)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_delete_hello as fn(_) -> _); - assert_eq!(params.parameters, vec![] as Vec); + assert_eq!(params.len(), 0); } #[test] @@ -108,13 +111,14 @@ fn test_options_route_with_static_path() { unimplemented!() }; - let router = Router::new() - .add(Route::options("/hello", handle_options_hello)) - .add(Route::post("/hello", handle_post_hello)); + let router = RouterBuilder::new() + .add(Route::options("/hello").using(handle_options_hello)) + .add(Route::post("/hello").using(handle_post_hello)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_options_hello as fn(_) -> _); - assert_eq!(params.parameters, vec![] as Vec); + assert_eq!(params.len(), 0); } #[test] @@ -132,13 +136,14 @@ fn test_put_route_with_static_path() { unimplemented!() }; - let router = Router::new() - .add(Route::put("/hello", handle_put_hello)) - .add(Route::post("/hello", handle_post_hello)); + let router = RouterBuilder::new() + .add(Route::put("/hello").using(handle_put_hello)) + .add(Route::post("/hello").using(handle_post_hello)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_put_hello as fn(_) -> _); - assert_eq!(params.parameters, vec![] as Vec); + assert_eq!(params.len(), 0); } #[test] @@ -156,13 +161,14 @@ fn test_head_route_with_static_path() { unimplemented!() }; - let router = Router::new() - .add(Route::head("/hello", handle_head_hello)) - .add(Route::post("/hello", handle_post_hello)); + let router = RouterBuilder::new() + .add(Route::head("/hello").using(handle_head_hello)) + .add(Route::post("/hello").using(handle_post_hello)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_head_hello as fn(_) -> _); - assert_eq!(params.parameters, vec![] as Vec); + assert_eq!(params.len(), 0); } #[test] @@ -180,13 +186,14 @@ fn test_trace_route_with_static_path() { unimplemented!() }; - let router = Router::new() - .add(Route::trace("/hello", handle_trace_hello)) - .add(Route::post("/hello", handle_post_hello)); + let router = RouterBuilder::new() + .add(Route::trace("/hello").using(handle_trace_hello)) + .add(Route::post("/hello").using(handle_post_hello)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_trace_hello as fn(_) -> _); - assert_eq!(params.parameters, vec![] as Vec); + assert_eq!(params.len(), 0); } #[test] @@ -204,13 +211,14 @@ fn test_patch_route_with_static_path() { unimplemented!() }; - let router = Router::new() - .add(Route::patch("/hello", handle_patch_hello)) - .add(Route::post("/hello", handle_post_hello)); + let router = RouterBuilder::new() + .add(Route::patch("/hello").using(handle_patch_hello)) + .add(Route::post("/hello").using(handle_post_hello)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_patch_hello as fn(_) -> _); - assert_eq!(params.parameters, vec![] as Vec); + assert_eq!(params.len(), 0); } #[test] @@ -231,24 +239,19 @@ fn test_route_not_found() { unimplemented!() }; - let router = Router::new() + let router = RouterBuilder::new() .not_found(handle_not_found) - .add(Route::patch("/foo", handle_get_foo)) - .add(Route::patch("/bar", handle_get_bar)); + .add(Route::patch("/foo").using(handle_get_foo)) + .add(Route::patch("/bar").using(handle_get_bar)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_not_found as fn(_) -> _); - assert_eq!(params.parameters, vec![] as Vec); + assert_eq!(params.len(), 0); } #[test] fn test_method_not_supported_with_static_path() { - let request = Request::builder() - .method(Method::GET) - .uri(Uri::from_str("http://www.example.com/foo").unwrap()) - .body(Body::empty()) - .unwrap(); - fn handle_method_not_supported(_: Request) -> Response { unimplemented!() } @@ -259,12 +262,33 @@ fn test_method_not_supported_with_static_path() { unimplemented!() }; - let router = Router::new() + let router = RouterBuilder::new() .method_not_supported(handle_method_not_supported) - .add(Route::patch("/foo", handle_get_foo)) - .add(Route::patch("/bar", handle_get_bar)); - - let (handler, params) = router.find_handler(&request); - assert!(handler as fn(_) -> _ == handle_method_not_supported as fn(_) -> _); - assert_eq!(params.parameters, vec![] as Vec); + .add(Route::patch("/foo").using(handle_get_foo)) + .add(Route::patch("/bar").using(handle_get_bar)) + .add(Route::get("/bar").using(handle_get_bar)) + .build(); + + { + let request = Request::builder() + .method(Method::GET) + .uri(Uri::from_str("http://www.example.com/foo").unwrap()) + .body(Body::empty()) + .unwrap(); + let (handler, params) = router.find_handler(&request); + assert!(handler as fn(_) -> _ == handle_method_not_supported as fn(_) -> _); + assert_eq!(params.len(), 0); + } + { + // test that the router checks all routes before assuming the method + // is not supported. + let request = Request::builder() + .method(Method::GET) + .uri(Uri::from_str("http://www.example.com/bar").unwrap()) + .body(Body::empty()) + .unwrap(); + let (handler, params) = router.find_handler(&request); + assert!(handler as fn(_) -> _ == handle_get_bar as fn(_) -> _); + assert_eq!(params.len(), 0); + } } diff --git a/tests/parameters.rs b/tests/parameters.rs index fddd2958..510d6501 100644 --- a/tests/parameters.rs +++ b/tests/parameters.rs @@ -16,9 +16,6 @@ fn test_get_route_with_parametric_path() { fn handle_get_hello(_: Request) -> Response { unimplemented!() }; - fn handle_get_root(_: Request) -> Response { - unimplemented!() - }; fn handle_get_foo(_: Request) -> Response { unimplemented!() }; @@ -26,15 +23,16 @@ fn test_get_route_with_parametric_path() { unimplemented!() }; - let router = Router::new() - .add(Route::get("/hello/:id", handle_get_hello)) - .add(Route::get("/foo/:id", handle_get_root)) - .add(Route::get("/foo", handle_get_foo)) - .add(Route::post("/hello", handle_post_hello)); + let router = RouterBuilder::new() + .add(Route::get("/hello/:id").using(handle_get_hello)) + .add(Route::get("/foo/:id").using(handle_get_foo)) + .add(Route::get("/foo").using(handle_get_foo)) + .add(Route::post("/hello").using(handle_post_hello)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_get_hello as fn(_) -> _); - assert_eq!(params.parameters, vec!["123"]); + assert_eq!(params.get("id").unwrap(), &"123".to_string()); } #[test] @@ -51,27 +49,28 @@ fn test_post_route_with_parametric_path() { fn handle_post_hello(_: Request) -> Response { unimplemented!() }; - fn handle_post_root(_: Request) -> Response { + fn handle_get_root(_: Request) -> Response { unimplemented!() }; - fn handle_post_foo(_: Request) -> Response { + fn handle_get_foo(_: Request) -> Response { unimplemented!() }; fn handle_get_hello(_: Request) -> Response { unimplemented!() }; - let router = Router::new() - .add(Route::post("/hello/:id", handle_post_hello)) - .add(Route::get("/", handle_post_root)) - .add(Route::get("/foo", handle_post_foo)) - .add(Route::get("/hello", handle_get_hello)); + let router = RouterBuilder::new() + .add(Route::post("/hello/:id").using(handle_post_hello)) + .add(Route::get("/").using(handle_get_root)) + .add(Route::get("/foo").using(handle_get_foo)) + .add(Route::get("/hello").using(handle_get_hello)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_post_hello as fn(_) -> _); assert_eq!( - params.parameters, - vec!["5ea67884-245e-43f8-80f7-91d00971a562"] + params.get("id").unwrap(), + &"5ea67884-245e-43f8-80f7-91d00971a562".to_string() ); } @@ -90,13 +89,14 @@ fn test_delete_route_with_parametric_path() { unimplemented!() }; - let router = Router::new() - .add(Route::delete("/hello/:id", handle_delete_hello)) - .add(Route::post("/hello", handle_post_hello)); + let router = RouterBuilder::new() + .add(Route::delete("/hello/:id").using(handle_delete_hello)) + .add(Route::post("/hello").using(handle_post_hello)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_delete_hello as fn(_) -> _); - assert_eq!(params.parameters, vec!["my-hello"]); + assert_eq!(params.get("id").unwrap(), &"my-hello".to_string()); } #[test] @@ -114,13 +114,14 @@ fn test_options_route_with_parametric_path() { unimplemented!() }; - let router = Router::new() - .add(Route::options("/hello/:id", handle_options_hello)) - .add(Route::post("/hello/:id", handle_post_hello)); + let router = RouterBuilder::new() + .add(Route::options("/hello/:id").using(handle_options_hello)) + .add(Route::post("/hello/:id").using(handle_post_hello)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_options_hello as fn(_) -> _); - assert_eq!(params.parameters, vec!["7777"]); + assert_eq!(params.get("id").unwrap(), &"7777".to_string()); } #[test] @@ -138,13 +139,14 @@ fn test_put_route_with_parametric_path() { unimplemented!() }; - let router = Router::new() - .add(Route::put("/hello/:id", handle_put_hello)) - .add(Route::post("/hello/:id", handle_post_hello)); + let router = RouterBuilder::new() + .add(Route::put("/hello/:id").using(handle_put_hello)) + .add(Route::post("/hello/:id").using(handle_post_hello)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_put_hello as fn(_) -> _); - assert_eq!(params.parameters, vec!["deadbeef"]); + assert_eq!(params.get("id").unwrap(), &"deadbeef".to_string()); } #[test] @@ -162,13 +164,14 @@ fn test_head_route_with_parametric_path() { unimplemented!() }; - let router = Router::new() - .add(Route::head("/hello/:id", handle_head_hello)) - .add(Route::post("/hello/:id", handle_post_hello)); + let router = RouterBuilder::new() + .add(Route::head("/hello/:id").using(handle_head_hello)) + .add(Route::post("/hello/:id").using(handle_post_hello)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_head_hello as fn(_) -> _); - assert_eq!(params.parameters, vec!["goodbye"]); + assert_eq!(params.get("id").unwrap(), &"goodbye".to_string()); } #[test] @@ -186,19 +189,14 @@ fn test_trace_route_with_parametric_path() { unimplemented!() }; - let router = Router::new() - .add(Route::trace( - "/hello/:something-very-long", - handle_trace_hello, - )) - .add(Route::post( - "/hello/:something-very-long", - handle_post_hello, - )); + let router = RouterBuilder::new() + .add(Route::trace("/hello/:someparam").using(handle_trace_hello)) + .add(Route::post("/hello/:someparam").using(handle_post_hello)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_trace_hello as fn(_) -> _); - assert_eq!(params.parameters, vec!["hi"]); + assert_eq!(params.get("someparam").unwrap(), &"hi".to_string()); } #[test] @@ -216,13 +214,55 @@ fn test_patch_route_with_parametric_path() { unimplemented!() }; - let router = Router::new() - .add(Route::patch("/hello/:hello", handle_patch_hello)) - .add(Route::post("/hello/:hello", handle_post_hello)); + let router = RouterBuilder::new() + .add(Route::patch("/hello/:hello").using(handle_patch_hello)) + .add(Route::post("/hello/:hello").using(handle_post_hello)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_patch_hello as fn(_) -> _); - assert_eq!(params.parameters, vec!["hello"]); + assert_eq!(params.get("hello").unwrap(), &"hello".to_string()); +} + +#[test] +fn test_method_not_supported_with_parametric_path() { + fn handle_method_not_supported(_: Request) -> Response { + unimplemented!() + } + fn handle_get_foo(_: Request) -> Response { + unimplemented!() + }; + + let router = RouterBuilder::new() + .method_not_supported(handle_method_not_supported) + .add(Route::post("/foo/:id").using(handle_get_foo)) + .add(Route::get("/foo/:id").using(handle_get_foo)) + .build(); + + { + let request = Request::builder() + .method(Method::PUT) + .uri(Uri::from_str("http://www.example.com/foo/999").unwrap()) + .body(Body::empty()) + .unwrap(); + let (handler, params) = router.find_handler(&request); + assert!(handler as fn(_) -> _ == handle_method_not_supported as fn(_) -> _); + assert_eq!(params.len(), 1); + assert_eq!(params.get("id").unwrap(), &"999".to_string()); + } + { + // test that the router checks all routes before assuming the method + // is not supported. + let request = Request::builder() + .method(Method::GET) + .uri(Uri::from_str("http://www.example.com/foo/123").unwrap()) + .body(Body::empty()) + .unwrap(); + let (handler, params) = router.find_handler(&request); + assert!(handler as fn(_) -> _ == handle_get_foo as fn(_) -> _); + assert_eq!(params.len(), 1); + assert_eq!(params.get("id").unwrap(), &"123".to_string()); + } } #[test] @@ -243,12 +283,13 @@ fn test_route_not_found() { unimplemented!() }; - let router = Router::new() + let router = RouterBuilder::new() .not_found(handle_not_found) - .add(Route::patch("/foo/:id", handle_get_foo)) - .add(Route::patch("/bar/:id", handle_get_bar)); + .add(Route::patch("/foo/:id").using(handle_get_foo)) + .add(Route::patch("/bar/:id").using(handle_get_bar)) + .build(); let (handler, params) = router.find_handler(&request); assert!(handler as fn(_) -> _ == handle_not_found as fn(_) -> _); - assert_eq!(params.parameters, vec![] as Vec); + assert_eq!(params.len(), 0); } From f446f83fa22833741196e7ee5b612c4951c09f48 Mon Sep 17 00:00:00 2001 From: Alexandros Katechis Date: Sat, 3 Aug 2019 23:30:33 -0400 Subject: [PATCH 6/7] Rename benchmark binary. Add a couple more benchmark scenarios --- Cargo.toml | 2 +- benches/{parameter_capture.rs => matching.rs} | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) rename benches/{parameter_capture.rs => matching.rs} (56%) diff --git a/Cargo.toml b/Cargo.toml index 52a570b2..73558e1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,5 +25,5 @@ hyper = "^0.12" criterion = "0.2" [[bench]] -name = "parameter_capture" +name = "matching" harness = false diff --git a/benches/parameter_capture.rs b/benches/matching.rs similarity index 56% rename from benches/parameter_capture.rs rename to benches/matching.rs index c0b3f2b1..1bada650 100644 --- a/benches/parameter_capture.rs +++ b/benches/matching.rs @@ -22,6 +22,7 @@ fn create_router() -> Router { .add(Route::get("/g/:id").using(handler)) .add(Route::get("/h/:id").using(handler)) .add(Route::get("/a/:id").using(handler)) + .add(Route::get("/static/route").using(handler)) .build() } @@ -39,6 +40,34 @@ fn run_benchmark(c: &mut Criterion) { router.find_handler(&request) }) }); + + c.bench_function("capture single parameter", |bench| { + let router = create_router(); + bench.iter(move || { + let url = "http://a.com/a/09879182c79isdcnvwevbyqw8e7vby2873rvby283rbvwqrb283r7238rcb2378brc2387bcrq283bcr823rc283rbc2387vbgr238crbg"; + let req_uri = Uri::from_str(url).unwrap(); + let request = Request::builder() + .method(Method::GET) + .uri(req_uri) + .body(Body::empty()) + .unwrap(); + router.find_handler(&request) + }) + }); + + c.bench_function("match static route", |bench| { + let router = create_router(); + bench.iter(move || { + let url = "http://a.com/static/route"; + let req_uri = Uri::from_str(url).unwrap(); + let request = Request::builder() + .method(Method::GET) + .uri(req_uri) + .body(Body::empty()) + .unwrap(); + router.find_handler(&request) + }) + }); } criterion_group!(benches, run_benchmark); From 0a9bbb79512aae9031445a2c3634fbb243bc787a Mon Sep 17 00:00:00 2001 From: Alexandros Katechis Date: Mon, 5 Aug 2019 08:58:22 -0400 Subject: [PATCH 7/7] Re-enable and fix a doctest --- src/router.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/router.rs b/src/router.rs index 5b7e49b5..0ad40f7c 100644 --- a/src/router.rs +++ b/src/router.rs @@ -89,15 +89,21 @@ impl RouterBuilder { /// /// Example: /// - /// ```ignore - /// use hyper::server::{Request, Response}; + /// ```rust + /// use hyper::{Request, Response, Body}; + /// use hyper::header::{CONTENT_TYPE, CONTENT_LENGTH}; /// use hyper_router::{Route, RouterBuilder}; /// - /// fn some_handler(_: Request) -> Response { - /// // do something + /// fn some_handler(_: Request) -> Response { + /// let body = "Hello World"; + /// Response::builder() + /// .header(CONTENT_LENGTH, body.len() as u64) + /// .header(CONTENT_TYPE, "text/plain") + /// .body(Body::from(body)) + /// .expect("Failed to construct the response") /// } /// - /// RouterBuilder::new().add(Route::get("/person/:id").using(some_handler)); + /// RouterBuilder::new().add(Route::get("/hello").using(some_handler)); /// ``` #[allow(clippy::should_implement_trait)] pub fn add(mut self, route: Route) -> Self {