Reimplement 'catch' attribute as a proc-macro.

This commit is contained in:
Sergio Benitez 2018-09-16 00:33:16 -07:00
parent 1f2f38ea5f
commit 112e700836
28 changed files with 373 additions and 270 deletions

View File

@ -21,7 +21,7 @@ proc-macro = true
[dependencies.derive_utils] [dependencies.derive_utils]
git = "https://github.com/SergioBenitez/derive-utils" git = "https://github.com/SergioBenitez/derive-utils"
rev = "5fad71394" rev = "87ad56ba"
[dependencies] [dependencies]
quote = "0.6" quote = "0.6"

View File

@ -1,101 +0,0 @@
use ::{CATCH_STRUCT_PREFIX, CATCH_FN_PREFIX, CATCHER_ATTR};
use parser::CatchParams;
use utils::*;
use syntax::source_map::{Span};
use syntax::ast::{MetaItem, Ident, TyKind};
use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::tokenstream::TokenTree;
use syntax::parse::token;
const ERR_PARAM: &str = "__err";
const REQ_PARAM: &str = "__req";
trait CatchGenerateExt {
fn generate_fn_arguments(&self, &ExtCtxt, Ident, Ident) -> Vec<TokenTree>;
}
impl CatchGenerateExt for CatchParams {
fn generate_fn_arguments(&self, ecx: &ExtCtxt, err: Ident, req: Ident)
-> Vec<TokenTree> {
let arg_help = "error catchers can take either a `rocket::Error`, \
`rocket::Request` type, or both.";
// Retrieve the params from the user's handler and check the number.
let input_args = &self.annotated_fn.decl().inputs;
if input_args.len() > 2 {
let sp = self.annotated_fn.span();
ecx.struct_span_err(sp, "error catchers can have at most 2 arguments")
.help(arg_help).emit()
}
// (Imperfectly) inspect the types to figure which params to pass in.
let args = input_args.iter().map(|arg| &arg.ty).filter_map(|ty| {
match ty.node {
TyKind::Rptr(..) => Some(req),
TyKind::Path(..) => Some(err),
_ => {
ecx.struct_span_err(ty.span, "unknown error catcher argument")
.help(arg_help)
.emit();
None
}
}
}).collect::<Vec<_>>();
sep_by_tok(ecx, &args, token::Comma)
}
}
pub fn catch_decorator(
ecx: &mut ExtCtxt,
sp: Span,
meta_item: &MetaItem,
annotated: Annotatable
) -> Vec<Annotatable> {
let mut output = Vec::new();
// Parse the parameters from the `catch` annotation.
let catch = CatchParams::from(ecx, sp, meta_item, &annotated);
// Get all of the information we learned from the attribute + function.
let user_fn_name = catch.annotated_fn.ident();
let catch_fn_name = user_fn_name.prepend(CATCH_FN_PREFIX);
let code = catch.code.node;
let err_ident = Ident::from_str(ERR_PARAM);
let req_ident = Ident::from_str(REQ_PARAM);
let fn_arguments = catch.generate_fn_arguments(ecx, err_ident, req_ident);
// Push the Rocket generated catch function.
emit_item(&mut output, quote_item!(ecx,
fn $catch_fn_name<'_b>($err_ident: ::rocket::Error,
$req_ident: &'_b ::rocket::Request)
-> ::rocket::response::Result<'_b> {
let user_response = $user_fn_name($fn_arguments);
let response = ::rocket::response::Responder::respond_to(user_response,
$req_ident)?;
let status = ::rocket::http::Status::raw($code);
::rocket::response::Response::build().status(status).merge(response).ok()
}
).expect("catch function"));
// Push the static catch info. This is what the `catchers!` macro refers to.
let struct_name = user_fn_name.prepend(CATCH_STRUCT_PREFIX);
emit_item(&mut output, quote_item!(ecx,
/// Rocket code generated static catch information structure.
#[allow(non_upper_case_globals)]
pub static $struct_name: ::rocket::StaticCatchInfo =
::rocket::StaticCatchInfo {
code: $code,
handler: $catch_fn_name
};
).expect("catch info struct"));
// Attach a `rocket_catcher` attribute to the user's function and emit it.
let attr_name = Ident::from_str(CATCHER_ATTR);
let catcher_attr = quote_attr!(ecx, #[$attr_name($struct_name)]);
attach_and_emit(&mut output, catcher_attr, annotated);
output
}

