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:
Sergio Benitez 2018-08-06 19:58:07 -07:00
parent b0f86dcba0
commit d7f6d82fe4
58 changed files with 2179 additions and 1200 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
#[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"
_cookies: Cookies
) -> String {
format!("{}:{}", user.get().name, user.get().nickname)
}
#[test]

View File

@ -1,4 +1,4 @@
#![feature(plugin, decl_macro, custom_derive)]
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
#![feature(plugin, decl_macro, custom_derive)]
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;

View File

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

View File

@ -1,4 +1,4 @@
#![feature(plugin, decl_macro, custom_derive)]
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
#![allow(dead_code, unused_variables)]

View File

@ -1,4 +1,4 @@
#![feature(plugin, decl_macro, custom_derive)]
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
#![allow(dead_code, unused_variables)]

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
pub mod from_form;
pub mod from_form_value;
pub mod responder;

View File

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

View File

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

View File

@ -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))
)),*
])
}))
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
#![feature(plugin, decl_macro, custom_derive)]
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
#![feature(plugin, decl_macro, custom_derive)]
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]
extern crate rocket;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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