mirror of https://github.com/rwf2/Rocket.git
Move catcher, route types into eponymous modules.
In the course, significantly improve their documentation.
This commit is contained in:
parent
4c96ae7b52
commit
887b2aed87
|
@ -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`.
|
||||||
///
|
///
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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`
|
||||||
|
|
|
@ -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`
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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::*;
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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>;
|
|
@ -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;
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue