From 887b2aed877983feabb483f5213cd569691cddbb Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Tue, 13 Apr 2021 18:58:05 -0700 Subject: [PATCH] Move catcher, route types into eponymous modules. In the course, significantly improve their documentation. --- contrib/lib/src/serve.rs | 4 +- core/codegen/src/attribute/catch/mod.rs | 10 +- core/codegen/src/attribute/route/mod.rs | 14 +- core/codegen/src/exports.rs | 47 ++- core/codegen/src/lib.rs | 6 +- .../ui-fail-nightly/responder-types.stderr | 2 +- .../ui-fail-stable/responder-types.stderr | 2 +- core/lib/src/{ => catcher}/catcher.rs | 243 +++--------- core/lib/src/catcher/handler.rs | 156 ++++++++ core/lib/src/catcher/mod.rs | 7 + core/lib/src/codegen.rs | 36 -- core/lib/src/lib.rs | 11 +- core/lib/src/request/from_request.rs | 3 +- core/lib/src/rocket.rs | 20 +- core/lib/src/{ => route}/handler.rs | 132 +++---- core/lib/src/route/mod.rs | 12 + core/lib/src/route/route.rs | 356 ++++++++++++++++++ core/lib/src/{router => route}/segment.rs | 0 core/lib/src/{router => route}/uri.rs | 18 +- core/lib/src/router/collider.rs | 58 ++- core/lib/src/router/mod.rs | 10 +- core/lib/src/router/route.rs | 249 ------------ core/lib/src/router/router.rs | 47 +-- core/lib/src/server.rs | 8 +- core/lib/tests/panic-handling.rs | 8 +- examples/manual-routing/src/main.rs | 42 +-- 26 files changed, 793 insertions(+), 708 deletions(-) rename core/lib/src/{ => catcher}/catcher.rs (64%) create mode 100644 core/lib/src/catcher/handler.rs create mode 100644 core/lib/src/catcher/mod.rs delete mode 100644 core/lib/src/codegen.rs rename core/lib/src/{ => route}/handler.rs (69%) create mode 100644 core/lib/src/route/mod.rs create mode 100644 core/lib/src/route/route.rs rename core/lib/src/{router => route}/segment.rs (100%) rename core/lib/src/{router => route}/uri.rs (94%) delete mode 100644 core/lib/src/router/route.rs diff --git a/contrib/lib/src/serve.rs b/contrib/lib/src/serve.rs index d3a083dc..f86e5a25 100644 --- a/contrib/lib/src/serve.rs +++ b/contrib/lib/src/serve.rs @@ -16,10 +16,10 @@ use std::path::{PathBuf, Path}; -use rocket::{Request, Data, Route}; +use rocket::{Request, Data}; use rocket::http::{Method, uri::Segments, ext::IntoOwned}; -use rocket::handler::{Handler, Outcome}; use rocket::response::{NamedFile, Redirect}; +use rocket::route::{Route, Handler, Outcome}; /// Generates a crate-relative version of `$path`. /// diff --git a/core/codegen/src/attribute/catch/mod.rs b/core/codegen/src/attribute/catch/mod.rs index 6ae4718c..a1f92134 100644 --- a/core/codegen/src/attribute/catch/mod.rs +++ b/core/codegen/src/attribute/catch/mod.rs @@ -62,12 +62,12 @@ pub fn _catch( #vis struct #user_catcher_fn_name { } /// Rocket code generated proxy static conversion implementation. - impl From<#user_catcher_fn_name> for #StaticCatcherInfo { - fn from(_: #user_catcher_fn_name) -> #StaticCatcherInfo { + impl From<#user_catcher_fn_name> for #_catcher::StaticInfo { + fn from(_: #user_catcher_fn_name) -> #_catcher::StaticInfo { fn monomorphized_function<'_b>( #__status: #Status, #__req: &'_b #Request<'_> - ) -> #ErrorHandlerFuture<'_b> { + ) -> #_catcher::BoxFuture<'_b> { #_Box::pin(async move { let __response = #catcher_response; #Response::build() @@ -77,7 +77,7 @@ pub fn _catch( }) } - #StaticCatcherInfo { + #_catcher::StaticInfo { name: stringify!(#user_catcher_fn_name), code: #status_code, handler: monomorphized_function, @@ -89,7 +89,7 @@ pub fn _catch( impl From<#user_catcher_fn_name> for #Catcher { #[inline] fn from(_: #user_catcher_fn_name) -> #Catcher { - #StaticCatcherInfo::from(#user_catcher_fn_name {}).into() + #_catcher::StaticInfo::from(#user_catcher_fn_name {}).into() } } }) diff --git a/core/codegen/src/attribute/route/mod.rs b/core/codegen/src/attribute/route/mod.rs index f0f4cb86..498a6d1e 100644 --- a/core/codegen/src/attribute/route/mod.rs +++ b/core/codegen/src/attribute/route/mod.rs @@ -222,10 +222,10 @@ fn responder_outcome_expr(route: &Route) -> TokenStream { let _await = route.handler.sig.asyncness .map(|a| quote_spanned!(a.span().into() => .await)); - define_spanned_export!(ret_span => __req, _handler); + define_spanned_export!(ret_span => __req, _route); quote_spanned! { ret_span => let ___responder = #user_handler_fn_name(#(#parameter_names),*) #_await; - #_handler::Outcome::from(#__req, ___responder) + #_route::Outcome::from(#__req, ___responder) } } @@ -258,13 +258,13 @@ fn codegen_route(route: Route) -> Result { #vis struct #handler_fn_name { } /// Rocket code generated proxy static conversion implementation. - impl From<#handler_fn_name> for #StaticRouteInfo { + impl From<#handler_fn_name> for #_route::StaticInfo { #[allow(non_snake_case, unreachable_patterns, unreachable_code)] - fn from(_: #handler_fn_name) -> #StaticRouteInfo { + fn from(_: #handler_fn_name) -> #_route::StaticInfo { fn monomorphized_function<'_b>( #__req: &'_b #Request<'_>, #__data: #Data - ) -> #HandlerFuture<'_b> { + ) -> #_route::BoxFuture<'_b> { #_Box::pin(async move { #(#request_guards)* #(#param_guards)* @@ -275,7 +275,7 @@ fn codegen_route(route: Route) -> Result { }) } - #StaticRouteInfo { + #_route::StaticInfo { name: stringify!(#handler_fn_name), method: #method, path: #path, @@ -290,7 +290,7 @@ fn codegen_route(route: Route) -> Result { impl From<#handler_fn_name> for #Route { #[inline] fn from(_: #handler_fn_name) -> #Route { - #StaticRouteInfo::from(#handler_fn_name {}).into() + #_route::StaticInfo::from(#handler_fn_name {}).into() } } diff --git a/core/codegen/src/exports.rs b/core/codegen/src/exports.rs index 05f08fb4..9215832f 100644 --- a/core/codegen/src/exports.rs +++ b/core/codegen/src/exports.rs @@ -67,13 +67,14 @@ define_exported_paths! { __data => __data, __error => __error, __trail => __trail, - _request => rocket::request, - _response => rocket::response, - _handler => rocket::handler, - _log => rocket::logger, - _form => rocket::form::prelude, - _http => rocket::http, - _uri => rocket::http::uri, + _request => ::rocket::request, + _response => ::rocket::response, + _route => ::rocket::route, + _catcher => ::rocket::catcher, + _log => ::rocket::logger, + _form => ::rocket::form::prelude, + _http => ::rocket::http, + _uri => ::rocket::http::uri, _Option => ::std::option::Option, _Result => ::std::result::Result, _Some => ::std::option::Option::Some, @@ -84,23 +85,21 @@ define_exported_paths! { _Vec => ::std::vec::Vec, _Cow => ::std::borrow::Cow, BorrowMut => ::std::borrow::BorrowMut, - Outcome => rocket::outcome::Outcome, - FromForm => rocket::form::FromForm, - FromRequest => rocket::request::FromRequest, - FromData => rocket::data::FromData, - FromSegments => rocket::request::FromSegments, - FromParam => rocket::request::FromParam, - Request => rocket::request::Request, - Response => rocket::response::Response, - Data => rocket::data::Data, - StaticRouteInfo => rocket::StaticRouteInfo, - StaticCatcherInfo => rocket::StaticCatcherInfo, - Route => rocket::Route, - Catcher => rocket::Catcher, - SmallVec => rocket::http::private::SmallVec, - Status => rocket::http::Status, - HandlerFuture => rocket::handler::HandlerFuture, - ErrorHandlerFuture => rocket::catcher::ErrorHandlerFuture, + Outcome => ::rocket::outcome::Outcome, + FromForm => ::rocket::form::FromForm, + FromRequest => ::rocket::request::FromRequest, + FromData => ::rocket::data::FromData, + FromSegments => ::rocket::request::FromSegments, + FromParam => ::rocket::request::FromParam, + Request => ::rocket::request::Request, + Response => ::rocket::response::Response, + Data => ::rocket::data::Data, + StaticRouteInfo => ::rocket::StaticRouteInfo, + StaticCatcherInfo => ::rocket::StaticCatcherInfo, + Route => ::rocket::Route, + Catcher => ::rocket::Catcher, + SmallVec => ::rocket::http::private::SmallVec, + Status => ::rocket::http::Status, } macro_rules! define_spanned_export { diff --git a/core/codegen/src/lib.rs b/core/codegen/src/lib.rs index 518593e3..2f4a2794 100644 --- a/core/codegen/src/lib.rs +++ b/core/codegen/src/lib.rs @@ -262,7 +262,7 @@ macro_rules! route_attribute { /// 3. A macro used by [`uri!`] to type-check and generate an /// [`Origin`]. /// - /// [`Handler`]: rocket::handler::Handler + /// [`Handler`]: rocket::route::Handler /// [`routes!`]: macro.routes.html /// [`uri!`]: macro.uri.html /// [`Origin`]: rocket::http::uri::Origin @@ -330,7 +330,7 @@ route_attribute!(options => Method::Options); /// /// The attribute generates two items: /// -/// 1. An [`ErrorHandler`]. +/// 1. An error [`Handler`]. /// /// The generated handler calls the decorated function, passing in the /// [`Status`] and [`&Request`] values if requested. The returned value is @@ -345,7 +345,7 @@ route_attribute!(options => Method::Options); /// /// [`&Request`]: rocket::Request /// [`Status`]: rocket::http::Status -/// [`ErrorHandler`]: rocket::catcher::ErrorHandler +/// [`Handler`]: rocket::catcher::Handler /// [`catchers!`]: macro.catchers.html /// [`Catcher`]: rocket::Catcher /// [`Response`]: rocket::Response diff --git a/core/codegen/tests/ui-fail-nightly/responder-types.stderr b/core/codegen/tests/ui-fail-nightly/responder-types.stderr index 1c842144..ca586d84 100644 --- a/core/codegen/tests/ui-fail-nightly/responder-types.stderr +++ b/core/codegen/tests/ui-fail-nightly/responder-types.stderr @@ -53,4 +53,4 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied 28 | fn foo() -> usize { 0 } | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize` | - = note: required by `handler::, Status, rocket::Data>>::from` + = note: required by `route::handler::, Status, rocket::Data>>::from` diff --git a/core/codegen/tests/ui-fail-stable/responder-types.stderr b/core/codegen/tests/ui-fail-stable/responder-types.stderr index e7281807..3ef3c6db 100644 --- a/core/codegen/tests/ui-fail-stable/responder-types.stderr +++ b/core/codegen/tests/ui-fail-stable/responder-types.stderr @@ -53,4 +53,4 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied 28 | fn foo() -> usize { 0 } | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize` | - = note: required by `handler::, Status, rocket::Data>>::from` + = note: required by `route::handler::, Status, rocket::Data>>::from` diff --git a/core/lib/src/catcher.rs b/core/lib/src/catcher/catcher.rs similarity index 64% rename from core/lib/src/catcher.rs rename to core/lib/src/catcher/catcher.rs index 33881a0d..af90dba9 100644 --- a/core/lib/src/catcher.rs +++ b/core/lib/src/catcher/catcher.rs @@ -1,37 +1,18 @@ -//! Types and traits for error catchers, error handlers, and their return -//! types. - use std::fmt; use std::io::Cursor; use crate::response::Response; -use crate::codegen::StaticCatcherInfo; use crate::request::Request; use crate::http::{Status, ContentType, uri}; +use crate::catcher::{Handler, BoxFuture}; -use futures::future::BoxFuture; use yansi::Paint; -/// Type alias for the return value of an [`ErrorHandler`]. For now, identical -/// to [`response::Result`](crate::response::Result). -pub type Result<'r> = std::result::Result, crate::http::Status>; - -/// Type alias for the unwieldy [`ErrorHandler::handle()`] return type. -pub type ErrorHandlerFuture<'r> = BoxFuture<'r, Result<'r>>; - -// A handler to use when one is needed temporarily. Don't use outside of Rocket! -#[cfg(test)] -pub(crate) fn dummy<'r>(_: Status, _: &'r Request<'_>) -> ErrorHandlerFuture<'r> { - Box::pin(async move { Ok(Response::new()) }) -} - /// An error catching route. /// -/// # Overview -/// /// Catchers are routes that run when errors are produced by the application. -/// They consist of an [`ErrorHandler`] and an optional status code to match -/// against arising errors. Errors arise from the the following sources: +/// They consist of a [`Handler`] and an optional status code to match against +/// arising errors. Errors arise from the the following sources: /// /// * A failing guard. /// * A failing responder. @@ -42,23 +23,39 @@ pub(crate) fn dummy<'r>(_: Status, _: &'r Request<'_>) -> ErrorHandlerFuture<'r> /// failure is always a `404`. Rocket invokes the error handler for the catcher /// with the error's status code. /// -/// ## Default Catchers -/// -/// If no catcher for a given status code exists, the _default_ catcher is -/// called. A _default_ catcher is a `Catcher` with a `code` of `None`. There is -/// at-most one default catcher. -/// -/// ## Error Handler Restrictions +/// ### Error Handler Restrictions /// /// Because error handlers are a last resort, they should not fail to produce a /// response. If an error handler _does_ fail, Rocket invokes its default `500` /// error catcher. Error handlers cannot forward. /// -/// # Built-In Default Catcher +/// # Routing /// -/// Rocket's built-in default catcher can handle all errors. It produces HTML or -/// JSON, depending on the value of the `Accept` header. As such, catchers only -/// need to be registered if an error needs to be handled in a custom fashion. +/// An error arising from a particular request _matches_ a catcher _iff_: +/// +/// * It is a default catcher _or_ has a status code matching the error code. +/// * Its base is a prefix of the normalized/decoded request URI path. +/// +/// A _default_ catcher is a catcher with no explicit status code: `None`. The +/// catcher's _base_ is provided as the first argument to +/// [`Rocket::register()`](crate::Rocket::register()). +/// +/// # Collisions +/// +/// Two catchers are said to _collide_ if there exists an error that matches +/// both catchers. Colliding catchers present a routing ambiguity and are thus +/// disallowed by Rocket. Because catchers can be constructed dynamically, +/// collision checking is done at [`ignite`](crate::Rocket::ignite()) time, +/// after it becomes statically impossible to register any more catchers on an +/// instance of `Rocket`. +/// +/// ### Built-In Default +/// +/// Rocket's provides a built-in default catcher that can handle all errors. It +/// produces HTML or JSON, depending on the value of the `Accept` header. As +/// such, catchers only need to be registered if an error needs to be handled in +/// a custom fashion. The built-in default never conflicts with any +/// user-registered catchers. /// /// # Code Generation /// @@ -87,7 +84,7 @@ pub(crate) fn dummy<'r>(_: Status, _: &'r Request<'_>) -> ErrorHandlerFuture<'r> /// } /// /// #[launch] -/// fn rocket() -> rocket::Rocket { +/// fn rocket() -> _ { /// rocket::build().register("/", catchers![internal_error, not_found, default]) /// } /// ``` @@ -117,7 +114,7 @@ pub struct Catcher { pub code: Option, /// The catcher's associated error handler. - pub handler: Box, + pub handler: Box, } impl Catcher { @@ -129,20 +126,20 @@ impl Catcher { /// /// ```rust /// use rocket::request::Request; - /// use rocket::catcher::{Catcher, ErrorHandlerFuture}; + /// use rocket::catcher::{Catcher, BoxFuture}; /// use rocket::response::Responder; /// use rocket::http::Status; /// - /// fn handle_404<'r>(status: Status, req: &'r Request<'_>) -> ErrorHandlerFuture<'r> { + /// fn handle_404<'r>(status: Status, req: &'r Request<'_>) -> BoxFuture<'r> { /// let res = (status, format!("404: {}", req.uri())); /// Box::pin(async move { res.respond_to(req) }) /// } /// - /// fn handle_500<'r>(_: Status, req: &'r Request<'_>) -> ErrorHandlerFuture<'r> { + /// fn handle_500<'r>(_: Status, req: &'r Request<'_>) -> BoxFuture<'r> { /// Box::pin(async move{ "Whoops, we messed up!".respond_to(req) }) /// } /// - /// fn handle_default<'r>(status: Status, req: &'r Request<'_>) -> ErrorHandlerFuture<'r> { + /// fn handle_default<'r>(status: Status, req: &'r Request<'_>) -> BoxFuture<'r> { /// let res = (status, format!("{}: {}", status, req.uri())); /// Box::pin(async move { res.respond_to(req) }) /// } @@ -158,7 +155,7 @@ impl Catcher { /// 600)`. #[inline(always)] pub fn new(code: S, handler: H) -> Catcher - where S: Into>, H: ErrorHandler + where S: Into>, H: Handler { let code = code.into(); if let Some(code) = code { @@ -185,11 +182,11 @@ impl Catcher { /// /// ```rust /// use rocket::request::Request; - /// use rocket::catcher::{Catcher, ErrorHandlerFuture}; + /// use rocket::catcher::{Catcher, BoxFuture}; /// use rocket::response::Responder; /// use rocket::http::Status; /// - /// fn handle_404<'r>(status: Status, req: &'r Request<'_>) -> ErrorHandlerFuture<'r> { + /// fn handle_404<'r>(status: Status, req: &'r Request<'_>) -> BoxFuture<'r> { /// let res = (status, format!("404: {}", req.uri())); /// Box::pin(async move { res.respond_to(req) }) /// } @@ -220,8 +217,8 @@ impl Catcher { impl Default for Catcher { fn default() -> Self { - fn handler<'r>(s: Status, req: &'r Request<'_>) -> ErrorHandlerFuture<'r> { - Box::pin(async move { Ok(default(s, req)) }) + fn handler<'r>(s: Status, req: &'r Request<'_>) -> BoxFuture<'r> { + Box::pin(async move { Ok(default_handler(s, req)) }) } let mut catcher = Catcher::new(None, handler); @@ -230,121 +227,21 @@ impl Default for Catcher { } } -/// Trait implemented by types that can handle errors. -/// -/// This trait is exactly like [`Handler`](crate::handler::Handler) except it -/// handles error instead of requests. We defer to its documentation. -/// -/// ## Async Trait -/// -/// This is an _async_ trait. Implementations must be decorated -/// [`#[rocket::async_trait]`](crate::async_trait). -/// -/// # Example -/// -/// Say you'd like to write a handler that changes its functionality based on a -/// `Kind` enum value that the user provides. Such a handler might be written -/// and used as follows: -/// -/// ```rust,no_run -/// use rocket::{Request, Catcher}; -/// use rocket::catcher::{self, ErrorHandler}; -/// use rocket::response::{Response, Responder}; -/// use rocket::http::Status; -/// -/// #[derive(Copy, Clone)] -/// enum Kind { -/// Simple, -/// Intermediate, -/// Complex, -/// } -/// -/// #[derive(Clone)] -/// struct CustomHandler(Kind); -/// -/// #[rocket::async_trait] -/// impl ErrorHandler for CustomHandler { -/// async fn handle<'r, 's: 'r>( -/// &'s self, -/// status: Status, -/// req: &'r Request<'_> -/// ) -> catcher::Result<'r> { -/// let inner = match self.0 { -/// Kind::Simple => "simple".respond_to(req)?, -/// Kind::Intermediate => "intermediate".respond_to(req)?, -/// Kind::Complex => "complex".respond_to(req)?, -/// }; -/// -/// Response::build_from(inner).status(status).ok() -/// } -/// } -/// -/// impl CustomHandler { -/// /// Returns a `default` catcher that uses `CustomHandler`. -/// fn default(kind: Kind) -> Vec { -/// vec![Catcher::new(None, CustomHandler(kind))] -/// } -/// -/// /// Returns a catcher for code `status` that uses `CustomHandler`. -/// fn catch(status: Status, kind: Kind) -> Vec { -/// vec![Catcher::new(status.code, CustomHandler(kind))] -/// } -/// } -/// -/// #[rocket::launch] -/// fn rocket() -> rocket::Rocket { -/// rocket::build() -/// // to handle only `404` -/// .register("/", CustomHandler::catch(Status::NotFound, Kind::Simple)) -/// // or to register as the default -/// .register("/", CustomHandler::default(Kind::Simple)) -/// } -/// ``` -/// -/// Note the following: -/// -/// 1. `CustomHandler` implements `Clone`. This is required so that -/// `CustomHandler` implements `Cloneable` automatically. The `Cloneable` -/// trait serves no other purpose but to ensure that every `ErrorHandler` -/// can be cloned, allowing `Catcher`s to be cloned. -/// 2. `CustomHandler`'s methods return `Vec`, allowing for use -/// directly as the parameter to `rocket.register("/", )`. -/// 3. Unlike static-function-based handlers, this custom handler can make use -/// of internal state. -#[crate::async_trait] -pub trait ErrorHandler: Cloneable + Send + Sync + 'static { - /// Called by Rocket when an error with `status` for a given `Request` - /// should be handled by this handler. - /// - /// Error handlers _should not_ fail and thus _should_ always return `Ok`. - /// Nevertheless, failure is allowed, both for convenience and necessity. If - /// an error handler fails, Rocket's default `500` catcher is invoked. If it - /// succeeds, the returned `Response` is used to respond to the client. - async fn handle<'r, 's: 'r>(&'s self, status: Status, req: &'r Request<'_>) -> Result<'r>; -} - -// We write this manually to avoid double-boxing. -impl ErrorHandler for F - where for<'x> F: Fn(Status, &'x Request<'_>) -> ErrorHandlerFuture<'x>, -{ - fn handle<'r, 's: 'r, 'life0, 'async_trait>( - &'s self, - status: Status, - req: &'r Request<'life0>, - ) -> ErrorHandlerFuture<'r> - where 'r: 'async_trait, - 's: 'async_trait, - 'life0: 'async_trait, - Self: 'async_trait, - { - self(status, req) - } +/// Information generated by the `catch` attribute during codegen. +#[doc(hidden)] +pub struct StaticInfo { + /// The catcher's name, i.e, the name of the function. + pub name: &'static str, + /// The catcher's status code. + pub code: Option, + /// The catcher's handler, i.e, the annotated function. + pub handler: for<'r> fn(Status, &'r Request<'_>) -> BoxFuture<'r>, } #[doc(hidden)] -impl From for Catcher { +impl From for Catcher { #[inline] - fn from(info: StaticCatcherInfo) -> Catcher { + fn from(info: StaticInfo) -> Catcher { let mut catcher = Catcher::new(info.code, info.handler); catcher.name = Some(info.name.into()); catcher @@ -431,11 +328,14 @@ r#"{{ ) } -macro_rules! default_catcher_fn { +macro_rules! default_handler_fn { ($($code:expr, $reason:expr, $description:expr),+) => ( use std::borrow::Cow; - pub(crate) fn default<'r>(status: Status, req: &'r Request<'_>) -> Response<'r> { + pub(crate) fn default_handler<'r>( + status: Status, + req: &'r Request<'_> + ) -> Response<'r> { let preferred = req.accept().map(|a| a.preferred()); let (mime, text) = if preferred.map_or(false, |a| a.is_json()) { let json: Cow<'_, str> = match status.code { @@ -466,7 +366,7 @@ macro_rules! default_catcher_fn { ) } -default_catcher_fn! { +default_handler_fn! { 400, "Bad Request", "The request could not be understood by the server due \ to malformed syntax.", 401, "Unauthorized", "The request requires user authentication.", @@ -515,30 +415,3 @@ default_catcher_fn! { the server to fulfill it." } -// `Cloneable` implementation below. - -mod private { - pub trait Sealed {} - impl Sealed for T {} -} - -/// Unfortunate but necessary hack to be able to clone a `Box`. -/// -/// This trait cannot be implemented by any type. Instead, all types that -/// implement `Clone` and `Handler` automatically implement `Cloneable`. -pub trait Cloneable: private::Sealed { - #[doc(hidden)] - fn clone_handler(&self) -> Box; -} - -impl Cloneable for T { - fn clone_handler(&self) -> Box { - Box::new(self.clone()) - } -} - -impl Clone for Box { - fn clone(&self) -> Box { - self.clone_handler() - } -} diff --git a/core/lib/src/catcher/handler.rs b/core/lib/src/catcher/handler.rs new file mode 100644 index 00000000..07960212 --- /dev/null +++ b/core/lib/src/catcher/handler.rs @@ -0,0 +1,156 @@ +use crate::{Request, Response}; +use crate::http::Status; + +/// Type alias for the return type of a [`Catcher`](crate::Catcher)'s +/// [`Handler::handle()`]. +pub type Result<'r> = std::result::Result, crate::http::Status>; + +/// Type alias for the return type of a _raw_ [`Catcher`](crate::Catcher)'s +/// [`Handler`]. +pub type BoxFuture<'r, T = Result<'r>> = futures::future::BoxFuture<'r, T>; + +/// Trait implemented by [`Catcher`](crate::Catcher) error handlers. +/// +/// This trait is exactly like a [`Route`](crate::Route)'s +/// [`Handler`](crate::route::Handler) except it handles errors instead of +/// requests. Thus, the documentaiton for +/// [`route::Handler`](crate::route::Handler) applies to this trait as well. We +/// defer to it for full details. +/// +/// ## Async Trait +/// +/// This is an _async_ trait. Implementations must be decorated +/// [`#[rocket::async_trait]`](crate::async_trait). +/// +/// # Example +/// +/// Say you'd like to write a handler that changes its functionality based on a +/// `Kind` enum value that the user provides. Such a handler might be written +/// and used as follows: +/// +/// ```rust,no_run +/// use rocket::{Request, Catcher, catcher}; +/// use rocket::response::{Response, Responder}; +/// use rocket::http::Status; +/// +/// #[derive(Copy, Clone)] +/// enum Kind { +/// Simple, +/// Intermediate, +/// Complex, +/// } +/// +/// #[derive(Clone)] +/// struct CustomHandler(Kind); +/// +/// #[rocket::async_trait] +/// impl catcher::Handler for CustomHandler { +/// async fn handle<'r, 's: 'r>( +/// &'s self, +/// status: Status, +/// req: &'r Request<'_> +/// ) -> catcher::Result<'r> { +/// let inner = match self.0 { +/// Kind::Simple => "simple".respond_to(req)?, +/// Kind::Intermediate => "intermediate".respond_to(req)?, +/// Kind::Complex => "complex".respond_to(req)?, +/// }; +/// +/// Response::build_from(inner).status(status).ok() +/// } +/// } +/// +/// impl CustomHandler { +/// /// Returns a `default` catcher that uses `CustomHandler`. +/// fn default(kind: Kind) -> Vec { +/// vec![Catcher::new(None, CustomHandler(kind))] +/// } +/// +/// /// Returns a catcher for code `status` that uses `CustomHandler`. +/// fn catch(status: Status, kind: Kind) -> Vec { +/// vec![Catcher::new(status.code, CustomHandler(kind))] +/// } +/// } +/// +/// #[rocket::launch] +/// fn rocket() -> _ { +/// rocket::build() +/// // to handle only `404` +/// .register("/", CustomHandler::catch(Status::NotFound, Kind::Simple)) +/// // or to register as the default +/// .register("/", CustomHandler::default(Kind::Simple)) +/// } +/// ``` +/// +/// Note the following: +/// +/// 1. `CustomHandler` implements `Clone`. This is required so that +/// `CustomHandler` implements `Cloneable` automatically. The `Cloneable` +/// trait serves no other purpose but to ensure that every `Handler` +/// can be cloned, allowing `Catcher`s to be cloned. +/// 2. `CustomHandler`'s methods return `Vec`, allowing for use +/// directly as the parameter to `rocket.register("/", )`. +/// 3. Unlike static-function-based handlers, this custom handler can make use +/// of internal state. +#[crate::async_trait] +pub trait Handler: Cloneable + Send + Sync + 'static { + /// Called by Rocket when an error with `status` for a given `Request` + /// should be handled by this handler. + /// + /// Error handlers _should not_ fail and thus _should_ always return `Ok`. + /// Nevertheless, failure is allowed, both for convenience and necessity. If + /// an error handler fails, Rocket's default `500` catcher is invoked. If it + /// succeeds, the returned `Response` is used to respond to the client. + async fn handle<'r, 's: 'r>(&'s self, status: Status, req: &'r Request<'_>) -> Result<'r>; +} + +// We write this manually to avoid double-boxing. +impl Handler for F + where for<'x> F: Fn(Status, &'x Request<'_>) -> BoxFuture<'x>, +{ + fn handle<'r, 's: 'r, 'life0, 'async_trait>( + &'s self, + status: Status, + req: &'r Request<'life0>, + ) -> BoxFuture<'r> + where 'r: 'async_trait, + 's: 'async_trait, + 'life0: 'async_trait, + Self: 'async_trait, + { + self(status, req) + } +} + +#[cfg(test)] +pub fn dummy_handler<'r>(_: Status, _: &'r Request<'_>) -> BoxFuture<'r> { + Box::pin(async move { Ok(Response::new()) }) +} + +mod private { + pub trait Sealed {} + impl Sealed for T {} +} + +/// Helper trait to make a [`Catcher`](crate::Catcher)'s `Box` +/// `Clone`. +/// +/// This trait cannot be implemented directly. Instead, implement `Clone` and +/// [`Handler`]; all types that implement `Clone` and `Handler` automatically +/// implement `Cloneable`. +pub trait Cloneable: private::Sealed { + #[doc(hidden)] + fn clone_handler(&self) -> Box; +} + +impl Cloneable for T { + fn clone_handler(&self) -> Box { + Box::new(self.clone()) + } +} + +impl Clone for Box { + fn clone(&self) -> Box { + self.clone_handler() + } +} diff --git a/core/lib/src/catcher/mod.rs b/core/lib/src/catcher/mod.rs new file mode 100644 index 00000000..4f5fefa1 --- /dev/null +++ b/core/lib/src/catcher/mod.rs @@ -0,0 +1,7 @@ +//! Types and traits for error catchers and their handlers and return types. + +mod catcher; +mod handler; + +pub use catcher::*; +pub use handler::*; diff --git a/core/lib/src/codegen.rs b/core/lib/src/codegen.rs deleted file mode 100644 index 943701b0..00000000 --- a/core/lib/src/codegen.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::{Request, Data}; -use crate::handler::HandlerFuture; -use crate::catcher::ErrorHandlerFuture; -use crate::http::{Status, Method, MediaType}; - -/// Type of a route handler, generated from a `fn` annotated with `#[route]`. -pub type StaticHandler = for<'r> fn(&'r Request<'_>, Data) -> HandlerFuture<'r>; - -/// Type of an error handler, generated from a `fn` annotated with `#[catch]`. -pub type StaticErrorHandler = for<'r> fn(Status, &'r Request<'_>) -> ErrorHandlerFuture<'r>; - -/// Information generated by the `route` attribute during codegen. -pub struct StaticRouteInfo { - /// The route's name, i.e, the name of the function. - pub name: &'static str, - /// The route's method. - pub method: Method, - /// The route's path, without the base mount point. - pub path: &'static str, - /// The route's format, if any. - pub format: Option, - /// The route's handler, i.e, the annotated function. - pub handler: StaticHandler, - /// The route's rank, if any. - pub rank: Option, -} - -/// Information generated by the `catch` attribute during codegen. -pub struct StaticCatcherInfo { - /// The catcher's name, i.e, the name of the function. - pub name: &'static str, - /// The catcher's status code. - pub code: Option, - /// The catcher's handler, i.e, the annotated function. - pub handler: StaticErrorHandler, -} diff --git a/core/lib/src/lib.rs b/core/lib/src/lib.rs index 4d3d26e7..45e96ce4 100644 --- a/core/lib/src/lib.rs +++ b/core/lib/src/lib.rs @@ -124,11 +124,10 @@ pub mod request; pub mod response; pub mod config; pub mod form; -pub mod handler; pub mod fairing; pub mod error; pub mod catcher; -pub mod router; +pub mod route; // Reexport of HTTP everything. pub mod http { @@ -145,20 +144,20 @@ pub mod http { } mod shutdown; -mod rocket; mod server; -mod codegen; mod ext; mod state; mod cookies; +mod rocket; +mod router; +mod phase; #[doc(hidden)] pub use log::{info, warn, error, debug}; #[doc(inline)] pub use crate::response::Response; -#[doc(hidden)] pub use crate::codegen::{StaticRouteInfo, StaticCatcherInfo}; #[doc(inline)] pub use crate::data::Data; #[doc(inline)] pub use crate::config::Config; #[doc(inline)] pub use crate::catcher::Catcher; -#[doc(inline)] pub use crate::router::Route; +#[doc(inline)] pub use crate::route::Route; #[doc(hidden)] pub use either::Either; pub use crate::request::Request; pub use crate::rocket::Rocket; diff --git a/core/lib/src/request/from_request.rs b/core/lib/src/request/from_request.rs index c5ed2d38..baa5346e 100644 --- a/core/lib/src/request/from_request.rs +++ b/core/lib/src/request/from_request.rs @@ -1,8 +1,7 @@ use std::fmt::Debug; use std::net::{IpAddr, SocketAddr}; -use crate::router::Route; -use crate::request::Request; +use crate::{Request, Route}; use crate::outcome::{self, IntoOutcome}; use crate::outcome::Outcome::*; diff --git a/core/lib/src/rocket.rs b/core/lib/src/rocket.rs index 84a16311..1fe6429e 100644 --- a/core/lib/src/rocket.rs +++ b/core/lib/src/rocket.rs @@ -2,14 +2,10 @@ use std::fmt::Display; use std::convert::TryInto; use yansi::Paint; -use state::Container; -use figment::Figment; -use tokio::sync::mpsc; +use tokio::sync::Notify; -use crate::logger; -use crate::config::Config; -use crate::catcher::Catcher; -use crate::router::{Router, Route}; +use crate::{Route, Catcher, Config, Shutdown}; +use crate::router::Router; use crate::fairing::{Fairing, Fairings}; use crate::logger::PaintExt; use crate::shutdown::Shutdown; @@ -189,12 +185,12 @@ impl Rocket { /// `hi` route. /// /// ```rust - /// use rocket::{Request, Route, Data}; - /// use rocket::handler::{HandlerFuture, Outcome}; - /// use rocket::http::Method::*; + /// # #[macro_use] extern crate rocket; + /// use rocket::{Request, Route, Data, route}; + /// use rocket::http::Method; /// - /// fn hi<'r>(req: &'r Request, _: Data) -> HandlerFuture<'r> { - /// Outcome::from(req, "Hello!").pin() + /// fn hi<'r>(req: &'r Request, _: Data) -> route::BoxFuture<'r> { + /// route::Outcome::from(req, "Hello!").pin() /// } /// /// #[launch] diff --git a/core/lib/src/handler.rs b/core/lib/src/route/handler.rs similarity index 69% rename from core/lib/src/handler.rs rename to core/lib/src/route/handler.rs index 0d20f52b..9472b86e 100644 --- a/core/lib/src/handler.rs +++ b/core/lib/src/route/handler.rs @@ -1,20 +1,16 @@ -//! Types and traits for request handlers and their return values. - -use futures::future::BoxFuture; - -use crate::data::Data; -use crate::request::Request; +use crate::{Request, Data}; use crate::response::{Response, Responder}; use crate::http::Status; -use crate::outcome; -/// Type alias for the `Outcome` of a `Handler`. -pub type Outcome<'r> = outcome::Outcome, Status, Data>; +/// Type alias for the return type of a [`Route`](crate::Route)'s +/// [`Handler::handle()`]. +pub type Outcome<'r> = crate::outcome::Outcome, Status, Data>; -/// Type alias for the unwieldy `Handler` return type -pub type HandlerFuture<'r> = BoxFuture<'r, Outcome<'r>>; +/// Type alias for the return type of a _raw_ [`Route`](crate::Route)'s +/// [`Handler`]. +pub type BoxFuture<'r, T = Outcome<'r>> = futures::future::BoxFuture<'r, T>; -/// Trait implemented by types that can handle requests. +/// Trait implemented by [`Route`](crate::Route) request handlers. /// /// In general, you will never need to implement `Handler` manually or be /// concerned about the `Handler` trait; Rocket's code generation handles @@ -27,8 +23,8 @@ pub type HandlerFuture<'r> = BoxFuture<'r, Outcome<'r>>; /// /// ## Async Trait /// -/// [`Handler`] is an _async_ trait. Implementations of `Handler` must be -/// decorated with an attribute of `#[rocket::async_trait]`. +/// This is an _async_ trait. Implementations must be decorated +/// [`#[rocket::async_trait]`](crate::async_trait). /// /// # Example /// @@ -48,8 +44,9 @@ pub type HandlerFuture<'r> = BoxFuture<'r, Outcome<'r>>; /// /// ```rust,no_run /// # #[derive(Copy, Clone)] enum Kind { Simple, Intermediate, Complex, } -/// use rocket::{Request, Data, Route, http::Method}; -/// use rocket::handler::{self, Handler, Outcome}; +/// use rocket::{Request, Data}; +/// use rocket::route::{Handler, Route, Outcome}; +/// use rocket::http::Method; /// /// #[derive(Clone)] /// struct CustomHandler(Kind); @@ -72,7 +69,7 @@ pub type HandlerFuture<'r> = BoxFuture<'r, Outcome<'r>>; /// } /// /// #[rocket::launch] -/// fn rocket() -> rocket::Rocket { +/// fn rocket() -> _ { /// rocket::build().mount("/", CustomHandler(Kind::Simple)) /// } /// ``` @@ -115,7 +112,7 @@ pub type HandlerFuture<'r> = BoxFuture<'r, Outcome<'r>>; /// } /// /// #[launch] -/// fn rocket() -> rocket::Rocket { +/// fn rocket() -> _ { /// rocket::build() /// .mount("/", routes![custom_handler]) /// .manage(Kind::Simple) @@ -153,14 +150,14 @@ pub trait Handler: Cloneable + Send + Sync + 'static { // We write this manually to avoid double-boxing. impl Handler for F - where for<'x> F: Fn(&'x Request<'_>, Data) -> HandlerFuture<'x>, + where for<'x> F: Fn(&'x Request<'_>, Data) -> BoxFuture<'x>, { #[inline(always)] fn handle<'r, 's: 'r, 'life0, 'async_trait>( &'s self, req: &'r Request<'life0>, data: Data, - ) -> HandlerFuture<'r> + ) -> BoxFuture<'r> where 'r: 'async_trait, 's: 'async_trait, 'life0: 'async_trait, @@ -170,51 +167,43 @@ impl Handler for F } } -// A handler to use when one is needed temporarily. Don't use outside of Rocket! -#[doc(hidden)] -pub fn dummy<'r>(r: &'r Request<'_>, _: Data) -> HandlerFuture<'r> { - Outcome::from(r, ()).pin() -} - impl<'r, 'o: 'r> Outcome<'o> { /// Return the `Outcome` of response to `req` from `responder`. /// - /// If the responder returns `Ok`, an outcome of `Success` is - /// returned with the response. If the responder returns `Err`, an - /// outcome of `Failure` is returned with the status code. + /// If the responder returns `Ok`, an outcome of `Success` is returned with + /// the response. If the responder returns `Err`, an outcome of `Failure` is + /// returned with the status code. /// /// # Example /// /// ```rust - /// use rocket::{Request, Data}; - /// use rocket::handler::Outcome; + /// use rocket::{Request, Data, route}; /// - /// fn str_responder<'r>(req: &'r Request, _: Data) -> Outcome<'r> { - /// Outcome::from(req, "Hello, world!") + /// fn str_responder<'r>(req: &'r Request, _: Data) -> route::Outcome<'r> { + /// route::Outcome::from(req, "Hello, world!") /// } /// ``` #[inline] pub fn from>(req: &'r Request<'_>, responder: R) -> Outcome<'o> { match responder.respond_to(req) { - Ok(response) => outcome::Outcome::Success(response), - Err(status) => outcome::Outcome::Failure(status) + Ok(response) => Outcome::Success(response), + Err(status) => Outcome::Failure(status) } } /// Return the `Outcome` of response to `req` from `responder`. /// - /// If the responder returns `Ok`, an outcome of `Success` is - /// returned with the response. If the responder returns `Err`, an - /// outcome of `Failure` is returned with the status code. + /// If the responder returns `Ok`, an outcome of `Success` is returned with + /// the response. If the responder returns `Err`, an outcome of `Failure` is + /// returned with the status code. /// /// # Example /// /// ```rust - /// use rocket::{Request, Data}; - /// use rocket::handler::Outcome; + /// use rocket::{Request, Data, route}; /// - /// fn str_responder<'r>(req: &'r Request, _: Data) -> Outcome<'r> { - /// Outcome::from(req, "Hello, world!") + /// fn str_responder<'r>(req: &'r Request, _: Data) -> route::Outcome<'r> { + /// route::Outcome::from(req, "Hello, world!") /// } /// ``` #[inline] @@ -223,25 +212,24 @@ impl<'r, 'o: 'r> Outcome<'o> { { let responder = result.map_err(crate::response::Debug); match responder.respond_to(req) { - Ok(response) => outcome::Outcome::Success(response), - Err(status) => outcome::Outcome::Failure(status) + Ok(response) => Outcome::Success(response), + Err(status) => Outcome::Failure(status) } } /// Return the `Outcome` of response to `req` from `responder`. /// - /// If the responder returns `Ok`, an outcome of `Success` is - /// returned with the response. If the responder returns `Err`, an - /// outcome of `Forward` is returned. + /// If the responder returns `Ok`, an outcome of `Success` is returned with + /// the response. If the responder returns `Err`, an outcome of `Forward` is + /// returned. /// /// # Example /// /// ```rust - /// use rocket::{Request, Data}; - /// use rocket::handler::Outcome; + /// use rocket::{Request, Data, route}; /// - /// fn str_responder<'r>(req: &'r Request, data: Data) -> Outcome<'r> { - /// Outcome::from_or_forward(req, data, "Hello, world!") + /// fn str_responder<'r>(req: &'r Request, data: Data) -> route::Outcome<'r> { + /// route::Outcome::from_or_forward(req, data, "Hello, world!") /// } /// ``` #[inline] @@ -249,64 +237,68 @@ impl<'r, 'o: 'r> Outcome<'o> { where R: Responder<'r, 'o> { match responder.respond_to(req) { - Ok(response) => outcome::Outcome::Success(response), - Err(_) => outcome::Outcome::Forward(data) + Ok(response) => Outcome::Success(response), + Err(_) => Outcome::Forward(data) } } /// Return an `Outcome` of `Failure` with the status code `code`. This is /// equivalent to `Outcome::Failure(code)`. /// - /// This method exists to be used during manual routing where - /// `rocket::handler::Outcome` is imported instead of `rocket::Outcome`. + /// This method exists to be used during manual routing. /// /// # Example /// /// ```rust - /// use rocket::{Request, Data}; - /// use rocket::handler::Outcome; + /// use rocket::{Request, Data, route}; /// use rocket::http::Status; /// - /// fn bad_req_route<'r>(_: &'r Request, _: Data) -> Outcome<'r> { - /// Outcome::failure(Status::BadRequest) + /// fn bad_req_route<'r>(_: &'r Request, _: Data) -> route::Outcome<'r> { + /// route::Outcome::failure(Status::BadRequest) /// } /// ``` #[inline(always)] pub fn failure(code: Status) -> Outcome<'static> { - outcome::Outcome::Failure(code) + Outcome::Failure(code) } /// Return an `Outcome` of `Forward` with the data `data`. This is /// equivalent to `Outcome::Forward(data)`. /// - /// This method exists to be used during manual routing where - /// `rocket::handler::Outcome` is imported instead of `rocket::Outcome`. + /// This method exists to be used during manual routing. /// /// # Example /// /// ```rust - /// use rocket::{Request, Data}; - /// use rocket::handler::Outcome; + /// use rocket::{Request, Data, route}; /// - /// fn always_forward<'r>(_: &'r Request, data: Data) -> Outcome<'r> { - /// Outcome::forward(data) + /// fn always_forward<'r>(_: &'r Request, data: Data) -> route::Outcome<'r> { + /// route::Outcome::forward(data) /// } /// ``` #[inline(always)] pub fn forward(data: Data) -> Outcome<'static> { - outcome::Outcome::Forward(data) + Outcome::Forward(data) } } +// INTERNAL: A handler to use when one is needed temporarily. +#[doc(hidden)] +pub fn dummy_handler<'r>(r: &'r Request<'_>, _: Data) -> BoxFuture<'r> { + Outcome::from(r, ()).pin() +} + mod private { pub trait Sealed {} impl Sealed for T {} } -/// Unfortunate but necessary hack to be able to clone a `Box`. +/// Helper trait to make a [`Route`](crate::Route)'s `Box` +/// `Clone`. /// -/// This trait cannot be implemented by any type. Instead, all types that -/// implement `Clone` and `Handler` automatically implement `Cloneable`. +/// This trait cannot be implemented directly. Instead, implement `Clone` and +/// [`Handler`]; all types that implement `Clone` and `Handler` automatically +/// implement `Cloneable`. pub trait Cloneable: private::Sealed { #[doc(hidden)] fn clone_handler(&self) -> Box; diff --git a/core/lib/src/route/mod.rs b/core/lib/src/route/mod.rs new file mode 100644 index 00000000..d240d8a8 --- /dev/null +++ b/core/lib/src/route/mod.rs @@ -0,0 +1,12 @@ +//! Types and traits for routes and their request handlers and return types. + +mod route; +mod handler; +mod uri; +mod segment; + +pub use route::*; +pub use handler::*; +pub use uri::*; + +pub(crate) use segment::Segment; diff --git a/core/lib/src/route/route.rs b/core/lib/src/route/route.rs new file mode 100644 index 00000000..01bb312c --- /dev/null +++ b/core/lib/src/route/route.rs @@ -0,0 +1,356 @@ +use std::fmt; +use std::convert::From; +use std::borrow::Cow; + +use yansi::Paint; + +use crate::http::{uri, Method, MediaType}; +use crate::route::{Handler, RouteUri, BoxFuture}; + +/// A request handling route. +/// +/// A route consists of exactly the information in its fields. While a `Route` +/// can be instantiated directly, doing so should be a rare or nonexistent +/// event. Instead, a Rocket application should use Rocket's +/// [`#[route]`](macro@crate::route) series of attributes to generate a `Route`. +/// +/// ```rust +/// # #[macro_use] extern crate rocket; +/// # use std::path::PathBuf; +/// #[get("/route/?query", rank = 2, format = "json")] +/// fn route_name(path: PathBuf) { /* handler procedure */ } +/// +/// use rocket::http::{Method, MediaType}; +/// +/// let route = routes![route_name].remove(0); +/// assert_eq!(route.name.unwrap(), "route_name"); +/// assert_eq!(route.method, Method::Get); +/// assert_eq!(route.uri, "/route/?query"); +/// assert_eq!(route.rank, 2); +/// assert_eq!(route.format.unwrap(), MediaType::JSON); +/// ``` +/// +/// Note that the `rank` and `format` attribute parameters are optional. See +/// [`#[route]`](macro@crate::route) for details on macro usage. Note also that +/// a route's mounted _base_ becomes part of its URI; see [`RouteUri`] for +/// details. +/// +/// # Routing +/// +/// A request _matches_ a route _iff_: +/// +/// * The route's method matches that of the incoming request. +/// * The route's format (if any) matches that of the incoming request. +/// - If route specifies a format, it only matches requests for that format. +/// - If route doesn't specify a format, it matches requests for any format. +/// - A route's `format` matches against the `Accept` header in the request +/// when the route's method [`supports_payload()`] and `Content-Type` +/// header otherwise. +/// - Non-specific `Accept` header components (`*`) match anything. +/// * All static components in the route's path match the corresponding +/// components in the same position in the incoming request. +/// * All static components in the route's query string are also in the +/// request query string, though in any position. If there is no query +/// in the route, requests with and without queries match. +/// +/// Rocket routes requests to matching routes. +/// +/// [`supports_payload()`]: Method::supports_payload() +/// +/// # Collisions +/// +/// Two routes are said to _collide_ if there exists a request that matches both +/// routes. Colliding routes present a routing ambiguity and are thus disallowed +/// by Rocket. Because routes can be constructed dynamically, collision checking +/// is done at [`ignite`](crate::Rocket::ignite()) time, after it becomes +/// statically impossible to add any more routes to an instance of `Rocket`. +/// +/// Note that because query parsing is always lenient -- extra and missing query +/// parameters are allowed -- queries do not directly impact whether two routes +/// collide. +/// +/// ## Resolving Collisions +/// +/// Collisions are resolved through _ranking_. Routes with lower ranks have +/// higher precedence during routing than routes with higher ranks. Thus, routes +/// are attempted in ascending rank order. If a higher precendence route returns +/// an `Outcome` of `Forward`, the next highest precedence route is attempted, +/// and so on, until a route returns `Success` or `Failure`, or there are no +/// more routes to try. When all routes have been attempted, Rocket issues a +/// `404` error, handled by the appropriate [`Catcher`](crate::Catcher). +/// +/// ## Default Ranking +/// +/// Most collisions are automatically resolved by Rocket's _default rank_. The +/// default rank prefers static components over dynamic components in both paths +/// and queries: the _more_ static a route's path and query are, the lower its +/// rank and thus the higher its precedence. +/// +/// There are three "colors" to paths and queries: +/// 1. `static` - all components are static +/// 2. `partial` - at least one, but not all, components are dynamic +/// 3. `wild` - all components are dynamic +/// +/// Static paths carry more weight than static queries. The same is true for +/// partial and wild paths. This results in the following default ranking +/// table: +/// +/// | path | query | rank | +/// |---------|---------|------| +/// | static | static | -12 | +/// | static | partial | -11 | +/// | static | wild | -10 | +/// | static | none | -9 | +/// | partial | static | -8 | +/// | partial | partial | -7 | +/// | partial | wild | -6 | +/// | partial | none | -5 | +/// | wild | static | -4 | +/// | wild | partial | -3 | +/// | wild | wild | -2 | +/// | wild | none | -1 | +/// +/// Recall that _lower_ ranks have _higher_ precedence. +/// +/// ### Example +/// +/// ```rust +/// use rocket::Route; +/// use rocket::http::Method; +/// +/// macro_rules! assert_rank { +/// ($($uri:expr => $rank:expr,)*) => {$( +/// let route = Route::new(Method::Get, $uri, rocket::route::dummy_handler); +/// assert_eq!(route.rank, $rank); +/// )*} +/// } +/// +/// assert_rank! { +/// "/?foo" => -12, // static path, static query +/// "/foo/bar?a=b&bob" => -12, // static path, static query +/// "/?a=b&bob" => -12, // static path, static query +/// +/// "/?a&" => -11, // static path, partial query +/// "/foo?a&" => -11, // static path, partial query +/// "/?a&" => -11, // static path, partial query +/// +/// "/?" => -10, // static path, wild query +/// "/foo?" => -10, // static path, wild query +/// "/foo?&" => -10, // static path, wild query +/// +/// "/" => -9, // static path, no query +/// "/foo/bar" => -9, // static path, no query +/// +/// "/a/?foo" => -8, // partial path, static query +/// "/a/?foo" => -8, // partial path, static query +/// "//b?foo" => -8, // partial path, static query +/// +/// "/a/?&c" => -7, // partial path, partial query +/// "/a/?a&" => -7, // partial path, partial query +/// +/// "/a/?" => -6, // partial path, wild query +/// "/a/?&" => -6, // partial path, wild query +/// "/a/?" => -6, // partial path, wild query +/// +/// "/a/" => -5, // partial path, no query +/// "//b" => -5, // partial path, no query +/// "/a/" => -5, // partial path, no query +/// +/// "//?foo&bar" => -4, // wild path, static query +/// "//?foo" => -4, // wild path, static query +/// "/?cat" => -4, // wild path, static query +/// +/// "//?&bar" => -3, // wild path, partial query +/// "//?a&" => -3, // wild path, partial query +/// "/?cat&" => -3, // wild path, partial query +/// +/// "//?" => -2, // wild path, wild query +/// "//?" => -2, // wild path, wild query +/// "/?&" => -2, // wild path, wild query +/// +/// "//" => -1, // wild path, no query +/// "//" => -1, // wild path, no query +/// "/" => -1, // wild path, no query +/// } +/// ``` +#[derive(Clone)] +pub struct Route { + /// The name of this route, if one was given. + pub name: Option>, + /// The method this route matches against. + pub method: Method, + /// The function that should be called when the route matches. + pub handler: Box, + /// The route URI. + pub uri: RouteUri<'static>, + /// The rank of this route. Lower ranks have higher priorities. + pub rank: isize, + /// The media type this route matches against, if any. + pub format: Option, +} + +impl Route { + /// Creates a new route with the given method, path, and handler with a base + /// of `/` and a computed [default rank](#default-ranking). + /// + /// # Panics + /// + /// Panics if `path` is not a valid Rocket route URI. + /// + /// # Example + /// + /// ```rust + /// use rocket::Route; + /// use rocket::http::Method; + /// # use rocket::route::dummy_handler as handler; + /// + /// // this is a rank 1 route matching requests to `GET /` + /// let index = Route::new(Method::Get, "/", handler); + /// assert_eq!(index.rank, -9); + /// assert_eq!(index.method, Method::Get); + /// assert_eq!(index.uri, "/"); + /// ``` + pub fn new(method: Method, uri: &str, handler: H) -> Route { + Route::ranked(None, method, uri, handler) + } + + /// Creates a new route with the given rank, method, path, and handler with + /// a base of `/`. If `rank` is `None`, the computed [default + /// rank](#default-ranking) is used. + /// + /// # Panics + /// + /// Panics if `path` is not a valid Rocket route URI. + /// + /// # Example + /// + /// ```rust + /// use rocket::Route; + /// use rocket::http::Method; + /// # use rocket::route::dummy_handler as handler; + /// + /// let foo = Route::ranked(1, Method::Post, "/foo?bar", handler); + /// assert_eq!(foo.rank, 1); + /// assert_eq!(foo.method, Method::Post); + /// assert_eq!(foo.uri, "/foo?bar"); + /// + /// let foo = Route::ranked(None, Method::Post, "/foo?bar", handler); + /// assert_eq!(foo.rank, -12); + /// assert_eq!(foo.method, Method::Post); + /// assert_eq!(foo.uri, "/foo?bar"); + /// ``` + pub fn ranked(rank: R, method: Method, uri: &str, handler: H) -> Route + where H: Handler + 'static, R: Into>, + { + let uri = RouteUri::new("/", uri); + let rank = rank.into().unwrap_or_else(|| uri.default_rank()); + Route { + name: None, + format: None, + handler: Box::new(handler), + rank, uri, method, + } + } + + /// Maps the `base` of this route using `mapper`, returning a new `Route` + /// with the returned base. + /// + /// `mapper` is called with the current base. The returned `String` is used + /// as the new base if it is a valid URI. If the returned base URI contains + /// a query, it is ignored. Returns an error if the base produced by + /// `mapper` is not a valid origin URI. + /// + /// # Example + /// + /// ```rust + /// use rocket::Route; + /// use rocket::http::{Method, uri::Origin}; + /// # use rocket::route::dummy_handler as handler; + /// + /// let index = Route::new(Method::Get, "/foo/bar", handler); + /// assert_eq!(index.uri.base(), "/"); + /// assert_eq!(index.uri.unmounted_origin.path(), "/foo/bar"); + /// assert_eq!(index.uri.path(), "/foo/bar"); + /// + /// let index = index.map_base(|base| format!("{}{}", "/boo", base)).unwrap(); + /// assert_eq!(index.uri.base(), "/boo"); + /// assert_eq!(index.uri.unmounted_origin.path(), "/foo/bar"); + /// assert_eq!(index.uri.path(), "/boo/foo/bar"); + /// ``` + pub fn map_base<'a, F>(mut self, mapper: F) -> Result> + where F: FnOnce(uri::Origin<'a>) -> String + { + let base = mapper(self.uri.base); + self.uri = RouteUri::try_new(&base, &self.uri.unmounted_origin.to_string())?; + Ok(self) + } +} + +impl fmt::Display for Route { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(ref n) = self.name { + write!(f, "{}{}{} ", Paint::cyan("("), Paint::white(n), Paint::cyan(")"))?; + } + + write!(f, "{} ", Paint::green(&self.method))?; + if self.uri.base() != "/" { + write!(f, "{}", Paint::blue(self.uri.base()).underline())?; + } + + write!(f, "{}", Paint::blue(&self.uri.unmounted_origin))?; + + if self.rank > 1 { + write!(f, " [{}]", Paint::default(&self.rank).bold())?; + } + + if let Some(ref format) = self.format { + write!(f, " {}", Paint::yellow(format))?; + } + + Ok(()) + } +} + +impl fmt::Debug for Route { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Route") + .field("name", &self.name) + .field("method", &self.method) + .field("uri", &self.uri) + .field("rank", &self.rank) + .field("format", &self.format) + .finish() + } +} + +/// Information generated by the `route` attribute during codegen. +#[doc(hidden)] +pub struct StaticInfo { + /// The route's name, i.e, the name of the function. + pub name: &'static str, + /// The route's method. + pub method: Method, + /// The route's path, without the base mount point. + pub path: &'static str, + /// The route's format, if any. + pub format: Option, + /// The route's handler, i.e, the annotated function. + pub handler: for<'r> fn(&'r crate::Request<'_>, crate::Data) -> BoxFuture<'r>, + /// The route's rank, if any. + pub rank: Option, +} + +#[doc(hidden)] +impl From for Route { + fn from(info: StaticInfo) -> Route { + // This should never panic since `info.path` is statically checked. + let mut route = Route::new(info.method, info.path, info.handler); + route.format = info.format; + route.name = Some(info.name.into()); + if let Some(rank) = info.rank { + route.rank = rank; + } + + route + } +} diff --git a/core/lib/src/router/segment.rs b/core/lib/src/route/segment.rs similarity index 100% rename from core/lib/src/router/segment.rs rename to core/lib/src/route/segment.rs diff --git a/core/lib/src/router/uri.rs b/core/lib/src/route/uri.rs similarity index 94% rename from core/lib/src/router/uri.rs rename to core/lib/src/route/uri.rs index ab0141a6..be6f647b 100644 --- a/core/lib/src/router/uri.rs +++ b/core/lib/src/route/uri.rs @@ -4,9 +4,9 @@ use std::borrow::Cow; use crate::http::uri::{self, Origin}; use crate::http::ext::IntoOwned; use crate::form::ValueField; -use crate::router::segment::Segment; +use crate::route::Segment; -/// A URI or a route, used to match against requests. +/// A route URI which is matched against requests. /// /// A route URI is composed of two components: /// @@ -21,7 +21,7 @@ use crate::router::segment::Segment; /// ```rust /// use rocket::Route; /// use rocket::http::Method; -/// # use rocket::handler::dummy as handler; +/// # use rocket::route::dummy_handler as handler; /// /// let route = Route::new(Method::Get, "/foo/", handler); /// assert_eq!(route.uri.base(), "/"); @@ -41,7 +41,7 @@ use crate::router::segment::Segment; /// ```rust /// use rocket::Route; /// use rocket::http::Method; -/// # use rocket::handler::dummy as handler; +/// # use rocket::route::dummy_handler as handler; /// /// let route = Route::new(Method::Get, "/foo/", handler); /// assert_eq!(route.uri, "/foo/"); @@ -95,7 +95,7 @@ pub(crate) struct Metadata { pub trailing_path: bool, } -type Result = std::result::Result>; +type Result> = std::result::Result; impl<'a> RouteUri<'a> { /// Create a new `RouteUri`. @@ -140,7 +140,7 @@ impl<'a> RouteUri<'a> { /// ```rust /// use rocket::Route; /// use rocket::http::Method; - /// # use rocket::handler::{dummy as handler, Outcome, HandlerFuture}; + /// # use rocket::route::dummy_handler as handler; /// /// let index = Route::new(Method::Get, "/foo/bar?a=1", handler); /// assert_eq!(index.uri.base(), "/"); @@ -159,7 +159,7 @@ impl<'a> RouteUri<'a> { /// ```rust /// use rocket::Route; /// use rocket::http::Method; - /// # use rocket::handler::{dummy as handler, Outcome, HandlerFuture}; + /// # use rocket::route::dummy_handler as handler; /// /// let index = Route::new(Method::Get, "/foo/bar?a=1", handler); /// assert_eq!(index.uri.path(), "/foo/bar"); @@ -178,7 +178,7 @@ impl<'a> RouteUri<'a> { /// ```rust /// use rocket::Route; /// use rocket::http::Method; - /// # use rocket::handler::{dummy as handler, Outcome, HandlerFuture}; + /// # use rocket::route::dummy_handler as handler; /// /// let index = Route::new(Method::Get, "/foo/bar?a=1", handler); /// assert_eq!(index.uri.query(), Some("a=1")); @@ -197,7 +197,7 @@ impl<'a> RouteUri<'a> { /// ```rust /// use rocket::Route; /// use rocket::http::Method; - /// # use rocket::handler::{dummy as handler, Outcome, HandlerFuture}; + /// # use rocket::route::dummy_handler as handler; /// /// let index = Route::new(Method::Get, "/foo/bar?a=1", handler); /// assert_eq!(index.uri.as_str(), "/foo/bar?a=1"); diff --git a/core/lib/src/router/collider.rs b/core/lib/src/router/collider.rs index dd15f371..1b6d82b6 100644 --- a/core/lib/src/router/collider.rs +++ b/core/lib/src/router/collider.rs @@ -1,5 +1,5 @@ -use super::{Route, uri::Color}; use crate::catcher::Catcher; +use crate::route::{Route, Color}; use crate::http::{MediaType, Status}; use crate::request::Request; @@ -44,18 +44,17 @@ fn paths_collide(route: &Route, other: &Route) -> bool { } fn formats_collide(route: &Route, other: &Route) -> bool { - // When matching against the `Accept` header, the client can always - // provide a media type that will cause a collision through - // non-specificity, i.e, `*/*` matches everything. + // When matching against the `Accept` header, the client can always provide + // a media type that will cause a collision through non-specificity, i.e, + // `*/*` matches everything. if !route.method.supports_payload() { return true; } - // When matching against the `Content-Type` header, we'll only - // consider requests as having a `Content-Type` if they're fully - // specified. If a route doesn't have a `format`, it accepts all - // `Content-Type`s. If a request doesn't have a format, it only - // matches routes without a format. + // When matching against the `Content-Type` header, we'll only consider + // requests as having a `Content-Type` if they're fully specified. If a + // route doesn't have a `format`, it accepts all `Content-Type`s. If a + // request doesn't have a format, it only matches routes without a format. match (route.format.as_ref(), other.format.as_ref()) { (Some(a), Some(b)) => a.collides_with(b), _ => true @@ -203,31 +202,28 @@ mod tests { use std::str::FromStr; use super::*; - use crate::rocket::Rocket; - use crate::config::Config; - use crate::request::Request; - use crate::router::route::Route; + use crate::route::{Route, dummy_handler}; + use crate::local::blocking::Client; use crate::http::{Method, Method::*, MediaType, ContentType, Accept}; use crate::http::uri::Origin; - use crate::handler::dummy; type SimpleRoute = (Method, &'static str); fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool { - let route_a = Route::new(a.0, a.1, dummy); - route_a.collides_with(&Route::new(b.0, b.1, dummy)) + let route_a = Route::new(a.0, a.1, dummy_handler); + route_a.collides_with(&Route::new(b.0, b.1, dummy_handler)) } fn unranked_collide(a: &'static str, b: &'static str) -> bool { - let route_a = Route::ranked(0, Get, a, dummy); - let route_b = Route::ranked(0, Get, b, dummy); + let route_a = Route::ranked(0, Get, a, dummy_handler); + let route_b = Route::ranked(0, Get, b, dummy_handler); eprintln!("Checking {} against {}.", route_a, route_b); route_a.collides_with(&route_b) } fn s_s_collide(a: &'static str, b: &'static str) -> bool { - let a = Route::new(Get, a, dummy); - let b = Route::new(Get, b, dummy); + let a = Route::new(Get, a, dummy_handler); + let b = Route::new(Get, b, dummy_handler); paths_collide(&a, &b) } @@ -402,12 +398,12 @@ mod tests { fn r_mt_mt_collide(m: Method, mt1: S1, mt2: S2) -> bool where S1: Into>, S2: Into> { - let mut route_a = Route::new(m, "/", dummy); + let mut route_a = Route::new(m, "/", dummy_handler); if let Some(mt_str) = mt1.into() { route_a.format = Some(mt_str.parse::().unwrap()); } - let mut route_b = Route::new(m, "/", dummy); + let mut route_b = Route::new(m, "/", dummy_handler); if let Some(mt_str) = mt2.into() { route_b.format = Some(mt_str.parse::().unwrap()); } @@ -460,8 +456,8 @@ mod tests { fn req_route_mt_collide(m: Method, mt1: S1, mt2: S2) -> bool where S1: Into>, S2: Into> { - let rocket = Rocket::custom(Config::default()); - let mut req = Request::new(&rocket, m, Origin::dummy()); + let client = Client::debug_with(vec![]).expect("client"); + let mut req = client.req(m, "/"); if let Some(mt_str) = mt1.into() { if m.supports_payload() { req.replace_header(mt_str.parse::().unwrap()); @@ -470,7 +466,7 @@ mod tests { } } - let mut route = Route::new(m, "/", dummy); + let mut route = Route::new(m, "/", dummy_handler); if let Some(mt_str) = mt2.into() { route.format = Some(mt_str.parse::().unwrap()); } @@ -527,9 +523,9 @@ mod tests { } fn req_route_path_match(a: &'static str, b: &'static str) -> bool { - let rocket = Rocket::custom(Config::default()); - let req = Request::new(&rocket, Get, Origin::parse(a).expect("valid URI")); - let route = Route::ranked(0, Get, b, dummy); + let client = Client::debug_with(vec![]).expect("client"); + let req = client.get(Origin::parse(a).expect("valid URI")); + let route = Route::ranked(0, Get, b, dummy_handler); route.matches(&req) } @@ -573,8 +569,10 @@ mod tests { fn catchers_collide(a: A, ap: &str, b: B, bp: &str) -> bool where A: Into>, B: Into> { - let a = Catcher::new(a, crate::catcher::dummy).map_base(|_| ap.into()).unwrap(); - let b = Catcher::new(b, crate::catcher::dummy).map_base(|_| bp.into()).unwrap(); + use crate::catcher::dummy_handler as handler; + + let a = Catcher::new(a, handler).map_base(|_| ap.into()).unwrap(); + let b = Catcher::new(b, handler).map_base(|_| bp.into()).unwrap(); a.collides_with(&b) } diff --git a/core/lib/src/router/mod.rs b/core/lib/src/router/mod.rs index 77b672b3..dc1a6621 100644 --- a/core/lib/src/router/mod.rs +++ b/core/lib/src/router/mod.rs @@ -1,13 +1,7 @@ -//! Routing types: [`Route`] and [`RouteUri`]. +//! Rocket's router. -mod route; -mod segment; -mod uri; mod router; mod collider; pub(crate) use router::*; - -pub use route::Route; -pub use collider::Collide; -pub use uri::RouteUri; +pub(crate) use collider::*; diff --git a/core/lib/src/router/route.rs b/core/lib/src/router/route.rs deleted file mode 100644 index 4800d3c9..00000000 --- a/core/lib/src/router/route.rs +++ /dev/null @@ -1,249 +0,0 @@ -use std::fmt; -use std::convert::From; -use std::borrow::Cow; - -use yansi::Paint; - -use crate::codegen::StaticRouteInfo; -use crate::handler::Handler; -use crate::http::{uri, Method, MediaType}; -use crate::router::RouteUri; - -/// A route: a method, its handler, path, rank, and format/media type. -#[derive(Clone)] -pub struct Route { - /// The name of this route, if one was given. - pub name: Option>, - /// The method this route matches against. - pub method: Method, - /// The function that should be called when the route matches. - pub handler: Box, - /// The route URI. - pub uri: RouteUri<'static>, - /// The rank of this route. Lower ranks have higher priorities. - pub rank: isize, - /// The media type this route matches against, if any. - pub format: Option, -} - -impl Route { - /// Creates a new route with the given method, path, and handler with a base - /// of `/`. - /// - /// # Ranking - /// - /// The default rank prefers static components over dynamic components in - /// both paths and queries: the _more_ static a route's path and query are, - /// the higher its precedence. - /// - /// There are three "colors" to paths and queries: - /// 1. `static`, meaning all components are static - /// 2. `partial`, meaning at least one component is dynamic - /// 3. `wild`, meaning all components are dynamic - /// - /// Static paths carry more weight than static queries. The same is true for - /// partial and wild paths. This results in the following default ranking - /// table: - /// - /// | path | query | rank | - /// |---------|---------|------| - /// | static | static | -12 | - /// | static | partial | -11 | - /// | static | wild | -10 | - /// | static | none | -9 | - /// | partial | static | -8 | - /// | partial | partial | -7 | - /// | partial | wild | -6 | - /// | partial | none | -5 | - /// | wild | static | -4 | - /// | wild | partial | -3 | - /// | wild | wild | -2 | - /// | wild | none | -1 | - /// - /// Note that _lower_ ranks have _higher_ precedence. - /// - /// # Example - /// - /// ```rust - /// use rocket::Route; - /// use rocket::http::Method; - /// # use rocket::handler::{dummy as handler}; - /// - /// macro_rules! assert_rank { - /// ($($uri:expr => $rank:expr,)*) => {$( - /// let route = Route::new(Method::Get, $uri, handler); - /// assert_eq!(route.rank, $rank); - /// )*} - /// } - /// - /// assert_rank! { - /// "/?foo" => -12, // static path, static query - /// "/foo/bar?a=b&bob" => -12, // static path, static query - /// "/?a=b&bob" => -12, // static path, static query - /// - /// "/?a&" => -11, // static path, partial query - /// "/foo?a&" => -11, // static path, partial query - /// "/?a&" => -11, // static path, partial query - /// - /// "/?" => -10, // static path, wild query - /// "/foo?" => -10, // static path, wild query - /// "/foo?&" => -10, // static path, wild query - /// - /// "/" => -9, // static path, no query - /// "/foo/bar" => -9, // static path, no query - /// - /// "/a/?foo" => -8, // partial path, static query - /// "/a/?foo" => -8, // partial path, static query - /// "//b?foo" => -8, // partial path, static query - /// - /// "/a/?&c" => -7, // partial path, partial query - /// "/a/?a&" => -7, // partial path, partial query - /// - /// "/a/?" => -6, // partial path, wild query - /// "/a/?&" => -6, // partial path, wild query - /// "/a/?" => -6, // partial path, wild query - /// - /// "/a/" => -5, // partial path, no query - /// "//b" => -5, // partial path, no query - /// "/a/" => -5, // partial path, no query - /// - /// "//?foo&bar" => -4, // wild path, static query - /// "//?foo" => -4, // wild path, static query - /// "/?cat" => -4, // wild path, static query - /// - /// "//?&bar" => -3, // wild path, partial query - /// "//?a&" => -3, // wild path, partial query - /// "/?cat&" => -3, // wild path, partial query - /// - /// "//?" => -2, // wild path, wild query - /// "//?" => -2, // wild path, wild query - /// "/?&" => -2, // wild path, wild query - /// - /// "//" => -1, // wild path, no query - /// "//" => -1, // wild path, no query - /// "/" => -1, // wild path, no query - /// } - /// ``` - /// - /// # Panics - /// - /// Panics if `path` is not a valid origin URI or Rocket route URI. - pub fn new(method: Method, uri: &str, handler: H) -> Route { - Route::ranked(None, method, uri, handler) - } - - /// Creates a new route with the given rank, method, path, and handler with - /// a base of `/`. - /// - /// # Example - /// - /// ```rust - /// use rocket::Route; - /// use rocket::http::Method; - /// # use rocket::handler::{dummy as handler}; - /// - /// // this is a rank 1 route matching requests to `GET /` - /// let index = Route::ranked(1, Method::Get, "/", handler); - /// ``` - /// - /// # Panics - /// - /// Panics if `path` is not a valid origin URI or Rocket route URI. - pub fn ranked(rank: R, method: Method, uri: &str, handler: H) -> Route - where H: Handler + 'static, R: Into>, - { - let uri = RouteUri::new("/", uri); - let rank = rank.into().unwrap_or_else(|| uri.default_rank()); - Route { - name: None, - format: None, - handler: Box::new(handler), - rank, uri, method, - } - } - - /// Maps the `base` of this route using `mapper`, returning a new `Route` - /// with the returned base. - /// - /// `mapper` is called with the current base. The returned `String` is used - /// as the new base if it is a valid URI. If the returned base URI contains - /// a query, it is ignored. Returns an error if the base produced by - /// `mapper` is not a valid origin URI. - /// - /// # Example - /// - /// ```rust - /// use rocket::Route; - /// use rocket::http::{Method, uri::Origin}; - /// # use rocket::handler::{dummy as handler, Outcome, HandlerFuture}; - /// - /// let index = Route::new(Method::Get, "/foo/bar", handler); - /// assert_eq!(index.uri.base(), "/"); - /// assert_eq!(index.uri.unmounted_origin.path(), "/foo/bar"); - /// assert_eq!(index.uri.path(), "/foo/bar"); - /// - /// let index = index.map_base(|base| format!("{}{}", "/boo", base)).unwrap(); - /// assert_eq!(index.uri.base(), "/boo"); - /// assert_eq!(index.uri.unmounted_origin.path(), "/foo/bar"); - /// assert_eq!(index.uri.path(), "/boo/foo/bar"); - /// ``` - pub fn map_base<'a, F>(mut self, mapper: F) -> Result> - where F: FnOnce(uri::Origin<'a>) -> String - { - let base = mapper(self.uri.base); - self.uri = RouteUri::try_new(&base, &self.uri.unmounted_origin.to_string())?; - Ok(self) - } -} - -impl fmt::Display for Route { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(ref n) = self.name { - write!(f, "{}{}{} ", Paint::cyan("("), Paint::white(n), Paint::cyan(")"))?; - } - - write!(f, "{} ", Paint::green(&self.method))?; - if self.uri.base() != "/" { - write!(f, "{}", Paint::blue(self.uri.base()).underline())?; - } - - write!(f, "{}", Paint::blue(&self.uri.unmounted_origin))?; - - if self.rank > 1 { - write!(f, " [{}]", Paint::default(&self.rank).bold())?; - } - - if let Some(ref format) = self.format { - write!(f, " {}", Paint::yellow(format))?; - } - - Ok(()) - } -} - -impl fmt::Debug for Route { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Route") - .field("name", &self.name) - .field("method", &self.method) - .field("uri", &self.uri) - .field("rank", &self.rank) - .field("format", &self.format) - .finish() - } -} - -#[doc(hidden)] -impl From for Route { - fn from(info: StaticRouteInfo) -> Route { - // This should never panic since `info.path` is statically checked. - let mut route = Route::new(info.method, info.path, info.handler); - route.format = info.format; - route.name = Some(info.name.into()); - if let Some(rank) = info.rank { - route.rank = rank; - } - - route - } -} diff --git a/core/lib/src/router/router.rs b/core/lib/src/router/router.rs index 01c3a3e7..62f93075 100644 --- a/core/lib/src/router/router.rs +++ b/core/lib/src/router/router.rs @@ -3,9 +3,8 @@ use std::collections::HashMap; use crate::request::Request; use crate::http::{Method, Status}; -pub use crate::router::{Route, RouteUri}; -pub use crate::router::collider::Collide; -pub use crate::catcher::Catcher; +use crate::{Route, Catcher}; +use crate::router::Collide; #[derive(Debug, Default)] pub(crate) struct Router { @@ -106,12 +105,9 @@ impl Router { mod test { use super::*; - use crate::rocket::Rocket; - use crate::config::Config; - use crate::http::{Method, Method::*}; - use crate::http::uri::Origin; - use crate::request::Request; - use crate::handler::dummy; + use crate::route::dummy_handler; + use crate::local::blocking::Client; + use crate::http::{Method, Method::*, uri::Origin}; impl Router { fn has_collisions(&self) -> bool { @@ -122,7 +118,7 @@ mod test { fn router_with_routes(routes: &[&'static str]) -> Router { let mut router = Router::new(); for route in routes { - let route = Route::new(Get, route, dummy); + let route = Route::new(Get, route, dummy_handler); router.add_route(route); } @@ -132,7 +128,7 @@ mod test { fn router_with_ranked_routes(routes: &[(isize, &'static str)]) -> Router { let mut router = Router::new(); for &(rank, route) in routes { - let route = Route::ranked(rank, Get, route, dummy); + let route = Route::ranked(rank, Get, route, dummy_handler); router.add_route(route); } @@ -142,7 +138,7 @@ mod test { fn router_with_rankless_routes(routes: &[&'static str]) -> Router { let mut router = Router::new(); for route in routes { - let route = Route::ranked(0, Get, route, dummy); + let route = Route::ranked(0, Get, route, dummy_handler); router.add_route(route); } @@ -290,17 +286,14 @@ mod test { assert!(!default_rank_route_collisions(&["/?a=b", "/?c=d&"])); } - fn route<'a>(router: &'a Router, method: Method, uri: &'a str) -> Option<&'a Route> { - let rocket = Rocket::custom(Config::default()); - let request = Request::new(&rocket, method, Origin::parse(uri).unwrap()); - let route = router.route(&request).next(); - route + fn matches<'a>(router: &'a Router, method: Method, uri: &'a str) -> Vec<&'a Route> { + let client = Client::debug_with(vec![]).expect("client"); + let request = client.req(method, Origin::parse(uri).unwrap()); + router.route(&request).collect() } - fn matches<'a>(router: &'a Router, method: Method, uri: &'a str) -> Vec<&'a Route> { - let rocket = Rocket::custom(Config::default()); - let request = Request::new(&rocket, method, Origin::parse(uri).unwrap()); - router.route(&request).collect() + fn route<'a>(router: &'a Router, method: Method, uri: &'a str) -> Option<&'a Route> { + matches(router, method, uri).into_iter().next() } #[test] @@ -321,9 +314,9 @@ mod test { assert!(route(&router, Get, "/jdlk/asdij").is_some()); let mut router = Router::new(); - router.add_route(Route::new(Put, "/hello", dummy)); - router.add_route(Route::new(Post, "/hello", dummy)); - router.add_route(Route::new(Delete, "/hello", dummy)); + router.add_route(Route::new(Put, "/hello", dummy_handler)); + router.add_route(Route::new(Post, "/hello", dummy_handler)); + router.add_route(Route::new(Delete, "/hello", dummy_handler)); assert!(route(&router, Put, "/hello").is_some()); assert!(route(&router, Post, "/hello").is_some()); assert!(route(&router, Delete, "/hello").is_some()); @@ -558,7 +551,7 @@ mod test { fn router_with_catchers(catchers: &[(Option, &str)]) -> Router { let mut router = Router::new(); for (code, base) in catchers { - let catcher = Catcher::new(*code, crate::catcher::dummy); + let catcher = Catcher::new(*code, crate::catcher::dummy_handler); router.add_catcher(catcher.map_base(|_| base.to_string()).unwrap()); } @@ -566,8 +559,8 @@ mod test { } fn catcher<'a>(router: &'a Router, status: Status, uri: &str) -> Option<&'a Catcher> { - let rocket = Rocket::custom(Config::default()); - let request = Request::new(&rocket, Method::Get, Origin::parse(uri).unwrap()); + let client = Client::debug_with(vec![]).expect("client"); + let request = client.get(Origin::parse(uri).unwrap()); router.catch(status, &request) } diff --git a/core/lib/src/server.rs b/core/lib/src/server.rs index 5c599cc0..35c88fb2 100644 --- a/core/lib/src/server.rs +++ b/core/lib/src/server.rs @@ -6,7 +6,7 @@ use futures::future::{self, FutureExt, Future, TryFutureExt, BoxFuture}; use tokio::sync::oneshot; use yansi::Paint; -use crate::{Rocket, Request, Data, handler}; +use crate::{Rocket, Request, Data, route}; use crate::form::Form; use crate::response::{Response, Body}; use crate::outcome::Outcome; @@ -284,7 +284,7 @@ impl Rocket { &'s self, request: &'r Request<'s>, mut data: Data, - ) -> handler::Outcome<'r> { + ) -> route::Outcome<'r> { // Go through the list of matching routes until we fail or succeed. for route in self.router.route(request) { // Retrieve and set the requests parameters. @@ -338,7 +338,7 @@ impl Rocket { } else { let code = Paint::blue(status.code).bold(); warn_!("No {} catcher registered. Using Rocket default.", code); - Ok(crate::catcher::default(status, req)) + Ok(crate::catcher::default_handler(status, req)) } } @@ -367,7 +367,7 @@ impl Rocket { // If it failed again or if it was already a 500, use Rocket's default. error_!("{} catcher failed. Using Rocket default 500.", status.code); - crate::catcher::default(Status::InternalServerError, req) + crate::catcher::default_handler(Status::InternalServerError, req) } pub async fn default_tcp_http_server(mut self, ready: C) -> Result<(), Error> diff --git a/core/lib/tests/panic-handling.rs b/core/lib/tests/panic-handling.rs index 969420ec..1270f1a1 100644 --- a/core/lib/tests/panic-handling.rs +++ b/core/lib/tests/panic-handling.rs @@ -1,11 +1,9 @@ #[macro_use] extern crate rocket; -use rocket::{Rocket, Route, Request}; +use rocket::{Request, Rocket, Route, Catcher, route, catcher}; use rocket::data::Data; use rocket::http::{Method, Status}; use rocket::local::blocking::Client; -use rocket::catcher::{Catcher, ErrorHandlerFuture}; -use rocket::handler::HandlerFuture; #[get("/panic")] fn panic_route() -> &'static str { @@ -22,7 +20,7 @@ fn ise() -> &'static str { "Hey, sorry! :(" } -fn pre_future_route<'r>(_: &'r Request<'_>, _: Data) -> HandlerFuture<'r> { +fn pre_future_route<'r>(_: &'r Request<'_>, _: Data) -> route::BoxFuture<'r> { panic!("hey now..."); } @@ -75,7 +73,7 @@ fn catches_early_route_panic() { #[test] fn catches_early_catcher_panic() { - fn pre_future_catcher<'r>(_: Status, _: &'r Request) -> ErrorHandlerFuture<'r> { + fn pre_future_catcher<'r>(_: Status, _: &'r Request) -> catcher::BoxFuture<'r> { panic!("a panicking pre-future catcher") } diff --git a/examples/manual-routing/src/main.rs b/examples/manual-routing/src/main.rs index 4fb79477..a7921bda 100644 --- a/examples/manual-routing/src/main.rs +++ b/examples/manual-routing/src/main.rs @@ -3,68 +3,66 @@ mod tests; use std::env; -use rocket::{Request, Route}; +use rocket::{Request, Route, Catcher, route, catcher}; use rocket::data::{Data, ToByteUnit}; use rocket::http::{Status, Method::*}; use rocket::response::{Responder, status::Custom}; -use rocket::handler::{Handler, Outcome, HandlerFuture}; -use rocket::catcher::{Catcher, ErrorHandlerFuture}; use rocket::outcome::{try_outcome, IntoOutcome}; use rocket::tokio::fs::File; -fn forward<'r>(_req: &'r Request, data: Data) -> HandlerFuture<'r> { - Box::pin(async move { Outcome::forward(data) }) +fn forward<'r>(_req: &'r Request, data: Data) -> route::BoxFuture<'r> { + Box::pin(async move { route::Outcome::forward(data) }) } -fn hi<'r>(req: &'r Request, _: Data) -> HandlerFuture<'r> { - Outcome::from(req, "Hello!").pin() +fn hi<'r>(req: &'r Request, _: Data) -> route::BoxFuture<'r> { + route::Outcome::from(req, "Hello!").pin() } -fn name<'a>(req: &'a Request, _: Data) -> HandlerFuture<'a> { +fn name<'a>(req: &'a Request, _: Data) -> route::BoxFuture<'a> { let param = req.param::<&'a str>(0) .and_then(|res| res.ok()) .unwrap_or("unnamed".into()); - Outcome::from(req, param).pin() + route::Outcome::from(req, param).pin() } -fn echo_url<'r>(req: &'r Request, _: Data) -> HandlerFuture<'r> { +fn echo_url<'r>(req: &'r Request, _: Data) -> route::BoxFuture<'r> { let param_outcome = req.param::<&str>(1) .and_then(|res| res.ok()) .into_outcome(Status::BadRequest); Box::pin(async move { - Outcome::from(req, try_outcome!(param_outcome)) + route::Outcome::from(req, try_outcome!(param_outcome)) }) } -fn upload<'r>(req: &'r Request, data: Data) -> HandlerFuture<'r> { +fn upload<'r>(req: &'r Request, data: Data) -> route::BoxFuture<'r> { Box::pin(async move { if !req.content_type().map_or(false, |ct| ct.is_plain()) { println!(" => Content-Type of upload must be text/plain. Ignoring."); - return Outcome::failure(Status::BadRequest); + return route::Outcome::failure(Status::BadRequest); } let file = File::create(env::temp_dir().join("upload.txt")).await; if let Ok(file) = file { if let Ok(n) = data.open(2.mebibytes()).stream_to(file).await { - return Outcome::from(req, format!("OK: {} bytes uploaded.", n)); + return route::Outcome::from(req, format!("OK: {} bytes uploaded.", n)); } println!(" => Failed copying."); - Outcome::failure(Status::InternalServerError) + route::Outcome::failure(Status::InternalServerError) } else { println!(" => Couldn't open file: {:?}", file.unwrap_err()); - Outcome::failure(Status::InternalServerError) + route::Outcome::failure(Status::InternalServerError) } }) } -fn get_upload<'r>(req: &'r Request, _: Data) -> HandlerFuture<'r> { - Outcome::from(req, std::fs::File::open(env::temp_dir().join("upload.txt")).ok()).pin() +fn get_upload<'r>(req: &'r Request, _: Data) -> route::BoxFuture<'r> { + route::Outcome::from(req, std::fs::File::open(env::temp_dir().join("upload.txt")).ok()).pin() } -fn not_found_handler<'r>(_: Status, req: &'r Request) -> ErrorHandlerFuture<'r> { +fn not_found_handler<'r>(_: Status, req: &'r Request) -> catcher::BoxFuture<'r> { let res = Custom(Status::NotFound, format!("Couldn't find: {}", req.uri())); Box::pin(async move { res.respond_to(req) }) } @@ -81,14 +79,14 @@ impl CustomHandler { } #[rocket::async_trait] -impl Handler for CustomHandler { - async fn handle<'r, 's: 'r>(&'s self, req: &'r Request<'_>, data: Data) -> Outcome<'r> { +impl route::Handler for CustomHandler { + async fn handle<'r, 's: 'r>(&'s self, req: &'r Request<'_>, data: Data) -> route::Outcome<'r> { let self_data = self.data; let id = req.param::<&str>(0) .and_then(|res| res.ok()) .or_forward(data); - Outcome::from(req, format!("{} - {}", self_data, try_outcome!(id))) + route::Outcome::from(req, format!("{} - {}", self_data, try_outcome!(id))) } }