2018-08-07 02:58:07 +00:00
|
|
|
#[macro_use] extern crate rocket;
|
|
|
|
|
2018-09-26 08:13:57 +00:00
|
|
|
use rocket::request::{FromForm, FormItems, FormParseError};
|
2018-08-07 02:58:07 +00:00
|
|
|
use rocket::http::RawStr;
|
|
|
|
|
2018-09-26 08:13:57 +00:00
|
|
|
fn parse<'f, T>(string: &'f str, strict: bool) -> Result<T, FormParseError<'f>>
|
|
|
|
where T: FromForm<'f, Error = FormParseError<'f>>
|
2018-08-07 02:58:07 +00:00
|
|
|
{
|
|
|
|
let mut items = FormItems::from(string);
|
|
|
|
let result = T::from_form(items.by_ref(), strict);
|
|
|
|
if !items.exhaust() {
|
|
|
|
panic!("Invalid form input.");
|
|
|
|
}
|
|
|
|
|
|
|
|
result
|
|
|
|
}
|
|
|
|
|
2018-09-26 08:13:57 +00:00
|
|
|
fn strict<'f, T>(string: &'f str) -> Result<T, FormParseError<'f>>
|
|
|
|
where T: FromForm<'f, Error = FormParseError<'f>>
|
2018-08-07 02:58:07 +00:00
|
|
|
{
|
|
|
|
parse(string, true)
|
|
|
|
}
|
|
|
|
|
2018-09-26 08:13:57 +00:00
|
|
|
fn lenient<'f, T>(string: &'f str) -> Result<T, FormParseError<'f>>
|
|
|
|
where T: FromForm<'f, Error = FormParseError<'f>>
|
2018-08-07 02:58:07 +00:00
|
|
|
{
|
|
|
|
parse(string, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, FromForm)]
|
|
|
|
struct TodoTask {
|
|
|
|
description: String,
|
|
|
|
completed: bool
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn simple() {
|
|
|
|
// Same number of arguments: simple case.
|
|
|
|
let task: Option<TodoTask> = strict("description=Hello&completed=on").ok();
|
|
|
|
assert_eq!(task, Some(TodoTask {
|
|
|
|
description: "Hello".to_string(),
|
|
|
|
completed: true
|
|
|
|
}));
|
|
|
|
|
|
|
|
// Argument in string but not in form.
|
|
|
|
let task: Option<TodoTask> = strict("other=a&description=Hello&completed=on").ok();
|
|
|
|
assert!(task.is_none());
|
|
|
|
|
|
|
|
// Ensure _method isn't required.
|
|
|
|
let task: Option<TodoTask> = strict("_method=patch&description=Hello&completed=off").ok();
|
|
|
|
assert_eq!(task, Some(TodoTask {
|
|
|
|
description: "Hello".to_string(),
|
|
|
|
completed: false
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, FromFormValue)]
|
|
|
|
enum FormOption {
|
|
|
|
A, B, C
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, FromForm)]
|
|
|
|
struct FormInput<'r> {
|
|
|
|
checkbox: bool,
|
|
|
|
number: usize,
|
|
|
|
radio: FormOption,
|
|
|
|
password: &'r RawStr,
|
|
|
|
textarea: String,
|
|
|
|
select: FormOption,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, FromForm)]
|
|
|
|
struct DefaultInput<'r> {
|
|
|
|
arg: Option<&'r RawStr>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, FromForm)]
|
|
|
|
struct ManualMethod<'r> {
|
|
|
|
_method: Option<&'r RawStr>,
|
|
|
|
done: bool
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, FromForm)]
|
|
|
|
struct UnpresentCheckbox {
|
|
|
|
checkbox: bool
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, FromForm)]
|
|
|
|
struct UnpresentCheckboxTwo<'r> {
|
|
|
|
checkbox: bool,
|
|
|
|
something: &'r RawStr
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, FromForm)]
|
|
|
|
struct FieldNamedV<'r> {
|
|
|
|
v: &'r RawStr,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn base_conditions() {
|
|
|
|
let form_string = &[
|
|
|
|
"password=testing", "checkbox=off", "checkbox=on", "number=10",
|
|
|
|
"checkbox=off", "textarea=", "select=a", "radio=c",
|
|
|
|
].join("&");
|
|
|
|
|
2019-06-13 01:59:25 +00:00
|
|
|
let input: Option<FormInput<'_>> = strict(&form_string).ok();
|
2018-08-07 02:58:07 +00:00
|
|
|
assert_eq!(input, Some(FormInput {
|
|
|
|
checkbox: false,
|
|
|
|
number: 10,
|
|
|
|
radio: FormOption::C,
|
|
|
|
password: "testing".into(),
|
|
|
|
textarea: "".to_string(),
|
|
|
|
select: FormOption::A,
|
|
|
|
}));
|
|
|
|
|
|
|
|
// Argument not in string with default in form.
|
2019-06-13 01:59:25 +00:00
|
|
|
let default: Option<DefaultInput<'_>> = strict("").ok();
|
2018-08-07 02:58:07 +00:00
|
|
|
assert_eq!(default, Some(DefaultInput {
|
|
|
|
arg: None
|
|
|
|
}));
|
|
|
|
|
|
|
|
// Ensure _method can be captured if desired.
|
2019-06-13 01:59:25 +00:00
|
|
|
let manual: Option<ManualMethod<'_>> = strict("_method=put&done=true").ok();
|
2018-08-07 02:58:07 +00:00
|
|
|
assert_eq!(manual, Some(ManualMethod {
|
|
|
|
_method: Some("put".into()),
|
|
|
|
done: true
|
|
|
|
}));
|
|
|
|
|
2019-06-13 01:59:25 +00:00
|
|
|
let manual: Option<ManualMethod<'_>> = lenient("_method=put&done=true").ok();
|
2018-08-07 02:58:07 +00:00
|
|
|
assert_eq!(manual, Some(ManualMethod {
|
|
|
|
_method: Some("put".into()),
|
|
|
|
done: true
|
|
|
|
}));
|
|
|
|
|
|
|
|
// And ignored when not present.
|
2019-06-13 01:59:25 +00:00
|
|
|
let manual: Option<ManualMethod<'_>> = strict("done=true").ok();
|
2018-08-07 02:58:07 +00:00
|
|
|
assert_eq!(manual, Some(ManualMethod {
|
|
|
|
_method: None,
|
|
|
|
done: true
|
|
|
|
}));
|
|
|
|
|
|
|
|
// Check that a `bool` value that isn't in the form is marked as `false`.
|
|
|
|
let manual: Option<UnpresentCheckbox> = strict("").ok();
|
|
|
|
assert_eq!(manual, Some(UnpresentCheckbox {
|
|
|
|
checkbox: false
|
|
|
|
}));
|
|
|
|
|
|
|
|
// Check that a `bool` value that isn't in the form is marked as `false`.
|
2019-06-13 01:59:25 +00:00
|
|
|
let manual: Option<UnpresentCheckboxTwo<'_>> = strict("something=hello").ok();
|
2018-08-07 02:58:07 +00:00
|
|
|
assert_eq!(manual, Some(UnpresentCheckboxTwo {
|
|
|
|
checkbox: false,
|
|
|
|
something: "hello".into()
|
|
|
|
}));
|
|
|
|
|
|
|
|
// Check that a structure with one field `v` parses correctly.
|
2019-06-13 01:59:25 +00:00
|
|
|
let manual: Option<FieldNamedV<'_>> = strict("v=abc").ok();
|
2018-08-07 02:58:07 +00:00
|
|
|
assert_eq!(manual, Some(FieldNamedV {
|
|
|
|
v: "abc".into()
|
|
|
|
}));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn lenient_parsing() {
|
|
|
|
// Check that a structure with one field `v` parses correctly (lenient).
|
2019-06-13 01:59:25 +00:00
|
|
|
let manual: Option<FieldNamedV<'_>> = lenient("v=abc").ok();
|
2018-08-07 02:58:07 +00:00
|
|
|
assert_eq!(manual, Some(FieldNamedV { v: "abc".into() }));
|
|
|
|
|
2019-06-13 01:59:25 +00:00
|
|
|
let manual: Option<FieldNamedV<'_>> = lenient("v=abc&a=123").ok();
|
2018-08-07 02:58:07 +00:00
|
|
|
assert_eq!(manual, Some(FieldNamedV { v: "abc".into() }));
|
|
|
|
|
2019-06-13 01:59:25 +00:00
|
|
|
let manual: Option<FieldNamedV<'_>> = lenient("c=abcddef&v=abc&a=123").ok();
|
2018-08-07 02:58:07 +00:00
|
|
|
assert_eq!(manual, Some(FieldNamedV { v: "abc".into() }));
|
|
|
|
|
|
|
|
// Check default values (bool) with lenient parsing.
|
2019-06-13 01:59:25 +00:00
|
|
|
let manual: Option<UnpresentCheckboxTwo<'_>> = lenient("something=hello").ok();
|
2018-08-07 02:58:07 +00:00
|
|
|
assert_eq!(manual, Some(UnpresentCheckboxTwo {
|
|
|
|
checkbox: false,
|
|
|
|
something: "hello".into()
|
|
|
|
}));
|
|
|
|
|
2019-06-13 01:59:25 +00:00
|
|
|
let manual: Option<UnpresentCheckboxTwo<'_>> = lenient("hi=hi&something=hello").ok();
|
2018-08-07 02:58:07 +00:00
|
|
|
assert_eq!(manual, Some(UnpresentCheckboxTwo {
|
|
|
|
checkbox: false,
|
|
|
|
something: "hello".into()
|
|
|
|
}));
|
|
|
|
|
|
|
|
// Check that a missing field doesn't parse, even leniently.
|
2019-06-13 01:59:25 +00:00
|
|
|
let manual: Option<FieldNamedV<'_>> = lenient("a=abc").ok();
|
2018-08-07 02:58:07 +00:00
|
|
|
assert!(manual.is_none());
|
|
|
|
|
2019-06-13 01:59:25 +00:00
|
|
|
let manual: Option<FieldNamedV<'_>> = lenient("_method=abc").ok();
|
2018-08-07 02:58:07 +00:00
|
|
|
assert!(manual.is_none());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, FromForm)]
|
|
|
|
struct RenamedForm {
|
|
|
|
single: usize,
|
|
|
|
#[form(field = "camelCase")]
|
|
|
|
camel_case: String,
|
|
|
|
#[form(field = "TitleCase")]
|
|
|
|
title_case: String,
|
|
|
|
#[form(field = "type")]
|
|
|
|
field_type: isize,
|
|
|
|
#[form(field = "DOUBLE")]
|
|
|
|
double: String,
|
|
|
|
#[form(field = "a.b")]
|
|
|
|
dot: isize,
|
|
|
|
#[form(field = "some space")]
|
|
|
|
some_space: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn field_renaming() {
|
|
|
|
let form_string = &[
|
|
|
|
"single=100", "camelCase=helloThere", "TitleCase=HiHi", "type=-2",
|
|
|
|
"DOUBLE=bing_bong", "a.b=123", "some space=okay"
|
|
|
|
].join("&");
|
|
|
|
|
|
|
|
let form: Option<RenamedForm> = strict(&form_string).ok();
|
|
|
|
assert_eq!(form, Some(RenamedForm {
|
|
|
|
single: 100,
|
|
|
|
camel_case: "helloThere".into(),
|
|
|
|
title_case: "HiHi".into(),
|
|
|
|
field_type: -2,
|
|
|
|
double: "bing_bong".into(),
|
|
|
|
dot: 123,
|
|
|
|
some_space: "okay".into(),
|
|
|
|
}));
|
|
|
|
|
|
|
|
let form_string = &[
|
|
|
|
"single=100", "camel_case=helloThere", "TitleCase=HiHi", "type=-2",
|
|
|
|
"DOUBLE=bing_bong", "dot=123", "some_space=okay"
|
|
|
|
].join("&");
|
|
|
|
|
|
|
|
let form: Option<RenamedForm> = strict(&form_string).ok();
|
|
|
|
assert!(form.is_none());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(FromForm, Debug, PartialEq)]
|
|
|
|
struct YetOneMore<'f, T> {
|
|
|
|
string: &'f RawStr,
|
|
|
|
other: T,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(FromForm, Debug, PartialEq)]
|
|
|
|
struct Oops<A, B, C> {
|
|
|
|
base: String,
|
|
|
|
a: A,
|
|
|
|
b: B,
|
|
|
|
c: C,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn generics() {
|
|
|
|
let form_string = &[
|
|
|
|
"string=hello", "other=00128"
|
|
|
|
].join("&");
|
|
|
|
|
2019-06-13 01:59:25 +00:00
|
|
|
let form: Option<YetOneMore<'_, usize>> = strict(&form_string).ok();
|
2018-08-07 02:58:07 +00:00
|
|
|
assert_eq!(form, Some(YetOneMore {
|
|
|
|
string: "hello".into(),
|
|
|
|
other: 128,
|
|
|
|
}));
|
|
|
|
|
2019-06-13 01:59:25 +00:00
|
|
|
let form: Option<YetOneMore<'_, u8>> = strict(&form_string).ok();
|
2018-08-07 02:58:07 +00:00
|
|
|
assert_eq!(form, Some(YetOneMore {
|
|
|
|
string: "hello".into(),
|
|
|
|
other: 128,
|
|
|
|
}));
|
|
|
|
|
2019-06-13 01:59:25 +00:00
|
|
|
let form: Option<YetOneMore<'_, i8>> = strict(&form_string).ok();
|
2018-08-07 02:58:07 +00:00
|
|
|
assert!(form.is_none());
|
|
|
|
|
|
|
|
let form_string = &[
|
|
|
|
"base=just%20a%20test", "a=hey%20there", "b=a", "c=811",
|
|
|
|
].join("&");
|
|
|
|
|
|
|
|
let form: Option<Oops<&RawStr, FormOption, usize>> = strict(&form_string).ok();
|
|
|
|
assert_eq!(form, Some(Oops {
|
|
|
|
base: "just a test".into(),
|
|
|
|
a: "hey%20there".into(),
|
|
|
|
b: FormOption::A,
|
|
|
|
c: 811,
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, FromForm)]
|
|
|
|
struct WhoopsForm {
|
|
|
|
complete: bool,
|
|
|
|
other: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn form_errors() {
|
|
|
|
let form: Result<WhoopsForm, _> = strict("complete=true&other=781");
|
|
|
|
assert_eq!(form, Ok(WhoopsForm { complete: true, other: 781 }));
|
|
|
|
|
|
|
|
let form: Result<WhoopsForm, _> = strict("complete=true&other=unknown");
|
2018-09-26 08:13:57 +00:00
|
|
|
assert_eq!(form, Err(FormParseError::BadValue("other".into(), "unknown".into())));
|
2018-08-07 02:58:07 +00:00
|
|
|
|
|
|
|
let form: Result<WhoopsForm, _> = strict("complete=unknown&other=unknown");
|
2018-09-26 08:13:57 +00:00
|
|
|
assert_eq!(form, Err(FormParseError::BadValue("complete".into(), "unknown".into())));
|
2018-08-07 02:58:07 +00:00
|
|
|
|
|
|
|
let form: Result<WhoopsForm, _> = strict("complete=true&other=1&extra=foo");
|
2018-09-26 08:13:57 +00:00
|
|
|
assert_eq!(form, Err(FormParseError::Unknown("extra".into(), "foo".into())));
|
2018-08-07 02:58:07 +00:00
|
|
|
|
|
|
|
// Bad values take highest precedence.
|
|
|
|
let form: Result<WhoopsForm, _> = strict("complete=unknown&unknown=foo");
|
2018-09-26 08:13:57 +00:00
|
|
|
assert_eq!(form, Err(FormParseError::BadValue("complete".into(), "unknown".into())));
|
2018-08-07 02:58:07 +00:00
|
|
|
|
|
|
|
// Then unknown key/values for strict parses.
|
|
|
|
let form: Result<WhoopsForm, _> = strict("complete=true&unknown=foo");
|
2018-09-26 08:13:57 +00:00
|
|
|
assert_eq!(form, Err(FormParseError::Unknown("unknown".into(), "foo".into())));
|
2018-08-07 02:58:07 +00:00
|
|
|
|
|
|
|
// Finally, missing.
|
|
|
|
let form: Result<WhoopsForm, _> = strict("complete=true");
|
2018-09-26 08:13:57 +00:00
|
|
|
assert_eq!(form, Err(FormParseError::Missing("other".into())));
|
2018-08-07 02:58:07 +00:00
|
|
|
}
|