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