summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Scott <sam@osohq.com>2022-03-05 21:19:28 -0600
committerSam Scott <sam@osohq.com>2022-03-05 21:25:17 -0600
commit4bd3699faba3f00dd8f59a358605abb6e485deb9 (patch)
tree3570c30f8e8f6cd4a1aa1e9b7ec1bf2330c9d33a
parent58c1832578a103498b0120469b7a5d84833ad1b5 (diff)
Add error message hint for strict mode.
-rw-r--r--Cargo.toml2
-rw-r--r--src/de/mod.rs26
-rw-r--r--src/de/parse.rs25
-rw-r--r--src/error.rs2
-rw-r--r--src/ser.rs4
-rw-r--r--tests/test_deserialize.rs13
6 files changed, 56 insertions, 16 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 442970d..ad417fa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,7 +9,7 @@ license = "MIT/Apache-2.0"
name = "serde_qs"
repository = "https://github.com/samscott89/serde_qs"
readme = "README.md"
-version = "0.9.0"
+version = "0.9.1"
rust-version = "1.36"
[dependencies]
diff --git a/src/de/mod.rs b/src/de/mod.rs
index c3f9e40..6f71b95 100644
--- a/src/de/mod.rs
+++ b/src/de/mod.rs
@@ -200,7 +200,7 @@ enum Level<'a> {
OrderedSeq(BTreeMap<usize, Level<'a>>),
Sequence(Vec<Level<'a>>),
Flat(Cow<'a, str>),
- Invalid(&'static str),
+ Invalid(String),
Uninitialised,
}
@@ -335,9 +335,21 @@ impl<'de> de::MapAccess<'de> for QsDeserializer<'de> {
{
if let Some((key, value)) = self.iter.next() {
self.value = Some(value);
- return seed.deserialize(ParsableStringDeserializer(key)).map(Some);
- };
- Ok(None)
+ let has_bracket = key.contains('[');
+ seed.deserialize(ParsableStringDeserializer(key))
+ .map(Some)
+ .map_err(|e| {
+ if has_bracket {
+ de::Error::custom(
+ format!("{}\nInvalid field contains an encoded bracket -- did you mean to use non-strict mode?\n https://docs.rs/serde_qs/latest/serde_qs/#strict-vs-non-strict-modes", e,)
+ )
+ } else {
+ e
+ }
+ })
+ } else {
+ Ok(None)
+ }
}
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
@@ -348,8 +360,7 @@ impl<'de> de::MapAccess<'de> for QsDeserializer<'de> {
seed.deserialize(LevelDeserializer(v))
} else {
Err(de::Error::custom(
- "Somehow the list was empty after a \
- non-empty key was returned",
+ "Somehow the map was empty after a non-empty key was returned",
))
}
}
@@ -424,7 +435,8 @@ impl<'de> de::EnumAccess<'de> for LevelDeserializer<'de> {
LevelDeserializer(Level::Invalid(
"this value can only \
deserialize to a \
- UnitVariant",
+ UnitVariant"
+ .to_string(),
)),
)),
_ => Err(de::Error::custom(
diff --git a/src/de/parse.rs b/src/de/parse.rs
index a466e5a..5c85b12 100644
--- a/src/de/parse.rs
+++ b/src/de/parse.rs
@@ -1,5 +1,8 @@
+use crate::ser::{replace_space, QS_ENCODE_SET};
+
use super::*;
+use percent_encoding::percent_encode;
use serde::de;
use std::borrow::Cow;
@@ -25,8 +28,17 @@ impl<'a> Level<'a> {
if let Level::Nested(ref mut map) = *self {
match map.entry(key) {
Entry::Occupied(mut o) => {
+ let key = o.key();
+ let error = if key.contains('[') {
+ let newkey = percent_encode(key.as_bytes(), QS_ENCODE_SET)
+ .map(replace_space)
+ .collect::<String>();
+ format!("Multiple values for one key: \"{}\"\nInvalid field contains an encoded bracket -- did you mean to use non-strict mode?\n https://docs.rs/serde_qs/latest/serde_qs/#strict-vs-non-strict-modes", newkey)
+ } else {
+ format!("Multiple values for one key: \"{}\"", key)
+ };
// Throw away old result; map is now invalid anyway.
- let _ = o.insert(Level::Invalid("Multiple values for one key"));
+ let _ = o.insert(Level::Invalid(error));
}
Entry::Vacant(vm) => {
// Map is empty, result is None
@@ -40,7 +52,8 @@ impl<'a> Level<'a> {
} else {
*self = Level::Invalid(
"Attempted to insert map value into \
- non-map structure",
+ non-map structure"
+ .to_string(),
);
}
}
@@ -51,7 +64,7 @@ impl<'a> Level<'a> {
match map.entry(key) {
Entry::Occupied(mut o) => {
// Throw away old result; map is now invalid anyway.
- let _ = o.insert(Level::Invalid("Multiple values for one key"));
+ let _ = o.insert(Level::Invalid("Multiple values for one key".to_string()));
}
Entry::Vacant(vm) => {
// Map is empty, result is None
@@ -66,7 +79,8 @@ impl<'a> Level<'a> {
} else {
*self = Level::Invalid(
"Attempted to insert seq value into \
- non-seq structure",
+ non-seq structure"
+ .to_string(),
);
}
}
@@ -85,7 +99,8 @@ impl<'a> Level<'a> {
} else {
*self = Level::Invalid(
"Attempted to insert seq value into \
- non-seq structure",
+ non-seq structure"
+ .to_string(),
);
}
}
diff --git a/src/error.rs b/src/error.rs
index e303abf..9634643 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -10,7 +10,7 @@ use std::string;
#[derive(thiserror::Error, Debug)]
pub enum Error {
/// Custom string-based error
- #[error("failed with reason: {0}")]
+ #[error("{0}")]
Custom(String),
/// Parse error at a specified position in the query string
diff --git a/src/ser.rs b/src/ser.rs
index e23d677..2e1ea6e 100644
--- a/src/ser.rs
+++ b/src/ser.rs
@@ -10,7 +10,7 @@ use std::fmt::Display;
use std::io::Write;
use std::str;
-const QS_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
+pub const QS_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
.remove(b' ')
.remove(b'*')
.remove(b'-')
@@ -104,7 +104,7 @@ pub struct QsSerializer<'a, W: 'a + Write> {
first: &'a mut bool,
}
-fn replace_space(input: &str) -> Cow<str> {
+pub fn replace_space(input: &str) -> Cow<str> {
match input.as_bytes().iter().position(|&b| b == b' ') {
None => Cow::Borrowed(input),
Some(first_position) => {
diff --git a/tests/test_deserialize.rs b/tests/test_deserialize.rs
index 408255a..40cd43b 100644
--- a/tests/test_deserialize.rs
+++ b/tests/test_deserialize.rs
@@ -547,6 +547,19 @@ fn strict_mode() {
// Test that we don't panic
let malformed_params: Result<Query, _> = loose_config.deserialize_str("%");
assert!(malformed_params.is_err());
+
+ #[derive(Debug, Serialize, Deserialize, PartialEq)]
+ struct Query2 {
+ vec: Vec<u32>,
+ }
+ let repeated_key: Result<Query2, _> = strict_config.deserialize_str("vec%5B%5D=1&vec%5B%5D=2");
+ assert!(repeated_key.is_err());
+ println!("{}", repeated_key.unwrap_err());
+
+ let params: Query2 = loose_config
+ .deserialize_str("vec%5B%5D=1&vec%5B%5D=2")
+ .unwrap();
+ assert_eq!(params.vec, vec![1, 2]);
}
#[test]