diff options
author | Sam Scott <sam.scott89@gmail.com> | 2017-03-10 19:11:33 -0500 |
---|---|---|
committer | Sam Scott <sam.scott89@gmail.com> | 2017-03-10 19:11:33 -0500 |
commit | 217dd144ff31af3673624f8a241f7e52995ed9ec (patch) | |
tree | 47107f54b3892d8c1e3384b40c73465cf890dc3a | |
parent | b7cb1b9aef155fc9b7b886fc21e92e3e12be0d86 (diff) |
Expand tests and examples.
Need to fix sequences.
-rw-r--r-- | examples/csv_vectors.rs | 51 | ||||
-rw-r--r-- | src/de.rs | 332 | ||||
-rw-r--r-- | tests/test_deserialize.rs | 181 |
3 files changed, 424 insertions, 140 deletions
diff --git a/examples/csv_vectors.rs b/examples/csv_vectors.rs new file mode 100644 index 0000000..95e4398 --- /dev/null +++ b/examples/csv_vectors.rs @@ -0,0 +1,51 @@ +extern crate csv; +extern crate serde; +#[macro_use] +extern crate serde_derive; +extern crate serde_qs as qs; + +#[derive(Debug, Deserialize, Serialize)] +struct Query { + #[serde(deserialize_with="from_csv")] + r: Vec<u8>, + s: u8, +} + +fn main() { + let q = "s=12&r=1,2,3"; + let q: Query = qs::from_str(&q).unwrap(); + println!("{:?}", q); +} + + +fn from_csv<D>(deserializer: D) -> Result<Vec<u8>, D::Error> + where D: serde::Deserializer +{ + deserializer.deserialize_str(CSVVecVisitor) +} + +/// Visits a string value of the form "v1,v2,v3" into a vector of bytes Vec<u8> +struct CSVVecVisitor; + +impl serde::de::Visitor for CSVVecVisitor { + type Value = Vec<u8>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "a str") + } + + fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E> + where E: serde::de::Error + { + let mut output = Vec::new(); + let mut items = csv::Reader::from_string(s); + // let items = items.next_str(); + while let csv::NextField::Data(item) = items.next_str() { + output.push(u8::from_str_radix(item, 10).unwrap()); + } + + Ok(output) + } + + +} @@ -9,6 +9,47 @@ use serde::de::value::MapDeserializer; use std::io::Read; use url::percent_encoding; +/// +pub struct Config { + max_depth: usize, +} + +impl Default for Config { + fn default() -> Self { + Config { + max_depth: 6, + } + } +} + +impl Config { + pub fn max_depth(&mut self, depth: usize) { + self.max_depth = depth; + } +} + +impl Config { + pub fn from_bytes<T: de::Deserialize>(&self, input: &[u8]) -> Result<T, Error> { + T::deserialize(Deserializer::with_config(self, input)) + } + + pub fn from_str<T: de::Deserialize>(&self, input: &str) -> Result<T, Error> { + self.from_bytes(input.as_bytes()) + } + + pub fn from_reader<T, R>(&self, mut reader: R) -> Result<T, Error> + where T: de::Deserialize, R: Read + { + let mut buf = vec![]; + reader.read_to_end(&mut buf) + .map_err(|e| { + de::Error::custom(format_args!("could not read input: {}", e)) + })?; + self.from_bytes(&buf) + // from_bytes(&buf) + // T::deserialize(Deserializer::with_config(self, input.as_bytes())) + } +} /// Deserializes a query-string from a `&[u8]`. /// /// ``` @@ -35,7 +76,7 @@ use url::percent_encoding; /// # } /// ``` pub fn from_bytes<T: de::Deserialize>(input: &[u8]) -> Result<T, Error> { - T::deserialize(Deserializer::new(input)) + Config::default().from_bytes(input) } /// Deserializes a query-string from a `&str`. @@ -113,6 +154,7 @@ struct Parser<I: Iterator<Item=u8>> { inner: I, acc: Vec<u8>, peeked: Option<u8>, + depth: usize, } impl<I: Iterator<Item=u8>> Iterator for Parser<I> @@ -125,14 +167,6 @@ impl<I: Iterator<Item=u8>> Iterator for Parser<I> } impl<I: Iterator<Item=u8>> Parser<I> { - fn new(iter: I) -> Self { - Parser { - inner: iter, - acc: Vec::new(), - peeked: None, - } - } - #[inline] fn peek(&mut self) -> Option<<Self as Iterator>::Item> { if !self.acc.is_empty() { @@ -148,99 +182,136 @@ impl<I: Iterator<Item=u8>> Parser<I> { } } - fn parse_string_key(&mut self, end_on: u8, consume: bool) -> Result<String, Error> { + fn parse_key(&mut self, end_on: u8, consume: bool) -> Result<String, Error> { loop { - match tu!(self.next()) { - x if x == end_on => { - let res = String::from_utf8(self.acc.split_off(0)); - self.acc.clear(); - - // Add this character back to the buffer for peek. - if !consume { + if let Some(x) = self.next() { + match x { + x if x == end_on => { + let res = String::from_utf8(self.acc.split_off(0)); + self.acc.clear(); + + // Add this character back to the buffer for peek. + if !consume { + self.acc.push(x); + self.peeked = Some(x); + } + return res.map_err(|_| de::Error::custom("blah")) + }, + b'=' => { + // Allow the '=' byte when parsing keys within [] + if end_on == b']' { + self.acc.push(b'='); + } else { + let res = String::from_utf8(self.acc.split_off(0)); + self.acc.clear(); + + // Add this character back to the buffer for peek. + self.acc.push(b'='); + self.peeked = Some(b'='); + + return res.map_err(|_| de::Error::custom("blah")) + } + } + // x @ b']' | x @ b'[' => { + // return Err(de::Error::custom(format!("unexpected character {} in query string, waiting for: {}.", x as char, end_on as char))); + // } + b' ' => { + self.acc.push(b' '); + } + b'&' => { + let res = String::from_utf8(self.acc.split_off(0)); + self.acc.clear(); + self.acc.push(b'&'); + self.peeked = Some(b'&'); + return res.map_err(|_| de::Error::custom("blah")) + } + x @ 0x20 ... 0x7e => { self.acc.push(x); - self.peeked = Some(x); + }, + _ => { + return Err(de::Error::custom("unexpected character in query string.")); } - return res.map_err(|_| de::Error::custom("blah")) - }, - x @ b'=' => { - let res = String::from_utf8(self.acc.split_off(0)); - self.acc.clear(); - - // Add this character back to the buffer for peek. - self.acc.push(x); - self.peeked = Some(x); - return res.map_err(|_| de::Error::custom("blah")) - } - x @ b']' | x @ b'[' => { - return Err(de::Error::custom(format!("unexpected character {} in query string, waiting for: {}.", x as char, end_on as char))); - } - x @ 0x20 ... 0x7e => { - self.acc.push(x); - }, - _ => { - return Err(de::Error::custom("unexpected character in query string.")); } + } else { + // End of string. + let res = String::from_utf8(self.acc.split_off(0)); + self.acc.clear(); + return res.map_err(|_| de::Error::custom("blah")) } } } - fn parse_int_key(&mut self, end_on: u8) -> Result<String, Error> { - loop { - match tu!(self.next()) { - x if x == end_on => { - let res = String::from_utf8(self.acc.split_off(0)).unwrap(); + fn parse_map_value(&mut self, key: String, node: &mut Level) -> Result<(), Error> { + if let Some(x) = self.peek() { + match x { + b'=' => { self.acc.clear(); - return Ok(res); + for b in self.inner.by_ref().take_while(|b| b != &b'&') { + if b == b'+' { + self.acc.push(b' '); + } else { + self.acc.push(b); + } + } + let value = String::from_utf8(self.acc.split_off(0)); + let value = value.map_err(|_e| de::Error::custom("blah"))?; + // Reached the end of the key string + if let Level::Nested(ref mut map) = *node { + match map.entry(key) { + Entry::Occupied(mut o) => { + o.insert(Level::Invalid("Multiple values for one key")); + }, + Entry::Vacant(vm) => { + vm.insert(Level::Flat(value)); + } + } + } else { + panic!(""); + } + Ok(()) }, - x @ b'[' => { - return Err(de::Error::custom(format!("unexpected character {} in query string, waiting for: {}.", x as char, end_on as char))); + b'&' => { + if let Level::Nested(ref mut map) = *node { + match map.entry(key) { + Entry::Occupied(mut o) => { + o.insert(Level::Invalid("Multiple values for one key")); + }, + Entry::Vacant(vm) => { + vm.insert(Level::Flat("".into())); + } + } + } else { + panic!(""); + } + Ok(()) } - x @ b'0' ... b'9' => { - self.acc.push(x); - }, _ => { - return Err(de::Error::custom("unexpected character in query string.")); + // Ok(()) + if let Level::Nested(ref mut map) = *node { + self.depth -= 1; + self.parse( + map.entry(key).or_insert(Level::Nested(BTreeMap::default())) + )?; + Ok(()) + } else { + Ok(()) + } } } - } - - } - - fn parse_map_value(&mut self, key: String, node: &mut Level) -> Result<(), Error> { - match tu!(self.peek()) { - b'=' => { - self.acc.clear(); - for b in self.inner.by_ref().take_while(|b| b != &b'&') { - self.acc.push(b); - } - let value = String::from_utf8(self.acc.split_off(0)); - let value = value.map_err(|_e| de::Error::custom("blah"))?; - // Reached the end of the key string - if let Level::Nested(ref mut map) = *node { - match map.entry(key) { - Entry::Occupied(mut o) => { - o.insert(Level::Invalid("Multiple values for one key")); - }, - Entry::Vacant(vm) => { - vm.insert(Level::Flat(value)); - } + } else { + if let Level::Nested(ref mut map) = *node { + match map.entry(key) { + Entry::Occupied(mut o) => { + o.insert(Level::Invalid("Multiple values for one key")); + }, + Entry::Vacant(vm) => { + vm.insert(Level::Flat("".into())); } - } else { - panic!(""); - } - Ok(()) - }, - _ => { - // Ok(()) - if let Level::Nested(ref mut map) = *node { - self.parse( - map.entry(key).or_insert(Level::Nested(BTreeMap::default())) - )?; - Ok(()) - } else { - Ok(()) } + } else { + panic!(""); } + Ok(()) } } @@ -272,45 +343,53 @@ impl<I: Iterator<Item=u8>> Parser<I> { fn parse(&mut self, node: &mut Level) -> Result<bool, Error> { // First character determines parsing type + if self.depth == 0 { + let key = self.parse_key(b'\x00', true)?; + self.parse_map_value(key.into(), node)?; + self.depth += 1; + return Ok(true); + } match self.peek() { Some(x) => match x { - b'a' ... b'z' | b'A' ... b'Z' => { - let key = self.parse_string_key(b'[', false).unwrap(); - self.parse_map_value(key.into(), node)?; - Ok(true) - }, b'[' => { self.acc.clear(); // let _ = self.next(); match tu!(self.peek()) { - b'a' ... b'z' | b'A' ... b'Z' => { - let key = self.parse_string_key(b']', true).unwrap(); - // key.into() - self.parse_map_value(key.into(), node)?; - Ok(true) + b'[' => { + panic!(""); }, b']' => { self.parse_seq_value(node)?; + self.depth += 1; Ok(true) }, - b'0' ... b'9' => { - let key = self.parse_int_key(b']').unwrap(); - self.parse_map_value(key, node)?; + 0x20 ... 0x7e => { + let key = self.parse_key(b']', true).unwrap(); + // key.into() + self.parse_map_value(key.into(), node)?; + self.depth += 1; Ok(true) + }, _ => { panic!(""); } } }, + 0x20 ... 0x7e => { + let key = self.parse_key(b'[', false).unwrap(); + self.parse_map_value(key.into(), node)?; + self.depth += 1; + Ok(true) + }, _ => { panic!(""); } }, // Ran out of characters to parse - None => return Ok(false) + None => Ok(false) } } @@ -325,12 +404,18 @@ impl Deserializer { } /// Returns a new `Deserializer`. - pub fn new(input: &[u8]) -> Self { + fn with_config(config: &Config, input: &[u8]) -> Self { let map = BTreeMap::default(); let mut root = Level::Nested(map); let decoded = percent_encoding::percent_decode(&input); - let mut parser = Parser::new(decoded); + let mut parser = Parser { + inner: decoded, + acc: Vec::new(), + peeked: None, + depth: config.max_depth, + }; + while let Ok(x) = parser.parse(&mut root) { if !x { break @@ -347,6 +432,30 @@ impl Deserializer { value: None, } } + + // /// Returns a new `Deserializer`. + // pub fn new(input: &[u8]) -> Self { + // let map = BTreeMap::default(); + // let mut root = Level::Nested(map); + + // let decoded = percent_encoding::percent_decode(&input); + // let mut parser = Parser::new(decoded); + // while let Ok(x) = parser.parse(&mut root) { + // if !x { + // break + // } + // } + // // self.input = Some(decoded.as_bytes()); + // // println!("{:?}", root); + // let iter = match root { + // Level::Nested(map) => map.into_iter(), + // _ => panic!(""), + // }; + // Deserializer { + // iter: iter, + // value: None, + // } + // } } impl de::Deserializer for Deserializer { @@ -463,7 +572,7 @@ impl de::Deserializer for LevelDeserializer { if let Level::Nested(x) = self.0 { Deserializer::with_map(x).deserialize_map(visitor) } else { - Err(de::Error::custom("value does not appear to be a map")) + Err(de::Error::custom(format!("value: {:?} does not appear to be a map", self.0))) } } @@ -504,6 +613,23 @@ impl de::Deserializer for LevelDeserializer { self.deserialize_seq(visitor) } + fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error> where V: de::Visitor + + { + match self.0 { + Level::Flat(x) => { + if x == "" { + visitor.visit_none() + } else { + visitor.visit_some(x.into_deserializer()) + } + } + _ => { + Err(de::Error::custom("value does not appear to be a value")) + } + } + } + forward_to_deserialize! { bool @@ -521,7 +647,7 @@ impl de::Deserializer for LevelDeserializer { str string unit - option + // option bytes byte_buf unit_struct diff --git a/tests/test_deserialize.rs b/tests/test_deserialize.rs index fb7d67e..bac84bf 100644 --- a/tests/test_deserialize.rs +++ b/tests/test_deserialize.rs @@ -2,55 +2,162 @@ extern crate serde_derive; extern crate serde_qs as qs; +use std::collections::HashMap; -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -struct A { b: B, c: C } -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -struct B { b1: u8, b2: String } -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -struct C { c1: String, c2: u8 } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +struct Address { + city: String, + postcode: String, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +struct QueryParams { + id: u8, + name: String, + address: Address, + user_ids: Vec<u8>, +} + +macro_rules! map_test { + ($string:expr, $($mapvars:tt)*) => { + let expected_map = hash_to_map!(New $($mapvars)*); + let testmap: HashMap<_, _> = qs::from_str($string).unwrap(); + assert_eq!(expected_map, testmap); + } +} + + +macro_rules! hash_to_map { + // Base case: a map with no inputs, do nothing + ($map:expr, ) => (); + //{} + // This parses a single map entry, with a value explicitly an expression. + ($map:expr, $k:tt[e $v:expr] $($rest:tt)*) => {{ + $map.insert($k.to_owned(), $v.to_owned()); + hash_to_map!($map, $($rest)*); + }}; -#[derive(PartialEq, Debug, Serialize, Deserialize)] -struct Complex { x: Vec<u8>, y: Vec<C> } + // This parses a single map entry, plus the rest of the values. + ($map:expr, $k:tt[$v:tt] $($rest:tt)*) => {{ + $map.insert($k.to_owned(), $v.to_owned()); + hash_to_map!($map, $($rest)*); + }}; + // This parses the first entry as a nested entry, and tail calls the remaining + // in rest. + ($map:expr, $k:tt[$($inner:tt)*] $($rest:tt)*) => {{ + let mut inner_map = HashMap::new(); + hash_to_map!(inner_map, $($inner)*); + $map.insert($k.to_owned(), inner_map); + hash_to_map!($map, $($rest)*); + }}; + + // Constructs the map and then runs the macro. This infers the types for the + // hashmap. + (New $($rest:tt)*) => {{ + let mut map = HashMap::new(); + hash_to_map!(map, $($rest)*); + map + }} + +} #[test] fn deserialize_struct() { - let params = A { - b: B { - b1: 10, - b2: "Ten".to_owned() - }, - c: C { - c1: "Seven".to_owned(), - c2: 7 - } + let params = QueryParams { + id: 42, + name: "Acme".to_string(), + address: Address { + city: "Carrot City".to_string(), + postcode: "12345".to_string(), + }, + user_ids: vec!(1,2,3,4), }; - let complex_params = Complex { - x: vec![0,1,2], - y: vec![params.c.clone()], - }; + let rec_params: QueryParams = qs::from_str("name=Acme&id=42&phone=12345&address[postcode]=12345&address[city]=Carrot+City&user_ids[0]=1&user_ids[1]=2&user_ids[2]=3&user_ids[3]=4").unwrap(); + assert_eq!(rec_params, params); + let rec_params: QueryParams = qs::from_str("name=Acme&id=42&phone=12345&address[postcode]=12345&address[city]=Carrot+City&user_ids[]=1&user_ids[]=2&user_ids[]=3&user_ids[]=4").unwrap(); + assert_eq!(rec_params, params); + +} + +#[test] +fn qs_test_simple() { +// test('parse()', function (t) { + // t.test('parses a simple string', function (st) { + // st.deepEqual(qs.parse('0=foo'), { 0: 'foo' }); + map_test!("0=foo", 0["foo"]); - let input = "b[b1]=10&b[b2]=Ten&c[c1]=Seven&c[c2]=7"; - let input2 = "c[c1]=Seven&b[b2]=Ten&b[b1]=10&c[c2]=7"; - let result: A = qs::from_str(&urlencode(input)).unwrap(); - assert_eq!(result, params); - let result: A = qs::from_str(&input).unwrap(); - assert_eq!(result, params); - let result: A = qs::from_str(&urlencode(input2)).unwrap(); - assert_eq!(result, params); - let result: A = qs::from_str(&input2).unwrap(); - assert_eq!(result, params); + // st.deepEqual(qs.parse('foo=c++'), { foo: 'c ' }); + map_test!("foo=c++", "foo"["c "]); - let input3 = "x[0]=0&x[1]=1&x[2]=2&y[0][c1]=Seven&y[0][c2]=7"; - let result: Complex = qs::from_str(&input3).unwrap(); - assert_eq!(complex_params, result); + // st.deepEqual(qs.parse('a[>=]=23'), { a: { '>=': '23' } }); + map_test!("a[>=]=23", "a"[">="[23]]); + // st.deepEqual(qs.parse('a[<=>]==23'), { a: { '<=>': '=23' } }); + map_test!("a[<=>]==23", "a"["<=>"["=23"]]); + // st.deepEqual(qs.parse('a[==]=23'), { a: { '==': '23' } }); + map_test!("a[==]=23", "a"["=="[23]]); + // st.deepEqual(qs.parse('foo', { strictNullHandling: true }), { foo: null }); + let none: Option<String> = Option::None; + map_test!("foo", "foo"[none]); + + // st.deepEqual(qs.parse('foo'), { foo: '' }); + map_test!("foo", "foo"[""]); + + // st.deepEqual(qs.parse('foo='), { foo: '' }); + map_test!("foo=", "foo"[""]); + + // st.deepEqual(qs.parse('foo=bar'), { foo: 'bar' }); + map_test!("foo=bar", "foo"["bar"]); + + // st.deepEqual(qs.parse(' foo = bar = baz '), { ' foo ': ' bar = baz ' }); + map_test!(" foo = bar = baz ", " foo "[" bar = baz "]); + + // st.deepEqual(qs.parse('foo=bar=baz'), { foo: 'bar=baz' }); + map_test!("foo=bar=baz", "foo"["bar=baz"]); + + // st.deepEqual(qs.parse('foo=bar&bar=baz'), { foo: 'bar', bar: 'baz' }); + map_test!("foo=bar&bar=baz", "foo"["bar"] "bar"["baz"]); + + // st.deepEqual(qs.parse('foo2=bar2&baz2='), { foo2: 'bar2', baz2: '' }); + map_test!("foo2=bar2&baz2=", "foo2"["bar2"] "baz2"[""]); + + // st.deepEqual(qs.parse('foo=bar&baz', { strictNullHandling: true }), { foo: 'bar', baz: null }); + map_test!("foo=bar&baz", "foo"[e Some("bar".to_string())] "baz"[e None]); + + // st.deepEqual(qs.parse('foo=bar&baz'), { foo: 'bar', baz: '' }); + map_test!("foo=bar&baz", "foo"["bar"] "baz"[""]); + + // st.deepEqual(qs.parse('cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World'), { + // cht: 'p3', + // chd: 't:60,40', + // chs: '250x100', + // chl: 'Hello|World' + // }); + map_test!("cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World", + "cht"["p3"] + "chd"["t:60,40"] + "chs"["250x100"] + "chl"["Hello|World"] + ); + // st.end(); +// }); } -fn urlencode(input: &str) -> String { - str::replace(&str::replace(input, "[", "%5B"), "]", "%5D") -}
\ No newline at end of file +#[test] +fn qs_nesting() { + // t.deepEqual(qs.parse('a[b]=c'), { a: { b: 'c' } }, 'parses a single nested string'); + map_test!("a[b]=c", "a"["b"["c"]]); + + // t.deepEqual(qs.parse('a[b][c]=d'), { a: { b: { c: 'd' } } }, 'parses a double nested string'); + map_test!("a[b][c]=d", "a"["b"["c"["d"]]]); + // t.deepEqual( + // qs.parse('a[b][c][d][e][f][g][h]=i'), + // { a: { b: { c: { d: { e: { f: { '[g][h]': 'i' } } } } } } }, + // 'defaults to a depth of 5' + // ); + // map_test!("a[b][c][d][e][f][g][h]=i", "a"["b"["c"["d"["e"["f"["[g][h]"["i"]]]]]]]); +} |