Rocket

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 112e7008366bfcfa88da385977af3e8a5f8bfd8d
parent 1f2f38ea5f441b9517de43271eb47ddaccd802bd
Author: Sergio Benitez <sb@sergio.bz>
Date:   Sun, 16 Sep 2018 00:33:16 -0700

Reimplement 'catch' attribute as a proc-macro.

Diffstat:
Mcontrib/codegen/Cargo.toml | 2+-
Dcore/codegen/src/decorators/catch.rs | 101-------------------------------------------------------------------------------
Mcore/codegen/src/decorators/mod.rs | 2--
Mcore/codegen/src/lib.rs | 4----
Dcore/codegen/src/parser/catch.rs | 83-------------------------------------------------------------------------------
Mcore/codegen/src/parser/mod.rs | 2--
Dcore/codegen/tests/error-handler.rs | 24------------------------
Dcore/codegen/tests/ui/bad-error-fn.rs | 12------------
Mcore/codegen_next/Cargo.toml | 2+-
Acore/codegen_next/src/attribute/catch.rs | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/codegen_next/src/attribute/mod.rs | 1+
Mcore/codegen_next/src/http_codegen.rs | 9++++++---
Mcore/codegen_next/src/lib.rs | 7+++++++
Acore/codegen_next/src/syn_ext.rs | 32++++++++++++++++++++++++++++++++
Acore/codegen_next/tests/ui-fail/catch.rs | 43+++++++++++++++++++++++++++++++++++++++++++
Acore/codegen_next/tests/ui-fail/catch.stderr | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/codegen_next/tests/ui-fail/catch_type_errors.rs | 29+++++++++++++++++++++++++++++
Acore/codegen_next/tests/ui-fail/catch_type_errors.stderr | 37+++++++++++++++++++++++++++++++++++++
Mcore/http/src/status.rs | 1-
Mcore/lib/src/catcher.rs | 16++++++----------
Mcore/lib/src/rocket.rs | 7+++----
Mcore/lib/tests/redirect_from_catcher-issue-113.rs | 2+-
Mexamples/content_types/src/main.rs | 6++----
Mexamples/errors/src/main.rs | 2+-
Mexamples/handlebars_templates/src/main.rs | 2+-
Mexamples/json/src/main.rs | 2+-
Mexamples/tera_templates/src/main.rs | 2+-
Msite/guide/requests.md | 26+++++++++++++-------------
28 files changed, 373 insertions(+), 270 deletions(-)

