Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ let encoded = encode(
assert_eq!(encoded, "user%5Bname%5D=alice&tags%5B%5D=x&tags%5B%5D=y");
```

Query-string decoding only produces `Null`, `String`, `Array`, and `Object`. Structured inputs passed to `encode` or `decode_pairs` may also contain `Bool`, numeric variants, and `Bytes`.
Query-string decoding normally produces `Null`, `String`, `Array`, and
`Object`. The legacy `with_strict_merge(false)` mode may also produce
`Bool(true)` marker values to match Node `qs` object/scalar merge behavior.
Structured inputs passed to `encode` or `decode_pairs` may also contain `Bool`,
numeric variants, and `Bytes`.

## Decoding

Expand Down Expand Up @@ -194,6 +198,36 @@ let first = decode(
.unwrap();
assert_eq!(first.get("foo"), Some(&Value::String("bar".to_owned())));

let bracketed = decode(
"a=1&a=2&b[]=1&b[]=2",
&DecodeOptions::new().with_duplicates(Duplicates::Last),
)
.unwrap();
assert_eq!(bracketed.get("a"), Some(&Value::String("2".to_owned())));
assert_eq!(
bracketed.get("b"),
Some(&Value::Array(vec![
Value::String("1".to_owned()),
Value::String("2".to_owned()),
])),
);

let legacy_merge = decode(
"a[b]=c&a=d",
&DecodeOptions::new().with_strict_merge(false),
)
.unwrap();
assert_eq!(
legacy_merge.get("a"),
Some(&Value::Object(
[
("b".to_owned(), Value::String("c".to_owned())),
("d".to_owned(), Value::Bool(true)),
]
.into(),
)),
);
Comment thread
techouse marked this conversation as resolved.
Comment thread
techouse marked this conversation as resolved.

let comma = decode("a=b,c", &DecodeOptions::new().with_comma(true)).unwrap();
assert_eq!(
comma.get("a"),
Expand Down
34 changes: 25 additions & 9 deletions src/decode/accumulate/insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,34 @@ pub(in crate::decode) fn insert_value(
entry: Entry<'_, String, ParsedFlatValue>,
value: ParsedFlatValue,
options: &DecodeOptions,
) -> Result<(), DecodeError> {
insert_value_with_duplicates(entry, value, options, options.duplicates)
}

pub(in crate::decode) fn insert_value_with_duplicates(
entry: Entry<'_, String, ParsedFlatValue>,
value: ParsedFlatValue,
options: &DecodeOptions,
duplicates: Duplicates,
) -> Result<(), DecodeError> {
match entry {
Entry::Occupied(mut entry) => insert_occupied_value(&mut entry, value, options),
Entry::Occupied(mut entry) => {
insert_occupied_value_with_duplicates(&mut entry, value, options, duplicates)
}
Entry::Vacant(entry) => {
entry.insert(value);
Ok(())
}
}
}

pub(super) fn insert_occupied_value(
pub(super) fn insert_occupied_value_with_duplicates(
entry: &mut OccupiedEntry<'_, String, ParsedFlatValue>,
value: ParsedFlatValue,
options: &DecodeOptions,
duplicates: Duplicates,
) -> Result<(), DecodeError> {
match options.duplicates {
match duplicates {
Duplicates::Combine => {
let current = std::mem::replace(
entry.get_mut(),
Expand All @@ -46,16 +58,17 @@ pub(super) fn insert_occupied_value(
Ok(())
}

pub(super) fn insert_default_value(
pub(super) fn insert_default_value_with_duplicates(
values: &mut FlatValues,
key: String,
value: ParsedFlatValue,
options: &DecodeOptions,
duplicates: Duplicates,
) -> Result<(), DecodeError> {
match values {
FlatValues::Concrete(entries) => {
if let ParsedFlatValue::Concrete(value) = value {
match options.duplicates {
match duplicates {
Duplicates::First => {
entries.entry(key).or_insert(value);
return Ok(());
Expand All @@ -70,23 +83,26 @@ pub(super) fn insert_default_value(
return Ok(());
}
let values = values.ensure_parsed();
return insert_value(
return insert_value_with_duplicates(
values.entry(key),
ParsedFlatValue::concrete(value),
options,
duplicates,
);
}
}
}

if matches!(options.duplicates, Duplicates::First) && entries.contains_key(&key) {
if matches!(duplicates, Duplicates::First) && entries.contains_key(&key) {
return Ok(());
}

let values = values.ensure_parsed();
insert_value(values.entry(key), value, options)
insert_value_with_duplicates(values.entry(key), value, options, duplicates)
}
FlatValues::Parsed(entries) => {
insert_value_with_duplicates(entries.entry(key), value, options, duplicates)
}
FlatValues::Parsed(entries) => insert_value(entries.entry(key), value, options),
}
}

Expand Down
35 changes: 23 additions & 12 deletions src/decode/accumulate/insert/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{insert_default_value, insert_occupied_value};
use super::{insert_default_value_with_duplicates, insert_occupied_value_with_duplicates};
use crate::decode::flat::{FlatValues, ParsedFlatValue};
use crate::internal::node::Node;
use crate::options::{DecodeOptions, Duplicates};
Expand Down Expand Up @@ -39,10 +39,11 @@ fn occupied_insert_respects_duplicate_strategies() {
IndexMap::from([("a".to_owned(), ParsedFlatValue::concrete(scalar("1")))]);
match combine_entries.entry("a".to_owned()) {
Entry::Occupied(mut entry) => {
insert_occupied_value(
insert_occupied_value_with_duplicates(
&mut entry,
ParsedFlatValue::concrete(scalar("2")),
&combine_options,
Duplicates::Combine,
)
.unwrap();
}
Expand All @@ -58,10 +59,11 @@ fn occupied_insert_respects_duplicate_strategies() {
IndexMap::from([("a".to_owned(), ParsedFlatValue::concrete(scalar("1")))]);
match last_entries.entry("a".to_owned()) {
Entry::Occupied(mut entry) => {
insert_occupied_value(
insert_occupied_value_with_duplicates(
&mut entry,
ParsedFlatValue::concrete(scalar("2")),
&last_options,
Duplicates::Last,
)
.unwrap();
}
Expand All @@ -77,10 +79,11 @@ fn occupied_insert_respects_duplicate_strategies() {
IndexMap::from([("a".to_owned(), ParsedFlatValue::concrete(scalar("1")))]);
match first_entries.entry("a".to_owned()) {
Entry::Occupied(mut entry) => {
insert_occupied_value(
insert_occupied_value_with_duplicates(
&mut entry,
ParsedFlatValue::concrete(scalar("2")),
&first_options,
Duplicates::First,
)
.unwrap();
}
Expand All @@ -96,18 +99,20 @@ fn occupied_insert_respects_duplicate_strategies() {
fn default_insert_keeps_concrete_storage_until_parsing_is_required() {
let mut first_values = FlatValues::Concrete(Default::default());
let first_options = DecodeOptions::new().with_duplicates(Duplicates::First);
insert_default_value(
insert_default_value_with_duplicates(
&mut first_values,
"a".to_owned(),
ParsedFlatValue::concrete(scalar("1")),
&first_options,
Duplicates::First,
)
.unwrap();
insert_default_value(
insert_default_value_with_duplicates(
&mut first_values,
"a".to_owned(),
ParsedFlatValue::concrete(scalar("2")),
&first_options,
Duplicates::First,
)
.unwrap();
assert!(stores_concrete_value(&first_values, "a"));
Expand All @@ -118,18 +123,20 @@ fn default_insert_keeps_concrete_storage_until_parsing_is_required() {

let mut last_values = FlatValues::Concrete(Default::default());
let last_options = DecodeOptions::new().with_duplicates(Duplicates::Last);
insert_default_value(
insert_default_value_with_duplicates(
&mut last_values,
"a".to_owned(),
ParsedFlatValue::concrete(scalar("1")),
&last_options,
Duplicates::Last,
)
.unwrap();
insert_default_value(
insert_default_value_with_duplicates(
&mut last_values,
"a".to_owned(),
ParsedFlatValue::concrete(scalar("2")),
&last_options,
Duplicates::Last,
)
.unwrap();
let FlatValues::Concrete(last_entries) = &last_values else {
Expand All @@ -139,28 +146,31 @@ fn default_insert_keeps_concrete_storage_until_parsing_is_required() {

let mut combine_values = FlatValues::Concrete(Default::default());
let combine_options = DecodeOptions::new().with_duplicates(Duplicates::Combine);
insert_default_value(
insert_default_value_with_duplicates(
&mut combine_values,
"a".to_owned(),
ParsedFlatValue::concrete(scalar("1")),
&combine_options,
Duplicates::Combine,
)
.unwrap();
insert_default_value(
insert_default_value_with_duplicates(
&mut combine_values,
"a".to_owned(),
ParsedFlatValue::concrete(scalar("2")),
&combine_options,
Duplicates::Combine,
)
.unwrap();
assert!(stores_parsed_value(&combine_values, "a"));

let mut parsed_values = FlatValues::Concrete(Default::default());
insert_default_value(
insert_default_value_with_duplicates(
&mut parsed_values,
"a".to_owned(),
ParsedFlatValue::parsed(Node::Array(vec![Node::scalar(scalar("1"))]), true),
&DecodeOptions::new(),
Duplicates::Combine,
)
.unwrap();
assert!(stores_parsed_value_with_compaction(&parsed_values, "a"));
Expand All @@ -169,11 +179,12 @@ fn default_insert_keeps_concrete_storage_until_parsing_is_required() {
#[test]
fn default_insert_first_ignores_late_parsed_values_for_existing_concrete_keys() {
let mut values = FlatValues::Concrete([("a".to_owned(), scalar("1"))].into());
insert_default_value(
insert_default_value_with_duplicates(
&mut values,
"a".to_owned(),
ParsedFlatValue::parsed(Node::Array(vec![Node::scalar(scalar("2"))]), true),
&DecodeOptions::new().with_duplicates(Duplicates::First),
Duplicates::First,
)
.unwrap();

Expand Down
Loading
Loading