This commit changes parsing traits and documents some of the core library:

* All From* trait methods are now named like the trait.
  * All From* traits have an associated Error type.
  * Document all of the `form` module.
  * Add codegen tests for auto-derived forms.
  * The param parsing traits now live under Request.
This commit is contained in:
Sergio Benitez 2016-09-30 01:25:07 -07:00
parent 76cbc14d23
commit 008605bec7
12 changed files with 281 additions and 62 deletions

View File

@ -92,9 +92,10 @@ Contributions are absolutely, positively welcome and encouraged! Contributions
come in many forms. You could:
1. Submit a feature request or bug report as an [issue](https://github.com/SergioBenitez/Rocket/issues).
2. Comment on [issues that require
2. Ask for improved documentation as an [issue](https://github.com/SergioBenitez/Rocket/issues).
3. Comment on [issues that require
feedback](https://github.com/SergioBenitez/Rocket/issues?q=is%3Aissue+is%3Aopen+label%3A%22feedback+wanted%22).
3. Contribute code via [pull requests](https://github.com/SergioBenitez/Rocket/pulls).
4. Contribute code via [pull requests](https://github.com/SergioBenitez/Rocket/pulls).
We aim to keep Rocket's code quality at the highest level. This means that any
code you contribute must be:

View File

@ -2,6 +2,7 @@
use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::print::pprust::{stmt_to_string};
use syntax::parse::token::{str_to_ident};
use syntax::ast::{ItemKind, Expr, MetaItem, Mutability, VariantData};
use syntax::codemap::Span;
use syntax::ext::build::AstBuilder;
@ -58,6 +59,9 @@ pub fn from_form_derive(ecx: &mut ExtCtxt, span: Span, meta_item: &MetaItem,
})
};
// The error type in the derived implementation.
let error_type = ty::Ty::Literal(ty::Path::new(vec!["rocket", "Error"]));
let trait_def = TraitDef {
is_unsafe: false,
supports_unions: false,
@ -88,9 +92,7 @@ pub fn from_form_derive(ecx: &mut ExtCtxt, span: Span, meta_item: &MetaItem,
lifetime: None,
params: vec![
Box::new(ty::Ty::Self_),
Box::new(ty::Ty::Literal(
ty::Path::new(vec!["rocket", "Error"])
)),
Box::new(error_type.clone())
],
global: true,
}
@ -101,7 +103,9 @@ pub fn from_form_derive(ecx: &mut ExtCtxt, span: Span, meta_item: &MetaItem,
unify_fieldless_variants: false,
}
],
associated_types: vec![],
associated_types: vec![
(str_to_ident("Error"), error_type.clone())
],
};
trait_def.expand(ecx, meta_item, annotated, push);
@ -164,12 +168,14 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
let ident_string = ident.to_string();
let id_str = ident_string.as_str();
arms.push(quote_tokens!(cx,
$id_str => $ident = match ::rocket::form::FromFormValue::parse(v) {
Ok(v) => Some(v),
Err(e) => {
println!("\tError parsing form value '{}': {:?}", $id_str, e);
$return_err_stmt
}
$id_str => {
$ident = match ::rocket::form::FromFormValue::from_form_value(v) {
Ok(v) => Some(v),
Err(e) => {
println!("\tError parsing form val '{}': {:?}", $id_str, e);
$return_err_stmt
}
};
},
));
}

View File

@ -0,0 +1,84 @@
#![feature(plugin, custom_derive)]
#![plugin(rocket_codegen)]
extern crate rocket;
use rocket::form::FromForm;
#[derive(Debug, PartialEq, FromForm)]
struct TodoTask {
description: String,
completed: bool
}
// TODO: Make deriving `FromForm` for this enum possible.
#[derive(Debug, PartialEq)]
enum FormOption {
A, B, C
}
use rocket::form::FromFormValue;
impl<'v> FromFormValue<'v> for FormOption {
type Error = &'v str;
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
let variant = match v {
"a" => FormOption::A,
"b" => FormOption::B,
"c" => FormOption::C,
_ => return Err(v)
};
Ok(variant)
}
}
#[derive(Debug, PartialEq, FromForm)]
struct FormInput<'r> {
checkbox: bool,
number: usize,
radio: FormOption,
password: &'r str,
textarea: String,
select: FormOption,
}
#[derive(Debug, PartialEq, FromForm)]
struct DefaultInput<'r> {
arg: Option<&'r str>,
}
fn main() {
// Same number of arguments: simple case.
let task = TodoTask::from_form_string("description=Hello&completed=on");
assert_eq!(task, Ok(TodoTask {
description: "Hello".to_string(),
completed: true
}));
// Argument in string but not in form.
let task = TodoTask::from_form_string("other=a&description=Hello&completed=on");
assert!(task.is_err());
let form_string = &[
"password=testing", "checkbox=off", "checkbox=on", "number=10",
"checkbox=off", "textarea=", "select=a", "radio=c",
].join("&");
let input = FormInput::from_form_string(&form_string);
assert_eq!(input, Ok(FormInput {
checkbox: false,
number: 10,
radio: FormOption::C,
password: "testing",
textarea: "".to_string(),
select: FormOption::A,
}));
// Argument not in string with default in form.
let default = DefaultInput::from_form_string("");
assert_eq!(default, Ok(DefaultInput {
arg: None
}));
}