diff --git a/contrib/codegen/Cargo.toml b/contrib/codegen/Cargo.toml @@ -21,7 +21,7 @@ proc-macro = true [dependencies.derive_utils] git = "https://github.com/SergioBenitez/derive-utils" -rev = "5fad71394" +rev = "87ad56ba" [dependencies] quote = "0.6" diff --git a/core/codegen/src/decorators/catch.rs b/core/codegen/src/decorators/catch.rs @@ -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 -} diff --git a/core/codegen/src/decorators/mod.rs b/core/codegen/src/decorators/mod.rs @@ -1,5 +1,3 @@ mod route; -mod catch; pub use self::route::*; -pub use self::catch::*; diff --git a/core/codegen/src/lib.rs b/core/codegen/src/lib.rs @@ -412,14 +412,11 @@ const PARAM_PREFIX: &str = "rocket_param_"; const ROUTE_STRUCT_PREFIX: &str = "static_rocket_route_info_for_"; const CATCH_STRUCT_PREFIX: &str = "static_rocket_catch_info_for_"; 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 ROUTE_ATTR: &str = "rocket_route"; const ROUTE_INFO_ATTR: &str = "rocket_route_info"; -const CATCHER_ATTR: &str = "rocket_catcher"; - macro_rules! register_decorators { ($registry:expr, $($name:expr => $func:ident),+) => ( $($registry.register_syntax_extension(Symbol::intern($name), @@ -445,7 +442,6 @@ pub fn plugin_registrar(reg: &mut Registry) { ); register_decorators!(reg, - "catch" => catch_decorator, "route" => route_decorator, "get" => get_decorator, "put" => put_decorator, diff --git a/core/codegen/src/parser/catch.rs b/core/codegen/src/parser/catch.rs @@ -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) -} diff --git a/core/codegen/src/parser/mod.rs b/core/codegen/src/parser/mod.rs @@ -1,6 +1,5 @@ mod keyvalue; mod route; -mod catch; mod param; mod function; mod uri; @@ -8,7 +7,6 @@ mod uri_macro; pub use self::keyvalue::KVSpanned; pub use self::route::RouteParams; -pub use self::catch::CatchParams; pub use self::param::Param; pub use self::function::Function; pub use self::uri_macro::{Args, InternalUriParams, UriParams, Validation}; diff --git a/core/codegen/tests/error-handler.rs b/core/codegen/tests/error-handler.rs @@ -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() { } diff --git a/core/codegen/tests/ui/bad-error-fn.rs b/core/codegen/tests/ui/bad-error-fn.rs @@ -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" } diff --git a/core/codegen_next/Cargo.toml b/core/codegen_next/Cargo.toml @@ -22,7 +22,7 @@ rocket_http = { version = "0.4.0-dev", path = "../http/" } [dependencies.derive_utils] git = "https://github.com/SergioBenitez/derive-utils" -rev = "5fad71394" +rev = "87ad56ba" [dev-dependencies] rocket = { version = "0.4.0-dev", path = "../lib" } diff --git a/core/codegen_next/src/attribute/catch.rs b/core/codegen_next/src/attribute/catch.rs @@ -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() + } + } +} diff --git a/core/codegen_next/src/attribute/mod.rs b/core/codegen_next/src/attribute/mod.rs @@ -0,0 +1 @@ +pub mod catch; diff --git a/core/codegen_next/src/http_codegen.rs b/core/codegen_next/src/http_codegen.rs @@ -3,17 +3,20 @@ use proc_macro2::TokenStream as TokenStream2; use derive_utils::{FromMeta, MetaItem, Result, ext::Split2}; use rocket_http as http; +#[derive(Debug)] pub struct ContentType(http::ContentType); -pub struct Status(http::Status); +#[derive(Debug)] +pub struct Status(pub http::Status); +#[derive(Debug)] struct MediaType(http::MediaType); impl FromMeta for Status { fn from_meta(meta: MetaItem) -> Result<Self> { let num = usize::from_meta(meta)?; 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))) @@ -23,7 +26,7 @@ impl FromMeta for Status { impl ToTokens for Status { fn to_tokens(&self, tokens: &mut TokenStream2) { 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 })); } } diff --git a/core/codegen_next/src/lib.rs b/core/codegen_next/src/lib.rs @@ -8,7 +8,9 @@ extern crate proc_macro; extern crate rocket_http; mod derive; +mod attribute; mod http_codegen; +mod syn_ext; 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 { derive::responder::derive_responder(input) } + +#[proc_macro_attribute] +pub fn catch(args: TokenStream, input: TokenStream) -> TokenStream { + attribute::catch::catch_attribute(args, input) +} diff --git a/core/codegen_next/src/syn_ext.rs b/core/codegen_next/src/syn_ext.rs @@ -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), + } + } +} + diff --git a/core/codegen_next/tests/ui-fail/catch.rs b/core/codegen_next/tests/ui-fail/catch.rs @@ -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 +} diff --git a/core/codegen_next/tests/ui-fail/catch.stderr b/core/codegen_next/tests/ui-fail/catch.stderr @@ -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 + diff --git a/core/codegen_next/tests/ui-fail/catch_type_errors.rs b/core/codegen_next/tests/ui-fail/catch_type_errors.rs @@ -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() { } diff --git a/core/codegen_next/tests/ui-fail/catch_type_errors.stderr b/core/codegen_next/tests/ui-fail/catch_type_errors.stderr @@ -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`. diff --git a/core/http/src/status.rs b/core/http/src/status.rs @@ -134,7 +134,6 @@ macro_rules! ctrs { _ => None } } - }; } diff --git a/core/lib/src/catcher.rs b/core/lib/src/catcher.rs @@ -40,7 +40,7 @@ use yansi::Color::*; /// /// extern crate rocket; /// -/// use rocket::Request; +/// use rocket::{catch, Request}; /// /// #[catch(500)] /// 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: -/// `Error`, `&Request`, or both, as desired. +/// A function decorated with `catch` must take exactly zero or one arguments. +/// If the catcher takes an argument, it must be of type `&Request`. pub struct Catcher { /// The HTTP status code to match against. pub code: u16, - handler: ErrorHandler, - is_default: bool, + /// The catcher's associated handler. + pub handler: ErrorHandler, + crate is_default: bool, } impl Catcher { @@ -107,11 +108,6 @@ impl Catcher { fn new_default(code: u16, handler: ErrorHandler) -> Catcher { Catcher { code, handler, is_default: true, } } - - #[inline(always)] - crate fn is_default(&self) -> bool { - self.is_default - } } #[doc(hidden)] diff --git a/core/lib/src/rocket.rs b/core/lib/src/rocket.rs @@ -548,7 +548,7 @@ impl Rocket { /// /// extern crate rocket; /// - /// use rocket::Request; + /// use rocket::{catch, Request}; /// /// #[catch(500)] /// fn internal_error() -> &'static str { @@ -571,9 +571,8 @@ impl Rocket { pub fn catch(mut self, catchers: Vec<Catcher>) -> Self { info!("{}{}:", Paint::masked("👾 "), Paint::purple("Catchers")); for c in catchers { - if self.catchers.get(&c.code).map_or(false, |e| !e.is_default()) { - let msg = "(warning: duplicate catcher!)"; - info_!("{} {}", c, Paint::yellow(msg)); + if self.catchers.get(&c.code).map_or(false, |e| !e.is_default) { + info_!("{} {}", c, Paint::yellow("(warning: duplicate catcher!)")); } else { info_!("{}", c); } diff --git a/core/lib/tests/redirect_from_catcher-issue-113.rs b/core/lib/tests/redirect_from_catcher-issue-113.rs @@ -3,7 +3,7 @@ extern crate rocket; -use rocket::response::Redirect; +use rocket::{catch, Request, response::Redirect}; #[catch(404)] fn not_found() -> Redirect { diff --git a/examples/content_types/src/main.rs b/examples/content_types/src/main.rs @@ -3,13 +3,11 @@ extern crate rocket; extern crate serde_json; -#[macro_use] -extern crate serde_derive; +#[macro_use] extern crate serde_derive; #[cfg(test)] mod tests; -use rocket::Request; -use rocket::response::content; +use rocket::{catch, Request, response::content}; #[derive(Debug, Serialize, Deserialize)] struct Person { diff --git a/examples/errors/src/main.rs b/examples/errors/src/main.rs @@ -5,7 +5,7 @@ extern crate rocket; #[cfg(test)] mod tests; -use rocket::response::content; +use rocket::{catch, response::content}; #[get("/hello/<name>/<age>")] fn hello(name: String, age: i8) -> String { diff --git a/examples/handlebars_templates/src/main.rs b/examples/handlebars_templates/src/main.rs @@ -7,7 +7,7 @@ extern crate rocket; #[cfg(test)] mod tests; -use rocket::Request; +use rocket::{catch, Request}; use rocket::response::Redirect; use rocket_contrib::{Template, handlebars}; diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs @@ -7,8 +7,8 @@ extern crate rocket; #[cfg(test)] mod tests; +use rocket::{catch, Request, State}; use rocket_contrib::{Json, JsonValue}; -use rocket::State; use std::collections::HashMap; use std::sync::Mutex; diff --git a/examples/tera_templates/src/main.rs b/examples/tera_templates/src/main.rs @@ -10,7 +10,7 @@ extern crate serde_json; use std::collections::HashMap; -use rocket::Request; +use rocket::{catch, Request}; use rocket::response::Redirect; use rocket_contrib::Template; diff --git a/site/guide/requests.md b/site/guide/requests.md @@ -742,13 +742,17 @@ Routing may fail for a variety of reasons. These include: * No matching route was found. 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. -A catcher is like a route, except it only handles errors. Catchers are declared -via the `catch` attribute, which takes a single integer corresponding to the -HTTP status code to catch. For instance, to declare a catcher for **404** -errors, you'd write: +so, Rocket invokes the _catcher_ corresponding to the error's status code. A +catcher is like a route, except it only handles errors. Rocket provides default +catchers for all of the standard HTTP error codes. To override a default +catcher, or declare a catcher for a custom status code, use the `catch` +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 +use rocket::catch; + #[catch(404)] fn not_found(req: &Request) -> T { .. } ``` @@ -772,12 +776,8 @@ catcher declared above looks like: rocket::ignite().catch(catchers![not_found]) ``` -Unlike route request handlers, catchers can only take 0, 1, or 2 parameters of -types [`Request`](https://api.rocket.rs/rocket/struct.Request.html) and/or -[`Error`](https://api.rocket.rs/rocket/enum.Error.html). At present, the `Error` -type is not particularly useful, and so it is often omitted. The [error catcher -example](https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/examples/errors) +Unlike route request handlers, catchers take exactly zero or one parameters. If +the catcher takes a parameter, it must be of type +[`&Request`](https://api.rocket.rs/rocket/struct.Request.html) The [error +catcher example](https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/examples/errors) 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.