mirror of
https://github.com/rwf2/Rocket.git
synced 2025-01-17 23:19:06 +00:00
Reimplement 'catch' attribute as a proc-macro.
This commit is contained in:
parent
1f2f38ea5f
commit
112e700836
@ -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"
|
||||
|
@ -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
|
||||
}
|
@ -1,5 +1,3 @@
|
||||
mod route;
|
||||
mod catch;
|
||||
|
||||
pub use self::route::*;
|
||||
pub use self::catch::*;
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
@ -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};
|
||||
|
@ -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() { }
|
@ -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" }
|
@ -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" }
|
||||
|
113
core/codegen_next/src/attribute/catch.rs
Normal file
113
core/codegen_next/src/attribute/catch.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
1
core/codegen_next/src/attribute/mod.rs
Normal file
1
core/codegen_next/src/attribute/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod catch;
|
@ -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 }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
32
core/codegen_next/src/syn_ext.rs
Normal file
32
core/codegen_next/src/syn_ext.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
43
core/codegen_next/tests/ui-fail/catch.rs
Normal file
43
core/codegen_next/tests/ui-fail/catch.rs
Normal 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
|
||||
}
|
74
core/codegen_next/tests/ui-fail/catch.stderr
Normal file
74
core/codegen_next/tests/ui-fail/catch.stderr
Normal 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
|
||||
|
29
core/codegen_next/tests/ui-fail/catch_type_errors.rs
Normal file
29
core/codegen_next/tests/ui-fail/catch_type_errors.rs
Normal 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() { }
|
37
core/codegen_next/tests/ui-fail/catch_type_errors.stderr
Normal file
37
core/codegen_next/tests/ui-fail/catch_type_errors.stderr
Normal 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`.
|
@ -134,7 +134,6 @@ macro_rules! ctrs {
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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)]
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
extern crate rocket;
|
||||
|
||||
use rocket::response::Redirect;
|
||||
use rocket::{catch, Request, response::Redirect};
|
||||
|
||||
#[catch(404)]
|
||||
fn not_found() -> Redirect {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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};
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user