View File

@ -25,7 +25,7 @@ struct UserLogin<'r> {
impl<'v> FromFormValue<'v> for StrongPassword<'v> {
type Error = &'static str;
fn parse(v: &'v str) -> Result<Self, Self::Error> {
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
if v.len() < 8 {
Err("Too short!")
} else {
@ -37,8 +37,8 @@ impl<'v> FromFormValue<'v> for StrongPassword<'v> {
impl<'v> FromFormValue<'v> for AdultAge {
type Error = &'static str;
fn parse(v: &'v str) -> Result<Self, Self::Error> {
let age = match isize::parse(v) {
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
let age = match isize::from_form_value(v) {
Ok(v) => v,
Err(_) => return Err("Age value is not a number."),
};

View File

@ -17,7 +17,7 @@ enum FormOption {
impl<'v> FromFormValue<'v> for FormOption {
type Error = &'v str;
fn parse(v: &'v str) -> Result<Self, Self::Error> {
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
let variant = match v {
"a" => FormOption::A,
"b" => FormOption::B,

View File

@ -3,7 +3,7 @@
extern crate rocket;
use rocket::{Rocket, Error};
use rocket::Rocket;
#[derive(FromForm)]
struct Person<'r> {

View File

@ -1,25 +1,111 @@
//! 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};
use url;
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 {
fn from_form_string(s: &'f str) -> Result<Self, Error>;
/// 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 ued during debugging!
#[doc(hidden)]
/// 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::form::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;
fn parse(v: &'v str) -> Result<Self, Self::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
@ -32,7 +118,7 @@ pub trait FromFormValue<'v>: Sized {
impl<'v> FromFormValue<'v> for &'v str {
type Error = Error;
fn parse(v: &'v str) -> Result<Self, Self::Error> {
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
Ok(v)
}
}
@ -41,7 +127,7 @@ impl<'v> FromFormValue<'v> for String {
type Error = &'v str;
// This actually parses the value according to the standard.
fn parse(v: &'v str) -> Result<Self, Self::Error> {
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 {
@ -64,7 +150,7 @@ 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> {
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
match v {
"on" | "true" => Ok(true),
"off" | "false" => Ok(false),
@ -77,7 +163,7 @@ macro_rules! impl_with_fromstr {
($($T:ident),+) => ($(
impl<'v> FromFormValue<'v> for $T {
type Error = &'v str;
fn parse(v: &'v str) -> Result<Self, Self::Error> {
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
$T::from_str(v).map_err(|_| v)
}
}
@ -90,8 +176,8 @@ impl_with_fromstr!(f32, f64, isize, i8, i16, i32, i64, usize, u8, u16, u32, u64,
impl<'v, T: FromFormValue<'v>> FromFormValue<'v> for Option<T> {
type Error = Error;
fn parse(v: &'v str) -> Result<Self, Self::Error> {
match T::parse(v) {
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)
}
@ -106,14 +192,43 @@ impl<'v, T: FromFormValue<'v>> FromFormValue<'v> for Option<T> {
impl<'v, T: FromFormValue<'v>> FromFormValue<'v> for Result<T, T::Error> {
type Error = Error;
fn parse(v: &'v str) -> Result<Self, Self::Error> {
match T::parse(v) {
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::form::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::form::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> {

View File

@ -13,7 +13,7 @@
//! chapter](https://rocket.rs/guide/getting_started) of the guide.
//!
//! You may also be interested in looking at the [contrib API
//! documentation](../rocket_contrib), which contains JSON and templaring
//! documentation](../rocket_contrib), which contains JSON and templating
//! support.
extern crate term_painter;
@ -22,6 +22,7 @@ extern crate url;
extern crate mime;
#[macro_use] extern crate log;
#[doc(hidden)]
#[macro_use]
pub mod logger;
pub mod form;
@ -32,30 +33,33 @@ pub mod content_type;
mod method;
mod error;
mod param;
mod router;
mod rocket;
mod codegen;
mod catcher;
#[doc(hidden)]
/// Defines the types for request and error handlers.
pub mod handler {
use super::{Request, Response, Error};
/// The type of a request handler.
pub type Handler = for<'r> fn(&'r Request<'r>) -> Response<'r>;
/// The type of an error handler.
pub type ErrorHandler = for<'r> fn(error: Error, &'r Request<'r>) -> Response<'r>;
}
#[doc(hidden)]
pub use logger::{RocketLogger, LoggingLevel};
pub use content_type::ContentType;
pub use codegen::{StaticRouteInfo, StaticCatchInfo};
pub use request::Request;
pub use method::Method;
#[doc(inline)]
pub use response::{Response, Responder};
pub use error::Error;
pub use param::{FromParam, FromSegments};
pub use router::{Router, Route};
pub use catcher::Catcher;
pub use rocket::Rocket;
#[doc(inline)]
pub use handler::{Handler, ErrorHandler};
#[doc(inline)]
pub use logger::LoggingLevel;

View File

@ -1,9 +1,12 @@
//! Rocket's logging infrastructure.
use log::{self, Log, LogLevel, LogRecord, LogMetadata};
use term_painter::Color::*;
use term_painter::ToStyle;
pub struct RocketLogger(LoggingLevel);
struct RocketLogger(LoggingLevel);
/// Defines the different levels for log messages.
#[derive(PartialEq)]
pub enum LoggingLevel {
/// Only shows errors and warning.

View File

@ -1,17 +1,18 @@
//! Types and traits that deal with request parsing and handling.
mod request;
mod param;
mod from_request;
pub use self::request::Request;
pub use self::from_request::FromRequest;
#[doc(hidden)]
pub use hyper::server::Request as HyperRequest;
#[doc(hidden)]
pub use hyper::header::Headers as HyperHeaders;
#[doc(hidden)]
pub use hyper::header::Cookie as HyperCookie;
pub use self::param::{FromParam, FromSegments};
pub use hyper::header::CookiePair as Cookie;
// Unexported Hyper types.
#[doc(hidden)] pub use hyper::server::Request as HyperRequest;
#[doc(hidden)] pub use hyper::header::Headers as HyperHeaders;
#[doc(hidden)] pub use hyper::header::Cookie as HyperCookie;
use hyper::header::CookieJar;
pub type Cookies = CookieJar<'static>;

View File

@ -1,34 +1,36 @@
use std::str::FromStr;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr};
use std::path::PathBuf;
use router::Segments;
use router::Segments;
use url;
use error::Error;
pub trait FromParam<'a>: Sized {
fn from_param(param: &'a str) -> Result<Self, Error>;
type Error;
fn from_param(param: &'a str) -> Result<Self, Self::Error>;
}
impl<'a> FromParam<'a> for &'a str {
fn from_param(param: &'a str) -> Result<&'a str, Error> {
type Error = ();
fn from_param(param: &'a str) -> Result<&'a str, Self::Error> {
Ok(param)
}
}
impl<'a> FromParam<'a> for String {
fn from_param(p: &'a str) -> Result<String, Error> {
type Error = &'a str;
fn from_param(p: &'a str) -> Result<String, Self::Error> {
let decoder = url::percent_encoding::percent_decode(p.as_bytes());
decoder.decode_utf8().map_err(|_| Error::BadParse).map(|s| s.into_owned())
decoder.decode_utf8().map_err(|_| p).map(|s| s.into_owned())
}
}
macro_rules! impl_with_fromstr {
($($T:ident),+) => ($(
impl<'a> FromParam<'a> for $T {
fn from_param(param: &'a str) -> Result<Self, Error> {
$T::from_str(param).map_err(|_| Error::BadParse)
type Error = &'a str;
fn from_param(param: &'a str) -> Result<Self, Self::Error> {
$T::from_str(param).map_err(|_| param)
}
}
)+)
@ -39,17 +41,20 @@ impl_with_fromstr!(f32, f64, isize, i8, i16, i32, i64, usize, u8, u16, u32, u64,
SocketAddr);
pub trait FromSegments<'a>: Sized {
fn from_segments(segments: Segments<'a>) -> Result<Self, Error>;
type Error;
fn from_segments(segments: Segments<'a>) -> Result<Self, Self::Error>;
}
impl<'a> FromSegments<'a> for Segments<'a> {
fn from_segments(segments: Segments<'a>) -> Result<Segments<'a>, Error> {
type Error = ();
fn from_segments(segments: Segments<'a>) -> Result<Segments<'a>, ()> {
Ok(segments)
}
}
impl<'a> FromSegments<'a> for PathBuf {
fn from_segments(segments: Segments<'a>) -> Result<PathBuf, Error> {
type Error = ();
fn from_segments(segments: Segments<'a>) -> Result<PathBuf, ()> {
Ok(segments.collect())
}
}

View File

@ -6,7 +6,7 @@ use term_painter::Color::*;
use term_painter::ToStyle;
use error::Error;
use param::{FromParam, FromSegments};
use super::{FromParam, FromSegments};
use method::Method;
use content_type::ContentType;
@ -36,7 +36,7 @@ impl<'a> Request<'a> {
debug!("{} is >= param count {}", n, params.as_ref().unwrap().len());
Err(Error::NoKey)
} else {
T::from_param(params.as_ref().unwrap()[n])
T::from_param(params.as_ref().unwrap()[n]).map_err(|_| Error::BadParse)
}
}
@ -55,7 +55,7 @@ impl<'a> Request<'a> {
// but the std lib doesn't implement it for Skip.
let mut segments = self.uri.segments();
for _ in segments.by_ref().take(i) { /* do nothing */ }
T::from_segments(segments)
T::from_segments(segments).map_err(|_| Error::BadParse)
}
}