diff options
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | src/ser.rs | 117 | ||||
-rw-r--r-- | tests/test_deserialize.rs | 17 | ||||
-rw-r--r-- | tests/test_serialize.rs | 21 |
5 files changed, 105 insertions, 52 deletions
@@ -24,7 +24,6 @@ itoa = "0.3.0" percent-encoding = "1.0.0" serde = "1.0.1" serde_derive = "1.0.1" -url = "1.4.0" [dev-dependencies] csv = "0.15" @@ -126,7 +126,6 @@ extern crate error_chain; extern crate percent_encoding; #[macro_use] extern crate serde; -extern crate url; mod de; mod error; @@ -2,16 +2,29 @@ //! Serialization support for querystrings. use data_encoding::BASE64URL_NOPAD as BASE64; +use percent_encoding::{percent_encode, EncodeSet}; use serde::ser; -use url::form_urlencoded::Serializer as UrlEncodedSerializer; -use url::form_urlencoded::Target as UrlEncodedTarget; -use std::fmt::Display; use std::borrow::Cow; +use std::fmt::Display; +use std::io::Write; use std::str; use error::*; +#[allow(non_camel_case_types)] +#[derive(Clone)] +struct QS_ENCODE_SET; + +impl EncodeSet for QS_ENCODE_SET { + fn contains(&self, byte: u8) -> bool { + match byte { + b' ' | b'*' | b'-' | b'.' | b'0' ... b'9' | b'A' ... b'Z' | b'_' | b'a' ... b'z' => false, + _ => true + } + } +} + /// Serializes a value into a querystring. /// /// ``` @@ -39,9 +52,10 @@ use error::*; /// # } /// ``` pub fn to_string<T: ser::Serialize>(input: &T) -> Result<String> { - let mut urlencoder = UrlEncodedSerializer::new("".to_owned()); - input.serialize(&mut QsSerializer { key: None, urlencoder: &mut urlencoder })?; - Ok(urlencoder.finish()) + let mut buffer = Vec::new(); + let mut first = true; + input.serialize(&mut QsSerializer { writer: &mut buffer, key: None, first: &mut first })?; + String::from_utf8(buffer).map_err(Error::from) } /// A serializer for the querystring format. @@ -52,30 +66,61 @@ pub fn to_string<T: ser::Serialize>(input: &T) -> Result<String> { /// sequences. Sequences are serialized with an incrementing key index. /// /// * Newtype structs defer to their inner values. -pub struct QsSerializer<'a, Target: 'a + UrlEncodedTarget> { +pub struct QsSerializer<'a, W: 'a + Write> { key: Option<Cow<'static, str>>, - urlencoder: &'a mut UrlEncodedSerializer<Target>, + writer: &'a mut W, + first: &'a mut bool, +} + +fn replace_space(input: &str) -> Cow<str> { + match input.as_bytes().iter().position(|&b| b == b' ') { + None => Cow::Borrowed(input), + Some(first_position) => { + let mut replaced = input.as_bytes().to_owned(); + replaced[first_position] = b'+'; + for byte in &mut replaced[first_position + 1..] { + if *byte == b' ' { + *byte = b'+'; + } + } + Cow::Owned(String::from_utf8(replaced).expect("replacing ' ' with '+' cannot panic")) + } + } } -impl<'a, Target: 'a + UrlEncodedTarget> QsSerializer<'a, Target> { +impl<'a, W: 'a + Write> QsSerializer<'a, W> { fn extend_key(&mut self, newkey: &str) { + let newkey = percent_encode(replace_space(newkey).as_bytes(), QS_ENCODE_SET).collect::<Cow<str>>(); let key = if let Some(ref key) = self.key { format!("{}[{}]", key, newkey).into() } else { - newkey.to_owned().into() + newkey.to_owned() }; self.key = Some(key) } fn write_value(&mut self, value: &str) -> Result<()> { if let Some(ref key) = self.key { - // returns &Self back anyway - let _ = self.urlencoder.append_pair(key, value); - Ok(()) + write!(self.writer, "{}{}={}", + if *self.first { *self.first = false; "" } else { "&" }, + key, + percent_encode(value.as_bytes(), QS_ENCODE_SET).map(replace_space).collect::<String>() + ).map_err(Error::from) } else { Err(Error::no_key()) } } + + /// Creates a new `QsSerializer` with a distinct key, but `writer` and + ///`first` referring to the original data. + fn new_from_ref<'b: 'a>(other: &'a mut QsSerializer<'b, W>) -> QsSerializer<'a, W> + { + Self { + key: other.key.clone(), + writer: other.writer, + first: other.first, + } + } } impl Error { @@ -102,14 +147,14 @@ macro_rules! serialize_as_string { }; } -impl<'a, Target: 'a + UrlEncodedTarget> ser::Serializer for &'a mut QsSerializer<'a, Target> { +impl<'a, W: Write> ser::Serializer for &'a mut QsSerializer<'a, W> { type Ok = (); type Error = Error; - type SerializeSeq = QsSeq<'a, Target>; - type SerializeTuple = QsSeq<'a, Target>; - type SerializeTupleStruct = QsSeq<'a, Target>; - type SerializeTupleVariant = QsSeq<'a, Target>; - type SerializeMap = QsMap<'a, Target>; + type SerializeSeq = QsSeq<'a, W>; + type SerializeTuple = QsSeq<'a, W>; + type SerializeTupleStruct = QsSeq<'a, W>; + type SerializeTupleVariant = QsSeq<'a, W>; + type SerializeMap = QsMap<'a, W>; type SerializeStruct = Self; type SerializeStructVariant = Self; @@ -257,17 +302,17 @@ impl ser::Error for Error { } } -pub struct QsSeq<'a, Target: 'a + UrlEncodedTarget>(&'a mut QsSerializer<'a, Target>, usize); -pub struct QsMap<'a, Target: 'a + UrlEncodedTarget>(&'a mut QsSerializer<'a, Target>, Option<Cow<'a, str>>); +pub struct QsSeq<'a, W: 'a + Write>(&'a mut QsSerializer<'a, W>, usize); +pub struct QsMap<'a, W: 'a + Write>(&'a mut QsSerializer<'a, W>, Option<Cow<'a, str>>); -impl<'a, Target: 'a + UrlEncodedTarget> ser::SerializeTuple for QsSeq<'a, Target> { +impl<'a, W: Write> ser::SerializeTuple for QsSeq<'a, W> { type Ok = (); type Error = Error; fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<()> where T: ser::Serialize { - let mut serializer = QsSerializer { key: self.0.key.clone(), urlencoder: self.0.urlencoder }; + let mut serializer = QsSerializer::new_from_ref(self.0); serializer.extend_key(&self.1.to_string()); self.1 += 1; value.serialize(&mut serializer) @@ -279,13 +324,13 @@ impl<'a, Target: 'a + UrlEncodedTarget> ser::SerializeTuple for QsSeq<'a, Target } } -impl<'a, Target: 'a + UrlEncodedTarget> ser::SerializeSeq for QsSeq<'a, Target> { +impl<'a, W: Write> ser::SerializeSeq for QsSeq<'a, W> { type Ok = (); type Error = Error; fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<()> where T: ser::Serialize { - let mut serializer = QsSerializer { key: self.0.key.clone(), urlencoder: self.0.urlencoder }; + let mut serializer = QsSerializer::new_from_ref(self.0); serializer.extend_key(&self.1.to_string()); self.1 += 1; value.serialize(&mut serializer) @@ -296,13 +341,13 @@ impl<'a, Target: 'a + UrlEncodedTarget> ser::SerializeSeq for QsSeq<'a, Target> } } -impl<'a, Target: 'a + UrlEncodedTarget> ser::SerializeStruct for &'a mut QsSerializer<'a, Target> { +impl<'a, W: Write> ser::SerializeStruct for &'a mut QsSerializer<'a, W> { type Ok = (); type Error = Error; fn serialize_field<T: ?Sized>(&mut self, key: &'static str, value: &T) -> Result<()> where T: ser::Serialize { - let mut serializer = QsSerializer { key: self.key.clone(), urlencoder: self.urlencoder }; + let mut serializer = QsSerializer::new_from_ref(self); serializer.extend_key(key); value.serialize(&mut serializer) } @@ -311,14 +356,14 @@ impl<'a, Target: 'a + UrlEncodedTarget> ser::SerializeStruct for &'a mut QsSeria } } -impl<'a, Target: 'a + UrlEncodedTarget> ser::SerializeStructVariant for &'a mut QsSerializer<'a, Target> { +impl<'a, W: Write> ser::SerializeStructVariant for &'a mut QsSerializer<'a, W> { type Ok = (); type Error = Error; fn serialize_field<T: ?Sized>(&mut self, key: &'static str, value: &T) -> Result<()> where T: ser::Serialize { - let mut serializer = QsSerializer { key: self.key.clone(), urlencoder: self.urlencoder }; + let mut serializer = QsSerializer::new_from_ref(self); serializer.extend_key(key); value.serialize(&mut serializer) } @@ -329,14 +374,14 @@ impl<'a, Target: 'a + UrlEncodedTarget> ser::SerializeStructVariant for &'a mut } -impl<'a, Target: 'a + UrlEncodedTarget> ser::SerializeTupleVariant for QsSeq<'a, Target> { +impl<'a, W: Write> ser::SerializeTupleVariant for QsSeq<'a, W> { type Ok = (); type Error = Error; fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<()> where T: ser::Serialize { - let mut serializer = QsSerializer { key: self.0.key.clone(), urlencoder: self.0.urlencoder }; + let mut serializer = QsSerializer::new_from_ref(self.0); serializer.extend_key(&self.1.to_string()); self.1 += 1; value.serialize(&mut serializer) @@ -348,14 +393,14 @@ impl<'a, Target: 'a + UrlEncodedTarget> ser::SerializeTupleVariant for QsSeq<'a, } -impl<'a, Target: 'a + UrlEncodedTarget> ser::SerializeTupleStruct for QsSeq<'a, Target> { +impl<'a, W: Write> ser::SerializeTupleStruct for QsSeq<'a, W> { type Ok = (); type Error = Error; fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<()> where T: ser::Serialize { - let mut serializer = QsSerializer { key: self.0.key.clone(), urlencoder: self.0.urlencoder }; + let mut serializer = QsSerializer::new_from_ref(self.0); serializer.extend_key(&self.1.to_string()); self.1 += 1; value.serialize(&mut serializer) @@ -367,7 +412,7 @@ impl<'a, Target: 'a + UrlEncodedTarget> ser::SerializeTupleStruct for QsSeq<'a, } -impl<'a, Target: 'a + UrlEncodedTarget> ser::SerializeMap for QsMap<'a, Target> { +impl<'a, W: Write> ser::SerializeMap for QsMap<'a, W> { type Ok = (); type Error = Error; @@ -381,7 +426,7 @@ impl<'a, Target: 'a + UrlEncodedTarget> ser::SerializeMap for QsMap<'a, Target> fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<()> where T: ser::Serialize { - let mut serializer = QsSerializer { key: self.0.key.clone(), urlencoder: self.0.urlencoder }; + let mut serializer = QsSerializer::new_from_ref(self.0); if let Some(ref key) = self.1 { serializer.extend_key(key); } else { @@ -398,7 +443,7 @@ impl<'a, Target: 'a + UrlEncodedTarget> ser::SerializeMap for QsMap<'a, Target> fn serialize_entry<K: ?Sized, V: ?Sized>(&mut self, key: &K, value: &V) -> Result<()> where K: ser::Serialize, V: ser::Serialize, { - let mut serializer = QsSerializer { key: self.0.key.clone(), urlencoder: self.0.urlencoder }; + let mut serializer = QsSerializer::new_from_ref(self.0); serializer.extend_key(&key.serialize(StringSerializer)?); value.serialize(&mut serializer) } diff --git a/tests/test_deserialize.rs b/tests/test_deserialize.rs index e0392f5..a71e4bf 100644 --- a/tests/test_deserialize.rs +++ b/tests/test_deserialize.rs @@ -443,4 +443,21 @@ fn strict_mode() { let params: Result<Query, _> = loose_config.deserialize_str("vec%5B0%5D%5Ba%5D]=1&vec[1][a]=2"); assert_eq!(params.unwrap(), Query { vec: vec![Test { a: 1 }, Test { a: 2 }] }); + + #[derive(Deserialize,Serialize,Debug, PartialEq)] + struct OddTest { + #[serde(rename="[but&why=?]")] + a: u8 + } + + let params = OddTest { a: 12 }; + let enc_params = qs::to_string(¶ms).unwrap(); + println!("Enocded as: {}", enc_params); + let rec_params: Result<OddTest, _> = strict_config.deserialize_str(&enc_params); + assert_eq!(rec_params.unwrap(), params); + + // Non-strict decoding cannot necessarily handle these weird scenerios. + let rec_params: Result<OddTest, _> = loose_config.deserialize_str(&enc_params); + assert!(rec_params.is_err()); + println!("{}", rec_params.unwrap_err()); }
\ No newline at end of file diff --git a/tests/test_serialize.rs b/tests/test_serialize.rs index 7d9c38f..a2b725f 100644 --- a/tests/test_serialize.rs +++ b/tests/test_serialize.rs @@ -31,15 +31,10 @@ fn serialize_struct() { user_ids: vec![1, 2, 3, 4], }; - assert_eq!(qs::to_string(¶ms).unwrap(), - urlencode("\ + assert_eq!(qs::to_string(¶ms).unwrap(),"\ id=42&name=Acme&phone=12345&address[city]=Carrot+City&\ address[postcode]=12345&user_ids[0]=1&user_ids[1]=2&\ - user_ids[2]=3&user_ids[3]=4")); -} - -fn urlencode(input: &str) -> String { - str::replace(&str::replace(input, "[", "%5B"), "]", "%5D") + user_ids[2]=3&user_ids[3]=4"); } #[test] @@ -56,7 +51,7 @@ fn serialize_option() { let rec_params = qs::to_string(&query).unwrap(); assert_eq!(rec_params, params); - let params = urlencode("vec[0]=1&vec[1]=2"); + let params = "vec[0]=1&vec[1]=2"; let query = Query { vec: Some(vec![1,2]), }; @@ -80,33 +75,31 @@ fn serialize_enum() { e: TestEnum, } - let params = urlencode("e=a"); + let params = "e=a"; let query = Query { e: TestEnum::A, }; let rec_params = qs::to_string(&query).unwrap(); assert_eq!(rec_params, params); - let params = urlencode("e[b]=true"); + let params = "e[b]=true"; let query = Query { e: TestEnum::B(true), }; let rec_params = qs::to_string(&query).unwrap(); assert_eq!(rec_params, params); - let params = urlencode("e[c][x]=2&e[c][y]=3"); + let params = "e[c][x]=2&e[c][y]=3"; let query = Query { e: TestEnum::C { x: 2, y: 3 }, }; let rec_params = qs::to_string(&query).unwrap(); assert_eq!(rec_params, params); - let params = urlencode("e[d][0]=128&e[d][1]=1"); + let params = "e[d][0]=128&e[d][1]=1"; let query = Query { e: TestEnum::D(128, 1), }; let rec_params = qs::to_string(&query).unwrap(); assert_eq!(rec_params, params); - - }
\ No newline at end of file |