mirror of https://github.com/rwf2/Rocket.git
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]
|
[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"
|
||||||
|
|
|
@ -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 route;
|
||||||
mod catch;
|
|
||||||
|
|
||||||
pub use self::route::*;
|
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 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,
|
||||||
|
|
|
@ -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 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};
|
||||||
|
|
|
@ -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]
|
[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" }
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 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 }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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() { }
|
|
@ -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
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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.
|
|
||||||
|
|
Loading…
Reference in New Issue