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/json",
"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)
);
// 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
// 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
@ -178,17 +165,20 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
arms.push(quote_tokens!(cx,
$id_str => $ident = match ::rocket::form::FromFormValue::parse(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,
for &(k, v) in &items[..form_count] {
for (k, v) in ::rocket::form::FormItems($arg) {
match k {
$arms
// Return error when a field is in the form but not in struct.
_ => {
println!("\t{}={} has no matching field in struct.", k, v);
$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.
let mut failure_conditions = vec![];
for (i, &(ref ident, ty)) in (&fields_and_types).iter().enumerate() {
// Pushing an "||" (or) between every condition.
if i > 0 {
failure_conditions.push(quote_tokens!(cx, ||));
}
failure_conditions.push(quote_tokens!(cx, $ident.is_none() &&
<$ty as ::rocket::form::FromFormValue>::default().is_none()));
failure_conditions.push(quote_tokens!(cx,
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
@ -225,7 +221,6 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
let self_ident = substr.type_ident;
let final_block = quote_block!(cx, {
if $failure_conditions {
println!("\tOne of the fields didn't parse.");
$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
`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
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
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
that by writing `#![feature(plugin)]`. We also have to tell the compiler to use
Rocket's code generation crate during compilation with
That's it! Let's break this down.
We'll start with lines 1 and 2. Rocket depends on the latest version Rust
nightly; it makes 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 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
namespace.
@ -60,7 +62,7 @@ The fun begins on line 7, where the `hello` route and request handler are
declared.
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:
* 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
The `hello` route declaration on line 7 tells Rocket that the `hello` function
will handle HTTP `GET` requests to the `<name>/<age>` path. The handler uses
`name` and `age` from the path to format and return a `String` to the user. Here
are lines 7 - 10 again:
The `hello` route declaration beginning on line 7 of our example applications
tells Rocket that the `hello` function will handle HTTP `GET` requests to the
`<name>/<age>` path. The handler uses `name` and `age` from the path to format
and return a `String` to the user. Here are lines 7 - 10 again:
```rust
#[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
these segments won't be known until someone visits a URL that matches. For
example, if someone visit `Mike/21`, `<name>` will be `"Mike"`, and `<age>` will
be `21`. If someone else visits `Bob/91`, `<name>` and `<age>` will be `"Bob"`
and `91`, respectively. Rocket automatically parses dynamic path segments and
passes them to the request handler in variables with matching names. This means
that `name` and `age` can be used immediately in the handler - no parsing, no
checking.
these segments won't be known until someone visits a matching URL. For example,
if someone visit `Mike/21`, `<name>` will be `"Mike"`, and `<age>` will be `21`.
If someone else visits `Bob/91`, `<name>` and `<age>` will be `"Bob"` and
`91`, respectively. Rocket automatically parses dynamic path segments and
passes them to the request handler in variables with matching names. This
means that `name` and `age` can be used immediately in the handler - no
parsing, no checking.
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
_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
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.
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
Now that we're set with the `hello` route, let's move on to lines 13 - 14.
Before Rocket dispatches requests to a route, the route needs to be _mounted_.
And before we can mount a route, we need an instance of `Rocket`.
Now that we understand the `hello` route, let's move on to lines 13 - 14. Before
Rocket dispatches requests to a route, the route needs to be _mounted_. And
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
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
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:
```
```rust
let mut rocket = Rocket::ignite();
rocket.mount(“/hello”, routes![hello]);
```
@ -169,7 +172,9 @@ invalid paths and see what happens! This example's complete crate, ready to
# 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

View File

@ -8,6 +8,14 @@ pub trait FromForm<'f>: Sized {
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 {
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 {
($($T:ident),+) => ($(
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,
bool, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6,
SocketAddr);
IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr);
impl<'v, T: FromFormValue<'v>> FromFormValue<'v> for Option<T> {
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)]
mod test {
use super::form_items;
use super::FormItems;
macro_rules! check_form {
($string:expr, $expected: expr) => ({
let mut output = Vec::with_capacity($expected.len());
unsafe { output.set_len($expected.len()); }
let results = output.as_mut_slice();
assert_eq!($expected.len(), form_items($string, results));
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);