Move catcher, route types into eponymous modules.

In the course, significantly improve their documentation.
This commit is contained in:
Sergio Benitez 2021-04-13 18:58:05 -07:00
parent 4c96ae7b52
commit 887b2aed87
26 changed files with 793 additions and 708 deletions

View File

@ -16,10 +16,10 @@
use std::path::{PathBuf, Path}; use std::path::{PathBuf, Path};
use rocket::{Request, Data, Route}; use rocket::{Request, Data};
use rocket::http::{Method, uri::Segments, ext::IntoOwned}; use rocket::http::{Method, uri::Segments, ext::IntoOwned};
use rocket::handler::{Handler, Outcome};
use rocket::response::{NamedFile, Redirect}; use rocket::response::{NamedFile, Redirect};
use rocket::route::{Route, Handler, Outcome};
/// Generates a crate-relative version of `$path`. /// Generates a crate-relative version of `$path`.
/// ///

View File

@ -62,12 +62,12 @@ pub fn _catch(
#vis struct #user_catcher_fn_name { } #vis struct #user_catcher_fn_name { }
/// Rocket code generated proxy static conversion implementation. /// Rocket code generated proxy static conversion implementation.
impl From<#user_catcher_fn_name> for #StaticCatcherInfo { impl From<#user_catcher_fn_name> for #_catcher::StaticInfo {
fn from(_: #user_catcher_fn_name) -> #StaticCatcherInfo { fn from(_: #user_catcher_fn_name) -> #_catcher::StaticInfo {
fn monomorphized_function<'_b>( fn monomorphized_function<'_b>(
#__status: #Status, #__status: #Status,
#__req: &'_b #Request<'_> #__req: &'_b #Request<'_>
) -> #ErrorHandlerFuture<'_b> { ) -> #_catcher::BoxFuture<'_b> {
#_Box::pin(async move { #_Box::pin(async move {
let __response = #catcher_response; let __response = #catcher_response;
#Response::build() #Response::build()
@ -77,7 +77,7 @@ pub fn _catch(
}) })
} }
#StaticCatcherInfo { #_catcher::StaticInfo {
name: stringify!(#user_catcher_fn_name), name: stringify!(#user_catcher_fn_name),
code: #status_code, code: #status_code,
handler: monomorphized_function, handler: monomorphized_function,
@ -89,7 +89,7 @@ pub fn _catch(
impl From<#user_catcher_fn_name> for #Catcher { impl From<#user_catcher_fn_name> for #Catcher {
#[inline] #[inline]
fn from(_: #user_catcher_fn_name) -> #Catcher { fn from(_: #user_catcher_fn_name) -> #Catcher {
#StaticCatcherInfo::from(#user_catcher_fn_name {}).into() #_catcher::StaticInfo::from(#user_catcher_fn_name {}).into()
} }
} }
}) })

View File

@ -222,10 +222,10 @@ fn responder_outcome_expr(route: &Route) -> TokenStream {
let _await = route.handler.sig.asyncness let _await = route.handler.sig.asyncness
.map(|a| quote_spanned!(a.span().into() => .await)); .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 => quote_spanned! { ret_span =>
let ___responder = #user_handler_fn_name(#(#parameter_names),*) #_await; 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<TokenStream> {
#vis struct #handler_fn_name { } #vis struct #handler_fn_name { }
/// Rocket code generated proxy static conversion implementation. /// 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)] #[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>( fn monomorphized_function<'_b>(
#__req: &'_b #Request<'_>, #__req: &'_b #Request<'_>,
#__data: #Data #__data: #Data
) -> #HandlerFuture<'_b> { ) -> #_route::BoxFuture<'_b> {
#_Box::pin(async move { #_Box::pin(async move {
#(#request_guards)* #(#request_guards)*
#(#param_guards)* #(#param_guards)*
@ -275,7 +275,7 @@ fn codegen_route(route: Route) -> Result<TokenStream> {
}) })
} }
#StaticRouteInfo { #_route::StaticInfo {
name: stringify!(#handler_fn_name), name: stringify!(#handler_fn_name),
method: #method, method: #method,
path: #path, path: #path,
@ -290,7 +290,7 @@ fn codegen_route(route: Route) -> Result<TokenStream> {
impl From<#handler_fn_name> for #Route { impl From<#handler_fn_name> for #Route {
#[inline] #[inline]
fn from(_: #handler_fn_name) -> #Route { fn from(_: #handler_fn_name) -> #Route {
#StaticRouteInfo::from(#handler_fn_name {}).into() #_route::StaticInfo::from(#handler_fn_name {}).into()
} }
} }

View File

