Rocket/core/codegen/tests/from_form.rs

1041 lines
33 KiB
Rust

use std::net::{IpAddr, SocketAddr};
use std::collections::{BTreeMap, HashMap};
use pretty_assertions::assert_eq;
use rocket::UriDisplayQuery;
use rocket::http::uri::fmt::{UriDisplay, Query};
use rocket::form::{self, Form, Strict, FromForm, FromFormField, Errors};
use rocket::form::error::{ErrorKind, Entity};
use rocket::serde::json::Json;
fn strict<'f, T: FromForm<'f>>(string: &'f str) -> Result<T, Errors<'f>> {
Form::<Strict<T>>::parse(string).map(|s| s.into_inner())
}
fn lenient<'f, T: FromForm<'f>>(string: &'f str) -> Result<T, Errors<'f>> {
Form::<T>::parse(string)
}
fn strict_encoded<T: 'static>(string: &str) -> Result<T, Errors<'static>>
where for<'a> T: FromForm<'a>
{
Form::<Strict<T>>::parse_encoded(string.into()).map(|s| s.into_inner())
}
#[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());
let task: Option<TodoTask> = lenient("other=a&description=Hello&completed=on").ok();
assert_eq!(task, Some(TodoTask {
description: "Hello".to_string(),
completed: true
}));
// 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, FromFormField)]
enum FormOption {
A, B, C
}
#[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>,
}
#[derive(Debug, PartialEq, FromForm)]
struct ManualMethod<'r> {
_method: Option<&'r str>,
done: bool
}
#[derive(Debug, PartialEq, FromForm)]
struct UnpresentCheckbox {
checkbox: bool
}
#[derive(Debug, PartialEq, FromForm)]
struct UnpresentCheckboxTwo<'r> {
checkbox: bool,
something: &'r str
}
#[derive(Debug, PartialEq, FromForm)]
struct FieldNamedV<'r> {
v: &'r str,
}
#[test]
fn base_conditions() {
let form_string = &[
"password=testing", "checkbox=off", "number=10", "textarea=",
"select=a", "radio=c",
].join("&");
let input: Result<FormInput<'_>, _> = strict(&form_string);
assert_eq!(input, Ok(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.
let default: Option<DefaultInput<'_>> = strict("").ok();
assert_eq!(default, Some(DefaultInput {
arg: None
}));
// Ensure _method can be captured if desired.
let manual: Option<ManualMethod<'_>> = strict("_method=put&done=true").ok();
assert_eq!(manual, Some(ManualMethod {
_method: Some("put".into()),
done: true
}));
let manual: Option<ManualMethod<'_>> = lenient("_method=put&done=true").ok();
assert_eq!(manual, Some(ManualMethod {
_method: Some("put".into()),
done: true
}));
// And ignored when not present.
let manual: Option<ManualMethod<'_>> = strict("done=true").ok();
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> = lenient("").ok();
assert_eq!(manual, Some(UnpresentCheckbox {
checkbox: false
}));
// Check that a `bool` value that isn't in the form is marked as `false`.
let manual: Option<UnpresentCheckboxTwo<'_>> = lenient("something=hello").ok();
assert_eq!(manual, Some(UnpresentCheckboxTwo {
checkbox: false,
something: "hello".into()
}));
// Check that a structure with one field `v` parses correctly.
let manual: Option<FieldNamedV<'_>> = strict("v=abc").ok();
assert_eq!(manual, Some(FieldNamedV {
v: "abc".into()
}));
}
#[test]
fn lenient_parsing() {
// Check that a structure with one field `v` parses correctly (lenient).
let manual: Option<FieldNamedV<'_>> = lenient("v=abc").ok();
assert_eq!(manual, Some(FieldNamedV { v: "abc".into() }));
let manual: Option<FieldNamedV<'_>> = lenient("v=abc&a=123").ok();
assert_eq!(manual, Some(FieldNamedV { v: "abc".into() }));
let manual: Option<FieldNamedV<'_>> = lenient("c=abcddef&v=abc&a=123").ok();
assert_eq!(manual, Some(FieldNamedV { v: "abc".into() }));
// Check default values (bool) with lenient parsing.
let manual: Option<UnpresentCheckboxTwo<'_>> = lenient("something=hello").ok();
assert_eq!(manual, Some(UnpresentCheckboxTwo {
checkbox: false,
something: "hello".into()
}));
let manual: Option<UnpresentCheckboxTwo<'_>> = lenient("hi=hi&something=hello").ok();
assert_eq!(manual, Some(UnpresentCheckboxTwo {
checkbox: false,
something: "hello".into()
}));
// Check that a missing field doesn't parse, even leniently.
let manual: Option<FieldNamedV<'_>> = lenient("a=abc").ok();
assert!(manual.is_none());
let manual: Option<FieldNamedV<'_>> = lenient("_method=abc").ok();
assert!(manual.is_none());
}
#[test]
fn field_renaming() {
#[derive(Debug, PartialEq, FromForm)]
struct RenamedForm {
single: usize,
#[field(name = "camelCase")]
camel_case: String,
#[field(name = "TitleCase")]
title_case: String,
#[field(name = "type")]
field_type: isize,
#[field(name = "DOUBLE")]
double: String,
#[field(name = "a:b")]
colon: isize,
}
let form_string = &[
"single=100", "camelCase=helloThere", "TitleCase=HiHi", "type=-2",
"DOUBLE=bing_bong", "a:b=123"
].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(),
colon: 123,
}));
let form_string = &[
"single=100", "camel_case=helloThere", "TitleCase=HiHi", "type=-2",
"DOUBLE=bing_bong", "colon=123"
].join("&");
let form: Option<RenamedForm> = strict(&form_string).ok();
assert!(form.is_none());
#[derive(Debug, PartialEq, FromForm)]
struct MultiName<'r> {
single: usize,
#[field(name = "SomeCase")]
#[field(name = "some_case")]
some_case: &'r str,
}
let form_string = &["single=123", "some_case=hi_im_here"].join("&");
let form: Option<MultiName> = strict(&form_string).ok();
assert_eq!(form, Some(MultiName { single: 123, some_case: "hi_im_here", }));
let form_string = &["single=123", "SomeCase=HiImHere"].join("&");
let form: Option<MultiName> = strict(&form_string).ok();
assert_eq!(form, Some(MultiName { single: 123, some_case: "HiImHere", }));
let form_string = &["single=123", "some_case=hi_im_here", "SomeCase=HiImHere"].join("&");
let form: Option<MultiName> = strict(&form_string).ok();
assert!(form.is_none());
let form_string = &["single=123", "some_case=hi_im_here", "SomeCase=HiImHere"].join("&");
let form: Option<MultiName> = lenient(&form_string).ok();
assert_eq!(form, Some(MultiName { single: 123, some_case: "hi_im_here", }));
let form_string = &["single=123", "SomeCase=HiImHere", "some_case=hi_im_here"].join("&");
let form: Option<MultiName> = lenient(&form_string).ok();
assert_eq!(form, Some(MultiName { single: 123, some_case: "HiImHere", }));
#[derive(Debug, PartialEq, FromForm)]
struct CaseInsensitive<'r> {
#[field(name = uncased("SomeCase"))]
#[field(name = "some_case")]
some_case: &'r str,
#[field(name = uncased("hello"))]
hello: usize,
}
let form_string = &["HeLLO=123", "sOMECASe=hi_im_here"].join("&");
let form: Option<CaseInsensitive> = strict(&form_string).ok();
assert_eq!(form, Some(CaseInsensitive { hello: 123, some_case: "hi_im_here", }));
let form_string = &["hello=456", "SomeCase=HiImHere"].join("&");
let form: Option<CaseInsensitive> = strict(&form_string).ok();
assert_eq!(form, Some(CaseInsensitive { hello: 456, some_case: "HiImHere", }));
let form_string = &["helLO=789", "some_case=hi_there"].join("&");
let form: Option<CaseInsensitive> = strict(&form_string).ok();
assert_eq!(form, Some(CaseInsensitive { hello: 789, some_case: "hi_there", }));
let form_string = &["hello=123", "SOme_case=hi_im_here"].join("&");
let form: Option<CaseInsensitive> = strict(&form_string).ok();
assert!(form.is_none());
}
#[test]
fn generics() {
#[derive(FromForm, Debug, PartialEq)]
struct Oops<A, B, C> {
base: String,
a: A,
b: B,
c: C,
}
#[derive(FromForm, Debug, PartialEq)]
struct YetOneMore<'f, T> {
string: &'f str,
other: T,
}
let form_string = &[
"string=hello", "other=00128"
].join("&");
let form: Option<YetOneMore<'_, usize>> = strict(&form_string).ok();
assert_eq!(form, Some(YetOneMore {
string: "hello".into(),
other: 128,
}));
let form: Option<YetOneMore<'_, u8>> = strict(&form_string).ok();
assert_eq!(form, Some(YetOneMore {
string: "hello".into(),
other: 128,
}));
let form: Option<YetOneMore<'_, i8>> = strict(&form_string).ok();
assert!(form.is_none());
let form_string = "base=just%20a%20test&a=hey%20there&b=a&c=811";
let form: Option<Oops<String, FormOption, usize>> = strict_encoded(&form_string).ok();
assert_eq!(form, Some(Oops {
base: "just a test".into(),
a: "hey there".into(),
b: FormOption::A,
c: 811,
}));
}
#[test]
fn form_errors() {
#[derive(Debug, PartialEq, FromForm)]
struct WhoopsForm {
complete: bool,
other: usize,
}
let form: Result<WhoopsForm, _> = strict("complete=true&other=781");
assert_eq!(form, Ok(WhoopsForm { complete: true, other: 781 }));
let errors = strict::<WhoopsForm>("complete=true&other=unknown").unwrap_err();
assert!(errors.iter().any(|e| {
e.name.as_ref().unwrap() == "other"
&& e.value.as_deref() == Some("unknown")
&& matches!(e.kind, ErrorKind::Int(..))
}));
let errors = strict::<WhoopsForm>("complete=unknown&other=unknown").unwrap_err();
assert!(errors.iter().any(|e| {
"complete" == e.name.as_ref().unwrap()
&& e.value.as_deref() == Some("unknown")
&& matches!(e.kind, ErrorKind::Bool(..))
}));
let errors = strict::<WhoopsForm>("complete=true&other=1&extra=foo").unwrap_err();
assert!(errors.iter().any(|e| {
e.name.as_ref().unwrap() == "extra"
&& e.value.as_deref() == Some("foo")
&& matches!(e.kind, ErrorKind::Unexpected)
}));
let errors = strict::<WhoopsForm>("complete=unknown&unknown=!").unwrap_err();
assert!(errors.iter().any(|e| {
e.name.as_ref().unwrap() == "complete"
&& e.value.as_deref() == Some("unknown")
&& matches!(e.kind, ErrorKind::Bool(..))
}));
assert!(errors.iter().any(|e| {
e.name.as_ref().unwrap() == "unknown"
&& e.value.as_deref() == Some("!")
&& matches!(e.kind, ErrorKind::Unexpected)
}));
let errors = strict::<WhoopsForm>("unknown=!").unwrap_err();
assert!(errors.iter().any(|e| {
e.name.as_ref().unwrap() == "unknown"
&& e.value.as_deref() == Some("!")
&& matches!(e.kind, ErrorKind::Unexpected)
}));
assert!(errors.iter().any(|e| {
e.name.as_ref().unwrap() == "complete"
&& e.value.is_none()
&& e.entity == Entity::Field
&& matches!(e.kind, ErrorKind::Missing)
}));
assert!(errors.iter().any(|e| {
e.name.as_ref().unwrap() == "other"
&& e.value.is_none()
&& e.entity == Entity::Field
&& matches!(e.kind, ErrorKind::Missing)
}));
let errors = strict::<WhoopsForm>("complete=true").unwrap_err();
assert!(errors.iter().any(|e| {
e.name.as_ref().unwrap() == "other"
&& e.value.is_none()
&& e.entity == Entity::Field
&& matches!(e.kind, ErrorKind::Missing)
}));
}
#[test]
fn form_validate_error_return_correct_field_name() {
fn evaluate_other<'v>(_other: &str, _check: &bool) -> form::Result<'v, ()> {
Err(form::Error::validation(""))?
}
#[derive(Debug, PartialEq, FromForm)]
struct WhoopsForm {
name: String,
check: bool,
// in the error context this is returned as "name" but should be "other"
// the problem is dependent on an argument existing for evaluate_other
#[field(validate = evaluate_other(&self.check))]
other: String,
}
let errors = strict::<WhoopsForm>("name=test&check=true&other=").unwrap_err();
assert!(errors.iter().any(|e| e.name.as_ref().unwrap() == "other"));
}
#[test]
fn form_validate_contains_all_errors() {
fn evaluate<'v>(_value: &str) -> form::Result<'v, ()> {
Err(form::Error::validation(""))?
}
fn evaluate_with_argument<'v>(_value: &str, _check: bool) -> form::Result<'v, ()> {
Err(form::Error::validation("lastname failed"))?
}
#[derive(Debug, PartialEq, FromForm)]
struct WhoopsForm {
#[field(validate = evaluate())]
firstname: String,
check: bool,
// this validator is hardcoded to return an error but it doesn't
#[field(validate = evaluate_with_argument(self.check))]
lastname: String,
}
let errors = strict::<WhoopsForm>("firstname=&check=true&lastname=").unwrap_err();
assert!(errors.iter().any(|e| e.name.as_ref().unwrap() == "lastname"));
assert!(errors.iter().any(|e| e.name.as_ref().unwrap() == "firstname"));
}
#[test]
fn raw_ident_form() {
#[derive(Debug, PartialEq, FromForm)]
struct RawIdentForm {
r#type: String,
}
let form: Result<RawIdentForm, _> = strict("type=a");
assert_eq!(form, Ok(RawIdentForm { r#type: "a".into() }));
}
#[test]
fn test_multi() {
use std::collections::HashMap;
#[derive(Debug, PartialEq, FromForm)]
struct Multi<'r> {
checks: Vec<bool>,
names: Vec<&'r str>,
news: Vec<String>,
dogs: HashMap<String, Dog>,
#[field(name = "more:dogs")]
more_dogs: HashMap<&'r str, Dog>,
}
let multi: Multi = strict("checks=true&checks=false&checks=false\
&names=Sam&names[]=Smith&names[]=Bob\
&news[]=Here&news[]=also here\
&dogs[fido].barks=true&dogs[George].barks=false\
&dogs[fido].trained=on&dogs[George].trained=yes\
&dogs[bob boo].trained=no&dogs[bob boo].barks=off\
&more:dogs[k:0]=My Dog&more:dogs[v:0].barks=true&more:dogs[v:0].trained=yes\
").unwrap();
assert_eq!(multi, Multi {
checks: vec![true, false, false],
names: vec!["Sam".into(), "Smith".into(), "Bob".into()],
news: vec!["Here".into(), "also here".into()],
dogs: {
let mut map = HashMap::new();
map.insert("fido".into(), Dog { barks: true, trained: true });
map.insert("George".into(), Dog { barks: false, trained: true });
map.insert("bob boo".into(), Dog { barks: false, trained: false });
map
},
more_dogs: {
let mut map = HashMap::new();
map.insert("My Dog".into(), Dog { barks: true, trained: true });
map
}
});
#[derive(Debug, PartialEq, FromForm)]
struct MultiOwned {
names: Vec<String>,
}
let raw = "names=Sam&names%5B%5D=Smith&names%5B%5D=Bob%20Smith%3F";
let multi: MultiOwned = strict_encoded(raw).unwrap();
assert_eq!(multi, MultiOwned {
names: vec!["Sam".into(), "Smith".into(), "Bob Smith?".into()],
});
}
#[derive(Debug, FromForm, PartialEq)]
struct Dog {
barks: bool,
trained: bool,
}
#[derive(Debug, FromForm, PartialEq)]
struct Cat<'r> {
nip: &'r str,
meows: bool
}
#[derive(Debug, FromForm, PartialEq)]
struct Pet<'r, T> {
pet: T,
name: &'r str,
age: u8
}
#[derive(Debug, PartialEq, FromForm)]
struct Person<'r> {
dogs: Vec<Pet<'r, Dog>>,
cats: Vec<Pet<'r, Cat<'r>>>,
sitting: Dog,
}
#[test]
fn test_nested_multi() {
let person: Person = lenient("sitting.barks=true&sitting.trained=true").unwrap();
assert_eq!(person, Person {
sitting: Dog { barks: true, trained: true },
cats: vec![],
dogs: vec![],
});
let person = strict::<Person>("sitting.barks=true&sitting.trained=true");
assert!(person.is_err());
let person: Person = lenient("sitting.barks=true&sitting.trained=true\
&dogs[0].name=fido&dogs[0].pet.trained=yes&dogs[0].age=7&dogs[0].pet.barks=no\
").unwrap();
assert_eq!(person, Person {
sitting: Dog { barks: true, trained: true },
cats: vec![],
dogs: vec![Pet {
pet: Dog { barks: false, trained: true },
name: "fido".into(),
age: 7
}]
});
let person = strict::<Person>("sitting.barks=true&sitting.trained=true\
&dogs[0].name=fido&dogs[0].pet.trained=yes&dogs[0].age=7&dogs[0].pet.barks=no");
assert!(person.is_err());
let person: Person = lenient("sitting.trained=no&sitting.barks=true\
&dogs[0].name=fido&dogs[0].pet.trained=yes&dogs[0].age=7&dogs[0].pet.barks=no\
&dogs[1].pet.barks=true&dogs[1].name=Bob&dogs[1].pet.trained=no&dogs[1].age=1\
").unwrap();
assert_eq!(person, Person {
sitting: Dog { barks: true, trained: false },
cats: vec![],
dogs: vec![
Pet {
pet: Dog { barks: false, trained: true },
name: "fido".into(),
age: 7
},
Pet {
pet: Dog { barks: true, trained: false },
name: "Bob".into(),
age: 1
},
]
});
let person: Person = strict("sitting.barks=true&sitting.trained=no\
&dogs[0].name=fido&dogs[0].pet.trained=yes&dogs[0].age=7&dogs[0].pet.barks=no\
&dogs[1].pet.barks=true&dogs[1].name=Bob&dogs[1].pet.trained=no&dogs[1].age=1\
&cats[george].pet.nip=paws&cats[george].name=George&cats[george].age=2\
&cats[george].pet.meows=yes\
").unwrap();
assert_eq!(person, Person {
sitting: Dog { barks: true, trained: false },
cats: vec![
Pet {
pet: Cat { nip: "paws".into(), meows: true },
name: "George".into(),
age: 2
}
],
dogs: vec![
Pet {
pet: Dog { barks: false, trained: true },
name: "fido".into(),
age: 7
},
Pet {
pet: Dog { barks: true, trained: false },
name: "Bob".into(),
age: 1
},
]
});
}
#[test]
fn test_multipart() {
use rocket::http::ContentType;
use rocket::local::blocking::Client;
use rocket::fs::TempFile;
#[derive(FromForm)]
struct MyForm<'r> {
names: Vec<&'r str>,
file: TempFile<'r>,
}
#[rocket::post("/", data = "<form>")]
fn form(form: Form<MyForm>) {
assert_eq!(form.names, &["abcd", "123"]);
assert_eq!(form.file.name(), Some("foo"));
}
let client = Client::debug_with(rocket::routes![form]).unwrap();
let ct = "multipart/form-data; boundary=X-BOUNDARY"
.parse::<ContentType>()
.unwrap();
let body = &[
"--X-BOUNDARY",
r#"Content-Disposition: form-data; name="names[]""#,
"",
"abcd",
"--X-BOUNDARY",
r#"Content-Disposition: form-data; name="names[]""#,
"",
"123",
"--X-BOUNDARY",
r#"Content-Disposition: form-data; name="file"; filename="foo.txt""#,
"Content-Type: text/plain",
"",
"hi there",
"--X-BOUNDARY--",
"",
].join("\r\n");
let response = client.post("/")
.header(ct)
.body(body)
.dispatch();
assert!(response.status().class().is_success());
}
#[test]
fn test_default_removed() {
#[derive(FromForm, PartialEq, Debug)]
struct FormNoDefault {
#[field(default = None)]
field1: bool,
#[field(default = None)]
field2: Option<usize>,
#[field(default = None)]
field3: Option<Option<usize>>,
#[field(default_with = None)]
field4: bool,
}
let form_string = &["field1=false", "field2=10", "field3=23", "field4"].join("&");
let form1: Option<FormNoDefault> = lenient(&form_string).ok();
assert_eq!(form1, Some(FormNoDefault {
field1: false,
field2: Some(10),
field3: Some(Some(23)),
field4: true,
}));
let form_string = &["field1=true", "field2=10", "field3=23", "field4"].join("&");
let form1: Option<FormNoDefault> = lenient(&form_string).ok();
assert_eq!(form1, Some(FormNoDefault {
field1: true,
field2: Some(10),
field3: Some(Some(23)),
field4: true,
}));
// Field 1 missing.
let form_string = &["field2=20", "field3=10", "field4"].join("&");
assert!(lenient::<FormNoDefault>(&form_string).is_err());
// Field 2 missing.
let form_string = &["field1=true", "field3=10", "field4"].join("&");
assert!(lenient::<FormNoDefault>(&form_string).is_err());
// Field 3 missing.
let form_string = &["field1=true", "field2=10", "field4=false"].join("&");
assert!(lenient::<FormNoDefault>(&form_string).is_err());
// Field 4 missing.
let form_string = &["field1=true", "field2=10", "field3=23"].join("&");
assert!(lenient::<FormNoDefault>(&form_string).is_err());
}
#[test]
fn test_defaults() {
fn test_hashmap() -> HashMap<&'static str, &'static str> {
let mut map = HashMap::new();
map.insert("key", "value");
map.insert("one-more", "good-value");
map
}
fn test_btreemap() -> BTreeMap<Vec<usize>, &'static str> {
let mut map = BTreeMap::new();
map.insert(vec![1], "empty");
map.insert(vec![1, 2], "one-and-two");
map.insert(vec![3, 7, 9], "prime");
map
}
#[derive(FromForm, UriDisplayQuery, PartialEq, Debug)]
struct FormWithDefaults<'a> {
field2: i128,
field5: bool,
#[field(default = 100)]
field1: usize,
#[field(default = true)]
field3: bool,
#[field(default = false)]
field4: bool,
#[field(default = 254 + 1)]
field6: u8,
#[field(default = Some(true))]
opt1: Option<bool>,
#[field(default = false)]
opt2: Option<bool>,
#[field(default = Ok("hello".into()))]
res: form::Result<'a, String>,
#[field(default = Ok("hello"))]
res2: form::Result<'a, &'a str>,
#[field(default = vec![1, 2, 3])]
vec_num: Vec<usize>,
#[field(default = vec!["wow", "a", "string", "nice"])]
vec_str: Vec<&'a str>,
#[field(default = test_hashmap())]
hashmap: HashMap<&'a str, &'a str>,
#[field(default = test_btreemap())]
btreemap: BTreeMap<Vec<usize>, &'a str>,
#[field(default_with = Some(false))]
boolean: bool,
#[field(default_with = (|| Some(777))())]
unsigned: usize,
#[field(default = std::num::NonZeroI32::new(3).unwrap())]
nonzero: std::num::NonZeroI32,
#[field(default_with = std::num::NonZeroI32::new(9001))]
nonzero2: std::num::NonZeroI32,
#[field(default = 3.0)]
float: f64,
#[field(default = "wow")]
str_ref: &'a str,
#[field(default = "wowie")]
string: String,
#[field(default = [192u8, 168, 1, 0])]
ip: IpAddr,
#[field(default = ([192u8, 168, 1, 0], 20))]
addr: SocketAddr,
#[field(default = time::macros::date!(2021-05-27))]
date: time::Date,
#[field(default = time::macros::time!(01:15:00))]
time: time::Time,
#[field(default = time::PrimitiveDateTime::new(
time::macros::date!(2021-05-27),
time::macros::time!(01:15:00),
))]
datetime: time::PrimitiveDateTime,
}
// `field2` has no default.
assert!(lenient::<FormWithDefaults>("").is_err());
// every other field should.
let form_string = &["field2=102"].join("&");
let form1: Option<FormWithDefaults> = lenient(&form_string).ok();
assert_eq!(form1, Some(FormWithDefaults {
field1: 100,
field2: 102,
field3: true,
field4: false,
field5: false,
field6: 255,
opt1: Some(true),
opt2: Some(false),
res: Ok("hello".into()),
res2: Ok("hello"),
vec_num: vec![1, 2, 3],
vec_str: vec!["wow", "a", "string", "nice"],
hashmap: test_hashmap(),
btreemap: test_btreemap(),
boolean: false,
unsigned: 777,
nonzero: std::num::NonZeroI32::new(3).unwrap(),
nonzero2: std::num::NonZeroI32::new(9001).unwrap(),
float: 3.0,
str_ref: "wow",
string: "wowie".to_string(),
ip: [192u8, 168, 1, 0].into(),
addr: ([192u8, 168, 1, 0], 20).into(),
date: time::macros::date!(2021-05-27),
time: time::macros::time!(01:15:00),
datetime: time::PrimitiveDateTime::new(
time::macros::date!(2021-05-27),
time::macros::time!(01:15:00)
),
}));
let form2: Option<FormWithDefaults> = strict(&form_string).ok();
assert!(form2.is_none());
// Ensure actual form field values take precedence.
let form_string = &["field1=101", "field2=102", "field3=true", "field5=true"].join("&");
let form3: Option<FormWithDefaults> = lenient(&form_string).ok();
assert_eq!(form3, Some(FormWithDefaults {
field1: 101,
field2: 102,
field3: true,
field4: false,
field5: true,
..form1.unwrap()
}));
// And that strict parsing still works.
let form = form3.unwrap();
let form_string = format!("{}", &form as &dyn UriDisplay<Query>);
let form4: form::Result<'_, FormWithDefaults> = strict(&form_string);
assert_eq!(form4, Ok(form), "parse from {}", form_string);
#[derive(FromForm, UriDisplayQuery, PartialEq, Debug)]
struct OwnedFormWithDefaults {
#[field(default = {
let mut map = BTreeMap::new();
map.insert(vec![], "empty!/!? neat".into());
map.insert(vec![1, 2], "one/ and+two".into());
map.insert(vec![3, 7, 9], "prime numbers".into());
map
})]
btreemap: BTreeMap<Vec<usize>, String>,
}
let form5: Option<OwnedFormWithDefaults> = lenient("").ok();
assert_eq!(form5, Some(OwnedFormWithDefaults {
btreemap: {
let mut map = BTreeMap::new();
map.insert(vec![3, 7, 9], "prime numbers".into());
map.insert(vec![1, 2], "one/ and+two".into());
map.insert(vec![], "empty!/!? neat".into());
map
}
}));
// And that strict parsing still works even when encoded. We add one value
// to the empty vec because it would not parse as `strict` otherwise.
let mut form = form5.unwrap();
form.btreemap.remove(&vec![]);
let form_string = format!("{}", &form as &dyn UriDisplay<Query>);
let form6: form::Result<'_, OwnedFormWithDefaults> = strict_encoded(&form_string);
assert_eq!(form6, Ok(form));
}
#[test]
fn test_lazy_default() {
use std::sync::atomic::{AtomicUsize, Ordering};
static VAL: AtomicUsize = AtomicUsize::new(40);
fn f() -> usize {
VAL.fetch_add(1, Ordering::Relaxed)
}
fn opt_f() -> Option<usize> {
Some(f())
}
#[derive(Debug, PartialEq, FromForm)]
struct MyForm {
#[field(default = f())]
missing0: usize,
#[field(default = VAL.load(Ordering::Relaxed))]
missing1: usize,
#[field(default_with = opt_f())]
missing2: usize,
#[field(default_with = opt_f())]
a: usize,
#[field(default = f())]
b: usize,
#[field(default = VAL.load(Ordering::Relaxed))]
missing3: usize,
}
// Ensure actual form field values take precedence.
let form_string = &["a=100", "b=300"].join("&");
let form3: Option<MyForm> = lenient(&form_string).ok();
assert_eq!(form3, Some(MyForm {
missing0: 40,
missing1: 41,
missing2: 41,
a: 100,
b: 300,
missing3: 42
}));
}
#[derive(Debug, PartialEq, FromForm, UriDisplayQuery)]
#[field(validate = len(3..), default = "some default hello")]
struct Token<'r>(&'r str);
#[derive(Debug, PartialEq, FromForm, UriDisplayQuery)]
#[field(validate = try_with(|s| s.parse::<usize>()), default = "123456")]
struct TokenOwned(String);
#[test]
fn wrapper_works() {
let form: Option<Token> = lenient("").ok();
assert_eq!(form, Some(Token("some default hello")));
let form: Option<TokenOwned> = lenient("").ok();
assert_eq!(form, Some(TokenOwned("123456".into())));
let errors = strict::<Token>("").unwrap_err();
assert!(errors.iter().any(|e| matches!(e.kind, ErrorKind::Missing)));
let form: Option<Token> = lenient("=hi there").ok();
assert_eq!(form, Some(Token("hi there")));
let form: Option<TokenOwned> = strict_encoded("=2318").ok();
assert_eq!(form, Some(TokenOwned("2318".into())));
let errors = lenient::<Token>("=hi").unwrap_err();
assert!(errors.iter().any(|e| matches!(e.kind, ErrorKind::InvalidLength { .. })));
let errors = lenient::<TokenOwned>("=hellothere").unwrap_err();
assert!(errors.iter().any(|e| matches!(e.kind, ErrorKind::Validation { .. })));
}
#[derive(Debug, PartialEq, FromForm, UriDisplayQuery)]
struct JsonToken<T>(Json<T>);
#[test]
fn json_wrapper_works() {
let form: JsonToken<String> = lenient("=\"hello\"").unwrap();
assert_eq!(form, JsonToken(Json("hello".into())));
let form: JsonToken<usize> = lenient("=10").unwrap();
assert_eq!(form, JsonToken(Json(10)));
let form: JsonToken<()> = lenient("=null").unwrap();
assert_eq!(form, JsonToken(Json(())));
let form: JsonToken<Vec<usize>> = lenient("=[1, 4, 3, 9]").unwrap();
assert_eq!(form, JsonToken(Json(vec![1, 4, 3, 9])));
let string = String::from("=\"foo bar\"");
let form: JsonToken<&str> = lenient(&string).unwrap();
assert_eq!(form, JsonToken(Json("foo bar")));
}
#[allow(renamed_and_removed_lints)]
#[allow(private_in_public)]
struct Q<T>(T);
// This is here to ensure we don't warn, which we can't test with trybuild.
#[derive(FromForm)]
pub struct JsonTokenBad<T>(Q<T>);
#[test]
fn range() {
use std::ops::{Range, RangeFrom, RangeTo, RangeToInclusive};
let range: Range<usize> = strict("start=1&end=2").unwrap();
assert_eq!(range, Range { start: 1, end: 2 });
let range: Range<isize> = strict_encoded("start=-1&end=2").unwrap();
assert_eq!(range, Range { start: -1, end: 2 });
let range: Range<isize> = strict("start=-1&end=-95").unwrap();
assert_eq!(range, Range { start: -1, end: -95 });
let range: Range<isize> = strict_encoded("start=-1&end=-95").unwrap();
assert_eq!(range, Range { start: -1, end: -95 });
let range: RangeFrom<char> = strict("start=a").unwrap();
assert_eq!(range, RangeFrom { start: 'a' });
let range: RangeTo<char> = strict("end=z").unwrap();
assert_eq!(range, RangeTo { end: 'z' });
let range: RangeToInclusive<u8> = strict("end=255").unwrap();
assert_eq!(range, RangeToInclusive { end: 255 });
// now with compound, non-Step values
let form_string = &["start.start=0", "start.end=1", "end.start=1", "end.end=2"].join("&");
let range: Range<Range<usize>> = strict(&form_string).unwrap();
assert_eq!(range, Range {
start: Range { start: 0, end : 1},
end: Range { start: 1, end : 2 }
});
let form_string = &[
"start.description=some%20task",
"start.completed=false",
"end.description=yet%20more%20work",
"end.completed=true",
].join("&");
let range: Range<TodoTask> = strict_encoded(form_string).unwrap();
assert_eq!(range, Range {
start: TodoTask { description: "some task".into(), completed: false, },
end: TodoTask { description: "yet more work".into(), completed: true, },
});
}