View File

@ -1,5 +1,3 @@
mod route; mod route;
mod catch;
pub use self::route::*; pub use self::route::*;
pub use self::catch::*;

View File

@ -412,14 +412,11 @@ const PARAM_PREFIX: &str = "rocket_param_";
const ROUTE_STRUCT_PREFIX: &str = "static_rocket_route_info_for_"; const ROUTE_STRUCT_PREFIX: &str = "static_rocket_route_info_for_";
const CATCH_STRUCT_PREFIX: &str = "static_rocket_catch_info_for_"; const CATCH_STRUCT_PREFIX: &str = "static_rocket_catch_info_for_";
const ROUTE_FN_PREFIX: &str = "rocket_route_fn_"; const ROUTE_FN_PREFIX: &str = "rocket_route_fn_";
const CATCH_FN_PREFIX: &str = "rocket_catch_fn_";
const URI_INFO_MACRO_PREFIX: &str = "rocket_uri_for_"; const URI_INFO_MACRO_PREFIX: &str = "rocket_uri_for_";
const ROUTE_ATTR: &str = "rocket_route"; const ROUTE_ATTR: &str = "rocket_route";
const ROUTE_INFO_ATTR: &str = "rocket_route_info"; const ROUTE_INFO_ATTR: &str = "rocket_route_info";
const CATCHER_ATTR: &str = "rocket_catcher";
macro_rules! register_decorators { macro_rules! register_decorators {
($registry:expr, $($name:expr => $func:ident),+) => ( ($registry:expr, $($name:expr => $func:ident),+) => (
$($registry.register_syntax_extension(Symbol::intern($name), $($registry.register_syntax_extension(Symbol::intern($name),
@ -445,7 +442,6 @@ pub fn plugin_registrar(reg: &mut Registry) {
); );
register_decorators!(reg, register_decorators!(reg,
"catch" => catch_decorator,
"route" => route_decorator, "route" => route_decorator,
"get" => get_decorator, "get" => get_decorator,
"put" => put_decorator, "put" => put_decorator,

View File

@ -1,83 +0,0 @@
use syntax::ast::*;
use syntax::ext::base::{ExtCtxt, Annotatable};
use syntax::source_map::{Span, Spanned, dummy_spanned};
use rocket_http::Status;
use utils::{span, MetaItemExt};
use super::Function;
/// This structure represents the parsed `catch` attribute.
pub struct CatchParams {
pub annotated_fn: Function,
pub code: Spanned<u16>,
}
impl CatchParams {
/// Parses the route attribute from the given decorator context. If the
/// parse is not successful, this function exits early with the appropriate
/// error message to the user.
pub fn from(ecx: &mut ExtCtxt,
sp: Span,
meta_item: &MetaItem,
annotated: &Annotatable)
-> CatchParams {
let function = Function::from(annotated).unwrap_or_else(|item_sp| {
ecx.span_err(sp, "this attribute can only be used on functions...");
ecx.span_fatal(item_sp, "...but was applied to the item above.");
});
let meta_items = meta_item.meta_item_list().unwrap_or_else(|| {
ecx.struct_span_fatal(sp, "incorrect use of attribute")
.help("attributes in Rocket must have the form: #[name(...)]")
.emit();
ecx.span_fatal(sp, "malformed attribute");
});
if meta_items.is_empty() {
ecx.span_fatal(sp, "attribute requires the `code` parameter");
} else if meta_items.len() > 1 {
ecx.span_fatal(sp, "attribute can only have one `code` parameter");
}
CatchParams {
annotated_fn: function,
code: parse_code(ecx, &meta_items[0])
}
}
}
fn parse_code(ecx: &ExtCtxt, meta_item: &NestedMetaItem) -> Spanned<u16> {
let code_from_u128 = |n: Spanned<u128>| {
if n.node < 400 || n.node > 599 {
ecx.span_err(n.span, "code must be >= 400 and <= 599.");
span(0, n.span)
} else if Status::from_code(n.node as u16).is_none() {
ecx.span_warn(n.span, "status code is unknown.");
span(n.node as u16, n.span)
} else {
span(n.node as u16, n.span)
}
};
let sp = meta_item.span();
if let Some((name, lit)) = meta_item.name_value() {
if name != "code" {
ecx.span_err(sp, "the first key, if any, must be 'code'");
} else if let LitKind::Int(n, _) = lit.node {
return code_from_u128(span(n, lit.span))
} else {
ecx.span_err(lit.span, "`code` value must be an integer")
}
} else if let Some(n) = meta_item.int_lit() {
return code_from_u128(span(n, sp))
} else {
ecx.struct_span_err(sp, r#"expected `code = int` or an integer literal"#)
.help(r#"you can specify the code directly as an integer,
e.g: #[catch(404)], or as a key-value pair,
e.g: $[catch(code = 404)]"#)
.emit();
}
dummy_spanned(0)
}

View File

@ -1,6 +1,5 @@
mod keyvalue; mod keyvalue;
mod route; mod route;
mod catch;
mod param; mod param;
mod function; mod function;
mod uri; mod uri;
@ -8,7 +7,6 @@ mod uri_macro;
pub use self::keyvalue::KVSpanned; pub use self::keyvalue::KVSpanned;
pub use self::route::RouteParams; pub use self::route::RouteParams;
pub use self::catch::CatchParams;
pub use self::param::Param; pub use self::param::Param;
pub use self::function::Function; pub use self::function::Function;
pub use self::uri_macro::{Args, InternalUriParams, UriParams, Validation}; pub use self::uri_macro::{Args, InternalUriParams, UriParams, Validation};

View File

@ -1,24 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;
use rocket::{Error, Request};
#[catch(404)]
fn err0() -> &'static str { "hi" }
#[catch(404)]
fn err1a(_err: Error) -> &'static str { "hi" }
#[catch(404)]
fn err1b(_req: &Request) -> &'static str { "hi" }
#[catch(404)]
fn err2a(_err: Error, _req: &Request) -> &'static str { "hi" }
#[catch(404)]
fn err2b<'a>(_err: Error, _req: &'a Request) -> &'a str { "hi" }
#[test]
fn main() { }

View File

@ -1,12 +0,0 @@
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;
use rocket::{Error, Request};
#[catch(404)]
fn err_a(_a: Error, _b: Request, _c: Error) -> &'static str { "hi" }
#[catch(404)]
fn err_b(_a: (isize, usize)) -> &'static str { "hi" }

View File

@ -22,7 +22,7 @@ rocket_http = { version = "0.4.0-dev", path = "../http/" }
[dependencies.derive_utils] [dependencies.derive_utils]
git = "https://github.com/SergioBenitez/derive-utils" git = "https://github.com/SergioBenitez/derive-utils"
rev = "5fad71394" rev = "87ad56ba"
[dev-dependencies] [dev-dependencies]
rocket = { version = "0.4.0-dev", path = "../lib" } rocket = { version = "0.4.0-dev", path = "../lib" }

View File

@ -0,0 +1,113 @@
use proc_macro::TokenStream;
use derive_utils::{syn, Spanned, Result, FromMeta};
use proc_macro2::TokenStream as TokenStream2;
use proc_macro::Span;
use http_codegen::Status;
use syn_ext::{syn_to_diag, IdentExt, ReturnTypeExt};
use self::syn::{Attribute, parse::Parser};
crate const CATCH_FN_PREFIX: &str = "rocket_catch_fn_";
crate const CATCH_STRUCT_PREFIX: &str = "static_rocket_catch_info_for_";
/// The raw, parsed `#[catch(code)]` attribute.
#[derive(Debug, FromMeta)]
struct CatchAttribute {
#[meta(naked)]
status: Status
}
/// This structure represents the parsed `catch` attribute an associated items.
struct CatchParams {
/// The status associated with the code in the `#[catch(code)]` attribute.
status: Status,
/// The function that was decorated with the `catch` attribute.
function: syn::ItemFn,
}
fn parse_params(args: TokenStream2, input: TokenStream) -> Result<CatchParams> {
let function: syn::ItemFn = syn::parse(input).map_err(syn_to_diag)
.map_err(|diag| diag.help("`#[catch]` can only be used on functions"))?;
let full_attr = quote!(#[catch(#args)]);
let attrs = Attribute::parse_outer.parse2(full_attr).map_err(syn_to_diag)?;
let attribute = match CatchAttribute::from_attrs("catch", &attrs) {
Some(result) => result.map_err(|d| {
d.help("`#[catch]` expects a single status integer, e.g.: #[catch(404)]")
})?,
None => return Err(Span::call_site().error("internal error: bad attribute"))
};
Ok(CatchParams { status: attribute.status, function })
}
pub fn _catch(args: TokenStream, input: TokenStream) -> Result<TokenStream> {
// Parse and validate all of the user's input.
let catch = parse_params(TokenStream2::from(args), input)?;
// Gather everything we'll need to generate the catcher.
let user_catcher_fn = &catch.function;
let mut user_catcher_fn_name = catch.function.ident.clone();
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 (vis, status) = (&catch.function.vis, &catch.status);
let status_code = status.0.code;
// Determine the number of parameters that will be passed in.
let (fn_sig, inputs) = match catch.function.decl.inputs.len() {
0 => (quote!(fn() -> _), quote!()),
1 => (quote!(fn(&::rocket::Request) -> _), quote!(__req)),
_ => return Err(catch.function.decl.inputs.span()
.error("invalid number of arguments: must be zero or one")
.help("catchers may optionally take an argument of type `&Request`"))
};
// Set the span of the function name to point to inputs so that a later type
// coercion failure points to the user's catcher's handler input.
user_catcher_fn_name.set_span(catch.function.decl.inputs.span().into());
// This ensures that "Responder not implemented" points to the return type.
let return_type_span = catch.function.decl.output.ty()
.map(|ty| ty.span().into())
.unwrap_or(Span::call_site().into());
let catcher_response = quote_spanned!(return_type_span => {
// Check the type signature.
let __catcher: #fn_sig = #user_catcher_fn_name;
// Generate the response.
::rocket::response::Responder::respond_to(__catcher(#inputs), __req)?
});
// Generate the catcher, keeping the user's input around.
Ok(quote! {
#user_catcher_fn
#vis fn #generated_fn_name<'_b>(
_: ::rocket::Error,
__req: &'_b ::rocket::Request
) -> ::rocket::response::Result<'_b> {
let response = #catcher_response;
::rocket::response::Response::build()
.status(#status)
.merge(response)
.ok()
}
#[allow(non_upper_case_globals)]
#vis static #generated_struct_name: ::rocket::StaticCatchInfo =
::rocket::StaticCatchInfo {
code: #status_code,
handler: #generated_fn_name,
};
}.into())
}
pub fn catch_attribute(args: TokenStream, input: TokenStream) -> TokenStream {
match _catch(args, input) {
Ok(tokens) => tokens,
Err(diag) => {
diag.emit();
TokenStream::new()
}
}
}

View File

@ -0,0 +1 @@
pub mod catch;

View File

@ -3,17 +3,20 @@ use proc_macro2::TokenStream as TokenStream2;
use derive_utils::{FromMeta, MetaItem, Result, ext::Split2}; use derive_utils::{FromMeta, MetaItem, Result, ext::Split2};
use rocket_http as http; use rocket_http as http;
#[derive(Debug)]
pub struct ContentType(http::ContentType); pub struct ContentType(http::ContentType);
pub struct Status(http::Status); #[derive(Debug)]
pub struct Status(pub http::Status);
#[derive(Debug)]
struct MediaType(http::MediaType); struct MediaType(http::MediaType);
impl FromMeta for Status { impl FromMeta for Status {
fn from_meta(meta: MetaItem) -> Result<Self> { fn from_meta(meta: MetaItem) -> Result<Self> {
let num = usize::from_meta(meta)?; let num = usize::from_meta(meta)?;
if num < 100 || num >= 600 { if num < 100 || num >= 600 {
return Err(meta.value_span().error("status must be in range [100, 600)")); return Err(meta.value_span().error("status must be in range [100, 599]"));
} }
Ok(Status(http::Status::raw(num as u16))) Ok(Status(http::Status::raw(num as u16)))
@ -23,7 +26,7 @@ impl FromMeta for Status {
impl ToTokens for Status { impl ToTokens for Status {
fn to_tokens(&self, tokens: &mut TokenStream2) { fn to_tokens(&self, tokens: &mut TokenStream2) {
let (code, reason) = (self.0.code, self.0.reason); let (code, reason) = (self.0.code, self.0.reason);
tokens.extend(quote!(rocket::http::Status::new(#code, #reason))); tokens.extend(quote!(rocket::http::Status { code: #code, reason: #reason }));
} }
} }

View File

@ -8,7 +8,9 @@ extern crate proc_macro;
extern crate rocket_http; extern crate rocket_http;
mod derive; mod derive;
mod attribute;
mod http_codegen; mod http_codegen;
mod syn_ext;
crate use derive_utils::proc_macro2; crate use derive_utils::proc_macro2;
@ -28,3 +30,8 @@ pub fn derive_from_form(input: TokenStream) -> TokenStream {
pub fn derive_responder(input: TokenStream) -> TokenStream { pub fn derive_responder(input: TokenStream) -> TokenStream {
derive::responder::derive_responder(input) derive::responder::derive_responder(input)
} }
#[proc_macro_attribute]
pub fn catch(args: TokenStream, input: TokenStream) -> TokenStream {
attribute::catch::catch_attribute(args, input)
}

View File

@ -0,0 +1,32 @@
//! Extensions to `syn` types.
use derive_utils::syn;
use proc_macro::Diagnostic;
pub fn syn_to_diag(error: syn::parse::Error) -> Diagnostic {
error.span().unstable().error(error.to_string())
}
pub trait IdentExt {
fn prepend(&self, string: &str) -> syn::Ident;
}
impl IdentExt for syn::Ident {
fn prepend(&self, string: &str) -> syn::Ident {
syn::Ident::new(&format!("{}{}", string, self), self.span())
}
}
pub trait ReturnTypeExt {
fn ty(&self) -> Option<&syn::Type>;
}
impl ReturnTypeExt for syn::ReturnType {
fn ty(&self) -> Option<&syn::Type> {
match self {
syn::ReturnType::Default => None,
syn::ReturnType::Type(_, ty) => Some(ty),
}
}
}

View File

@ -0,0 +1,43 @@
extern crate rocket;
use rocket::{catch, Request};
#[catch(404)]
struct Catcher(String);
//~^ ERROR expected `fn`
//~^^ HELP on functions
#[catch(404)]
const CATCH: &str = "Catcher";
//~^ ERROR expected `fn`
//~^^ HELP on functions
#[catch("404")] //~ ERROR expected unsigned integer literal
//~^ HELP #[catch(404)]
fn e1(_request: &Request) { }
#[catch(code = "404")] //~ ERROR unexpected named parameter
//~^ HELP #[catch(404)]
fn e2(_request: &Request) { }
#[catch(code = 404)] //~ ERROR unexpected named parameter
//~^ HELP #[catch(404)]
fn e3(_request: &Request) { }
#[catch(99)] //~ ERROR in range [100, 599]
//~^ HELP #[catch(404)]
fn e4(_request: &Request) { }
#[catch(600)] //~ ERROR in range [100, 599]
//~^ HELP #[catch(404)]
fn e5(_request: &Request) { }
#[catch(400, message = "foo")] //~ ERROR unexpected attribute parameter: `message`
//~^ HELP #[catch(404)]
fn e5(_request: &Request) { }
#[catch(404)]
fn f3(_request: &Request, other: bool) {
//~^ ERROR invalid number of arguments
//~^^ HELP optionally take an argument
}

View File

@ -0,0 +1,74 @@
error: expected `fn`
--> $DIR/catch.rs:6:1
|
6 | struct Catcher(String);
| ^^^^^^
|
= help: `#[catch]` can only be used on functions
error: expected `fn`
--> $DIR/catch.rs:11:7
|
11 | const CATCH: &str = "Catcher";
| ^^^^^
|
= help: `#[catch]` can only be used on functions
error: invalid value: expected unsigned integer literal
--> $DIR/catch.rs:15:9
|
15 | #[catch("404")] //~ ERROR expected unsigned integer literal
| ^^^^^
|
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
error: unexpected named parameter: expected bare literal
--> $DIR/catch.rs:19:9
|
19 | #[catch(code = "404")] //~ ERROR unexpected named parameter
| ^^^^^^^^^^^^
|
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
error: unexpected named parameter: expected bare literal
--> $DIR/catch.rs:23:9
|
23 | #[catch(code = 404)] //~ ERROR unexpected named parameter
| ^^^^^^^^^^
|
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
error: status must be in range [100, 599]
--> $DIR/catch.rs:27:9
|
27 | #[catch(99)] //~ ERROR in range [100, 599]
| ^^
|
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
error: status must be in range [100, 599]
--> $DIR/catch.rs:31:9
|
31 | #[catch(600)] //~ ERROR in range [100, 599]
| ^^^
|
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
error: unexpected attribute parameter: `message`
--> $DIR/catch.rs:35:14
|
35 | #[catch(400, message = "foo")] //~ ERROR unexpected attribute parameter: `message`
| ^^^^^^^^^^^^^^^
|
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
error: invalid number of arguments: must be zero or one
--> $DIR/catch.rs:40:7
|
40 | fn f3(_request: &Request, other: bool) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: catchers may optionally take an argument of type `&Request`
error: aborting due to 9 previous errors

View File

@ -0,0 +1,29 @@
extern crate rocket;
use rocket::{catch, Request};
#[catch(404)]
fn f1(_request: &Request) -> usize {
//~^ ERROR usize: rocket::response::Responder
10
}
#[catch(404)]
fn f2(_request: &Request) -> bool {
//~^ ERROR bool: rocket::response::Responder
false
}
#[catch(404)]
fn f3(_request: bool) -> usize {
//~^ ERROR mismatched types
10
}
#[catch(404)]
fn f4() -> usize {
//~^ ERROR usize: rocket::response::Responder
10
}
fn main() { }

View File

@ -0,0 +1,37 @@
error[E0277]: the trait bound `usize: rocket::response::Responder<'_>` is not satisfied
--> $DIR/catch_type_errors.rs:6:30
|
6 | fn f1(_request: &Request) -> usize {
| ^^^^^ the trait `rocket::response::Responder<'_>` is not implemented for `usize`
|
= note: required by `rocket::response::Responder::respond_to`
error[E0277]: the trait bound `bool: rocket::response::Responder<'_>` is not satisfied
--> $DIR/catch_type_errors.rs:12:30
|
12 | fn f2(_request: &Request) -> bool {
| ^^^^ the trait `rocket::response::Responder<'_>` is not implemented for `bool`
|
= note: required by `rocket::response::Responder::respond_to`
error[E0308]: mismatched types
--> $DIR/catch_type_errors.rs:18:7
|
18 | fn f3(_request: bool) -> usize {
| ^^^^^^^^^^^^^^ expected reference, found bool
|
= note: expected type `for<'r, 's> fn(&'r rocket::Request<'s>) -> _`
found type `fn(bool) -> usize {f3}`
error[E0277]: the trait bound `usize: rocket::response::Responder<'_>` is not satisfied
--> $DIR/catch_type_errors.rs:24:12
|
24 | fn f4() -> usize {
| ^^^^^ the trait `rocket::response::Responder<'_>` is not implemented for `usize`
|
= note: required by `rocket::response::Responder::respond_to`
error: aborting due to 4 previous errors
Some errors occurred: E0277, E0308.
For more information about an error, try `rustc --explain E0277`.

View File

@ -134,7 +134,6 @@ macro_rules! ctrs {
_ => None _ => None
} }
} }
}; };
} }

View File

@ -40,7 +40,7 @@ use yansi::Color::*;
/// ///
/// extern crate rocket; /// extern crate rocket;
/// ///
/// use rocket::Request; /// use rocket::{catch, Request};
/// ///
/// #[catch(500)] /// #[catch(500)]
/// fn internal_error() -> &'static str { /// fn internal_error() -> &'static str {
@ -59,13 +59,14 @@ use yansi::Color::*;
/// } /// }
/// ``` /// ```
/// ///
/// A function decorated with `catch` can take in 0, 1, or 2 parameters: /// A function decorated with `catch` must take exactly zero or one arguments.
/// `Error`, `&Request`, or both, as desired. /// If the catcher takes an argument, it must be of type `&Request`.
pub struct Catcher { pub struct Catcher {
/// The HTTP status code to match against. /// The HTTP status code to match against.
pub code: u16, pub code: u16,
handler: ErrorHandler, /// The catcher's associated handler.
is_default: bool, pub handler: ErrorHandler,
crate is_default: bool,
} }
impl Catcher { impl Catcher {
@ -107,11 +108,6 @@ impl Catcher {
fn new_default(code: u16, handler: ErrorHandler) -> Catcher { fn new_default(code: u16, handler: ErrorHandler) -> Catcher {
Catcher { code, handler, is_default: true, } Catcher { code, handler, is_default: true, }
} }
#[inline(always)]
crate fn is_default(&self) -> bool {
self.is_default
}
} }
#[doc(hidden)] #[doc(hidden)]

View File

@ -548,7 +548,7 @@ impl Rocket {
/// ///
/// extern crate rocket; /// extern crate rocket;
/// ///
/// use rocket::Request; /// use rocket::{catch, Request};
/// ///
/// #[catch(500)] /// #[catch(500)]
/// fn internal_error() -> &'static str { /// fn internal_error() -> &'static str {
@ -571,9 +571,8 @@ impl Rocket {
pub fn catch(mut self, catchers: Vec<Catcher>) -> Self { pub fn catch(mut self, catchers: Vec<Catcher>) -> Self {
info!("{}{}:", Paint::masked("👾 "), Paint::purple("Catchers")); info!("{}{}:", Paint::masked("👾 "), Paint::purple("Catchers"));
for c in catchers { for c in catchers {
if self.catchers.get(&c.code).map_or(false, |e| !e.is_default()) { if self.catchers.get(&c.code).map_or(false, |e| !e.is_default) {
let msg = "(warning: duplicate catcher!)"; info_!("{} {}", c, Paint::yellow("(warning: duplicate catcher!)"));
info_!("{} {}", c, Paint::yellow(msg));
} else { } else {
info_!("{}", c); info_!("{}", c);
} }

View File

@ -3,7 +3,7 @@
extern crate rocket; extern crate rocket;
use rocket::response::Redirect; use rocket::{catch, Request, response::Redirect};
#[catch(404)] #[catch(404)]
fn not_found() -> Redirect { fn not_found() -> Redirect {

View File

@ -3,13 +3,11 @@
extern crate rocket; extern crate rocket;
extern crate serde_json; extern crate serde_json;
#[macro_use] #[macro_use] extern crate serde_derive;
extern crate serde_derive;
#[cfg(test)] mod tests; #[cfg(test)] mod tests;
use rocket::Request; use rocket::{catch, Request, response::content};
use rocket::response::content;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct Person { struct Person {

View File

@ -5,7 +5,7 @@ extern crate rocket;
#[cfg(test)] mod tests; #[cfg(test)] mod tests;
use rocket::response::content; use rocket::{catch, response::content};
#[get("/hello/<name>/<age>")] #[get("/hello/<name>/<age>")]
fn hello(name: String, age: i8) -> String { fn hello(name: String, age: i8) -> String {

View File

@ -7,7 +7,7 @@ extern crate rocket;
#[cfg(test)] mod tests; #[cfg(test)] mod tests;
use rocket::Request; use rocket::{catch, Request};
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket_contrib::{Template, handlebars}; use rocket_contrib::{Template, handlebars};

View File

@ -7,8 +7,8 @@ extern crate rocket;
#[cfg(test)] mod tests; #[cfg(test)] mod tests;
use rocket::{catch, Request, State};
use rocket_contrib::{Json, JsonValue}; use rocket_contrib::{Json, JsonValue};
use rocket::State;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Mutex; use std::sync::Mutex;

View File

@ -10,7 +10,7 @@ extern crate serde_json;
use std::collections::HashMap; use std::collections::HashMap;
use rocket::Request; use rocket::{catch, Request};
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket_contrib::Template; use rocket_contrib::Template;

View File

@ -742,13 +742,17 @@ Routing may fail for a variety of reasons. These include:
* No matching route was found. * No matching route was found.
If any of these conditions occur, Rocket returns an error to the client. To do If any of these conditions occur, Rocket returns an error to the client. To do
so, Rocket invokes the _error catcher_ corresponding to the error's status code. so, Rocket invokes the _catcher_ corresponding to the error's status code. A
A catcher is like a route, except it only handles errors. Catchers are declared catcher is like a route, except it only handles errors. Rocket provides default
via the `catch` attribute, which takes a single integer corresponding to the catchers for all of the standard HTTP error codes. To override a default
HTTP status code to catch. For instance, to declare a catcher for **404** catcher, or declare a catcher for a custom status code, use the `catch`
errors, you'd write: attribute, which takes a single integer corresponding to the HTTP status code to
catch. For instance, to declare a catcher for `404 Not Found` errors, you'd
write:
```rust ```rust
use rocket::catch;
#[catch(404)] #[catch(404)]
fn not_found(req: &Request) -> T { .. } fn not_found(req: &Request) -> T { .. }
``` ```
@ -772,12 +776,8 @@ catcher declared above looks like:
rocket::ignite().catch(catchers![not_found]) rocket::ignite().catch(catchers![not_found])
``` ```
Unlike route request handlers, catchers can only take 0, 1, or 2 parameters of Unlike route request handlers, catchers take exactly zero or one parameters. If
types [`Request`](https://api.rocket.rs/rocket/struct.Request.html) and/or the catcher takes a parameter, it must be of type
[`Error`](https://api.rocket.rs/rocket/enum.Error.html). At present, the `Error` [`&Request`](https://api.rocket.rs/rocket/struct.Request.html) The [error
type is not particularly useful, and so it is often omitted. The [error catcher catcher example](https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/examples/errors)
example](https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/examples/errors)
on GitHub illustrates their use in full. on GitHub illustrates their use in full.
Rocket has a default catcher for all of the standard HTTP error codes including
**404**, **500**, and more.