mirror of https://github.com/rwf2/Rocket.git
Remove non-streaming requests. Use streaming requests everywhere.
This commit includes the following important API changes: * The `form` route parameter has been removed. * The `data` route parameter has been added. * Forms are not handled via the `data` parameter and `Form` type. * Removed the `data` parameter from `Request`. * Added `FromData` conversion trate and default implementation. * Added `DataOutcome` enum, which is the return type of `from_data`. * 'FromData' is now used to automatically derive the `data` parameter. * Moved `form` into `request` module. * Removed `Failure::new` in favor of direct value construction. This commit includes the following important package additions: * Added a 'raw_upload' example. * `manual_routes` example uses `Data` parameter. * Now building and running tests with `--all-features` flag. * All exmaples have been updated to latest API. * Now using upstream Tera. This commit includes the following important fixes: * Any valid ident is now allowed in single-parameter route parameters. * Lifetimes are now properly stripped in code generation. * `FromForm` derive now works on empty structs.
This commit is contained in:
parent
a688f59ca0
commit
2f35b23514
|
@ -26,4 +26,5 @@ members = [
|
||||||
"examples/form_kitchen_sink",
|
"examples/form_kitchen_sink",
|
||||||
"examples/config",
|
"examples/config",
|
||||||
"examples/hello_alt_methods",
|
"examples/hello_alt_methods",
|
||||||
|
"examples/raw_upload",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#![allow(unused_imports)] // FIXME: Why is this coming from quote_tokens?
|
#![allow(unused_imports)] // FIXME: Why is this coming from quote_tokens?
|
||||||
|
|
||||||
|
use std::mem::transmute;
|
||||||
|
|
||||||
use syntax::ext::base::{Annotatable, ExtCtxt};
|
use syntax::ext::base::{Annotatable, ExtCtxt};
|
||||||
use syntax::print::pprust::{stmt_to_string};
|
use syntax::print::pprust::{stmt_to_string};
|
||||||
use syntax::parse::token::{str_to_ident};
|
use syntax::parse::token::{str_to_ident};
|
||||||
|
@ -7,12 +9,13 @@ use syntax::ast::{ItemKind, Expr, MetaItem, Mutability, VariantData};
|
||||||
use syntax::codemap::Span;
|
use syntax::codemap::Span;
|
||||||
use syntax::ext::build::AstBuilder;
|
use syntax::ext::build::AstBuilder;
|
||||||
use syntax::ptr::P;
|
use syntax::ptr::P;
|
||||||
use std::mem::transmute;
|
|
||||||
|
|
||||||
use syntax_ext::deriving::generic::MethodDef;
|
use syntax_ext::deriving::generic::MethodDef;
|
||||||
use syntax_ext::deriving::generic::{StaticStruct, Substructure, TraitDef, ty};
|
use syntax_ext::deriving::generic::{StaticStruct, Substructure, TraitDef, ty};
|
||||||
use syntax_ext::deriving::generic::combine_substructure as c_s;
|
use syntax_ext::deriving::generic::combine_substructure as c_s;
|
||||||
|
|
||||||
|
use utils::strip_ty_lifetimes;
|
||||||
|
|
||||||
static ONLY_STRUCTS_ERR: &'static str = "`FromForm` can only be derived for \
|
static ONLY_STRUCTS_ERR: &'static str = "`FromForm` can only be derived for \
|
||||||
structures with named fields.";
|
structures with named fields.";
|
||||||
static PRIVATE_LIFETIME: &'static str = "'rocket";
|
static PRIVATE_LIFETIME: &'static str = "'rocket";
|
||||||
|
@ -113,11 +116,12 @@ pub fn from_form_derive(ecx: &mut ExtCtxt, span: Span, meta_item: &MetaItem,
|
||||||
|
|
||||||
fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substructure) -> P<Expr> {
|
fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substructure) -> P<Expr> {
|
||||||
// Check that we specified the methods to the argument correctly.
|
// Check that we specified the methods to the argument correctly.
|
||||||
let arg = if substr.nonself_args.len() == 1 {
|
const EXPECTED_ARGS: usize = 1;
|
||||||
|
let arg = if substr.nonself_args.len() == EXPECTED_ARGS {
|
||||||
&substr.nonself_args[0]
|
&substr.nonself_args[0]
|
||||||
} else {
|
} else {
|
||||||
let msg = format!("incorrect number of arguments in `from_form_string`: \
|
let msg = format!("incorrect number of arguments in `from_form_string`: \
|
||||||
expected {}, found {}", 1, substr.nonself_args.len());
|
expected {}, found {}", EXPECTED_ARGS, substr.nonself_args.len());
|
||||||
cx.span_bug(trait_span, msg.as_str());
|
cx.span_bug(trait_span, msg.as_str());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -140,7 +144,8 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
|
||||||
None => cx.span_fatal(trait_span, ONLY_STRUCTS_ERR)
|
None => cx.span_fatal(trait_span, ONLY_STRUCTS_ERR)
|
||||||
};
|
};
|
||||||
|
|
||||||
fields_and_types.push((ident, &field.ty));
|
let stripped_ty = strip_ty_lifetimes(field.ty.clone());
|
||||||
|
fields_and_types.push((ident, stripped_ty));
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Fields and types: {:?}", fields_and_types);
|
debug!("Fields and types: {:?}", fields_and_types);
|
||||||
|
@ -155,7 +160,7 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
|
||||||
// placed into the final struct. They start out as `None` and are changed
|
// 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
|
// to Some when a parse completes, or some default value if the parse was
|
||||||
// unsuccessful and default() returns Some.
|
// unsuccessful and default() returns Some.
|
||||||
for &(ref ident, ty) in &fields_and_types {
|
for &(ref ident, ref ty) in &fields_and_types {
|
||||||
stmts.push(quote_stmt!(cx,
|
stmts.push(quote_stmt!(cx,
|
||||||
let mut $ident: ::std::option::Option<$ty> = None;
|
let mut $ident: ::std::option::Option<$ty> = None;
|
||||||
).unwrap());
|
).unwrap());
|
||||||
|
@ -199,11 +204,13 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
|
||||||
// This looks complicated but just generates the boolean condition checking
|
// This looks complicated but just generates the boolean condition checking
|
||||||
// that each parameter actually is Some() or has a default value.
|
// that each parameter actually is Some() or has a default value.
|
||||||
let mut failure_conditions = vec![];
|
let mut failure_conditions = vec![];
|
||||||
for (i, &(ref ident, ty)) in (&fields_and_types).iter().enumerate() {
|
|
||||||
|
// Start with `false` in case there are no fields.
|
||||||
|
failure_conditions.push(quote_tokens!(cx, false));
|
||||||
|
|
||||||
|
for &(ref ident, ref ty) in (&fields_and_types).iter() {
|
||||||
// Pushing an "||" (or) between every condition.
|
// Pushing an "||" (or) between every condition.
|
||||||
if i > 0 {
|
failure_conditions.push(quote_tokens!(cx, ||));
|
||||||
failure_conditions.push(quote_tokens!(cx, ||));
|
|
||||||
}
|
|
||||||
|
|
||||||
failure_conditions.push(quote_tokens!(cx,
|
failure_conditions.push(quote_tokens!(cx,
|
||||||
if $ident.is_none() &&
|
if $ident.is_none() &&
|
||||||
|
@ -217,7 +224,7 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
|
||||||
// The fields of the struct, which are just the let bindings declared above
|
// The fields of the struct, which are just the let bindings declared above
|
||||||
// or the default value.
|
// or the default value.
|
||||||
let mut result_fields = vec![];
|
let mut result_fields = vec![];
|
||||||
for &(ref ident, ty) in &fields_and_types {
|
for &(ref ident, ref ty) in &fields_and_types {
|
||||||
result_fields.push(quote_tokens!(cx,
|
result_fields.push(quote_tokens!(cx,
|
||||||
$ident: $ident.unwrap_or_else(||
|
$ident: $ident.unwrap_or_else(||
|
||||||
<$ty as ::rocket::request::FromFormValue>::default().unwrap()
|
<$ty as ::rocket::request::FromFormValue>::default().unwrap()
|
||||||
|
|
|
@ -52,7 +52,7 @@ trait RouteGenerateExt {
|
||||||
fn gen_form(&self, &ExtCtxt, Option<&Spanned<Ident>>, P<Expr>) -> Option<Stmt>;
|
fn gen_form(&self, &ExtCtxt, Option<&Spanned<Ident>>, P<Expr>) -> Option<Stmt>;
|
||||||
fn missing_declared_err<T: Display>(&self, ecx: &ExtCtxt, arg: &Spanned<T>);
|
fn missing_declared_err<T: Display>(&self, ecx: &ExtCtxt, arg: &Spanned<T>);
|
||||||
|
|
||||||
fn generate_form_statement(&self, ecx: &ExtCtxt) -> Option<Stmt>;
|
fn generate_data_statement(&self, ecx: &ExtCtxt) -> Option<Stmt>;
|
||||||
fn generate_query_statement(&self, ecx: &ExtCtxt) -> Option<Stmt>;
|
fn generate_query_statement(&self, ecx: &ExtCtxt) -> Option<Stmt>;
|
||||||
fn generate_param_statements(&self, ecx: &ExtCtxt) -> Vec<Stmt>;
|
fn generate_param_statements(&self, ecx: &ExtCtxt) -> Vec<Stmt>;
|
||||||
fn generate_fn_arguments(&self, ecx: &ExtCtxt) -> Vec<TokenTree>;
|
fn generate_fn_arguments(&self, ecx: &ExtCtxt) -> Vec<TokenTree>;
|
||||||
|
@ -89,20 +89,31 @@ impl RouteGenerateExt for RouteParams {
|
||||||
).expect("form statement"))
|
).expect("form statement"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_form_statement(&self, ecx: &ExtCtxt) -> Option<Stmt> {
|
fn generate_data_statement(&self, ecx: &ExtCtxt) -> Option<Stmt> {
|
||||||
let param = self.form_param.as_ref().map(|p| &p.value);
|
let param = self.data_param.as_ref().map(|p| &p.value);
|
||||||
let expr = quote_expr!(ecx,
|
let arg = param.and_then(|p| self.annotated_fn.find_input(&p.node.name));
|
||||||
match ::std::str::from_utf8(_req.data.as_slice()) {
|
if param.is_none() {
|
||||||
Ok(s) => s,
|
return None;
|
||||||
Err(_) => {
|
} else if arg.is_none() {
|
||||||
println!(" => Form is not valid UTF8.");
|
self.missing_declared_err(ecx, ¶m.unwrap());
|
||||||
return ::rocket::Response::failed(
|
return None;
|
||||||
::rocket::http::StatusCode::BadRequest);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
self.gen_form(ecx, param, expr)
|
let arg = arg.unwrap();
|
||||||
|
let name = arg.ident().expect("form param identifier").prepend(PARAM_PREFIX);
|
||||||
|
let ty = strip_ty_lifetimes(arg.ty.clone());
|
||||||
|
Some(quote_stmt!(ecx,
|
||||||
|
let $name: $ty =
|
||||||
|
match ::rocket::request::FromData::from_data(&_req, _data) {
|
||||||
|
::rocket::request::DataOutcome::Success(d) => d,
|
||||||
|
::rocket::request::DataOutcome::Forward(d) =>
|
||||||
|
return ::rocket::Response::forward(d),
|
||||||
|
::rocket::request::DataOutcome::Failure(_) => {
|
||||||
|
let code = ::rocket::http::StatusCode::BadRequest;
|
||||||
|
return ::rocket::Response::failed(code);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
).expect("data statement"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_query_statement(&self, ecx: &ExtCtxt) -> Option<Stmt> {
|
fn generate_query_statement(&self, ecx: &ExtCtxt) -> Option<Stmt> {
|
||||||
|
@ -148,11 +159,11 @@ impl RouteGenerateExt for RouteParams {
|
||||||
).expect("declared param parsing statement"));
|
).expect("declared param parsing statement"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// A from_request parameter is one that isn't declared, form, or query.
|
// A from_request parameter is one that isn't declared, data, or query.
|
||||||
let from_request = |a: &&Arg| {
|
let from_request = |a: &&Arg| {
|
||||||
if let Some(name) = a.name() {
|
if let Some(name) = a.name() {
|
||||||
!declared_set.contains(name)
|
!declared_set.contains(name)
|
||||||
&& self.form_param.as_ref().map_or(true, |p| {
|
&& self.data_param.as_ref().map_or(true, |p| {
|
||||||
!a.named(&p.value().name)
|
!a.named(&p.value().name)
|
||||||
}) && self.query_param.as_ref().map_or(true, |p| {
|
}) && self.query_param.as_ref().map_or(true, |p| {
|
||||||
!a.named(&p.node.name)
|
!a.named(&p.node.name)
|
||||||
|
@ -163,7 +174,7 @@ impl RouteGenerateExt for RouteParams {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate the code for `form_request` parameters.
|
// Generate the code for `from_request` parameters.
|
||||||
let all = &self.annotated_fn.decl().inputs;
|
let all = &self.annotated_fn.decl().inputs;
|
||||||
for arg in all.iter().filter(from_request) {
|
for arg in all.iter().filter(from_request) {
|
||||||
let ident = arg.ident().unwrap().prepend(PARAM_PREFIX);
|
let ident = arg.ident().unwrap().prepend(PARAM_PREFIX);
|
||||||
|
@ -214,9 +225,9 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
|
||||||
let route = RouteParams::from(ecx, sp, known_method, meta_item, annotated);
|
let route = RouteParams::from(ecx, sp, known_method, meta_item, annotated);
|
||||||
debug!("Route params: {:?}", route);
|
debug!("Route params: {:?}", route);
|
||||||
|
|
||||||
let form_statement = route.generate_form_statement(ecx);
|
|
||||||
let query_statement = route.generate_query_statement(ecx);
|
|
||||||
let param_statements = route.generate_param_statements(ecx);
|
let param_statements = route.generate_param_statements(ecx);
|
||||||
|
let query_statement = route.generate_query_statement(ecx);
|
||||||
|
let data_statement = route.generate_data_statement(ecx);
|
||||||
let fn_arguments = route.generate_fn_arguments(ecx);
|
let fn_arguments = route.generate_fn_arguments(ecx);
|
||||||
|
|
||||||
// Generate and emit the wrapping function with the Rocket handler signature.
|
// Generate and emit the wrapping function with the Rocket handler signature.
|
||||||
|
@ -225,9 +236,9 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
|
||||||
emit_item(push, quote_item!(ecx,
|
emit_item(push, quote_item!(ecx,
|
||||||
fn $route_fn_name<'_b>(_req: &'_b ::rocket::Request, _data: ::rocket::Data)
|
fn $route_fn_name<'_b>(_req: &'_b ::rocket::Request, _data: ::rocket::Data)
|
||||||
-> ::rocket::Response<'_b> {
|
-> ::rocket::Response<'_b> {
|
||||||
$form_statement
|
|
||||||
$query_statement
|
|
||||||
$param_statements
|
$param_statements
|
||||||
|
$query_statement
|
||||||
|
$data_statement
|
||||||
let result = $user_fn_name($fn_arguments);
|
let result = $user_fn_name($fn_arguments);
|
||||||
::rocket::Response::complete(result)
|
::rocket::Response::complete(result)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use syntax::ext::base::{ExtCtxt, Annotatable};
|
||||||
use syntax::codemap::{Span, Spanned, dummy_spanned};
|
use syntax::codemap::{Span, Spanned, dummy_spanned};
|
||||||
use syntax::parse::token::str_to_ident;
|
use syntax::parse::token::str_to_ident;
|
||||||
|
|
||||||
use utils::{span, MetaItemExt, SpanExt};
|
use utils::{span, MetaItemExt, SpanExt, is_valid_ident};
|
||||||
use super::{Function, ParamIter};
|
use super::{Function, ParamIter};
|
||||||
use super::keyvalue::KVSpanned;
|
use super::keyvalue::KVSpanned;
|
||||||
use rocket::http::{Method, ContentType};
|
use rocket::http::{Method, ContentType};
|
||||||
|
@ -22,7 +22,7 @@ pub struct RouteParams {
|
||||||
pub annotated_fn: Function,
|
pub annotated_fn: Function,
|
||||||
pub method: Spanned<Method>,
|
pub method: Spanned<Method>,
|
||||||
pub path: Spanned<String>,
|
pub path: Spanned<String>,
|
||||||
pub form_param: Option<KVSpanned<Ident>>,
|
pub data_param: Option<KVSpanned<Ident>>,
|
||||||
pub query_param: Option<Spanned<Ident>>,
|
pub query_param: Option<Spanned<Ident>>,
|
||||||
pub format: Option<KVSpanned<ContentType>>,
|
pub format: Option<KVSpanned<ContentType>>,
|
||||||
pub rank: Option<KVSpanned<isize>>,
|
pub rank: Option<KVSpanned<isize>>,
|
||||||
|
@ -74,7 +74,7 @@ impl RouteParams {
|
||||||
|
|
||||||
// Parse all of the optional parameters.
|
// Parse all of the optional parameters.
|
||||||
let mut seen_keys = HashSet::new();
|
let mut seen_keys = HashSet::new();
|
||||||
let (mut rank, mut form, mut format) = Default::default();
|
let (mut rank, mut data, mut format) = Default::default();
|
||||||
for param in &attr_params[1..] {
|
for param in &attr_params[1..] {
|
||||||
let kv_opt = kv_from_nested(¶m);
|
let kv_opt = kv_from_nested(¶m);
|
||||||
if kv_opt.is_none() {
|
if kv_opt.is_none() {
|
||||||
|
@ -85,7 +85,7 @@ impl RouteParams {
|
||||||
let kv = kv_opt.unwrap();
|
let kv = kv_opt.unwrap();
|
||||||
match kv.key().as_str() {
|
match kv.key().as_str() {
|
||||||
"rank" => rank = parse_opt(ecx, &kv, parse_rank),
|
"rank" => rank = parse_opt(ecx, &kv, parse_rank),
|
||||||
"form" => form = parse_opt(ecx, &kv, parse_form),
|
"data" => data = parse_opt(ecx, &kv, parse_data),
|
||||||
"format" => format = parse_opt(ecx, &kv, parse_format),
|
"format" => format = parse_opt(ecx, &kv, parse_format),
|
||||||
_ => {
|
_ => {
|
||||||
let msg = format!("'{}' is not a known parameter", kv.key());
|
let msg = format!("'{}' is not a known parameter", kv.key());
|
||||||
|
@ -107,7 +107,7 @@ impl RouteParams {
|
||||||
RouteParams {
|
RouteParams {
|
||||||
method: method,
|
method: method,
|
||||||
path: path,
|
path: path,
|
||||||
form_param: form,
|
data_param: data,
|
||||||
query_param: query,
|
query_param: query,
|
||||||
format: format,
|
format: format,
|
||||||
rank: rank,
|
rank: rank,
|
||||||
|
@ -145,7 +145,7 @@ fn param_string_to_ident(ecx: &ExtCtxt, s: Spanned<&str>) -> Option<Ident> {
|
||||||
let string = s.node;
|
let string = s.node;
|
||||||
if string.starts_with('<') && string.ends_with('>') {
|
if string.starts_with('<') && string.ends_with('>') {
|
||||||
let param = &string[1..(string.len() - 1)];
|
let param = &string[1..(string.len() - 1)];
|
||||||
if param.chars().all(char::is_alphanumeric) {
|
if is_valid_ident(param) {
|
||||||
return Some(str_to_ident(param));
|
return Some(str_to_ident(param));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,21 +217,23 @@ fn parse_opt<O, T, F>(ecx: &ExtCtxt, kv: &KVSpanned<T>, f: F) -> Option<KVSpanne
|
||||||
Some(kv.map_ref(|_| f(ecx, kv)))
|
Some(kv.map_ref(|_| f(ecx, kv)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_form(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> Ident {
|
fn parse_data(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> Ident {
|
||||||
|
let mut str_name = "unknown";
|
||||||
if let LitKind::Str(ref s, _) = *kv.value() {
|
if let LitKind::Str(ref s, _) = *kv.value() {
|
||||||
|
str_name = s;
|
||||||
if let Some(ident) = param_string_to_ident(ecx, span(s, kv.value.span)) {
|
if let Some(ident) = param_string_to_ident(ecx, span(s, kv.value.span)) {
|
||||||
return ident;
|
return ident;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let err_string = r#"`form` value must be a parameter, e.g: "<name>"`"#;
|
let err_string = r#"`data` value must be a parameter, e.g: "<name>"`"#;
|
||||||
ecx.struct_span_fatal(kv.span, err_string)
|
ecx.struct_span_fatal(kv.span, err_string)
|
||||||
.help(r#"form, if specified, must be a key-value pair where
|
.help(r#"data, if specified, must be a key-value pair where
|
||||||
the key is `form` and the value is a string with a single
|
the key is `data` and the value is a string with a single
|
||||||
parameter inside '<' '>'. e.g: form = "<login>""#)
|
parameter inside '<' '>'. e.g: data = "<user_form>""#)
|
||||||
.emit();
|
.emit();
|
||||||
|
|
||||||
str_to_ident("")
|
str_to_ident(str_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_rank(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> isize {
|
fn parse_rank(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> isize {
|
||||||
|
|
|
@ -87,6 +87,10 @@ impl Folder for TyLifetimeRemover {
|
||||||
fn fold_lifetime_defs(&mut self, _: Vec<LifetimeDef>) -> Vec<LifetimeDef> {
|
fn fold_lifetime_defs(&mut self, _: Vec<LifetimeDef>) -> Vec<LifetimeDef> {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fold_lifetimes(&mut self, _: Vec<Lifetime>) -> Vec<Lifetime> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn strip_ty_lifetimes(ty: P<Ty>) -> P<Ty> {
|
pub fn strip_ty_lifetimes(ty: P<Ty>) -> P<Ty> {
|
||||||
|
|
|
@ -7,7 +7,7 @@ fn get(other: &str) -> &'static str { "hi" } //~ ERROR isn't in the function
|
||||||
#[get("/a?<r>")] //~ ERROR 'r' is declared
|
#[get("/a?<r>")] //~ ERROR 'r' is declared
|
||||||
fn get1() -> &'static str { "hi" } //~ ERROR isn't in the function
|
fn get1() -> &'static str { "hi" } //~ ERROR isn't in the function
|
||||||
|
|
||||||
#[get("/a", form = "<test>")] //~ ERROR 'test' is declared
|
#[get("/a", data = "<test>")] //~ ERROR 'test' is declared
|
||||||
fn get2() -> &'static str { "hi" } //~ ERROR isn't in the function
|
fn get2() -> &'static str { "hi" } //~ ERROR isn't in the function
|
||||||
|
|
||||||
fn main() { }
|
fn main() { }
|
||||||
|
|
|
@ -4,14 +4,15 @@
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
||||||
use rocket::http::Cookies;
|
use rocket::http::Cookies;
|
||||||
|
use rocket::request::Form;
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
struct User {
|
struct User {
|
||||||
name: String
|
name: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/<name>?<query>", format = "application/json", form = "<user>", rank = 2)]
|
#[post("/<name>?<query>", format = "application/json", data = "<user>", rank = 2)]
|
||||||
fn get(name: &str, query: User, user: User, cookies: &Cookies) -> &'static str { "hi" }
|
fn get(name: &str, query: User, user: Form<User>, cookies: &Cookies) -> &'static str { "hi" }
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let _ = routes![get];
|
let _ = routes![get];
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
#![feature(plugin, custom_derive)]
|
||||||
|
#![plugin(rocket_codegen)]
|
||||||
|
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
|
use rocket::request::FromForm;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug, FromForm)]
|
||||||
|
struct Form { }
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Same number of arguments: simple case.
|
||||||
|
let task = Form::from_form_string("");
|
||||||
|
assert_eq!(task, Ok(Form { }));
|
||||||
|
}
|
|
@ -25,8 +25,4 @@ serde_json = { version = "^0.8", optional = true }
|
||||||
handlebars = { version = "^0.21", optional = true, features = ["serde_type"] }
|
handlebars = { version = "^0.21", optional = true, features = ["serde_type"] }
|
||||||
glob = { version = "^0.2", optional = true }
|
glob = { version = "^0.2", optional = true }
|
||||||
lazy_static = { version = "^0.2", optional = true }
|
lazy_static = { version = "^0.2", optional = true }
|
||||||
|
tera = { version = "^0.3", optional = true }
|
||||||
# Tera dependency
|
|
||||||
[dependencies.tera]
|
|
||||||
git = "https://github.com/SergioBenitez/tera"
|
|
||||||
optional = true
|
|
||||||
|
|
|
@ -2,39 +2,39 @@ extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
use rocket::request::{Request, FromRequest};
|
use rocket::request::{Request, Data, FromData, DataOutcome};
|
||||||
use rocket::response::{Responder, Outcome, ResponseOutcome, data};
|
use rocket::response::{Responder, Outcome, ResponseOutcome, data};
|
||||||
use rocket::http::StatusCode;
|
use rocket::http::StatusCode;
|
||||||
use rocket::http::hyper::FreshHyperResponse;
|
use rocket::http::hyper::FreshHyperResponse;
|
||||||
|
|
||||||
use self::serde::{Serialize, Deserialize};
|
use self::serde::{Serialize, Deserialize};
|
||||||
use self::serde_json::Error as JSONError;
|
use self::serde_json::error::Error as SerdeError;
|
||||||
|
|
||||||
/// The JSON type, which implements both `FromRequest` and `Responder`. This
|
/// The JSON type, which implements `FromData` and `Responder`. This type allows
|
||||||
/// type allows you to trivially consume and respond with JSON in your Rocket
|
/// you to trivially consume and respond with JSON in your Rocket application.
|
||||||
/// application.
|
|
||||||
///
|
///
|
||||||
/// If you're receiving JSON data, simple add a `JSON<T>` type to your function
|
/// If you're receiving JSON data, simple add a `data` parameter to your route
|
||||||
/// signature where `T` is some type you'd like to parse from JSON. `T` must
|
/// arguments and ensure the type o the parameter is a `JSON<T>`, where `T` is
|
||||||
/// implement `Deserialize` from [Serde](https://github.com/serde-rs/json). The
|
/// some type you'd like to parse from JSON. `T` must implement `Deserialize`
|
||||||
/// data is parsed from the HTTP request body.
|
/// from [Serde](https://github.com/serde-rs/json). The data is parsed from the
|
||||||
|
/// HTTP request body.
|
||||||
///
|
///
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
/// #[post("/users/", format = "application/json")]
|
/// #[post("/users/", format = "application/json", data = "<user>")]
|
||||||
/// fn new_user(user: JSON<User>) {
|
/// fn new_user(user: JSON<User>) {
|
||||||
/// ...
|
/// ...
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
/// You don't _need_ to use `format = "application/json"`, but it _may_ be what
|
/// You don't _need_ to use `format = "application/json"`, but it _may_ be what
|
||||||
/// you want. Using `format = application/json` means that any request that
|
/// you want. Using `format = application/json` means that any request that
|
||||||
/// doesn't specify "application/json" as its first `Accept:` header parameter
|
/// doesn't specify "application/json" as its first `Content-Type:` header
|
||||||
/// will not be routed to this handler.
|
/// parameter will not be routed to this handler.
|
||||||
///
|
///
|
||||||
/// If you're responding with JSON data, return a `JSON<T>` type, where `T`
|
/// If you're responding with JSON data, return a `JSON<T>` type, where `T`
|
||||||
/// implements implements `Serialize` from
|
/// implements `Serialize` from [Serde](https://github.com/serde-rs/json). The
|
||||||
/// [Serde](https://github.com/serde-rs/json). The content type is set to
|
/// content type of the response is set to `application/json` automatically.
|
||||||
/// `application/json` automatically.
|
|
||||||
///
|
///
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
/// #[get("/users/<id>")]
|
/// #[get("/users/<id>")]
|
||||||
|
@ -63,10 +63,21 @@ impl<T> JSON<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, T: Deserialize> FromRequest<'r> for JSON<T> {
|
/// Maximum size of JSON is 1MB.
|
||||||
type Error = JSONError;
|
/// TODO: Determine this size from some configuration parameter.
|
||||||
fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
|
const MAX_SIZE: u64 = 1048576;
|
||||||
Ok(JSON(serde_json::from_slice(request.data.as_slice())?))
|
|
||||||
|
impl<T: Deserialize> FromData for JSON<T> {
|
||||||
|
type Error = SerdeError;
|
||||||
|
|
||||||
|
fn from_data(request: &Request, data: Data) -> DataOutcome<Self, SerdeError> {
|
||||||
|
if !request.content_type().is_json() {
|
||||||
|
error_!("Content-Type is not JSON.");
|
||||||
|
return DataOutcome::Forward(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
let reader = data.open().take(MAX_SIZE);
|
||||||
|
DataOutcome::from(serde_json::from_reader(reader).map(|val| JSON(val)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ extern crate rocket;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use rocket::request::Form;
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
use rocket::http::{Cookie, Cookies};
|
use rocket::http::{Cookie, Cookies};
|
||||||
use rocket_contrib::Template;
|
use rocket_contrib::Template;
|
||||||
|
@ -17,9 +18,9 @@ struct Message {
|
||||||
message: String
|
message: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/submit", form = "<message>")]
|
#[post("/submit", data = "<message>")]
|
||||||
fn submit(cookies: &Cookies, message: Message) -> Redirect {
|
fn submit(cookies: &Cookies, message: Form<Message>) -> Redirect {
|
||||||
cookies.add(Cookie::new("message".into(), message.message));
|
cookies.add(Cookie::new("message".into(), message.into_inner().message));
|
||||||
Redirect::to("/")
|
Redirect::to("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ fn index() -> io::Result<NamedFile> {
|
||||||
NamedFile::open("static/index.html")
|
NamedFile::open("static/index.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<file..>")]
|
#[get("/<file..>", rank = 2)]
|
||||||
fn files(file: PathBuf) -> io::Result<NamedFile> {
|
fn files(file: PathBuf) -> io::Result<NamedFile> {
|
||||||
NamedFile::open(Path::new("static/").join(file))
|
NamedFile::open(Path::new("static/").join(file))
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ extern crate rocket;
|
||||||
mod files;
|
mod files;
|
||||||
|
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
use rocket::request::FromFormValue;
|
use rocket::request::{Form, FromFormValue};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct StrongPassword<'r>(&'r str);
|
struct StrongPassword<'r>(&'r str);
|
||||||
|
@ -49,24 +49,26 @@ impl<'v> FromFormValue<'v> for AdultAge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/login", form = "<user>")]
|
#[post("/login", data = "<user_form>")]
|
||||||
fn login(user: UserLogin) -> Result<Redirect, String> {
|
fn login<'a>(user_form: Form<'a, UserLogin<'a>>) -> Result<Redirect, String> {
|
||||||
if user.age.is_err() {
|
let user = user_form.get();
|
||||||
return Err(String::from(user.age.unwrap_err()));
|
|
||||||
|
if let Err(e) = user.age {
|
||||||
|
return Err(format!("Age is invalid: {}", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.password.is_err() {
|
if let Err(e) = user.password {
|
||||||
return Err(String::from(user.password.unwrap_err()));
|
return Err(format!("Password is invalid: {}", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
match user.username {
|
if user.username == "Sergio" {
|
||||||
"Sergio" => {
|
if let Ok(StrongPassword("password")) = user.password {
|
||||||
match user.password.unwrap().0 {
|
Ok(Redirect::other("/user/Sergio"))
|
||||||
"password" => Ok(Redirect::other("/user/Sergio")),
|
} else {
|
||||||
_ => Err("Wrong password!".to_string()),
|
Err("Wrong password!".to_string())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => Err(format!("Unrecognized user, '{}'.", user.username)),
|
} else {
|
||||||
|
Err(format!("Unrecognized user, '{}'.", user.username))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
||||||
use rocket::request::{Request, FromFormValue};
|
use rocket::request::{Form, FromFormValue};
|
||||||
use rocket::response::NamedFile;
|
use rocket::response::NamedFile;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
|
@ -29,24 +29,22 @@ impl<'v> FromFormValue<'v> for FormOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, FromForm)]
|
#[derive(Debug, FromForm)]
|
||||||
struct FormInput<'r> {
|
struct FormInput {
|
||||||
checkbox: bool,
|
checkbox: bool,
|
||||||
number: usize,
|
number: usize,
|
||||||
radio: FormOption,
|
radio: FormOption,
|
||||||
password: &'r str,
|
password: String,
|
||||||
textarea: String,
|
textarea: String,
|
||||||
select: FormOption,
|
select: FormOption,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/", form = "<sink>")]
|
#[post("/", data = "<sink>")]
|
||||||
fn sink(sink: FormInput) -> String {
|
fn sink(sink: Result<Form<FormInput>, Option<String>>) -> String {
|
||||||
format!("{:?}", sink)
|
match sink {
|
||||||
}
|
Ok(form) => format!("{:?}", form.get()),
|
||||||
|
Err(Some(f)) => format!("Invalid form input: {}", f),
|
||||||
#[post("/", rank = 2)]
|
Err(None) => format!("Form input was invalid UTF8."),
|
||||||
fn sink2(request: &Request) -> &'static str {
|
}
|
||||||
println!("form: {:?}", std::str::from_utf8(request.data.as_slice()));
|
|
||||||
"Sorry, the form is invalid."
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
|
@ -56,6 +54,6 @@ fn index() -> io::Result<NamedFile> {
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
rocket::ignite()
|
rocket::ignite()
|
||||||
.mount("/", routes![index, sink, sink2])
|
.mount("/", routes![index, sink])
|
||||||
.launch();
|
.launch();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ fn index() -> io::Result<NamedFile> {
|
||||||
NamedFile::open("static/index.html")
|
NamedFile::open("static/index.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<file..>")]
|
#[get("/<file..>", rank = 5)]
|
||||||
fn files(file: PathBuf) -> io::Result<NamedFile> {
|
fn files(file: PathBuf) -> io::Result<NamedFile> {
|
||||||
NamedFile::open(Path::new("static/").join(file))
|
NamedFile::open(Path::new("static/").join(file))
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,37 +5,38 @@ extern crate rocket;
|
||||||
|
|
||||||
mod files;
|
mod files;
|
||||||
|
|
||||||
|
use rocket::request::Form;
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
struct UserLogin<'r> {
|
struct UserLogin<'r> {
|
||||||
username: &'r str,
|
username: &'r str,
|
||||||
password: &'r str,
|
password: &'r str,
|
||||||
age: Result<isize, &'r str>,
|
age: Result<usize, &'r str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/login", form = "<user>")]
|
#[post("/login", data = "<user_form>")]
|
||||||
fn login(user: UserLogin) -> Result<Redirect, String> {
|
fn login<'a>(user_form: Form<'a, UserLogin<'a>>) -> Result<Redirect, String> {
|
||||||
if user.age.is_err() {
|
let user = user_form.get();
|
||||||
let input = user.age.unwrap_err();
|
match user.age {
|
||||||
return Err(format!("'{}' is not a valid age integer.", input));
|
Ok(age) if age < 21 => return Err(format!("Sorry, {} is too young!", age)),
|
||||||
}
|
Ok(age) if age > 120 => return Err(format!("Are you sure you're {}?", age)),
|
||||||
|
Err(e) => return Err(format!("'{}' is not a valid integer.", e)),
|
||||||
|
Ok(_) => { /* Move along, adult. */ }
|
||||||
|
};
|
||||||
|
|
||||||
let age = user.age.unwrap();
|
if user.username == "Sergio" {
|
||||||
if age < 20 {
|
match user.password {
|
||||||
return Err(format!("Sorry, {} is too young!", age));
|
|
||||||
}
|
|
||||||
|
|
||||||
match user.username {
|
|
||||||
"Sergio" => match user.password {
|
|
||||||
"password" => Ok(Redirect::other("/user/Sergio")),
|
"password" => Ok(Redirect::other("/user/Sergio")),
|
||||||
_ => Err("Wrong password!".to_string())
|
_ => Err("Wrong password!".to_string())
|
||||||
},
|
}
|
||||||
_ => Err(format!("Unrecognized user, '{}'.", user.username))
|
} else {
|
||||||
|
Err(format!("Unrecognized user, '{}'.", user.username))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/user/<username>")]
|
|
||||||
|
#[get("/user/<username>")]
|
||||||
fn user_page(username: &str) -> String {
|
fn user_page(username: &str) -> String {
|
||||||
format!("This is {}'s page.", username)
|
format!("This is {}'s page.", username)
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ struct Message {
|
||||||
// None
|
// None
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[post("/<id>", format = "application/json")]
|
#[post("/<id>", format = "application/json", data = "<message>")]
|
||||||
fn new(id: ID, message: JSON<Message>) -> JSON<SimpleMap> {
|
fn new(id: ID, message: JSON<Message>) -> JSON<SimpleMap> {
|
||||||
let mut hashmap = MAP.lock().unwrap();
|
let mut hashmap = MAP.lock().unwrap();
|
||||||
if hashmap.contains_key(&id) {
|
if hashmap.contains_key(&id) {
|
||||||
|
@ -54,7 +54,7 @@ fn new(id: ID, message: JSON<Message>) -> JSON<SimpleMap> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/<id>", format = "application/json")]
|
#[put("/<id>", format = "application/json", data = "<message>")]
|
||||||
fn update(id: ID, message: JSON<Message>) -> Option<JSON<SimpleMap>> {
|
fn update(id: ID, message: JSON<Message>) -> Option<JSON<SimpleMap>> {
|
||||||
let mut hashmap = MAP.lock().unwrap();
|
let mut hashmap = MAP.lock().unwrap();
|
||||||
if hashmap.contains_key(&id) {
|
if hashmap.contains_key(&id) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ fn echo_url<'a>(req: &'a Request, _: Data) -> Response<'a> {
|
||||||
|
|
||||||
fn upload(req: &Request, data: Data) -> Response {
|
fn upload(req: &Request, data: Data) -> Response {
|
||||||
if !req.content_type().is_text() {
|
if !req.content_type().is_text() {
|
||||||
|
println!(" => Content-Type of upload must be data. Ignoring.");
|
||||||
return Response::failed(StatusCode::BadRequest);
|
return Response::failed(StatusCode::BadRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "raw_upload"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = ["Sergio Benitez <sb@sergio.bz>"]
|
||||||
|
workspace = "../../"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rocket = { path = "../../lib" }
|
||||||
|
rocket_codegen = { path = "../../codegen" }
|
|
@ -0,0 +1,33 @@
|
||||||
|
#![feature(plugin)]
|
||||||
|
#![plugin(rocket_codegen)]
|
||||||
|
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
|
use rocket::request::Data;
|
||||||
|
use rocket::response::Failure;
|
||||||
|
use rocket::http::{StatusCode, ContentType};
|
||||||
|
|
||||||
|
#[post("/upload", data = "<data>")]
|
||||||
|
fn upload(content_type: ContentType, data: Data) -> Result<String, Failure> {
|
||||||
|
if !content_type.is_text() {
|
||||||
|
println!(" => Content-Type of upload must be text. Ignoring.");
|
||||||
|
return Err(Failure(StatusCode::BadRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
match data.stream_to_file("/tmp/upload.txt") {
|
||||||
|
Ok(n) => Ok(format!("OK: {} bytes uploaded.", n)),
|
||||||
|
Err(e) => {
|
||||||
|
println!(" => Failed writing to file: {:?}.", e);
|
||||||
|
return Err(Failure(StatusCode::InternalServerError));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
fn index() -> &'static str {
|
||||||
|
"Upload your text files by POSTing them to /upload."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
rocket::ignite().mount("/", routes![index, upload]).launch();
|
||||||
|
}
|
|
@ -11,12 +11,13 @@ extern crate serde_json;
|
||||||
mod static_files;
|
mod static_files;
|
||||||
mod task;
|
mod task;
|
||||||
|
|
||||||
|
use rocket::request::Form;
|
||||||
use rocket::response::{Flash, Redirect};
|
use rocket::response::{Flash, Redirect};
|
||||||
use rocket_contrib::Template;
|
use rocket_contrib::Template;
|
||||||
use task::Task;
|
use task::Task;
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct Context<'a, 'b>{msg: Option<(&'a str, &'b str)>, tasks: Vec<Task>}
|
struct Context<'a, 'b>{ msg: Option<(&'a str, &'b str)>, tasks: Vec<Task> }
|
||||||
|
|
||||||
impl<'a, 'b> Context<'a, 'b> {
|
impl<'a, 'b> Context<'a, 'b> {
|
||||||
pub fn err(msg: &'a str) -> Context<'static, 'a> {
|
pub fn err(msg: &'a str) -> Context<'static, 'a> {
|
||||||
|
@ -28,14 +29,15 @@ impl<'a, 'b> Context<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/", form = "<todo>")]
|
#[post("/", data = "<todo_form>")]
|
||||||
fn new(todo: Task) -> Result<Flash<Redirect>, Template> {
|
fn new(todo_form: Form<Task>) -> Flash<Redirect> {
|
||||||
|
let todo = todo_form.into_inner();
|
||||||
if todo.description.is_empty() {
|
if todo.description.is_empty() {
|
||||||
Err(Template::render("index", &Context::err("Description cannot be empty.")))
|
Flash::error(Redirect::to("/"), "Description cannot be empty.")
|
||||||
} else if todo.insert() {
|
} else if todo.insert() {
|
||||||
Ok(Flash::success(Redirect::to("/"), "Todo successfully added."))
|
Flash::success(Redirect::to("/"), "Todo successfully added.")
|
||||||
} else {
|
} else {
|
||||||
Err(Template::render("index", &Context::err("Whoops! The server failed.")))
|
Flash::error(Redirect::to("/"), "Whoops! The server failed.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#![feature(question_mark)]
|
#![feature(question_mark)]
|
||||||
#![feature(specialization)]
|
#![feature(specialization)]
|
||||||
#![feature(conservative_impl_trait)]
|
#![feature(conservative_impl_trait)]
|
||||||
|
#![feature(associated_type_defaults)]
|
||||||
|
|
||||||
//! # Rocket - Core API Documentation
|
//! # Rocket - Core API Documentation
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
use std::io::{self, BufRead, Read, Cursor, BufReader, Chain, Take};
|
||||||
|
use std::net::Shutdown;
|
||||||
|
|
||||||
|
use http::hyper::{HyperHttpStream, HyperHttpReader};
|
||||||
|
use http::hyper::HyperNetworkStream;
|
||||||
|
|
||||||
|
pub type StreamReader = HyperHttpReader<HyperHttpStream>;
|
||||||
|
pub type InnerStream = Chain<Take<Cursor<Vec<u8>>>, BufReader<StreamReader>>;
|
||||||
|
|
||||||
|
pub struct DataStream {
|
||||||
|
pub stream: InnerStream,
|
||||||
|
pub network: HyperHttpStream,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for DataStream {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
self.stream.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BufRead for DataStream {
|
||||||
|
fn fill_buf(&mut self) -> io::Result<&[u8]> {
|
||||||
|
self.stream.fill_buf()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consume(&mut self, amt: usize) {
|
||||||
|
self.stream.consume(amt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kill_stream<S: Read, N: HyperNetworkStream>(stream: &mut S, network: &mut N) {
|
||||||
|
io::copy(&mut stream.take(1024), &mut io::sink()).expect("sink");
|
||||||
|
|
||||||
|
// If there are any more bytes, kill it.
|
||||||
|
let mut buf = [0];
|
||||||
|
if let Ok(n) = stream.read(&mut buf) {
|
||||||
|
if n > 0 {
|
||||||
|
warn_!("Data left unread. Force closing network stream.");
|
||||||
|
if let Err(e) = network.close(Shutdown::Both) {
|
||||||
|
error_!("Failed to close network stream: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for DataStream {
|
||||||
|
// Be a bad citizen and close the TCP stream if there's unread data.
|
||||||
|
fn drop(&mut self) {
|
||||||
|
kill_stream(&mut self.stream, &mut self.network);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use request::{Request, Data};
|
||||||
|
|
||||||
|
/// Trait used to derive an object from incoming request data.
|
||||||
|
pub trait FromData: Sized {
|
||||||
|
type Error = ();
|
||||||
|
fn from_data(request: &Request, data: Data) -> DataOutcome<Self, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: FromData> FromData for Result<T, T::Error> {
|
||||||
|
fn from_data(request: &Request, data: Data) -> DataOutcome<Self, Self::Error> {
|
||||||
|
match T::from_data(request, data) {
|
||||||
|
DataOutcome::Success(val) => DataOutcome::Success(Ok(val)),
|
||||||
|
DataOutcome::Failure(val) => DataOutcome::Success(Err(val)),
|
||||||
|
DataOutcome::Forward(data) => DataOutcome::Forward(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: FromData> FromData for Option<T> {
|
||||||
|
fn from_data(request: &Request, data: Data) -> DataOutcome<Self, Self::Error> {
|
||||||
|
match T::from_data(request, data) {
|
||||||
|
DataOutcome::Success(val) => DataOutcome::Success(Some(val)),
|
||||||
|
DataOutcome::Failure(_) => DataOutcome::Success(None),
|
||||||
|
DataOutcome::Forward(data) => DataOutcome::Forward(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub enum DataOutcome<T, E> {
|
||||||
|
/// Signifies that all processing completed successfully.
|
||||||
|
Success(T),
|
||||||
|
/// Signifies that some processing occurred that ultimately resulted in
|
||||||
|
/// failure. As a result, no further processing can occur.
|
||||||
|
Failure(E),
|
||||||
|
/// Signifies that no processing occured and as such, processing can be
|
||||||
|
/// forwarded to the next available target.
|
||||||
|
Forward(Data),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E: Debug> From<Result<T, E>> for DataOutcome<T, E> {
|
||||||
|
fn from(result: Result<T, E>) -> Self {
|
||||||
|
match result {
|
||||||
|
Ok(val) => DataOutcome::Success(val),
|
||||||
|
Err(e) => {
|
||||||
|
error_!("{:?}", e);
|
||||||
|
DataOutcome::Failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<Option<T>> for DataOutcome<T, ()> {
|
||||||
|
fn from(result: Option<T>) -> Self {
|
||||||
|
match result {
|
||||||
|
Some(val) => DataOutcome::Success(val),
|
||||||
|
None => DataOutcome::Failure(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,71 +1,30 @@
|
||||||
use std::io::{self, BufRead, Read, Cursor, BufReader, Chain, Take};
|
//! Talk about the data thing.
|
||||||
use std::time::Duration;
|
|
||||||
use std::net::Shutdown;
|
|
||||||
|
|
||||||
use http::hyper::{HyperBodyReader, HyperHttpStream, HyperHttpReader};
|
mod from_data;
|
||||||
|
mod data_stream;
|
||||||
|
|
||||||
|
pub use self::from_data::{FromData, DataOutcome};
|
||||||
|
|
||||||
|
use std::io::{self, BufRead, Read, Write, Cursor, BufReader};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::mem::transmute;
|
||||||
|
|
||||||
|
use self::data_stream::{DataStream, StreamReader, kill_stream};
|
||||||
|
use request::Request;
|
||||||
|
use http::hyper::{HyperBodyReader, HyperHttpStream};
|
||||||
use http::hyper::HyperNetworkStream;
|
use http::hyper::HyperNetworkStream;
|
||||||
use http::hyper::HyperHttpReader::*;
|
use http::hyper::HyperHttpReader::*;
|
||||||
|
|
||||||
type StreamReader = HyperHttpReader<HyperHttpStream>;
|
|
||||||
|
|
||||||
pub struct DataStream {
|
|
||||||
stream: Chain<Take<Cursor<Vec<u8>>>, BufReader<StreamReader>>,
|
|
||||||
network: HyperHttpStream,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for DataStream {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
self.stream.read(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BufRead for DataStream {
|
|
||||||
fn fill_buf(&mut self) -> io::Result<&[u8]> {
|
|
||||||
self.stream.fill_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn consume(&mut self, amt: usize) {
|
|
||||||
self.stream.consume(amt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_sinking<N: HyperNetworkStream>(net: &mut N) -> bool {
|
|
||||||
warn_!("Data left unread. Sinking 1k bytes.");
|
|
||||||
io::copy(&mut net.take(1024), &mut io::sink()).expect("sink");
|
|
||||||
|
|
||||||
// If there are any more bytes, kill it.
|
|
||||||
let mut buf = [0];
|
|
||||||
if let Ok(n) = net.read(&mut buf) {
|
|
||||||
if n > 0 {
|
|
||||||
warn_!("Data still remains. Force closing network stream.");
|
|
||||||
return net.close(Shutdown::Both).is_ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for DataStream {
|
|
||||||
// Be a bad citizen and close the TCP stream if there's unread data.
|
|
||||||
// Unfortunately, Hyper forces us to do this.
|
|
||||||
fn drop(&mut self) {
|
|
||||||
try_sinking(&mut self.network);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
buffer: Vec<u8>,
|
buffer: Vec<u8>,
|
||||||
|
is_done: bool,
|
||||||
stream: StreamReader,
|
stream: StreamReader,
|
||||||
position: usize,
|
position: usize,
|
||||||
capacity: usize,
|
capacity: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Data {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
try_sinking(self.stream.get_mut());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Data {
|
impl Data {
|
||||||
pub fn open(mut self) -> impl BufRead {
|
pub fn open(mut self) -> impl BufRead {
|
||||||
// Swap out the buffer and stream for empty ones so we can move.
|
// Swap out the buffer and stream for empty ones so we can move.
|
||||||
|
@ -115,19 +74,88 @@ impl Data {
|
||||||
Ok(Data::new(vec, pos, cap, stream))
|
Ok(Data::new(vec, pos, cap, stream))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
pub fn peek(&self) -> &[u8] {
|
pub fn peek(&self) -> &[u8] {
|
||||||
&self.buffer[self.position..self.capacity]
|
&self.buffer[self.position..self.capacity]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(buf: Vec<u8>, pos: usize, cap: usize, stream: StreamReader) -> Data {
|
#[inline(always)]
|
||||||
|
pub fn peek_complete(&self) -> bool {
|
||||||
|
self.is_done
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn stream_to<W: Write>(self, writer: &mut W) -> io::Result<u64> {
|
||||||
|
io::copy(&mut self.open(), writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn stream_to_file<P: AsRef<Path>>(self, path: P) -> io::Result<u64> {
|
||||||
|
io::copy(&mut self.open(), &mut File::create(path)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(mut buf: Vec<u8>,
|
||||||
|
pos: usize,
|
||||||
|
mut cap: usize,
|
||||||
|
mut stream: StreamReader)
|
||||||
|
-> Data {
|
||||||
// TODO: Make sure we always try to get some number of bytes in the
|
// TODO: Make sure we always try to get some number of bytes in the
|
||||||
// buffer so that peek actually does something.
|
// buffer so that peek actually does something.
|
||||||
// const PEEK_BYTES: usize = 4096;
|
|
||||||
|
// Make sure the buffer is large enough for the bytes we want to peek.
|
||||||
|
const PEEK_BYTES: usize = 4096;
|
||||||
|
if buf.len() < PEEK_BYTES {
|
||||||
|
trace!("Resizing peek buffer from {} to {}.", buf.len(), PEEK_BYTES);
|
||||||
|
buf.resize(PEEK_BYTES, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!("Init buffer cap: {}", cap);
|
||||||
|
let buf_len = buf.len();
|
||||||
|
let eof = match stream.read(&mut buf[cap..(buf_len - 1)]) {
|
||||||
|
Ok(n) if n == 0 => true,
|
||||||
|
Ok(n) => {
|
||||||
|
trace!("Filled peek buf with {} bytes.", n);
|
||||||
|
cap += n;
|
||||||
|
match stream.read(&mut buf[cap..(cap + 1)]) {
|
||||||
|
Ok(n) => {
|
||||||
|
cap += n;
|
||||||
|
n == 0
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error_!("Failed to check stream EOF status: {:?}", e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error_!("Failed to read into peek buffer: {:?}", e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
trace!("Peek buffer size: {}, remaining: {}", buf_len, buf_len - cap);
|
||||||
Data {
|
Data {
|
||||||
buffer: buf,
|
buffer: buf,
|
||||||
stream: stream,
|
stream: stream,
|
||||||
|
is_done: eof,
|
||||||
position: pos,
|
position: pos,
|
||||||
capacity: cap,
|
capacity: cap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for Data {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// This is okay since the network stream expects to be shared mutably.
|
||||||
|
unsafe {
|
||||||
|
let stream: &mut StreamReader = transmute(self.stream.by_ref());
|
||||||
|
kill_stream(stream, self.stream.get_mut());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromData for Data {
|
||||||
|
fn from_data(_: &Request, data: Data) -> DataOutcome<Self, Self::Error> {
|
||||||
|
DataOutcome::Success(data)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,298 +0,0 @@
|
||||||
//! Types and traits to handle form processing.
|
|
||||||
//!
|
|
||||||
//! In general, you will deal with forms in Rocket via the `form` parameter in
|
|
||||||
//! routes:
|
|
||||||
//!
|
|
||||||
//! ```rust,ignore
|
|
||||||
//! #[post("/", form = <my_form>)]
|
|
||||||
//! fn form_submit(my_form: MyType) -> ...
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Form parameter types must implement the [FromForm](trait.FromForm.html)
|
|
||||||
//! trait, which is automatically derivable. Automatically deriving `FromForm`
|
|
||||||
//! for a structure requires that all of its fields implement
|
|
||||||
//! [FromFormValue](trait.FormFormValue.html). See the
|
|
||||||
//! [codegen](/rocket_codegen/) documentation or the [forms guide](/guide/forms)
|
|
||||||
//! for more information on forms and on deriving `FromForm`.
|
|
||||||
|
|
||||||
use url;
|
|
||||||
use error::Error;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr};
|
|
||||||
|
|
||||||
/// Trait to create instance of some type from an HTTP form; used by code
|
|
||||||
/// generation for `form` route parameters.
|
|
||||||
///
|
|
||||||
/// This trait can be automatically derived via the
|
|
||||||
/// [rocket_codegen](/rocket_codegen) plugin:
|
|
||||||
///
|
|
||||||
/// ```rust,ignore
|
|
||||||
/// #![feature(plugin, custom_derive)]
|
|
||||||
/// #![plugin(rocket_codegen)]
|
|
||||||
///
|
|
||||||
/// extern crate rocket;
|
|
||||||
///
|
|
||||||
/// #[derive(FromForm)]
|
|
||||||
/// struct TodoTask {
|
|
||||||
/// description: String,
|
|
||||||
/// completed: bool
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// When deriving `FromForm`, every field in the structure must implement
|
|
||||||
/// [FromFormValue](trait.FromFormValue.html). If you implement `FormForm`
|
|
||||||
/// yourself, use the [FormItems](struct.FormItems.html) iterator to iterate
|
|
||||||
/// through the form key/value pairs.
|
|
||||||
pub trait FromForm<'f>: Sized {
|
|
||||||
/// The associated error which can be returned from parsing.
|
|
||||||
type Error;
|
|
||||||
|
|
||||||
/// Parses an instance of `Self` from a raw HTTP form
|
|
||||||
/// (`application/x-www-form-urlencoded data`) or returns an `Error` if one
|
|
||||||
/// cannot be parsed.
|
|
||||||
fn from_form_string(form_string: &'f str) -> Result<Self, Self::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This implementation should only be used during debugging!
|
|
||||||
impl<'f> FromForm<'f> for &'f str {
|
|
||||||
type Error = Error;
|
|
||||||
fn from_form_string(s: &'f str) -> Result<Self, Error> {
|
|
||||||
Ok(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait to create instance of some type from a form value; expected from field
|
|
||||||
/// types in structs deriving `FromForm`.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// This trait is generally implemented when verifying form inputs. For example,
|
|
||||||
/// if you'd like to verify that some user is over some age in a form, then you
|
|
||||||
/// might define a new type and implement `FromFormValue` as follows:
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use rocket::request::FromFormValue;
|
|
||||||
/// use rocket::Error;
|
|
||||||
///
|
|
||||||
/// struct AdultAge(usize);
|
|
||||||
///
|
|
||||||
/// impl<'v> FromFormValue<'v> for AdultAge {
|
|
||||||
/// type Error = &'v str;
|
|
||||||
///
|
|
||||||
/// fn from_form_value(form_value: &'v str) -> Result<AdultAge, &'v str> {
|
|
||||||
/// match usize::from_form_value(form_value) {
|
|
||||||
/// Ok(age) if age >= 21 => Ok(AdultAge(age)),
|
|
||||||
/// _ => Err(form_value),
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// This type can then be used in a `FromForm` struct as follows:
|
|
||||||
///
|
|
||||||
/// ```rust,ignore
|
|
||||||
/// #[derive(FromForm)]
|
|
||||||
/// struct User {
|
|
||||||
/// age: AdultAge,
|
|
||||||
/// ...
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub trait FromFormValue<'v>: Sized {
|
|
||||||
/// The associated error which can be returned from parsing. It is a good
|
|
||||||
/// idea to have the return type be or contain an `&'v str` so that the
|
|
||||||
/// unparseable string can be examined after a bad parse.
|
|
||||||
type Error;
|
|
||||||
|
|
||||||
/// Parses an instance of `Self` from an HTTP form field value or returns an
|
|
||||||
/// `Error` if one cannot be parsed.
|
|
||||||
fn from_form_value(form_value: &'v str) -> Result<Self, Self::Error>;
|
|
||||||
|
|
||||||
/// Returns a default value to be used when the form field does not exist.
|
|
||||||
/// If this returns None, then the field is required. Otherwise, this should
|
|
||||||
/// return Some(default_value).
|
|
||||||
fn default() -> Option<Self> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'v> FromFormValue<'v> for &'v str {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
// This just gives the raw string.
|
|
||||||
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
|
|
||||||
Ok(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'v> FromFormValue<'v> for String {
|
|
||||||
type Error = &'v str;
|
|
||||||
|
|
||||||
// This actually parses the value according to the standard.
|
|
||||||
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
|
|
||||||
let decoder = url::percent_encoding::percent_decode(v.as_bytes());
|
|
||||||
let res = decoder.decode_utf8().map_err(|_| v).map(|s| s.into_owned());
|
|
||||||
match res {
|
|
||||||
e@Err(_) => e,
|
|
||||||
Ok(mut string) => Ok({
|
|
||||||
unsafe {
|
|
||||||
for c in string.as_mut_vec() {
|
|
||||||
if *c == b'+' {
|
|
||||||
*c = b' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'v> FromFormValue<'v> for bool {
|
|
||||||
type Error = &'v str;
|
|
||||||
|
|
||||||
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
|
|
||||||
match v {
|
|
||||||
"on" | "true" => Ok(true),
|
|
||||||
"off" | "false" => Ok(false),
|
|
||||||
_ => Err(v),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_with_fromstr {
|
|
||||||
($($T:ident),+) => ($(
|
|
||||||
impl<'v> FromFormValue<'v> for $T {
|
|
||||||
type Error = &'v str;
|
|
||||||
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
|
|
||||||
$T::from_str(v).map_err(|_| v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)+)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_with_fromstr!(f32, f64, isize, i8, i16, i32, i64, usize, u8, u16, u32, u64,
|
|
||||||
IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr);
|
|
||||||
|
|
||||||
impl<'v, T: FromFormValue<'v>> FromFormValue<'v> for Option<T> {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
|
|
||||||
match T::from_form_value(v) {
|
|
||||||
Ok(v) => Ok(Some(v)),
|
|
||||||
Err(_) => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default() -> Option<Option<T>> {
|
|
||||||
Some(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Add more useful implementations (range, regex, etc.).
|
|
||||||
impl<'v, T: FromFormValue<'v>> FromFormValue<'v> for Result<T, T::Error> {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
|
|
||||||
match T::from_form_value(v) {
|
|
||||||
ok@Ok(_) => Ok(ok),
|
|
||||||
e@Err(_) => Ok(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterator over the key/value pairs of a given HTTP form string. You'll likely
|
|
||||||
/// want to use this if you're implementing [FromForm](trait.FromForm.html)
|
|
||||||
/// manually, for whatever reason, by iterating over the items in `form_string`.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// `FormItems` can be used directly as an iterator:
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use rocket::request::FormItems;
|
|
||||||
///
|
|
||||||
/// // prints "greeting = hello" then "username = jake"
|
|
||||||
/// let form_string = "greeting=hello&username=jake";
|
|
||||||
/// for (key, value) in FormItems(form_string) {
|
|
||||||
/// println!("{} = {}", key, value);
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// This is the same example as above, but the iterator is used explicitly.
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use rocket::request::FormItems;
|
|
||||||
///
|
|
||||||
/// let form_string = "greeting=hello&username=jake";
|
|
||||||
/// let mut items = FormItems(form_string);
|
|
||||||
/// assert_eq!(items.next(), Some(("greeting", "hello")));
|
|
||||||
/// assert_eq!(items.next(), Some(("username", "jake")));
|
|
||||||
/// assert_eq!(items.next(), None);
|
|
||||||
/// ```
|
|
||||||
pub struct FormItems<'f>(pub &'f str);
|
|
||||||
|
|
||||||
impl<'f> Iterator for FormItems<'f> {
|
|
||||||
type Item = (&'f str, &'f str);
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let string = self.0;
|
|
||||||
let (key, rest) = match string.find('=') {
|
|
||||||
Some(index) => (&string[..index], &string[(index + 1)..]),
|
|
||||||
None => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (value, remainder) = match rest.find('&') {
|
|
||||||
Some(index) => (&rest[..index], &rest[(index + 1)..]),
|
|
||||||
None => (rest, ""),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.0 = remainder;
|
|
||||||
Some((key, value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::FormItems;
|
|
||||||
|
|
||||||
macro_rules! check_form {
|
|
||||||
($string:expr, $expected: expr) => ({
|
|
||||||
let results: Vec<(&str, &str)> = FormItems($string).collect();
|
|
||||||
assert_eq!($expected.len(), results.len());
|
|
||||||
|
|
||||||
for i in 0..results.len() {
|
|
||||||
let (expected_key, actual_key) = ($expected[i].0, results[i].0);
|
|
||||||
let (expected_val, actual_val) = ($expected[i].1, results[i].1);
|
|
||||||
|
|
||||||
assert!(expected_key == actual_key,
|
|
||||||
"key [{}] mismatch: expected {}, got {}",
|
|
||||||
i, expected_key, actual_key);
|
|
||||||
|
|
||||||
assert!(expected_val == actual_val,
|
|
||||||
"val [{}] mismatch: expected {}, got {}",
|
|
||||||
i, expected_val, actual_val);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_form_string() {
|
|
||||||
check_form!("username=user&password=pass",
|
|
||||||
&[("username", "user"), ("password", "pass")]);
|
|
||||||
|
|
||||||
check_form!("user=user&user=pass",
|
|
||||||
&[("user", "user"), ("user", "pass")]);
|
|
||||||
|
|
||||||
check_form!("user=&password=pass",
|
|
||||||
&[("user", ""), ("password", "pass")]);
|
|
||||||
|
|
||||||
check_form!("=&=", &[("", ""), ("", "")]);
|
|
||||||
|
|
||||||
check_form!("a=b", &[("a", "b")]);
|
|
||||||
|
|
||||||
check_form!("a=b&a", &[("a", "b")]);
|
|
||||||
|
|
||||||
check_form!("a=b&a=", &[("a", "b"), ("a", "")]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/// Iterator over the key/value pairs of a given HTTP form string. You'll likely
|
||||||
|
/// want to use this if you're implementing [FromForm](trait.FromForm.html)
|
||||||
|
/// manually, for whatever reason, by iterating over the items in `form_string`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// `FormItems` can be used directly as an iterator:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rocket::request::FormItems;
|
||||||
|
///
|
||||||
|
/// // prints "greeting = hello" then "username = jake"
|
||||||
|
/// let form_string = "greeting=hello&username=jake";
|
||||||
|
/// for (key, value) in FormItems(form_string) {
|
||||||
|
/// println!("{} = {}", key, value);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This is the same example as above, but the iterator is used explicitly.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rocket::request::FormItems;
|
||||||
|
///
|
||||||
|
/// let form_string = "greeting=hello&username=jake";
|
||||||
|
/// let mut items = FormItems(form_string);
|
||||||
|
/// assert_eq!(items.next(), Some(("greeting", "hello")));
|
||||||
|
/// assert_eq!(items.next(), Some(("username", "jake")));
|
||||||
|
/// assert_eq!(items.next(), None);
|
||||||
|
/// ```
|
||||||
|
pub struct FormItems<'f>(pub &'f str);
|
||||||
|
|
||||||
|
impl<'f> Iterator for FormItems<'f> {
|
||||||
|
type Item = (&'f str, &'f str);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let string = self.0;
|
||||||
|
let (key, rest) = match string.find('=') {
|
||||||
|
Some(index) => (&string[..index], &string[(index + 1)..]),
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (value, remainder) = match rest.find('&') {
|
||||||
|
Some(index) => (&rest[..index], &rest[(index + 1)..]),
|
||||||
|
None => (rest, ""),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.0 = remainder;
|
||||||
|
Some((key, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::FormItems;
|
||||||
|
|
||||||
|
macro_rules! check_form {
|
||||||
|
($string:expr, $expected: expr) => ({
|
||||||
|
let results: Vec<(&str, &str)> = FormItems($string).collect();
|
||||||
|
assert_eq!($expected.len(), results.len());
|
||||||
|
|
||||||
|
for i in 0..results.len() {
|
||||||
|
let (expected_key, actual_key) = ($expected[i].0, results[i].0);
|
||||||
|
let (expected_val, actual_val) = ($expected[i].1, results[i].1);
|
||||||
|
|
||||||
|
assert!(expected_key == actual_key,
|
||||||
|
"key [{}] mismatch: expected {}, got {}",
|
||||||
|
i, expected_key, actual_key);
|
||||||
|
|
||||||
|
assert!(expected_val == actual_val,
|
||||||
|
"val [{}] mismatch: expected {}, got {}",
|
||||||
|
i, expected_val, actual_val);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_form_string() {
|
||||||
|
check_form!("username=user&password=pass",
|
||||||
|
&[("username", "user"), ("password", "pass")]);
|
||||||
|
|
||||||
|
check_form!("user=user&user=pass",
|
||||||
|
&[("user", "user"), ("user", "pass")]);
|
||||||
|
|
||||||
|
check_form!("user=&password=pass",
|
||||||
|
&[("user", ""), ("password", "pass")]);
|
||||||
|
|
||||||
|
check_form!("=&=", &[("", ""), ("", "")]);
|
||||||
|
|
||||||
|
check_form!("a=b", &[("a", "b")]);
|
||||||
|
|
||||||
|
check_form!("a=b&a", &[("a", "b")]);
|
||||||
|
|
||||||
|
check_form!("a=b&a=", &[("a", "b"), ("a", "")]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
use error::Error;
|
||||||
|
|
||||||
|
/// Trait to create instance of some type from an HTTP form; used by code
|
||||||
|
/// generation for `form` route parameters.
|
||||||
|
///
|
||||||
|
/// This trait can be automatically derived via the
|
||||||
|
/// [rocket_codegen](/rocket_codegen) plugin:
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// #![feature(plugin, custom_derive)]
|
||||||
|
/// #![plugin(rocket_codegen)]
|
||||||
|
///
|
||||||
|
/// extern crate rocket;
|
||||||
|
///
|
||||||
|
/// #[derive(FromForm)]
|
||||||
|
/// struct TodoTask {
|
||||||
|
/// description: String,
|
||||||
|
/// completed: bool
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// When deriving `FromForm`, every field in the structure must implement
|
||||||
|
/// [FromFormValue](trait.FromFormValue.html). If you implement `FormForm`
|
||||||
|
/// yourself, use the [FormItems](struct.FormItems.html) iterator to iterate
|
||||||
|
/// through the form key/value pairs.
|
||||||
|
pub trait FromForm<'f>: Sized {
|
||||||
|
/// The associated error which can be returned from parsing.
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
/// Parses an instance of `Self` from a raw HTTP form
|
||||||
|
/// (`application/x-www-form-urlencoded data`) or returns an `Error` if one
|
||||||
|
/// cannot be parsed.
|
||||||
|
fn from_form_string(form_string: &'f str) -> Result<Self, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This implementation should only be used during debugging!
|
||||||
|
impl<'f> FromForm<'f> for &'f str {
|
||||||
|
type Error = Error;
|
||||||
|
fn from_form_string(s: &'f str) -> Result<Self, Error> {
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
use url;
|
||||||
|
|
||||||
|
use error::Error;
|
||||||
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
/// Trait to create instance of some type from a form value; expected from field
|
||||||
|
/// types in structs deriving `FromForm`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// This trait is generally implemented when verifying form inputs. For example,
|
||||||
|
/// if you'd like to verify that some user is over some age in a form, then you
|
||||||
|
/// might define a new type and implement `FromFormValue` as follows:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rocket::request::FromFormValue;
|
||||||
|
/// use rocket::Error;
|
||||||
|
///
|
||||||
|
/// struct AdultAge(usize);
|
||||||
|
///
|
||||||
|
/// impl<'v> FromFormValue<'v> for AdultAge {
|
||||||
|
/// type Error = &'v str;
|
||||||
|
///
|
||||||
|
/// fn from_form_value(form_value: &'v str) -> Result<AdultAge, &'v str> {
|
||||||
|
/// match usize::from_form_value(form_value) {
|
||||||
|
/// Ok(age) if age >= 21 => Ok(AdultAge(age)),
|
||||||
|
/// _ => Err(form_value),
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This type can then be used in a `FromForm` struct as follows:
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// #[derive(FromForm)]
|
||||||
|
/// struct User {
|
||||||
|
/// age: AdultAge,
|
||||||
|
/// ...
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub trait FromFormValue<'v>: Sized {
|
||||||
|
/// The associated error which can be returned from parsing. It is a good
|
||||||
|
/// idea to have the return type be or contain an `&'v str` so that the
|
||||||
|
/// unparseable string can be examined after a bad parse.
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
/// Parses an instance of `Self` from an HTTP form field value or returns an
|
||||||
|
/// `Error` if one cannot be parsed.
|
||||||
|
fn from_form_value(form_value: &'v str) -> Result<Self, Self::Error>;
|
||||||
|
|
||||||
|
/// Returns a default value to be used when the form field does not exist.
|
||||||
|
/// If this returns None, then the field is required. Otherwise, this should
|
||||||
|
/// return Some(default_value).
|
||||||
|
fn default() -> Option<Self> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'v> FromFormValue<'v> for &'v str {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
// This just gives the raw string.
|
||||||
|
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'v> FromFormValue<'v> for String {
|
||||||
|
type Error = &'v str;
|
||||||
|
|
||||||
|
// This actually parses the value according to the standard.
|
||||||
|
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
|
||||||
|
let decoder = url::percent_encoding::percent_decode(v.as_bytes());
|
||||||
|
let res = decoder.decode_utf8().map_err(|_| v).map(|s| s.into_owned());
|
||||||
|
match res {
|
||||||
|
e@Err(_) => e,
|
||||||
|
Ok(mut string) => Ok({
|
||||||
|
unsafe {
|
||||||
|
for c in string.as_mut_vec() {
|
||||||
|
if *c == b'+' {
|
||||||
|
*c = b' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'v> FromFormValue<'v> for bool {
|
||||||
|
type Error = &'v str;
|
||||||
|
|
||||||
|
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
|
||||||
|
match v {
|
||||||
|
"on" | "true" => Ok(true),
|
||||||
|
"off" | "false" => Ok(false),
|
||||||
|
_ => Err(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_with_fromstr {
|
||||||
|
($($T:ident),+) => ($(
|
||||||
|
impl<'v> FromFormValue<'v> for $T {
|
||||||
|
type Error = &'v str;
|
||||||
|
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
|
||||||
|
$T::from_str(v).map_err(|_| v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_with_fromstr!(f32, f64, isize, i8, i16, i32, i64, usize, u8, u16, u32, u64,
|
||||||
|
IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr);
|
||||||
|
|
||||||
|
impl<'v, T: FromFormValue<'v>> FromFormValue<'v> for Option<T> {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
|
||||||
|
match T::from_form_value(v) {
|
||||||
|
Ok(v) => Ok(Some(v)),
|
||||||
|
Err(_) => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default() -> Option<Option<T>> {
|
||||||
|
Some(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add more useful implementations (range, regex, etc.).
|
||||||
|
impl<'v, T: FromFormValue<'v>> FromFormValue<'v> for Result<T, T::Error> {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
|
||||||
|
match T::from_form_value(v) {
|
||||||
|
ok@Ok(_) => Ok(ok),
|
||||||
|
e@Err(_) => Ok(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
//! Types and traits to handle form processing.
|
||||||
|
//!
|
||||||
|
//! In general, you will deal with forms in Rocket via the `form` parameter in
|
||||||
|
//! routes:
|
||||||
|
//!
|
||||||
|
//! ```rust,ignore
|
||||||
|
//! #[post("/", form = <my_form>)]
|
||||||
|
//! fn form_submit(my_form: MyType) -> ...
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Form parameter types must implement the [FromForm](trait.FromForm.html)
|
||||||
|
//! trait, which is automatically derivable. Automatically deriving `FromForm`
|
||||||
|
//! for a structure requires that all of its fields implement
|
||||||
|
//! [FromFormValue](trait.FormFormValue.html). See the
|
||||||
|
//! [codegen](/rocket_codegen/) documentation or the [forms guide](/guide/forms)
|
||||||
|
//! for more information on forms and on deriving `FromForm`.
|
||||||
|
|
||||||
|
mod form_items;
|
||||||
|
mod from_form;
|
||||||
|
mod from_form_value;
|
||||||
|
|
||||||
|
pub use self::form_items::FormItems;
|
||||||
|
pub use self::from_form::FromForm;
|
||||||
|
pub use self::from_form_value::FromFormValue;
|
||||||
|
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::fmt::{self, Debug};
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
use request::{Request, FromData, Data, DataOutcome};
|
||||||
|
|
||||||
|
// This works, and it's safe, but it sucks to have the lifetime appear twice.
|
||||||
|
pub struct Form<'f, T: FromForm<'f> + 'f> {
|
||||||
|
object: T,
|
||||||
|
form_string: String,
|
||||||
|
_phantom: PhantomData<&'f T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'f, T: FromForm<'f> + 'f> Form<'f, T> {
|
||||||
|
pub fn get(&'f self) -> &'f T {
|
||||||
|
&self.object
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&'f mut self) -> &'f mut T {
|
||||||
|
&mut self.object
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raw_form_string(&self) -> &str {
|
||||||
|
&self.form_string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alright, so here's what's going on here. We'd like to have form
|
||||||
|
// objects have pointers directly to the form string. This means that
|
||||||
|
// the form string has to live at least as long as the form object. So,
|
||||||
|
// to enforce this, we store the form_string along with the form object.
|
||||||
|
//
|
||||||
|
// So far so good. Now, this means that the form_string can never be
|
||||||
|
// deallocated while the object is alive. That implies that the
|
||||||
|
// `form_string` value should never be moved away. We can enforce that
|
||||||
|
// easily by 1) not making `form_string` public, and 2) not exposing any
|
||||||
|
// `&mut self` methods that could modify `form_string`.
|
||||||
|
//
|
||||||
|
// Okay, we do all of these things. Now, we still need to give a
|
||||||
|
// lifetime to `FromForm`. Which one do we choose? The danger is that
|
||||||
|
// references inside `object` may be copied out, and we have to ensure
|
||||||
|
// that they don't outlive this structure. So we would really like
|
||||||
|
// something like `self` and then to transmute to that. But this doesn't
|
||||||
|
// exist. So we do the next best: we use the first lifetime supplied by the
|
||||||
|
// caller via `get()` and contrain everything to that lifetime. This is, in
|
||||||
|
// reality a little coarser than necessary, but the user can simply move the
|
||||||
|
// call to right after the creation of a Form object to get the same effect.
|
||||||
|
fn new(form_string: String) -> Result<Self, (String, T::Error)> {
|
||||||
|
let long_lived_string: &'f str = unsafe {
|
||||||
|
::std::mem::transmute(form_string.as_str())
|
||||||
|
};
|
||||||
|
|
||||||
|
match T::from_form_string(long_lived_string) {
|
||||||
|
Ok(obj) => Ok(Form {
|
||||||
|
form_string: form_string,
|
||||||
|
object: obj,
|
||||||
|
_phantom: PhantomData
|
||||||
|
}),
|
||||||
|
Err(e) => Err((form_string, e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'f, T: FromForm<'f> + 'static> Form<'f, T> {
|
||||||
|
pub fn into_inner(self) -> T {
|
||||||
|
self.object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'f, T: FromForm<'f> + Debug + 'f> Debug for Form<'f, T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{:?} from form string: {:?}", self.object, self.form_string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'f, T: FromForm<'f>> FromData for Form<'f, T> where T::Error: Debug {
|
||||||
|
type Error = Option<String>;
|
||||||
|
|
||||||
|
fn from_data(request: &Request, data: Data) -> DataOutcome<Self, Self::Error> {
|
||||||
|
if !request.content_type().is_form() {
|
||||||
|
warn_!("Form data does not have form content type.");
|
||||||
|
return DataOutcome::Forward(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut form_string = String::with_capacity(4096);
|
||||||
|
let mut stream = data.open().take(32768);
|
||||||
|
if let Err(e) = stream.read_to_string(&mut form_string) {
|
||||||
|
error_!("IO Error: {:?}", e);
|
||||||
|
DataOutcome::Failure(None)
|
||||||
|
} else {
|
||||||
|
match Form::new(form_string) {
|
||||||
|
Ok(form) => DataOutcome::Success(form),
|
||||||
|
Err((form_string, e)) => {
|
||||||
|
error_!("Failed to parse value from form: {:?}", e);
|
||||||
|
DataOutcome::Failure(Some(form_string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::Form;
|
||||||
|
use ::request::FromForm;
|
||||||
|
|
||||||
|
struct Simple<'s> {
|
||||||
|
value: &'s str
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Other {
|
||||||
|
value: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> FromForm<'s> for Simple<'s> {
|
||||||
|
type Error = &'s str;
|
||||||
|
|
||||||
|
fn from_form_string(fs: &'s str) -> Result<Simple<'s>, &'s str> {
|
||||||
|
Ok(Simple { value: fs })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> FromForm<'s> for Other {
|
||||||
|
type Error = &'s str;
|
||||||
|
|
||||||
|
fn from_form_string(fs: &'s str) -> Result<Other, &'s str> {
|
||||||
|
Ok(Other { value: fs.to_string() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lifetime() {
|
||||||
|
let form_string = "hello=world".to_string();
|
||||||
|
let form: Form<Simple> = Form::new(form_string).unwrap();
|
||||||
|
|
||||||
|
let string: &str = form.get().value;
|
||||||
|
assert_eq!(string, "hello=world");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lifetime_2() {
|
||||||
|
let form_string = "hello=world".to_string();
|
||||||
|
let mut _y = "hi";
|
||||||
|
let _form: Form<Simple> = Form::new(form_string).unwrap();
|
||||||
|
// _y = form.get().value;
|
||||||
|
|
||||||
|
// fn should_not_compile<'f>(form: Form<'f, &'f str>) -> &'f str {
|
||||||
|
// form.get()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn should_not_compile_2<'f>(form: Form<'f, Simple<'f>>) -> &'f str {
|
||||||
|
// form.into_inner().value
|
||||||
|
// }
|
||||||
|
|
||||||
|
// assert_eq!(should_not_compile(form), "hello=world");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lifetime_3() {
|
||||||
|
let form_string = "hello=world".to_string();
|
||||||
|
let form: Form<Other> = Form::new(form_string).unwrap();
|
||||||
|
|
||||||
|
// Not bad.
|
||||||
|
fn should_compile(form: Form<Other>) -> String {
|
||||||
|
form.into_inner().value
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(should_compile(form), "hello=world".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lifetime_4() {
|
||||||
|
let form_string = "hello=world".to_string();
|
||||||
|
let form: Form<Simple> = Form::new(form_string).unwrap();
|
||||||
|
|
||||||
|
fn should_compile<'f>(_form: Form<'f, Simple<'f>>) { }
|
||||||
|
|
||||||
|
should_compile(form)
|
||||||
|
// assert_eq!(should_not_compile(form), "hello=world");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,5 +9,5 @@ mod from_request;
|
||||||
pub use self::request::Request;
|
pub use self::request::Request;
|
||||||
pub use self::from_request::FromRequest;
|
pub use self::from_request::FromRequest;
|
||||||
pub use self::param::{FromParam, FromSegments};
|
pub use self::param::{FromParam, FromSegments};
|
||||||
pub use self::form::{FromForm, FromFormValue, FormItems};
|
pub use self::form::{Form, FromForm, FromFormValue, FormItems};
|
||||||
pub use self::data::{Data};
|
pub use self::data::{Data, FromData, DataOutcome};
|
||||||
|
|
|
@ -31,7 +31,6 @@ pub struct Request {
|
||||||
/// </div>
|
/// </div>
|
||||||
///
|
///
|
||||||
/// The data in the request.
|
/// The data in the request.
|
||||||
pub data: Vec<u8>, // FIXME: Don't read this! (bad Hyper.)
|
|
||||||
uri: URIBuf, // FIXME: Should be URI (without Hyper).
|
uri: URIBuf, // FIXME: Should be URI (without Hyper).
|
||||||
params: RefCell<Vec<&'static str>>,
|
params: RefCell<Vec<&'static str>>,
|
||||||
cookies: Cookies,
|
cookies: Cookies,
|
||||||
|
@ -104,7 +103,6 @@ impl Request {
|
||||||
method: method,
|
method: method,
|
||||||
cookies: Cookies::new(&[]),
|
cookies: Cookies::new(&[]),
|
||||||
uri: URIBuf::from(uri),
|
uri: URIBuf::from(uri),
|
||||||
data: vec![],
|
|
||||||
headers: HyperHeaders::new(),
|
headers: HyperHeaders::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +209,6 @@ impl Request {
|
||||||
method: method,
|
method: method,
|
||||||
cookies: cookies,
|
cookies: cookies,
|
||||||
uri: uri,
|
uri: uri,
|
||||||
data: vec![], // TODO: Remove me.
|
|
||||||
headers: h_headers,
|
headers: h_headers,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
use response::{ResponseOutcome, Outcome, Responder};
|
use response::{ResponseOutcome, Outcome, Responder};
|
||||||
use http::hyper::{FreshHyperResponse, StatusCode};
|
use http::hyper::{FreshHyperResponse, StatusCode};
|
||||||
|
|
||||||
pub struct Failure(StatusCode);
|
#[derive(Debug)]
|
||||||
|
pub struct Failure(pub StatusCode);
|
||||||
impl Failure {
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn new(status: StatusCode) -> Failure {
|
|
||||||
Failure(status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Responder for Failure {
|
impl Responder for Failure {
|
||||||
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> {
|
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> {
|
||||||
|
|
|
@ -44,7 +44,7 @@ impl<'a> Response<'a> {
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn failed(code: StatusCode) -> Response<'static> {
|
pub fn failed(code: StatusCode) -> Response<'static> {
|
||||||
Response::complete(Failure::new(code))
|
Response::complete(Failure(code))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
|
|
@ -72,7 +72,7 @@ impl Rocket {
|
||||||
self.preprocess_request(&mut request, &data);
|
self.preprocess_request(&mut request, &data);
|
||||||
|
|
||||||
info!("{}:", request);
|
info!("{}:", request);
|
||||||
info_!("Peek size: {} bytes", data.peek().len());
|
trace_!("Peek size: {} bytes", data.peek().len());
|
||||||
let matches = self.router.route(&request);
|
let matches = self.router.route(&request);
|
||||||
for route in matches {
|
for route in matches {
|
||||||
// Retrieve and set the requests parameters.
|
// Retrieve and set the requests parameters.
|
||||||
|
@ -180,9 +180,9 @@ impl Rocket {
|
||||||
pub fn catch(mut self, catchers: Vec<Catcher>) -> Self {
|
pub fn catch(mut self, catchers: Vec<Catcher>) -> Self {
|
||||||
info!("👾 {}:", Magenta.paint("Catchers"));
|
info!("👾 {}:", Magenta.paint("Catchers"));
|
||||||
for c in catchers {
|
for c in catchers {
|
||||||
if self.catchers.get(&c.code).map_or(false, |e| e.is_default()) {
|
if self.catchers.get(&c.code).map_or(false, |e| !e.is_default()) {
|
||||||
let msg = format!("warning: overrides {} catcher!", c.code);
|
let msg = "(warning: duplicate catcher!)";
|
||||||
warn!("{} ({})", c, Yellow.paint(msg.as_str()));
|
info_!("{} {}", c, Yellow.paint(msg));
|
||||||
} else {
|
} else {
|
||||||
info_!("{}", c);
|
info_!("{}", c);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,10 @@ function build_and_test() {
|
||||||
|
|
||||||
pushd ${dir}
|
pushd ${dir}
|
||||||
echo ":: Building '${PWD}'..."
|
echo ":: Building '${PWD}'..."
|
||||||
RUST_BACKTRACE=1 cargo build
|
RUST_BACKTRACE=1 cargo build --all-features
|
||||||
|
|
||||||
echo ":: Running unit tests in '${PWD}'..."
|
echo ":: Running unit tests in '${PWD}'..."
|
||||||
RUST_BACKTRACE=1 cargo test
|
RUST_BACKTRACE=1 cargo test --all-features
|
||||||
popd
|
popd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue