diff options
author | Sam Scott <sam@osohq.com> | 2022-03-05 21:19:28 -0600 |
---|---|---|
committer | Sam Scott <sam@osohq.com> | 2022-03-05 21:25:17 -0600 |
commit | 4bd3699faba3f00dd8f59a358605abb6e485deb9 (patch) | |
tree | 3570c30f8e8f6cd4a1aa1e9b7ec1bf2330c9d33a | |
parent | 58c1832578a103498b0120469b7a5d84833ad1b5 (diff) |
Add error message hint for strict mode.
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/de/mod.rs | 26 | ||||
-rw-r--r-- | src/de/parse.rs | 25 | ||||
-rw-r--r-- | src/error.rs | 2 | ||||
-rw-r--r-- | src/ser.rs | 4 | ||||
-rw-r--r-- | tests/test_deserialize.rs | 13 |
6 files changed, 56 insertions, 16 deletions
@@ -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 @@ -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] |