Reimplement 'catch' attribute as a proc-macro.

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

View File

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

View File

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

View File

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

View File

@ -412,14 +412,11 @@ const PARAM_PREFIX: &str = "rocket_param_";
const ROUTE_STRUCT_PREFIX: &str = "static_rocket_route_info_for_";
const 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,

View File

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

View File

@ -1,6 +1,5 @@
mod keyvalue;
mod 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};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,17 +3,20 @@ use proc_macro2::TokenStream as TokenStream2;
use derive_utils::{FromMeta, MetaItem, Result, ext::Split2};
use 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 }));
}
}

View File

@ -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)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)]

View File

@ -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);
}

View File

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

View File

@ -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 {

View File

@ -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 {

View File

@ -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};

View File

@ -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;

View File

@ -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;

View File

@ -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.