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