mirror of
https://github.com/rwf2/Rocket.git
synced 2025-01-20 08:29:07 +00:00
Consider form parsing strategy for 'Vec', 'Map'.
Prior to this commit, 'Vec', 'HashMap', and 'BTreeMap' would parse leniently irrespetive of the requested parsing strategy. This commit changes their behavior so that the parsing strategy is respected. Resolves #2131.
This commit is contained in:
parent
7bbe0457a5
commit
6bdd2f8186
@ -500,14 +500,17 @@ struct Person<'r> {
|
||||
|
||||
#[test]
|
||||
fn test_nested_multi() {
|
||||
let person: Person = strict("sitting.barks=true&sitting.trained=true").unwrap();
|
||||
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: Person = strict("sitting.barks=true&sitting.trained=true\
|
||||
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 {
|
||||
@ -520,7 +523,11 @@ fn test_nested_multi() {
|
||||
}]
|
||||
});
|
||||
|
||||
let person: Person = strict("sitting.trained=no&sitting.barks=true\
|
||||
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();
|
||||
@ -680,7 +687,7 @@ fn test_defaults() {
|
||||
|
||||
fn test_btreemap() -> BTreeMap<Vec<usize>, &'static str> {
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert(vec![], "empty");
|
||||
map.insert(vec![1], "empty");
|
||||
map.insert(vec![1, 2], "one-and-two");
|
||||
map.insert(vec![3, 7, 9], "prime");
|
||||
map
|
||||
@ -801,7 +808,7 @@ fn test_defaults() {
|
||||
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));
|
||||
assert_eq!(form4, Ok(form), "parse from {}", form_string);
|
||||
|
||||
#[derive(FromForm, UriDisplayQuery, PartialEq, Debug)]
|
||||
struct OwnedFormWithDefaults {
|
||||
@ -815,7 +822,6 @@ fn test_defaults() {
|
||||
btreemap: BTreeMap<Vec<usize>, String>,
|
||||
}
|
||||
|
||||
// And that strict parsing still works even when encoded.
|
||||
let form5: Option<OwnedFormWithDefaults> = lenient("").ok();
|
||||
assert_eq!(form5, Some(OwnedFormWithDefaults {
|
||||
btreemap: {
|
||||
@ -827,7 +833,10 @@ fn test_defaults() {
|
||||
}
|
||||
}));
|
||||
|
||||
let form = form5.unwrap();
|
||||
// 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));
|
||||
|
@ -563,6 +563,16 @@ pub struct VecContext<'v, T: FromForm<'v>> {
|
||||
}
|
||||
|
||||
impl<'v, T: FromForm<'v>> VecContext<'v, T> {
|
||||
fn new(opts: Options) -> Self {
|
||||
VecContext {
|
||||
opts,
|
||||
last_key: None,
|
||||
current: None,
|
||||
items: vec![],
|
||||
errors: Errors::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn shift(&mut self) {
|
||||
if let Some(current) = self.current.take() {
|
||||
match T::finalize(current) {
|
||||
@ -575,7 +585,7 @@ impl<'v, T: FromForm<'v>> VecContext<'v, T> {
|
||||
fn context(&mut self, name: &NameView<'v>) -> &mut T::Context {
|
||||
let this_key = name.key();
|
||||
let keys_match = match (self.last_key, this_key) {
|
||||
(Some(k1), Some(k2)) if k1 == k2 => true,
|
||||
(Some(k1), Some(k2)) => k1 == k2,
|
||||
_ => false
|
||||
};
|
||||
|
||||
@ -594,28 +604,25 @@ impl<'v, T: FromForm<'v> + 'v> FromForm<'v> for Vec<T> {
|
||||
type Context = VecContext<'v, T>;
|
||||
|
||||
fn init(opts: Options) -> Self::Context {
|
||||
VecContext {
|
||||
opts,
|
||||
last_key: None,
|
||||
current: None,
|
||||
items: vec![],
|
||||
errors: Errors::new(),
|
||||
}
|
||||
VecContext::new(opts)
|
||||
}
|
||||
|
||||
fn push_value(this: &mut Self::Context, field: ValueField<'v>) {
|
||||
T::push_value(this.context(&field.name), field.shift());
|
||||
}
|
||||
|
||||
async fn push_data(ctxt: &mut Self::Context, field: DataField<'v, '_>) {
|
||||
T::push_data(ctxt.context(&field.name), field.shift()).await
|
||||
async fn push_data(this: &mut Self::Context, field: DataField<'v, '_>) {
|
||||
T::push_data(this.context(&field.name), field.shift()).await
|
||||
}
|
||||
|
||||
fn finalize(mut this: Self::Context) -> Result<'v, Self> {
|
||||
this.shift();
|
||||
match this.errors.is_empty() {
|
||||
true => Ok(this.items),
|
||||
false => Err(this.errors)?,
|
||||
if !this.errors.is_empty() {
|
||||
Err(this.errors)
|
||||
} else if this.opts.strict && this.items.is_empty() {
|
||||
Err(Errors::from(ErrorKind::Missing))
|
||||
} else {
|
||||
Ok(this.items)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -623,10 +630,14 @@ impl<'v, T: FromForm<'v> + 'v> FromForm<'v> for Vec<T> {
|
||||
#[doc(hidden)]
|
||||
pub struct MapContext<'v, K, V> where K: FromForm<'v>, V: FromForm<'v> {
|
||||
opts: Options,
|
||||
/// Maps from the string key to the index in `map`.
|
||||
key_map: IndexMap<&'v str, (usize, NameView<'v>)>,
|
||||
keys: Vec<K::Context>,
|
||||
values: Vec<V::Context>,
|
||||
/// Maps an index key (&str, map.key=foo, map.k:key) to its entry.
|
||||
/// NOTE: `table`, `entries`, and `metadata` are always the same size.
|
||||
table: IndexMap<&'v str, usize>,
|
||||
/// The `FromForm` context for the (key, value) indexed by `table`.
|
||||
entries: Vec<(K::Context, V::Context)>,
|
||||
/// Recorded metadata for a given key/value pair.
|
||||
metadata: Vec<NameView<'v>>,
|
||||
/// Errors collected while finalizing keys and values.
|
||||
errors: Errors<'v>,
|
||||
}
|
||||
|
||||
@ -636,31 +647,27 @@ impl<'v, K, V> MapContext<'v, K, V>
|
||||
fn new(opts: Options) -> Self {
|
||||
MapContext {
|
||||
opts,
|
||||
key_map: IndexMap::new(),
|
||||
keys: vec![],
|
||||
values: vec![],
|
||||
table: IndexMap::new(),
|
||||
entries: vec![],
|
||||
metadata: vec![],
|
||||
errors: Errors::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn ctxt(&mut self, key: &'v str, name: NameView<'v>) -> (&mut K::Context, &mut V::Context) {
|
||||
match self.key_map.get(key) {
|
||||
Some(&(i, _)) => (&mut self.keys[i], &mut self.values[i]),
|
||||
fn ctxt(&mut self, key: &'v str, name: NameView<'v>) -> &mut (K::Context, V::Context) {
|
||||
match self.table.get(key) {
|
||||
Some(i) => &mut self.entries[*i],
|
||||
None => {
|
||||
debug_assert_eq!(self.keys.len(), self.values.len());
|
||||
let map_index = self.keys.len();
|
||||
self.keys.push(K::init(self.opts));
|
||||
self.values.push(V::init(self.opts));
|
||||
self.key_map.insert(key, (map_index, name));
|
||||
(self.keys.last_mut().unwrap(), self.values.last_mut().unwrap())
|
||||
let i = self.entries.len();
|
||||
self.table.insert(key, i);
|
||||
self.entries.push((K::init(self.opts), V::init(self.opts)));
|
||||
self.metadata.push(name);
|
||||
&mut self.entries[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push(
|
||||
&mut self,
|
||||
name: NameView<'v>
|
||||
) -> Option<Either<&mut K::Context, &mut V::Context>> {
|
||||
fn push(&mut self, name: NameView<'v>) -> Option<Either<&mut K::Context, &mut V::Context>> {
|
||||
let index_pair = name.key()
|
||||
.map(|k| k.indices())
|
||||
.map(|mut i| (i.next(), i.next()))
|
||||
@ -668,7 +675,7 @@ impl<'v, K, V> MapContext<'v, K, V>
|
||||
|
||||
match index_pair {
|
||||
(Some(key), None) => {
|
||||
let is_new_key = !self.key_map.contains_key(key);
|
||||
let is_new_key = !self.table.contains_key(key);
|
||||
let (key_ctxt, val_ctxt) = self.ctxt(key, name);
|
||||
if is_new_key {
|
||||
K::push_value(key_ctxt, ValueField::from_value(key));
|
||||
@ -678,9 +685,9 @@ impl<'v, K, V> MapContext<'v, K, V>
|
||||
},
|
||||
(Some(kind), Some(key)) => {
|
||||
if kind.as_uncased().starts_with("k") {
|
||||
return Some(Either::Left(self.ctxt(key, name).0));
|
||||
return Some(Either::Left(&mut self.ctxt(key, name).0));
|
||||
} else if kind.as_uncased().starts_with("v") {
|
||||
return Some(Either::Right(self.ctxt(key, name).1));
|
||||
return Some(Either::Right(&mut self.ctxt(key, name).1));
|
||||
} else {
|
||||
let error = Error::from(&[Cow::Borrowed("k"), Cow::Borrowed("v")])
|
||||
.with_entity(Entity::Index(0))
|
||||
@ -717,29 +724,34 @@ impl<'v, K, V> MapContext<'v, K, V>
|
||||
}
|
||||
}
|
||||
|
||||
fn finalize<T: std::iter::FromIterator<(K, V)>>(self) -> Result<'v, T> {
|
||||
let (keys, values, key_map) = (self.keys, self.values, self.key_map);
|
||||
let errors = std::cell::RefCell::new(self.errors);
|
||||
fn finalize<T: std::iter::FromIterator<(K, V)>>(mut self) -> Result<'v, T> {
|
||||
let map: T = self.entries.into_iter()
|
||||
.zip(self.metadata.iter())
|
||||
.zip(self.table.keys())
|
||||
.filter_map(|(((k_ctxt, v_ctxt), name), idx)| {
|
||||
let key = K::finalize(k_ctxt)
|
||||
.map_err(|e| {
|
||||
// FIXME: Fix `NameBuf` to take in `k` and add it.
|
||||
// FIXME: Perhaps the `k` should come after: `map.0:k`.
|
||||
let form_key = format!("k:{}", idx);
|
||||
self.errors.extend(e.with_name((name.parent(), form_key)));
|
||||
})
|
||||
.ok();
|
||||
|
||||
let keys = keys.into_iter()
|
||||
.zip(key_map.values().map(|(_, name)| name))
|
||||
.filter_map(|(ctxt, name)| match K::finalize(ctxt) {
|
||||
Ok(value) => Some(value),
|
||||
Err(e) => { errors.borrow_mut().extend(e.with_name(*name)); None },
|
||||
});
|
||||
let val = V::finalize(v_ctxt)
|
||||
.map_err(|e| self.errors.extend(e.with_name((name.parent(), *idx))))
|
||||
.ok();
|
||||
|
||||
let values = values.into_iter()
|
||||
.zip(key_map.values().map(|(_, name)| name))
|
||||
.filter_map(|(ctxt, name)| match V::finalize(ctxt) {
|
||||
Ok(value) => Some(value),
|
||||
Err(e) => { errors.borrow_mut().extend(e.with_name(*name)); None },
|
||||
});
|
||||
Some((key?, val?))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let map: T = keys.zip(values).collect();
|
||||
let no_errors = errors.borrow().is_empty();
|
||||
match no_errors {
|
||||
true => Ok(map),
|
||||
false => Err(errors.into_inner())
|
||||
if !self.errors.is_empty() {
|
||||
Err(self.errors)
|
||||
} else if self.opts.strict && self.table.is_empty() {
|
||||
Err(Errors::from(ErrorKind::Missing))
|
||||
} else {
|
||||
Ok(map)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -124,6 +124,16 @@ impl<'v> From<(Option<&'v Name>, Cow<'v, str>)> for NameBuf<'v> {
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl<'v> From<(Option<&'v Name>, String)> for NameBuf<'v> {
|
||||
fn from((prefix, right): (Option<&'v Name>, String)) -> Self {
|
||||
match prefix {
|
||||
Some(left) => NameBuf { left, right: right.into() },
|
||||
None => NameBuf { left: "".into(), right: right.into() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl<'v> From<(Option<&'v Name>, &'v str)> for NameBuf<'v> {
|
||||
fn from((prefix, suffix): (Option<&'v Name>, &'v str)) -> Self {
|
||||
|
@ -184,6 +184,13 @@ impl<'v> NameView<'v> {
|
||||
|
||||
/// Returns the key currently viewed by `self` if it is non-empty.
|
||||
///
|
||||
/// ```text
|
||||
/// food.bart[bar:foo].blam[0_0][][1000]=some-value
|
||||
/// name |----------------------------------|
|
||||
/// non-empty key |--| |--| |-----| |--| |-| |--|
|
||||
/// empty key |-|
|
||||
/// ```
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
@ -211,6 +218,13 @@ impl<'v> NameView<'v> {
|
||||
|
||||
/// Returns the key currently viewed by `self`, even if it is non-empty.
|
||||
///
|
||||
/// ```text
|
||||
/// food.bart[bar:foo].blam[0_0][][1000]=some-value
|
||||
/// name |----------------------------------|
|
||||
/// non-empty key |--| |--| |-----| |--| |-| |--|
|
||||
/// empty key |-|
|
||||
/// ```
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
|
Loading…
Reference in New Issue
Block a user