use rocket::form::{Form, Strict, FromForm, FromFormField, Errors}; 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: &'static 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() { use rocket::form::error::{ErrorKind, Entity}; #[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 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 = strict("sitting.barks=true&sitting.trained=true").unwrap(); assert_eq!(person, Person { sitting: Dog { barks: true, trained: true }, cats: vec![], dogs: vec![], }); let person: 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\ ").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: Person = strict("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()); }