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> { Form::>::parse(string).map(|s| s.into_inner()) } fn lenient<'f, T: FromForm<'f>>(string: &'f str) -> Result> { Form::::parse(string) } fn strict_encoded(string: &str) -> Result> where for<'a> T: FromForm<'a> { Form::>::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 = 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 = strict("other=a&description=Hello&completed=on").ok(); assert!(task.is_none()); let task: Option = 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 = 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, _> = 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> = strict("").ok(); assert_eq!(default, Some(DefaultInput { arg: None })); // Ensure _method can be captured if desired. let manual: Option> = strict("_method=put&done=true").ok(); assert_eq!(manual, Some(ManualMethod { _method: Some("put".into()), done: true })); let manual: Option> = 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> = 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 = 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> = 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> = 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> = lenient("v=abc").ok(); assert_eq!(manual, Some(FieldNamedV { v: "abc".into() })); let manual: Option> = lenient("v=abc&a=123").ok(); assert_eq!(manual, Some(FieldNamedV { v: "abc".into() })); let manual: Option> = 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> = lenient("something=hello").ok(); assert_eq!(manual, Some(UnpresentCheckboxTwo { checkbox: false, something: "hello".into() })); let manual: Option> = 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> = lenient("a=abc").ok(); assert!(manual.is_none()); let manual: Option> = 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 = 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 = 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> = 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> = 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> = strict(&form_string).ok(); assert!(form.is_none()); let form_string = &["single=123", "some_case=hi_im_here", "SomeCase=HiImHere"].join("&"); let form: Option> = 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> = 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> = 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> = 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> = 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> = strict(&form_string).ok(); assert!(form.is_none()); } #[test] fn generics() { #[derive(FromForm, Debug, PartialEq)] struct Oops { 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> = strict(&form_string).ok(); assert_eq!(form, Some(YetOneMore { string: "hello".into(), other: 128, })); let form: Option> = strict(&form_string).ok(); assert_eq!(form, Some(YetOneMore { string: "hello".into(), other: 128, })); let form: Option> = 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> = 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 = strict("complete=true&other=781"); assert_eq!(form, Ok(WhoopsForm { complete: true, other: 781 })); let errors = strict::("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::("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::("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::("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::("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::("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::("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::("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 = 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, names: Vec<&'r str>, news: Vec, dogs: HashMap, #[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, } 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>, cats: Vec>>, 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::>("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::>("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 = "
")] fn form(form: Form>) { 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::() .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, #[field(default = None)] field3: Option>, #[field(default_with = None)] field4: bool, } let form_string = &["field1=false", "field2=10", "field3=23", "field4"].join("&"); let form1: Option = 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 = 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::(&form_string).is_err()); // Field 2 missing. let form_string = &["field1=true", "field3=10", "field4"].join("&"); assert!(lenient::(&form_string).is_err()); // Field 3 missing. let form_string = &["field1=true", "field2=10", "field4=false"].join("&"); assert!(lenient::(&form_string).is_err()); // Field 4 missing. let form_string = &["field1=true", "field2=10", "field3=23"].join("&"); assert!(lenient::(&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, &'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, #[field(default = false)] opt2: Option, #[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, #[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, &'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::>("").is_err()); // every other field should. let form_string = &["field2=102"].join("&"); let form1: Option> = 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> = 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> = 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); 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, String>, } let form5: Option = 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); 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 { 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 = 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::()), default = "123456")] struct TokenOwned(String); #[test] fn wrapper_works() { let form: Option> = lenient("").ok(); assert_eq!(form, Some(Token("some default hello"))); let form: Option = lenient("").ok(); assert_eq!(form, Some(TokenOwned("123456".into()))); let errors = strict::>("").unwrap_err(); assert!(errors.iter().any(|e| matches!(e.kind, ErrorKind::Missing))); let form: Option> = lenient("=hi there").ok(); assert_eq!(form, Some(Token("hi there"))); let form: Option = strict_encoded("=2318").ok(); assert_eq!(form, Some(TokenOwned("2318".into()))); let errors = lenient::>("=hi").unwrap_err(); assert!(errors.iter().any(|e| matches!(e.kind, ErrorKind::InvalidLength { .. }))); let errors = lenient::("=hellothere").unwrap_err(); assert!(errors.iter().any(|e| matches!(e.kind, ErrorKind::Validation { .. }))); } #[derive(Debug, PartialEq, FromForm, UriDisplayQuery)] struct JsonToken(Json); #[test] fn json_wrapper_works() { let form: JsonToken = lenient("=\"hello\"").unwrap(); assert_eq!(form, JsonToken(Json("hello".into()))); let form: JsonToken = lenient("=10").unwrap(); assert_eq!(form, JsonToken(Json(10))); let form: JsonToken<()> = lenient("=null").unwrap(); assert_eq!(form, JsonToken(Json(()))); let form: JsonToken> = 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); // This is here to ensure we don't warn, which we can't test with trybuild. #[derive(FromForm)] pub struct JsonTokenBad(Q); #[test] fn range() { use std::ops::{Range, RangeFrom, RangeTo, RangeToInclusive}; let range: Range = strict("start=1&end=2").unwrap(); assert_eq!(range, Range { start: 1, end: 2 }); let range: Range = strict_encoded("start=-1&end=2").unwrap(); assert_eq!(range, Range { start: -1, end: 2 }); let range: Range = strict("start=-1&end=-95").unwrap(); assert_eq!(range, Range { start: -1, end: -95 }); let range: Range = strict_encoded("start=-1&end=-95").unwrap(); assert_eq!(range, Range { start: -1, end: -95 }); let range: RangeFrom = strict("start=a").unwrap(); assert_eq!(range, RangeFrom { start: 'a' }); let range: RangeTo = strict("end=z").unwrap(); assert_eq!(range, RangeTo { end: 'z' }); let range: RangeToInclusive = 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> = 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 = 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, }, }); }