This commit squash three form-related commits:

Remove form_items function in favor of FormItems iterator.

  Add specialized `bool` implementation of FromFormValue.

  Add `&str` implementation of FromFormValue for debugging.
This commit is contained in:
Sergio Benitez 2016-09-28 19:29:18 -07:00
parent be50ddeca4
commit 23808d00bc
4 changed files with 70 additions and 64 deletions

View File

@ -23,4 +23,5 @@ members = [
"examples/stream", "examples/stream",
"examples/json", "examples/json",
"examples/handlebars_templates", "examples/handlebars_templates",
"examples/form_types",
] ]

View File

@ -146,19 +146,6 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
return Err(::rocket::Error::BadParse) return Err(::rocket::Error::BadParse)
); );
// Generating the code that checks that the number of fields is correct.
let num_fields = fields_and_types.len();
let initial_block = quote_block!(cx, {
let mut items = [("", ""); $num_fields];
let form_count = ::rocket::form::form_items($arg, &mut items);
// if form_count != items.len() {
// println!("\t Form parse: Wrong number of items!");
// $return_err_stmt;
// };
});
stmts.extend(initial_block.unwrap().stmts);
// Generate the let bindings for parameters that will be unwrapped and // Generate the let bindings for parameters that will be unwrapped and
// 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
@ -178,17 +165,20 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
arms.push(quote_tokens!(cx, arms.push(quote_tokens!(cx,
$id_str => $ident = match ::rocket::form::FromFormValue::parse(v) { $id_str => $ident = match ::rocket::form::FromFormValue::parse(v) {
Ok(v) => Some(v), Ok(v) => Some(v),
Err(_) => $return_err_stmt Err(e) => {
println!("\tError parsing form value '{}': {:?}", $id_str, e);
$return_err_stmt
}
}, },
)); ));
} }
// The actual match statement. Uses the $arms generated above. // The actual match statement. Iterate through all of the fields in the form
// and use the $arms generated above.
stmts.push(quote_stmt!(cx, stmts.push(quote_stmt!(cx,
for &(k, v) in &items[..form_count] { for (k, v) in ::rocket::form::FormItems($arg) {
match k { match k {
$arms $arms
// Return error when a field is in the form but not in struct.
_ => { _ => {
println!("\t{}={} has no matching field in struct.", k, v); println!("\t{}={} has no matching field in struct.", k, v);
$return_err_stmt $return_err_stmt
@ -201,12 +191,18 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
// 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() { for (i, &(ref ident, ty)) in (&fields_and_types).iter().enumerate() {
// Pushing an "||" (or) between every condition.
if i > 0 { if i > 0 {
failure_conditions.push(quote_tokens!(cx, ||)); failure_conditions.push(quote_tokens!(cx, ||));
} }
failure_conditions.push(quote_tokens!(cx, $ident.is_none() && failure_conditions.push(quote_tokens!(cx,
<$ty as ::rocket::form::FromFormValue>::default().is_none())); if $ident.is_none() &&
<$ty as ::rocket::form::FromFormValue>::default().is_none() {
println!("\t'{}' did not parse.", stringify!($ident));
true
} else { false }
));
} }
// 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
@ -225,7 +221,6 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
let self_ident = substr.type_ident; let self_ident = substr.type_ident;
let final_block = quote_block!(cx, { let final_block = quote_block!(cx, {
if $failure_conditions { if $failure_conditions {
println!("\tOne of the fields didn't parse.");
$return_err_stmt; $return_err_stmt;
} }

View File

@ -44,13 +44,15 @@ Here's a quick summary of what it does: first, on lines 7 - 10, it declares the
`hello` route to `GET /<name>/<age>`, which returns a `String` formatted with `hello` route to `GET /<name>/<age>`, which returns a `String` formatted with
`name` and `age` from the dynamic path. Then, in the `main` function, it creates `name` and `age` from the dynamic path. Then, in the `main` function, it creates
a new `Rocket` instance, mounts the `hello` route at `"/hello"`, and launches a new `Rocket` instance, mounts the `hello` route at `"/hello"`, and launches
the application. That's it! Let's break it down. the application.
Let's start with lines 1 and 2. Rocket depends on Rust nightly; it makes That's it! Let's break this down.
extensive use of Rust's code generation facilities through compiler plugins.
Plugins are still experimental, so we have to tell Rust that we're okay with We'll start with lines 1 and 2. Rocket depends on the latest version Rust
that by writing `#![feature(plugin)]`. We also have to tell the compiler to use nightly; it makes extensive use of Rust's code generation facilities through
Rocket's code generation crate during compilation with compiler plugins. Plugins are still experimental, so we have to tell Rust that
we're okay with that by writing `#![feature(plugin)]`. We also have to tell the
compiler to use Rocket's code generation crate during compilation with
`#![plugin(rocket_codegen)]`. Lines 4 and 5 bring `rocket::Rocket` into the `#![plugin(rocket_codegen)]`. Lines 4 and 5 bring `rocket::Rocket` into the
namespace. namespace.
@ -60,7 +62,7 @@ The fun begins on line 7, where the `hello` route and request handler are
declared. declared.
Rocket applications are composed primarily of request handlers and routes. A Rocket applications are composed primarily of request handlers and routes. A
_request handler_ is a function that takes an arbitrary number of parameters and _request handler_ is a function that takes an arbitrary number of arguments and
returns a response. A _route_ is a combination of: returns a response. A _route_ is a combination of:
* A set of parameters to match an incoming request against. * A set of parameters to match an incoming request against.
@ -81,10 +83,10 @@ You can also use `put`, `post`, `delete`, and `patch` in place of `get`.
## Dynamic Paths ## Dynamic Paths
The `hello` route declaration on line 7 tells Rocket that the `hello` function The `hello` route declaration beginning on line 7 of our example applications
will handle HTTP `GET` requests to the `<name>/<age>` path. The handler uses tells Rocket that the `hello` function will handle HTTP `GET` requests to the
`name` and `age` from the path to format and return a `String` to the user. Here `<name>/<age>` path. The handler uses `name` and `age` from the path to format
are lines 7 - 10 again: and return a `String` to the user. Here are lines 7 - 10 again:
```rust ```rust
#[get("/<name>/<age>")] #[get("/<name>/<age>")]
@ -94,20 +96,20 @@ fn hello(name: &str, age: u8) -> String {
``` ```
The `<name>` and `<age>` parts of the path are _dynamic_: the actual values for The `<name>` and `<age>` parts of the path are _dynamic_: the actual values for
these segments won't be known until someone visits a URL that matches. For these segments won't be known until someone visits a matching URL. For example,
example, if someone visit `Mike/21`, `<name>` will be `"Mike"`, and `<age>` will if someone visit `Mike/21`, `<name>` will be `"Mike"`, and `<age>` will be `21`.
be `21`. If someone else visits `Bob/91`, `<name>` and `<age>` will be `"Bob"` If someone else visits `Bob/91`, `<name>` and `<age>` will be `"Bob"` and
and `91`, respectively. Rocket automatically parses dynamic path segments and `91`, respectively. Rocket automatically parses dynamic path segments and
passes them to the request handler in variables with matching names. This means passes them to the request handler in variables with matching names. This
that `name` and `age` can be used immediately in the handler - no parsing, no means that `name` and `age` can be used immediately in the handler - no
checking. parsing, no checking.
But wait: what happens if someone goes to a URL with an `<age>` that isn't a But wait: what happens if someone goes to a URL with an `<age>` that isn't a
valid `u8`? In that case, Rocket doesn't call the handler. Instead, it valid `u8`? In that case, Rocket doesn't call the handler. Instead, it
_forwards_ the request to the next matching route, if any, and ultimately _forwards_ the request to the next matching route, if any, and ultimately
returns a `404` if all of them fail. If you want to know if the user passed in a returns a `404` if all of them fail. If you want to know if the user passed in a
bad `<age>`, simply use a `Result<u8, &str>` or an `Option<u8>` type for `age` bad `<age>`, simply use a `Result<u8, &str>` or an `Option<u8>` type for `age`
instead. For more details on routing, route collisions, and more see the instead. For more details on routing, route collisions, and much more see the
[routing](guide/routing) chapter of the guide. [routing](guide/routing) chapter of the guide.
Oh, one more thing before we move on! Notice how dynamic path parameters can be Oh, one more thing before we move on! Notice how dynamic path parameters can be
@ -119,19 +121,20 @@ implemented `FromParam` for plenty of types in the standard library. See the
## Mounting ## Mounting
Now that we're set with the `hello` route, let's move on to lines 13 - 14. Now that we understand the `hello` route, let's move on to lines 13 - 14. Before
Before Rocket dispatches requests to a route, the route needs to be _mounted_. Rocket dispatches requests to a route, the route needs to be _mounted_. And
And before we can mount a route, we need an instance of `Rocket`. before we can mount a route, we need an instance of `Rocket`.
Mounting a route is like namespacing it. Routes can be mounted any number of Mounting a route is like namespacing it. Routes can be mounted any number of
times. Mounting happens with the `mount` method on a `Rocket` instance, which times. Mounting happens with the `mount` method on a `Rocket` instance, which
itself is created with the `ignite()` static method. The `mount` method takes a itself is created with the `ignite()` static method. The `mount` method takes a
list of route handlers given inside of the `route!` macro. The `route!` macro list of route handlers given inside of the `route!` macro. The `route!` macro
ties Rocket's code generation to your application. ties Rocket's code generation to your application. If you'd like to learn more
about the `route!` macro, see the [internals guide](guide/internals).
Let's look at lines 13 - 14 again, which we reproduce below: Let's look at lines 13 - 14 again, which we reproduce below:
``` ```rust
let mut rocket = Rocket::ignite(); let mut rocket = Rocket::ignite();
rocket.mount(“/hello”, routes![hello]); rocket.mount(“/hello”, routes![hello]);
``` ```
@ -169,7 +172,9 @@ invalid paths and see what happens! This example's complete crate, ready to
# Requests # Requests
There's a lot more to do with requests. Let's take a closer look. There's a lot more we can do with requests. The [requests](guide/requests)
chapter of the guide talks about requests in details. We'll give you a short
overview of some of the more important and useful features here.
## Forms and Queries ## Forms and Queries

View File

@ -8,6 +8,14 @@ pub trait FromForm<'f>: Sized {
fn from_form_string(s: &'f str) -> Result<Self, Error>; fn from_form_string(s: &'f str) -> Result<Self, Error>;
} }
// This implementation should only be ued during debugging!
#[doc(hidden)]
impl<'f> FromForm<'f> for &'f str {
fn from_form_string(s: &'f str) -> Result<Self, Error> {
Ok(s)
}
}
pub trait FromFormValue<'v>: Sized { pub trait FromFormValue<'v>: Sized {
type Error; type Error;
@ -53,6 +61,18 @@ impl<'v> FromFormValue<'v> for String {
} }
} }
impl<'v> FromFormValue<'v> for bool {
type Error = &'v str;
fn parse(v: &'v str) -> Result<Self, Self::Error> {
match v {
"on" | "true" => Ok(true),
"off" | "false" => Ok(false),
_ => Err(v)
}
}
}
macro_rules! impl_with_fromstr { macro_rules! impl_with_fromstr {
($($T:ident),+) => ($( ($($T:ident),+) => ($(
impl<'v> FromFormValue<'v> for $T { impl<'v> FromFormValue<'v> for $T {
@ -65,8 +85,7 @@ macro_rules! impl_with_fromstr {
} }
impl_with_fromstr!(f32, f64, isize, i8, i16, i32, i64, usize, u8, u16, u32, u64, impl_with_fromstr!(f32, f64, isize, i8, i16, i32, i64, usize, u8, u16, u32, u64,
bool, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr);
SocketAddr);
impl<'v, T: FromFormValue<'v>> FromFormValue<'v> for Option<T> { impl<'v, T: FromFormValue<'v>> FromFormValue<'v> for Option<T> {
type Error = Error; type Error = Error;
@ -117,28 +136,14 @@ impl<'f> Iterator for FormItems<'f> {
} }
} }
pub fn form_items<'f>(string: &'f str, items: &mut [(&'f str, &'f str)]) -> usize {
let mut param_count = 0;
for (i, item) in FormItems(string).take(items.len()).enumerate() {
items[i] = item;
param_count += 1;
}
param_count
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::form_items; use super::FormItems;
macro_rules! check_form { macro_rules! check_form {
($string:expr, $expected: expr) => ({ ($string:expr, $expected: expr) => ({
let mut output = Vec::with_capacity($expected.len()); let results: Vec<(&str, &str)> = FormItems($string).collect();
unsafe { output.set_len($expected.len()); } assert_eq!($expected.len(), results.len());
let results = output.as_mut_slice();
assert_eq!($expected.len(), form_items($string, results));
for i in 0..results.len() { for i in 0..results.len() {
let (expected_key, actual_key) = ($expected[i].0, results[i].0); let (expected_key, actual_key) = ($expected[i].0, results[i].0);