From 3e449d2fb97c3606e463a9cf936eb5f2a44db1a7 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Sun, 3 Apr 2016 21:53:25 -0700 Subject: [PATCH] Forms are now working! --- examples/forms/src/main.rs | 117 +++++++++++++++++++++++++++++++--- lib/src/request.rs | 8 ++- lib/src/rocket.rs | 2 +- lib/src/router/uri.rs | 14 ++-- macros/src/route_decorator.rs | 18 ++++-- 5 files changed, 134 insertions(+), 25 deletions(-) diff --git a/examples/forms/src/main.rs b/examples/forms/src/main.rs index 0e0e1e92..7ff7f19e 100644 --- a/examples/forms/src/main.rs +++ b/examples/forms/src/main.rs @@ -14,21 +14,70 @@ fn user_page(username: &str) -> String { format!("This is {}'s page.", username) } -// #[derive(FormItem)] // FIXME: Make that happen. +// #[derive(FromForm)] // FIXME: Make that happen. struct UserLogin<'a> { username: &'a str, password: &'a str } -trait FormItem: Sized { - fn from_form_string(s: &str) -> Result; +fn form_items<'f>(string: &'f str, items: &mut [(&'f str, &'f str)]) -> usize { + let mut param_num = 0; + let mut rest = string; + while rest.len() > 0 && param_num < items.len() { + let (key, remainder) = match rest.find('=') { + Some(index) => (&rest[..index], &rest[(index + 1)..]), + None => return param_num + }; + + rest = remainder; + let (value, remainder) = match rest.find('&') { + Some(index) => (&rest[..index], &rest[(index + 1)..]), + None => (rest, "") + }; + + rest = remainder; + items[param_num] = (key, value); + param_num += 1; + } + + param_num } -impl<'a> FormItem for UserLogin<'a> { - fn from_form_string(s: &str) -> Result { +trait FromForm<'f>: Sized { + fn from_form_string(s: &'f str) -> Result; +} + +// FIXME: Add a 'FromFormValue' trait and use it on each field in the form +// structure. Should be pretty simple. Implement for all FromStr and then +// implement for OptionT: FromFormValue> so forms still exist. Maybe have a way +// for FromFormValue types to have an error that says why it didn't work. This +// will help for validation. IE, can have a type Range(1, 10) that returns an +// enum with one of: TooLow(isize), TooHigh(isize), etc. +impl<'f> FromForm<'f> for UserLogin<'f> { + fn from_form_string(s: &'f str) -> Result { + let mut items = [("", ""); 2]; + let form_count = form_items(s, &mut items); + if form_count != 2 { + return Err(Error::BadParse); + } + + let mut username = None; + let mut password = None; + for &(key, value) in &items { + match key { + "username" => username = Some(value), + "password" => password = Some(value), + _ => return Err(Error::BadParse) + } + } + + if username.is_none() || password.is_none() { + return Err(Error::BadParse); + } + Ok(UserLogin { - username: "this", - password: "that" + username: username.unwrap(), + password: password.unwrap() }) } } @@ -39,7 +88,7 @@ impl<'a> FormItem for UserLogin<'a> { fn login(user: UserLogin) -> Result { match user.username { "Sergio" => match user.password { - "password" => Ok(Redirect::other("/user/some_name")), + "password" => Ok(Redirect::other("/user/Sergio")), _ => Err("Wrong password!".to_string()) }, _ => Err(format!("Unrecognized user, '{}'.", user.username)) @@ -51,3 +100,55 @@ fn main() { rocket.mount("/", routes![files::index, files::files, user_page, login]); rocket.launch(); } + +#[cfg(test)] +mod test { + use super::form_items; + + 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)); + + 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() { + let results = &[("username", "user"), ("password", "pass")]; + check_form!("username=user&password=pass", results); + + let results = &[("user", "user"), ("user", "pass")]; + check_form!("user=user&user=pass", results); + + let results = &[("user", ""), ("password", "pass")]; + check_form!("user=&password=pass", results); + + let results = &[("", ""), ("", "")]; + check_form!("=&=", results); + + let results = &[("a", "b")]; + check_form!("a=b", results); + + let results = &[("a", "b")]; + check_form!("a=b&a", results); + + let results = &[("a", "b"), ("a", "")]; + check_form!("a=b&a=", results); + } +} diff --git a/lib/src/request.rs b/lib/src/request.rs index e3f8ffe6..6334aedd 100644 --- a/lib/src/request.rs +++ b/lib/src/request.rs @@ -5,14 +5,16 @@ pub use hyper::server::Request as HyperRequest; pub struct Request<'a> { params: Vec<&'a str>, - uri: &'a str, + pub uri: &'a str, + pub data: &'a [u8] } impl<'a> Request<'a> { - pub fn new(params: Vec<&'a str>, uri: &'a str) -> Request<'a> { + pub fn new(params: Vec<&'a str>, uri: &'a str, data: &'a [u8]) -> Request<'a> { Request { params: params, - uri: uri + uri: uri, + data: data } } diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index cdd08796..de4754d9 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -31,7 +31,7 @@ impl HyperHandler for Rocket { let route = self.router.route(method, uri_str); let mut response = route.map_or(Response::not_found(), |route| { let params = route.get_params(uri_str); - let request = Request::new(params, uri_str); + let request = Request::new(params, uri_str, &buf); (route.handler)(request) }); diff --git a/lib/src/router/uri.rs b/lib/src/router/uri.rs index b064c60c..924978f1 100644 --- a/lib/src/router/uri.rs +++ b/lib/src/router/uri.rs @@ -154,19 +154,15 @@ impl<'a> Iterator for Segments<'a> { fn next(&mut self) -> Option { // Find the start of the next segment (first that's not '/'). - let s = self.0.chars().enumerate().skip_while(|&(_, c)| c == '/').next(); - if s.is_none() { - return None; - } - - // i is the index of the first character that's not '/' - let (i, _) = s.unwrap(); + let i = match self.0.find(|c| c != '/') { + Some(index) => index, + None => return None + }; // Get the index of the first character that _is_ a '/' after start. // j = index of first character after i (hence the i +) that's not a '/' let rest = &self.0[i..]; - let mut end_iter = rest.chars().enumerate().skip_while(|&(_, c)| c != '/'); - let j = end_iter.next().map_or(self.0.len(), |(j, _)| i + j); + let j = rest.find('/').map_or(self.0.len(), |j| i + j); // Save the result, update the iterator, and return! let result = Some(&self.0[i..j]); diff --git a/macros/src/route_decorator.rs b/macros/src/route_decorator.rs index 0a81bd45..12a9648d 100644 --- a/macros/src/route_decorator.rs +++ b/macros/src/route_decorator.rs @@ -310,10 +310,20 @@ fn get_form_stmt(ecx: &ExtCtxt, fn_args: &mut Vec, quote_stmt!(ecx, // TODO: Actually get the form parameters to pass into from_form_string. // Alternatively, pass in some already parsed thing. - let $param_ident: $param_ty = match FormItem::from_form_string("test string") { - Ok(v) => v, - Err(_) => return rocket::Response::not_found() - }; + let $param_ident: $param_ty = { + let form_string = std::str::from_utf8(_req.data); + if form_string.is_err() { + return rocket::Response::not_found() + }; + + match FromForm::from_form_string(form_string.unwrap()) { + Ok(v) => v, + Err(_) => { + println!("\t=> Form failed to parse."); + return rocket::Response::not_found() + } + } + } ) }