mirror of https://github.com/rwf2/Rocket.git
Add default catchers: '#[catch(default)]'.
The bulk of the changes in this commit are for creating an 'ErrorHandler' trait that works like the 'Handler' trait, but for errors. Furthermore, Rocket's default catcher now responds with a JSON payload if the preferred 'Accept' media type is JSON. This commit also fixes a bug in 'LocalRequest' where the internal 'Request' contained an correct 'URI'.
This commit is contained in:
parent
e531770989
commit
45b4436ed3
|
@ -1,8 +1,8 @@
|
||||||
use devise::{syn, Spanned, Result, FromMeta, Diagnostic};
|
|
||||||
use devise::ext::SpanDiagnosticExt;
|
use devise::ext::SpanDiagnosticExt;
|
||||||
|
use devise::{syn, MetaItem, Spanned, Result, FromMeta, Diagnostic};
|
||||||
|
|
||||||
|
use crate::http_codegen::{self, Optional};
|
||||||
use crate::proc_macro2::{TokenStream, Span};
|
use crate::proc_macro2::{TokenStream, Span};
|
||||||
use crate::http_codegen::Status;
|
|
||||||
use crate::syn_ext::{IdentExt, ReturnTypeExt, TokenStreamExt};
|
use crate::syn_ext::{IdentExt, ReturnTypeExt, TokenStreamExt};
|
||||||
use self::syn::{Attribute, parse::Parser};
|
use self::syn::{Attribute, parse::Parser};
|
||||||
use crate::{CATCH_FN_PREFIX, CATCH_STRUCT_PREFIX};
|
use crate::{CATCH_FN_PREFIX, CATCH_STRUCT_PREFIX};
|
||||||
|
@ -11,13 +11,35 @@ use crate::{CATCH_FN_PREFIX, CATCH_STRUCT_PREFIX};
|
||||||
#[derive(Debug, FromMeta)]
|
#[derive(Debug, FromMeta)]
|
||||||
struct CatchAttribute {
|
struct CatchAttribute {
|
||||||
#[meta(naked)]
|
#[meta(naked)]
|
||||||
status: Status
|
status: CatcherCode
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This structure represents the parsed `catch` attribute an associated items.
|
/// `Some` if there's a code, `None` if it's `default`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct CatcherCode(Option<http_codegen::Status>);
|
||||||
|
|
||||||
|
impl FromMeta for CatcherCode {
|
||||||
|
fn from_meta(m: MetaItem<'_>) -> Result<Self> {
|
||||||
|
if usize::from_meta(m).is_ok() {
|
||||||
|
let status = http_codegen::Status::from_meta(m)?;
|
||||||
|
Ok(CatcherCode(Some(status)))
|
||||||
|
} else if let MetaItem::Path(path) = m {
|
||||||
|
if path.is_ident("default") {
|
||||||
|
Ok(CatcherCode(None))
|
||||||
|
} else {
|
||||||
|
Err(m.span().error(format!("expected `default`")))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let msg = format!("expected integer or identifier, found {}", m.description());
|
||||||
|
Err(m.span().error(msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This structure represents the parsed `catch` attribute and associated items.
|
||||||
struct CatchParams {
|
struct CatchParams {
|
||||||
/// The status associated with the code in the `#[catch(code)]` attribute.
|
/// The status associated with the code in the `#[catch(code)]` attribute.
|
||||||
status: Status,
|
status: Option<http_codegen::Status>,
|
||||||
/// The function that was decorated with the `catch` attribute.
|
/// The function that was decorated with the `catch` attribute.
|
||||||
function: syn::ItemFn,
|
function: syn::ItemFn,
|
||||||
}
|
}
|
||||||
|
@ -33,13 +55,14 @@ fn parse_params(
|
||||||
let full_attr = quote!(#[catch(#args)]);
|
let full_attr = quote!(#[catch(#args)]);
|
||||||
let attrs = Attribute::parse_outer.parse2(full_attr)?;
|
let attrs = Attribute::parse_outer.parse2(full_attr)?;
|
||||||
let attribute = match CatchAttribute::from_attrs("catch", &attrs) {
|
let attribute = match CatchAttribute::from_attrs("catch", &attrs) {
|
||||||
Some(result) => result.map_err(|d| {
|
Some(result) => result.map_err(|diag| {
|
||||||
d.help("`#[catch]` expects a single status integer, e.g.: #[catch(404)]")
|
diag.help("`#[catch]` expects a status code int or `default`: \
|
||||||
|
`#[catch(404)]` or `#[catch(default)]`")
|
||||||
})?,
|
})?,
|
||||||
None => return Err(Span::call_site().error("internal error: bad attribute"))
|
None => return Err(Span::call_site().error("internal error: bad attribute"))
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(CatchParams { status: attribute.status, function })
|
Ok(CatchParams { status: attribute.status.0, function })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _catch(
|
pub fn _catch(
|
||||||
|
@ -54,59 +77,41 @@ pub fn _catch(
|
||||||
let user_catcher_fn_name = catch.function.sig.ident.clone();
|
let user_catcher_fn_name = catch.function.sig.ident.clone();
|
||||||
let generated_struct_name = user_catcher_fn_name.prepend(CATCH_STRUCT_PREFIX);
|
let generated_struct_name = user_catcher_fn_name.prepend(CATCH_STRUCT_PREFIX);
|
||||||
let generated_fn_name = user_catcher_fn_name.prepend(CATCH_FN_PREFIX);
|
let generated_fn_name = user_catcher_fn_name.prepend(CATCH_FN_PREFIX);
|
||||||
let (vis, status) = (&catch.function.vis, &catch.status);
|
let (vis, catcher_status) = (&catch.function.vis, &catch.status);
|
||||||
let status_code = status.0.code;
|
let status_code = Optional(catcher_status.as_ref().map(|s| s.0.code));
|
||||||
|
|
||||||
// Variables names we'll use and reuse.
|
// Variables names we'll use and reuse.
|
||||||
define_vars_and_mods!(catch.function.span().into() =>
|
define_vars_and_mods!(catch.function.span().into() =>
|
||||||
req, _Box, Request, Response, CatcherFuture);
|
req, status, _Box, Request, Response, ErrorHandlerFuture, Status);
|
||||||
|
|
||||||
// Determine the number of parameters that will be passed in.
|
// Determine the number of parameters that will be passed in.
|
||||||
if catch.function.sig.inputs.len() > 1 {
|
if catch.function.sig.inputs.len() > 2 {
|
||||||
return Err(catch.function.sig.paren_token.span
|
return Err(catch.function.sig.paren_token.span
|
||||||
.error("invalid number of arguments: must be zero or one")
|
.error("invalid number of arguments: must be zero, one, or two")
|
||||||
.help("catchers may optionally take an argument of type `&Request`"));
|
.help("catchers optionally take `&Request` or `Status, &Request`"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: It would be nice if this worked! Alas, either there is a rustc bug
|
|
||||||
// that prevents this from working (error on `Output` type of `Future`), or
|
|
||||||
// this simply isn't possible with `async fn`.
|
|
||||||
// // Typecheck the catcher function if it has arguments.
|
|
||||||
// user_catcher_fn_name.set_span(catch.function.sig.paren_token.span.into());
|
|
||||||
// let user_catcher_fn_call = catch.function.sig.inputs.first()
|
|
||||||
// .map(|arg| {
|
|
||||||
// let ty = quote!(fn(&#Request) -> _).respanned(Span::call_site().into());
|
|
||||||
// let req = req.respanned(arg.span().into());
|
|
||||||
// quote!({
|
|
||||||
// let #user_catcher_fn_name: #ty = #user_catcher_fn_name;
|
|
||||||
// #user_catcher_fn_name(#req)
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// .unwrap_or_else(|| quote!(#user_catcher_fn_name()));
|
|
||||||
//
|
|
||||||
// let catcher_response = quote_spanned!(return_type_span => {
|
|
||||||
// let ___responder = #user_catcher_fn_call #dot_await;
|
|
||||||
// ::rocket::response::Responder::respond_to(___responder, #req)?
|
|
||||||
// });
|
|
||||||
|
|
||||||
// This ensures that "Responder not implemented" points to the return type.
|
// This ensures that "Responder not implemented" points to the return type.
|
||||||
let return_type_span = catch.function.sig.output.ty()
|
let return_type_span = catch.function.sig.output.ty()
|
||||||
.map(|ty| ty.span().into())
|
.map(|ty| ty.span().into())
|
||||||
.unwrap_or(Span::call_site().into());
|
.unwrap_or(Span::call_site().into());
|
||||||
|
|
||||||
// Set the `req` span to that of the arg for a correct `Wrong type` span.
|
// Set the `req` and `status` spans to that of their respective function
|
||||||
let input = catch.function.sig.inputs.first()
|
// arguments for a more correct `wrong type` error span. `rev` to be cute.
|
||||||
.map(|arg| match arg {
|
let codegen_args = &[&req, &status];
|
||||||
syn::FnArg::Receiver(_) => req.respanned(arg.span()),
|
let inputs = catch.function.sig.inputs.iter().rev()
|
||||||
syn::FnArg::Typed(a) => req.respanned(a.ty.span())
|
.zip(codegen_args.into_iter())
|
||||||
});
|
.map(|(fn_arg, codegen_arg)| match fn_arg {
|
||||||
|
syn::FnArg::Receiver(_) => codegen_arg.respanned(fn_arg.span()),
|
||||||
|
syn::FnArg::Typed(a) => codegen_arg.respanned(a.ty.span())
|
||||||
|
}).rev();
|
||||||
|
|
||||||
// We append `.await` to the function call if this is `async`.
|
// We append `.await` to the function call if this is `async`.
|
||||||
let dot_await = catch.function.sig.asyncness
|
let dot_await = catch.function.sig.asyncness
|
||||||
.map(|a| quote_spanned!(a.span().into() => .await));
|
.map(|a| quote_spanned!(a.span().into() => .await));
|
||||||
|
|
||||||
let catcher_response = quote_spanned!(return_type_span => {
|
let catcher_response = quote_spanned!(return_type_span => {
|
||||||
let ___responder = #user_catcher_fn_name(#input) #dot_await;
|
let ___responder = #user_catcher_fn_name(#(#inputs),*) #dot_await;
|
||||||
::rocket::response::Responder::respond_to(___responder, #req)?
|
::rocket::response::Responder::respond_to(___responder, #req)?
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -116,7 +121,10 @@ pub fn _catch(
|
||||||
|
|
||||||
/// Rocket code generated wrapping catch function.
|
/// Rocket code generated wrapping catch function.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#vis fn #generated_fn_name<'_b>(#req: &'_b #Request) -> #CatcherFuture<'_b> {
|
#vis fn #generated_fn_name<'_b>(
|
||||||
|
#status: #Status,
|
||||||
|
#req: &'_b #Request
|
||||||
|
) -> #ErrorHandlerFuture<'_b> {
|
||||||
#_Box::pin(async move {
|
#_Box::pin(async move {
|
||||||
let __response = #catcher_response;
|
let __response = #catcher_response;
|
||||||
#Response::build()
|
#Response::build()
|
||||||
|
@ -129,8 +137,8 @@ pub fn _catch(
|
||||||
/// Rocket code generated static catcher info.
|
/// Rocket code generated static catcher info.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
#vis static #generated_struct_name: ::rocket::StaticCatchInfo =
|
#vis static #generated_struct_name: ::rocket::StaticCatcherInfo =
|
||||||
::rocket::StaticCatchInfo {
|
::rocket::StaticCatcherInfo {
|
||||||
code: #status_code,
|
code: #status_code,
|
||||||
handler: #generated_fn_name,
|
handler: #generated_fn_name,
|
||||||
};
|
};
|
||||||
|
|
|
@ -74,6 +74,7 @@ macro_rules! vars_and_mods {
|
||||||
|
|
||||||
vars_and_mods! {
|
vars_and_mods! {
|
||||||
req => __req,
|
req => __req,
|
||||||
|
status => __status,
|
||||||
catcher => __catcher,
|
catcher => __catcher,
|
||||||
data => __data,
|
data => __data,
|
||||||
error => __error,
|
error => __error,
|
||||||
|
@ -92,8 +93,9 @@ vars_and_mods! {
|
||||||
Data => rocket::data::Data,
|
Data => rocket::data::Data,
|
||||||
StaticRouteInfo => rocket::StaticRouteInfo,
|
StaticRouteInfo => rocket::StaticRouteInfo,
|
||||||
SmallVec => rocket::http::private::SmallVec,
|
SmallVec => rocket::http::private::SmallVec,
|
||||||
|
Status => rocket::http::Status,
|
||||||
HandlerFuture => rocket::handler::HandlerFuture,
|
HandlerFuture => rocket::handler::HandlerFuture,
|
||||||
CatcherFuture => rocket::handler::CatcherFuture,
|
ErrorHandlerFuture => rocket::catcher::ErrorHandlerFuture,
|
||||||
_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,
|
||||||
|
@ -350,11 +352,17 @@ route_attribute!(options => Method::Options);
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// #
|
/// #
|
||||||
/// use rocket::Request;
|
/// use rocket::Request;
|
||||||
|
/// use rocket::http::Status;
|
||||||
///
|
///
|
||||||
/// #[catch(404)]
|
/// #[catch(404)]
|
||||||
/// fn not_found(req: &Request) -> String {
|
/// fn not_found(req: &Request) -> String {
|
||||||
/// format!("Sorry, {} does not exist.", req.uri())
|
/// format!("Sorry, {} does not exist.", req.uri())
|
||||||
/// }
|
/// }
|
||||||
|
///
|
||||||
|
/// #[catch(default)]
|
||||||
|
/// fn default(status: Status, req: &Request) -> String {
|
||||||
|
/// format!("{} - {} ({})", status.code, status.reason, req.uri())
|
||||||
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Grammar
|
/// # Grammar
|
||||||
|
@ -362,19 +370,19 @@ route_attribute!(options => Method::Options);
|
||||||
/// The grammar for the `#[catch]` attributes is defined as:
|
/// The grammar for the `#[catch]` attributes is defined as:
|
||||||
///
|
///
|
||||||
/// ```text
|
/// ```text
|
||||||
/// catch := STATUS
|
/// catch := STATUS | 'default'
|
||||||
///
|
///
|
||||||
/// STATUS := valid HTTP status code (integer in [200, 599])
|
/// STATUS := valid HTTP status code (integer in [200, 599])
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Typing Requirements
|
/// # Typing Requirements
|
||||||
///
|
///
|
||||||
/// The decorated function must take exactly zero or one argument. If the
|
/// The decorated function may take zero, one, or two arguments. It's type
|
||||||
/// decorated function takes an argument, the argument's type must be
|
/// signature must be one of the following, where `R:`[`Responder`]:
|
||||||
/// [`&Request`].
|
|
||||||
///
|
///
|
||||||
/// The return type of the decorated function must implement the [`Responder`]
|
/// * `fn() -> R`
|
||||||
/// trait.
|
/// * `fn(`[`&Request`]`) -> R`
|
||||||
|
/// * `fn(`[`Status`]`, `[`&Request`]`) -> R`
|
||||||
///
|
///
|
||||||
/// # Semantics
|
/// # Semantics
|
||||||
///
|
///
|
||||||
|
@ -383,16 +391,18 @@ route_attribute!(options => Method::Options);
|
||||||
/// 1. An [`ErrorHandler`].
|
/// 1. An [`ErrorHandler`].
|
||||||
///
|
///
|
||||||
/// The generated handler calls the decorated function, passing in the
|
/// The generated handler calls the decorated function, passing in the
|
||||||
/// [`&Request`] value if requested. The returned value is used to generate
|
/// [`Status`] and [`&Request`] values if requested. The returned value is
|
||||||
/// a [`Response`] via the type's [`Responder`] implementation.
|
/// used to generate a [`Response`] via the type's [`Responder`]
|
||||||
|
/// implementation.
|
||||||
///
|
///
|
||||||
/// 2. A static structure used by [`catchers!`] to generate a [`Catcher`].
|
/// 2. A static structure used by [`catchers!`] to generate a [`Catcher`].
|
||||||
///
|
///
|
||||||
/// The static structure (and resulting [`Catcher`]) is populated
|
/// The static structure (and resulting [`Catcher`]) is populated with the
|
||||||
/// with the name (the function's name) and status code from the
|
/// name (the function's name) and status code from the route attribute or
|
||||||
/// route attribute. The handler is set to the generated handler.
|
/// `None` if `default`. The handler is set to the generated handler.
|
||||||
///
|
///
|
||||||
/// [`&Request`]: ../rocket/struct.Request.html
|
/// [`&Request`]: ../rocket/struct.Request.html
|
||||||
|
/// [`Status`]: ../rocket/http/struct.Status.html
|
||||||
/// [`ErrorHandler`]: ../rocket/type.ErrorHandler.html
|
/// [`ErrorHandler`]: ../rocket/type.ErrorHandler.html
|
||||||
/// [`catchers!`]: macro.catchers.html
|
/// [`catchers!`]: macro.catchers.html
|
||||||
/// [`Catcher`]: ../rocket/struct.Catcher.html
|
/// [`Catcher`]: ../rocket/struct.Catcher.html
|
||||||
|
@ -842,6 +852,9 @@ pub fn routes(input: TokenStream) -> TokenStream {
|
||||||
/// #[catch(400)]
|
/// #[catch(400)]
|
||||||
/// pub fn unauthorized() { /* .. */ }
|
/// pub fn unauthorized() { /* .. */ }
|
||||||
/// }
|
/// }
|
||||||
|
///
|
||||||
|
/// #[catch(default)]
|
||||||
|
/// fn default_catcher() { /* .. */ }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// The `catchers!` macro can be used as:
|
/// The `catchers!` macro can be used as:
|
||||||
|
@ -850,18 +863,21 @@ pub fn routes(input: TokenStream) -> TokenStream {
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// #
|
/// #
|
||||||
/// # #[catch(404)] fn not_found() { /* .. */ }
|
/// # #[catch(404)] fn not_found() { /* .. */ }
|
||||||
|
/// # #[catch(default)] fn default_catcher() { /* .. */ }
|
||||||
/// # mod inner {
|
/// # mod inner {
|
||||||
/// # #[catch(400)] pub fn unauthorized() { /* .. */ }
|
/// # #[catch(400)] pub fn unauthorized() { /* .. */ }
|
||||||
/// # }
|
/// # }
|
||||||
/// #
|
/// let my_catchers = catchers![not_found, inner::unauthorized, default_catcher];
|
||||||
/// let my_catchers = catchers![not_found, inner::unauthorized];
|
/// assert_eq!(my_catchers.len(), 3);
|
||||||
/// assert_eq!(my_catchers.len(), 2);
|
|
||||||
///
|
///
|
||||||
/// let not_found = &my_catchers[0];
|
/// let not_found = &my_catchers[0];
|
||||||
/// assert_eq!(not_found.code, 404);
|
/// assert_eq!(not_found.code, Some(404));
|
||||||
///
|
///
|
||||||
/// let unauthorized = &my_catchers[1];
|
/// let unauthorized = &my_catchers[1];
|
||||||
/// assert_eq!(unauthorized.code, 400);
|
/// assert_eq!(unauthorized.code, Some(400));
|
||||||
|
///
|
||||||
|
/// let default = &my_catchers[2];
|
||||||
|
/// assert_eq!(default.code, None);
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// The grammar for `catchers!` is defined as:
|
/// The grammar for `catchers!` is defined as:
|
||||||
|
|
|
@ -14,13 +14,13 @@ error: expected `fn`
|
||||||
|
|
|
|
||||||
= help: `#[catch]` can only be used on functions
|
= help: `#[catch]` can only be used on functions
|
||||||
|
|
||||||
error: invalid value: expected unsigned integer literal
|
error: expected integer or identifier, found string literal
|
||||||
--> $DIR/catch.rs:11:9
|
--> $DIR/catch.rs:11:9
|
||||||
|
|
|
|
||||||
11 | #[catch("404")]
|
11 | #[catch("404")]
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
|
|
|
|
||||||
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
= help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]`
|
||||||
|
|
||||||
error: unexpected keyed parameter: expected literal or identifier
|
error: unexpected keyed parameter: expected literal or identifier
|
||||||
--> $DIR/catch.rs:14:9
|
--> $DIR/catch.rs:14:9
|
||||||
|
@ -28,7 +28,7 @@ error: unexpected keyed parameter: expected literal or identifier
|
||||||
14 | #[catch(code = "404")]
|
14 | #[catch(code = "404")]
|
||||||
| ^^^^^^^^^^^^
|
| ^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
= help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]`
|
||||||
|
|
||||||
error: unexpected keyed parameter: expected literal or identifier
|
error: unexpected keyed parameter: expected literal or identifier
|
||||||
--> $DIR/catch.rs:17:9
|
--> $DIR/catch.rs:17:9
|
||||||
|
@ -36,7 +36,7 @@ error: unexpected keyed parameter: expected literal or identifier
|
||||||
17 | #[catch(code = 404)]
|
17 | #[catch(code = 404)]
|
||||||
| ^^^^^^^^^^
|
| ^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
= help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]`
|
||||||
|
|
||||||
error: status must be in range [100, 599]
|
error: status must be in range [100, 599]
|
||||||
--> $DIR/catch.rs:20:9
|
--> $DIR/catch.rs:20:9
|
||||||
|
@ -44,7 +44,7 @@ error: status must be in range [100, 599]
|
||||||
20 | #[catch(99)]
|
20 | #[catch(99)]
|
||||||
| ^^
|
| ^^
|
||||||
|
|
|
|
||||||
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
= help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]`
|
||||||
|
|
||||||
error: status must be in range [100, 599]
|
error: status must be in range [100, 599]
|
||||||
--> $DIR/catch.rs:23:9
|
--> $DIR/catch.rs:23:9
|
||||||
|
@ -52,7 +52,7 @@ error: status must be in range [100, 599]
|
||||||
23 | #[catch(600)]
|
23 | #[catch(600)]
|
||||||
| ^^^
|
| ^^^
|
||||||
|
|
|
|
||||||
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
= help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]`
|
||||||
|
|
||||||
error: unexpected attribute parameter: `message`
|
error: unexpected attribute parameter: `message`
|
||||||
--> $DIR/catch.rs:26:14
|
--> $DIR/catch.rs:26:14
|
||||||
|
@ -60,20 +60,16 @@ error: unexpected attribute parameter: `message`
|
||||||
26 | #[catch(400, message = "foo")]
|
26 | #[catch(400, message = "foo")]
|
||||||
| ^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
= help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]`
|
||||||
|
|
||||||
error: invalid number of arguments: must be zero or one
|
error[E0308]: mismatched types
|
||||||
--> $DIR/catch.rs:30:6
|
--> $DIR/catch.rs:30:17
|
||||||
|
|
|
|
||||||
30 | fn f3(_request: &Request, other: bool) { }
|
30 | fn f3(_request: &Request, other: bool) { }
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^ expected `&rocket::Request<'_>`, found struct `rocket::http::Status`
|
||||||
|
|
|
||||||
= help: catchers may optionally take an argument of type `&Request`
|
|
||||||
|
|
||||||
warning: unused import: `rocket::Request`
|
error[E0308]: mismatched types
|
||||||
--> $DIR/catch.rs:3:5
|
--> $DIR/catch.rs:30:34
|
||||||
|
|
|
|
||||||
3 | use rocket::Request;
|
30 | fn f3(_request: &Request, other: bool) { }
|
||||||
| ^^^^^^^^^^^^^^^
|
| ^^^^ expected `bool`, found `&rocket::Request<'_>`
|
||||||
|
|
|
||||||
= note: `#[warn(unused_imports)]` on by default
|
|
||||||
|
|
|
@ -12,59 +12,56 @@ error: expected `fn`
|
||||||
9 | const CATCH: &str = "Catcher";
|
9 | const CATCH: &str = "Catcher";
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
|
|
||||||
error: invalid value: expected unsigned integer literal
|
error: expected integer or identifier, found string literal
|
||||||
--- help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
--- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]`
|
||||||
--> $DIR/catch.rs:11:9
|
--> $DIR/catch.rs:11:9
|
||||||
|
|
|
|
||||||
11 | #[catch("404")]
|
11 | #[catch("404")]
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
|
|
||||||
error: unexpected keyed parameter: expected literal or identifier
|
error: unexpected keyed parameter: expected literal or identifier
|
||||||
--- help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
--- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]`
|
||||||
--> $DIR/catch.rs:14:9
|
--> $DIR/catch.rs:14:9
|
||||||
|
|
|
|
||||||
14 | #[catch(code = "404")]
|
14 | #[catch(code = "404")]
|
||||||
| ^^^^
|
| ^^^^
|
||||||
|
|
||||||
error: unexpected keyed parameter: expected literal or identifier
|
error: unexpected keyed parameter: expected literal or identifier
|
||||||
--- help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
--- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]`
|
||||||
--> $DIR/catch.rs:17:9
|
--> $DIR/catch.rs:17:9
|
||||||
|
|
|
|
||||||
17 | #[catch(code = 404)]
|
17 | #[catch(code = 404)]
|
||||||
| ^^^^
|
| ^^^^
|
||||||
|
|
||||||
error: status must be in range [100, 599]
|
error: status must be in range [100, 599]
|
||||||
--- help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
--- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]`
|
||||||
--> $DIR/catch.rs:20:9
|
--> $DIR/catch.rs:20:9
|
||||||
|
|
|
|
||||||
20 | #[catch(99)]
|
20 | #[catch(99)]
|
||||||
| ^^
|
| ^^
|
||||||
|
|
||||||
error: status must be in range [100, 599]
|
error: status must be in range [100, 599]
|
||||||
--- help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
--- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]`
|
||||||
--> $DIR/catch.rs:23:9
|
--> $DIR/catch.rs:23:9
|
||||||
|
|
|
|
||||||
23 | #[catch(600)]
|
23 | #[catch(600)]
|
||||||
| ^^^
|
| ^^^
|
||||||
|
|
||||||
error: unexpected attribute parameter: `message`
|
error: unexpected attribute parameter: `message`
|
||||||
--- help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
--- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]`
|
||||||
--> $DIR/catch.rs:26:14
|
--> $DIR/catch.rs:26:14
|
||||||
|
|
|
|
||||||
26 | #[catch(400, message = "foo")]
|
26 | #[catch(400, message = "foo")]
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
|
|
||||||
error: invalid number of arguments: must be zero or one
|
error[E0308]: mismatched types
|
||||||
--- help: catchers may optionally take an argument of type `&Request`
|
--> $DIR/catch.rs:30:17
|
||||||
--> $DIR/catch.rs:30:6
|
|
||||||
|
|
|
|
||||||
30 | fn f3(_request: &Request, other: bool) { }
|
30 | fn f3(_request: &Request, other: bool) { }
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^ expected `&rocket::Request<'_>`, found struct `rocket::http::Status`
|
||||||
|
|
||||||
warning: unused import: `rocket::Request`
|
error[E0308]: mismatched types
|
||||||
--> $DIR/catch.rs:3:5
|
--> $DIR/catch.rs:30:34
|
||||||
|
|
|
|
||||||
3 | use rocket::Request;
|
30 | fn f3(_request: &Request, other: bool) { }
|
||||||
| ^^^^^^^^^^^^^^^
|
| ^^^^ expected `bool`, found `&rocket::Request<'_>`
|
||||||
|
|
|
||||||
= note: `#[warn(unused_imports)]` on by default
|
|
||||||
|
|
|
@ -154,7 +154,7 @@ impl Status {
|
||||||
/// assert_eq!(custom.to_string(), "299 Somewhat Successful".to_string());
|
/// assert_eq!(custom.to_string(), "299 Somewhat Successful".to_string());
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn new(code: u16, reason: &'static str) -> Status {
|
pub const fn new(code: u16, reason: &'static str) -> Status {
|
||||||
Status { code, reason }
|
Status { code, reason }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +184,7 @@ impl Status {
|
||||||
/// let custom = Status::new(600, "Bizarre");
|
/// let custom = Status::new(600, "Bizarre");
|
||||||
/// assert_eq!(custom.class(), StatusClass::Unknown);
|
/// assert_eq!(custom.class(), StatusClass::Unknown);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn class(&self) -> StatusClass {
|
pub fn class(self) -> StatusClass {
|
||||||
match self.code / 100 {
|
match self.code / 100 {
|
||||||
1 => StatusClass::Informational,
|
1 => StatusClass::Informational,
|
||||||
2 => StatusClass::Success,
|
2 => StatusClass::Success,
|
||||||
|
|
|
@ -1,44 +1,67 @@
|
||||||
use std::future::Future;
|
//! Types and traits for error catchers, error handlers, and their return
|
||||||
|
//! values.
|
||||||
use crate::response;
|
|
||||||
use crate::handler::ErrorHandler;
|
|
||||||
use crate::codegen::StaticCatchInfo;
|
|
||||||
use crate::request::Request;
|
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use yansi::Color::*;
|
|
||||||
|
use crate::response::Response;
|
||||||
|
use crate::codegen::StaticCatcherInfo;
|
||||||
|
use crate::request::Request;
|
||||||
|
|
||||||
|
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>>;
|
||||||
|
|
||||||
/// An error catching route.
|
/// An error catching route.
|
||||||
///
|
///
|
||||||
/// Catchers are routes that run when errors occur. They correspond directly
|
/// # Overview
|
||||||
/// with the HTTP error status code they will be handling and are registered
|
|
||||||
/// with Rocket via [`Rocket::register()`](crate::Rocket::register()). For example,
|
|
||||||
/// to handle "404 not found" errors, a catcher for the "404" status code is
|
|
||||||
/// registered.
|
|
||||||
///
|
///
|
||||||
/// Because error handlers are only called when all routes are exhausted, they
|
/// Catchers are routes that run when errors are produced by the application.
|
||||||
/// should not fail nor forward. If an error catcher fails, the user will
|
/// They consist of an [`ErrorHandler`] and an optional status code to match
|
||||||
/// receive no response. If an error catcher forwards, Rocket will respond with
|
/// against arising errors. Errors arise from the the following sources:
|
||||||
/// an internal server error.
|
|
||||||
///
|
///
|
||||||
/// # Built-In Catchers
|
/// * A failing guard.
|
||||||
|
/// * A failing responder.
|
||||||
|
/// * Routing failure.
|
||||||
///
|
///
|
||||||
/// Rocket has many built-in, pre-registered default catchers. In particular,
|
/// Each failure is paired with a status code. Guards and responders indicate
|
||||||
/// Rocket has catchers for all of the following status codes: 400, 401, 402,
|
/// the status code themselves via their `Err` return value while a routing
|
||||||
/// 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417,
|
/// failure is always a `404`. Rocket invokes the error handler for the catcher
|
||||||
/// 418, 421, 426, 428, 429, 431, 451, 500, 501, 503, and 510. As such, catchers
|
/// with the error's status code.
|
||||||
/// only need to be registered if an error needs to be handled in a custom
|
///
|
||||||
/// fashion.
|
/// ## 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
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
///
|
///
|
||||||
/// # Code Generation
|
/// # Code Generation
|
||||||
///
|
///
|
||||||
/// Catchers should rarely be used directly. Instead, they are typically
|
/// Catchers should rarely be constructed or used directly. Instead, they are
|
||||||
/// declared using the `catch` decorator, as follows:
|
/// typically generated via the [`catch`] attribute, as follows:
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// #[macro_use] extern crate rocket;
|
/// #[macro_use] extern crate rocket;
|
||||||
///
|
///
|
||||||
/// use rocket::Request;
|
/// use rocket::Request;
|
||||||
|
/// use rocket::http::Status;
|
||||||
///
|
///
|
||||||
/// #[catch(500)]
|
/// #[catch(500)]
|
||||||
/// fn internal_error() -> &'static str {
|
/// fn internal_error() -> &'static str {
|
||||||
|
@ -50,74 +73,204 @@ use yansi::Color::*;
|
||||||
/// format!("I couldn't find '{}'. Try something else?", req.uri())
|
/// format!("I couldn't find '{}'. Try something else?", req.uri())
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
|
/// #[catch(default)]
|
||||||
|
/// fn default(status: Status, req: &Request) -> String {
|
||||||
|
/// format!("{} - {} ({})", status.code, status.reason, req.uri())
|
||||||
|
/// }
|
||||||
|
///
|
||||||
/// #[launch]
|
/// #[launch]
|
||||||
/// fn rocket() -> rocket::Rocket {
|
/// fn rocket() -> rocket::Rocket {
|
||||||
/// rocket::ignite().register(catchers![internal_error, not_found])
|
/// rocket::ignite().register(catchers![internal_error, not_found, default])
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// A function decorated with `catch` must take exactly zero or one arguments.
|
/// A function decorated with `#[catch]` may take zero, one, or two arguments.
|
||||||
/// If the catcher takes an argument, it must be of type [`&Request`](Request).
|
/// It's type signature must be one of the following, where `R:`[`Responder`]:
|
||||||
|
///
|
||||||
|
/// * `fn() -> R`
|
||||||
|
/// * `fn(`[`&Request`]`) -> R`
|
||||||
|
/// * `fn(`[`Status`]`, `[`&Request`]`) -> R`
|
||||||
|
///
|
||||||
|
/// See the [`catch`] documentation for full details.
|
||||||
|
///
|
||||||
|
/// [`catch`]: rocket_codegen::catch
|
||||||
|
/// [`Responder`]: crate::response::Responder
|
||||||
|
/// [`&Request`]: crate::request::Request
|
||||||
|
/// [`Status`]: crate::http::Status
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Catcher {
|
pub struct Catcher {
|
||||||
/// The HTTP status code to match against.
|
/// The HTTP status code to match against if this route is not `default`.
|
||||||
pub code: u16,
|
pub code: Option<u16>,
|
||||||
/// The catcher's associated handler.
|
|
||||||
pub handler: ErrorHandler,
|
/// The catcher's associated error handler.
|
||||||
pub(crate) is_default: bool,
|
pub handler: Box<dyn ErrorHandler>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Catcher {
|
impl Catcher {
|
||||||
/// Creates a catcher for the given status code using the given error
|
/// Creates a catcher for the given status code, or a default catcher if
|
||||||
/// handler. This should only be used when routing manually.
|
/// `code` is `None`, using the given error handler. This should only be
|
||||||
|
/// used when routing manually.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![allow(unused_variables)]
|
/// use rocket::request::Request;
|
||||||
/// use rocket::{Catcher, Request};
|
/// use rocket::catcher::{Catcher, ErrorHandlerFuture};
|
||||||
/// use rocket::handler::CatcherFuture;
|
/// use rocket::response::{Result, Responder, status::Custom};
|
||||||
/// use rocket::response::{Result, Responder};
|
|
||||||
/// use rocket::response::status::Custom;
|
|
||||||
/// use rocket::http::Status;
|
/// use rocket::http::Status;
|
||||||
///
|
///
|
||||||
/// fn handle_404<'r>(req: &'r Request) -> CatcherFuture<'r> {
|
/// fn handle_404<'r>(status: Status, req: &'r Request<'_>) -> ErrorHandlerFuture<'r> {
|
||||||
/// let res = Custom(Status::NotFound, format!("404: {}", req.uri()));
|
/// let res = Custom(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>(req: &'r Request) -> CatcherFuture<'r> {
|
/// fn handle_500<'r>(_: Status, req: &'r Request<'_>) -> ErrorHandlerFuture<'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> {
|
||||||
|
/// let res = Custom(status, format!("{}: {}", status, req.uri()));
|
||||||
|
/// Box::pin(async move { res.respond_to(req) })
|
||||||
|
/// }
|
||||||
|
///
|
||||||
/// let not_found_catcher = Catcher::new(404, handle_404);
|
/// let not_found_catcher = Catcher::new(404, handle_404);
|
||||||
/// let internal_server_error_catcher = Catcher::new(500, handle_500);
|
/// let internal_server_error_catcher = Catcher::new(500, handle_500);
|
||||||
|
/// let default_error_catcher = Catcher::new(None, handle_default);
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn new(code: u16, handler: ErrorHandler) -> Catcher {
|
pub fn new<C, H>(code: C, handler: H) -> Catcher
|
||||||
Catcher { code, handler, is_default: false }
|
where C: Into<Option<u16>>, H: ErrorHandler
|
||||||
|
{
|
||||||
|
Catcher { code: code.into(), handler: Box::new(handler) }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
impl Default for Catcher {
|
||||||
pub(crate) fn handle<'r>(&self, req: &'r Request<'_>) -> impl Future<Output = response::Result<'r>> {
|
fn default() -> Self {
|
||||||
(self.handler)(req)
|
fn async_default<'r>(status: Status, request: &'r Request<'_>) -> ErrorHandlerFuture<'r> {
|
||||||
|
Box::pin(async move { default(status, request) })
|
||||||
|
}
|
||||||
|
|
||||||
|
Catcher { code: None, handler: Box::new(async_default) }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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::ignite()
|
||||||
|
/// // 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>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[crate::async_trait]
|
||||||
|
impl<F: Clone + Sync + Send + 'static> ErrorHandler for F
|
||||||
|
where for<'x> F: Fn(Status, &'x Request<'_>) -> ErrorHandlerFuture<'x>
|
||||||
|
{
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn new_default(code: u16, handler: ErrorHandler) -> Catcher {
|
async fn handle<'r, 's: 'r>(&'s self, status: Status, req: &'r Request<'_>) -> Result<'r> {
|
||||||
Catcher { code, handler, is_default: true, }
|
self(status, req).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
impl<'a> From<&'a StaticCatchInfo> for Catcher {
|
impl<'a> From<&'a StaticCatcherInfo> for Catcher {
|
||||||
fn from(info: &'a StaticCatchInfo) -> Catcher {
|
fn from(info: &'a StaticCatcherInfo) -> Catcher {
|
||||||
Catcher::new(info.code, info.handler)
|
Catcher::new(info.code, info.handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Catcher {
|
impl fmt::Display for Catcher {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}", Blue.paint(&self.code))
|
match self.code {
|
||||||
|
Some(code) => write!(f, "{}", Paint::blue(code)),
|
||||||
|
None => write!(f, "{}", Paint::blue("default"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,23 +278,22 @@ impl fmt::Debug for Catcher {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("Catcher")
|
f.debug_struct("Catcher")
|
||||||
.field("code", &self.code)
|
.field("code", &self.code)
|
||||||
.field("default", &self.is_default)
|
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! error_page_template {
|
macro_rules! html_error_template {
|
||||||
($code:expr, $name:expr, $description:expr) => (
|
($code:expr, $reason:expr, $description:expr) => (
|
||||||
concat!(r#"
|
concat!(r#"
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>"#, $code, " ", $name, r#"</title>
|
<title>"#, $code, " ", $reason, r#"</title>
|
||||||
</head>
|
</head>
|
||||||
<body align="center">
|
<body align="center">
|
||||||
<div role="main" align="center">
|
<div role="main" align="center">
|
||||||
<h1>"#, $code, ": ", $name, r#"</h1>
|
<h1>"#, $code, ": ", $reason, r#"</h1>
|
||||||
<p>"#, $description, r#"</p>
|
<p>"#, $description, r#"</p>
|
||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
|
@ -155,98 +307,136 @@ macro_rules! error_page_template {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! default_catchers {
|
macro_rules! json_error_template {
|
||||||
($($code:expr, $name:expr, $description:expr, $fn_name:ident),+) => (
|
($code:expr, $reason:expr, $description:expr) => (
|
||||||
let mut map = HashMap::new();
|
concat!(
|
||||||
|
r#"{
|
||||||
$(
|
"error": {
|
||||||
fn $fn_name<'r>(req: &'r Request<'_>) -> crate::handler::CatcherFuture<'r> {
|
"code": "#, $code, r#",
|
||||||
let status = Status::from_code($code).unwrap();
|
"reason": ""#, $reason, r#"",
|
||||||
let html = content::Html(error_page_template!($code, $name, $description));
|
"description": ""#, $description, r#""
|
||||||
Box::pin(async move { status::Custom(status, html).respond_to(req) })
|
}
|
||||||
}
|
}"#
|
||||||
|
)
|
||||||
map.insert($code, Catcher::new_default($code, $fn_name));
|
|
||||||
)+
|
|
||||||
|
|
||||||
map
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod defaults {
|
// This is unfortunate, but the `{`, `}` above make it unusable for `format!`.
|
||||||
use super::Catcher;
|
macro_rules! json_error_fmt_template {
|
||||||
|
($code:expr, $reason:expr, $description:expr) => (
|
||||||
|
concat!(
|
||||||
|
r#"{{
|
||||||
|
"error": {{
|
||||||
|
"code": "#, $code, r#",
|
||||||
|
"reason": ""#, $reason, r#"",
|
||||||
|
"description": ""#, $description, r#""
|
||||||
|
}}
|
||||||
|
}}"#
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
use std::collections::HashMap;
|
macro_rules! default_catcher_fn {
|
||||||
|
($($code:expr, $reason:expr, $description:expr),+) => (
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use crate::http::Status;
|
||||||
|
use crate::response::{content, status, Responder};
|
||||||
|
|
||||||
use crate::request::Request;
|
pub(crate) fn default<'r>(status: Status, req: &'r Request<'_>) -> Result<'r> {
|
||||||
use crate::response::{content, status, Responder};
|
if req.accept().map(|a| a.preferred().is_json()).unwrap_or(false) {
|
||||||
use crate::http::Status;
|
let json: Cow<'_, str> = match status.code {
|
||||||
|
$($code => json_error_template!($code, $reason, $description).into(),)*
|
||||||
|
code => format!(json_error_fmt_template!("{}", "Unknown Error",
|
||||||
|
"An unknown error has occurred."), code).into()
|
||||||
|
};
|
||||||
|
|
||||||
pub fn get() -> HashMap<u16, Catcher> {
|
status::Custom(status, content::Json(json)).respond_to(req)
|
||||||
default_catchers! {
|
} else {
|
||||||
400, "Bad Request", "The request could not be understood by the server due
|
let html: Cow<'_, str> = match status.code {
|
||||||
to malformed syntax.", handle_400,
|
$($code => html_error_template!($code, $reason, $description).into(),)*
|
||||||
401, "Unauthorized", "The request requires user authentication.",
|
code => format!(html_error_template!("{}", "Unknown Error",
|
||||||
handle_401,
|
"An unknown error has occurred."), code, code).into(),
|
||||||
402, "Payment Required", "The request could not be processed due to lack of
|
};
|
||||||
payment.", handle_402,
|
|
||||||
403, "Forbidden", "The server refused to authorize the request.", handle_403,
|
status::Custom(status, content::Html(html)).respond_to(req)
|
||||||
404, "Not Found", "The requested resource could not be found.", handle_404,
|
}
|
||||||
405, "Method Not Allowed", "The request method is not supported for the
|
|
||||||
requested resource.", handle_405,
|
|
||||||
406, "Not Acceptable", "The requested resource is capable of generating
|
|
||||||
only content not acceptable according to the Accept headers sent in the
|
|
||||||
request.", handle_406,
|
|
||||||
407, "Proxy Authentication Required", "Authentication with the proxy is
|
|
||||||
required.", handle_407,
|
|
||||||
408, "Request Timeout", "The server timed out waiting for the
|
|
||||||
request.", handle_408,
|
|
||||||
409, "Conflict", "The request could not be processed because of a conflict
|
|
||||||
in the request.", handle_409,
|
|
||||||
410, "Gone", "The resource requested is no longer available and will not be
|
|
||||||
available again.", handle_410,
|
|
||||||
411, "Length Required", "The request did not specify the length of its
|
|
||||||
content, which is required by the requested resource.", handle_411,
|
|
||||||
412, "Precondition Failed", "The server does not meet one of the
|
|
||||||
preconditions specified in the request.", handle_412,
|
|
||||||
413, "Payload Too Large", "The request is larger than the server is
|
|
||||||
willing or able to process.", handle_413,
|
|
||||||
414, "URI Too Long", "The URI provided was too long for the server to
|
|
||||||
process.", handle_414,
|
|
||||||
415, "Unsupported Media Type", "The request entity has a media type which
|
|
||||||
the server or resource does not support.", handle_415,
|
|
||||||
416, "Range Not Satisfiable", "The portion of the requested file cannot be
|
|
||||||
supplied by the server.", handle_416,
|
|
||||||
417, "Expectation Failed", "The server cannot meet the requirements of the
|
|
||||||
Expect request-header field.", handle_417,
|
|
||||||
418, "I'm a teapot", "I was requested to brew coffee, and I am a
|
|
||||||
teapot.", handle_418,
|
|
||||||
421, "Misdirected Request", "The server cannot produce a response for this
|
|
||||||
request.", handle_421,
|
|
||||||
422, "Unprocessable Entity", "The request was well-formed but was unable to
|
|
||||||
be followed due to semantic errors.", handle_422,
|
|
||||||
426, "Upgrade Required", "Switching to the protocol in the Upgrade header
|
|
||||||
field is required.", handle_426,
|
|
||||||
428, "Precondition Required", "The server requires the request to be
|
|
||||||
conditional.", handle_428,
|
|
||||||
429, "Too Many Requests", "Too many requests have been received
|
|
||||||
recently.", handle_429,
|
|
||||||
431, "Request Header Fields Too Large", "The server is unwilling to process
|
|
||||||
the request because either an individual header field, or all
|
|
||||||
the header fields collectively, are too large.", handle_431,
|
|
||||||
451, "Unavailable For Legal Reasons", "The requested resource is
|
|
||||||
unavailable due to a legal demand to deny access to this
|
|
||||||
resource.", handle_451,
|
|
||||||
500, "Internal Server Error", "The server encountered an internal error
|
|
||||||
while processing this request.", handle_500,
|
|
||||||
501, "Not Implemented", "The server either does not recognize the request
|
|
||||||
method, or it lacks the ability to fulfill the request.", handle_501,
|
|
||||||
503, "Service Unavailable", "The server is currently unavailable.",
|
|
||||||
handle_503,
|
|
||||||
504, "Gateway Timeout", "The server did not receive a timely
|
|
||||||
response from an upstream server.", handle_504,
|
|
||||||
510, "Not Extended", "Further extensions to the request are required for
|
|
||||||
the server to fulfill it.", handle_510
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
default_catcher_fn! {
|
||||||
|
400, "Bad Request", "The request could not be understood by the server due \
|
||||||
|
to malformed syntax.",
|
||||||
|
401, "Unauthorized", "The request requires user authentication.",
|
||||||
|
402, "Payment Required", "The request could not be processed due to lack of payment.",
|
||||||
|
403, "Forbidden", "The server refused to authorize the request.",
|
||||||
|
404, "Not Found", "The requested resource could not be found.",
|
||||||
|
405, "Method Not Allowed", "The request method is not supported for the requested resource.",
|
||||||
|
406, "Not Acceptable", "The requested resource is capable of generating only content not \
|
||||||
|
acceptable according to the Accept headers sent in the request.",
|
||||||
|
407, "Proxy Authentication Required", "Authentication with the proxy is required.",
|
||||||
|
408, "Request Timeout", "The server timed out waiting for the request.",
|
||||||
|
409, "Conflict", "The request could not be processed because of a conflict in the request.",
|
||||||
|
410, "Gone", "The resource requested is no longer available and will not be available again.",
|
||||||
|
411, "Length Required", "The request did not specify the length of its content, which is \
|
||||||
|
required by the requested resource.",
|
||||||
|
412, "Precondition Failed", "The server does not meet one of the \
|
||||||
|
preconditions specified in the request.",
|
||||||
|
413, "Payload Too Large", "The request is larger than the server is \
|
||||||
|
willing or able to process.",
|
||||||
|
414, "URI Too Long", "The URI provided was too long for the server to process.",
|
||||||
|
415, "Unsupported Media Type", "The request entity has a media type which \
|
||||||
|
the server or resource does not support.",
|
||||||
|
416, "Range Not Satisfiable", "The portion of the requested file cannot be \
|
||||||
|
supplied by the server.",
|
||||||
|
417, "Expectation Failed", "The server cannot meet the requirements of the \
|
||||||
|
Expect request-header field.",
|
||||||
|
418, "I'm a teapot", "I was requested to brew coffee, and I am a teapot.",
|
||||||
|
421, "Misdirected Request", "The server cannot produce a response for this request.",
|
||||||
|
422, "Unprocessable Entity", "The request was well-formed but was unable to \
|
||||||
|
be followed due to semantic errors.",
|
||||||
|
426, "Upgrade Required", "Switching to the protocol in the Upgrade header field is required.",
|
||||||
|
428, "Precondition Required", "The server requires the request to be conditional.",
|
||||||
|
429, "Too Many Requests", "Too many requests have been received recently.",
|
||||||
|
431, "Request Header Fields Too Large", "The server is unwilling to process \
|
||||||
|
the request because either an individual header field, or all the header \
|
||||||
|
fields collectively, are too large.",
|
||||||
|
451, "Unavailable For Legal Reasons", "The requested resource is unavailable \
|
||||||
|
due to a legal demand to deny access to this resource.",
|
||||||
|
500, "Internal Server Error", "The server encountered an internal error while \
|
||||||
|
processing this request.",
|
||||||
|
501, "Not Implemented", "The server either does not recognize the request \
|
||||||
|
method, or it lacks the ability to fulfill the request.",
|
||||||
|
503, "Service Unavailable", "The server is currently unavailable.",
|
||||||
|
504, "Gateway Timeout", "The server did not receive a timely response from an upstream server.",
|
||||||
|
510, "Not Extended", "Further extensions to the request are required for \
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
use crate::futures::future::BoxFuture;
|
|
||||||
|
|
||||||
use crate::{Request, Data};
|
use crate::{Request, Data};
|
||||||
use crate::handler::{Outcome, ErrorHandler};
|
use crate::handler::HandlerFuture;
|
||||||
use crate::http::{Method, MediaType};
|
use crate::catcher::ErrorHandlerFuture;
|
||||||
|
use crate::http::{Status, Method, MediaType};
|
||||||
|
|
||||||
/// Type of a static handler, which users annotate with Rocket's attribute.
|
/// Type of a route handler, generated from a `fn` annotated with `#[route]`.
|
||||||
pub type StaticHandler = for<'r> fn(&'r Request<'_>, Data) -> BoxFuture<'r, Outcome<'r>>;
|
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.
|
/// Information generated by the `route` attribute during codegen.
|
||||||
pub struct StaticRouteInfo {
|
pub struct StaticRouteInfo {
|
||||||
|
@ -24,9 +26,9 @@ pub struct StaticRouteInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information generated by the `catch` attribute during codegen.
|
/// Information generated by the `catch` attribute during codegen.
|
||||||
pub struct StaticCatchInfo {
|
pub struct StaticCatcherInfo {
|
||||||
/// The catcher's status code.
|
/// The catcher's status code.
|
||||||
pub code: u16,
|
pub code: Option<u16>,
|
||||||
/// The catcher's handler, i.e, the annotated function.
|
/// The catcher's handler, i.e, the annotated function.
|
||||||
pub handler: ErrorHandler,
|
pub handler: StaticErrorHandler,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
//! Types and traits for request and error handlers and their return values.
|
//! Types and traits for request handlers and their return values.
|
||||||
|
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
|
|
||||||
use crate::data::Data;
|
use crate::data::Data;
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::{self, Response, Responder};
|
use crate::response::{Response, Responder};
|
||||||
use crate::http::Status;
|
use crate::http::Status;
|
||||||
use crate::outcome;
|
use crate::outcome;
|
||||||
|
|
||||||
|
@ -151,30 +151,6 @@ pub trait Handler: Cloneable + Send + Sync + 'static {
|
||||||
async fn handle<'r, 's: 'r>(&'s self, request: &'r Request<'_>, data: Data) -> Outcome<'r>;
|
async fn handle<'r, 's: 'r>(&'s self, request: &'r Request<'_>, data: Data) -> Outcome<'r>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unfortunate but necessary hack to be able to clone a `Box<Handler>`.
|
|
||||||
///
|
|
||||||
/// This trait should _never_ (and cannot, due to coherence) be implemented by
|
|
||||||
/// any type. Instead, implement `Clone`. All types that implement `Clone` and
|
|
||||||
/// `Handler` automatically implement `Cloneable`.
|
|
||||||
pub trait Cloneable {
|
|
||||||
/// Clones `self`.
|
|
||||||
fn clone_handler(&self) -> Box<dyn Handler>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Handler + Clone> Cloneable for T {
|
|
||||||
#[inline(always)]
|
|
||||||
fn clone_handler(&self) -> Box<dyn Handler> {
|
|
||||||
Box::new(self.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for Box<dyn Handler> {
|
|
||||||
#[inline(always)]
|
|
||||||
fn clone(&self) -> Box<dyn Handler> {
|
|
||||||
self.clone_handler()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[crate::async_trait]
|
#[crate::async_trait]
|
||||||
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) -> HandlerFuture<'x>
|
||||||
|
@ -185,12 +161,6 @@ impl<F: Clone + Sync + Send + 'static> Handler for F
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type of an error handler.
|
|
||||||
pub type ErrorHandler = for<'r> fn(&'r Request<'_>) -> CatcherFuture<'r>;
|
|
||||||
|
|
||||||
/// Type type of `Future` returned by an error handler.
|
|
||||||
pub type CatcherFuture<'r> = BoxFuture<'r, response::Result<'r>>;
|
|
||||||
|
|
||||||
// A handler to use when one is needed temporarily. Don't use outside of Rocket!
|
// A handler to use when one is needed temporarily. Don't use outside of Rocket!
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn dummy<'r>(r: &'r Request<'_>, _: Data) -> HandlerFuture<'r> {
|
pub fn dummy<'r>(r: &'r Request<'_>, _: Data) -> HandlerFuture<'r> {
|
||||||
|
@ -318,3 +288,29 @@ impl<'r, 'o: 'r> Outcome<'o> {
|
||||||
outcome::Outcome::Forward(data)
|
outcome::Outcome::Forward(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>`.
|
||||||
|
///
|
||||||
|
/// 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 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -106,6 +106,7 @@ pub mod data;
|
||||||
pub mod handler;
|
pub mod handler;
|
||||||
pub mod fairing;
|
pub mod fairing;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod catcher;
|
||||||
|
|
||||||
// Reexport of HTTP everything.
|
// Reexport of HTTP everything.
|
||||||
pub mod http {
|
pub mod http {
|
||||||
|
@ -122,16 +123,15 @@ mod shutdown;
|
||||||
mod router;
|
mod router;
|
||||||
mod rocket;
|
mod rocket;
|
||||||
mod codegen;
|
mod codegen;
|
||||||
mod catcher;
|
|
||||||
mod ext;
|
mod ext;
|
||||||
|
|
||||||
#[doc(inline)] pub use crate::response::Response;
|
#[doc(inline)] pub use crate::response::Response;
|
||||||
#[doc(hidden)] pub use crate::codegen::{StaticRouteInfo, StaticCatchInfo};
|
#[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;
|
||||||
pub use crate::router::Route;
|
pub use crate::router::Route;
|
||||||
pub use crate::request::{Request, State};
|
pub use crate::request::{Request, State};
|
||||||
pub use crate::catcher::Catcher;
|
|
||||||
pub use crate::rocket::{Cargo, Rocket};
|
pub use crate::rocket::{Cargo, Rocket};
|
||||||
pub use crate::shutdown::Shutdown;
|
pub use crate::shutdown::Shutdown;
|
||||||
|
|
||||||
|
|
|
@ -42,8 +42,10 @@ impl<'c> LocalRequest<'c> {
|
||||||
method: Method,
|
method: Method,
|
||||||
uri: Cow<'c, str>
|
uri: Cow<'c, str>
|
||||||
) -> LocalRequest<'c> {
|
) -> LocalRequest<'c> {
|
||||||
// We set a dummy string for now and check the user's URI on dispatch.
|
// We try to validate the URI now so that the inner `Request` contains a
|
||||||
let request = Request::new(client.rocket(), method, Origin::dummy());
|
// valid URI. If it doesn't, we set a dummy one.
|
||||||
|
let origin = Origin::parse(&uri).unwrap_or_else(|_| Origin::dummy());
|
||||||
|
let request = Request::new(client.rocket(), method, origin.into_owned());
|
||||||
|
|
||||||
// Set up any cookies we know about.
|
// Set up any cookies we know about.
|
||||||
if let Some(ref jar) = client.cookies {
|
if let Some(ref jar) = client.cookies {
|
||||||
|
@ -70,12 +72,11 @@ impl<'c> LocalRequest<'c> {
|
||||||
|
|
||||||
// Performs the actual dispatch.
|
// Performs the actual dispatch.
|
||||||
async fn _dispatch(mut self) -> LocalResponse<'c> {
|
async fn _dispatch(mut self) -> LocalResponse<'c> {
|
||||||
// First, validate the URI, returning an error response (generated from
|
// First, revalidate the URI, returning an error response (generated
|
||||||
// an error catcher) immediately if it's invalid.
|
// from an error catcher) immediately if it's invalid. If it's valid,
|
||||||
|
// then `request` already contains the correct URI.
|
||||||
let rocket = self.client.rocket();
|
let rocket = self.client.rocket();
|
||||||
if let Ok(uri) = Origin::parse(&self.uri) {
|
if let Err(_) = Origin::parse(&self.uri) {
|
||||||
self.request.set_uri(uri.into_owned());
|
|
||||||
} else {
|
|
||||||
error!("Malformed request URI: {}", self.uri);
|
error!("Malformed request URI: {}", self.uri);
|
||||||
return LocalResponse::new(self.request, move |req| {
|
return LocalResponse::new(self.request, move |req| {
|
||||||
rocket.handle_error(Status::BadRequest, req)
|
rocket.handle_error(Status::BadRequest, req)
|
||||||
|
|
|
@ -683,6 +683,9 @@ macro_rules! try_outcome {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use try_outcome;
|
||||||
|
|
||||||
impl<S, E, F> fmt::Debug for Outcome<S, E, F> {
|
impl<S, E, F> fmt::Debug for Outcome<S, E, F> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "Outcome::{}", self.formatting().1)
|
write!(f, "Outcome::{}", self.formatting().1)
|
||||||
|
|
|
@ -258,6 +258,18 @@ impl<'r> Responder<'r, 'static> for () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Responds with the inner `Responder` in `Cow`.
|
||||||
|
impl<'r, 'o: 'r, R: ?Sized + ToOwned> Responder<'r, 'o> for std::borrow::Cow<'o, R>
|
||||||
|
where &'o R: Responder<'r, 'o> + 'o, <R as ToOwned>::Owned: Responder<'r, 'o> + 'r
|
||||||
|
{
|
||||||
|
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
|
||||||
|
match self {
|
||||||
|
std::borrow::Cow::Borrowed(b) => b.respond_to(req),
|
||||||
|
std::borrow::Cow::Owned(o) => o.respond_to(req),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// If `self` is `Some`, responds with the wrapped `Responder`. Otherwise prints
|
/// If `self` is `Some`, responds with the wrapped `Responder`. Otherwise prints
|
||||||
/// a warning message and returns an `Err` of `Status::NotFound`.
|
/// a warning message and returns an `Err` of `Status::NotFound`.
|
||||||
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Option<R> {
|
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Option<R> {
|
||||||
|
@ -311,7 +323,6 @@ impl<'r> Responder<'r, 'static> for Status {
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error_!("Invalid status used as responder: {}.", self);
|
error_!("Invalid status used as responder: {}.", self);
|
||||||
warn_!("Fowarding to 500 (Internal Server Error) catcher.");
|
|
||||||
Err(Status::InternalServerError)
|
Err(Status::InternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,9 @@ use crate::{logger, handler};
|
||||||
use crate::config::{Config, FullConfig, ConfigError, LoggedValue};
|
use crate::config::{Config, FullConfig, ConfigError, LoggedValue};
|
||||||
use crate::request::{Request, FormItems};
|
use crate::request::{Request, FormItems};
|
||||||
use crate::data::Data;
|
use crate::data::Data;
|
||||||
|
use crate::catcher::Catcher;
|
||||||
use crate::response::{Body, Response};
|
use crate::response::{Body, Response};
|
||||||
use crate::router::{Router, Route};
|
use crate::router::{Router, Route};
|
||||||
use crate::catcher::{self, Catcher};
|
|
||||||
use crate::outcome::Outcome;
|
use crate::outcome::Outcome;
|
||||||
use crate::error::{LaunchError, LaunchErrorKind};
|
use crate::error::{LaunchError, LaunchErrorKind};
|
||||||
use crate::fairing::{Fairing, Fairings};
|
use crate::fairing::{Fairing, Fairings};
|
||||||
|
@ -39,7 +39,7 @@ pub struct Rocket {
|
||||||
pub(crate) managed_state: Container,
|
pub(crate) managed_state: Container,
|
||||||
manifest: Vec<PreLaunchOp>,
|
manifest: Vec<PreLaunchOp>,
|
||||||
router: Router,
|
router: Router,
|
||||||
default_catchers: HashMap<u16, Catcher>,
|
default_catcher: Option<Catcher>,
|
||||||
catchers: HashMap<u16, Catcher>,
|
catchers: HashMap<u16, Catcher>,
|
||||||
fairings: Fairings,
|
fairings: Fairings,
|
||||||
shutdown_receiver: Option<mpsc::Receiver<()>>,
|
shutdown_receiver: Option<mpsc::Receiver<()>>,
|
||||||
|
@ -91,14 +91,17 @@ impl Rocket {
|
||||||
fn _register(&mut self, catchers: Vec<Catcher>) {
|
fn _register(&mut self, catchers: Vec<Catcher>) {
|
||||||
info!("{}{}", Paint::emoji("👾 "), Paint::magenta("Catchers:"));
|
info!("{}{}", Paint::emoji("👾 "), Paint::magenta("Catchers:"));
|
||||||
|
|
||||||
for c in catchers {
|
for catcher in catchers {
|
||||||
if self.catchers.get(&c.code).map_or(false, |e| !e.is_default) {
|
info_!("{}", catcher);
|
||||||
info_!("{} {}", c, Paint::yellow("(warning: duplicate catcher!)"));
|
|
||||||
} else {
|
|
||||||
info_!("{}", c);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.catchers.insert(c.code, c);
|
let existing = match catcher.code {
|
||||||
|
Some(code) => self.catchers.insert(code, catcher),
|
||||||
|
None => self.default_catcher.replace(catcher)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(existing) = existing {
|
||||||
|
warn_!("Replacing existing '{}' catcher.", existing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +123,7 @@ impl Rocket {
|
||||||
manifest: vec![],
|
manifest: vec![],
|
||||||
config: Config::development(),
|
config: Config::development(),
|
||||||
router: Router::new(),
|
router: Router::new(),
|
||||||
default_catchers: HashMap::new(),
|
default_catcher: None,
|
||||||
catchers: HashMap::new(),
|
catchers: HashMap::new(),
|
||||||
managed_state: Container::new(),
|
managed_state: Container::new(),
|
||||||
fairings: Fairings::new(),
|
fairings: Fairings::new(),
|
||||||
|
@ -459,19 +462,25 @@ impl Rocket {
|
||||||
req.cookies().reset_delta();
|
req.cookies().reset_delta();
|
||||||
|
|
||||||
// Try to get the active catcher but fallback to user's 500 catcher.
|
// Try to get the active catcher but fallback to user's 500 catcher.
|
||||||
let catcher = self.catchers.get(&status.code).unwrap_or_else(|| {
|
let code = Paint::red(status.code);
|
||||||
error_!("No catcher found for {}. Using 500 catcher.", status);
|
let response = if let Some(catcher) = self.catchers.get(&status.code) {
|
||||||
self.catchers.get(&500).expect("500 catcher.")
|
catcher.handler.handle(status, req).await
|
||||||
});
|
} else if let Some(ref default) = self.default_catcher {
|
||||||
|
warn_!("No {} catcher found. Using default catcher.", code);
|
||||||
|
default.handler.handle(status, req).await
|
||||||
|
} else {
|
||||||
|
warn_!("No {} or default catcher found. Using Rocket default catcher.", code);
|
||||||
|
crate::catcher::default(status, req)
|
||||||
|
};
|
||||||
|
|
||||||
// Dispatch to the user's catcher. If it fails, use the default 500.
|
// Dispatch to the catcher. If it fails, use the Rocket default 500.
|
||||||
match catcher.handle(req).await {
|
match response {
|
||||||
Ok(r) => return r,
|
Ok(r) => r,
|
||||||
Err(err_status) => {
|
Err(err_status) => {
|
||||||
error_!("Catcher failed with status: {}!", err_status);
|
error_!("Catcher unexpectedly failed with {}.", err_status);
|
||||||
warn_!("Using default 500 error catcher.");
|
warn_!("Using Rocket's default 500 error catcher.");
|
||||||
let default = self.default_catchers.get(&500).expect("Default 500");
|
let default = crate::catcher::default(Status::InternalServerError, req);
|
||||||
default.handle(req).await.expect("Default 500 response.")
|
default.expect("Rocket has default 500 response")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -660,8 +669,8 @@ impl Rocket {
|
||||||
shutdown_handle: Shutdown(shutdown_sender),
|
shutdown_handle: Shutdown(shutdown_sender),
|
||||||
manifest: vec![],
|
manifest: vec![],
|
||||||
router: Router::new(),
|
router: Router::new(),
|
||||||
default_catchers: catcher::defaults::get(),
|
default_catcher: None,
|
||||||
catchers: catcher::defaults::get(),
|
catchers: HashMap::new(),
|
||||||
fairings: Fairings::new(),
|
fairings: Fairings::new(),
|
||||||
shutdown_receiver: Some(shutdown_receiver),
|
shutdown_receiver: Some(shutdown_receiver),
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ impl Route {
|
||||||
///
|
///
|
||||||
/// Panics if `path` is not a valid origin URI or Rocket route URI.
|
/// Panics if `path` is not a valid origin URI or Rocket route URI.
|
||||||
pub fn new<S, H>(method: Method, path: S, handler: H) -> Route
|
pub fn new<S, H>(method: Method, path: S, handler: H) -> Route
|
||||||
where S: AsRef<str>, H: Handler + 'static
|
where S: AsRef<str>, H: Handler
|
||||||
{
|
{
|
||||||
let mut route = Route::ranked(0, method, path, handler);
|
let mut route = Route::ranked(0, method, path, handler);
|
||||||
route.rank = default_rank(&route);
|
route.rank = default_rank(&route);
|
||||||
|
|
|
@ -2,30 +2,44 @@
|
||||||
|
|
||||||
#[cfg(test)] mod tests;
|
#[cfg(test)] mod tests;
|
||||||
|
|
||||||
use rocket::response::content;
|
use rocket::Request;
|
||||||
|
use rocket::response::{content, status};
|
||||||
|
use rocket::http::Status;
|
||||||
|
|
||||||
#[get("/hello/<name>/<age>")]
|
#[get("/hello/<name>/<age>")]
|
||||||
fn hello(name: String, age: i8) -> String {
|
fn hello(name: String, age: i8) -> String {
|
||||||
format!("Hello, {} year old named {}!", age, name)
|
format!("Hello, {} year old named {}!", age, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/<code>")]
|
||||||
|
fn forced_error(code: u16) -> Status {
|
||||||
|
Status::raw(code)
|
||||||
|
}
|
||||||
|
|
||||||
#[catch(404)]
|
#[catch(404)]
|
||||||
fn not_found(req: &rocket::Request<'_>) -> content::Html<String> {
|
fn not_found(req: &Request<'_>) -> content::Html<String> {
|
||||||
content::Html(format!("<p>Sorry, but '{}' is not a valid path!</p>
|
content::Html(format!("<p>Sorry, but '{}' is not a valid path!</p>
|
||||||
<p>Try visiting /hello/<name>/<age> instead.</p>",
|
<p>Try visiting /hello/<name>/<age> instead.</p>",
|
||||||
req.uri()))
|
req.uri()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[catch(default)]
|
||||||
|
fn default_catcher(status: Status, req: &Request<'_>) -> status::Custom<String> {
|
||||||
|
let msg = format!("{} - {} ({})", status.code, status.reason, req.uri());
|
||||||
|
status::Custom(status, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rocket() -> rocket::Rocket {
|
||||||
|
rocket::ignite()
|
||||||
|
// .mount("/", routes![hello, hello]) // uncoment this to get an error
|
||||||
|
.mount("/", routes![hello, forced_error])
|
||||||
|
.register(catchers![not_found, default_catcher])
|
||||||
|
}
|
||||||
|
|
||||||
#[rocket::main]
|
#[rocket::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let result = rocket::ignite()
|
if let Err(e) = rocket().launch().await {
|
||||||
// .mount("/", routes![hello, hello]) // uncoment this to get an error
|
|
||||||
.mount("/", routes![hello])
|
|
||||||
.register(catchers![not_found])
|
|
||||||
.launch().await;
|
|
||||||
|
|
||||||
if let Err(e) = result {
|
|
||||||
println!("Whoops! Rocket didn't launch!");
|
println!("Whoops! Rocket didn't launch!");
|
||||||
println!("This went wrong: {:?}", e);
|
println!("Error: {:?}", e);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,56 @@
|
||||||
use rocket::local::blocking::Client;
|
use rocket::local::blocking::Client;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
|
|
||||||
fn test(uri: &str, status: Status, body: String) {
|
#[test]
|
||||||
let rocket = rocket::ignite()
|
fn test_hello() {
|
||||||
.mount("/", routes![super::hello])
|
let client = Client::new(super::rocket()).unwrap();
|
||||||
.register(catchers![super::not_found]);
|
|
||||||
|
|
||||||
let client = Client::new(rocket).unwrap();
|
let (name, age) = ("Arthur", 42);
|
||||||
|
let uri = format!("/hello/{}/{}", name, age);
|
||||||
let response = client.get(uri).dispatch();
|
let response = client.get(uri).dispatch();
|
||||||
assert_eq!(response.status(), status);
|
|
||||||
assert_eq!(response.into_string(), Some(body));
|
assert_eq!(response.status(), Status::Ok);
|
||||||
|
assert_eq!(response.into_string().unwrap(), super::hello(name.into(), age));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hello() {
|
fn forced_error_and_default_catcher() {
|
||||||
let (name, age) = ("Arthur", 42);
|
let client = Client::new(super::rocket()).unwrap();
|
||||||
let uri = format!("/hello/{}/{}", name, age);
|
|
||||||
test(&uri, Status::Ok, format!("Hello, {} year old named {}!", age, name));
|
let request = client.get("/404");
|
||||||
|
let expected = super::not_found(request.inner());
|
||||||
|
let response = request.dispatch();
|
||||||
|
assert_eq!(response.status(), Status::NotFound);
|
||||||
|
assert_eq!(response.into_string().unwrap(), expected.0);
|
||||||
|
|
||||||
|
let request = client.get("/405");
|
||||||
|
let expected = super::default_catcher(Status::MethodNotAllowed, request.inner());
|
||||||
|
let response = request.dispatch();
|
||||||
|
assert_eq!(response.status(), Status::MethodNotAllowed);
|
||||||
|
assert_eq!(response.into_string().unwrap(), expected.1);
|
||||||
|
|
||||||
|
let request = client.get("/533");
|
||||||
|
let expected = super::default_catcher(Status::raw(533), request.inner());
|
||||||
|
let response = request.dispatch();
|
||||||
|
assert_eq!(response.status(), Status::raw(533));
|
||||||
|
assert_eq!(response.into_string().unwrap(), expected.1);
|
||||||
|
|
||||||
|
let request = client.get("/700");
|
||||||
|
let expected = super::default_catcher(Status::InternalServerError, request.inner());
|
||||||
|
let response = request.dispatch();
|
||||||
|
assert_eq!(response.status(), Status::InternalServerError);
|
||||||
|
assert_eq!(response.into_string().unwrap(), expected.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hello_invalid_age() {
|
fn test_hello_invalid_age() {
|
||||||
|
let client = Client::new(super::rocket()).unwrap();
|
||||||
|
|
||||||
for &(name, age) in &[("Ford", -129), ("Trillian", 128)] {
|
for &(name, age) in &[("Ford", -129), ("Trillian", 128)] {
|
||||||
let uri = format!("/hello/{}/{}", name, age);
|
let request = client.get(format!("/hello/{}/{}", name, age));
|
||||||
let body = format!("<p>Sorry, but '{}' is not a valid path!</p>
|
let expected = super::not_found(request.inner());
|
||||||
<p>Try visiting /hello/<name>/<age> instead.</p>",
|
let response = request.dispatch();
|
||||||
uri);
|
assert_eq!(response.status(), Status::NotFound);
|
||||||
test(&uri, Status::NotFound, body);
|
assert_eq!(response.into_string().unwrap(), expected.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,12 @@ mod tests;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use rocket::{Request, Route, Data, Catcher, try_outcome};
|
use rocket::{Request, Route, Data};
|
||||||
use rocket::http::{Status, RawStr};
|
use rocket::http::{Status, RawStr, Method::*};
|
||||||
use rocket::response::{Responder, status::Custom};
|
use rocket::response::{Responder, status::Custom};
|
||||||
use rocket::handler::{Handler, Outcome, HandlerFuture, CatcherFuture};
|
use rocket::handler::{Handler, Outcome, HandlerFuture};
|
||||||
use rocket::outcome::IntoOutcome;
|
use rocket::catcher::{Catcher, ErrorHandlerFuture};
|
||||||
use rocket::http::Method::*;
|
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) -> HandlerFuture<'r> {
|
||||||
|
@ -64,7 +64,7 @@ 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()
|
Outcome::from(req, std::fs::File::open(env::temp_dir().join("upload.txt")).ok()).pin()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn not_found_handler<'r>(req: &'r Request) -> CatcherFuture<'r> {
|
fn not_found_handler<'r>(_: Status, req: &'r Request) -> ErrorHandlerFuture<'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) })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1088,27 +1088,26 @@ function, so we must `await` it.
|
||||||
|
|
||||||
## Error Catchers
|
## Error Catchers
|
||||||
|
|
||||||
Routing may fail for a variety of reasons. These include:
|
Application processing is fallible. Errors arise from the following sources:
|
||||||
|
|
||||||
* A guard fails.
|
* A failing guard.
|
||||||
* A handler returns a [`Responder`](../responses/#responder) that fails.
|
* A failing responder.
|
||||||
* No routes matched.
|
* A routing failure.
|
||||||
|
|
||||||
If any of these conditions occur, Rocket returns an error to the client. To do
|
If any of these occur, Rocket returns an error to the client. To generate the
|
||||||
so, Rocket invokes the _catcher_ corresponding to the error's status code.
|
error, Rocket invokes the _catcher_ corresponding to the error's status code.
|
||||||
Catchers are similar to routes except in that:
|
Catchers are similar to routes except in that:
|
||||||
|
|
||||||
1. Catchers are only invoked on error conditions.
|
1. Catchers are only invoked on error conditions.
|
||||||
2. Catchers are declared with the `catch` attribute.
|
2. Catchers are declared with the `catch` attribute.
|
||||||
3. Catchers are _registered_ with [`register()`] instead of [`mount()`].
|
3. Catchers are _registered_ with [`register()`] instead of [`mount()`].
|
||||||
4. Any modifications to cookies are cleared before a catcher is invoked.
|
4. Any modifications to cookies are cleared before a catcher is invoked.
|
||||||
5. Error catchers cannot invoke guards of any sort.
|
5. Error catchers cannot invoke guards.
|
||||||
|
6. Error catchers should not fail to produce a response.
|
||||||
|
|
||||||
Rocket provides default catchers for all of the standard HTTP error codes. To
|
To declare a catcher for a given status code, use the [`catch`] attribute, which
|
||||||
override a default catcher, or declare a catcher for a custom status code, use
|
takes a single integer corresponding to the HTTP status code to catch. For
|
||||||
the [`catch`] attribute, which takes a single integer corresponding to the HTTP
|
instance, to declare a catcher for `404 Not Found` errors, you'd write:
|
||||||
status code to catch. For instance, to declare a catcher for `404 Not Found`
|
|
||||||
errors, you'd write:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# #[macro_use] extern crate rocket;
|
# #[macro_use] extern crate rocket;
|
||||||
|
@ -1120,8 +1119,10 @@ use rocket::Request;
|
||||||
fn not_found(req: &Request) { /* .. */ }
|
fn not_found(req: &Request) { /* .. */ }
|
||||||
```
|
```
|
||||||
|
|
||||||
As with routes, the return type (here `T`) must implement `Responder`. A
|
Catchers may take zero, one, or two arguments. If the catcher takes one
|
||||||
concrete implementation may look like:
|
argument, it must be of type [`&Request`]. It it takes two, they must be of type
|
||||||
|
[`Status`] and [`&Request`], in that order. As with routes, the return type must
|
||||||
|
implement `Responder`. A concrete implementation may look like:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# #[macro_use] extern crate rocket;
|
# #[macro_use] extern crate rocket;
|
||||||
|
@ -1152,12 +1153,38 @@ fn main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Unlike route request handlers, catchers take exactly zero or one parameter. If
|
### Default Catchers
|
||||||
the catcher takes a parameter, it must be of type [`&Request`]. The [error
|
|
||||||
catcher example](@example/errors) on GitHub illustrates their use in full.
|
If no catcher for a given status code has been registered, Rocket calls the
|
||||||
|
_default_ catcher. Rocket provides a default catcher for all applications
|
||||||
|
automatically, so providing one is usually unnecessary. 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, a default catcher, or catchers in
|
||||||
|
general, only need to be registered if an error needs to be handled in a custom
|
||||||
|
fashion.
|
||||||
|
|
||||||
|
Declaring a default catcher is done with `#[catch(default)]`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# #[macro_use] extern crate rocket;
|
||||||
|
# fn main() {}
|
||||||
|
|
||||||
|
use rocket::Request;
|
||||||
|
use rocket::http::Status;
|
||||||
|
|
||||||
|
#[catch(default)]
|
||||||
|
fn default_catcher(status: Status, request: &Request) { /* .. */ }
|
||||||
|
```
|
||||||
|
|
||||||
|
It must similarly be registered with [`register()`].
|
||||||
|
|
||||||
|
The [error catcher example](@example/errors) illustrates their use in full,
|
||||||
|
while the [`Catcher`] API documentation provides further details.
|
||||||
|
|
||||||
[`catch`]: @api/rocket/attr.catch.html
|
[`catch`]: @api/rocket/attr.catch.html
|
||||||
[`register()`]: @api/rocket/struct.Rocket.html#method.register
|
[`register()`]: @api/rocket/struct.Rocket.html#method.register
|
||||||
[`mount()`]: @api/rocket/struct.Rocket.html#method.mount
|
[`mount()`]: @api/rocket/struct.Rocket.html#method.mount
|
||||||
[`catchers!`]: @api/rocket/macro.catchers.html
|
[`catchers!`]: @api/rocket/macro.catchers.html
|
||||||
[`&Request`]: @api/rocket/struct.Request.html
|
[`&Request`]: @api/rocket/struct.Request.html
|
||||||
|
[`Status`]: @api/rocket/http/struct.Status.html
|
||||||
|
[`Catcher`]: @api/rocket/catcher/struct.Catcher.html
|
||||||
|
|
Loading…
Reference in New Issue