mirror of https://github.com/rwf2/Rocket.git
Implement 'FromForm[Value]', 'Responder' proc-macro derives.
This completes the migration of custom derives to proc-macros, removing the need for the `custom_derive` feature in consumer code. This commit also includes documentation, unit tests, and compile UI tests for each of the derives. Additionally, this commit improves the existing `FromForm` and `FromFormValue` derives. The generated code for `FromForm` now returns an error value indicating the error condition. The `FromFormValue` derive now accepts a `form` attribute on variants for specifying the exact value string to match against. Closes #590. Closes #670.
This commit is contained in:
parent
b0f86dcba0
commit
d7f6d82fe4
|
@ -1,329 +0,0 @@
|
|||
#![allow(unused_imports)] // FIXME: Why is this coming from quote_tokens?
|
||||
|
||||
use std::mem::transmute;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use syntax::ext::base::{Annotatable, ExtCtxt};
|
||||
use syntax::print::pprust::{stmt_to_string};
|
||||
use syntax::ast::{ItemKind, Expr, MetaItem, Mutability, VariantData, Ident};
|
||||
use syntax::ast::{StructField, GenericParamKind};
|
||||
use syntax::codemap::Span;
|
||||
use syntax::ext::build::AstBuilder;
|
||||
use syntax::ptr::P;
|
||||
|
||||
use syntax_ext::deriving::generic::MethodDef;
|
||||
use syntax_ext::deriving::generic::{StaticStruct, Substructure, TraitDef, ty};
|
||||
use syntax_ext::deriving::generic::combine_substructure as c_s;
|
||||
|
||||
use utils::{strip_ty_lifetimes, is_valid_ident, SpanExt, GenericParamExt};
|
||||
|
||||
static ONLY_STRUCTS_ERR: &'static str = "`FromForm` can only be derived for \
|
||||
structures with named fields.";
|
||||
static PRIVATE_LIFETIME: &'static str = "'rocket";
|
||||
|
||||
fn struct_lifetime(ecx: &mut ExtCtxt, item: &Annotatable, sp: Span) -> Option<String> {
|
||||
match *item {
|
||||
Annotatable::Item(ref item) => match item.node {
|
||||
ItemKind::Struct(_, ref generics) => {
|
||||
let mut lifetimes = generics.params.iter()
|
||||
.filter(|p| p.is_lifetime())
|
||||
.map(|p| p.ident.to_string());
|
||||
|
||||
let lifetime = lifetimes.next();
|
||||
if lifetimes.next().is_some() {
|
||||
ecx.span_err(generics.span, "cannot have more than one \
|
||||
lifetime parameter when deriving `FromForm`.");
|
||||
}
|
||||
|
||||
lifetime
|
||||
},
|
||||
_ => ecx.span_fatal(sp, ONLY_STRUCTS_ERR)
|
||||
},
|
||||
_ => ecx.span_fatal(sp, ONLY_STRUCTS_ERR)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use proper logging to emit the error messages.
|
||||
pub fn from_form_derive(
|
||||
ecx: &mut ExtCtxt,
|
||||
span: Span,
|
||||
meta_item: &MetaItem,
|
||||
annotated: &Annotatable,
|
||||
push: &mut FnMut(Annotatable)
|
||||
) {
|
||||
let struct_lifetime = struct_lifetime(ecx, annotated, span);
|
||||
let (lifetime_var, trait_generics) = match struct_lifetime {
|
||||
Some(ref lifetime) => (Some(lifetime.as_str()), ty::LifetimeBounds::empty()),
|
||||
None => (Some(PRIVATE_LIFETIME), ty::LifetimeBounds {
|
||||
lifetimes: vec![(PRIVATE_LIFETIME, vec![])],
|
||||
bounds: vec![]
|
||||
})
|
||||
};
|
||||
|
||||
// The error type in the derived implementation.
|
||||
let error_type = ty::Ty::Literal(ty::Path::new_(vec!["rocket", "Error"],
|
||||
None, vec![], ty::PathKind::Global));
|
||||
|
||||
let trait_def = TraitDef {
|
||||
is_unsafe: false,
|
||||
supports_unions: false,
|
||||
span: span,
|
||||
// We add these attribute because some `FromFormValue` implementations
|
||||
// can't fail. This is indicated via the `!` type. Rust checks if a
|
||||
// match is made with something of that type, and since we always emit
|
||||
// an `Err` match, we'll get this lint warning.
|
||||
attributes: vec![quote_attr!(ecx, #[allow(unreachable_code, unreachable_patterns)])],
|
||||
path: ty::Path::new_(
|
||||
vec!["rocket", "request", "FromForm"],
|
||||
lifetime_var,
|
||||
vec![],
|
||||
ty::PathKind::Global,
|
||||
),
|
||||
additional_bounds: Vec::new(),
|
||||
generics: trait_generics,
|
||||
methods: vec![
|
||||
MethodDef {
|
||||
name: "from_form",
|
||||
generics: ty::LifetimeBounds::empty(),
|
||||
explicit_self: None,
|
||||
args: vec![
|
||||
(ty::Ptr(
|
||||
Box::new(ty::Literal(ty::Path::new_(
|
||||
vec!["rocket", "request", "FormItems"],
|
||||
lifetime_var,
|
||||
vec![],
|
||||
ty::PathKind::Global,
|
||||
))),
|
||||
ty::Borrowed(None, Mutability::Mutable)
|
||||
), "it"),
|
||||
(ty::Literal(ty::Path::new_local("bool")), "strict"),
|
||||
],
|
||||
ret_ty: ty::Literal(ty::Path::new_(
|
||||
vec!["result", "Result"],
|
||||
None,
|
||||
vec![
|
||||
Box::new(ty::Ty::Self_),
|
||||
Box::new(error_type.clone())
|
||||
],
|
||||
ty::PathKind::Std,
|
||||
)),
|
||||
attributes: vec![],
|
||||
is_unsafe: false,
|
||||
combine_substructure: c_s(Box::new(from_form_substructure)),
|
||||
unify_fieldless_variants: false,
|
||||
}
|
||||
],
|
||||
associated_types: vec![
|
||||
(Ident::from_str("Error"), error_type.clone())
|
||||
],
|
||||
};
|
||||
|
||||
trait_def.expand(ecx, meta_item, annotated, push);
|
||||
}
|
||||
|
||||
fn is_valid_field_name(name: &str) -> bool {
|
||||
// The HTML5 spec (4.10.18.1) says 'isindex' is not allowed.
|
||||
if name == "isindex" || name.is_empty() {
|
||||
return false
|
||||
}
|
||||
|
||||
// We allow all visible ASCII characters except '&', '=', and '?' since we
|
||||
// use those as control characters for parsing.
|
||||
name.chars()
|
||||
.all(|c| (c >= ' ' && c <= '~') && c != '&' && c != '=' && c != '?')
|
||||
}
|
||||
|
||||
pub fn extract_field_ident_name(ecx: &ExtCtxt, struct_field: &StructField)
|
||||
-> (Ident, String, Span) {
|
||||
let ident = match struct_field.ident {
|
||||
Some(ident) => ident,
|
||||
None => ecx.span_fatal(struct_field.span, ONLY_STRUCTS_ERR)
|
||||
};
|
||||
|
||||
let field_attrs: Vec<_> = struct_field.attrs.iter()
|
||||
.filter(|attr| attr.check_name("form"))
|
||||
.collect();
|
||||
|
||||
let default = |ident: Ident| (ident, ident.to_string(), struct_field.span);
|
||||
if field_attrs.len() == 0 {
|
||||
return default(ident);
|
||||
} else if field_attrs.len() > 1 {
|
||||
ecx.span_err(struct_field.span, "only a single #[form(..)] \
|
||||
attribute can be applied to a given struct field at a time");
|
||||
return default(ident);
|
||||
}
|
||||
|
||||
let field_attr = field_attrs[0];
|
||||
::syntax::attr::mark_known(&field_attr);
|
||||
if !field_attr.meta_item_list().map_or(false, |l| l.len() == 1) {
|
||||
ecx.struct_span_err(field_attr.span, "incorrect use of attribute")
|
||||
.help(r#"the `form` attribute must have the form: #[form(field = "..")]"#)
|
||||
.emit();
|
||||
return default(ident);
|
||||
}
|
||||
|
||||
let inner_item = &field_attr.meta_item_list().unwrap()[0];
|
||||
if !inner_item.check_name("field") {
|
||||
ecx.struct_span_err(inner_item.span, "invalid `form` attribute contents")
|
||||
.help(r#"only the 'field' key is supported: #[form(field = "..")]"#)
|
||||
.emit();
|
||||
return default(ident);
|
||||
}
|
||||
|
||||
if !inner_item.is_value_str() {
|
||||
ecx.struct_span_err(inner_item.span, "invalid `field` in attribute")
|
||||
.help(r#"the `form` attribute must have the form: #[form(field = "..")]"#)
|
||||
.emit();
|
||||
return default(ident);
|
||||
}
|
||||
|
||||
let name = inner_item.value_str().unwrap().as_str().to_string();
|
||||
let sp = inner_item.span.shorten_upto(name.len() + 2);
|
||||
if !is_valid_field_name(&name) {
|
||||
ecx.struct_span_err(sp, "invalid form field name")
|
||||
.help("field names must be visible ASCII without '&', '=', or '?'")
|
||||
.emit();
|
||||
}
|
||||
|
||||
(ident, name, sp)
|
||||
}
|
||||
|
||||
fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substructure) -> P<Expr> {
|
||||
// Check that we specified the methods to the argument correctly.
|
||||
const EXPECTED_ARGS: usize = 2;
|
||||
let (items_arg, strict_arg) = if substr.nonself_args.len() == EXPECTED_ARGS {
|
||||
(&substr.nonself_args[0], &substr.nonself_args[1])
|
||||
} else {
|
||||
let msg = format!("incorrect number of arguments in `from_form_string`: \
|
||||
expected {}, found {}", EXPECTED_ARGS, substr.nonself_args.len());
|
||||
cx.span_bug(trait_span, msg.as_str());
|
||||
};
|
||||
|
||||
debug!("arguments are: {:?}, {:?}", items_arg, strict_arg);
|
||||
|
||||
// Ensure the the fields are from a 'StaticStruct' and extract them.
|
||||
let fields = match *substr.fields {
|
||||
StaticStruct(var_data, _) => match *var_data {
|
||||
VariantData::Struct(ref fields, _) => fields,
|
||||
_ => cx.span_fatal(trait_span, ONLY_STRUCTS_ERR)
|
||||
},
|
||||
_ => cx.span_bug(trait_span, "impossible substructure in `from_form`")
|
||||
};
|
||||
|
||||
// Vec of (ident: Ident, type: Ty, name: String), one for each field.
|
||||
let mut names = HashMap::new();
|
||||
let mut fields_info = vec![];
|
||||
for field in fields {
|
||||
let (ident, name, span) = extract_field_ident_name(cx, field);
|
||||
let stripped_ty = strip_ty_lifetimes(field.ty.clone());
|
||||
|
||||
if let Some(sp) = names.get(&name).cloned() {
|
||||
cx.struct_span_err(span, "field with duplicate name")
|
||||
.span_note(sp, "original was declared here")
|
||||
.emit();
|
||||
} else {
|
||||
names.insert(name.clone(), span);
|
||||
}
|
||||
|
||||
fields_info.push((ident, stripped_ty, name));
|
||||
}
|
||||
|
||||
debug!("Fields, types, attrs: {:?}", fields_info);
|
||||
let mut stmts = Vec::new();
|
||||
|
||||
// The thing to do when we wish to exit with an error.
|
||||
let return_err_stmt = quote_tokens!(cx,
|
||||
return Err(::rocket::Error::BadParse)
|
||||
);
|
||||
|
||||
// Generate the let bindings for parameters that will be unwrapped and
|
||||
// placed into the final struct. They start out as `None` and are changed
|
||||
// to Some when a parse completes, or some default value if the parse was
|
||||
// unsuccessful and default() returns Some.
|
||||
for &(ref ident, ref ty, _) in &fields_info {
|
||||
stmts.push(quote_stmt!(cx,
|
||||
let mut $ident: ::std::option::Option<$ty> = None;
|
||||
).unwrap());
|
||||
}
|
||||
|
||||
// Generating an arm for each struct field. This matches against the key and
|
||||
// tries to parse the value according to the type.
|
||||
let mut arms = vec![];
|
||||
for &(ref ident, _, ref name) in &fields_info {
|
||||
arms.push(quote_tokens!(cx,
|
||||
$name => {
|
||||
let __r = ::rocket::http::RawStr::from_str(__v);
|
||||
$ident = match ::rocket::request::FromFormValue::from_form_value(__r) {
|
||||
Ok(__v) => Some(__v),
|
||||
Err(__e) => {
|
||||
println!(" => Error parsing form val '{}': {:?}",
|
||||
$name, __e);
|
||||
$return_err_stmt
|
||||
}
|
||||
};
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
// The actual match statement. Iterate through all of the fields in the form
|
||||
// and use the $arms generated above.
|
||||
stmts.push(quote_stmt!(cx,
|
||||
for (__k, __v) in $items_arg {
|
||||
match __k.as_str() {
|
||||
$arms
|
||||
_ => {
|
||||
// If we're parsing strictly, emit an error for everything
|
||||
// the user hasn't asked for. Keep synced with 'preprocess'.
|
||||
if $strict_arg && __k != "_method" {
|
||||
println!(" => {}={} has no matching field in struct.",
|
||||
__k, __v);
|
||||
$return_err_stmt
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
).unwrap());
|
||||
|
||||
// This looks complicated but just generates the boolean condition checking
|
||||
// that each parameter actually is Some() or has a default value.
|
||||
let mut failure_conditions = vec![];
|
||||
|
||||
for &(ref ident, ref ty, _) in (&fields_info).iter() {
|
||||
failure_conditions.push(quote_tokens!(cx,
|
||||
if $ident.is_none() &&
|
||||
<$ty as ::rocket::request::FromFormValue>::default().is_none() {
|
||||
println!(" => '{}' did not parse.", stringify!($ident));
|
||||
$return_err_stmt;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
// The fields of the struct, which are just the let bindings declared above
|
||||
// or the default value.
|
||||
let mut result_fields = vec![];
|
||||
for &(ref ident, ref ty, _) in &fields_info {
|
||||
result_fields.push(quote_tokens!(cx,
|
||||
$ident: $ident.unwrap_or_else(||
|
||||
<$ty as ::rocket::request::FromFormValue>::default().unwrap()
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
// The final block: check the error conditions, and if all is well, return
|
||||
// the structure.
|
||||
let self_ident = substr.type_ident;
|
||||
let final_block = quote_block!(cx, {
|
||||
$failure_conditions
|
||||
|
||||
Ok($self_ident { $result_fields })
|
||||
});
|
||||
|
||||
stmts.extend(final_block.into_inner().stmts);
|
||||
|
||||
debug!("Form statements:");
|
||||
for stmt in &stmts {
|
||||
debug!("{:?}", stmt_to_string(stmt));
|
||||
}
|
||||
|
||||
cx.expr_block(cx.block(trait_span, stmts))
|
||||
}
|
|
@ -1,8 +1,5 @@
|
|||
mod route;
|
||||
mod catch;
|
||||
mod derive_form;
|
||||
|
||||
pub use self::route::*;
|
||||
pub use self::catch::*;
|
||||
pub use self::derive_form::*;
|
||||
|
||||
|
|
|
@ -14,6 +14,16 @@
|
|||
//! here is purely technical. The code generation facilities are documented
|
||||
//! thoroughly in the [Rocket programming guide](https://rocket.rs/guide).
|
||||
//!
|
||||
//! ## **Table of Contents**
|
||||
//!
|
||||
//! 1. [Custom Attributes](#custom-attributes)
|
||||
//! 2. [Custom Derives](#custom-derives)
|
||||
//! * [`FromForm`](#fromform)
|
||||
//! * [`FromFormValue`](#fromformvalue)
|
||||
//! * [`Responder`](#responder)
|
||||
//! 3. [Procedural Macros](#procedural-macros)
|
||||
//! 4. [Debugging Generated Code](#debugging-codegen)
|
||||
//!
|
||||
//! ## Custom Attributes
|
||||
//!
|
||||
//! This crate implements the following custom attributes:
|
||||
|
@ -74,10 +84,15 @@
|
|||
//!
|
||||
//! ## Custom Derives
|
||||
//!
|
||||
//! This crate implements the following custom derives:
|
||||
//! This crate* implements the following custom derives:
|
||||
//!
|
||||
//! * **FromForm**
|
||||
//! * **FromFormValue**
|
||||
//! * **Responder**
|
||||
//!
|
||||
//! <small>* In reality, all of these custom derives are currently implemented
|
||||
//! by the `rocket_codegen_next` crate. Nonetheless, they are documented
|
||||
//! here.</small>
|
||||
//! ### `FromForm`
|
||||
//!
|
||||
//! The [`FromForm`] derive can be applied to structures with named fields:
|
||||
|
@ -120,6 +135,154 @@
|
|||
//! [`FromForm`]: /rocket/request/trait.FromForm.html
|
||||
//! [`FromFormValue`]: /rocket/request/trait.FromFormValue.html
|
||||
//!
|
||||
//! ### `FromFormValue`
|
||||
//!
|
||||
//! The [`FromFormValue`] derive can be applied to enums with nullary
|
||||
//! (zero-length) fields:
|
||||
//!
|
||||
//! #[derive(FromFormValue)]
|
||||
//! enum MyValue {
|
||||
//! First,
|
||||
//! Second,
|
||||
//! Third,
|
||||
//! }
|
||||
//!
|
||||
//! The derive generates an implementation of the [`FromFormValue`] trait for
|
||||
//! the decorated `enum`. The implementation returns successfully when the form
|
||||
//! value matches, case insensitively, the stringified version of a variant's
|
||||
//! name, returning an instance of said variant.
|
||||
//!
|
||||
//! As an example, for the `enum` above, the form values `"first"`, `"FIRST"`,
|
||||
//! `"fiRSt"`, and so on would parse as `MyValue::First`, while `"second"` and
|
||||
//! `"third"` would parse as `MyValue::Second` and `MyValue::Third`,
|
||||
//! respectively.
|
||||
//!
|
||||
//! The `form` field attribute can be used to change the string that is compared
|
||||
//! against for a given variant:
|
||||
//!
|
||||
//! #[derive(FromFormValue)]
|
||||
//! enum MyValue {
|
||||
//! First,
|
||||
//! Second,
|
||||
//! #[form(value = "fourth")]
|
||||
//! Third,
|
||||
//! }
|
||||
//!
|
||||
//! The attribute's grammar is:
|
||||
//!
|
||||
//! <pre>
|
||||
//! form := 'field' '=' STRING_LIT
|
||||
//!
|
||||
//! STRING_LIT := any valid string literal, as defined by Rust
|
||||
//! </pre>
|
||||
//!
|
||||
//! The attribute accepts a single string parameter of name `value`
|
||||
//! corresponding to the string to use to match against for the decorated
|
||||
//! variant. In the example above, the the strings `"fourth"`, `"FOUrth"` and so
|
||||
//! on would parse as `MyValue::Third`.
|
||||
//!
|
||||
//! ## `Responder`
|
||||
//!
|
||||
//! The [`Responder`] derive can be applied to enums and named structs. When
|
||||
//! applied to enums, variants must have at least one field. When applied to
|
||||
//! structs, the struct must have at least one field.
|
||||
//!
|
||||
//! #[derive(Responder)]
|
||||
//! enum MyResponder {
|
||||
//! A(String),
|
||||
//! B(OtherResponse, ContentType),
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Responder)]
|
||||
//! struct MyResponder {
|
||||
//! inner: OtherResponder,
|
||||
//! header: ContentType,
|
||||
//! }
|
||||
//!
|
||||
//! The derive generates an implementation of the [`Responder`] trait for the
|
||||
//! decorated enum or structure. The derive uses the _first_ field of a variant
|
||||
//! or structure to generate a `Response`. As such, the type of the first field
|
||||
//! must implement [`Responder`]. The remaining fields of a variant or structure
|
||||
//! are set as headers in the produced [`Response`] using
|
||||
//! [`Response::set_header()`]. As such, every other field (unless explicitly
|
||||
//! ignored, explained next) must implement `Into<Header>`.
|
||||
//!
|
||||
//! Except for the first field, fields decorated with `#[response(ignore)]` are
|
||||
//! ignored by the derive:
|
||||
//!
|
||||
//! #[derive(Responder)]
|
||||
//! enum MyResponder {
|
||||
//! A(String),
|
||||
//! B(OtherResponse, ContentType, #[response(ignore)] Other),
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Responder)]
|
||||
//! struct MyResponder {
|
||||
//! inner: InnerResponder,
|
||||
//! header: ContentType,
|
||||
//! #[response(ignore)]
|
||||
//! other: Other,
|
||||
//! }
|
||||
//!
|
||||
//! Decorating the first field with `#[response(ignore)]` has no effect.
|
||||
//!
|
||||
//! Additionally, the `response` attribute can be used on named structures and
|
||||
//! enum variants to override the status and/or content-type of the [`Response`]
|
||||
//! produced by the generated implementation. The `response` attribute used in
|
||||
//! these positions has the following grammar:
|
||||
//!
|
||||
//! <pre>
|
||||
//! response := parameter (',' parameter)?
|
||||
//!
|
||||
//! parameter := 'status' '=' STATUS
|
||||
//! | 'content_type' '=' CONTENT_TYPE
|
||||
//!
|
||||
//! STATUS := unsigned integer >= 100 and < 600
|
||||
//! CONTENT_TYPE := string literal, as defined by Rust, identifying a valid
|
||||
//! Content-Type, as defined by Rocket
|
||||
//! </pre>
|
||||
//!
|
||||
//! It can be used as follows:
|
||||
//!
|
||||
//! #[derive(Responder)]
|
||||
//! enum Error {
|
||||
//! #[response(status = 500, content_type = "json")]
|
||||
//! A(String),
|
||||
//! #[response(status = 404)]
|
||||
//! B(OtherResponse, ContentType),
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Responder)]
|
||||
//! #[response(status = 400)]
|
||||
//! struct MyResponder {
|
||||
//! inner: InnerResponder,
|
||||
//! header: ContentType,
|
||||
//! #[response(ignore)]
|
||||
//! other: Other,
|
||||
//! }
|
||||
//!
|
||||
//! The attribute accepts two key/value pairs: `status` and `content_type`. The
|
||||
//! value of `status` must be an unsigned integer representing a valid status
|
||||
//! code. The [`Response`] produced from the generated implementation will have
|
||||
//! its status overriden to this value.
|
||||
//!
|
||||
//! The value of `content_type` must be a valid media-type in `top/sub` form or
|
||||
//! `shorthand` form. Examples include:
|
||||
//!
|
||||
//! * `"text/html"`
|
||||
//! * `"application/x-custom"`
|
||||
//! * `"html"`
|
||||
//! * `"json"`
|
||||
//! * `"plain"`
|
||||
//! * `"binary"`
|
||||
//!
|
||||
//! The [`Response`] produced from the generated implementation will have its
|
||||
//! content-type overriden to this value.
|
||||
//!
|
||||
//! [`Responder`]: /rocket/response/trait.Responder.html
|
||||
//! [`Response`]: /rocket/struct.Response.html
|
||||
//! [`Response::set_header()`]: /rocket/struct.Response.html#method.set_header
|
||||
//!
|
||||
//! ## Procedural Macros
|
||||
//!
|
||||
//! This crate implements the following procedural macros:
|
||||
|
@ -265,14 +428,6 @@ macro_rules! register_decorators {
|
|||
)
|
||||
}
|
||||
|
||||
macro_rules! register_derives {
|
||||
($registry:expr, $($name:expr => $func:ident),+) => (
|
||||
$($registry.register_custom_derive(Symbol::intern($name),
|
||||
SyntaxExtension::MultiDecorator(Box::new(decorators::$func)));
|
||||
)+
|
||||
)
|
||||
}
|
||||
|
||||
macro_rules! register_macros {
|
||||
($reg:expr, $($n:expr => $f:ident),+) => (
|
||||
$($reg.register_macro($n, macros::$f);)+
|
||||
|
@ -289,10 +444,6 @@ pub fn plugin_registrar(reg: &mut Registry) {
|
|||
"rocket_internal_uri" => uri_internal
|
||||
);
|
||||
|
||||
register_derives!(reg,
|
||||
"derive_FromForm" => from_form_derive
|
||||
);
|
||||
|
||||
register_decorators!(reg,
|
||||
"catch" => catch_decorator,
|
||||
"route" => route_decorator,
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm {
|
||||
#[form(field = "blah", field = "bloo")]
|
||||
//~^ ERROR: incorrect use of attribute
|
||||
my_field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm1 {
|
||||
#[form]
|
||||
//~^ ERROR: incorrect use of attribute
|
||||
my_field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm2 {
|
||||
#[form("blah")]
|
||||
//~^ ERROR: invalid `form` attribute
|
||||
my_field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm3 {
|
||||
#[form(123)]
|
||||
//~^ ERROR: invalid `form` attribute
|
||||
my_field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm4 {
|
||||
#[form(beep = "bop")]
|
||||
//~^ ERROR: invalid `form` attribute
|
||||
my_field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm5 {
|
||||
#[form(field = "blah")]
|
||||
#[form(field = "blah")]
|
||||
my_field: String,
|
||||
//~^ ERROR: only a single
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm6 {
|
||||
#[form(field = true)]
|
||||
//~^ ERROR: invalid `field` in attribute
|
||||
my_field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm7 {
|
||||
#[form(field)]
|
||||
//~^ ERROR: invalid `field` in attribute
|
||||
my_field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm8 {
|
||||
#[form(field = 123)]
|
||||
//~^ ERROR: invalid `field` in attribute
|
||||
my_field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm9 {
|
||||
#[form(field = "hello")]
|
||||
first: String,
|
||||
#[form(field = "hello")]
|
||||
//~^ ERROR: field with duplicate name
|
||||
other: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm10 {
|
||||
first: String,
|
||||
#[form(field = "first")]
|
||||
//~^ ERROR: field with duplicate name
|
||||
other: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm11 {
|
||||
#[form(field = "hello&world")]
|
||||
//~^ ERROR: invalid form field
|
||||
first: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm12 {
|
||||
#[form(field = "!@#$%^&*()_")]
|
||||
//~^ ERROR: invalid form field
|
||||
first: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm13 {
|
||||
#[form(field = "?")]
|
||||
//~^ ERROR: invalid form field
|
||||
first: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm14 {
|
||||
#[form(field = "")]
|
||||
//~^ ERROR: invalid form field
|
||||
first: String,
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
extern crate rocket;
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
use rocket::http::{Cookies, RawStr};
|
||||
use rocket::request::Form;
|
||||
|
@ -13,13 +12,14 @@ struct User<'a> {
|
|||
nickname: String,
|
||||
}
|
||||
|
||||
#[post("/<name>?<_query>", format = "application/json", data = "<user>", rank = 2)]
|
||||
fn get<'r>(name: &RawStr,
|
||||
_query: User<'r>,
|
||||
user: Form<'r, User<'r>>,
|
||||
cookies: Cookies)
|
||||
-> &'static str {
|
||||
"hi"
|
||||
#[post("/<_name>?<_query>", format = "application/json", data = "<user>", rank = 2)]
|
||||
fn get<'r>(
|
||||
_name: &RawStr,
|
||||
_query: User<'r>,
|
||||
user: Form<'r, User<'r>>,
|
||||
_cookies: Cookies
|
||||
) -> String {
|
||||
format!("{}:{}", user.get().name, user.get().nickname)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket;
|
||||
|
|
|
@ -1,200 +0,0 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket;
|
||||
|
||||
use rocket::request::{FromForm, FromFormValue, FormItems};
|
||||
use rocket::http::RawStr;
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct TodoTask {
|
||||
description: String,
|
||||
completed: bool
|
||||
}
|
||||
|
||||
// TODO: Make deriving `FromForm` for this enum possible.
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum FormOption {
|
||||
A, B, C
|
||||
}
|
||||
|
||||
impl<'v> FromFormValue<'v> for FormOption {
|
||||
type Error = &'v str;
|
||||
|
||||
fn from_form_value(v: &'v RawStr) -> Result<Self, Self::Error> {
|
||||
let variant = match v.as_str() {
|
||||
"a" => FormOption::A,
|
||||
"b" => FormOption::B,
|
||||
"c" => FormOption::C,
|
||||
_ => return Err(v)
|
||||
};
|
||||
|
||||
Ok(variant)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct FormInput<'r> {
|
||||
checkbox: bool,
|
||||
number: usize,
|
||||
radio: FormOption,
|
||||
password: &'r RawStr,
|
||||
textarea: String,
|
||||
select: FormOption,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct DefaultInput<'r> {
|
||||
arg: Option<&'r RawStr>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct ManualMethod<'r> {
|
||||
_method: Option<&'r RawStr>,
|
||||
done: bool
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct UnpresentCheckbox {
|
||||
checkbox: bool
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct UnpresentCheckboxTwo<'r> {
|
||||
checkbox: bool,
|
||||
something: &'r RawStr
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct FieldNamedV<'r> {
|
||||
v: &'r RawStr,
|
||||
}
|
||||
|
||||
fn parse<'f, T: FromForm<'f>>(string: &'f str, strict: bool) -> Option<T> {
|
||||
let mut items = FormItems::from(string);
|
||||
let result = T::from_form(items.by_ref(), strict);
|
||||
if !items.exhaust() {
|
||||
panic!("Invalid form input.");
|
||||
}
|
||||
|
||||
result.ok()
|
||||
}
|
||||
|
||||
fn strict<'f, T: FromForm<'f>>(string: &'f str) -> Option<T> {
|
||||
parse(string, true)
|
||||
}
|
||||
|
||||
fn lenient<'f, T: FromForm<'f>>(string: &'f str) -> Option<T> {
|
||||
parse(string, false)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn main() {
|
||||
// Same number of arguments: simple case.
|
||||
let task: Option<TodoTask> = strict("description=Hello&completed=on");
|
||||
assert_eq!(task, Some(TodoTask {
|
||||
description: "Hello".to_string(),
|
||||
completed: true
|
||||
}));
|
||||
|
||||
// Argument in string but not in form.
|
||||
let task: Option<TodoTask> = strict("other=a&description=Hello&completed=on");
|
||||
assert!(task.is_none());
|
||||
|
||||
// Ensure _method isn't required.
|
||||
let task: Option<TodoTask> = strict("_method=patch&description=Hello&completed=off");
|
||||
assert_eq!(task, Some(TodoTask {
|
||||
description: "Hello".to_string(),
|
||||
completed: false
|
||||
}));
|
||||
|
||||
let form_string = &[
|
||||
"password=testing", "checkbox=off", "checkbox=on", "number=10",
|
||||
"checkbox=off", "textarea=", "select=a", "radio=c",
|
||||
].join("&");
|
||||
|
||||
let input: Option<FormInput> = strict(&form_string);
|
||||
assert_eq!(input, Some(FormInput {
|
||||
checkbox: false,
|
||||
number: 10,
|
||||
radio: FormOption::C,
|
||||
password: "testing".into(),
|
||||
textarea: "".to_string(),
|
||||
select: FormOption::A,
|
||||
}));
|
||||
|
||||
// Argument not in string with default in form.
|
||||
let default: Option<DefaultInput> = strict("");
|
||||
assert_eq!(default, Some(DefaultInput {
|
||||
arg: None
|
||||
}));
|
||||
|
||||
// Ensure _method can be captured if desired.
|
||||
let manual: Option<ManualMethod> = strict("_method=put&done=true");
|
||||
assert_eq!(manual, Some(ManualMethod {
|
||||
_method: Some("put".into()),
|
||||
done: true
|
||||
}));
|
||||
|
||||
let manual: Option<ManualMethod> = lenient("_method=put&done=true");
|
||||
assert_eq!(manual, Some(ManualMethod {
|
||||
_method: Some("put".into()),
|
||||
done: true
|
||||
}));
|
||||
|
||||
// And ignored when not present.
|
||||
let manual: Option<ManualMethod> = strict("done=true");
|
||||
assert_eq!(manual, Some(ManualMethod {
|
||||
_method: None,
|
||||
done: true
|
||||
}));
|
||||
|
||||
// Check that a `bool` value that isn't in the form is marked as `false`.
|
||||
let manual: Option<UnpresentCheckbox> = strict("");
|
||||
assert_eq!(manual, Some(UnpresentCheckbox {
|
||||
checkbox: false
|
||||
}));
|
||||
|
||||
// Check that a `bool` value that isn't in the form is marked as `false`.
|
||||
let manual: Option<UnpresentCheckboxTwo> = strict("something=hello");
|
||||
assert_eq!(manual, Some(UnpresentCheckboxTwo {
|
||||
checkbox: false,
|
||||
something: "hello".into()
|
||||
}));
|
||||
|
||||
// Check that a structure with one field `v` parses correctly.
|
||||
let manual: Option<FieldNamedV> = strict("v=abc");
|
||||
assert_eq!(manual, Some(FieldNamedV {
|
||||
v: "abc".into()
|
||||
}));
|
||||
|
||||
// Check that a structure with one field `v` parses correctly (lenient).
|
||||
let manual: Option<FieldNamedV> = lenient("v=abc");
|
||||
assert_eq!(manual, Some(FieldNamedV { v: "abc".into() }));
|
||||
|
||||
let manual: Option<FieldNamedV> = lenient("v=abc&a=123");
|
||||
assert_eq!(manual, Some(FieldNamedV { v: "abc".into() }));
|
||||
|
||||
let manual: Option<FieldNamedV> = lenient("c=abcddef&v=abc&a=123");
|
||||
assert_eq!(manual, Some(FieldNamedV { v: "abc".into() }));
|
||||
|
||||
// Check default values (bool) with lenient parsing.
|
||||
let manual: Option<UnpresentCheckboxTwo> = lenient("something=hello");
|
||||
assert_eq!(manual, Some(UnpresentCheckboxTwo {
|
||||
checkbox: false,
|
||||
something: "hello".into()
|
||||
}));
|
||||
|
||||
let manual: Option<UnpresentCheckboxTwo> = lenient("hi=hi&something=hello");
|
||||
assert_eq!(manual, Some(UnpresentCheckboxTwo {
|
||||
checkbox: false,
|
||||
something: "hello".into()
|
||||
}));
|
||||
|
||||
// Check that a missing field doesn't parse, even leniently.
|
||||
let manual: Option<FieldNamedV> = lenient("a=abc");
|
||||
assert!(manual.is_none());
|
||||
|
||||
let manual: Option<FieldNamedV> = lenient("_method=abc");
|
||||
assert!(manual.is_none());
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket;
|
||||
|
||||
use rocket::request::{FromForm, FormItems};
|
||||
|
||||
#[derive(PartialEq, Debug, FromForm)]
|
||||
struct Form { }
|
||||
|
||||
#[test]
|
||||
fn main() {
|
||||
// Same number of arguments: simple case.
|
||||
let task = Form::from_form(&mut FormItems::from(""), true);
|
||||
assert_eq!(task, Ok(Form { }));
|
||||
|
||||
let task = Form::from_form(&mut FormItems::from(""), false);
|
||||
assert_eq!(task, Ok(Form { }));
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket;
|
||||
|
||||
use rocket::request::{FromForm, FormItems};
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct Form {
|
||||
single: usize,
|
||||
#[form(field = "camelCase")]
|
||||
camel_case: String,
|
||||
#[form(field = "TitleCase")]
|
||||
title_case: String,
|
||||
#[form(field = "type")]
|
||||
field_type: isize,
|
||||
#[form(field = "DOUBLE")]
|
||||
double: String,
|
||||
#[form(field = "a.b")]
|
||||
dot: isize,
|
||||
}
|
||||
|
||||
fn parse<'f, T: FromForm<'f>>(string: &'f str, strict: bool) -> Option<T> {
|
||||
let mut items = FormItems::from(string);
|
||||
let result = T::from_form(items.by_ref(), strict);
|
||||
if !items.exhaust() {
|
||||
panic!("Invalid form input.");
|
||||
}
|
||||
|
||||
result.ok()
|
||||
}
|
||||
|
||||
fn parse_strict<'f, T: FromForm<'f>>(string: &'f str) -> Option<T> {
|
||||
parse(string, true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn main() {
|
||||
let form_string = &[
|
||||
"single=100", "camelCase=helloThere", "TitleCase=HiHi", "type=-2",
|
||||
"DOUBLE=bing_bong", "a.b=123",
|
||||
].join("&");
|
||||
|
||||
let form: Option<Form> = parse_strict(&form_string);
|
||||
assert_eq!(form, Some(Form {
|
||||
single: 100,
|
||||
camel_case: "helloThere".into(),
|
||||
title_case: "HiHi".into(),
|
||||
field_type: -2,
|
||||
double: "bing_bong".into(),
|
||||
dot: 123,
|
||||
}));
|
||||
|
||||
let form_string = &[
|
||||
"single=100", "camel_case=helloThere", "TitleCase=HiHi", "type=-2",
|
||||
"DOUBLE=bing_bong", "dot=123",
|
||||
].join("&");
|
||||
|
||||
let form: Option<Form> = parse_strict(&form_string);
|
||||
assert!(form.is_none());
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
extern crate rocket;
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
|
|
|
@ -18,5 +18,15 @@ proc-macro = true
|
|||
|
||||
[dependencies]
|
||||
quote = "0.6.1"
|
||||
proc-macro2 = { version = "0.4.3", features = ["nightly"] }
|
||||
syn = { version = "0.14.0", features = ["full", "extra-traits"] }
|
||||
rocket_http = { version = "0.4.0-dev", path = "../http/" }
|
||||
|
||||
[dependencies.derive_utils]
|
||||
git = "https://github.com/SergioBenitez/derive-utils"
|
||||
rev = "160da392"
|
||||
|
||||
[dev-dependencies]
|
||||
rocket = { version = "0.4.0-dev", path = "../lib" }
|
||||
|
||||
[dev-dependencies.compiletest_rs]
|
||||
git = "https://github.com/SergioBenitez/compiletest-rs"
|
||||
branch = "regex-support"
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
use proc_macro::{Span, TokenStream};
|
||||
use derive_utils::{*, syn, ext::{TypeExt, Split3}};
|
||||
|
||||
#[derive(FromMeta)]
|
||||
struct Form {
|
||||
field: FormField,
|
||||
}
|
||||
|
||||
struct FormField {
|
||||
span: Span,
|
||||
name: String
|
||||
}
|
||||
|
||||
fn is_valid_field_name(s: &str) -> bool {
|
||||
// The HTML5 spec (4.10.18.1) says 'isindex' is not allowed.
|
||||
if s == "isindex" || s.is_empty() {
|
||||
return false
|
||||
}
|
||||
|
||||
// We allow all visible ASCII characters except '&', '=', and '?' since we
|
||||
// use those as control characters for parsing.
|
||||
s.chars().all(|c| (c >= ' ' && c <= '~') && c != '&' && c != '=' && c != '?')
|
||||
}
|
||||
|
||||
impl FromMeta for FormField {
|
||||
fn from_meta(meta: &syn::Meta) -> Result<Self> {
|
||||
let string = <SpanWrapped<String>>::from_meta(meta)?;
|
||||
if !is_valid_field_name(&string.value) {
|
||||
return Err(string.value_span.error("invalid form field name"));
|
||||
}
|
||||
|
||||
Ok(FormField { span: string.value_span, name: string.value })
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_struct(gen: &DeriveGenerator, data: Struct) -> Result<()> {
|
||||
if data.fields().is_empty() {
|
||||
return Err(gen.input.span().error("at least one field is required"));
|
||||
}
|
||||
|
||||
let mut names = ::std::collections::HashMap::new();
|
||||
for field in data.fields().iter() {
|
||||
let id = field.ident.as_ref().expect("named field");
|
||||
let field = match Form::from_attrs("form", &field.attrs) {
|
||||
Some(result) => result?.field,
|
||||
None => FormField { span: Spanned::span(&id), name: id.to_string() }
|
||||
};
|
||||
|
||||
if let Some(span) = names.get(&field.name) {
|
||||
return Err(field.span.error("duplicate field name")
|
||||
.span_note(*span, "previous definition here"));
|
||||
}
|
||||
|
||||
names.insert(field.name, field.span);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn derive_from_form(input: TokenStream) -> TokenStream {
|
||||
let form_error = quote!(::rocket::request::FormError);
|
||||
DeriveGenerator::build_for(input, "::rocket::request::FromForm<'__f>")
|
||||
.generic_support(GenericSupport::Lifetime | GenericSupport::Type)
|
||||
.replace_generic(0, 0)
|
||||
.data_support(DataSupport::NamedStruct)
|
||||
.map_type_generic(|_, ident, _| quote! {
|
||||
#ident : ::rocket::request::FromFormValue<'__f>
|
||||
})
|
||||
.validate_generics(|_, generics| match generics.lifetimes().count() > 1 {
|
||||
true => Err(generics.span().error("only one lifetime is supported")),
|
||||
false => Ok(())
|
||||
})
|
||||
.validate_struct(validate_struct)
|
||||
.function(|_, inner| quote! {
|
||||
type Error = ::rocket::request::FormError<'__f>;
|
||||
|
||||
fn from_form(
|
||||
__items: &mut ::rocket::request::FormItems<'__f>,
|
||||
__strict: bool,
|
||||
) -> ::std::result::Result<Self, Self::Error> {
|
||||
#inner
|
||||
}
|
||||
})
|
||||
.try_map_fields(move |_, fields| {
|
||||
let (constructors, matchers, builders) = fields.iter().map(|field| {
|
||||
let (ident, span) = (&field.ident, field.span().into());
|
||||
let default_name = ident.as_ref().expect("named").to_string();
|
||||
let name = Form::from_attrs("form", &field.attrs)
|
||||
.map(|result| result.map(|form| form.field.name))
|
||||
.unwrap_or_else(|| Ok(default_name))?;
|
||||
|
||||
let ty = field.ty.with_stripped_lifetimes();
|
||||
let ty = quote_spanned! {
|
||||
span => <#ty as ::rocket::request::FromFormValue>
|
||||
};
|
||||
|
||||
let constructor = quote_spanned!(span => let mut #ident = None;);
|
||||
|
||||
let matcher = quote_spanned! { span =>
|
||||
#name => { #ident = Some(#ty::from_form_value(__v)
|
||||
.map_err(|_| #form_error::BadValue(__k, __v))?); },
|
||||
};
|
||||
|
||||
let builder = quote_spanned! { span =>
|
||||
#ident: #ident.or_else(#ty::default)
|
||||
.ok_or_else(|| #form_error::Missing(#name.into()))?,
|
||||
};
|
||||
|
||||
Ok((constructor, matcher, builder))
|
||||
}).collect::<Result<Vec<_>>>()?.into_iter().split3();
|
||||
|
||||
Ok(quote! {
|
||||
#(#constructors)*
|
||||
|
||||
for (__k, __v) in __items {
|
||||
match __k.as_str() {
|
||||
#(#matchers)*
|
||||
_ if __strict && __k != "_method" => {
|
||||
return Err(#form_error::Unknown(__k, __v));
|
||||
}
|
||||
_ => { /* lenient or "method"; let it pass */ }
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { #(#builders)* })
|
||||
})
|
||||
})
|
||||
.to_tokens()
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
use derive_utils::*;
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
#[derive(FromMeta)]
|
||||
struct Form {
|
||||
value: String,
|
||||
}
|
||||
|
||||
pub fn derive_from_form_value(input: TokenStream) -> TokenStream {
|
||||
DeriveGenerator::build_for(input, "::rocket::request::FromFormValue<'__v>")
|
||||
.generic_support(GenericSupport::None)
|
||||
.data_support(DataSupport::Enum)
|
||||
.validate_enum(|generator, data| {
|
||||
// This derive only works for variants that are nullary.
|
||||
for variant in data.variants() {
|
||||
if !variant.fields().is_empty() {
|
||||
return Err(variant.span().error("variants cannot have fields"));
|
||||
}
|
||||
}
|
||||
|
||||
// Emit a warning if the enum is empty.
|
||||
if data.variants.is_empty() {
|
||||
generator.input.span().warning("deriving for empty enum").emit();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.function(|_, inner| quote! {
|
||||
type Error = &'__v ::rocket::http::RawStr;
|
||||
|
||||
fn from_form_value(
|
||||
value: &'__v ::rocket::http::RawStr
|
||||
) -> ::std::result::Result<Self, Self::Error> {
|
||||
let uncased = value.as_uncased_str();
|
||||
#inner
|
||||
::std::result::Result::Err(value)
|
||||
}
|
||||
})
|
||||
.try_map_enum(null_enum_mapper)
|
||||
.try_map_variant(|_, variant| {
|
||||
let variant_str = Form::from_attrs("form", &variant.attrs)
|
||||
.unwrap_or_else(|| Ok(Form { value: variant.ident.to_string() }))?
|
||||
.value;
|
||||
|
||||
let builder = variant.builder(|_| unreachable!());
|
||||
Ok(quote! {
|
||||
if uncased == #variant_str {
|
||||
return ::std::result::Result::Ok(#builder);
|
||||
}
|
||||
})
|
||||
})
|
||||
.to_tokens()
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
pub mod from_form;
|
||||
pub mod from_form_value;
|
||||
pub mod responder;
|
|
@ -0,0 +1,81 @@
|
|||
use quote::ToTokens;
|
||||
use proc_macro::TokenStream;
|
||||
use derive_utils::{*, ext::TypeExt};
|
||||
use derive_utils::proc_macro2::TokenStream as TokenStream2;
|
||||
|
||||
use http_codegen::{ContentType, Status};
|
||||
|
||||
#[derive(Default, FromMeta)]
|
||||
struct ItemAttr {
|
||||
content_type: Option<SpanWrapped<ContentType>>,
|
||||
status: Option<SpanWrapped<Status>>,
|
||||
}
|
||||
|
||||
#[derive(Default, FromMeta)]
|
||||
struct FieldAttr {
|
||||
ignore: bool,
|
||||
}
|
||||
|
||||
pub fn derive_responder(input: TokenStream) -> TokenStream {
|
||||
DeriveGenerator::build_for(input, "::rocket::response::Responder<'__r>")
|
||||
.generic_support(GenericSupport::Lifetime)
|
||||
.data_support(DataSupport::Struct | DataSupport::Enum)
|
||||
.replace_generic(0, 0)
|
||||
.validate_generics(|_, generics| match generics.lifetimes().count() > 1 {
|
||||
true => Err(generics.span().error("only one lifetime is supported")),
|
||||
false => Ok(())
|
||||
})
|
||||
.validate_fields(|_, fields| match fields.is_empty() {
|
||||
true => return Err(fields.span().error("need at least one field")),
|
||||
false => Ok(())
|
||||
})
|
||||
.function(|_, inner| quote! {
|
||||
fn respond_to(
|
||||
self,
|
||||
__req: &::rocket::Request
|
||||
) -> ::rocket::response::Result<'__r> {
|
||||
#inner
|
||||
}
|
||||
})
|
||||
.try_map_fields(|_, fields| {
|
||||
fn set_header_tokens<T: ToTokens + Spanned>(item: T) -> TokenStream2 {
|
||||
quote_spanned!(item.span().into() => __res.set_header(#item);)
|
||||
}
|
||||
|
||||
let attr = ItemAttr::from_attrs("response", fields.parent_attrs())
|
||||
.unwrap_or_else(|| Ok(Default::default()))?;
|
||||
|
||||
let responder = fields.iter().next().map(|f| {
|
||||
let (accessor, ty) = (f.accessor(), f.ty.with_stripped_lifetimes());
|
||||
quote_spanned! { f.span().into() =>
|
||||
let mut __res = <#ty as ::rocket::response::Responder>::respond_to(
|
||||
#accessor, __req
|
||||
)?;
|
||||
}
|
||||
}).expect("have at least one field");
|
||||
|
||||
let mut headers = vec![];
|
||||
for field in fields.iter().skip(1) {
|
||||
let attr = FieldAttr::from_attrs("response", &field.attrs)
|
||||
.unwrap_or_else(|| Ok(Default::default()))?;
|
||||
|
||||
if !attr.ignore {
|
||||
headers.push(set_header_tokens(field.accessor()));
|
||||
}
|
||||
}
|
||||
|
||||
let content_type = attr.content_type.map(set_header_tokens);
|
||||
let status = attr.status.map(|status| {
|
||||
quote_spanned!(status.span().into() => __res.set_status(#status);)
|
||||
});
|
||||
|
||||
Ok(quote! {
|
||||
#responder
|
||||
#(#headers)*
|
||||
#content_type
|
||||
#status
|
||||
Ok(__res)
|
||||
})
|
||||
})
|
||||
.to_tokens()
|
||||
}
|
|
@ -1,154 +0,0 @@
|
|||
use syn::*;
|
||||
|
||||
pub trait MemberExt {
|
||||
fn named(&self) -> Option<&Ident>;
|
||||
fn unnamed(&self) -> Option<&Index>;
|
||||
}
|
||||
|
||||
impl MemberExt for Member {
|
||||
fn named(&self) -> Option<&Ident> {
|
||||
match *self {
|
||||
Member::Named(ref named) => Some(named),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn unnamed(&self) -> Option<&Index> {
|
||||
match *self {
|
||||
Member::Unnamed(ref unnamed) => Some(unnamed),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FieldsExt {
|
||||
fn len(&self) -> usize;
|
||||
fn is_empty(&self) -> bool;
|
||||
fn named(&self) -> Option<&FieldsNamed>;
|
||||
fn is_named(&self) -> bool;
|
||||
fn unnamed(&self) -> Option<&FieldsUnnamed>;
|
||||
fn is_unnamed(&self) -> bool;
|
||||
fn is_unit(&self) -> bool;
|
||||
fn nth(&self, i: usize) -> Option<&Field>;
|
||||
fn find_member(&self, member: &Member) -> Option<&Field>;
|
||||
}
|
||||
|
||||
impl FieldsExt for Fields {
|
||||
fn len(&self) -> usize {
|
||||
match *self {
|
||||
Fields::Named(ref fields) => fields.named.len(),
|
||||
Fields::Unnamed(ref fields) => fields.unnamed.len(),
|
||||
Fields::Unit => 0
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
fn named(&self) -> Option<&FieldsNamed> {
|
||||
match *self {
|
||||
Fields::Named(ref named) => Some(named),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn is_named(&self) -> bool {
|
||||
self.named().is_some()
|
||||
}
|
||||
|
||||
fn unnamed(&self) -> Option<&FieldsUnnamed> {
|
||||
match *self {
|
||||
Fields::Unnamed(ref unnamed) => Some(unnamed),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn is_unnamed(&self) -> bool {
|
||||
self.unnamed().is_some()
|
||||
}
|
||||
|
||||
fn is_unit(&self) -> bool {
|
||||
match *self {
|
||||
Fields::Unit => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
fn nth(&self, i: usize) -> Option<&Field> {
|
||||
match *self {
|
||||
Fields::Named(ref fields) => fields.named.iter().nth(i),
|
||||
Fields::Unnamed(ref fields) => fields.unnamed.iter().nth(i),
|
||||
Fields::Unit => None
|
||||
}
|
||||
}
|
||||
|
||||
fn find_member(&self, member: &Member) -> Option<&Field> {
|
||||
if let (Some(fields), Some(ident)) = (self.named(), member.named()) {
|
||||
fields.named.iter().find(|f| f.ident.as_ref().unwrap() == ident)
|
||||
} else if let (Some(fields), Some(member)) = (self.unnamed(), member.unnamed()) {
|
||||
fields.unnamed.iter().nth(member.index as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PathExt {
|
||||
fn is(&self, global: bool, segments: &[&str]) -> bool;
|
||||
fn is_local(&self, segments: &[&str]) -> bool;
|
||||
fn is_global(&self, segments: &[&str]) -> bool;
|
||||
}
|
||||
|
||||
impl PathExt for Path {
|
||||
fn is(&self, global: bool, segments: &[&str]) -> bool {
|
||||
if self.global() != global || self.segments.len() != segments.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (segment, wanted) in self.segments.iter().zip(segments.iter()) {
|
||||
if segment.ident != wanted {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn is_local(&self, segments: &[&str]) -> bool {
|
||||
self.is(false, segments)
|
||||
}
|
||||
|
||||
fn is_global(&self, segments: &[&str]) -> bool {
|
||||
self.is(true, segments)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DataExt {
|
||||
fn into_enum(self) -> Option<DataEnum>;
|
||||
fn into_struct(self) -> Option<DataStruct>;
|
||||
fn into_union(self) -> Option<DataUnion>;
|
||||
}
|
||||
|
||||
impl DataExt for Data {
|
||||
fn into_enum(self) -> Option<DataEnum> {
|
||||
match self {
|
||||
Data::Enum(e) => Some(e),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn into_struct(self) -> Option<DataStruct> {
|
||||
match self {
|
||||
Data::Struct(s) => Some(s),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn into_union(self) -> Option<DataUnion> {
|
||||
match self {
|
||||
Data::Union(u) => Some(u),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
use syn;
|
||||
use quote::ToTokens;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use derive_utils::{SpanWrapped, FromMeta, Result, ext::Split2};
|
||||
use rocket_http as http;
|
||||
|
||||
pub struct ContentType(http::ContentType);
|
||||
|
||||
pub struct Status(http::Status);
|
||||
|
||||
struct MediaType(http::MediaType);
|
||||
|
||||
impl FromMeta for Status {
|
||||
fn from_meta(meta: &syn::Meta) -> Result<Self> {
|
||||
let num = <SpanWrapped<usize>>::from_meta(meta)?;
|
||||
if num.value < 100 || num.value >= 600 {
|
||||
return Err(num.value_span.error("status must be in range [100, 600)"));
|
||||
}
|
||||
|
||||
Ok(Status(http::Status::raw(num.value as u16)))
|
||||
}
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
impl FromMeta for ContentType {
|
||||
fn from_meta(meta: &syn::Meta) -> Result<Self> {
|
||||
let s = <SpanWrapped<String>>::from_meta(meta)?;
|
||||
let parsed = http::ContentType::parse_flexible(&s.value)
|
||||
.ok_or_else(|| s.value_span.error("invalid or unknown content-type"))?;
|
||||
|
||||
Ok(ContentType(parsed))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ContentType {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
// Yeah, yeah. (((((i))).kn0w()))
|
||||
let media_type = MediaType((self.0).clone().0);
|
||||
tokens.extend(quote!(::rocket::http::ContentType(#media_type)));
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for MediaType {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
use std::iter::repeat;
|
||||
let (top, sub) = (self.0.top().as_str(), self.0.sub().as_str());
|
||||
let (keys, values) = self.0.params().split2();
|
||||
|
||||
let (http, cow) = (quote!(::rocket::http), quote!(::std::borrow::Cow));
|
||||
let (http_, http__) = (repeat(&http), repeat(&http));
|
||||
let (cow_, cow__) = (repeat(&cow), repeat(&cow));
|
||||
|
||||
// TODO: Produce less code when possible (for known media types).
|
||||
tokens.extend(quote!(#http::MediaType {
|
||||
source: #http::Source::None,
|
||||
top: #http::Indexed::Concrete(#cow::Borrowed(#top)),
|
||||
sub: #http::Indexed::Concrete(#cow::Borrowed(#sub)),
|
||||
params: #http::MediaParams::Static(&[
|
||||
#((
|
||||
#http_::Indexed::Concrete(#cow_::Borrowed(#keys)),
|
||||
#http__::Indexed::Concrete(#cow__::Borrowed(#values))
|
||||
)),*
|
||||
])
|
||||
}))
|
||||
}
|
||||
}
|
|
@ -1,88 +1,30 @@
|
|||
#![feature(core_intrinsics, decl_macro)]
|
||||
#![feature(proc_macro_diagnostic, proc_macro_span)]
|
||||
#![recursion_limit="256"]
|
||||
#![feature(proc_macro_diagnostic)]
|
||||
#![feature(crate_visibility_modifier)]
|
||||
#![recursion_limit="128"]
|
||||
|
||||
extern crate syn;
|
||||
extern crate proc_macro;
|
||||
extern crate proc_macro2;
|
||||
#[macro_use] extern crate quote;
|
||||
#[macro_use] extern crate derive_utils;
|
||||
extern crate proc_macro;
|
||||
extern crate rocket_http;
|
||||
|
||||
mod parser;
|
||||
mod spanned;
|
||||
mod ext;
|
||||
mod derive;
|
||||
mod http_codegen;
|
||||
|
||||
use parser::Result as PResult;
|
||||
use proc_macro::{Span, TokenStream};
|
||||
use spanned::Spanned;
|
||||
crate use derive_utils::{syn, proc_macro2};
|
||||
|
||||
use ext::*;
|
||||
use syn::*;
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
const NO_FIELDS_ERR: &str = "variants in `FromFormValue` derives cannot have fields";
|
||||
const NO_GENERICS: &str = "enums with generics cannot derive `FromFormValue`";
|
||||
const ONLY_ENUMS: &str = "`FromFormValue` can only be derived for enums";
|
||||
const EMPTY_ENUM_WARN: &str = "deriving `FromFormValue` for empty enum";
|
||||
|
||||
fn validate_input(input: DeriveInput) -> PResult<DataEnum> {
|
||||
// This derive doesn't support generics. Error out if there are generics.
|
||||
if !input.generics.params.is_empty() {
|
||||
return Err(input.generics.span().error(NO_GENERICS));
|
||||
}
|
||||
|
||||
// This derive only works for enums. Error out if the input is not an enum.
|
||||
let input_span = input.span();
|
||||
let data = input.data.into_enum().ok_or_else(|| input_span.error(ONLY_ENUMS))?;
|
||||
|
||||
// This derive only works for variants that are nullary.
|
||||
for variant in data.variants.iter() {
|
||||
if !variant.fields.is_empty() {
|
||||
return Err(variant.span().error(NO_FIELDS_ERR));
|
||||
}
|
||||
}
|
||||
|
||||
// Emit a warning if the enum is empty.
|
||||
if data.variants.is_empty() {
|
||||
Span::call_site().warning(EMPTY_ENUM_WARN).emit();
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
fn real_derive_from_form_value(input: TokenStream) -> PResult<TokenStream> {
|
||||
// Parse the input `TokenStream` as a `syn::DeriveInput`, an AST.
|
||||
let input: DeriveInput = syn::parse(input).map_err(|e| {
|
||||
Span::call_site().error(format!("error: failed to parse input: {:?}", e))
|
||||
})?;
|
||||
|
||||
// Validate the enum.
|
||||
let name = input.ident.clone();
|
||||
let enum_data = validate_input(input)?;
|
||||
|
||||
// Create iterators over the identifers as idents and as strings.
|
||||
let variant_strs = enum_data.variants.iter().map(|v| v.ident.to_string());
|
||||
let variant_idents = enum_data.variants.iter().map(|v| &v.ident);
|
||||
let names = ::std::iter::repeat(&name);
|
||||
|
||||
// Generate the implementation.
|
||||
Ok(quote! {
|
||||
impl<'v> ::rocket::request::FromFormValue<'v> for #name {
|
||||
type Error = &'v ::rocket::http::RawStr;
|
||||
|
||||
fn from_form_value(v: &'v ::rocket::http::RawStr) -> ::std::result::Result<Self, Self::Error> {
|
||||
#(if v.as_uncased_str() == #variant_strs {
|
||||
return ::std::result::Result::Ok(#names::#variant_idents);
|
||||
})*
|
||||
|
||||
::std::result::Result::Err(v)
|
||||
}
|
||||
}
|
||||
}.into())
|
||||
}
|
||||
|
||||
#[proc_macro_derive(FromFormValue)]
|
||||
#[proc_macro_derive(FromFormValue, attributes(form))]
|
||||
pub fn derive_from_form_value(input: TokenStream) -> TokenStream {
|
||||
real_derive_from_form_value(input).unwrap_or_else(|diag| {
|
||||
diag.emit();
|
||||
TokenStream::new()
|
||||
})
|
||||
derive::from_form_value::derive_from_form_value(input)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(FromForm, attributes(form))]
|
||||
pub fn derive_from_form(input: TokenStream) -> TokenStream {
|
||||
derive::from_form::derive_from_form(input)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Responder, attributes(response))]
|
||||
pub fn derive_responder(input: TokenStream) -> TokenStream {
|
||||
derive::responder::derive_responder(input)
|
||||
}
|
||||
|
|
|
@ -1,126 +0,0 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use syn::token;
|
||||
use syn::synom::Synom;
|
||||
use syn::buffer::{Cursor, TokenBuffer};
|
||||
|
||||
use proc_macro::{TokenStream, Span, Diagnostic};
|
||||
|
||||
pub use proc_macro2::Delimiter;
|
||||
|
||||
pub type Result<T> = ::std::result::Result<T, Diagnostic>;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Seperator {
|
||||
Comma,
|
||||
Pipe,
|
||||
Semi,
|
||||
}
|
||||
|
||||
pub struct Parser {
|
||||
buffer: Box<TokenBuffer>,
|
||||
cursor: Cursor<'static>,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn new(tokens: TokenStream) -> Parser {
|
||||
let buffer = Box::new(TokenBuffer::new(tokens));
|
||||
// Our `Parser` is self-referential. We cast a pointer to the heap
|
||||
// allocation as `&'static` to allow the storage of the reference
|
||||
// along-side the allocation. This is safe as long as `buffer` is never
|
||||
// dropped while `self` lives, `buffer` is never mutated, and an
|
||||
// instance or reference to `cursor` is never allowed to escape. These
|
||||
// properties can be confirmed with a cursory look over the method
|
||||
// signatures and implementations of `Parser`.
|
||||
let cursor = unsafe {
|
||||
let buffer: &'static TokenBuffer = ::std::mem::transmute(&*buffer);
|
||||
buffer.begin()
|
||||
};
|
||||
|
||||
Parser { buffer, cursor }
|
||||
}
|
||||
|
||||
pub fn current_span(&self) -> Span {
|
||||
self.cursor.token_tree()
|
||||
.map(|_| self.cursor.span().unstable())
|
||||
.unwrap_or_else(Span::call_site)
|
||||
}
|
||||
|
||||
pub fn parse<T: Synom>(&mut self) -> Result<T> {
|
||||
let (val, cursor) = T::parse(self.cursor)
|
||||
.map_err(|e| {
|
||||
let expected = match T::description() {
|
||||
Some(desc) => desc,
|
||||
// We're just grabbing the type's name here. This is totally
|
||||
// unnecessary. There's nothing potentially memory-unsafe
|
||||
// about this. It's simply unsafe because it's an intrinsic.
|
||||
None => unsafe { ::std::intrinsics::type_name::<T>() }
|
||||
};
|
||||
|
||||
self.current_span().error(format!("{}: expected {}", e, expected))
|
||||
})?;
|
||||
|
||||
self.cursor = cursor;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
pub fn eat<T: Synom>(&mut self) -> bool {
|
||||
self.parse::<T>().is_ok()
|
||||
}
|
||||
|
||||
pub fn parse_group<F, T>(&mut self, delim: Delimiter, f: F) -> Result<T>
|
||||
where F: FnOnce(&mut Parser) -> Result<T>
|
||||
{
|
||||
if let Some((group_cursor, _, next_cursor)) = self.cursor.group(delim) {
|
||||
self.cursor = group_cursor;
|
||||
let result = f(self);
|
||||
self.cursor = next_cursor;
|
||||
result
|
||||
} else {
|
||||
let expected = match delim {
|
||||
Delimiter::Brace => "curly braced group",
|
||||
Delimiter::Bracket => "square bracketed group",
|
||||
Delimiter::Parenthesis => "parenthesized group",
|
||||
Delimiter::None => "invisible group"
|
||||
};
|
||||
|
||||
Err(self.current_span()
|
||||
.error(format!("parse error: expected {}", expected)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_sep<F, T>(&mut self, sep: Seperator, mut f: F) -> Result<Vec<T>>
|
||||
where F: FnMut(&mut Parser) -> Result<T>
|
||||
{
|
||||
let mut output = vec![];
|
||||
while !self.is_eof() {
|
||||
output.push(f(self)?);
|
||||
let have_sep = match sep {
|
||||
Seperator::Comma => self.eat::<token::Comma>(),
|
||||
Seperator::Pipe => self.eat::<token::Or>(),
|
||||
Seperator::Semi => self.eat::<token::Semi>(),
|
||||
};
|
||||
|
||||
if !have_sep {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
pub fn eof(&self) -> Result<()> {
|
||||
if !self.cursor.eof() {
|
||||
let diag = self.current_span()
|
||||
.error("trailing characters; expected eof");
|
||||
|
||||
return Err(diag);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_eof(&self) -> bool {
|
||||
self.eof().is_ok()
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
use proc_macro::Span;
|
||||
|
||||
use quote::ToTokens;
|
||||
|
||||
pub trait Spanned {
|
||||
fn span(&self) -> Span;
|
||||
}
|
||||
|
||||
// FIXME: Remove this once proc_macro's stabilize.
|
||||
impl<T: ToTokens> Spanned for T {
|
||||
fn span(&self) -> Span {
|
||||
let token_stream = self.into_token_stream();
|
||||
let mut iter = token_stream.into_iter();
|
||||
let mut span = match iter.next() {
|
||||
Some(tt) => tt.span().unstable(),
|
||||
None => {
|
||||
return Span::call_site();
|
||||
}
|
||||
};
|
||||
|
||||
for tt in iter {
|
||||
if let Some(joined) = span.join(tt.span().unstable()) {
|
||||
span = joined;
|
||||
}
|
||||
}
|
||||
|
||||
span
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
extern crate compiletest_rs as compiletest;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{io, fs::Metadata, time::SystemTime};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum Kind {
|
||||
Dynamic, Static
|
||||
}
|
||||
|
||||
impl Kind {
|
||||
fn extension(self) -> &'static str {
|
||||
match self {
|
||||
#[cfg(windows)] Kind::Dynamic => ".dll",
|
||||
#[cfg(all(unix, target_os = "macos"))] Kind::Dynamic => ".dylib",
|
||||
#[cfg(all(unix, not(target_os = "macos")))] Kind::Dynamic => ".so",
|
||||
Kind::Static => ".rlib"
|
||||
}
|
||||
}
|
||||
|
||||
fn prefix(self) -> &'static str {
|
||||
#[cfg(windows)] { "" }
|
||||
#[cfg(not(windows))] { "lib" }
|
||||
}
|
||||
}
|
||||
|
||||
fn target_path() -> PathBuf {
|
||||
#[cfg(debug_assertions)] const ENVIRONMENT: &str = "debug";
|
||||
#[cfg(not(debug_assertions))] const ENVIRONMENT: &str = "release";
|
||||
|
||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent().unwrap().parent().unwrap()
|
||||
.join("target")
|
||||
.join(ENVIRONMENT)
|
||||
}
|
||||
|
||||
fn link_flag(flag: &str, lib: &str, rel_path: &[&str]) -> String {
|
||||
let mut path = target_path();
|
||||
for component in rel_path {
|
||||
path = path.join(component);
|
||||
}
|
||||
|
||||
format!("{} {}={}", flag, lib, path.display())
|
||||
}
|
||||
|
||||
fn best_time_for(metadata: &Metadata) -> SystemTime {
|
||||
metadata.created()
|
||||
.or_else(|_| metadata.modified())
|
||||
.or_else(|_| metadata.accessed())
|
||||
.unwrap_or_else(|_| SystemTime::now())
|
||||
}
|
||||
|
||||
fn extern_dep(name: &str, kind: Kind) -> io::Result<String> {
|
||||
let deps_root = target_path().join("deps");
|
||||
let dep_name = format!("{}{}", kind.prefix(), name);
|
||||
|
||||
let mut dep_path: Option<PathBuf> = None;
|
||||
for entry in deps_root.read_dir().expect("read_dir call failed") {
|
||||
let entry = match entry {
|
||||
Ok(entry) => entry,
|
||||
Err(_) => continue
|
||||
};
|
||||
|
||||
let filename = entry.file_name();
|
||||
let filename = filename.to_string_lossy();
|
||||
let lib_name = filename.split('.').next().unwrap().split('-').next().unwrap();
|
||||
|
||||
if lib_name == dep_name && filename.ends_with(kind.extension()) {
|
||||
if let Some(ref mut existing) = dep_path {
|
||||
if best_time_for(&entry.metadata()?) > best_time_for(&existing.metadata()?) {
|
||||
*existing = entry.path().into();
|
||||
}
|
||||
} else {
|
||||
dep_path = Some(entry.path().into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let dep = dep_path.ok_or_else(|| io::Error::from(io::ErrorKind::NotFound))?;
|
||||
let filename = dep.file_name().ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))?;
|
||||
Ok(link_flag("--extern", name, &["deps", &filename.to_string_lossy()]))
|
||||
}
|
||||
|
||||
fn run_mode(mode: &'static str, path: &'static str) {
|
||||
let mut config = compiletest::Config::default();
|
||||
config.mode = mode.parse().expect("invalid mode");
|
||||
config.src_base = format!("tests/{}", path).into();
|
||||
config.clean_rmeta();
|
||||
|
||||
config.target_rustcflags = Some([
|
||||
link_flag("-L", "crate", &[]),
|
||||
link_flag("-L", "dependency", &["deps"]),
|
||||
extern_dep("rocket_codegen_next", Kind::Dynamic).expect("find codegen dep"),
|
||||
extern_dep("rocket_http", Kind::Static).expect("find http dep"),
|
||||
extern_dep("rocket", Kind::Static).expect("find core dep"),
|
||||
].join(" "));
|
||||
|
||||
compiletest::run_tests(&config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_test() {
|
||||
run_mode("ui", "ui-fail");
|
||||
run_mode("compile-fail", "ui-fail");
|
||||
}
|
|
@ -0,0 +1,319 @@
|
|||
#[macro_use] extern crate rocket;
|
||||
|
||||
use rocket::request::{FromForm, FormItems, FormError};
|
||||
use rocket::http::RawStr;
|
||||
|
||||
fn parse<'f, T>(string: &'f str, strict: bool) -> Result<T, FormError<'f>>
|
||||
where T: FromForm<'f, Error = FormError<'f>>
|
||||
{
|
||||
let mut items = FormItems::from(string);
|
||||
let result = T::from_form(items.by_ref(), strict);
|
||||
if !items.exhaust() {
|
||||
panic!("Invalid form input.");
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn strict<'f, T>(string: &'f str) -> Result<T, FormError<'f>>
|
||||
where T: FromForm<'f, Error = FormError<'f>>
|
||||
{
|
||||
parse(string, true)
|
||||
}
|
||||
|
||||
fn lenient<'f, T>(string: &'f str) -> Result<T, FormError<'f>>
|
||||
where T: FromForm<'f, Error = FormError<'f>>
|
||||
{
|
||||
parse(string, false)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct TodoTask {
|
||||
description: String,
|
||||
completed: bool
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
// Same number of arguments: simple case.
|
||||
let task: Option<TodoTask> = strict("description=Hello&completed=on").ok();
|
||||
assert_eq!(task, Some(TodoTask {
|
||||
description: "Hello".to_string(),
|
||||
completed: true
|
||||
}));
|
||||
|
||||
// Argument in string but not in form.
|
||||
let task: Option<TodoTask> = strict("other=a&description=Hello&completed=on").ok();
|
||||
assert!(task.is_none());
|
||||
|
||||
// Ensure _method isn't required.
|
||||
let task: Option<TodoTask> = strict("_method=patch&description=Hello&completed=off").ok();
|
||||
assert_eq!(task, Some(TodoTask {
|
||||
description: "Hello".to_string(),
|
||||
completed: false
|
||||
}));
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, FromFormValue)]
|
||||
enum FormOption {
|
||||
A, B, C
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct FormInput<'r> {
|
||||
checkbox: bool,
|
||||
number: usize,
|
||||
radio: FormOption,
|
||||
password: &'r RawStr,
|
||||
textarea: String,
|
||||
select: FormOption,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct DefaultInput<'r> {
|
||||
arg: Option<&'r RawStr>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct ManualMethod<'r> {
|
||||
_method: Option<&'r RawStr>,
|
||||
done: bool
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct UnpresentCheckbox {
|
||||
checkbox: bool
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct UnpresentCheckboxTwo<'r> {
|
||||
checkbox: bool,
|
||||
something: &'r RawStr
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct FieldNamedV<'r> {
|
||||
v: &'r RawStr,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base_conditions() {
|
||||
let form_string = &[
|
||||
"password=testing", "checkbox=off", "checkbox=on", "number=10",
|
||||
"checkbox=off", "textarea=", "select=a", "radio=c",
|
||||
].join("&");
|
||||
|
||||
let input: Option<FormInput> = strict(&form_string).ok();
|
||||
assert_eq!(input, Some(FormInput {
|
||||
checkbox: false,
|
||||
number: 10,
|
||||
radio: FormOption::C,
|
||||
password: "testing".into(),
|
||||
textarea: "".to_string(),
|
||||
select: FormOption::A,
|
||||
}));
|
||||
|
||||
// Argument not in string with default in form.
|
||||
let default: Option<DefaultInput> = strict("").ok();
|
||||
assert_eq!(default, Some(DefaultInput {
|
||||
arg: None
|
||||
}));
|
||||
|
||||
// Ensure _method can be captured if desired.
|
||||
let manual: Option<ManualMethod> = strict("_method=put&done=true").ok();
|
||||
assert_eq!(manual, Some(ManualMethod {
|
||||
_method: Some("put".into()),
|
||||
done: true
|
||||
}));
|
||||
|
||||
let manual: Option<ManualMethod> = lenient("_method=put&done=true").ok();
|
||||
assert_eq!(manual, Some(ManualMethod {
|
||||
_method: Some("put".into()),
|
||||
done: true
|
||||
}));
|
||||
|
||||
// And ignored when not present.
|
||||
let manual: Option<ManualMethod> = strict("done=true").ok();
|
||||
assert_eq!(manual, Some(ManualMethod {
|
||||
_method: None,
|
||||
done: true
|
||||
}));
|
||||
|
||||
// Check that a `bool` value that isn't in the form is marked as `false`.
|
||||
let manual: Option<UnpresentCheckbox> = strict("").ok();
|
||||
assert_eq!(manual, Some(UnpresentCheckbox {
|
||||
checkbox: false
|
||||
}));
|
||||
|
||||
// Check that a `bool` value that isn't in the form is marked as `false`.
|
||||
let manual: Option<UnpresentCheckboxTwo> = strict("something=hello").ok();
|
||||
assert_eq!(manual, Some(UnpresentCheckboxTwo {
|
||||
checkbox: false,
|
||||
something: "hello".into()
|
||||
}));
|
||||
|
||||
// Check that a structure with one field `v` parses correctly.
|
||||
let manual: Option<FieldNamedV> = strict("v=abc").ok();
|
||||
assert_eq!(manual, Some(FieldNamedV {
|
||||
v: "abc".into()
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lenient_parsing() {
|
||||
// Check that a structure with one field `v` parses correctly (lenient).
|
||||
let manual: Option<FieldNamedV> = lenient("v=abc").ok();
|
||||
assert_eq!(manual, Some(FieldNamedV { v: "abc".into() }));
|
||||
|
||||
let manual: Option<FieldNamedV> = lenient("v=abc&a=123").ok();
|
||||
assert_eq!(manual, Some(FieldNamedV { v: "abc".into() }));
|
||||
|
||||
let manual: Option<FieldNamedV> = lenient("c=abcddef&v=abc&a=123").ok();
|
||||
assert_eq!(manual, Some(FieldNamedV { v: "abc".into() }));
|
||||
|
||||
// Check default values (bool) with lenient parsing.
|
||||
let manual: Option<UnpresentCheckboxTwo> = lenient("something=hello").ok();
|
||||
assert_eq!(manual, Some(UnpresentCheckboxTwo {
|
||||
checkbox: false,
|
||||
something: "hello".into()
|
||||
}));
|
||||
|
||||
let manual: Option<UnpresentCheckboxTwo> = lenient("hi=hi&something=hello").ok();
|
||||
assert_eq!(manual, Some(UnpresentCheckboxTwo {
|
||||
checkbox: false,
|
||||
something: "hello".into()
|
||||
}));
|
||||
|
||||
// Check that a missing field doesn't parse, even leniently.
|
||||
let manual: Option<FieldNamedV> = lenient("a=abc").ok();
|
||||
assert!(manual.is_none());
|
||||
|
||||
let manual: Option<FieldNamedV> = lenient("_method=abc").ok();
|
||||
assert!(manual.is_none());
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct RenamedForm {
|
||||
single: usize,
|
||||
#[form(field = "camelCase")]
|
||||
camel_case: String,
|
||||
#[form(field = "TitleCase")]
|
||||
title_case: String,
|
||||
#[form(field = "type")]
|
||||
field_type: isize,
|
||||
#[form(field = "DOUBLE")]
|
||||
double: String,
|
||||
#[form(field = "a.b")]
|
||||
dot: isize,
|
||||
#[form(field = "some space")]
|
||||
some_space: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn field_renaming() {
|
||||
let form_string = &[
|
||||
"single=100", "camelCase=helloThere", "TitleCase=HiHi", "type=-2",
|
||||
"DOUBLE=bing_bong", "a.b=123", "some space=okay"
|
||||
].join("&");
|
||||
|
||||
let form: Option<RenamedForm> = strict(&form_string).ok();
|
||||
assert_eq!(form, Some(RenamedForm {
|
||||
single: 100,
|
||||
camel_case: "helloThere".into(),
|
||||
title_case: "HiHi".into(),
|
||||
field_type: -2,
|
||||
double: "bing_bong".into(),
|
||||
dot: 123,
|
||||
some_space: "okay".into(),
|
||||
}));
|
||||
|
||||
let form_string = &[
|
||||
"single=100", "camel_case=helloThere", "TitleCase=HiHi", "type=-2",
|
||||
"DOUBLE=bing_bong", "dot=123", "some_space=okay"
|
||||
].join("&");
|
||||
|
||||
let form: Option<RenamedForm> = strict(&form_string).ok();
|
||||
assert!(form.is_none());
|
||||
}
|
||||
|
||||
#[derive(FromForm, Debug, PartialEq)]
|
||||
struct YetOneMore<'f, T> {
|
||||
string: &'f RawStr,
|
||||
other: T,
|
||||
}
|
||||
|
||||
#[derive(FromForm, Debug, PartialEq)]
|
||||
struct Oops<A, B, C> {
|
||||
base: String,
|
||||
a: A,
|
||||
b: B,
|
||||
c: C,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generics() {
|
||||
let form_string = &[
|
||||
"string=hello", "other=00128"
|
||||
].join("&");
|
||||
|
||||
let form: Option<YetOneMore<usize>> = strict(&form_string).ok();
|
||||
assert_eq!(form, Some(YetOneMore {
|
||||
string: "hello".into(),
|
||||
other: 128,
|
||||
}));
|
||||
|
||||
let form: Option<YetOneMore<u8>> = strict(&form_string).ok();
|
||||
assert_eq!(form, Some(YetOneMore {
|
||||
string: "hello".into(),
|
||||
other: 128,
|
||||
}));
|
||||
|
||||
let form: Option<YetOneMore<i8>> = strict(&form_string).ok();
|
||||
assert!(form.is_none());
|
||||
|
||||
let form_string = &[
|
||||
"base=just%20a%20test", "a=hey%20there", "b=a", "c=811",
|
||||
].join("&");
|
||||
|
||||
let form: Option<Oops<&RawStr, FormOption, usize>> = strict(&form_string).ok();
|
||||
assert_eq!(form, Some(Oops {
|
||||
base: "just a test".into(),
|
||||
a: "hey%20there".into(),
|
||||
b: FormOption::A,
|
||||
c: 811,
|
||||
}));
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, FromForm)]
|
||||
struct WhoopsForm {
|
||||
complete: bool,
|
||||
other: usize,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn form_errors() {
|
||||
let form: Result<WhoopsForm, _> = strict("complete=true&other=781");
|
||||
assert_eq!(form, Ok(WhoopsForm { complete: true, other: 781 }));
|
||||
|
||||
let form: Result<WhoopsForm, _> = strict("complete=true&other=unknown");
|
||||
assert_eq!(form, Err(FormError::BadValue("other".into(), "unknown".into())));
|
||||
|
||||
let form: Result<WhoopsForm, _> = strict("complete=unknown&other=unknown");
|
||||
assert_eq!(form, Err(FormError::BadValue("complete".into(), "unknown".into())));
|
||||
|
||||
let form: Result<WhoopsForm, _> = strict("complete=true&other=1&extra=foo");
|
||||
assert_eq!(form, Err(FormError::Unknown("extra".into(), "foo".into())));
|
||||
|
||||
// Bad values take highest precedence.
|
||||
let form: Result<WhoopsForm, _> = strict("complete=unknown&unknown=foo");
|
||||
assert_eq!(form, Err(FormError::BadValue("complete".into(), "unknown".into())));
|
||||
|
||||
// Then unknown key/values for strict parses.
|
||||
let form: Result<WhoopsForm, _> = strict("complete=true&unknown=foo");
|
||||
assert_eq!(form, Err(FormError::Unknown("unknown".into(), "foo".into())));
|
||||
|
||||
// Finally, missing.
|
||||
let form: Result<WhoopsForm, _> = strict("complete=true");
|
||||
assert_eq!(form, Err(FormError::Missing("other".into())));
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
#[macro_use] extern crate rocket;
|
||||
|
||||
use rocket::request::FromFormValue;
|
||||
|
||||
macro_rules! assert_parse {
|
||||
($($string:expr),* => $item:ident :: $variant:ident) => ($(
|
||||
match $item::from_form_value($string.into()) {
|
||||
Ok($item::$variant) => { /* okay */ },
|
||||
Ok(item) => panic!("Failed to parse {} as {:?}. Got {:?} instead.",
|
||||
$string, $item::$variant, item),
|
||||
Err(e) => panic!("Failed to parse {} as {}: {:?}",
|
||||
$string, stringify!($item), e),
|
||||
|
||||
}
|
||||
)*)
|
||||
}
|
||||
|
||||
macro_rules! assert_no_parse {
|
||||
($($string:expr),* => $item:ident) => ($(
|
||||
match $item::from_form_value($string.into()) {
|
||||
Err(_) => { /* okay */ },
|
||||
Ok(item) => panic!("Unexpectedly parsed {} as {:?}", $string, item)
|
||||
}
|
||||
)*)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_form_value_simple() {
|
||||
#[derive(Debug, FromFormValue)]
|
||||
enum Foo { A, B, C, }
|
||||
|
||||
assert_parse!("a", "A" => Foo::A);
|
||||
assert_parse!("b", "B" => Foo::B);
|
||||
assert_parse!("c", "C" => Foo::C);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_form_value_weirder() {
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug, FromFormValue)]
|
||||
enum Foo { Ab_Cd, OtherA }
|
||||
|
||||
assert_parse!("ab_cd", "ab_CD", "Ab_CD" => Foo::Ab_Cd);
|
||||
assert_parse!("othera", "OTHERA", "otherA", "OtherA" => Foo::OtherA);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_form_value_no_parse() {
|
||||
#[derive(Debug, FromFormValue)]
|
||||
enum Foo { A, B, C, }
|
||||
|
||||
assert_no_parse!("abc", "ab", "bc", "ca" => Foo);
|
||||
assert_no_parse!("b ", "a ", "c ", "a b" => Foo);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_form_value_renames() {
|
||||
#[derive(Debug, FromFormValue)]
|
||||
enum Foo {
|
||||
#[form(value = "foo")]
|
||||
Bar,
|
||||
#[form(value = ":book")]
|
||||
Book
|
||||
}
|
||||
|
||||
assert_parse!("foo", "FOO", "FoO" => Foo::Bar);
|
||||
assert_parse!(":book", ":BOOK", ":bOOk", ":booK" => Foo::Book);
|
||||
assert_no_parse!("book", "bar" => Foo);
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
#![feature(attr_literals)]
|
||||
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
use rocket::local::Client;
|
||||
use rocket::response::Responder;
|
||||
use rocket::http::{Status, ContentType, Cookie};
|
||||
|
||||
#[derive(Responder)]
|
||||
pub enum Foo<'r> {
|
||||
First(String),
|
||||
#[response(status = 500)]
|
||||
Second(Vec<u8>),
|
||||
#[response(status = 404, content_type = "html")]
|
||||
Third {
|
||||
responder: &'r str,
|
||||
ct: ::rocket::http::ContentType,
|
||||
},
|
||||
#[response(status = 105)]
|
||||
Fourth {
|
||||
string: &'r str,
|
||||
ct: ::rocket::http::ContentType,
|
||||
},
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn responder_foo() {
|
||||
let client = Client::new(rocket::ignite()).expect("valid rocket");
|
||||
let local_req = client.get("/");
|
||||
let req = local_req.inner();
|
||||
|
||||
let mut response = Foo::First("hello".into())
|
||||
.respond_to(req)
|
||||
.expect("response okay");
|
||||
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
assert_eq!(response.content_type(), Some(ContentType::Plain));
|
||||
assert_eq!(response.body_string(), Some("hello".into()));
|
||||
|
||||
let mut response = Foo::Second("just a test".into())
|
||||
.respond_to(req)
|
||||
.expect("response okay");
|
||||
|
||||
assert_eq!(response.status(), Status::InternalServerError);
|
||||
assert_eq!(response.content_type(), Some(ContentType::Binary));
|
||||
assert_eq!(response.body_string(), Some("just a test".into()));
|
||||
|
||||
let mut response = Foo::Third { responder: "well, hi", ct: ContentType::JSON }
|
||||
.respond_to(req)
|
||||
.expect("response okay");
|
||||
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
assert_eq!(response.content_type(), Some(ContentType::HTML));
|
||||
assert_eq!(response.body_string(), Some("well, hi".into()));
|
||||
|
||||
let mut response = Foo::Fourth { string: "goodbye", ct: ContentType::JSON }
|
||||
.respond_to(req)
|
||||
.expect("response okay");
|
||||
|
||||
assert_eq!(response.status(), Status::raw(105));
|
||||
assert_eq!(response.content_type(), Some(ContentType::JSON));
|
||||
assert_eq!(response.body_string(), Some("goodbye".into()));
|
||||
}
|
||||
|
||||
#[derive(Responder)]
|
||||
#[response(content_type = "plain")]
|
||||
pub struct Bar<'r> {
|
||||
responder: Foo<'r>,
|
||||
other: ContentType,
|
||||
third: Cookie<'static>,
|
||||
#[response(ignore)]
|
||||
_yet_another: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn responder_bar() {
|
||||
let client = Client::new(rocket::ignite()).expect("valid rocket");
|
||||
let local_req = client.get("/");
|
||||
let req = local_req.inner();
|
||||
|
||||
let mut response = Bar {
|
||||
responder: Foo::Second("foo foo".into()),
|
||||
other: ContentType::HTML,
|
||||
third: Cookie::new("cookie", "here!"),
|
||||
_yet_another: "uh..hi?".into()
|
||||
}.respond_to(req).expect("response okay");
|
||||
|
||||
assert_eq!(response.status(), Status::InternalServerError);
|
||||
assert_eq!(response.content_type(), Some(ContentType::Plain));
|
||||
assert_eq!(response.body_string(), Some("foo foo".into()));
|
||||
assert_eq!(response.headers().get_one("Set-Cookie"), Some("cookie=here!"));
|
||||
}
|
||||
|
||||
#[derive(Responder)]
|
||||
#[response(content_type = "application/x-custom")]
|
||||
pub struct Baz {
|
||||
responder: &'static str,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn responder_baz() {
|
||||
let client = Client::new(rocket::ignite()).expect("valid rocket");
|
||||
let local_req = client.get("/");
|
||||
let req = local_req.inner();
|
||||
|
||||
let mut response = Baz { responder: "just a custom" }
|
||||
.respond_to(req)
|
||||
.expect("response okay");
|
||||
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
assert_eq!(response.content_type(), Some(ContentType::new("application", "x-custom")));
|
||||
assert_eq!(response.body_string(), Some("just a custom".into()));
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
#[macro_use] extern crate rocket;
|
||||
|
||||
#[derive(FromForm)]
|
||||
enum Thing { }
|
||||
//~^ ERROR not supported
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct Foo1;
|
||||
//~^ ERROR not supported
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct Foo2 { }
|
||||
//~^ ERROR one field is required
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct Foo3(usize);
|
||||
//~^ ERROR not supported
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct NextTodoTask<'f, 'a> {
|
||||
//~^ ERROR only one lifetime
|
||||
description: String,
|
||||
raw_description: &'f RawStr,
|
||||
other: &'a RawStr,
|
||||
completed: bool,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct BadName1 {
|
||||
#[form(field = "isindex")]
|
||||
//~^ ERROR invalid form field name
|
||||
field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct Demo2 {
|
||||
#[form(field = "foo")]
|
||||
field: String,
|
||||
foo: usize,
|
||||
//~^ ERROR duplicate field
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm9 {
|
||||
#[form(field = "hello")]
|
||||
first: String,
|
||||
#[form(field = "hello")]
|
||||
//~^ ERROR duplicate field
|
||||
other: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm10 {
|
||||
first: String,
|
||||
#[form(field = "first")]
|
||||
//~^ ERROR duplicate field
|
||||
other: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm {
|
||||
#[form(field = "blah", field = "bloo")]
|
||||
//~^ ERROR duplicate
|
||||
my_field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm1 {
|
||||
#[form]
|
||||
//~^ ERROR malformed attribute
|
||||
my_field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm2 {
|
||||
#[form("blah")]
|
||||
//~^ ERROR unexpected literal
|
||||
my_field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm3 {
|
||||
#[form(123)]
|
||||
//~^ ERROR unexpected literal
|
||||
my_field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm4 {
|
||||
#[form(beep = "bop")]
|
||||
//~^ ERROR unexpected attribute parameter
|
||||
my_field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm5 {
|
||||
#[form(field = "blah")]
|
||||
#[form(field = "bleh")]
|
||||
//~^ ERROR duplicate
|
||||
my_field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm6 {
|
||||
#[form(field = true)]
|
||||
//~^ ERROR invalid value: expected string
|
||||
my_field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm7 {
|
||||
#[form(field)]
|
||||
//~^ ERROR malformed parameter
|
||||
my_field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm8 {
|
||||
#[form(field = 123)]
|
||||
//~^ ERROR invalid value: expected string
|
||||
my_field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm11 {
|
||||
#[form(field = "hello&world")]
|
||||
//~^ ERROR invalid form field name
|
||||
first: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm12 {
|
||||
#[form(field = "!@#$%^&*()_")]
|
||||
//~^ ERROR invalid form field name
|
||||
first: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm13 {
|
||||
#[form(field = "?")]
|
||||
//~^ ERROR invalid form field name
|
||||
first: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct MyForm14 {
|
||||
#[form(field = "")]
|
||||
//~^ ERROR invalid form field name
|
||||
first: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct BadName2 {
|
||||
#[form(field = "a&b")]
|
||||
//~^ ERROR invalid form field name
|
||||
field: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct BadName3 {
|
||||
#[form(field = "a=")]
|
||||
//~^ ERROR invalid form field name
|
||||
field: String,
|
||||
}
|
|
@ -0,0 +1,306 @@
|
|||
error: enums are not supported
|
||||
--> $DIR/from_form.rs:4:1
|
||||
|
|
||||
4 | enum Thing { }
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:3:10
|
||||
|
|
||||
3 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: tuple structs are not supported
|
||||
--> $DIR/from_form.rs:8:1
|
||||
|
|
||||
8 | struct Foo1;
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:7:10
|
||||
|
|
||||
7 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: at least one field is required
|
||||
--> $DIR/from_form.rs:12:1
|
||||
|
|
||||
12 | struct Foo2 { }
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:11:10
|
||||
|
|
||||
11 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: tuple structs are not supported
|
||||
--> $DIR/from_form.rs:16:1
|
||||
|
|
||||
16 | struct Foo3(usize);
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:15:10
|
||||
|
|
||||
15 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: only one lifetime is supported
|
||||
--> $DIR/from_form.rs:20:20
|
||||
|
|
||||
20 | struct NextTodoTask<'f, 'a> {
|
||||
| ^^^^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:19:10
|
||||
|
|
||||
19 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: invalid form field name
|
||||
--> $DIR/from_form.rs:30:20
|
||||
|
|
||||
30 | #[form(field = "isindex")]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:28:10
|
||||
|
|
||||
28 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: duplicate field name
|
||||
--> $DIR/from_form.rs:39:5
|
||||
|
|
||||
39 | foo: usize,
|
||||
| ^^^
|
||||
|
|
||||
note: previous definition here
|
||||
--> $DIR/from_form.rs:37:20
|
||||
|
|
||||
37 | #[form(field = "foo")]
|
||||
| ^^^^^
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:35:10
|
||||
|
|
||||
35 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: duplicate field name
|
||||
--> $DIR/from_form.rs:47:20
|
||||
|
|
||||
47 | #[form(field = "hello")]
|
||||
| ^^^^^^^
|
||||
|
|
||||
note: previous definition here
|
||||
--> $DIR/from_form.rs:45:20
|
||||
|
|
||||
45 | #[form(field = "hello")]
|
||||
| ^^^^^^^
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:43:10
|
||||
|
|
||||
43 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: duplicate field name
|
||||
--> $DIR/from_form.rs:55:20
|
||||
|
|
||||
55 | #[form(field = "first")]
|
||||
| ^^^^^^^
|
||||
|
|
||||
note: previous definition here
|
||||
--> $DIR/from_form.rs:54:5
|
||||
|
|
||||
54 | first: String,
|
||||
| ^^^^^
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:52:10
|
||||
|
|
||||
52 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: duplicate attribute parameter: field
|
||||
--> $DIR/from_form.rs:62:28
|
||||
|
|
||||
62 | #[form(field = "blah", field = "bloo")]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:60:10
|
||||
|
|
||||
60 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: malformed attribute
|
||||
--> $DIR/from_form.rs:69:7
|
||||
|
|
||||
69 | #[form]
|
||||
| ^^^^
|
||||
|
|
||||
= help: expected syntax: #[attr(key = value, ..)]
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:67:10
|
||||
|
|
||||
67 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: unexpected literal
|
||||
--> $DIR/from_form.rs:76:12
|
||||
|
|
||||
76 | #[form("blah")]
|
||||
| ^^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:74:10
|
||||
|
|
||||
74 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: unexpected literal
|
||||
--> $DIR/from_form.rs:83:12
|
||||
|
|
||||
83 | #[form(123)]
|
||||
| ^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:81:10
|
||||
|
|
||||
81 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: unexpected attribute parameter: beep
|
||||
--> $DIR/from_form.rs:90:12
|
||||
|
|
||||
90 | #[form(beep = "bop")]
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:88:10
|
||||
|
|
||||
88 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: duplicate invocation of `form` attribute
|
||||
--> $DIR/from_form.rs:98:5
|
||||
|
|
||||
98 | #[form(field = "bleh")]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:95:10
|
||||
|
|
||||
95 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: invalid value: expected string
|
||||
--> $DIR/from_form.rs:105:20
|
||||
|
|
||||
105 | #[form(field = true)]
|
||||
| ^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:103:10
|
||||
|
|
||||
103 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: malformed parameter: expected key/value pair
|
||||
--> $DIR/from_form.rs:112:12
|
||||
|
|
||||
112 | #[form(field)]
|
||||
| ^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:110:10
|
||||
|
|
||||
110 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: invalid value: expected string
|
||||
--> $DIR/from_form.rs:119:20
|
||||
|
|
||||
119 | #[form(field = 123)]
|
||||
| ^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:117:10
|
||||
|
|
||||
117 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: invalid form field name
|
||||
--> $DIR/from_form.rs:126:20
|
||||
|
|
||||
126 | #[form(field = "hello&world")]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:124:10
|
||||
|
|
||||
124 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: invalid form field name
|
||||
--> $DIR/from_form.rs:133:20
|
||||
|
|
||||
133 | #[form(field = "!@#$%^&*()_")]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:131:10
|
||||
|
|
||||
131 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: invalid form field name
|
||||
--> $DIR/from_form.rs:140:20
|
||||
|
|
||||
140 | #[form(field = "?")]
|
||||
| ^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:138:10
|
||||
|
|
||||
138 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: invalid form field name
|
||||
--> $DIR/from_form.rs:147:20
|
||||
|
|
||||
147 | #[form(field = "")]
|
||||
| ^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:145:10
|
||||
|
|
||||
145 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: invalid form field name
|
||||
--> $DIR/from_form.rs:154:20
|
||||
|
|
||||
154 | #[form(field = "a&b")]
|
||||
| ^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:152:10
|
||||
|
|
||||
152 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: invalid form field name
|
||||
--> $DIR/from_form.rs:161:20
|
||||
|
|
||||
161 | #[form(field = "a=")]
|
||||
| ^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromForm`
|
||||
--> $DIR/from_form.rs:159:10
|
||||
|
|
||||
159 | #[derive(FromForm)]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: aborting due to 24 previous errors
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#[macro_use] extern crate rocket;
|
||||
|
||||
struct Unknown;
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct BadType3 {
|
||||
field: Unknown,
|
||||
//~^ rocket::request::FromFormValue
|
||||
}
|
||||
|
||||
struct Foo<T>(T);
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct Other {
|
||||
field: Foo<usize>,
|
||||
//~^ rocket::request::FromFormValue
|
||||
}
|
||||
|
||||
fn main() { }
|
|
@ -0,0 +1,15 @@
|
|||
error[E0277]: the trait bound `Unknown: rocket::request::FromFormValue<'_>` is not satisfied
|
||||
--> $DIR/from_form_type_errors.rs:7:5
|
||||
|
|
||||
7 | field: Unknown,
|
||||
| ^^^^^^^^^^^^^^ the trait `rocket::request::FromFormValue<'_>` is not implemented for `Unknown`
|
||||
|
||||
error[E0277]: the trait bound `Foo<usize>: rocket::request::FromFormValue<'_>` is not satisfied
|
||||
--> $DIR/from_form_type_errors.rs:15:5
|
||||
|
|
||||
15 | field: Foo<usize>,
|
||||
| ^^^^^^^^^^^^^^^^^ the trait `rocket::request::FromFormValue<'_>` is not implemented for `Foo<usize>`
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0277`.
|
|
@ -0,0 +1,45 @@
|
|||
#[macro_use] extern crate rocket;
|
||||
|
||||
#[derive(FromFormValue)]
|
||||
struct Foo1;
|
||||
//~^ ERROR not supported
|
||||
|
||||
#[derive(FromFormValue)]
|
||||
struct Foo2(usize);
|
||||
//~^ ERROR not supported
|
||||
|
||||
#[derive(FromFormValue)]
|
||||
struct Foo3 {
|
||||
//~^ ERROR not supported
|
||||
foo: usize,
|
||||
}
|
||||
|
||||
#[derive(FromFormValue)]
|
||||
enum Foo4 {
|
||||
A(usize),
|
||||
//~^ ERROR cannot have fields
|
||||
}
|
||||
|
||||
#[derive(FromFormValue)]
|
||||
enum Foo5 { }
|
||||
//~^ WARNING empty enum
|
||||
|
||||
#[derive(FromFormValue)]
|
||||
enum Foo6<T> {
|
||||
//~^ ERROR type generics are not supported
|
||||
A(T),
|
||||
}
|
||||
|
||||
#[derive(FromFormValue)]
|
||||
enum Bar1 {
|
||||
#[form(value = 123)]
|
||||
//~^ ERROR invalid value: expected string
|
||||
A,
|
||||
}
|
||||
|
||||
#[derive(FromFormValue)]
|
||||
enum Bar2 {
|
||||
#[form(value)]
|
||||
//~^ ERROR malformed parameter
|
||||
A,
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
error: tuple structs are not supported
|
||||
--> $DIR/from_form_value.rs:4:1
|
||||
|
|
||||
4 | struct Foo1;
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromFormValue`
|
||||
--> $DIR/from_form_value.rs:3:10
|
||||
|
|
||||
3 | #[derive(FromFormValue)]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: tuple structs are not supported
|
||||
--> $DIR/from_form_value.rs:8:1
|
||||
|
|
||||
8 | struct Foo2(usize);
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromFormValue`
|
||||
--> $DIR/from_form_value.rs:7:10
|
||||
|
|
||||
7 | #[derive(FromFormValue)]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: named structs are not supported
|
||||
--> $DIR/from_form_value.rs:12:1
|
||||
|
|
||||
12 | / struct Foo3 {
|
||||
13 | | //~^ ERROR not supported
|
||||
14 | | foo: usize,
|
||||
15 | | }
|
||||
| |_^
|
||||
|
|
||||
note: error occurred while deriving `FromFormValue`
|
||||
--> $DIR/from_form_value.rs:11:10
|
||||
|
|
||||
11 | #[derive(FromFormValue)]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: variants cannot have fields
|
||||
--> $DIR/from_form_value.rs:19:5
|
||||
|
|
||||
19 | A(usize),
|
||||
| ^^^^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromFormValue`
|
||||
--> $DIR/from_form_value.rs:17:10
|
||||
|
|
||||
17 | #[derive(FromFormValue)]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: deriving for empty enum
|
||||
--> $DIR/from_form_value.rs:24:1
|
||||
|
|
||||
24 | enum Foo5 { }
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: type generics are not supported
|
||||
--> $DIR/from_form_value.rs:28:11
|
||||
|
|
||||
28 | enum Foo6<T> {
|
||||
| ^
|
||||
|
|
||||
note: error occurred while deriving `FromFormValue`
|
||||
--> $DIR/from_form_value.rs:27:10
|
||||
|
|
||||
27 | #[derive(FromFormValue)]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: invalid value: expected string
|
||||
--> $DIR/from_form_value.rs:35:20
|
||||
|
|
||||
35 | #[form(value = 123)]
|
||||
| ^^^
|
||||
|
|
||||
note: error occurred while deriving `FromFormValue`
|
||||
--> $DIR/from_form_value.rs:33:10
|
||||
|
|
||||
33 | #[derive(FromFormValue)]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: malformed parameter: expected key/value pair
|
||||
--> $DIR/from_form_value.rs:42:12
|
||||
|
|
||||
42 | #[form(value)]
|
||||
| ^^^^^
|
||||
|
|
||||
note: error occurred while deriving `FromFormValue`
|
||||
--> $DIR/from_form_value.rs:40:10
|
||||
|
|
||||
40 | #[derive(FromFormValue)]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: aborting due to 7 previous errors
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// normalize-stderr-test: "<(.*) as (.*)>" -> "$1 as $$TRAIT"
|
||||
// normalize-stderr-test: "and \d+ others" -> "and $$N others"
|
||||
|
||||
#![feature(attr_literals)]
|
||||
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
#[derive(Responder)]
|
||||
struct Thing1 {
|
||||
thing: u8,
|
||||
//~^ ERROR Responder
|
||||
}
|
||||
|
||||
#[derive(Responder)]
|
||||
struct Thing2 {
|
||||
thing: String,
|
||||
other: u8,
|
||||
//~^ ERROR Header
|
||||
}
|
||||
|
||||
#[derive(Responder)]
|
||||
struct Thing3 {
|
||||
thing: u8,
|
||||
//~^ ERROR Responder
|
||||
other: u8,
|
||||
//~^ ERROR Header
|
||||
}
|
||||
|
||||
#[derive(Responder)]
|
||||
struct Thing4 {
|
||||
thing: String,
|
||||
other: ::rocket::http::ContentType,
|
||||
then: String,
|
||||
//~^ ERROR Header
|
||||
}
|
||||
|
||||
fn main() { }
|
|
@ -0,0 +1,61 @@
|
|||
error[E0277]: the trait bound `u8: rocket::response::Responder<'_>` is not satisfied
|
||||
--> $DIR/responder-types.rs:10:5
|
||||
|
|
||||
10 | thing: u8,
|
||||
| ^^^^^^^^^ the trait `rocket::response::Responder<'_>` is not implemented for `u8`
|
||||
|
|
||||
= note: required by `rocket::response::Responder::respond_to`
|
||||
|
||||
error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From<u8>` is not satisfied
|
||||
--> $DIR/responder-types.rs:17:5
|
||||
|
|
||||
17 | other: u8,
|
||||
| ^^^^^^^^^ the trait `std::convert::From<u8>` is not implemented for `rocket::http::Header<'_>`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
rocket::http::Header<'static> as $TRAIT
|
||||
rocket::http::Header<'static> as $TRAIT
|
||||
rocket::http::Header<'static> as $TRAIT
|
||||
rocket::http::Header<'static> as $TRAIT
|
||||
and $N others
|
||||
= note: required because of the requirements on the impl of `std::convert::Into<rocket::http::Header<'_>>` for `u8`
|
||||
|
||||
error[E0277]: the trait bound `u8: rocket::response::Responder<'_>` is not satisfied
|
||||
--> $DIR/responder-types.rs:23:5
|
||||
|
|
||||
23 | thing: u8,
|
||||
| ^^^^^^^^^ the trait `rocket::response::Responder<'_>` is not implemented for `u8`
|
||||
|
|
||||
= note: required by `rocket::response::Responder::respond_to`
|
||||
|
||||
error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From<u8>` is not satisfied
|
||||
--> $DIR/responder-types.rs:25:5
|
||||
|
|
||||
25 | other: u8,
|
||||
| ^^^^^^^^^ the trait `std::convert::From<u8>` is not implemented for `rocket::http::Header<'_>`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
rocket::http::Header<'static> as $TRAIT
|
||||
rocket::http::Header<'static> as $TRAIT
|
||||
rocket::http::Header<'static> as $TRAIT
|
||||
rocket::http::Header<'static> as $TRAIT
|
||||
and $N others
|
||||
= note: required because of the requirements on the impl of `std::convert::Into<rocket::http::Header<'_>>` for `u8`
|
||||
|
||||
error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From<std::string::String>` is not satisfied
|
||||
--> $DIR/responder-types.rs:33:5
|
||||
|
|
||||
33 | then: String,
|
||||
| ^^^^^^^^^^^^ the trait `std::convert::From<std::string::String>` is not implemented for `rocket::http::Header<'_>`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
rocket::http::Header<'static> as $TRAIT
|
||||
rocket::http::Header<'static> as $TRAIT
|
||||
rocket::http::Header<'static> as $TRAIT
|
||||
rocket::http::Header<'static> as $TRAIT
|
||||
and $N others
|
||||
= note: required because of the requirements on the impl of `std::convert::Into<rocket::http::Header<'_>>` for `std::string::String`
|
||||
|
||||
error: aborting due to 5 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0277`.
|
|
@ -0,0 +1,158 @@
|
|||
error: need at least one field
|
||||
--> $DIR/responder.rs:6:1
|
||||
|
|
||||
6 | struct Thing1;
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
note: error occurred while deriving `Responder`
|
||||
--> $DIR/responder.rs:5:10
|
||||
|
|
||||
5 | #[derive(Responder)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: need at least one field
|
||||
--> $DIR/responder.rs:10:14
|
||||
|
|
||||
10 | struct Thing2();
|
||||
| ^^
|
||||
|
|
||||
note: error occurred while deriving `Responder`
|
||||
--> $DIR/responder.rs:9:10
|
||||
|
|
||||
9 | #[derive(Responder)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: need at least one field
|
||||
--> $DIR/responder.rs:15:5
|
||||
|
|
||||
15 | Bark,
|
||||
| ^^^^
|
||||
|
|
||||
note: error occurred while deriving `Responder`
|
||||
--> $DIR/responder.rs:13:10
|
||||
|
|
||||
13 | #[derive(Responder)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: only one lifetime is supported
|
||||
--> $DIR/responder.rs:20:14
|
||||
|
|
||||
20 | struct Thing4<'a, 'b>(&'a str, &'b str);
|
||||
| ^^^^^^^^
|
||||
|
|
||||
note: error occurred while deriving `Responder`
|
||||
--> $DIR/responder.rs:19:10
|
||||
|
|
||||
19 | #[derive(Responder)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: type generics are not supported
|
||||
--> $DIR/responder.rs:24:15
|
||||
|
|
||||
24 | struct Thing5<T>(T);
|
||||
| ^
|
||||
|
|
||||
note: error occurred while deriving `Responder`
|
||||
--> $DIR/responder.rs:23:10
|
||||
|
|
||||
23 | #[derive(Responder)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: type generics are not supported
|
||||
--> $DIR/responder.rs:28:23
|
||||
|
|
||||
28 | struct Thing6<'a, 'b, T>(&'a str, &'b str, T);
|
||||
| ^
|
||||
|
|
||||
note: error occurred while deriving `Responder`
|
||||
--> $DIR/responder.rs:27:10
|
||||
|
|
||||
27 | #[derive(Responder)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: invalid or unknown content-type
|
||||
--> $DIR/responder.rs:33:31
|
||||
|
|
||||
33 | #[response(content_type = "")]
|
||||
| ^^
|
||||
|
|
||||
note: error occurred while deriving `Responder`
|
||||
--> $DIR/responder.rs:31:10
|
||||
|
|
||||
31 | #[derive(Responder)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: invalid or unknown content-type
|
||||
--> $DIR/responder.rs:40:31
|
||||
|
|
||||
40 | #[response(content_type = "idk")]
|
||||
| ^^^^^
|
||||
|
|
||||
note: error occurred while deriving `Responder`
|
||||
--> $DIR/responder.rs:38:10
|
||||
|
|
||||
38 | #[derive(Responder)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: invalid value: expected string
|
||||
--> $DIR/responder.rs:47:31
|
||||
|
|
||||
47 | #[response(content_type = 100)]
|
||||
| ^^^
|
||||
|
|
||||
note: error occurred while deriving `Responder`
|
||||
--> $DIR/responder.rs:45:10
|
||||
|
|
||||
45 | #[derive(Responder)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: status must be in range [100, 600)
|
||||
--> $DIR/responder.rs:54:25
|
||||
|
|
||||
54 | #[response(status = 8)]
|
||||
| ^
|
||||
|
|
||||
note: error occurred while deriving `Responder`
|
||||
--> $DIR/responder.rs:52:10
|
||||
|
|
||||
52 | #[derive(Responder)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: invalid value: expected unsigned integer
|
||||
--> $DIR/responder.rs:61:25
|
||||
|
|
||||
61 | #[response(status = "404")]
|
||||
| ^^^^^
|
||||
|
|
||||
note: error occurred while deriving `Responder`
|
||||
--> $DIR/responder.rs:59:10
|
||||
|
|
||||
59 | #[derive(Responder)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: invalid value: expected unsigned integer
|
||||
--> $DIR/responder.rs:68:25
|
||||
|
|
||||
68 | #[response(status = "404", content_type = "html")]
|
||||
| ^^^^^
|
||||
|
|
||||
note: error occurred while deriving `Responder`
|
||||
--> $DIR/responder.rs:66:10
|
||||
|
|
||||
66 | #[derive(Responder)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: invalid value: expected string
|
||||
--> $DIR/responder.rs:75:45
|
||||
|
|
||||
75 | #[response(status = 404, content_type = 120)]
|
||||
| ^^^
|
||||
|
|
||||
note: error occurred while deriving `Responder`
|
||||
--> $DIR/responder.rs:73:10
|
||||
|
|
||||
73 | #[derive(Responder)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: aborting due to 13 previous errors
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Copyright 2015 The Rust Project Developers. See the COPYRIGHT
|
||||
# file at the top-level directory of this distribution and at
|
||||
# http://rust-lang.org/COPYRIGHT.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
# option. This file may not be copied, modified, or distributed
|
||||
# except according to those terms.
|
||||
|
||||
# A script to update the references for particular tests. The idea is
|
||||
# that you do a run, which will generate files in the build directory
|
||||
# containing the (normalized) actual output of the compiler. This
|
||||
# script will then copy that output and replace the "expected output"
|
||||
# files. You can then commit the changes.
|
||||
#
|
||||
# If you find yourself manually editing a foo.stderr file, you're
|
||||
# doing it wrong.
|
||||
|
||||
if [[ "$1" == "--help" || "$1" == "-h" || "$1" == "" || "$2" == "" ]]; then
|
||||
echo "usage: $0 <build-directory> <relative-path-to-rs-files>"
|
||||
echo ""
|
||||
echo "For example:"
|
||||
echo " $0 ../../../build/x86_64-apple-darwin/test/ui *.rs */*.rs"
|
||||
fi
|
||||
|
||||
MYDIR=$(dirname $0)
|
||||
|
||||
BUILD_DIR="$1"
|
||||
shift
|
||||
|
||||
shopt -s nullglob
|
||||
|
||||
while [[ "$1" != "" ]]; do
|
||||
for EXT in "stderr" "stdout" "fixed"; do
|
||||
for OUT_NAME in $BUILD_DIR/${1%.rs}.*$EXT; do
|
||||
OUT_DIR=`dirname "$1"`
|
||||
OUT_BASE=`basename "$OUT_NAME"`
|
||||
if ! (diff $OUT_NAME $MYDIR/$OUT_DIR/$OUT_BASE >& /dev/null); then
|
||||
echo updating $MYDIR/$OUT_DIR/$OUT_BASE
|
||||
cp $OUT_NAME $MYDIR/$OUT_DIR
|
||||
fi
|
||||
done
|
||||
done
|
||||
shift
|
||||
done
|
|
@ -0,0 +1,21 @@
|
|||
use http::RawStr;
|
||||
|
||||
/// Error returned by the [`FromForm`] derive on form parsing errors.
|
||||
///
|
||||
/// If multiple errors occur while parsing a form, the first error in the
|
||||
/// following precedence, from highest to lowest, is returned:
|
||||
///
|
||||
/// * `BadValue` or `Unknown` in incoming form string field order
|
||||
/// * `Missing` in lexical field order
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum FormError<'f> {
|
||||
/// The field named `.0` with value `.1` failed to parse or validate.
|
||||
BadValue(&'f RawStr, &'f RawStr),
|
||||
/// The parse was strict and the field named `.0` with value `.1` appeared
|
||||
/// in the incoming form string but was unexpected.
|
||||
///
|
||||
/// This error cannot occur when parsing is lenient.
|
||||
Unknown(&'f RawStr, &'f RawStr),
|
||||
/// The field named `.0` was expected but is missing in the incoming form.
|
||||
Missing(&'f RawStr),
|
||||
}
|
|
@ -49,10 +49,10 @@ use request::form::{FromForm, FormItems};
|
|||
/// The simplest data structure with a reference into form data looks like this:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #![feature(plugin, decl_macro, custom_derive)]
|
||||
/// # #![feature(plugin, decl_macro)]
|
||||
/// # #![allow(deprecated, dead_code, unused_attributes)]
|
||||
/// # #![plugin(rocket_codegen)]
|
||||
/// # extern crate rocket;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// # use rocket::http::RawStr;
|
||||
/// #[derive(FromForm)]
|
||||
/// struct UserInput<'f> {
|
||||
|
@ -65,10 +65,10 @@ use request::form::{FromForm, FormItems};
|
|||
/// a string. A handler for this type can be written as:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #![feature(plugin, decl_macro, custom_derive)]
|
||||
/// # #![feature(plugin, decl_macro)]
|
||||
/// # #![allow(deprecated, unused_attributes)]
|
||||
/// # #![plugin(rocket_codegen)]
|
||||
/// # extern crate rocket;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// # use rocket::request::Form;
|
||||
/// # use rocket::http::RawStr;
|
||||
/// # #[derive(FromForm)]
|
||||
|
@ -91,24 +91,18 @@ use request::form::{FromForm, FormItems};
|
|||
/// The owned analog of the `UserInput` type above is:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #![feature(plugin, decl_macro, custom_derive)]
|
||||
/// # #![allow(deprecated, dead_code, unused_attributes)]
|
||||
/// # #![plugin(rocket_codegen)]
|
||||
/// # extern crate rocket;
|
||||
/// #[derive(FromForm)]
|
||||
/// struct OwnedUserInput {
|
||||
/// value: String
|
||||
/// }
|
||||
/// # fn main() { }
|
||||
/// ```
|
||||
///
|
||||
/// The handler is written similarly:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #![feature(plugin, decl_macro, custom_derive)]
|
||||
/// # #![feature(plugin, decl_macro)]
|
||||
/// # #![allow(deprecated, unused_attributes)]
|
||||
/// # #![plugin(rocket_codegen)]
|
||||
/// # extern crate rocket;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// # use rocket::request::Form;
|
||||
/// # #[derive(FromForm)]
|
||||
/// # struct OwnedUserInput {
|
||||
|
@ -170,9 +164,9 @@ impl<'f, T: FromForm<'f> + 'f> Form<'f, T> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #![feature(plugin, decl_macro, custom_derive)]
|
||||
/// # #![feature(plugin, decl_macro)]
|
||||
/// # #![plugin(rocket_codegen)]
|
||||
/// # extern crate rocket;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::request::Form;
|
||||
///
|
||||
/// #[derive(FromForm)]
|
||||
|
@ -198,9 +192,9 @@ impl<'f, T: FromForm<'f> + 'f> Form<'f, T> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #![feature(plugin, decl_macro, custom_derive)]
|
||||
/// # #![feature(plugin, decl_macro)]
|
||||
/// # #![plugin(rocket_codegen)]
|
||||
/// # extern crate rocket;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::request::Form;
|
||||
///
|
||||
/// #[derive(FromForm)]
|
||||
|
@ -269,9 +263,9 @@ impl<'f, T: FromForm<'f> + 'static> Form<'f, T> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #![feature(plugin, decl_macro, custom_derive)]
|
||||
/// # #![feature(plugin, decl_macro)]
|
||||
/// # #![plugin(rocket_codegen)]
|
||||
/// # extern crate rocket;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::request::Form;
|
||||
///
|
||||
/// #[derive(FromForm)]
|
||||
|
|
|
@ -14,11 +14,11 @@ use request::FormItems;
|
|||
/// validation.
|
||||
///
|
||||
/// ```rust
|
||||
/// #![feature(plugin, decl_macro, custom_derive)]
|
||||
/// #![feature(plugin, decl_macro)]
|
||||
/// #![plugin(rocket_codegen)]
|
||||
/// # #![allow(deprecated, dead_code, unused_attributes)]
|
||||
///
|
||||
/// extern crate rocket;
|
||||
/// #[macro_use] extern crate rocket;
|
||||
///
|
||||
/// #[derive(FromForm)]
|
||||
/// struct TodoTask {
|
||||
|
@ -34,10 +34,10 @@ use request::FormItems;
|
|||
/// data via the `data` parameter and `Form` type.
|
||||
///
|
||||
/// ```rust
|
||||
/// # #![feature(plugin, decl_macro, custom_derive)]
|
||||
/// # #![feature(plugin, decl_macro)]
|
||||
/// # #![allow(deprecated, dead_code, unused_attributes)]
|
||||
/// # #![plugin(rocket_codegen)]
|
||||
/// # extern crate rocket;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// # use rocket::request::Form;
|
||||
/// # #[derive(FromForm)]
|
||||
/// # struct TodoTask { description: String, completed: bool }
|
||||
|
|
|
@ -56,9 +56,9 @@ impl<'f, T: FromForm<'f> + 'f> LenientForm<'f, T> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #![feature(plugin, decl_macro, custom_derive)]
|
||||
/// # #![feature(plugin, decl_macro)]
|
||||
/// # #![plugin(rocket_codegen)]
|
||||
/// # extern crate rocket;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::request::LenientForm;
|
||||
///
|
||||
/// #[derive(FromForm)]
|
||||
|
@ -84,9 +84,9 @@ impl<'f, T: FromForm<'f> + 'f> LenientForm<'f, T> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #![feature(plugin, decl_macro, custom_derive)]
|
||||
/// # #![feature(plugin, decl_macro)]
|
||||
/// # #![plugin(rocket_codegen)]
|
||||
/// # extern crate rocket;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::request::LenientForm;
|
||||
///
|
||||
/// #[derive(FromForm)]
|
||||
|
@ -114,9 +114,9 @@ impl<'f, T: FromForm<'f> + 'static> LenientForm<'f, T> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #![feature(plugin, decl_macro, custom_derive)]
|
||||
/// # #![feature(plugin, decl_macro)]
|
||||
/// # #![plugin(rocket_codegen)]
|
||||
/// # extern crate rocket;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::request::LenientForm;
|
||||
///
|
||||
/// #[derive(FromForm)]
|
||||
|
|
|
@ -19,14 +19,16 @@
|
|||
mod form_items;
|
||||
mod from_form;
|
||||
mod from_form_value;
|
||||
mod form;
|
||||
mod lenient;
|
||||
mod error;
|
||||
mod form;
|
||||
|
||||
pub use self::form_items::FormItems;
|
||||
pub use self::from_form::FromForm;
|
||||
pub use self::from_form_value::FromFormValue;
|
||||
pub use self::form::Form;
|
||||
pub use self::lenient::LenientForm;
|
||||
pub use self::error::FormError;
|
||||
|
||||
use std::cmp;
|
||||
use std::io::Read;
|
||||
|
|
|
@ -12,7 +12,7 @@ mod tests;
|
|||
pub use self::request::Request;
|
||||
pub use self::from_request::{FromRequest, Outcome};
|
||||
pub use self::param::{FromParam, FromSegments, SegmentError};
|
||||
pub use self::form::{Form, LenientForm, FromForm, FromFormValue, FormItems};
|
||||
pub use self::form::{Form, FormError, LenientForm, FromForm, FromFormValue, FormItems};
|
||||
pub use self::state::State;
|
||||
|
||||
#[doc(inline)]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket;
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
use rocket::request::Form;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket;
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
use rocket::request::Form;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket;
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
use rocket::request::Form;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket;
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct Query {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
#![allow(dead_code)] // This test is only here so that we can ensure it compiles.
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket;
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
use rocket::request::{Form, LenientForm};
|
||||
use rocket::http::RawStr;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket_contrib;
|
||||
extern crate rocket;
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
#[macro_use] extern crate rocket;
|
||||
|
@ -11,7 +11,6 @@ use rocket::http::RawStr;
|
|||
|
||||
#[cfg(test)] mod tests;
|
||||
|
||||
// TODO: Make deriving `FromForm` for this enum possible.
|
||||
#[derive(Debug, FromFormValue)]
|
||||
enum FormOption {
|
||||
A, B, C
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket;
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
mod files;
|
||||
#[cfg(test)] mod tests;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate crossbeam;
|
||||
extern crate rocket;
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
#[cfg(test)] mod tests;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket;
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
#[cfg(test)] mod tests;
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![feature(plugin, decl_macro)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket_contrib;
|
||||
extern crate rocket;
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
#[cfg(test)] mod tests;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#![feature(plugin, decl_macro, custom_derive, const_fn)]
|
||||
#![feature(plugin, decl_macro, const_fn)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket;
|
||||
#[macro_use] extern crate rocket;
|
||||
#[macro_use] extern crate diesel;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
extern crate rocket_contrib;
|
||||
|
|
Loading…
Reference in New Issue