@ -67,13 +67,14 @@ define_exported_paths! {
__data => __data, __data => __data,
__error => __error, __error => __error,
__trail => __trail, __trail => __trail,
_request => rocket::request, _request => ::rocket::request,
_response => rocket::response, _response => ::rocket::response,
_handler => rocket::handler, _route => ::rocket::route,
_log => rocket::logger, _catcher => ::rocket::catcher,
_form => rocket::form::prelude, _log => ::rocket::logger,
_http => rocket::http, _form => ::rocket::form::prelude,
_uri => rocket::http::uri, _http => ::rocket::http,
_uri => ::rocket::http::uri,
_Option => ::std::option::Option, _Option => ::std::option::Option,
_Result => ::std::result::Result, _Result => ::std::result::Result,
_Some => ::std::option::Option::Some, _Some => ::std::option::Option::Some,
@ -84,23 +85,21 @@ define_exported_paths! {
_Vec => ::std::vec::Vec, _Vec => ::std::vec::Vec,
_Cow => ::std::borrow::Cow, _Cow => ::std::borrow::Cow,
BorrowMut => ::std::borrow::BorrowMut, BorrowMut => ::std::borrow::BorrowMut,
Outcome => rocket::outcome::Outcome, Outcome => ::rocket::outcome::Outcome,
FromForm => rocket::form::FromForm, FromForm => ::rocket::form::FromForm,
FromRequest => rocket::request::FromRequest, FromRequest => ::rocket::request::FromRequest,
FromData => rocket::data::FromData, FromData => ::rocket::data::FromData,
FromSegments => rocket::request::FromSegments, FromSegments => ::rocket::request::FromSegments,
FromParam => rocket::request::FromParam, FromParam => ::rocket::request::FromParam,
Request => rocket::request::Request, Request => ::rocket::request::Request,
Response => rocket::response::Response, Response => ::rocket::response::Response,
Data => rocket::data::Data, Data => ::rocket::data::Data,
StaticRouteInfo => rocket::StaticRouteInfo, StaticRouteInfo => ::rocket::StaticRouteInfo,
StaticCatcherInfo => rocket::StaticCatcherInfo, StaticCatcherInfo => ::rocket::StaticCatcherInfo,
Route => rocket::Route, Route => ::rocket::Route,
Catcher => rocket::Catcher, Catcher => ::rocket::Catcher,
SmallVec => rocket::http::private::SmallVec, SmallVec => ::rocket::http::private::SmallVec,
Status => rocket::http::Status, Status => ::rocket::http::Status,
HandlerFuture => rocket::handler::HandlerFuture,
ErrorHandlerFuture => rocket::catcher::ErrorHandlerFuture,
} }
macro_rules! define_spanned_export { macro_rules! define_spanned_export {

View File

@ -262,7 +262,7 @@ macro_rules! route_attribute {
/// 3. A macro used by [`uri!`] to type-check and generate an /// 3. A macro used by [`uri!`] to type-check and generate an
/// [`Origin`]. /// [`Origin`].
/// ///
/// [`Handler`]: rocket::handler::Handler /// [`Handler`]: rocket::route::Handler
/// [`routes!`]: macro.routes.html /// [`routes!`]: macro.routes.html
/// [`uri!`]: macro.uri.html /// [`uri!`]: macro.uri.html
/// [`Origin`]: rocket::http::uri::Origin /// [`Origin`]: rocket::http::uri::Origin
@ -330,7 +330,7 @@ route_attribute!(options => Method::Options);
/// ///
/// The attribute generates two items: /// The attribute generates two items:
/// ///
/// 1. An [`ErrorHandler`]. /// 1. An error [`Handler`].
/// ///
/// The generated handler calls the decorated function, passing in the /// The generated handler calls the decorated function, passing in the
/// [`Status`] and [`&Request`] values if requested. The returned value is /// [`Status`] and [`&Request`] values if requested. The returned value is
@ -345,7 +345,7 @@ route_attribute!(options => Method::Options);
/// ///
/// [`&Request`]: rocket::Request /// [`&Request`]: rocket::Request
/// [`Status`]: rocket::http::Status /// [`Status`]: rocket::http::Status
/// [`ErrorHandler`]: rocket::catcher::ErrorHandler /// [`Handler`]: rocket::catcher::Handler
/// [`catchers!`]: macro.catchers.html /// [`catchers!`]: macro.catchers.html
/// [`Catcher`]: rocket::Catcher /// [`Catcher`]: rocket::Catcher
/// [`Response`]: rocket::Response /// [`Response`]: rocket::Response

View File

@ -53,4 +53,4 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied
28 | fn foo() -> usize { 0 } 28 | fn foo() -> usize { 0 }
| ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize` | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize`
| |
= note: required by `handler::<impl Outcome<rocket::Response<'o>, Status, rocket::Data>>::from` = note: required by `route::handler::<impl Outcome<rocket::Response<'o>, Status, rocket::Data>>::from`

View File

@ -53,4 +53,4 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied
28 | fn foo() -> usize { 0 } 28 | fn foo() -> usize { 0 }
| ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize` | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize`
| |
= note: required by `handler::<impl rocket::outcome::Outcome<rocket::Response<'o>, Status, rocket::Data>>::from` = note: required by `route::handler::<impl rocket::outcome::Outcome<rocket::Response<'o>, Status, rocket::Data>>::from`

View File

@ -1,37 +1,18 @@
//! Types and traits for error catchers, error handlers, and their return
//! types.
use std::fmt; use std::fmt;
use std::io::Cursor; use std::io::Cursor;
use crate::response::Response; use crate::response::Response;
use crate::codegen::StaticCatcherInfo;
use crate::request::Request; use crate::request::Request;
use crate::http::{Status, ContentType, uri}; use crate::http::{Status, ContentType, uri};
use crate::catcher::{Handler, BoxFuture};
use futures::future::BoxFuture;
use yansi::Paint; 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<Response<'r>, 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. /// An error catching route.
/// ///
/// # Overview
///
/// Catchers are routes that run when errors are produced by the application. /// Catchers are routes that run when errors are produced by the application.
/// They consist of an [`ErrorHandler`] and an optional status code to match /// They consist of a [`Handler`] and an optional status code to match against
/// against arising errors. Errors arise from the the following sources: /// arising errors. Errors arise from the the following sources:
/// ///
/// * A failing guard. /// * A failing guard.
/// * A failing responder. /// * 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 /// failure is always a `404`. Rocket invokes the error handler for the catcher
/// with the error's status code. /// with the error's status code.
/// ///
/// ## Default Catchers /// ### Error Handler Restrictions
///
/// 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
/// ///
/// Because error handlers are a last resort, they should not fail to produce a /// 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` /// response. If an error handler _does_ fail, Rocket invokes its default `500`
/// error catcher. Error handlers cannot forward. /// 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 /// An error arising from a particular request _matches_ a catcher _iff_:
/// 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. /// * 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 /// # Code Generation
/// ///
@ -87,7 +84,7 @@ pub(crate) fn dummy<'r>(_: Status, _: &'r Request<'_>) -> ErrorHandlerFuture<'r>
/// } /// }
/// ///
/// #[launch] /// #[launch]
/// fn rocket() -> rocket::Rocket { /// fn rocket() -> _ {
/// rocket::build().register("/", catchers![internal_error, not_found, default]) /// rocket::build().register("/", catchers![internal_error, not_found, default])
/// } /// }
/// ``` /// ```
@ -117,7 +114,7 @@ pub struct Catcher {
pub code: Option<u16>, pub code: Option<u16>,
/// The catcher's associated error handler. /// The catcher's associated error handler.
pub handler: Box<dyn ErrorHandler>, pub handler: Box<dyn Handler>,
} }
impl Catcher { impl Catcher {
@ -129,20 +126,20 @@ impl Catcher {
/// ///
/// ```rust /// ```rust
/// use rocket::request::Request; /// use rocket::request::Request;
/// use rocket::catcher::{Catcher, ErrorHandlerFuture}; /// use rocket::catcher::{Catcher, BoxFuture};
/// use rocket::response::Responder; /// use rocket::response::Responder;
/// use rocket::http::Status; /// 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())); /// let res = (status, format!("404: {}", req.uri()));
/// Box::pin(async move { res.respond_to(req) }) /// 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) }) /// 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())); /// let res = (status, format!("{}: {}", status, req.uri()));
/// Box::pin(async move { res.respond_to(req) }) /// Box::pin(async move { res.respond_to(req) })
/// } /// }
@ -158,7 +155,7 @@ impl Catcher {
/// 600)`. /// 600)`.
#[inline(always)] #[inline(always)]
pub fn new<S, H>(code: S, handler: H) -> Catcher pub fn new<S, H>(code: S, handler: H) -> Catcher
where S: Into<Option<u16>>, H: ErrorHandler where S: Into<Option<u16>>, H: Handler
{ {
let code = code.into(); let code = code.into();
if let Some(code) = code { if let Some(code) = code {
@ -185,11 +182,11 @@ impl Catcher {
/// ///
/// ```rust /// ```rust
/// use rocket::request::Request; /// use rocket::request::Request;
/// use rocket::catcher::{Catcher, ErrorHandlerFuture}; /// use rocket::catcher::{Catcher, BoxFuture};
/// use rocket::response::Responder; /// use rocket::response::Responder;
/// use rocket::http::Status; /// 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())); /// let res = (status, format!("404: {}", req.uri()));
/// Box::pin(async move { res.respond_to(req) }) /// Box::pin(async move { res.respond_to(req) })
/// } /// }
@ -220,8 +217,8 @@ impl Catcher {
impl Default for Catcher { impl Default for Catcher {
fn default() -> Self { fn default() -> Self {
fn handler<'r>(s: Status, req: &'r Request<'_>) -> ErrorHandlerFuture<'r> { fn handler<'r>(s: Status, req: &'r Request<'_>) -> BoxFuture<'r> {
Box::pin(async move { Ok(default(s, req)) }) Box::pin(async move { Ok(default_handler(s, req)) })
} }
let mut catcher = Catcher::new(None, handler); let mut catcher = Catcher::new(None, handler);
@ -230,121 +227,21 @@ impl Default for Catcher {
} }
} }
/// Trait implemented by types that can handle errors. /// Information generated by the `catch` attribute during codegen.
/// #[doc(hidden)]
/// This trait is exactly like [`Handler`](crate::handler::Handler) except it pub struct StaticInfo {
/// handles error instead of requests. We defer to its documentation. /// The catcher's name, i.e, the name of the function.
/// pub name: &'static str,
/// ## Async Trait /// The catcher's status code.
/// pub code: Option<u16>,
/// This is an _async_ trait. Implementations must be decorated /// The catcher's handler, i.e, the annotated function.
/// [`#[rocket::async_trait]`](crate::async_trait). pub handler: for<'r> fn(Status, &'r Request<'_>) -> BoxFuture<'r>,
///
/// # 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<Catcher> {
/// vec![Catcher::new(None, CustomHandler(kind))]
/// }
///
/// /// Returns a catcher for code `status` that uses `CustomHandler`.
/// fn catch(status: Status, kind: Kind) -> Vec<Catcher> {
/// 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<Route>`, 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<F: Clone + Sync + Send + 'static> 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)
}
} }
#[doc(hidden)] #[doc(hidden)]
impl From<StaticCatcherInfo> for Catcher { impl From<StaticInfo> for Catcher {
#[inline] #[inline]
fn from(info: StaticCatcherInfo) -> Catcher { fn from(info: StaticInfo) -> Catcher {
let mut catcher = Catcher::new(info.code, info.handler); let mut catcher = Catcher::new(info.code, info.handler);
catcher.name = Some(info.name.into()); catcher.name = Some(info.name.into());
catcher catcher
@ -431,11 +328,14 @@ r#"{{
) )
} }
macro_rules! default_catcher_fn { macro_rules! default_handler_fn {
($($code:expr, $reason:expr, $description:expr),+) => ( ($($code:expr, $reason:expr, $description:expr),+) => (
use std::borrow::Cow; 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 preferred = req.accept().map(|a| a.preferred());
let (mime, text) = if preferred.map_or(false, |a| a.is_json()) { let (mime, text) = if preferred.map_or(false, |a| a.is_json()) {
let json: Cow<'_, str> = match status.code { 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 \ 400, "Bad Request", "The request could not be understood by the server due \
to malformed syntax.", to malformed syntax.",
401, "Unauthorized", "The request requires user authentication.", 401, "Unauthorized", "The request requires user authentication.",
@ -515,30 +415,3 @@ default_catcher_fn! {
the server to fulfill it." the server to fulfill it."
} }
// `Cloneable` implementation below.
mod private {
pub trait Sealed {}
impl<T: super::ErrorHandler + Clone> Sealed for T {}
}
/// Unfortunate but necessary hack to be able to clone a `Box<ErrorHandler>`.
///
/// 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<dyn ErrorHandler>;
}
impl<T: ErrorHandler + Clone> Cloneable for T {
fn clone_handler(&self) -> Box<dyn ErrorHandler> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn ErrorHandler> {
fn clone(&self) -> Box<dyn ErrorHandler> {
self.clone_handler()
}
}

View File

@ -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<Response<'r>, 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<Catcher> {
/// vec![Catcher::new(None, CustomHandler(kind))]
/// }
///
/// /// Returns a catcher for code `status` that uses `CustomHandler`.
/// fn catch(status: Status, kind: Kind) -> Vec<Catcher> {
/// 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<Route>`, 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<F: Clone + Sync + Send + 'static> 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<T: super::Handler + Clone> Sealed for T {}
}
/// Helper trait to make a [`Catcher`](crate::Catcher)'s `Box<dyn Handler>`
/// `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<dyn Handler>;
}
impl<T: Handler + Clone> Cloneable for T {
fn clone_handler(&self) -> Box<dyn Handler> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn Handler> {
fn clone(&self) -> Box<dyn Handler> {
self.clone_handler()
}
}

View File

@ -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::*;

View File

@ -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<MediaType>,
/// The route's handler, i.e, the annotated function.
pub handler: StaticHandler,
/// The route's rank, if any.
pub rank: Option<isize>,
}
/// 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<u16>,
/// The catcher's handler, i.e, the annotated function.
pub handler: StaticErrorHandler,
}

View File

@ -124,11 +124,10 @@ pub mod request;
pub mod response; pub mod response;
pub mod config; pub mod config;
pub mod form; pub mod form;
pub mod handler;
pub mod fairing; pub mod fairing;
pub mod error; pub mod error;
pub mod catcher; pub mod catcher;
pub mod router; pub mod route;
// Reexport of HTTP everything. // Reexport of HTTP everything.
pub mod http { pub mod http {
@ -145,20 +144,20 @@ pub mod http {
} }
mod shutdown; mod shutdown;
mod rocket;
mod server; mod server;
mod codegen;
mod ext; mod ext;
mod state; mod state;
mod cookies; mod cookies;
mod rocket;
mod router;
mod phase;
#[doc(hidden)] pub use log::{info, warn, error, debug}; #[doc(hidden)] pub use log::{info, warn, error, debug};
#[doc(inline)] pub use crate::response::Response; #[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::data::Data;
#[doc(inline)] pub use crate::config::Config; #[doc(inline)] pub use crate::config::Config;
#[doc(inline)] pub use crate::catcher::Catcher; #[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; #[doc(hidden)] pub use either::Either;
pub use crate::request::Request; pub use crate::request::Request;
pub use crate::rocket::Rocket; pub use crate::rocket::Rocket;

View File

@ -1,8 +1,7 @@
use std::fmt::Debug; use std::fmt::Debug;
use std::net::{IpAddr, SocketAddr}; use std::net::{IpAddr, SocketAddr};
use crate::router::Route; use crate::{Request, Route};
use crate::request::Request;
use crate::outcome::{self, IntoOutcome}; use crate::outcome::{self, IntoOutcome};
use crate::outcome::Outcome::*; use crate::outcome::Outcome::*;

View File

@ -2,14 +2,10 @@ use std::fmt::Display;
use std::convert::TryInto; use std::convert::TryInto;
use yansi::Paint; use yansi::Paint;
use state::Container; use tokio::sync::Notify;
use figment::Figment;
use tokio::sync::mpsc;
use crate::logger; use crate::{Route, Catcher, Config, Shutdown};
use crate::config::Config; use crate::router::Router;
use crate::catcher::Catcher;
use crate::router::{Router, Route};
use crate::fairing::{Fairing, Fairings}; use crate::fairing::{Fairing, Fairings};
use crate::logger::PaintExt; use crate::logger::PaintExt;
use crate::shutdown::Shutdown; use crate::shutdown::Shutdown;
@ -189,12 +185,12 @@ impl Rocket {
/// `hi` route. /// `hi` route.
/// ///
/// ```rust /// ```rust
/// use rocket::{Request, Route, Data}; /// # #[macro_use] extern crate rocket;
/// use rocket::handler::{HandlerFuture, Outcome}; /// use rocket::{Request, Route, Data, route};
/// use rocket::http::Method::*; /// use rocket::http::Method;
/// ///
/// fn hi<'r>(req: &'r Request, _: Data) -> HandlerFuture<'r> { /// fn hi<'r>(req: &'r Request, _: Data) -> route::BoxFuture<'r> {
/// Outcome::from(req, "Hello!").pin() /// route::Outcome::from(req, "Hello!").pin()
/// } /// }
/// ///
/// #[launch] /// #[launch]

View File

@ -1,20 +1,16 @@
//! Types and traits for request handlers and their return values. use crate::{Request, Data};
use futures::future::BoxFuture;
use crate::data::Data;
use crate::request::Request;
use crate::response::{Response, Responder}; use crate::response::{Response, Responder};
use crate::http::Status; use crate::http::Status;
use crate::outcome;
/// Type alias for the `Outcome` of a `Handler`. /// Type alias for the return type of a [`Route`](crate::Route)'s
pub type Outcome<'r> = outcome::Outcome<Response<'r>, Status, Data>; /// [`Handler::handle()`].
pub type Outcome<'r> = crate::outcome::Outcome<Response<'r>, Status, Data>;
/// Type alias for the unwieldy `Handler` return type /// Type alias for the return type of a _raw_ [`Route`](crate::Route)'s
pub type HandlerFuture<'r> = BoxFuture<'r, Outcome<'r>>; /// [`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 /// In general, you will never need to implement `Handler` manually or be
/// concerned about the `Handler` trait; Rocket's code generation handles /// concerned about the `Handler` trait; Rocket's code generation handles
@ -27,8 +23,8 @@ pub type HandlerFuture<'r> = BoxFuture<'r, Outcome<'r>>;
/// ///
/// ## Async Trait /// ## Async Trait
/// ///
/// [`Handler`] is an _async_ trait. Implementations of `Handler` must be /// This is an _async_ trait. Implementations must be decorated
/// decorated with an attribute of `#[rocket::async_trait]`. /// [`#[rocket::async_trait]`](crate::async_trait).
/// ///
/// # Example /// # Example
/// ///
@ -48,8 +44,9 @@ pub type HandlerFuture<'r> = BoxFuture<'r, Outcome<'r>>;
/// ///
/// ```rust,no_run /// ```rust,no_run
/// # #[derive(Copy, Clone)] enum Kind { Simple, Intermediate, Complex, } /// # #[derive(Copy, Clone)] enum Kind { Simple, Intermediate, Complex, }
/// use rocket::{Request, Data, Route, http::Method}; /// use rocket::{Request, Data};
/// use rocket::handler::{self, Handler, Outcome}; /// use rocket::route::{Handler, Route, Outcome};
/// use rocket::http::Method;
/// ///
/// #[derive(Clone)] /// #[derive(Clone)]
/// struct CustomHandler(Kind); /// struct CustomHandler(Kind);
@ -72,7 +69,7 @@ pub type HandlerFuture<'r> = BoxFuture<'r, Outcome<'r>>;
/// } /// }
/// ///
/// #[rocket::launch] /// #[rocket::launch]
/// fn rocket() -> rocket::Rocket { /// fn rocket() -> _ {
/// rocket::build().mount("/", CustomHandler(Kind::Simple)) /// rocket::build().mount("/", CustomHandler(Kind::Simple))
/// } /// }
/// ``` /// ```
@ -115,7 +112,7 @@ pub type HandlerFuture<'r> = BoxFuture<'r, Outcome<'r>>;
/// } /// }
/// ///
/// #[launch] /// #[launch]
/// fn rocket() -> rocket::Rocket { /// fn rocket() -> _ {
/// rocket::build() /// rocket::build()
/// .mount("/", routes![custom_handler]) /// .mount("/", routes![custom_handler])
/// .manage(Kind::Simple) /// .manage(Kind::Simple)
@ -153,14 +150,14 @@ pub trait Handler: Cloneable + Send + Sync + 'static {
// We write this manually to avoid double-boxing. // We write this manually to avoid double-boxing.
impl<F: Clone + Sync + Send + 'static> Handler for F impl<F: Clone + Sync + Send + 'static> 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)] #[inline(always)]
fn handle<'r, 's: 'r, 'life0, 'async_trait>( fn handle<'r, 's: 'r, 'life0, 'async_trait>(
&'s self, &'s self,
req: &'r Request<'life0>, req: &'r Request<'life0>,
data: Data, data: Data,
) -> HandlerFuture<'r> ) -> BoxFuture<'r>
where 'r: 'async_trait, where 'r: 'async_trait,
's: 'async_trait, 's: 'async_trait,
'life0: 'async_trait, 'life0: 'async_trait,
@ -170,51 +167,43 @@ impl<F: Clone + Sync + Send + 'static> 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> { impl<'r, 'o: 'r> Outcome<'o> {
/// Return the `Outcome` of response to `req` from `responder`. /// Return the `Outcome` of response to `req` from `responder`.
/// ///
/// If the responder returns `Ok`, an outcome of `Success` is /// If the responder returns `Ok`, an outcome of `Success` is returned with
/// returned with the response. If the responder returns `Err`, an /// the response. If the responder returns `Err`, an outcome of `Failure` is
/// outcome of `Failure` is returned with the status code. /// returned with the status code.
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// use rocket::{Request, Data}; /// use rocket::{Request, Data, route};
/// use rocket::handler::Outcome;
/// ///
/// fn str_responder<'r>(req: &'r Request, _: Data) -> Outcome<'r> { /// fn str_responder<'r>(req: &'r Request, _: Data) -> route::Outcome<'r> {
/// Outcome::from(req, "Hello, world!") /// route::Outcome::from(req, "Hello, world!")
/// } /// }
/// ``` /// ```
#[inline] #[inline]
pub fn from<R: Responder<'r, 'o>>(req: &'r Request<'_>, responder: R) -> Outcome<'o> { pub fn from<R: Responder<'r, 'o>>(req: &'r Request<'_>, responder: R) -> Outcome<'o> {
match responder.respond_to(req) { match responder.respond_to(req) {
Ok(response) => outcome::Outcome::Success(response), Ok(response) => Outcome::Success(response),
Err(status) => outcome::Outcome::Failure(status) Err(status) => Outcome::Failure(status)
} }
} }
/// Return the `Outcome` of response to `req` from `responder`. /// Return the `Outcome` of response to `req` from `responder`.
/// ///
/// If the responder returns `Ok`, an outcome of `Success` is /// If the responder returns `Ok`, an outcome of `Success` is returned with
/// returned with the response. If the responder returns `Err`, an /// the response. If the responder returns `Err`, an outcome of `Failure` is
/// outcome of `Failure` is returned with the status code. /// returned with the status code.
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// use rocket::{Request, Data}; /// use rocket::{Request, Data, route};
/// use rocket::handler::Outcome;
/// ///
/// fn str_responder<'r>(req: &'r Request, _: Data) -> Outcome<'r> { /// fn str_responder<'r>(req: &'r Request, _: Data) -> route::Outcome<'r> {
/// Outcome::from(req, "Hello, world!") /// route::Outcome::from(req, "Hello, world!")
/// } /// }
/// ``` /// ```
#[inline] #[inline]
@ -223,25 +212,24 @@ impl<'r, 'o: 'r> Outcome<'o> {
{ {
let responder = result.map_err(crate::response::Debug); let responder = result.map_err(crate::response::Debug);
match responder.respond_to(req) { match responder.respond_to(req) {
Ok(response) => outcome::Outcome::Success(response), Ok(response) => Outcome::Success(response),
Err(status) => outcome::Outcome::Failure(status) Err(status) => Outcome::Failure(status)
} }
} }
/// Return the `Outcome` of response to `req` from `responder`. /// Return the `Outcome` of response to `req` from `responder`.
/// ///
/// If the responder returns `Ok`, an outcome of `Success` is /// If the responder returns `Ok`, an outcome of `Success` is returned with
/// returned with the response. If the responder returns `Err`, an /// the response. If the responder returns `Err`, an outcome of `Forward` is
/// outcome of `Forward` is returned. /// returned.
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// use rocket::{Request, Data}; /// use rocket::{Request, Data, route};
/// use rocket::handler::Outcome;
/// ///
/// fn str_responder<'r>(req: &'r Request, data: Data) -> Outcome<'r> { /// fn str_responder<'r>(req: &'r Request, data: Data) -> route::Outcome<'r> {
/// Outcome::from_or_forward(req, data, "Hello, world!") /// route::Outcome::from_or_forward(req, data, "Hello, world!")
/// } /// }
/// ``` /// ```
#[inline] #[inline]
@ -249,64 +237,68 @@ impl<'r, 'o: 'r> Outcome<'o> {
where R: Responder<'r, 'o> where R: Responder<'r, 'o>
{ {
match responder.respond_to(req) { match responder.respond_to(req) {
Ok(response) => outcome::Outcome::Success(response), Ok(response) => Outcome::Success(response),
Err(_) => outcome::Outcome::Forward(data) Err(_) => Outcome::Forward(data)
} }
} }
/// Return an `Outcome` of `Failure` with the status code `code`. This is /// Return an `Outcome` of `Failure` with the status code `code`. This is
/// equivalent to `Outcome::Failure(code)`. /// equivalent to `Outcome::Failure(code)`.
/// ///
/// This method exists to be used during manual routing where /// This method exists to be used during manual routing.
/// `rocket::handler::Outcome` is imported instead of `rocket::Outcome`.
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// use rocket::{Request, Data}; /// use rocket::{Request, Data, route};
/// use rocket::handler::Outcome;
/// use rocket::http::Status; /// use rocket::http::Status;
/// ///
/// fn bad_req_route<'r>(_: &'r Request, _: Data) -> Outcome<'r> { /// fn bad_req_route<'r>(_: &'r Request, _: Data) -> route::Outcome<'r> {
/// Outcome::failure(Status::BadRequest) /// route::Outcome::failure(Status::BadRequest)
/// } /// }
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn failure(code: Status) -> Outcome<'static> { 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 /// Return an `Outcome` of `Forward` with the data `data`. This is
/// equivalent to `Outcome::Forward(data)`. /// equivalent to `Outcome::Forward(data)`.
/// ///
/// This method exists to be used during manual routing where /// This method exists to be used during manual routing.
/// `rocket::handler::Outcome` is imported instead of `rocket::Outcome`.
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// use rocket::{Request, Data}; /// use rocket::{Request, Data, route};
/// use rocket::handler::Outcome;
/// ///
/// fn always_forward<'r>(_: &'r Request, data: Data) -> Outcome<'r> { /// fn always_forward<'r>(_: &'r Request, data: Data) -> route::Outcome<'r> {
/// Outcome::forward(data) /// route::Outcome::forward(data)
/// } /// }
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn forward(data: Data) -> Outcome<'static> { 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 { mod private {
pub trait Sealed {} pub trait Sealed {}
impl<T: super::Handler + Clone> Sealed for T {} impl<T: super::Handler + Clone> Sealed for T {}
} }
/// Unfortunate but necessary hack to be able to clone a `Box<Handler>`. /// Helper trait to make a [`Route`](crate::Route)'s `Box<dyn Handler>`
/// `Clone`.
/// ///
/// This trait cannot be implemented by any type. Instead, all types that /// This trait cannot be implemented directly. Instead, implement `Clone` and
/// implement `Clone` and `Handler` automatically implement `Cloneable`. /// [`Handler`]; all types that implement `Clone` and `Handler` automatically
/// implement `Cloneable`.
pub trait Cloneable: private::Sealed { pub trait Cloneable: private::Sealed {
#[doc(hidden)] #[doc(hidden)]
fn clone_handler(&self) -> Box<dyn Handler>; fn clone_handler(&self) -> Box<dyn Handler>;

12
core/lib/src/route/mod.rs Normal file
View File

@ -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;

356
core/lib/src/route/route.rs Normal file
View File

@ -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/<path..>?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/<path..>?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&<zoo..>" => -11, // static path, partial query
/// "/foo?a&<zoo..>" => -11, // static path, partial query
/// "/?a&<zoo>" => -11, // static path, partial query
///
/// "/?<zoo..>" => -10, // static path, wild query
/// "/foo?<zoo..>" => -10, // static path, wild query
/// "/foo?<a>&<b>" => -10, // static path, wild query
///
/// "/" => -9, // static path, no query
/// "/foo/bar" => -9, // static path, no query
///
/// "/a/<b>?foo" => -8, // partial path, static query
/// "/a/<b..>?foo" => -8, // partial path, static query
/// "/<a>/b?foo" => -8, // partial path, static query
///
/// "/a/<b>?<b>&c" => -7, // partial path, partial query
/// "/a/<b..>?a&<c..>" => -7, // partial path, partial query
///
/// "/a/<b>?<c..>" => -6, // partial path, wild query
/// "/a/<b..>?<c>&<d>" => -6, // partial path, wild query
/// "/a/<b..>?<c>" => -6, // partial path, wild query
///
/// "/a/<b>" => -5, // partial path, no query
/// "/<a>/b" => -5, // partial path, no query
/// "/a/<b..>" => -5, // partial path, no query
///
/// "/<b>/<c>?foo&bar" => -4, // wild path, static query
/// "/<a>/<b..>?foo" => -4, // wild path, static query
/// "/<b..>?cat" => -4, // wild path, static query
///
/// "/<b>/<c>?<foo>&bar" => -3, // wild path, partial query
/// "/<a>/<b..>?a&<b..>" => -3, // wild path, partial query
/// "/<b..>?cat&<dog>" => -3, // wild path, partial query
///
/// "/<b>/<c>?<foo>" => -2, // wild path, wild query
/// "/<a>/<b..>?<b..>" => -2, // wild path, wild query
/// "/<b..>?<c>&<dog>" => -2, // wild path, wild query
///
/// "/<b>/<c>" => -1, // wild path, no query
/// "/<a>/<b..>" => -1, // wild path, no query
/// "/<b..>" => -1, // wild path, no query
/// }
/// ```
#[derive(Clone)]
pub struct Route {
/// The name of this route, if one was given.
pub name: Option<Cow<'static, str>>,
/// The method this route matches against.
pub method: Method,
/// The function that should be called when the route matches.
pub handler: Box<dyn Handler>,
/// 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<MediaType>,
}
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<H: Handler>(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<H, R>(rank: R, method: Method, uri: &str, handler: H) -> Route
where H: Handler + 'static, R: Into<Option<isize>>,
{
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<Self, uri::Error<'static>>
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<MediaType>,
/// 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<isize>,
}
#[doc(hidden)]
impl From<StaticInfo> 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
}
}

View File

@ -4,9 +4,9 @@ use std::borrow::Cow;
use crate::http::uri::{self, Origin}; use crate::http::uri::{self, Origin};
use crate::http::ext::IntoOwned; use crate::http::ext::IntoOwned;
use crate::form::ValueField; 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: /// A route URI is composed of two components:
/// ///
@ -21,7 +21,7 @@ use crate::router::segment::Segment;
/// ```rust /// ```rust
/// use rocket::Route; /// use rocket::Route;
/// use rocket::http::Method; /// use rocket::http::Method;
/// # use rocket::handler::dummy as handler; /// # use rocket::route::dummy_handler as handler;
/// ///
/// let route = Route::new(Method::Get, "/foo/<bar>", handler); /// let route = Route::new(Method::Get, "/foo/<bar>", handler);
/// assert_eq!(route.uri.base(), "/"); /// assert_eq!(route.uri.base(), "/");
@ -41,7 +41,7 @@ use crate::router::segment::Segment;
/// ```rust /// ```rust
/// use rocket::Route; /// use rocket::Route;
/// use rocket::http::Method; /// use rocket::http::Method;
/// # use rocket::handler::dummy as handler; /// # use rocket::route::dummy_handler as handler;
/// ///
/// let route = Route::new(Method::Get, "/foo/<bar>", handler); /// let route = Route::new(Method::Get, "/foo/<bar>", handler);
/// assert_eq!(route.uri, "/foo/<bar>"); /// assert_eq!(route.uri, "/foo/<bar>");
@ -95,7 +95,7 @@ pub(crate) struct Metadata {
pub trailing_path: bool, pub trailing_path: bool,
} }
type Result<T> = std::result::Result<T, uri::Error<'static>>; type Result<T, E = uri::Error<'static>> = std::result::Result<T, E>;
impl<'a> RouteUri<'a> { impl<'a> RouteUri<'a> {
/// Create a new `RouteUri`. /// Create a new `RouteUri`.
@ -140,7 +140,7 @@ impl<'a> RouteUri<'a> {
/// ```rust /// ```rust
/// use rocket::Route; /// use rocket::Route;
/// use rocket::http::Method; /// 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); /// let index = Route::new(Method::Get, "/foo/bar?a=1", handler);
/// assert_eq!(index.uri.base(), "/"); /// assert_eq!(index.uri.base(), "/");
@ -159,7 +159,7 @@ impl<'a> RouteUri<'a> {
/// ```rust /// ```rust
/// use rocket::Route; /// use rocket::Route;
/// use rocket::http::Method; /// 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); /// let index = Route::new(Method::Get, "/foo/bar?a=1", handler);
/// assert_eq!(index.uri.path(), "/foo/bar"); /// assert_eq!(index.uri.path(), "/foo/bar");
@ -178,7 +178,7 @@ impl<'a> RouteUri<'a> {
/// ```rust /// ```rust
/// use rocket::Route; /// use rocket::Route;
/// use rocket::http::Method; /// 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); /// let index = Route::new(Method::Get, "/foo/bar?a=1", handler);
/// assert_eq!(index.uri.query(), Some("a=1")); /// assert_eq!(index.uri.query(), Some("a=1"));
@ -197,7 +197,7 @@ impl<'a> RouteUri<'a> {
/// ```rust /// ```rust
/// use rocket::Route; /// use rocket::Route;
/// use rocket::http::Method; /// 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); /// let index = Route::new(Method::Get, "/foo/bar?a=1", handler);
/// assert_eq!(index.uri.as_str(), "/foo/bar?a=1"); /// assert_eq!(index.uri.as_str(), "/foo/bar?a=1");

View File

@ -1,5 +1,5 @@
use super::{Route, uri::Color};
use crate::catcher::Catcher; use crate::catcher::Catcher;
use crate::route::{Route, Color};
use crate::http::{MediaType, Status}; use crate::http::{MediaType, Status};
use crate::request::Request; use crate::request::Request;
@ -44,18 +44,17 @@ fn paths_collide(route: &Route, other: &Route) -> bool {
} }
fn formats_collide(route: &Route, other: &Route) -> bool { fn formats_collide(route: &Route, other: &Route) -> bool {
// When matching against the `Accept` header, the client can always // When matching against the `Accept` header, the client can always provide
// provide a media type that will cause a collision through // a media type that will cause a collision through non-specificity, i.e,
// non-specificity, i.e, `*/*` matches everything. // `*/*` matches everything.
if !route.method.supports_payload() { if !route.method.supports_payload() {
return true; return true;
} }
// When matching against the `Content-Type` header, we'll only // When matching against the `Content-Type` header, we'll only consider
// consider requests as having a `Content-Type` if they're fully // requests as having a `Content-Type` if they're fully specified. If a
// specified. If a route doesn't have a `format`, it accepts all // route doesn't have a `format`, it accepts all `Content-Type`s. If a
// `Content-Type`s. If a request doesn't have a format, it only // request doesn't have a format, it only matches routes without a format.
// matches routes without a format.
match (route.format.as_ref(), other.format.as_ref()) { match (route.format.as_ref(), other.format.as_ref()) {
(Some(a), Some(b)) => a.collides_with(b), (Some(a), Some(b)) => a.collides_with(b),
_ => true _ => true
@ -203,31 +202,28 @@ mod tests {
use std::str::FromStr; use std::str::FromStr;
use super::*; use super::*;
use crate::rocket::Rocket; use crate::route::{Route, dummy_handler};
use crate::config::Config; use crate::local::blocking::Client;
use crate::request::Request;
use crate::router::route::Route;
use crate::http::{Method, Method::*, MediaType, ContentType, Accept}; use crate::http::{Method, Method::*, MediaType, ContentType, Accept};
use crate::http::uri::Origin; use crate::http::uri::Origin;
use crate::handler::dummy;
type SimpleRoute = (Method, &'static str); type SimpleRoute = (Method, &'static str);
fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool { fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool {
let route_a = Route::new(a.0, a.1, dummy); let route_a = Route::new(a.0, a.1, dummy_handler);
route_a.collides_with(&Route::new(b.0, b.1, dummy)) route_a.collides_with(&Route::new(b.0, b.1, dummy_handler))
} }
fn unranked_collide(a: &'static str, b: &'static str) -> bool { fn unranked_collide(a: &'static str, b: &'static str) -> bool {
let route_a = Route::ranked(0, Get, a, dummy); let route_a = Route::ranked(0, Get, a, dummy_handler);
let route_b = Route::ranked(0, Get, b, dummy); let route_b = Route::ranked(0, Get, b, dummy_handler);
eprintln!("Checking {} against {}.", route_a, route_b); eprintln!("Checking {} against {}.", route_a, route_b);
route_a.collides_with(&route_b) route_a.collides_with(&route_b)
} }
fn s_s_collide(a: &'static str, b: &'static str) -> bool { fn s_s_collide(a: &'static str, b: &'static str) -> bool {
let a = Route::new(Get, a, dummy); let a = Route::new(Get, a, dummy_handler);
let b = Route::new(Get, b, dummy); let b = Route::new(Get, b, dummy_handler);
paths_collide(&a, &b) paths_collide(&a, &b)
} }
@ -402,12 +398,12 @@ mod tests {
fn r_mt_mt_collide<S1, S2>(m: Method, mt1: S1, mt2: S2) -> bool fn r_mt_mt_collide<S1, S2>(m: Method, mt1: S1, mt2: S2) -> bool
where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>> where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>>
{ {
let mut route_a = Route::new(m, "/", dummy); let mut route_a = Route::new(m, "/", dummy_handler);
if let Some(mt_str) = mt1.into() { if let Some(mt_str) = mt1.into() {
route_a.format = Some(mt_str.parse::<MediaType>().unwrap()); route_a.format = Some(mt_str.parse::<MediaType>().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() { if let Some(mt_str) = mt2.into() {
route_b.format = Some(mt_str.parse::<MediaType>().unwrap()); route_b.format = Some(mt_str.parse::<MediaType>().unwrap());
} }
@ -460,8 +456,8 @@ mod tests {
fn req_route_mt_collide<S1, S2>(m: Method, mt1: S1, mt2: S2) -> bool fn req_route_mt_collide<S1, S2>(m: Method, mt1: S1, mt2: S2) -> bool
where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>> where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>>
{ {
let rocket = Rocket::custom(Config::default()); let client = Client::debug_with(vec![]).expect("client");
let mut req = Request::new(&rocket, m, Origin::dummy()); let mut req = client.req(m, "/");
if let Some(mt_str) = mt1.into() { if let Some(mt_str) = mt1.into() {
if m.supports_payload() { if m.supports_payload() {
req.replace_header(mt_str.parse::<ContentType>().unwrap()); req.replace_header(mt_str.parse::<ContentType>().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() { if let Some(mt_str) = mt2.into() {
route.format = Some(mt_str.parse::<MediaType>().unwrap()); route.format = Some(mt_str.parse::<MediaType>().unwrap());
} }
@ -527,9 +523,9 @@ mod tests {
} }
fn req_route_path_match(a: &'static str, b: &'static str) -> bool { fn req_route_path_match(a: &'static str, b: &'static str) -> bool {
let rocket = Rocket::custom(Config::default()); let client = Client::debug_with(vec![]).expect("client");
let req = Request::new(&rocket, Get, Origin::parse(a).expect("valid URI")); let req = client.get(Origin::parse(a).expect("valid URI"));
let route = Route::ranked(0, Get, b, dummy); let route = Route::ranked(0, Get, b, dummy_handler);
route.matches(&req) route.matches(&req)
} }
@ -573,8 +569,10 @@ mod tests {
fn catchers_collide<A, B>(a: A, ap: &str, b: B, bp: &str) -> bool fn catchers_collide<A, B>(a: A, ap: &str, b: B, bp: &str) -> bool
where A: Into<Option<u16>>, B: Into<Option<u16>> where A: Into<Option<u16>>, B: Into<Option<u16>>
{ {
let a = Catcher::new(a, crate::catcher::dummy).map_base(|_| ap.into()).unwrap(); use crate::catcher::dummy_handler as handler;
let b = Catcher::new(b, crate::catcher::dummy).map_base(|_| bp.into()).unwrap();
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) a.collides_with(&b)
} }

View File

@ -1,13 +1,7 @@
//! Routing types: [`Route`] and [`RouteUri`]. //! Rocket's router.
mod route;
mod segment;
mod uri;
mod router; mod router;
mod collider; mod collider;
pub(crate) use router::*; pub(crate) use router::*;
pub(crate) use collider::*;
pub use route::Route;
pub use collider::Collide;
pub use uri::RouteUri;

View File

@ -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<Cow<'static, str>>,
/// The method this route matches against.
pub method: Method,
/// The function that should be called when the route matches.
pub handler: Box<dyn Handler>,
/// 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<MediaType>,
}
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&<zoo..>" => -11, // static path, partial query
/// "/foo?a&<zoo..>" => -11, // static path, partial query
/// "/?a&<zoo>" => -11, // static path, partial query
///
/// "/?<zoo..>" => -10, // static path, wild query
/// "/foo?<zoo..>" => -10, // static path, wild query
/// "/foo?<a>&<b>" => -10, // static path, wild query
///
/// "/" => -9, // static path, no query
/// "/foo/bar" => -9, // static path, no query
///
/// "/a/<b>?foo" => -8, // partial path, static query
/// "/a/<b..>?foo" => -8, // partial path, static query
/// "/<a>/b?foo" => -8, // partial path, static query
///
/// "/a/<b>?<b>&c" => -7, // partial path, partial query
/// "/a/<b..>?a&<c..>" => -7, // partial path, partial query
///
/// "/a/<b>?<c..>" => -6, // partial path, wild query
/// "/a/<b..>?<c>&<d>" => -6, // partial path, wild query
/// "/a/<b..>?<c>" => -6, // partial path, wild query
///
/// "/a/<b>" => -5, // partial path, no query
/// "/<a>/b" => -5, // partial path, no query
/// "/a/<b..>" => -5, // partial path, no query
///
/// "/<b>/<c>?foo&bar" => -4, // wild path, static query
/// "/<a>/<b..>?foo" => -4, // wild path, static query
/// "/<b..>?cat" => -4, // wild path, static query
///
/// "/<b>/<c>?<foo>&bar" => -3, // wild path, partial query
/// "/<a>/<b..>?a&<b..>" => -3, // wild path, partial query
/// "/<b..>?cat&<dog>" => -3, // wild path, partial query
///
/// "/<b>/<c>?<foo>" => -2, // wild path, wild query
/// "/<a>/<b..>?<b..>" => -2, // wild path, wild query
/// "/<b..>?<c>&<dog>" => -2, // wild path, wild query
///
/// "/<b>/<c>" => -1, // wild path, no query
/// "/<a>/<b..>" => -1, // wild path, no query
/// "/<b..>" => -1, // wild path, no query
/// }
/// ```
///
/// # Panics
///
/// Panics if `path` is not a valid origin URI or Rocket route URI.
pub fn new<H: Handler>(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<H, R>(rank: R, method: Method, uri: &str, handler: H) -> Route
where H: Handler + 'static, R: Into<Option<isize>>,
{
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<Self, uri::Error<'static>>
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<StaticRouteInfo> 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
}
}

View File

@ -3,9 +3,8 @@ use std::collections::HashMap;
use crate::request::Request; use crate::request::Request;
use crate::http::{Method, Status}; use crate::http::{Method, Status};
pub use crate::router::{Route, RouteUri}; use crate::{Route, Catcher};
pub use crate::router::collider::Collide; use crate::router::Collide;
pub use crate::catcher::Catcher;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(crate) struct Router { pub(crate) struct Router {
@ -106,12 +105,9 @@ impl Router {
mod test { mod test {
use super::*; use super::*;
use crate::rocket::Rocket; use crate::route::dummy_handler;
use crate::config::Config; use crate::local::blocking::Client;
use crate::http::{Method, Method::*}; use crate::http::{Method, Method::*, uri::Origin};
use crate::http::uri::Origin;
use crate::request::Request;
use crate::handler::dummy;
impl Router { impl Router {
fn has_collisions(&self) -> bool { fn has_collisions(&self) -> bool {
@ -122,7 +118,7 @@ mod test {
fn router_with_routes(routes: &[&'static str]) -> Router { fn router_with_routes(routes: &[&'static str]) -> Router {
let mut router = Router::new(); let mut router = Router::new();
for route in routes { for route in routes {
let route = Route::new(Get, route, dummy); let route = Route::new(Get, route, dummy_handler);
router.add_route(route); router.add_route(route);
} }
@ -132,7 +128,7 @@ mod test {
fn router_with_ranked_routes(routes: &[(isize, &'static str)]) -> Router { fn router_with_ranked_routes(routes: &[(isize, &'static str)]) -> Router {
let mut router = Router::new(); let mut router = Router::new();
for &(rank, route) in routes { 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); router.add_route(route);
} }
@ -142,7 +138,7 @@ mod test {
fn router_with_rankless_routes(routes: &[&'static str]) -> Router { fn router_with_rankless_routes(routes: &[&'static str]) -> Router {
let mut router = Router::new(); let mut router = Router::new();
for route in routes { 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); router.add_route(route);
} }
@ -290,17 +286,14 @@ mod test {
assert!(!default_rank_route_collisions(&["/<foo>?a=b", "/<foo>?c=d&<d>"])); assert!(!default_rank_route_collisions(&["/<foo>?a=b", "/<foo>?c=d&<d>"]));
} }
fn route<'a>(router: &'a Router, method: Method, uri: &'a str) -> Option<&'a Route> { fn matches<'a>(router: &'a Router, method: Method, uri: &'a str) -> Vec<&'a Route> {
let rocket = Rocket::custom(Config::default()); let client = Client::debug_with(vec![]).expect("client");
let request = Request::new(&rocket, method, Origin::parse(uri).unwrap()); let request = client.req(method, Origin::parse(uri).unwrap());
let route = router.route(&request).next(); router.route(&request).collect()
route
} }
fn matches<'a>(router: &'a Router, method: Method, uri: &'a str) -> Vec<&'a Route> { fn route<'a>(router: &'a Router, method: Method, uri: &'a str) -> Option<&'a Route> {
let rocket = Rocket::custom(Config::default()); matches(router, method, uri).into_iter().next()
let request = Request::new(&rocket, method, Origin::parse(uri).unwrap());
router.route(&request).collect()
} }
#[test] #[test]
@ -321,9 +314,9 @@ mod test {
assert!(route(&router, Get, "/jdlk/asdij").is_some()); assert!(route(&router, Get, "/jdlk/asdij").is_some());
let mut router = Router::new(); let mut router = Router::new();
router.add_route(Route::new(Put, "/hello", dummy)); router.add_route(Route::new(Put, "/hello", dummy_handler));
router.add_route(Route::new(Post, "/hello", dummy)); router.add_route(Route::new(Post, "/hello", dummy_handler));
router.add_route(Route::new(Delete, "/hello", dummy)); router.add_route(Route::new(Delete, "/hello", dummy_handler));
assert!(route(&router, Put, "/hello").is_some()); assert!(route(&router, Put, "/hello").is_some());
assert!(route(&router, Post, "/hello").is_some()); assert!(route(&router, Post, "/hello").is_some());
assert!(route(&router, Delete, "/hello").is_some()); assert!(route(&router, Delete, "/hello").is_some());
@ -558,7 +551,7 @@ mod test {
fn router_with_catchers(catchers: &[(Option<u16>, &str)]) -> Router { fn router_with_catchers(catchers: &[(Option<u16>, &str)]) -> Router {
let mut router = Router::new(); let mut router = Router::new();
for (code, base) in catchers { 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()); 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> { fn catcher<'a>(router: &'a Router, status: Status, uri: &str) -> Option<&'a Catcher> {
let rocket = Rocket::custom(Config::default()); let client = Client::debug_with(vec![]).expect("client");
let request = Request::new(&rocket, Method::Get, Origin::parse(uri).unwrap()); let request = client.get(Origin::parse(uri).unwrap());
router.catch(status, &request) router.catch(status, &request)
} }

View File

@ -6,7 +6,7 @@ use futures::future::{self, FutureExt, Future, TryFutureExt, BoxFuture};
use tokio::sync::oneshot; use tokio::sync::oneshot;
use yansi::Paint; use yansi::Paint;
use crate::{Rocket, Request, Data, handler}; use crate::{Rocket, Request, Data, route};
use crate::form::Form; use crate::form::Form;
use crate::response::{Response, Body}; use crate::response::{Response, Body};
use crate::outcome::Outcome; use crate::outcome::Outcome;
@ -284,7 +284,7 @@ impl Rocket {
&'s self, &'s self,
request: &'r Request<'s>, request: &'r Request<'s>,
mut data: Data, mut data: Data,
) -> handler::Outcome<'r> { ) -> route::Outcome<'r> {
// Go through the list of matching routes until we fail or succeed. // Go through the list of matching routes until we fail or succeed.
for route in self.router.route(request) { for route in self.router.route(request) {
// Retrieve and set the requests parameters. // Retrieve and set the requests parameters.
@ -338,7 +338,7 @@ impl Rocket {
} else { } else {
let code = Paint::blue(status.code).bold(); let code = Paint::blue(status.code).bold();
warn_!("No {} catcher registered. Using Rocket default.", code); 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. // If it failed again or if it was already a 500, use Rocket's default.
error_!("{} catcher failed. Using Rocket default 500.", status.code); 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<C>(mut self, ready: C) -> Result<(), Error> pub async fn default_tcp_http_server<C>(mut self, ready: C) -> Result<(), Error>

View File

@ -1,11 +1,9 @@
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
use rocket::{Rocket, Route, Request}; use rocket::{Request, Rocket, Route, Catcher, route, catcher};
use rocket::data::Data; use rocket::data::Data;
use rocket::http::{Method, Status}; use rocket::http::{Method, Status};
use rocket::local::blocking::Client; use rocket::local::blocking::Client;
use rocket::catcher::{Catcher, ErrorHandlerFuture};
use rocket::handler::HandlerFuture;
#[get("/panic")] #[get("/panic")]
fn panic_route() -> &'static str { fn panic_route() -> &'static str {
@ -22,7 +20,7 @@ fn ise() -> &'static str {
"Hey, sorry! :(" "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..."); panic!("hey now...");
} }
@ -75,7 +73,7 @@ fn catches_early_route_panic() {
#[test] #[test]
fn catches_early_catcher_panic() { 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") panic!("a panicking pre-future catcher")
} }

View File

@ -3,68 +3,66 @@ mod tests;
use std::env; use std::env;
use rocket::{Request, Route}; use rocket::{Request, Route, Catcher, route, catcher};
use rocket::data::{Data, ToByteUnit}; use rocket::data::{Data, ToByteUnit};
use rocket::http::{Status, Method::*}; use rocket::http::{Status, Method::*};
use rocket::response::{Responder, status::Custom}; 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::outcome::{try_outcome, IntoOutcome};
use rocket::tokio::fs::File; use rocket::tokio::fs::File;
fn forward<'r>(_req: &'r Request, data: Data) -> HandlerFuture<'r> { fn forward<'r>(_req: &'r Request, data: Data) -> route::BoxFuture<'r> {
Box::pin(async move { Outcome::forward(data) }) Box::pin(async move { route::Outcome::forward(data) })
} }
fn hi<'r>(req: &'r Request, _: Data) -> HandlerFuture<'r> { fn hi<'r>(req: &'r Request, _: Data) -> route::BoxFuture<'r> {
Outcome::from(req, "Hello!").pin() 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) let param = req.param::<&'a str>(0)
.and_then(|res| res.ok()) .and_then(|res| res.ok())
.unwrap_or("unnamed".into()); .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) let param_outcome = req.param::<&str>(1)
.and_then(|res| res.ok()) .and_then(|res| res.ok())
.into_outcome(Status::BadRequest); .into_outcome(Status::BadRequest);
Box::pin(async move { 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 { Box::pin(async move {
if !req.content_type().map_or(false, |ct| ct.is_plain()) { if !req.content_type().map_or(false, |ct| ct.is_plain()) {
println!(" => Content-Type of upload must be text/plain. Ignoring."); 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; let file = File::create(env::temp_dir().join("upload.txt")).await;
if let Ok(file) = file { if let Ok(file) = file {
if let Ok(n) = data.open(2.mebibytes()).stream_to(file).await { 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."); println!(" => Failed copying.");
Outcome::failure(Status::InternalServerError) route::Outcome::failure(Status::InternalServerError)
} else { } else {
println!(" => Couldn't open file: {:?}", file.unwrap_err()); 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> { fn get_upload<'r>(req: &'r Request, _: Data) -> route::BoxFuture<'r> {
Outcome::from(req, std::fs::File::open(env::temp_dir().join("upload.txt")).ok()).pin() 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())); let res = Custom(Status::NotFound, format!("Couldn't find: {}", req.uri()));
Box::pin(async move { res.respond_to(req) }) Box::pin(async move { res.respond_to(req) })
} }
@ -81,14 +79,14 @@ impl CustomHandler {
} }
#[rocket::async_trait] #[rocket::async_trait]
impl Handler for CustomHandler { impl route::Handler for CustomHandler {
async fn handle<'r, 's: 'r>(&'s self, req: &'r Request<'_>, data: Data) -> Outcome<'r> { async fn handle<'r, 's: 'r>(&'s self, req: &'r Request<'_>, data: Data) -> route::Outcome<'r> {
let self_data = self.data; let self_data = self.data;
let id = req.param::<&str>(0) let id = req.param::<&str>(0)
.and_then(|res| res.ok()) .and_then(|res| res.ok())
.or_forward(data); .or_forward(data);
Outcome::from(req, format!("{} - {}", self_data, try_outcome!(id))) route::Outcome::from(req, format!("{} - {}", self_data, try_outcome!(id)))
} }
} }