Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialize vectors without indices #115

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,9 @@ pub use de::{from_bytes, from_str};
pub use de::{Config, QsDeserializer as Deserializer};
pub use error::Error;
#[doc(inline)]
pub use ser::{to_string, to_writer, Serializer};
pub use ser::Config as SerializerConfig;
#[doc(inline)]
pub use ser::{to_string, to_string_config, to_writer, Serializer};

#[cfg(feature = "axum")]
pub mod axum;
Expand Down
116 changes: 113 additions & 3 deletions src/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,64 @@ use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;

/// To override the default serialization parameters, first construct a new
/// Config.
///
/// The `use_indices` parameter controls whether the serializer will use
/// indices for vectors or not. If `use_indices` is `true`, the serializer will
/// use indices for vectors, e.g. `a[0]=1&a[1]=2`. If `use_indices` is `false`,
/// the serializer will not use indices for vectors, e.g. `a[]=1&a[]=2`.
///
/// The default value for `use_indices` is true.
///
/// ```
/// use serde::Serialize;
/// use serde_qs::SerializerConfig;
///
/// let config = SerializerConfig { use_indices: false };
///
/// #[derive(Serialize)]
/// struct QueryParams {
/// id: u32,
/// user_ids: Vec<u32>,
/// }
///
/// let params = QueryParams {
/// id: 42,
/// user_ids: vec![1, 2, 3, 4],
/// };
///
/// assert_eq!(
/// serde_qs::to_string_config(&params, config).unwrap(),
/// "\
/// id=42&user_ids[]=1&user_ids[]=2&\
/// user_ids[]=3&user_ids[]=4"
/// );
///
/// let config = SerializerConfig { use_indices: true };
///
/// assert_eq!(
/// serde_qs::to_string_config(&params, config).unwrap(),
/// "\
/// id=42&user_ids[0]=1&user_ids[1]=2&\
/// user_ids[2]=3&user_ids[3]=4"
/// );
/// ```
///
#[derive(Clone, Copy)]
pub struct Config {
/// Specifies whether to use indices for vectors or not.
pub use_indices: bool,
}

pub const DEFAULT_CONFIG: Config = Config { use_indices: true };

impl Default for Config {
fn default() -> Self {
DEFAULT_CONFIG
}
}

/// Serializes a value into a querystring.
///
/// ```
Expand Down Expand Up @@ -41,8 +99,41 @@ use std::sync::Arc;
/// # }
/// ```
pub fn to_string<T: ser::Serialize>(input: &T) -> Result<String> {
to_string_config(input, Config::default())
}

/// Serializes a value into a querystring with a custom configuration.
///
/// ```
/// # #[macro_use]
/// # extern crate serde_derive;
/// # extern crate serde_qs;
/// #[derive(Deserialize, Serialize)]
/// struct Query {
/// name: String,
/// age: u8,
/// occupation: String,
/// degrees: Vec<String>,
/// }
///
/// # fn main(){
/// let q = Query {
/// name: "Alice".to_owned(),
/// age: 24,
/// occupation: "Student".to_owned(),
/// degrees: vec!["BSc".to_owned(), "MSc".to_owned()],
/// };
///
/// let config = serde_qs::SerializerConfig { use_indices: false };
///
/// assert_eq!(
/// serde_qs::to_string_config(&q, config).unwrap(),
/// "name=Alice&age=24&occupation=Student&degrees[]=BSc&degrees[]=MSc");
/// # }
/// ```
pub fn to_string_config<T: ser::Serialize>(input: &T, config: Config) -> Result<String> {
let mut buffer = Vec::new();
input.serialize(&mut Serializer::new(&mut buffer))?;
input.serialize(&mut Serializer::with_config(&mut buffer, config))?;
String::from_utf8(buffer).map_err(Error::from)
}

Expand Down Expand Up @@ -79,18 +170,27 @@ pub fn to_writer<T: ser::Serialize, W: Write>(input: &T, writer: &mut W) -> Resu

pub struct Serializer<W: Write> {
writer: W,
config: Config,
}

impl<W: Write> Serializer<W> {
pub fn new(writer: W) -> Self {
Self { writer }
Self {
writer,
config: Config::default(),
}
}

pub fn with_config(writer: W, config: Config) -> Self {
Self { writer, config }
}

fn as_qs_serializer(&mut self) -> QsSerializer<W> {
QsSerializer {
writer: &mut self.writer,
first: Arc::new(AtomicBool::new(true)),
key: None,
config: self.config,
}
}
}
Expand Down Expand Up @@ -258,6 +358,7 @@ pub struct QsSerializer<'a, W: 'a + Write> {
key: Option<Cow<'static, str>>,
writer: &'a mut W,
first: Arc<AtomicBool>,
config: Config,
}

impl<'a, W: 'a + Write> QsSerializer<'a, W> {
Expand All @@ -266,7 +367,15 @@ impl<'a, W: 'a + Write> QsSerializer<'a, W> {
.map(replace_space)
.collect::<String>();
let key = if let Some(ref key) = self.key {
format!("{}[{}]", key, newkey)
if newkey.parse::<usize>().is_ok() {
if self.config.use_indices {
format!("{}[{}]", key, newkey)
} else {
format!("{}[]", key)
}
} else {
format!("{}[{}]", key, newkey)
}
} else {
newkey
};
Expand Down Expand Up @@ -309,6 +418,7 @@ impl<'a, W: 'a + Write> QsSerializer<'a, W> {
key: other.key.clone(),
writer: other.writer,
first: other.first.clone(),
config: other.config,
}
}
}
Expand Down
26 changes: 26 additions & 0 deletions tests/test_serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,32 @@ fn serialize_struct() {
);
}

#[test]
fn serialize_struct_without_indices() {
let params = QueryParams {
id: 42,
name: "Acme".to_string(),
phone: 12345,
address: Address {
city: "Carrot City".to_string(),
street: "Special-Street* No. 11".to_string(),
postcode: "12345".to_string(),
},
user_ids: vec![1, 2, 3, 4],
};

let config = qs::SerializerConfig { use_indices: false };

assert_eq!(
qs::to_string_config(&params, config).unwrap(),
"\
id=42&name=Acme&phone=12345&address[city]=Carrot+City&\
address[street]=Special-Street*+No.+11&\
address[postcode]=12345&user_ids[]=1&user_ids[]=2&\
user_ids[]=3&user_ids[]=4"
);
}

#[test]
fn serialize_option() {
#[derive(Debug, Serialize, Deserialize, PartialEq)]
Expand Down