extern crate csv; extern crate rand; extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_qs as qs; extern crate serde_urlencoded as urlencoded; use rand::Rng; use std::collections::HashMap; #[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, phone: u32, user_ids: Vec, } fn main() { // Encodes as: // "user_ids%5B3%5D=4&user_ids%5B2%5D=3&address%5Bcity%5D=Carrot+City&\ // id=42&address%5Bpostcode%5D=12345&name=Acme&user_ids%5B0%5D=1&\ // phone=12345&user_ids%5B1%5D=2" let example_params = QueryParams { id: 42, name: "Acme".to_string(), phone: 12345, address: Address { city: "Carrot City".to_string(), postcode: "12345".to_string(), }, user_ids: vec![1, 2, 3, 4], }; // Naive approach: manually parameters in a map. Painful. let mut map = HashMap::<&str, &str>::new(); map.insert("id", "42"); map.insert("name", "Acme"); map.insert("phone", "12345"); map.insert("address[city]", "Carrot City"); map.insert("address[postcode]", "12345"); map.insert("user_ids[0]", "1"); map.insert("user_ids[1]", "2"); map.insert("user_ids[2]", "3"); map.insert("user_ids[3]", "4"); // Note this will be in some random order due to ordering of keys in map. let encoded = qs::to_string(&map).unwrap(); println!("`serde_qs` to_string for map:\n\t{}", encoded); // In this form, can also simply use `serde_urlencoded`: let encoded = urlencoded::to_string(&map).unwrap(); println!("`serde_urlencoded` to_string for map:\n\t{}", encoded); // Given this encoded string, you can recover the original map // as a list of pairs using serde_urlencoded: let pairs: Vec<(String, String)> = urlencoded::from_str(&encoded).unwrap(); println!("`serde_urlencoded` from_str to pairs:\n\t{:?}", pairs); // However, the best way is to use serde_qs to deserialize the entire thing // into a struct: let params: QueryParams = qs::from_str(&encoded).unwrap(); assert_eq!(params, example_params); println!("`serde_qs` from_str to struct:\n\t{:?}", params); // Similarly, we can serialize this structure using `serde_qs`: let encoded = qs::to_string(¶ms).unwrap(); println!("`serde_qs` to_string for struct:\n\t{:?}", encoded); // One nice feature is that this gives deterministic encodings: let encoded2 = qs::to_string(¶ms).unwrap(); assert_eq!(encoded, encoded2); // An advantage of `serde_qs` for deserializing, is that it is robust // against different orderings of inputs: let mut inputs = vec!["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"]; let mut rng = rand::thread_rng(); for _ in 0..10 { let mut acc = String::new(); rng.shuffle(&mut inputs); for input in &inputs { acc += input; acc += "&"; } // remove trailing character acc.pop(); let params: QueryParams = qs::from_str(&acc).unwrap(); assert_eq!(params, example_params); } // By default, `serde_qs` uses arrays with indices to denote position. // However, if omitted, will use input order: let encoded = "id=42&name=Acme&phone=12345&address[city]=Carrot+City&\ address[postcode]=12345&\ user_ids[]=1&\ user_ids[]=2&\ user_ids[]=3&\ user_ids[]=4"; let params: QueryParams = qs::from_str(encoded).unwrap(); assert_eq!(params, example_params); // Indices do not necessarily need to be continuous: let encoded = "id=42&name=Acme&phone=12345&address[city]=Carrot+City&\ address[postcode]=12345&\ user_ids[1]=2&\ user_ids[0]=1&\ user_ids[12]=3&\ user_ids[512]=4"; let params: QueryParams = qs::from_str(encoded).unwrap(); assert_eq!(params, example_params); // Enums are supported, but only adjacently tagged enums // (see https://serde.rs/enum-representations.html for more information). #[derive(Deserialize, Debug, PartialEq, Serialize)] // #[serde(tag = "type", content = "value")] enum AdjTaggedEnum { B(bool), S(String), V { id: u8, v: String }, } #[derive(Deserialize, Debug, PartialEq, Serialize)] struct EnumQuery { e: AdjTaggedEnum, } let example_params = EnumQuery { e: AdjTaggedEnum::B(false), }; // encodes as: // "e[type]=B&e[value]=false" let encoded = qs::to_string(&example_params).unwrap(); println!("`serde_qs` to_string for enum:\n\t{:?}", encoded); let params: EnumQuery = qs::from_str(&encoded).unwrap(); println!("`serde_qs` from_str for enum:\n\t{:?}", params); }