summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--src/lib.rs1
-rw-r--r--src/ser.rs117
-rw-r--r--tests/test_deserialize.rs17
-rw-r--r--tests/test_serialize.rs21
5 files changed, 105 insertions, 52 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 718bc76..a2b68a5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/src/lib.rs b/src/lib.rs
index e6f5190..16d4683 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -126,7 +126,6 @@ extern crate error_chain;
extern crate percent_encoding;
#[macro_use]
extern crate serde;
-extern crate url;
mod de;
mod error;
diff --git a/src/ser.rs b/src/ser.rs
index 2eb9aa0..cb6834f 100644
--- a/src/ser.rs
+++ b/src/ser.rs
@@ -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(&params).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(&params).unwrap(),
- urlencode("\
+ assert_eq!(qs::to_string(&params).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