From 23808d00bcbad7043c8089f88da02493be04f94f Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Wed, 28 Sep 2016 19:29:18 -0700 Subject: [PATCH] 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. --- Cargo.toml | 1 + codegen/src/decorators/derive_form.rs | 35 ++++++++--------- docs/overview.md | 55 +++++++++++++++------------ lib/src/form.rs | 43 ++++++++++++--------- 4 files changed, 70 insertions(+), 64 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 52de3b44..a5fa5dc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,4 +23,5 @@ members = [ "examples/stream", "examples/json", "examples/handlebars_templates", + "examples/form_types", ] diff --git a/codegen/src/decorators/derive_form.rs b/codegen/src/decorators/derive_form.rs index 6b8d6847..9812c818 100644 --- a/codegen/src/decorators/derive_form.rs +++ b/codegen/src/decorators/derive_form.rs @@ -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; } diff --git a/docs/overview.md b/docs/overview.md index edf28718..2f70ddb5 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -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 //`, 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 `/` 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 +`/` 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("//")] @@ -94,20 +96,20 @@ fn hello(name: &str, age: u8) -> String { ``` The `` and `` 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`, `` will be `"Mike"`, and `` will -be `21`. If someone else visits `Bob/91`, `` and `` 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`, `` will be `"Mike"`, and `` will be `21`. +If someone else visits `Bob/91`, `` and `` 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 `` 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 ``, simply use a `Result` or an `Option` 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 diff --git a/lib/src/form.rs b/lib/src/form.rs index 46fb8666..377e0ee8 100644 --- a/lib/src/form.rs +++ b/lib/src/form.rs @@ -8,6 +8,14 @@ pub trait FromForm<'f>: Sized { fn from_form_string(s: &'f str) -> Result; } +// 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 { + 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 { + 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 { 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);