mirror of https://github.com/rwf2/Rocket.git
parent
ef7b7a953e
commit
e532f4e2b3
|
@ -247,11 +247,13 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
|
|||
let _err = _Err;
|
||||
Ok(quote_spanned! { ty.span() => {
|
||||
let _name = #name_view;
|
||||
let _opts = __c.__opts;
|
||||
__c.#ident
|
||||
.map(<#ty as #_form::FromForm<'__f>>::finalize)
|
||||
.unwrap_or_else(|| <#ty as #_form::FromForm<'__f>>::default()
|
||||
.ok_or_else(|| #_form::ErrorKind::Missing.into())
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
<#ty as #_form::FromForm<'__f>>::default(_opts)
|
||||
.ok_or_else(|| #_form::ErrorKind::Missing.into())
|
||||
})
|
||||
.and_then(|#ident| {
|
||||
let mut _es = #_form::Errors::new();
|
||||
#(if let #_err(_e) = #validator { _es.extend(_e); })*
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#[macro_use]extern crate rocket;
|
||||
|
||||
use rocket::form::{Form, Strict, FromForm, Errors};
|
||||
use rocket::form::{Form, Strict, FromForm, FromFormField, Errors};
|
||||
|
||||
fn strict<'f, T: FromForm<'f>>(string: &'f str) -> Result<T, Errors<'f>> {
|
||||
Form::<Strict<T>>::parse(string).map(|s| s.into_inner())
|
||||
|
@ -135,13 +133,13 @@ fn base_conditions() {
|
|||
}));
|
||||
|
||||
// Check that a `bool` value that isn't in the form is marked as `false`.
|
||||
let manual: Option<UnpresentCheckbox> = strict("").ok();
|
||||
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<'_>> = strict("something=hello").ok();
|
||||
let manual: Option<UnpresentCheckboxTwo<'_>> = lenient("something=hello").ok();
|
||||
assert_eq!(manual, Some(UnpresentCheckboxTwo {
|
||||
checkbox: false,
|
||||
something: "hello".into()
|
||||
|
@ -152,7 +150,6 @@ fn base_conditions() {
|
|||
assert_eq!(manual, Some(FieldNamedV {
|
||||
v: "abc".into()
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -343,63 +340,65 @@ fn form_errors() {
|
|||
|
||||
let errors = strict::<WhoopsForm>("complete=true&other=unknown").unwrap_err();
|
||||
assert!(errors.iter().any(|e| {
|
||||
"other" == e.name.as_ref().unwrap()
|
||||
&& Some("unknown") == e.value.as_deref()
|
||||
&& match e.kind {
|
||||
ErrorKind::Int(..) => true,
|
||||
_ => false
|
||||
}
|
||||
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| {
|
||||
e.name.as_ref().unwrap() == "complete"
|
||||
&& Some("unknown") == e.value.as_deref()
|
||||
&& match e.kind {
|
||||
ErrorKind::Bool(..) => true,
|
||||
_ => false
|
||||
}
|
||||
"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();
|
||||
dbg!(&errors);
|
||||
assert!(errors.iter().any(|e| {
|
||||
"extra" == e.name.as_ref().unwrap()
|
||||
&& Some("foo") == e.value.as_deref()
|
||||
&& match e.kind {
|
||||
ErrorKind::Unexpected => true,
|
||||
_ => false
|
||||
}
|
||||
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| {
|
||||
"complete" == e.name.as_ref().unwrap()
|
||||
&& Some("unknown") == e.value.as_deref()
|
||||
&& match e.kind {
|
||||
ErrorKind::Bool(..) => true,
|
||||
_ => false
|
||||
}
|
||||
e.name.as_ref().unwrap() == "complete"
|
||||
&& e.value.as_deref() == Some("unknown")
|
||||
&& matches!(e.kind, ErrorKind::Bool(..))
|
||||
}));
|
||||
|
||||
assert!(errors.iter().any(|e| {
|
||||
"unknown" == e.name.as_ref().unwrap()
|
||||
&& Some("!") == e.value.as_deref()
|
||||
&& match e.kind {
|
||||
ErrorKind::Unexpected => true,
|
||||
_ => false
|
||||
}
|
||||
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| {
|
||||
"other" == e.name.as_ref().unwrap()
|
||||
e.name.as_ref().unwrap() == "other"
|
||||
&& e.value.is_none()
|
||||
&& e.entity == Entity::Field
|
||||
&& match e.kind {
|
||||
ErrorKind::Missing => true,
|
||||
_ => false
|
||||
}
|
||||
&& matches!(e.kind, ErrorKind::Missing)
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
@ -156,9 +156,4 @@ impl<'v, T: FromForm<'v>> FromForm<'v> for Contextual<'v, T> {
|
|||
|
||||
Ok(Contextual { value, context })
|
||||
}
|
||||
|
||||
|
||||
fn default() -> Option<Self> {
|
||||
Self::finalize(Self::init(Options::Lenient)).ok()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -387,10 +387,10 @@ pub trait FromForm<'r>: Send + Sized {
|
|||
/// Returns a default value, if any, to use when a value is desired and
|
||||
/// parsing fails.
|
||||
///
|
||||
/// The default implementation initializes `Self` with lenient options and
|
||||
/// finalizes immediately, returning the value if finalization succeeds.
|
||||
fn default() -> Option<Self> {
|
||||
Self::finalize(Self::init(Options::Lenient)).ok()
|
||||
/// The default implementation initializes `Self` with `opts` and finalizes
|
||||
/// immediately, returning the value if finalization succeeds.
|
||||
fn default(opts: Options) -> Option<Self> {
|
||||
Self::finalize(Self::init(opts)).ok()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -257,9 +257,9 @@ impl<'v, T: FromFormField<'v>> FromFieldContext<'v, T> {
|
|||
}
|
||||
|
||||
fn push(&mut self, name: NameView<'v>, result: Result<'v, T>) {
|
||||
let is_unexpected = |e: &Errors<'_>| e.last().map_or(false, |e| {
|
||||
if let ErrorKind::Unexpected = e.kind { true } else { false }
|
||||
});
|
||||
fn is_unexpected(e: &Errors<'_>) -> bool {
|
||||
matches!(e.last().map(|e| &e.kind), Some(ErrorKind::Unexpected))
|
||||
}
|
||||
|
||||
self.field_name = Some(name);
|
||||
match result {
|
||||
|
@ -299,12 +299,13 @@ impl<'v, T: FromFormField<'v>> FromForm<'v> for T {
|
|||
fn finalize(ctxt: Self::Context) -> Result<'v, Self> {
|
||||
let mut errors = match ctxt.value {
|
||||
Some(Ok(val)) if !ctxt.opts.strict || ctxt.pushes <= 1 => return Ok(val),
|
||||
Some(Err(e)) => e,
|
||||
Some(Ok(_)) => Errors::from(ErrorKind::Duplicate),
|
||||
None => match <T as FromFormField>::default() {
|
||||
Some(Err(errors)) => errors,
|
||||
None if !ctxt.opts.strict => match <T as FromFormField>::default() {
|
||||
Some(default) => return Ok(default),
|
||||
None => Errors::from(ErrorKind::Missing)
|
||||
}
|
||||
},
|
||||
None => Errors::from(ErrorKind::Missing),
|
||||
};
|
||||
|
||||
if let Some(name) = ctxt.field_name {
|
||||
|
@ -362,7 +363,9 @@ impl<'v> FromFormField<'v> for Capped<String> {
|
|||
impl_strict_from_form_field_from_capped!(String);
|
||||
|
||||
impl<'v> FromFormField<'v> for bool {
|
||||
fn default() -> Option<Self> { Some(false) }
|
||||
fn default() -> Option<Self> {
|
||||
Some(false)
|
||||
}
|
||||
|
||||
fn from_value(field: ValueField<'v>) -> Result<'v, Self> {
|
||||
match field.value.as_uncased() {
|
||||
|
|
|
@ -128,7 +128,8 @@
|
|||
//! This implementation is complete except for the following details:
|
||||
//!
|
||||
//! * not being pseudocode, of course
|
||||
//! * checking for duplicate pushes when paring is requested as `strict`
|
||||
//! * checking for duplicate pushes when parsing is `strict`
|
||||
//! * disallowing defaults when parsing is `strict`
|
||||
//! * tracking the field's name and value to generate a complete `Error`
|
||||
//!
|
||||
//! See [`FromForm`] for full details on push-parsing and a complete example.
|
||||
|
|
|
@ -9,7 +9,8 @@ use crate::http::uri::{Query, FromUriParam};
|
|||
/// generic parameter to the [`Form`] data guard: `Form<Strict<T>>`, where `T`
|
||||
/// implements `FromForm`. Unlike using `Form` directly, this type uses a
|
||||
/// _strict_ parsing strategy: forms that contains a superset of the expected
|
||||
/// fields (i.e, extra fields) will fail to parse.
|
||||
/// fields (i.e, extra fields) will fail to parse and defaults will not be use
|
||||
/// for missing fields.
|
||||
///
|
||||
/// # Strictness
|
||||
///
|
||||
|
@ -38,6 +39,20 @@ use crate::http::uri::{Query, FromUriParam};
|
|||
/// format!("Your value: {}", user_input.value)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// `Strict` can also be used to make individual fields strict while keeping the
|
||||
/// overall structure and remaining fields lenient:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::form::{Form, Strict};
|
||||
///
|
||||
/// #[derive(FromForm)]
|
||||
/// struct UserInput {
|
||||
/// required: Strict<bool>,
|
||||
/// uses_default: bool
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Strict<T>(T);
|
||||
|
||||
|
|
|
@ -716,11 +716,8 @@ parse or is simply invalid, a customizable error is returned. As before, a
|
|||
forward or failure can be caught by using the `Option` and `Result` types:
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate rocket;
|
||||
# fn main() {}
|
||||
|
||||
# use rocket::form::Form;
|
||||
# #[derive(FromForm)] struct Task { complete: bool }
|
||||
# use rocket::{post, form::Form};
|
||||
# type Task = String;
|
||||
|
||||
#[post("/todo", data = "<task>")]
|
||||
fn new(task: Option<Form<Task>>) { /* .. */ }
|
||||
|
@ -732,11 +729,12 @@ fn new(task: Option<Form<Task>>) { /* .. */ }
|
|||
### Strict Parsing
|
||||
|
||||
Rocket's `FromForm` parsing is _lenient_ by default: a `Form<T>` will parse
|
||||
successfully from an incoming form even if it contains extra or duplicate
|
||||
fields. The extras or duplicates are ignored -- no validation or parsing of the
|
||||
fields occurs. To change this behavior and make form parsing _strict_, use the
|
||||
[`Form<Strict<T>>`] data type, which errors if there are any extra, undeclared
|
||||
fields.
|
||||
successfully from an incoming form even if it contains extra, duplicate, or
|
||||
missing fields. Extras or duplicates are ignored -- no validation or parsing of
|
||||
the fields occurs -- and missing fields are filled with defaults when available.
|
||||
To change this behavior and make form parsing _strict_, use the
|
||||
[`Form<Strict<T>>`] data type, which emits errors if there are any extra or
|
||||
missing fields, irrespective of defaults.
|
||||
|
||||
You can use a `Form<Strict<T>>` anywhere you'd use a `Form<T>`. Its generic
|
||||
parameter is also required to implement `FromForm`. For instance, we can simply
|
||||
|
@ -744,21 +742,32 @@ replace `Form<T>` with `Form<Strict<T>>` above to get strict parsing:
|
|||
|
||||
```rust
|
||||
# #[macro_use] extern crate rocket;
|
||||
# fn main() {}
|
||||
|
||||
use rocket::form::{Form, Strict};
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct Task {
|
||||
/* .. */
|
||||
# complete: bool,
|
||||
# description: String,
|
||||
}
|
||||
# #[derive(FromForm)] struct Task { complete: bool, description: String, }
|
||||
|
||||
#[post("/todo", data = "<task>")]
|
||||
fn new(task: Form<Strict<Task>>) { /* .. */ }
|
||||
```
|
||||
|
||||
`Strict` can also be used to make individual fields strict while keeping the
|
||||
overall structure and remaining fields lenient:
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate rocket;
|
||||
# use rocket::form::{Form, Strict};
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct Input {
|
||||
required: Strict<bool>,
|
||||
uses_default: bool
|
||||
}
|
||||
|
||||
#[post("/", data = "<input>")]
|
||||
fn new(input: Form<Input>) { /* .. */ }
|
||||
```
|
||||
|
||||
[`Form<Strict<T>>`]: @api/rocket/form/struct.Strict.html
|
||||
|
||||
### Field Renaming
|
||||
|
|
Loading…
Reference in New Issue