summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Scott <sam@osohq.com>2022-07-13 10:30:29 -0500
committerSam Scott <sam@osohq.com>2022-07-13 10:30:29 -0500
commitc446c5f6d8853e63dca8e666bd502c8e5fa5337d (patch)
tree8e3411eacae71553307e8b509d3471d780483cee
parent0459c5e88e90e297ddb916ab96e62cdfaf7c3414 (diff)
Add a standalone serializer.
-rw-r--r--src/de/parse.rs2
-rw-r--r--src/lib.rs8
-rw-r--r--src/ser.rs300
-rw-r--r--src/utils.rs25
-rw-r--r--tests/test_serialize.rs34
5 files changed, 281 insertions, 88 deletions
diff --git a/src/de/parse.rs b/src/de/parse.rs
index 20ba962..f4d280a 100644
--- a/src/de/parse.rs
+++ b/src/de/parse.rs
@@ -1,4 +1,4 @@
-use crate::ser::{replace_space, QS_ENCODE_SET};
+use crate::utils::{replace_space, QS_ENCODE_SET};
use super::*;
diff --git a/src/lib.rs b/src/lib.rs
index 27a35c9..5da6284 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -203,8 +203,7 @@ serde_qs = { version = "0.9", features = ["actix4"] }
mod de;
mod error;
mod ser;
-#[cfg(feature = "warp")]
-pub mod warp;
+pub(crate) mod utils;
#[doc(inline)]
pub use de::Config;
@@ -212,7 +211,10 @@ pub use de::Config;
pub use de::{from_bytes, from_str};
pub use error::Error;
#[doc(inline)]
-pub use ser::{to_string, to_writer, QsSerializer};
+pub use ser::{to_string, to_writer, Serializer};
#[cfg(feature = "axum")]
pub mod axum;
+
+#[cfg(feature = "warp")]
+pub mod warp;
diff --git a/src/ser.rs b/src/ser.rs
index 2e1ea6e..b1e46d7 100644
--- a/src/ser.rs
+++ b/src/ser.rs
@@ -1,21 +1,18 @@
//! Serialization support for querystrings.
-use percent_encoding::{percent_encode, AsciiSet, NON_ALPHANUMERIC};
+use percent_encoding::percent_encode;
use serde::ser;
use crate::error::*;
+use crate::utils::*;
use std::borrow::Cow;
use std::fmt::Display;
use std::io::Write;
use std::str;
-
-pub const QS_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
- .remove(b' ')
- .remove(b'*')
- .remove(b'-')
- .remove(b'.')
- .remove(b'_');
+use std::sync::atomic::AtomicBool;
+use std::sync::atomic::Ordering;
+use std::sync::Arc;
/// Serializes a value into a querystring.
///
@@ -45,12 +42,7 @@ pub const QS_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
/// ```
pub fn to_string<T: ser::Serialize>(input: &T) -> Result<String> {
let mut buffer = Vec::new();
- let mut first = true;
- input.serialize(&mut QsSerializer {
- writer: &mut buffer,
- key: None,
- first: &mut first,
- })?;
+ input.serialize(&mut Serializer::new(&mut buffer))?;
String::from_utf8(buffer).map_err(Error::from)
}
@@ -82,12 +74,181 @@ pub fn to_string<T: ser::Serialize>(input: &T) -> Result<String> {
/// # }
/// ```
pub fn to_writer<T: ser::Serialize, W: Write>(input: &T, writer: &mut W) -> Result<()> {
- let mut first = true;
- input.serialize(&mut QsSerializer {
- writer,
- key: None,
- first: &mut first,
- })
+ input.serialize(&mut Serializer::new(writer))
+}
+
+pub struct Serializer<W: Write> {
+ writer: W,
+}
+
+impl<W: Write> Serializer<W> {
+ pub fn new(writer: W) -> Self {
+ Self { writer }
+ }
+
+ fn as_qs_serializer(&mut self) -> QsSerializer<W> {
+ QsSerializer {
+ writer: &mut self.writer,
+ first: Arc::new(AtomicBool::new(true)),
+ key: None,
+ }
+ }
+}
+
+macro_rules! serialize_as_string {
+ (Serializer $($ty:ty => $meth:ident,)*) => {
+ $(
+ fn $meth(self, v: $ty) -> Result<Self::Ok> {
+ let qs_serializer = self.as_qs_serializer();
+ qs_serializer.$meth(v)
+ }
+ )*
+ };
+ (Qs $($ty:ty => $meth:ident,)*) => {
+ $(
+ fn $meth(mut self, v: $ty) -> Result<Self::Ok> {
+ self.write_value(&v.to_string().as_bytes())
+ }
+ )*
+ };
+ ($($ty:ty => $meth:ident,)*) => {
+ $(
+ fn $meth(self, v: $ty) -> Result<Self::Ok> {
+ Ok(v.to_string())
+ }
+ )*
+ };
+}
+
+impl<'a, W: Write> ser::Serializer for &'a mut Serializer<W> {
+ type Ok = ();
+ type Error = Error;
+ 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 = QsSerializer<'a, W>;
+ type SerializeStructVariant = QsSerializer<'a, W>;
+
+ serialize_as_string! {
+ Serializer
+ bool => serialize_bool,
+ u8 => serialize_u8,
+ u16 => serialize_u16,
+ u32 => serialize_u32,
+ u64 => serialize_u64,
+ i8 => serialize_i8,
+ i16 => serialize_i16,
+ i32 => serialize_i32,
+ i64 => serialize_i64,
+ f32 => serialize_f32,
+ f64 => serialize_f64,
+ char => serialize_char,
+ &str => serialize_str,
+ }
+
+ fn serialize_bytes(self, value: &[u8]) -> Result<Self::Ok> {
+ self.as_qs_serializer().serialize_bytes(value)
+ }
+
+ fn serialize_unit(self) -> Result<Self::Ok> {
+ self.as_qs_serializer().serialize_unit()
+ }
+
+ /// Returns an error.
+ fn serialize_unit_struct(self, name: &'static str) -> Result<Self::Ok> {
+ self.as_qs_serializer().serialize_unit_struct(name)
+ }
+
+ /// Returns an error.
+ fn serialize_unit_variant(
+ self,
+ name: &'static str,
+ variant_index: u32,
+ variant: &'static str,
+ ) -> Result<Self::Ok> {
+ self.as_qs_serializer()
+ .serialize_unit_variant(name, variant_index, variant)
+ }
+
+ /// Returns an error.
+ fn serialize_newtype_struct<T: ?Sized + ser::Serialize>(
+ self,
+ name: &'static str,
+ value: &T,
+ ) -> Result<Self::Ok> {
+ self.as_qs_serializer()
+ .serialize_newtype_struct(name, value)
+ }
+
+ /// Returns an error.
+ fn serialize_newtype_variant<T: ?Sized + ser::Serialize>(
+ self,
+ name: &'static str,
+ variant_index: u32,
+ variant: &'static str,
+ value: &T,
+ ) -> Result<Self::Ok> {
+ self.as_qs_serializer()
+ .serialize_newtype_variant(name, variant_index, variant, value)
+ }
+
+ fn serialize_none(self) -> Result<Self::Ok> {
+ self.as_qs_serializer().serialize_none()
+ }
+
+ fn serialize_some<T: ?Sized + ser::Serialize>(self, value: &T) -> Result<Self::Ok> {
+ self.as_qs_serializer().serialize_some(value)
+ }
+
+ /// Returns an error.
+ fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
+ self.as_qs_serializer().serialize_seq(len)
+ }
+
+ fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
+ self.as_qs_serializer().serialize_tuple(len)
+ }
+
+ /// Returns an error.
+ fn serialize_tuple_struct(
+ self,
+ name: &'static str,
+ len: usize,
+ ) -> Result<Self::SerializeTupleStruct> {
+ self.as_qs_serializer().serialize_tuple_struct(name, len)
+ }
+
+ fn serialize_tuple_variant(
+ self,
+ name: &'static str,
+ variant_index: u32,
+ variant: &'static str,
+ len: usize,
+ ) -> Result<Self::SerializeTupleVariant> {
+ self.as_qs_serializer()
+ .serialize_tuple_variant(name, variant_index, variant, len)
+ }
+
+ fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap> {
+ self.as_qs_serializer().serialize_map(len)
+ }
+
+ fn serialize_struct(self, name: &'static str, len: usize) -> Result<Self::SerializeStruct> {
+ self.as_qs_serializer().serialize_struct(name, len)
+ }
+
+ fn serialize_struct_variant(
+ self,
+ name: &'static str,
+ variant_index: u32,
+ variant: &'static str,
+ len: usize,
+ ) -> Result<Self::SerializeStructVariant> {
+ self.as_qs_serializer()
+ .serialize_struct_variant(name, variant_index, variant, len)
+ }
}
/// A serializer for the querystring format.
@@ -98,26 +259,11 @@ pub fn to_writer<T: ser::Serialize, W: Write>(input: &T, writer: &mut W) -> Resu
/// sequences. Sequences are serialized with an incrementing key index.
///
/// * Newtype structs defer to their inner values.
+#[doc(hidden)]
pub struct QsSerializer<'a, W: 'a + Write> {
key: Option<Cow<'static, str>>,
writer: &'a mut W,
- first: &'a mut bool,
-}
-
-pub 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"))
- }
- }
+ first: Arc<AtomicBool>,
}
impl<'a, W: 'a + Write> QsSerializer<'a, W> {
@@ -138,8 +284,7 @@ impl<'a, W: 'a + Write> QsSerializer<'a, W> {
write!(
self.writer,
"{}{}={}",
- if *self.first {
- *self.first = false;
+ if self.first.swap(false, Ordering::Relaxed) {
""
} else {
"&"
@@ -161,7 +306,7 @@ impl<'a, W: 'a + Write> QsSerializer<'a, W> {
Self {
key: other.key.clone(),
writer: other.writer,
- first: other.first,
+ first: other.first.clone(),
}
}
}
@@ -173,24 +318,7 @@ impl Error {
}
}
-macro_rules! serialize_as_string {
- (Qs $($ty:ty => $meth:ident,)*) => {
- $(
- fn $meth(self, v: $ty) -> Result<Self::Ok> {
- self.write_value(&v.to_string().as_bytes())
- }
- )*
- };
- ($($ty:ty => $meth:ident,)*) => {
- $(
- fn $meth(self, v: $ty) -> Result<Self::Ok> {
- Ok(v.to_string())
- }
- )*
- };
-}
-
-impl<'a, W: Write> ser::Serializer for &'a mut QsSerializer<'a, W> {
+impl<'a, W: Write> ser::Serializer for QsSerializer<'a, W> {
type Ok = ();
type Error = Error;
type SerializeSeq = QsSeq<'a, W>;
@@ -218,22 +346,22 @@ impl<'a, W: Write> ser::Serializer for &'a mut QsSerializer<'a, W> {
&str => serialize_str,
}
- fn serialize_bytes(self, value: &[u8]) -> Result<Self::Ok> {
+ fn serialize_bytes(mut self, value: &[u8]) -> Result<Self::Ok> {
self.write_value(value)
}
- fn serialize_unit(self) -> Result<Self::Ok> {
+ fn serialize_unit(mut self) -> Result<Self::Ok> {
self.write_value(&[])
}
/// Returns an error.
- fn serialize_unit_struct(self, name: &'static str) -> Result<Self::Ok> {
+ fn serialize_unit_struct(mut self, name: &'static str) -> Result<Self::Ok> {
self.write_value(name.as_bytes())
}
/// Returns an error.
fn serialize_unit_variant(
- self,
+ mut self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
@@ -252,7 +380,7 @@ impl<'a, W: Write> ser::Serializer for &'a mut QsSerializer<'a, W> {
/// Returns an error.
fn serialize_newtype_variant<T: ?Sized + ser::Serialize>(
- self,
+ mut self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
@@ -290,7 +418,7 @@ impl<'a, W: Write> ser::Serializer for &'a mut QsSerializer<'a, W> {
}
fn serialize_tuple_variant(
- self,
+ mut self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
@@ -310,7 +438,7 @@ impl<'a, W: Write> ser::Serializer for &'a mut QsSerializer<'a, W> {
}
fn serialize_struct_variant(
- self,
+ mut self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
@@ -330,8 +458,11 @@ impl ser::Error for Error {
}
}
-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>>);
+#[doc(hidden)]
+pub struct QsSeq<'a, W: 'a + Write>(QsSerializer<'a, W>, usize);
+
+#[doc(hidden)]
+pub struct QsMap<'a, W: 'a + Write>(QsSerializer<'a, W>, Option<Cow<'a, str>>);
impl<'a, W: Write> ser::SerializeTuple for QsSeq<'a, W> {
type Ok = ();
@@ -340,10 +471,11 @@ impl<'a, W: Write> ser::SerializeTuple for QsSeq<'a, W> {
where
T: ser::Serialize,
{
- let mut serializer = QsSerializer::new_from_ref(self.0);
- serializer.extend_key(&self.1.to_string());
+ let key = self.1.to_string();
self.1 += 1;
- value.serialize(&mut serializer)
+ let mut serializer = QsSerializer::new_from_ref(&mut self.0);
+ serializer.extend_key(&key);
+ value.serialize(serializer)
}
fn end(self) -> Result<Self::Ok> {
@@ -358,17 +490,17 @@ impl<'a, W: Write> ser::SerializeSeq for QsSeq<'a, W> {
where
T: ser::Serialize,
{
- let mut serializer = QsSerializer::new_from_ref(self.0);
+ let mut serializer = QsSerializer::new_from_ref(&mut self.0);
serializer.extend_key(&self.1.to_string());
self.1 += 1;
- value.serialize(&mut serializer)
+ value.serialize(serializer)
}
fn end(self) -> Result<Self::Ok> {
Ok(())
}
}
-impl<'a, W: Write> ser::SerializeStruct for &'a mut QsSerializer<'a, W> {
+impl<'a, W: Write> ser::SerializeStruct for QsSerializer<'a, W> {
type Ok = ();
type Error = Error;
fn serialize_field<T: ?Sized>(&mut self, key: &'static str, value: &T) -> Result<()>
@@ -377,14 +509,14 @@ impl<'a, W: Write> ser::SerializeStruct for &'a mut QsSerializer<'a, W> {
{
let mut serializer = QsSerializer::new_from_ref(self);
serializer.extend_key(key);
- value.serialize(&mut serializer)
+ value.serialize(serializer)
}
fn end(self) -> Result<Self::Ok> {
Ok(())
}
}
-impl<'a, W: Write> ser::SerializeStructVariant for &'a mut QsSerializer<'a, W> {
+impl<'a, W: Write> ser::SerializeStructVariant for QsSerializer<'a, W> {
type Ok = ();
type Error = Error;
@@ -394,7 +526,7 @@ impl<'a, W: Write> ser::SerializeStructVariant for &'a mut QsSerializer<'a, W> {
{
let mut serializer = QsSerializer::new_from_ref(self);
serializer.extend_key(key);
- value.serialize(&mut serializer)
+ value.serialize(serializer)
}
fn end(self) -> Result<Self::Ok> {
@@ -410,10 +542,10 @@ impl<'a, W: Write> ser::SerializeTupleVariant for QsSeq<'a, W> {
where
T: ser::Serialize,
{
- let mut serializer = QsSerializer::new_from_ref(self.0);
+ let mut serializer = QsSerializer::new_from_ref(&mut self.0);
serializer.extend_key(&self.1.to_string());
self.1 += 1;
- value.serialize(&mut serializer)
+ value.serialize(serializer)
}
fn end(self) -> Result<Self::Ok> {
@@ -429,10 +561,10 @@ impl<'a, W: Write> ser::SerializeTupleStruct for QsSeq<'a, W> {
where
T: ser::Serialize,
{
- let mut serializer = QsSerializer::new_from_ref(self.0);
+ let mut serializer = QsSerializer::new_from_ref(&mut self.0);
serializer.extend_key(&self.1.to_string());
self.1 += 1;
- value.serialize(&mut serializer)
+ value.serialize(serializer)
}
fn end(self) -> Result<Self::Ok> {
@@ -456,14 +588,14 @@ impl<'a, W: Write> ser::SerializeMap for QsMap<'a, W> {
where
T: ser::Serialize,
{
- let mut serializer = QsSerializer::new_from_ref(self.0);
+ let mut serializer = QsSerializer::new_from_ref(&mut self.0);
if let Some(ref key) = self.1 {
serializer.extend_key(key);
} else {
return Err(Error::no_key());
}
self.1 = None;
- value.serialize(&mut serializer)
+ value.serialize(serializer)
}
fn end(self) -> Result<Self::Ok> {
@@ -475,9 +607,9 @@ impl<'a, W: Write> ser::SerializeMap for QsMap<'a, W> {
K: ser::Serialize,
V: ser::Serialize,
{
- let mut serializer = QsSerializer::new_from_ref(self.0);
+ let mut serializer = QsSerializer::new_from_ref(&mut self.0);
serializer.extend_key(&key.serialize(StringSerializer)?);
- value.serialize(&mut serializer)
+ value.serialize(serializer)
}
}
diff --git a/src/utils.rs b/src/utils.rs
new file mode 100644
index 0000000..f8c1191
--- /dev/null
+++ b/src/utils.rs
@@ -0,0 +1,25 @@
+use percent_encoding::{AsciiSet, NON_ALPHANUMERIC};
+use std::borrow::Cow;
+
+pub const QS_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
+ .remove(b' ')
+ .remove(b'*')
+ .remove(b'-')
+ .remove(b'.')
+ .remove(b'_');
+
+pub 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"))
+ }
+ }
+}
diff --git a/tests/test_serialize.rs b/tests/test_serialize.rs
index ddb7277..b7c4a21 100644
--- a/tests/test_serialize.rs
+++ b/tests/test_serialize.rs
@@ -202,3 +202,37 @@ fn serialize_hashmap_keys() {
|| s == "attrs[key+2%21]=val+2&attrs[key+1%21]=val+1"
);
}
+
+#[test]
+fn test_serializer() {
+ use serde::Serialize;
+ #[derive(Serialize, Debug, Clone)]
+ struct Query {
+ a: Vec<u8>,
+ b: &'static str,
+ }
+
+ let mut writer = Vec::new();
+ {
+ let serializer = &mut qs::Serializer::new(&mut writer);
+ let q = Query {
+ a: vec![0, 1],
+ b: "b",
+ };
+ q.serialize(serializer).unwrap();
+ }
+
+ assert_eq!(writer, b"a[0]=0&a[1]=1&b=b");
+ writer.clear();
+
+ {
+ let serializer = &mut qs::Serializer::new(&mut writer);
+ let q = Query {
+ a: vec![3, 2],
+ b: "a",
+ };
+ q.serialize(serializer).unwrap();
+ }
+
+ assert_eq!(writer, b"a[0]=3&a[1]=2&b=a");
+}