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/config",
|
||||
"examples/hello_alt_methods",
|
||||
"examples/raw_upload",
|
||||
]
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#![allow(unused_imports)] // FIXME: Why is this coming from quote_tokens?
|
||||
|
||||
use std::mem::transmute;
|
||||
|
||||
use syntax::ext::base::{Annotatable, ExtCtxt};
|
||||
use syntax::print::pprust::{stmt_to_string};
|
||||
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::ext::build::AstBuilder;
|
||||
use syntax::ptr::P;
|
||||
use std::mem::transmute;
|
||||
|
||||
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;
|
||||
|
||||
static ONLY_STRUCTS_ERR: &'static str = "`FromForm` can only be derived for \
|
||||
structures with named fields.";
|
||||
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> {
|
||||
// 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]
|
||||
} else {
|
||||
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());
|
||||
};
|
||||
|
||||
|
@ -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)
|
||||
};
|
||||
|
||||
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);
|
||||
|
@ -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
|
||||
// to Some when a parse completes, or some default value if the parse was
|
||||
// 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,
|
||||
let mut $ident: ::std::option::Option<$ty> = None;
|
||||
).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
|
||||
// that each parameter actually is Some() or has a default value.
|
||||
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.
|
||||
if i > 0 {
|
||||
failure_conditions.push(quote_tokens!(cx, ||));
|
||||
}
|
||||
failure_conditions.push(quote_tokens!(cx, ||));
|
||||
|
||||
failure_conditions.push(quote_tokens!(cx,
|
||||
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
|
||||
// or the default value.
|
||||
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,
|
||||
$ident: $ident.unwrap_or_else(||
|
||||
<$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 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_param_statements(&self, ecx: &ExtCtxt) -> Vec<Stmt>;
|
||||
fn generate_fn_arguments(&self, ecx: &ExtCtxt) -> Vec<TokenTree>;
|
||||
|
@ -89,20 +89,31 @@ impl RouteGenerateExt for RouteParams {
|
|||
).expect("form statement"))
|
||||
}
|
||||
|
||||
fn generate_form_statement(&self, ecx: &ExtCtxt) -> Option<Stmt> {
|
||||
let param = self.form_param.as_ref().map(|p| &p.value);
|
||||
let expr = quote_expr!(ecx,
|
||||
match ::std::str::from_utf8(_req.data.as_slice()) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
println!(" => Form is not valid UTF8.");
|
||||
return ::rocket::Response::failed(
|
||||
::rocket::http::StatusCode::BadRequest);
|
||||
}
|
||||
}
|
||||
);
|
||||
fn generate_data_statement(&self, ecx: &ExtCtxt) -> Option<Stmt> {
|
||||
let param = self.data_param.as_ref().map(|p| &p.value);
|
||||
let arg = param.and_then(|p| self.annotated_fn.find_input(&p.node.name));
|
||||
if param.is_none() {
|
||||
return None;
|
||||
} else if arg.is_none() {
|
||||
self.missing_declared_err(ecx, ¶m.unwrap());
|
||||
return None;
|
||||
}
|
||||
|
||||
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> {
|
||||
|
@ -148,11 +159,11 @@ impl RouteGenerateExt for RouteParams {
|
|||
).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| {
|
||||
if let Some(name) = a.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)
|
||||
}) && self.query_param.as_ref().map_or(true, |p| {
|
||||
!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;
|
||||
for arg in all.iter().filter(from_request) {
|
||||
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);
|
||||
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 query_statement = route.generate_query_statement(ecx);
|
||||
let data_statement = route.generate_data_statement(ecx);
|
||||
let fn_arguments = route.generate_fn_arguments(ecx);
|
||||
|
||||
// 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,
|
||||
fn $route_fn_name<'_b>(_req: &'_b ::rocket::Request, _data: ::rocket::Data)
|
||||
-> ::rocket::Response<'_b> {
|
||||
$form_statement
|
||||
$query_statement
|
||||
$param_statements
|
||||
$query_statement
|
||||
$data_statement
|
||||
let result = $user_fn_name($fn_arguments);
|
||||
::rocket::Response::complete(result)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use syntax::ext::base::{ExtCtxt, Annotatable};
|
|||
use syntax::codemap::{Span, Spanned, dummy_spanned};
|
||||
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::keyvalue::KVSpanned;
|
||||
use rocket::http::{Method, ContentType};
|
||||
|
@ -22,7 +22,7 @@ pub struct RouteParams {
|
|||
pub annotated_fn: Function,
|
||||
pub method: Spanned<Method>,
|
||||
pub path: Spanned<String>,
|
||||
pub form_param: Option<KVSpanned<Ident>>,
|
||||
pub data_param: Option<KVSpanned<Ident>>,
|
||||
pub query_param: Option<Spanned<Ident>>,
|
||||
pub format: Option<KVSpanned<ContentType>>,
|
||||
pub rank: Option<KVSpanned<isize>>,
|
||||
|
@ -74,7 +74,7 @@ impl RouteParams {
|
|||
|
||||
// Parse all of the optional parameters.
|
||||
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..] {
|
||||
let kv_opt = kv_from_nested(¶m);
|
||||
if kv_opt.is_none() {
|
||||
|
@ -85,7 +85,7 @@ impl RouteParams {
|
|||
let kv = kv_opt.unwrap();
|
||||
match kv.key().as_str() {
|
||||
"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),
|
||||
_ => {
|
||||
let msg = format!("'{}' is not a known parameter", kv.key());
|
||||
|
@ -107,7 +107,7 @@ impl RouteParams {
|
|||
RouteParams {
|
||||
method: method,
|
||||
path: path,
|
||||
form_param: form,
|
||||
data_param: data,
|
||||
query_param: query,
|
||||
format: format,
|
||||
rank: rank,
|
||||
|
@ -145,7 +145,7 @@ fn param_string_to_ident(ecx: &ExtCtxt, s: Spanned<&str>) -> Option<Ident> {
|
|||
let string = s.node;
|
||||
if string.starts_with('<') && string.ends_with('>') {
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -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)))
|
||||
}
|
||||
|
||||
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() {
|
||||
str_name = s;
|
||||
if let Some(ident) = param_string_to_ident(ecx, span(s, kv.value.span)) {
|
||||
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)
|
||||
.help(r#"form, if specified, must be a key-value pair where
|
||||
the key is `form` and the value is a string with a single
|
||||
parameter inside '<' '>'. e.g: form = "<login>""#)
|
||||
.help(r#"data, if specified, must be a key-value pair where
|
||||
the key is `data` and the value is a string with a single
|
||||
parameter inside '<' '>'. e.g: data = "<user_form>""#)
|
||||
.emit();
|
||||
|
||||
str_to_ident("")
|
||||
str_to_ident(str_name)
|
||||
}
|
||||
|
||||
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> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn fold_lifetimes(&mut self, _: Vec<Lifetime>) -> Vec<Lifetime> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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 main() { }
|
||||
|
|
|
@ -4,14 +4,15 @@
|
|||
extern crate rocket;
|
||||
|
||||
use rocket::http::Cookies;
|
||||
use rocket::request::Form;
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct User {
|
||||
name: String
|
||||
}
|
||||
|
||||
#[post("/<name>?<query>", format = "application/json", form = "<user>", rank = 2)]
|
||||
fn get(name: &str, query: User, user: User, cookies: &Cookies) -> &'static str { "hi" }
|
||||
#[post("/<name>?<query>", format = "application/json", data = "<user>", rank = 2)]
|
||||
fn get(name: &str, query: User, user: Form<User>, cookies: &Cookies) -> &'static str { "hi" }
|
||||
|
||||
fn main() {
|
||||
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"] }
|
||||
glob = { version = "^0.2", optional = true }
|
||||
lazy_static = { version = "^0.2", optional = true }
|
||||
|
||||
# Tera dependency
|
||||
[dependencies.tera]
|
||||
git = "https://github.com/SergioBenitez/tera"
|
||||
optional = true
|
||||
tera = { version = "^0.3", optional = true }
|
||||
|
|
|
@ -2,39 +2,39 @@ extern crate serde;
|
|||
extern crate serde_json;
|
||||
|
||||
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::http::StatusCode;
|
||||
use rocket::http::hyper::FreshHyperResponse;
|
||||
|
||||
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
|
||||
/// type allows you to trivially consume and respond with JSON in your Rocket
|
||||
/// application.
|
||||
/// The JSON type, which implements `FromData` and `Responder`. This type allows
|
||||
/// you to trivially consume and respond with JSON in your Rocket application.
|
||||
///
|
||||
/// If you're receiving JSON data, simple add a `JSON<T>` type to your function
|
||||
/// signature where `T` is some type you'd like to parse from JSON. `T` must
|
||||
/// implement `Deserialize` from [Serde](https://github.com/serde-rs/json). The
|
||||
/// data is parsed from the HTTP request body.
|
||||
/// If you're receiving JSON data, simple add a `data` parameter to your route
|
||||
/// arguments and ensure the type o the parameter is a `JSON<T>`, where `T` is
|
||||
/// some type you'd like to parse from JSON. `T` must implement `Deserialize`
|
||||
/// from [Serde](https://github.com/serde-rs/json). The data is parsed from the
|
||||
/// HTTP request body.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// #[post("/users/", format = "application/json")]
|
||||
/// #[post("/users/", format = "application/json", data = "<user>")]
|
||||
/// fn new_user(user: JSON<User>) {
|
||||
/// ...
|
||||
/// }
|
||||
/// ```
|
||||
/// 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
|
||||
/// doesn't specify "application/json" as its first `Accept:` header parameter
|
||||
/// will not be routed to this handler.
|
||||
/// doesn't specify "application/json" as its first `Content-Type:` header
|
||||
/// parameter will not be routed to this handler.
|
||||
///
|
||||
/// If you're responding with JSON data, return a `JSON<T>` type, where `T`
|
||||
/// implements implements `Serialize` from
|
||||
/// [Serde](https://github.com/serde-rs/json). The content type is set to
|
||||
/// `application/json` automatically.
|
||||
/// implements `Serialize` from [Serde](https://github.com/serde-rs/json). The
|
||||
/// content type of the response is set to `application/json` automatically.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// #[get("/users/<id>")]
|
||||
|
@ -63,10 +63,21 @@ impl<T> JSON<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'r, T: Deserialize> FromRequest<'r> for JSON<T> {
|
||||
type Error = JSONError;
|
||||
fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
|
||||
Ok(JSON(serde_json::from_slice(request.data.as_slice())?))
|
||||
/// Maximum size of JSON is 1MB.
|
||||
/// TODO: Determine this size from some configuration parameter.
|
||||
const MAX_SIZE: u64 = 1048576;
|
||||
|
||||
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 rocket::request::Form;
|
||||
use rocket::response::Redirect;
|
||||
use rocket::http::{Cookie, Cookies};
|
||||
use rocket_contrib::Template;
|
||||
|
@ -17,9 +18,9 @@ struct Message {
|
|||
message: String
|
||||
}
|
||||
|
||||
#[post("/submit", form = "<message>")]
|
||||
fn submit(cookies: &Cookies, message: Message) -> Redirect {
|
||||
cookies.add(Cookie::new("message".into(), message.message));
|
||||
#[post("/submit", data = "<message>")]
|
||||
fn submit(cookies: &Cookies, message: Form<Message>) -> Redirect {
|
||||
cookies.add(Cookie::new("message".into(), message.into_inner().message));
|
||||
Redirect::to("/")
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ fn index() -> io::Result<NamedFile> {
|
|||
NamedFile::open("static/index.html")
|
||||
}
|
||||
|
||||
#[get("/<file..>")]
|
||||
#[get("/<file..>", rank = 2)]
|
||||
fn files(file: PathBuf) -> io::Result<NamedFile> {
|
||||
NamedFile::open(Path::new("static/").join(file))
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ extern crate rocket;
|
|||
mod files;
|
||||
|
||||
use rocket::response::Redirect;
|
||||
use rocket::request::FromFormValue;
|
||||
use rocket::request::{Form, FromFormValue};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct StrongPassword<'r>(&'r str);
|
||||
|
@ -49,24 +49,26 @@ impl<'v> FromFormValue<'v> for AdultAge {
|
|||
}
|
||||
}
|
||||
|
||||
#[post("/login", form = "<user>")]
|
||||
fn login(user: UserLogin) -> Result<Redirect, String> {
|
||||
if user.age.is_err() {
|
||||
return Err(String::from(user.age.unwrap_err()));
|
||||
#[post("/login", data = "<user_form>")]
|
||||
fn login<'a>(user_form: Form<'a, UserLogin<'a>>) -> Result<Redirect, String> {
|
||||
let user = user_form.get();
|
||||
|
||||
if let Err(e) = user.age {
|
||||
return Err(format!("Age is invalid: {}", e));
|
||||
}
|
||||
|
||||
if user.password.is_err() {
|
||||
return Err(String::from(user.password.unwrap_err()));
|
||||
if let Err(e) = user.password {
|
||||
return Err(format!("Password is invalid: {}", e));
|
||||
}
|
||||
|
||||
match user.username {
|
||||
"Sergio" => {
|
||||
match user.password.unwrap().0 {
|
||||
"password" => Ok(Redirect::other("/user/Sergio")),
|
||||
_ => Err("Wrong password!".to_string()),
|
||||
}
|
||||
if user.username == "Sergio" {
|
||||
if let Ok(StrongPassword("password")) = user.password {
|
||||
Ok(Redirect::other("/user/Sergio"))
|
||||
} else {
|
||||
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;
|
||||
|
||||
use rocket::request::{Request, FromFormValue};
|
||||
use rocket::request::{Form, FromFormValue};
|
||||
use rocket::response::NamedFile;
|
||||
use std::io;
|
||||
|
||||
|
@ -29,24 +29,22 @@ impl<'v> FromFormValue<'v> for FormOption {
|
|||
}
|
||||
|
||||
#[derive(Debug, FromForm)]
|
||||
struct FormInput<'r> {
|
||||
struct FormInput {
|
||||
checkbox: bool,
|
||||
number: usize,
|
||||
radio: FormOption,
|
||||
password: &'r str,
|
||||
password: String,
|
||||
textarea: String,
|
||||
select: FormOption,
|
||||
}
|
||||
|
||||
#[post("/", form = "<sink>")]
|
||||
fn sink(sink: FormInput) -> String {
|
||||
format!("{:?}", sink)
|
||||
}
|
||||
|
||||
#[post("/", rank = 2)]
|
||||
fn sink2(request: &Request) -> &'static str {
|
||||
println!("form: {:?}", std::str::from_utf8(request.data.as_slice()));
|
||||
"Sorry, the form is invalid."
|
||||
#[post("/", data = "<sink>")]
|
||||
fn sink(sink: Result<Form<FormInput>, Option<String>>) -> String {
|
||||
match sink {
|
||||
Ok(form) => format!("{:?}", form.get()),
|
||||
Err(Some(f)) => format!("Invalid form input: {}", f),
|
||||
Err(None) => format!("Form input was invalid UTF8."),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
|
@ -56,6 +54,6 @@ fn index() -> io::Result<NamedFile> {
|
|||
|
||||
fn main() {
|
||||
rocket::ignite()
|
||||
.mount("/", routes![index, sink, sink2])
|
||||
.mount("/", routes![index, sink])
|
||||
.launch();
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ fn index() -> io::Result<NamedFile> {
|
|||
NamedFile::open("static/index.html")
|
||||
}
|
||||
|
||||
#[get("/<file..>")]
|
||||
#[get("/<file..>", rank = 5)]
|
||||
fn files(file: PathBuf) -> io::Result<NamedFile> {
|
||||
NamedFile::open(Path::new("static/").join(file))
|
||||
}
|
||||
|
|
|
@ -5,37 +5,38 @@ extern crate rocket;
|
|||
|
||||
mod files;
|
||||
|
||||
use rocket::request::Form;
|
||||
use rocket::response::Redirect;
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct UserLogin<'r> {
|
||||
username: &'r str,
|
||||
password: &'r str,
|
||||
age: Result<isize, &'r str>,
|
||||
age: Result<usize, &'r str>,
|
||||
}
|
||||
|
||||
#[post("/login", form = "<user>")]
|
||||
fn login(user: UserLogin) -> Result<Redirect, String> {
|
||||
if user.age.is_err() {
|
||||
let input = user.age.unwrap_err();
|
||||
return Err(format!("'{}' is not a valid age integer.", input));
|
||||
}
|
||||
#[post("/login", data = "<user_form>")]
|
||||
fn login<'a>(user_form: Form<'a, UserLogin<'a>>) -> Result<Redirect, String> {
|
||||
let user = user_form.get();
|
||||
match user.age {
|
||||
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 age < 20 {
|
||||
return Err(format!("Sorry, {} is too young!", age));
|
||||
}
|
||||
|
||||
match user.username {
|
||||
"Sergio" => match user.password {
|
||||
if user.username == "Sergio" {
|
||||
match user.password {
|
||||
"password" => Ok(Redirect::other("/user/Sergio")),
|
||||
_ => 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 {
|
||||
format!("This is {}'s page.", username)
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ struct Message {
|
|||
// None
|
||||
// }
|
||||
|
||||
#[post("/<id>", format = "application/json")]
|
||||
#[post("/<id>", format = "application/json", data = "<message>")]
|
||||
fn new(id: ID, message: JSON<Message>) -> JSON<SimpleMap> {
|
||||
let mut hashmap = MAP.lock().unwrap();
|
||||
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>> {
|
||||
let mut hashmap = MAP.lock().unwrap();
|
||||
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 {
|
||||
if !req.content_type().is_text() {
|
||||
println!(" => Content-Type of upload must be data. Ignoring.");
|
||||
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 task;
|
||||
|
||||
use rocket::request::Form;
|
||||
use rocket::response::{Flash, Redirect};
|
||||
use rocket_contrib::Template;
|
||||
use task::Task;
|
||||
|
||||
#[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> {
|
||||
pub fn err(msg: &'a str) -> Context<'static, 'a> {
|
||||
|
@ -28,14 +29,15 @@ impl<'a, 'b> Context<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
#[post("/", form = "<todo>")]
|
||||
fn new(todo: Task) -> Result<Flash<Redirect>, Template> {
|
||||
#[post("/", data = "<todo_form>")]
|
||||
fn new(todo_form: Form<Task>) -> Flash<Redirect> {
|
||||
let todo = todo_form.into_inner();
|
||||
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() {
|
||||
Ok(Flash::success(Redirect::to("/"), "Todo successfully added."))
|
||||
Flash::success(Redirect::to("/"), "Todo successfully added.")
|
||||
} 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(specialization)]
|
||||
#![feature(conservative_impl_trait)]
|
||||
#![feature(associated_type_defaults)]
|
||||
|
||||
//! # 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};
|
||||
use std::time::Duration;
|
||||
use std::net::Shutdown;
|
||||
//! Talk about the data thing.
|
||||
|
||||
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::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 {
|
||||
buffer: Vec<u8>,
|
||||
is_done: bool,
|
||||
stream: StreamReader,
|
||||
position: usize,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl Drop for Data {
|
||||
fn drop(&mut self) {
|
||||
try_sinking(self.stream.get_mut());
|
||||
}
|
||||
}
|
||||
|
||||
impl Data {
|
||||
pub fn open(mut self) -> impl BufRead {
|
||||
// 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))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn peek(&self) -> &[u8] {
|
||||
&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
|
||||
// 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 {
|
||||
buffer: buf,
|
||||
stream: stream,
|
||||
is_done: eof,
|
||||
position: pos,
|
||||
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::from_request::FromRequest;
|
||||
pub use self::param::{FromParam, FromSegments};
|
||||
pub use self::form::{FromForm, FromFormValue, FormItems};
|
||||
pub use self::data::{Data};
|
||||
pub use self::form::{Form, FromForm, FromFormValue, FormItems};
|
||||
pub use self::data::{Data, FromData, DataOutcome};
|
||||
|
|
|
@ -31,7 +31,6 @@ pub struct Request {
|
|||
/// </div>
|
||||
///
|
||||
/// The data in the request.
|
||||
pub data: Vec<u8>, // FIXME: Don't read this! (bad Hyper.)
|
||||
uri: URIBuf, // FIXME: Should be URI (without Hyper).
|
||||
params: RefCell<Vec<&'static str>>,
|
||||
cookies: Cookies,
|
||||
|
@ -104,7 +103,6 @@ impl Request {
|
|||
method: method,
|
||||
cookies: Cookies::new(&[]),
|
||||
uri: URIBuf::from(uri),
|
||||
data: vec![],
|
||||
headers: HyperHeaders::new(),
|
||||
}
|
||||
}
|
||||
|
@ -211,7 +209,6 @@ impl Request {
|
|||
method: method,
|
||||
cookies: cookies,
|
||||
uri: uri,
|
||||
data: vec![], // TODO: Remove me.
|
||||
headers: h_headers,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
use response::{ResponseOutcome, Outcome, Responder};
|
||||
use http::hyper::{FreshHyperResponse, StatusCode};
|
||||
|
||||
pub struct Failure(StatusCode);
|
||||
|
||||
impl Failure {
|
||||
#[inline(always)]
|
||||
pub fn new(status: StatusCode) -> Failure {
|
||||
Failure(status)
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct Failure(pub StatusCode);
|
||||
|
||||
impl Responder for Failure {
|
||||
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> {
|
||||
|
|
|
@ -44,7 +44,7 @@ impl<'a> Response<'a> {
|
|||
|
||||
#[inline(always)]
|
||||
pub fn failed(code: StatusCode) -> Response<'static> {
|
||||
Response::complete(Failure::new(code))
|
||||
Response::complete(Failure(code))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
|
|
@ -72,7 +72,7 @@ impl Rocket {
|
|||
self.preprocess_request(&mut request, &data);
|
||||
|
||||
info!("{}:", request);
|
||||
info_!("Peek size: {} bytes", data.peek().len());
|
||||
trace_!("Peek size: {} bytes", data.peek().len());
|
||||
let matches = self.router.route(&request);
|
||||
for route in matches {
|
||||
// Retrieve and set the requests parameters.
|
||||
|
@ -180,9 +180,9 @@ impl Rocket {
|
|||
pub fn catch(mut self, catchers: Vec<Catcher>) -> Self {
|
||||
info!("👾 {}:", Magenta.paint("Catchers"));
|
||||
for c in catchers {
|
||||
if self.catchers.get(&c.code).map_or(false, |e| e.is_default()) {
|
||||
let msg = format!("warning: overrides {} catcher!", c.code);
|
||||
warn!("{} ({})", c, Yellow.paint(msg.as_str()));
|
||||
if self.catchers.get(&c.code).map_or(false, |e| !e.is_default()) {
|
||||
let msg = "(warning: duplicate catcher!)";
|
||||
info_!("{} {}", c, Yellow.paint(msg));
|
||||
} else {
|
||||
info_!("{}", c);
|
||||
}
|
||||
|
|
|
@ -18,10 +18,10 @@ function build_and_test() {
|
|||
|
||||
pushd ${dir}
|
||||
echo ":: Building '${PWD}'..."
|
||||
RUST_BACKTRACE=1 cargo build
|
||||
RUST_BACKTRACE=1 cargo build --all-features
|
||||
|
||||
echo ":: Running unit tests in '${PWD}'..."
|
||||
RUST_BACKTRACE=1 cargo test
|
||||
RUST_BACKTRACE=1 cargo test --all-features
|
||||
popd
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue