pax_global_header00006660000000000000000000000064146045726450014527gustar00rootroot0000000000000052 comment=c6e5914a31e0d602695a3ea601f6976a1ab07d0e serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/000077500000000000000000000000001460457264500202405ustar00rootroot00000000000000serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/.github/000077500000000000000000000000001460457264500216005ustar00rootroot00000000000000serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/.github/workflows/000077500000000000000000000000001460457264500236355ustar00rootroot00000000000000serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/.github/workflows/ci.yml000066400000000000000000000030671460457264500247610ustar00rootroot00000000000000name: Rust CI checks on: push: jobs: lint: name: Run lint checks runs-on: ubuntu-latest strategy: matrix: rust: - stable - 1.36.0 steps: - uses: actions/checkout@v2 - name: Install Rust stable toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true components: rustfmt, clippy - name: Check Rust formatting uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check - name: Check clippy uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} args: --all-targets -- -D warnings test: name: Run tests runs-on: ubuntu-latest strategy: matrix: rust: - stable - 1.36.0 feature: - "" - actix4 - actix3 - warp - axum steps: - uses: actions/checkout@v2 - uses: actions/cache@v2 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-cargo-test-${{ hashFiles('Cargo.toml') }} - name: Install Rust stable toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable - name: Run test ${{ matrix.feature }} run: | cargo test --all-targets --features "${{ matrix.feature }}" cargo test --doc --features "${{ matrix.feature }}" serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/.gitignore000066400000000000000000000000221460457264500222220ustar00rootroot00000000000000target Cargo.lock serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/CHANGELOG.md000066400000000000000000000002371460457264500220530ustar00rootroot00000000000000# Changelog ## Version 0.13.0 - Bump `axum` support to 0.7 - Remove support for `actix-web 2.0` - Add support for extracting form data in actix via `QsForm` serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/Cargo.toml000066400000000000000000000027151460457264500221750ustar00rootroot00000000000000[package] authors = ["Sam Scott "] edition = "2018" categories = ["encoding", "web-programming"] description = "Querystrings for Serde" documentation = "https://docs.rs/serde_qs" keywords = ["serde", "serialization", "querystring"] license = "MIT/Apache-2.0" name = "serde_qs" repository = "https://github.com/samscott89/serde_qs" readme = "README.md" version = "0.13.0" rust-version = "1.36" [dependencies] actix-web4 = { version = "4.0", optional = true, package = "actix-web", default-features = false } actix-web3 = { version = "3.3", optional = true, package = "actix-web", default-features = false } futures = { version = "0.3", optional = true } percent-encoding = "2.1" serde = "1.0" thiserror = "1.0" tracing = { version = "0.1", optional = true } warp-framework = { package = "warp", version = "0.3", default-features = false, optional = true } axum-framework = { package = "axum", version = "0.7", default-features = false, optional = true } [dev-dependencies] chrono = { version = "0.4", features = ["serde"] } csv = "1.1" rand = "0.8" serde_derive = "1.0" serde_urlencoded = "0.7" serde_with = "2.0" [features] default = [] actix4 = ["actix-web4", "futures"] actix3 = ["actix-web3", "futures"] # deprecated feature -- used to return a warning actix2 = [] actix = [] warp = ["futures", "tracing", "warp-framework"] axum = ["axum-framework", "futures"] [package.metadata.docs.rs] features = ["actix4", "warp"] [[example]] name = "csv_vectors" test = trueserde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/LICENSE-APACHE000066400000000000000000000227731460457264500221770ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/LICENSE-MIT000066400000000000000000000021361460457264500216760ustar00rootroot00000000000000Copyright (c) 2016 Anthony Ramine Copyright (c) 2017 Sam Scott Copyright (c) 2017 Google Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/README.md000066400000000000000000000044411460457264500215220ustar00rootroot00000000000000# Serde Querystrings [![badge-ci]][badge-ci-link] [![Latest Version]][crates.io] [![Documentation]][docs-rs] [badge-ci]: https://github.com/samscott89/serde_qs/workflows/Rust%20CI%20checks/badge.svg [badge-ci-link]: https://github.com/samscott89/serde_qs/actions?query=workflow%3A%22Rust+CI+checks%22+branch%3Amain [Latest Version]: https://img.shields.io/crates/v/serde_qs.svg [crates.io]: https://crates.io/crates/serde\_qs [Documentation]: https://docs.rs/serde_qs/badge.svg [docs-rs]: https://docs.rs/serde_qs/ This crate is a Rust library for serialising to and deserialising from querystrings. This crate is designed to extend [`serde_urlencoded`][urlencoded] when using nested parameters, similar to those used by [qs][qs] for Node, and commonly used by Ruby on Rails via [Rack][Rack]. The core of the library was inspired by [`serde_urlencoded`][urlencoded]. In order to support abitrarily nested structs encoded in arbitrary orders, we perform two passes over the input string. This likely adds a non-trivial amount of memory and compute. Due to this `serde_urlencoded` should be preferred over this crate whenever non-nested query parameters are sufficient. The crate is built upon [Serde], a high performance generic serialization framework and [rust-url], a URL parser for Rust. [rust-url]: https://github.com/servo/rust-url [Serde]: https://github.com/serde-rs/serde [urlencoded]: https://github.com/nox/serde_urlencoded [qs]: https://www.npmjs.com/package/qs [Rack]: http://www.rubydoc.info/github/rack/rack/Rack/Utils#parse_nested_query-class_method Installation ============ This crate works with Cargo and can be found on [crates.io] with a `Cargo.toml` like: ```toml [dependencies] serde_qs = "0.12" ``` Minimum supported Rust version is 1.36. [crates.io]: https://crates.io/crates/serde_qs ## License serde_qs is licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in serde_qs by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/examples/000077500000000000000000000000001460457264500220565ustar00rootroot00000000000000serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/examples/csv_vectors.rs000066400000000000000000000052121460457264500247640ustar00rootroot00000000000000extern crate csv; extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_qs as qs; use serde::de::DeserializeOwned; use std::default::Default; #[derive(Debug, PartialEq, Deserialize, Serialize)] struct Query { #[serde(deserialize_with = "from_csv")] r: Vec, s: u8, } fn main() { let q = "s=12&r=1,2,3"; let q: Query = qs::from_str(q).unwrap(); println!("{:?}", q); } #[test] fn deserialize_sequence() { let q = "s=12&r=1,2,3"; let q: Query = qs::from_str(q).unwrap(); let expected = Query { r: vec![1, 2, 3], s: 12, }; assert_eq!(q, expected); } fn from_csv<'de, D, T>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, T: DeserializeOwned + std::str::FromStr, ::Err: std::fmt::Debug, { deserializer.deserialize_str(CSVVecVisitor::::default()) } /// Visits a string value of the form "v1,v2,v3" into a vector of bytes Vec struct CSVVecVisitor(std::marker::PhantomData); impl Default for CSVVecVisitor { fn default() -> Self { CSVVecVisitor(std::marker::PhantomData) } } impl<'de, T: DeserializeOwned + std::str::FromStr> serde::de::Visitor<'de> for CSVVecVisitor where ::Err: std::fmt::Debug, // handle the parse error in a generic way { type Value = Vec; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { write!(formatter, "a str") } fn visit_str(self, s: &str) -> std::result::Result where E: serde::de::Error, { // Treat the comma-separated string as a single record in a CSV. let mut rdr = csv::ReaderBuilder::new() .has_headers(false) .from_reader(s.as_bytes()); // Try to get the record and collect its values into a vector. let mut output = Vec::new(); for result in rdr.records() { match result { Ok(record) => { for field in record.iter() { output.push( field .parse::() .map_err(|_| E::custom("Failed to parse field"))?, ); } } Err(e) => { return Err(E::custom(format!( "could not deserialize sequence value: {:?}", e ))); } } } Ok(output) } } serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/examples/introduction.rs000066400000000000000000000133121460457264500251450ustar00rootroot00000000000000extern crate rand; #[macro_use] extern crate serde_derive; extern crate serde_qs as qs; extern crate serde_urlencoded as urlencoded; use rand::seq::SliceRandom; use std::collections::HashMap; use qs::Config; #[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); println!(); // 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: // // (For this round trip to work, it's necessary to parse the query string // in non-strict mode, to allow parsing of url_encoded square brackets // in the key. See the lib.rs documentation for why). let qs_non_strict = Config::new(5, false); let params: QueryParams = qs_non_strict.deserialize_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); println!(); // 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(); inputs.shuffle(&mut rng); 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 now fully supported! Most formats should work with varying // results. #[derive(Deserialize, Debug, PartialEq, Serialize)] enum AdjTaggedEnum { A, 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[B]=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); println!(); let example_params = EnumQuery { e: AdjTaggedEnum::A, }; // encodes as: // "e=A" 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); println!(); } serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/src/000077500000000000000000000000001460457264500210275ustar00rootroot00000000000000serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/src/actix.rs000066400000000000000000000200001460457264500224750ustar00rootroot00000000000000//! Functionality for using `serde_qs` with `actix_web`. //! //! Enable with the `actix4`, `actix3` or `actix2` features. use crate::de::Config as QsConfig; use crate::error::Error as QsError; #[cfg(feature = "actix3")] use actix_web3 as actix_web; #[cfg(feature = "actix4")] use actix_web4 as actix_web; use actix_web::dev::Payload; #[cfg(feature = "actix3")] use actix_web::HttpResponse; use actix_web::{web, Error as ActixError, FromRequest, HttpRequest, ResponseError}; use futures::future::{ready, FutureExt, LocalBoxFuture, Ready}; use futures::StreamExt; use serde::de; use serde::de::DeserializeOwned; use std::fmt; use std::fmt::{Debug, Display}; use std::ops::{Deref, DerefMut}; use std::sync::Arc; #[cfg(feature = "actix3")] impl ResponseError for QsError { fn error_response(&self) -> HttpResponse { HttpResponse::BadRequest().finish() } } #[cfg(feature = "actix4")] impl ResponseError for QsError { fn status_code(&self) -> actix_web::http::StatusCode { actix_web::http::StatusCode::BAD_REQUEST } } #[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from from the request's query. /// /// ## Example /// /// ```rust /// # #[macro_use] extern crate serde_derive; /// # #[cfg(feature = "actix4")] /// # use actix_web4 as actix_web; /// # #[cfg(feature = "actix3")] /// # use actix_web3 as actix_web; /// use actix_web::{web, App, HttpResponse}; /// use serde_qs::actix::QsQuery; /// /// #[derive(Deserialize)] /// pub struct UsersFilter { /// id: Vec, /// } /// /// // Use `QsQuery` extractor for query information. /// // The correct request for this handler would be `/users?id[]=1124&id[]=88"` /// async fn filter_users(info: QsQuery) -> HttpResponse { /// HttpResponse::Ok().body( /// info.id.iter().map(|i| i.to_string()).collect::>().join(", ") /// ) /// } /// /// fn main() { /// let app = App::new().service( /// web::resource("/users") /// .route(web::get().to(filter_users))); /// } /// ``` pub struct QsQuery(T); impl QsQuery { /// Unwrap into inner T value pub fn into_inner(self) -> T { self.0 } } impl Deref for QsQuery { type Target = T; fn deref(&self) -> &T { &self.0 } } impl DerefMut for QsQuery { fn deref_mut(&mut self) -> &mut T { &mut self.0 } } impl Debug for QsQuery { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) } } impl Display for QsQuery { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) } } impl FromRequest for QsQuery where T: de::DeserializeOwned, { type Error = ActixError; type Future = Ready>; #[cfg(feature = "actix3")] type Config = QsQueryConfig; #[inline] fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { let query_config = req.app_data::().unwrap_or(&DEFAULT_CONFIG); let res = query_config .qs_config .deserialize_str::(req.query_string()) .map(|val| Ok(QsQuery(val))) .unwrap_or_else(move |e| { let e = if let Some(error_handler) = &query_config.ehandler { (error_handler)(e, req) } else { e.into() }; Err(e) }); ready(res) } } /// Query extractor configuration /// /// ```rust /// # #[macro_use] extern crate serde_derive; /// # #[cfg(feature = "actix4")] /// # use actix_web4 as actix_web; /// # #[cfg(feature = "actix3")] /// # use actix_web3 as actix_web; /// use actix_web::{error, web, App, FromRequest, HttpResponse}; /// use serde_qs::actix::QsQuery; /// use serde_qs::Config as QsConfig; /// use serde_qs::actix::QsQueryConfig; /// /// #[derive(Deserialize)] /// struct Info { /// username: String, /// } /// /// /// deserialize `Info` from request's querystring /// async fn index(info: QsQuery) -> HttpResponse { /// HttpResponse::Ok().body( /// format!("Welcome {}!", info.username) /// ) /// } /// /// fn main() { /// let qs_config = QsQueryConfig::default() /// .error_handler(|err, req| { // <- create custom error response /// error::InternalError::from_response( /// err, HttpResponse::Conflict().finish()).into() /// }) /// .qs_config(QsConfig::default()); /// /// let app = App::new().service( /// web::resource("/index.html").app_data(qs_config) /// .route(web::post().to(index)) /// ); /// } /// ``` #[derive(Clone)] pub struct QsQueryConfig { ehandler: Option ActixError + Send + Sync>>, qs_config: QsConfig, } static DEFAULT_CONFIG: QsQueryConfig = QsQueryConfig { ehandler: None, qs_config: crate::de::DEFAULT_CONFIG, }; impl QsQueryConfig { /// Set custom error handler pub fn error_handler(mut self, f: F) -> Self where F: Fn(QsError, &HttpRequest) -> ActixError + Send + Sync + 'static, { self.ehandler = Some(Arc::new(f)); self } /// Set custom serialization parameters pub fn qs_config(mut self, config: QsConfig) -> Self { self.qs_config = config; self } } impl Default for QsQueryConfig { fn default() -> Self { QsQueryConfig { ehandler: None, qs_config: QsConfig::default(), } } } #[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from from the request's form data. /// /// ## Example /// /// ```rust /// # #[macro_use] extern crate serde_derive; /// # #[cfg(feature = "actix4")] /// # use actix_web4 as actix_web; /// # #[cfg(feature = "actix3")] /// # use actix_web3 as actix_web; /// use actix_web::{web, App, HttpResponse}; /// use serde_qs::actix::QsForm; /// /// #[derive(Debug, Deserialize)] /// pub struct UsersFilter { /// id: Vec, /// } /// /// // Use `QsForm` extractor for Form information. /// // Content-Type: application/x-www-form-urlencoded /// // The correct request payload for this handler would be `id[]=1124&id[]=88` /// async fn filter_users(info: QsForm) -> HttpResponse { /// HttpResponse::Ok().body( /// info.id.iter().map(|i| i.to_string()).collect::>().join(", ") /// ) /// } /// /// fn main() { /// let app = App::new().service( /// web::resource("/users") /// .route(web::get().to(filter_users))); /// } /// ``` #[derive(Debug)] pub struct QsForm(T); impl QsForm { /// Unwrap into inner T value pub fn into_inner(self) -> T { self.0 } } impl Deref for QsForm { type Target = T; fn deref(&self) -> &T { &self.0 } } impl DerefMut for QsForm { fn deref_mut(&mut self) -> &mut T { &mut self.0 } } impl FromRequest for QsForm where T: DeserializeOwned + Debug, { type Error = ActixError; type Future = LocalBoxFuture<'static, Result>; #[cfg(feature = "actix3")] type Config = QsQueryConfig; fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { let mut stream = payload.take(); let req_clone = req.clone(); let query_config: QsQueryConfig = req .app_data::() .unwrap_or(&DEFAULT_CONFIG) .clone(); async move { let mut bytes = web::BytesMut::new(); while let Some(item) = stream.next().await { bytes.extend_from_slice(&item.unwrap()); } query_config .qs_config .deserialize_bytes::(&bytes) .map(|val| Ok(QsForm(val))) .unwrap_or_else(|e| { let e = if let Some(error_handler) = &query_config.ehandler { (error_handler)(e, &req_clone) } else { e.into() }; Err(e) }) } .boxed_local() } } serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/src/axum.rs000066400000000000000000000131731460457264500223540ustar00rootroot00000000000000//! Functionality for using `serde_qs` with `axum`. //! //! Enable with the `axum` feature. use axum_framework as axum; use std::sync::Arc; use crate::de::Config as QsConfig; use crate::error::Error as QsError; use axum::{ extract::{Extension, FromRequestParts}, http::StatusCode, response::{IntoResponse, Response}, BoxError, Error, }; #[derive(Clone, Copy, Default)] /// Extract typed information from from the request's query. /// /// ## Example /// /// ```rust /// # extern crate axum_framework as axum; /// use serde_qs::axum::QsQuery; /// use serde_qs::Config; /// use axum::{response::IntoResponse, routing::get, Router, body::Body}; /// /// #[derive(serde::Deserialize)] /// pub struct UsersFilter { /// id: Vec, /// } /// /// async fn filter_users( /// QsQuery(info): QsQuery /// ) -> impl IntoResponse { /// info.id /// .iter() /// .map(|i| i.to_string()) /// .collect::>() /// .join(", ") /// } /// /// fn main() { /// let app = Router::<()>::new() /// .route("/users", get(filter_users)); /// } pub struct QsQuery(pub T); impl std::ops::Deref for QsQuery { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } impl std::fmt::Display for QsQuery { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { self.0.fmt(f) } } impl std::fmt::Debug for QsQuery { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { self.0.fmt(f) } } #[axum::async_trait] impl FromRequestParts for QsQuery where T: serde::de::DeserializeOwned, S: Send + Sync, { type Rejection = QsQueryRejection; async fn from_request_parts( parts: &mut axum::http::request::Parts, state: &S, ) -> Result { let Extension(qs_config) = Extension::::from_request_parts(parts, state) .await .unwrap_or_else(|_| Extension(QsQueryConfig::default())); let error_handler = qs_config.error_handler.clone(); let config: QsConfig = qs_config.into(); let query = parts.uri.query().unwrap_or_default(); match config.deserialize_str::(query) { Ok(value) => Ok(QsQuery(value)), Err(err) => match error_handler { Some(handler) => Err((handler)(err)), None => Err(QsQueryRejection::new(err, StatusCode::BAD_REQUEST)), }, } } } #[derive(Debug)] /// Rejection type for extractors that deserialize query strings pub struct QsQueryRejection { error: axum::Error, status: StatusCode, } impl std::fmt::Display for QsQueryRejection { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "Failed to deserialize query string. Error: {}", self.error, ) } } impl QsQueryRejection { /// Create new rejection pub fn new(error: E, status: StatusCode) -> Self where E: Into, { QsQueryRejection { error: Error::new(error), status, } } } impl IntoResponse for QsQueryRejection { fn into_response(self) -> Response { let mut res = self.to_string().into_response(); *res.status_mut() = self.status; res } } impl std::error::Error for QsQueryRejection { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.error) } } #[derive(Clone)] /// Query extractor configuration /// /// QsQueryConfig wraps [`Config`](crate::de::Config) and implement [`Clone`] /// for [`FromRequest`](https://docs.rs/axum/0.5/axum/extract/trait.FromRequest.html) /// /// ## Example /// /// ```rust /// # extern crate axum_framework as axum; /// use serde_qs::axum::{QsQuery, QsQueryConfig, QsQueryRejection}; /// use serde_qs::Config; /// use axum::{ /// response::IntoResponse, /// routing::get, /// Router, /// body::Body, /// extract::Extension, /// http::StatusCode, /// }; /// use std::sync::Arc; /// /// #[derive(serde::Deserialize)] /// pub struct UsersFilter { /// id: Vec, /// } /// /// async fn filter_users( /// QsQuery(info): QsQuery /// ) -> impl IntoResponse { /// info.id /// .iter() /// .map(|i| i.to_string()) /// .collect::>() /// .join(", ") /// } /// /// fn main() { /// let app = Router::<()>::new() /// .route("/users", get(filter_users)) /// .layer(Extension(QsQueryConfig::new(5, false) /// .error_handler(|err| { /// QsQueryRejection::new(err, StatusCode::UNPROCESSABLE_ENTITY) /// }))); /// } pub struct QsQueryConfig { max_depth: usize, strict: bool, error_handler: Option QsQueryRejection + Send + Sync>>, } impl QsQueryConfig { /// Create new config wrapper pub fn new(max_depth: usize, strict: bool) -> Self { Self { max_depth, strict, error_handler: None, } } /// Set custom error handler pub fn error_handler(mut self, f: F) -> Self where F: Fn(QsError) -> QsQueryRejection + Send + Sync + 'static, { self.error_handler = Some(Arc::new(f)); self } } impl From for QsConfig { fn from(config: QsQueryConfig) -> Self { Self::new(config.max_depth, config.strict) } } impl Default for QsQueryConfig { fn default() -> Self { Self { max_depth: 5, strict: true, error_handler: None, } } } serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/src/de/000077500000000000000000000000001460457264500214175ustar00rootroot00000000000000serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/src/de/mod.rs000066400000000000000000000536761460457264500225650ustar00rootroot00000000000000//! Deserialization support for querystrings. //! ### An overview of the design of `QsDeserializer` //! //! This code is designed to handle non-ordered query parameters. For example, //! `struct { a: Vec, b: String }` might be serialized as either //! `a[0]=1&a[1]=2&b=Hello or a[1]=2&b=Hello&a[0]=1`. //! //! In order to cover the latter case, we have two options: scan through the //! string each time we need to find a particular key - worst case O(n^2 ) //! running time; or pre-parse the list into a map structure, and then //! deserialize the map. //! //! We opt for the latter. But a TODO is implement the first case, which could //! potentially be more desirable, especially when the keys are known to be in //! order. //! //! The `parse` module handles this step of deserializing a querystring into the //! map structure. This uses `rust_url::percent_encoding` to handle //! first converting the string. //! //! From here, there are two main `Deserializer` objects: `QsDeserializer` and //! `LevelDeserializer`. //! //! The former is the top-level deserializer which is effectively only capable //! of deserializing map-like objects (i.e. those with (key, value) pairs). //! Hence, structs, maps, and enums are supported at this level. //! //! Each key is a `String`, and deserialized from a `String`. The values are //! `Level` elements. This is a recursive structure which can either be a "flat //! value", i.e. just a string, or a sequence or map of these elements. This can //! be thought of as similar to the `serde_json::Value` enum. //! //! Each `Level` can be deserialized through `LevelDeserializer`. This will //! recursively call back to the top level `QsDeserializer` for maps, or when //! `Level` is a flat value it will attempt to deserialize it to a primitive via //! `ParsableStringDeserializer`. mod parse; use crate::error::*; use serde::de; use serde::de::IntoDeserializer; use std::borrow::Cow; use std::collections::btree_map::{BTreeMap, Entry, IntoIter}; /// To override the default serialization parameters, first construct a new /// Config. /// /// The `strict` parameter controls whether the deserializer will tolerate /// encoded brackets as part of the key. For example, serializing the field /// `a = vec![12]` might give `a[0]=12`. In strict mode, the only string accepted /// will be this string, whereas in non-strict mode, this can also be deserialized /// from `a%5B0%5D=12`. Strict mode is more accurate for cases where it a field /// may contain square brackets. /// In non-strict mode, the deserializer will generally tolerate unexpected /// characters. /// /// A `max_depth` of 0 implies no nesting: the result will be a flat map. /// This is mostly useful when the maximum nested depth is known beforehand, /// to prevent denial of service attacks by providing incredibly deeply nested /// inputs. /// /// The default value for `max_depth` is 5, and the default mode is `strict=true`. /// /// ``` /// use serde_qs::Config; /// use std::collections::HashMap; /// /// let config = Config::new(0, true); /// let map: HashMap = config.deserialize_str("a[b][c]=1") /// .unwrap(); /// assert_eq!(map.get("a[b][c]").unwrap(), "1"); /// /// let config = Config::new(10, true); /// let map: HashMap>> = /// config.deserialize_str("a[b][c]=1").unwrap(); /// assert_eq!(map.get("a").unwrap().get("b").unwrap().get("c").unwrap(), "1"); /// ``` /// #[derive(Clone, Copy)] pub struct Config { /// Specifies the maximum depth key that `serde_qs` will attempt to /// deserialize. Default is 5. max_depth: usize, /// Strict deserializing mode will not tolerate encoded brackets. strict: bool, } pub const DEFAULT_CONFIG: Config = Config { max_depth: 5, strict: true, }; impl Default for Config { fn default() -> Self { DEFAULT_CONFIG } } impl Config { /// Create a new `Config` with the specified `max_depth` and `strict` mode. pub fn new(max_depth: usize, strict: bool) -> Self { Self { max_depth, strict } } /// Get maximum depth parameter. fn max_depth(&self) -> usize { self.max_depth } } impl Config { /// Deserializes a querystring from a `&[u8]` using this `Config`. pub fn deserialize_bytes<'de, T: de::Deserialize<'de>>(&self, input: &'de [u8]) -> Result { T::deserialize(QsDeserializer::with_config(self, input)?) } // pub fn deserialize_bytes_sloppy(&self, input: &[u8]) // -> Result // { // let buf = String::from_utf8(input.to_vec())?; // let buf = buf.replace("%5B", "[").replace("%5D", "]").into_bytes(); // let deser = QsDeserializer::with_config(self, &buf)?; // T::deserialize(deser) // } /// Deserializes a querystring from a `&str` using this `Config`. pub fn deserialize_str<'de, T: de::Deserialize<'de>>(&self, input: &'de str) -> Result { self.deserialize_bytes(input.as_bytes()) } } /// Deserializes a querystring from a `&[u8]`. /// /// ``` /// # #[macro_use] /// # extern crate serde_derive; /// # extern crate serde_qs; /// #[derive(Debug, Deserialize, PartialEq, Serialize)] /// struct Query { /// name: String, /// age: u8, /// occupation: String, /// } /// /// # fn main(){ /// let q = Query { /// name: "Alice".to_owned(), /// age: 24, /// occupation: "Student".to_owned(), /// }; /// /// assert_eq!( /// serde_qs::from_bytes::( /// "name=Alice&age=24&occupation=Student".as_bytes() /// ).unwrap(), q); /// # } /// ``` pub fn from_bytes<'de, T: de::Deserialize<'de>>(input: &'de [u8]) -> Result { Config::default().deserialize_bytes(input) } /// Deserializes a querystring from a `&str`. /// /// ``` /// # #[macro_use] /// # extern crate serde_derive; /// # extern crate serde_qs; /// #[derive(Debug, Deserialize, PartialEq, Serialize)] /// struct Query { /// name: String, /// age: u8, /// occupation: String, /// } /// /// # fn main(){ /// let q = Query { /// name: "Alice".to_owned(), /// age: 24, /// occupation: "Student".to_owned(), /// }; /// /// assert_eq!( /// serde_qs::from_str::("name=Alice&age=24&occupation=Student").unwrap(), /// q); /// # } /// ``` pub fn from_str<'de, T: de::Deserialize<'de>>(input: &'de str) -> Result { from_bytes(input.as_bytes()) } /// A deserializer for the querystring format. /// /// Supported top-level outputs are structs and maps. pub struct QsDeserializer<'a> { iter: IntoIter, Level<'a>>, value: Option>, } #[derive(Debug)] enum Level<'a> { Nested(BTreeMap, Level<'a>>), OrderedSeq(BTreeMap>), Sequence(Vec>), Flat(Cow<'a, str>), Invalid(String), Uninitialised, } impl<'a> QsDeserializer<'a> { fn with_map(map: BTreeMap, Level<'a>>) -> Self { QsDeserializer { iter: map.into_iter(), value: None, } } /// Returns a new `QsDeserializer<'a>`. pub fn with_config(config: &Config, input: &'a [u8]) -> Result { parse::Parser::new(input, config.max_depth(), config.strict).as_deserializer() } pub fn new(input: &'a [u8]) -> Result { Self::with_config(&Config::default(), input) } } impl<'de> de::Deserializer<'de> for QsDeserializer<'de> { type Error = Error; fn deserialize_any(mut self, visitor: V) -> Result where V: de::Visitor<'de>, { if self.iter.next().is_none() { return visitor.visit_unit(); } Err(Error::top_level("primitive")) } fn deserialize_map(self, visitor: V) -> Result where V: de::Visitor<'de>, { visitor.visit_map(self) } fn deserialize_struct( self, _name: &'static str, _fields: &'static [&'static str], visitor: V, ) -> Result where V: de::Visitor<'de>, { self.deserialize_map(visitor) } /// Throws an error. /// /// Sequences are not supported at the top level. fn deserialize_seq(self, _visitor: V) -> Result where V: de::Visitor<'de>, { Err(Error::top_level("sequence")) } fn deserialize_newtype_struct(self, _name: &'static str, visitor: V) -> Result where V: de::Visitor<'de>, { self.deserialize_map(visitor) } /// Throws an error. /// /// Tuples are not supported at the top level. fn deserialize_tuple(self, _len: usize, _visitor: V) -> Result where V: de::Visitor<'de>, { Err(Error::top_level("tuple")) } /// Throws an error. /// /// TupleStructs are not supported at the top level. fn deserialize_tuple_struct( self, _name: &'static str, _len: usize, _visitor: V, ) -> Result where V: de::Visitor<'de>, { Err(Error::top_level("tuple struct")) } fn deserialize_enum( self, _name: &'static str, _variants: &'static [&'static str], visitor: V, ) -> Result where V: de::Visitor<'de>, { visitor.visit_enum(self) } forward_to_deserialize_any! { bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string unit option bytes byte_buf unit_struct identifier ignored_any } } impl<'de> de::MapAccess<'de> for QsDeserializer<'de> { type Error = Error; fn next_key_seed(&mut self, seed: K) -> Result> where K: de::DeserializeSeed<'de>, { if let Some((key, value)) = self.iter.next() { self.value = Some(value); let has_bracket = key.contains('['); seed.deserialize(ParsableStringDeserializer(key)) .map(Some) .map_err(|e| { if has_bracket { de::Error::custom( format!("{}\nInvalid field contains an encoded bracket -- did you mean to use non-strict mode?\n https://docs.rs/serde_qs/latest/serde_qs/#strict-vs-non-strict-modes", e,) ) } else { e } }) } else { Ok(None) } } fn next_value_seed(&mut self, seed: V) -> Result where V: de::DeserializeSeed<'de>, { if let Some(v) = self.value.take() { seed.deserialize(LevelDeserializer(v)) } else { Err(de::Error::custom( "Somehow the map was empty after a non-empty key was returned", )) } } } impl<'de> de::EnumAccess<'de> for QsDeserializer<'de> { type Error = Error; type Variant = Self; fn variant_seed(mut self, seed: V) -> Result<(V::Value, Self::Variant)> where V: de::DeserializeSeed<'de>, { if let Some((key, value)) = self.iter.next() { self.value = Some(value); Ok((seed.deserialize(ParsableStringDeserializer(key))?, self)) } else { Err(de::Error::custom("No more values")) } } } impl<'de> de::VariantAccess<'de> for QsDeserializer<'de> { type Error = Error; fn unit_variant(self) -> Result<()> { Ok(()) } fn newtype_variant_seed(self, seed: T) -> Result where T: de::DeserializeSeed<'de>, { if let Some(value) = self.value { seed.deserialize(LevelDeserializer(value)) } else { Err(de::Error::custom("no value to deserialize")) } } fn tuple_variant(self, _len: usize, visitor: V) -> Result where V: de::Visitor<'de>, { if let Some(value) = self.value { de::Deserializer::deserialize_seq(LevelDeserializer(value), visitor) } else { Err(de::Error::custom("no value to deserialize")) } } fn struct_variant(self, _fields: &'static [&'static str], visitor: V) -> Result where V: de::Visitor<'de>, { if let Some(value) = self.value { de::Deserializer::deserialize_map(LevelDeserializer(value), visitor) } else { Err(de::Error::custom("no value to deserialize")) } } } impl<'de> de::EnumAccess<'de> for LevelDeserializer<'de> { type Error = Error; type Variant = Self; fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant)> where V: de::DeserializeSeed<'de>, { match self.0 { Level::Flat(x) => Ok(( seed.deserialize(ParsableStringDeserializer(x))?, LevelDeserializer(Level::Invalid( "this value can only \ deserialize to a \ UnitVariant" .to_string(), )), )), _ => Err(de::Error::custom( "this value can only deserialize to a \ UnitVariant", )), } } } impl<'de> de::VariantAccess<'de> for LevelDeserializer<'de> { type Error = Error; fn unit_variant(self) -> Result<()> { Ok(()) } fn newtype_variant_seed(self, seed: T) -> Result where T: de::DeserializeSeed<'de>, { seed.deserialize(self) } fn tuple_variant(self, _len: usize, visitor: V) -> Result where V: de::Visitor<'de>, { de::Deserializer::deserialize_seq(self, visitor) } fn struct_variant(self, _fields: &'static [&'static str], visitor: V) -> Result where V: de::Visitor<'de>, { de::Deserializer::deserialize_map(self, visitor) } } struct LevelSeq<'a, I: Iterator>>(I); impl<'de, I: Iterator>> de::SeqAccess<'de> for LevelSeq<'de, I> { type Error = Error; fn next_element_seed(&mut self, seed: T) -> Result> where T: de::DeserializeSeed<'de>, { if let Some(v) = self.0.next() { seed.deserialize(LevelDeserializer(v)).map(Some) } else { Ok(None) } } } struct LevelDeserializer<'a>(Level<'a>); macro_rules! deserialize_primitive { ($ty:ident, $method:ident, $visit_method:ident) => { fn $method(self, visitor: V) -> Result where V: de::Visitor<'de>, { match self.0 { Level::Nested(_) => Err(de::Error::custom(format!( "Expected: {:?}, got a Map", stringify!($ty) ))), Level::OrderedSeq(_) => Err(de::Error::custom(format!( "Expected: {:?}, got an OrderedSequence", stringify!($ty) ))), Level::Sequence(_) => Err(de::Error::custom(format!( "Expected: {:?}, got a Sequence", stringify!($ty) ))), Level::Flat(x) => ParsableStringDeserializer(x).$method(visitor), Level::Invalid(e) => Err(de::Error::custom(e)), Level::Uninitialised => Err(de::Error::custom( "attempted to deserialize unitialised value", )), } } }; } impl<'a> LevelDeserializer<'a> { fn into_deserializer(self) -> Result> { match self.0 { Level::Nested(map) => Ok(QsDeserializer::with_map(map)), Level::OrderedSeq(map) => Ok(QsDeserializer::with_map( map.into_iter() .map(|(k, v)| (Cow::Owned(k.to_string()), v)) .collect(), )), Level::Invalid(e) => Err(de::Error::custom(e)), l => Err(de::Error::custom(format!( "could not convert {:?} to \ QsDeserializer<'a>", l ))), } } } impl<'de> de::Deserializer<'de> for LevelDeserializer<'de> { type Error = Error; fn deserialize_any(self, visitor: V) -> Result where V: de::Visitor<'de>, { match self.0 { Level::Nested(_) => self.into_deserializer()?.deserialize_map(visitor), Level::OrderedSeq(map) => visitor.visit_seq(LevelSeq(map.into_values())), Level::Sequence(seq) => visitor.visit_seq(LevelSeq(seq.into_iter())), Level::Flat(x) => match x { Cow::Owned(s) => visitor.visit_string(s), Cow::Borrowed(s) => visitor.visit_borrowed_str(s), }, Level::Invalid(e) => Err(de::Error::custom(e)), Level::Uninitialised => Err(de::Error::custom( "attempted to deserialize unitialised \ value", )), } } fn deserialize_option(self, visitor: V) -> Result where V: de::Visitor<'de>, { match self.0 { Level::Flat(ref x) if x == "" => visitor.visit_none(), _ => visitor.visit_some(self), } } fn deserialize_unit(self, visitor: V) -> Result where V: de::Visitor<'de>, { match self.0 { Level::Flat(ref x) if x == "" => visitor.visit_unit(), _ => Err(de::Error::custom("expected unit".to_owned())), } } fn deserialize_enum( self, name: &'static str, variants: &'static [&'static str], visitor: V, ) -> Result where V: de::Visitor<'de>, { match self.0 { Level::Nested(map) => { QsDeserializer::with_map(map).deserialize_enum(name, variants, visitor) } Level::Flat(_) => visitor.visit_enum(self), x => Err(de::Error::custom(format!( "{:?} does not appear to be \ an enum", x ))), } } fn deserialize_newtype_struct(self, _name: &'static str, visitor: V) -> Result where V: de::Visitor<'de>, { match self.0 { Level::Nested(_) => self.into_deserializer()?.deserialize_map(visitor), Level::OrderedSeq(map) => visitor.visit_seq(LevelSeq(map.into_values())), Level::Sequence(seq) => visitor.visit_seq(LevelSeq(seq.into_iter())), Level::Flat(_) => { // For a newtype_struct, attempt to deserialize a flat value as a // single element sequence. visitor.visit_seq(LevelSeq(vec![self.0].into_iter())) } Level::Invalid(e) => Err(de::Error::custom(e)), Level::Uninitialised => Err(de::Error::custom( "attempted to deserialize unitialised \ value", )), } } /// given the hint that this is a map, will first /// attempt to deserialize ordered sequences into a map /// otherwise, follows the any code path fn deserialize_map(self, visitor: V) -> Result where V: de::Visitor<'de>, { match self.0 { Level::OrderedSeq(_) => self.into_deserializer()?.deserialize_map(visitor), _ => self.deserialize_any(visitor), } } deserialize_primitive!(bool, deserialize_bool, visit_bool); deserialize_primitive!(i8, deserialize_i8, visit_i8); deserialize_primitive!(i16, deserialize_i16, visit_i16); deserialize_primitive!(i32, deserialize_i32, visit_i32); deserialize_primitive!(i64, deserialize_i64, visit_i64); deserialize_primitive!(u8, deserialize_u8, visit_u8); deserialize_primitive!(u16, deserialize_u16, visit_u16); deserialize_primitive!(u32, deserialize_u32, visit_u32); deserialize_primitive!(u64, deserialize_u64, visit_u64); deserialize_primitive!(f32, deserialize_f32, visit_f32); deserialize_primitive!(f64, deserialize_f64, visit_f64); forward_to_deserialize_any! { char str string bytes byte_buf unit_struct // newtype_struct tuple_struct struct identifier tuple ignored_any seq // map } } macro_rules! forward_parsable_to_deserialize_any { ($($ty:ident => $meth:ident,)*) => { $( fn $meth(self, visitor: V) -> Result where V: de::Visitor<'de> { match self.0.parse::<$ty>() { Ok(val) => val.into_deserializer().$meth(visitor), Err(e) => Err(de::Error::custom(e)) } } )* } } struct ParsableStringDeserializer<'a>(Cow<'a, str>); impl<'de> de::Deserializer<'de> for ParsableStringDeserializer<'de> { type Error = Error; fn deserialize_any(self, visitor: V) -> Result where V: de::Visitor<'de>, { self.0.into_deserializer().deserialize_any(visitor) } fn deserialize_enum( self, _: &'static str, _: &'static [&'static str], visitor: V, ) -> Result where V: de::Visitor<'de>, { visitor.visit_enum(LevelDeserializer(Level::Flat(self.0))) } forward_to_deserialize_any! { map struct seq option char str string unit bytes byte_buf unit_struct newtype_struct tuple_struct identifier tuple ignored_any } forward_parsable_to_deserialize_any! { bool => deserialize_bool, u8 => deserialize_u8, u16 => deserialize_u16, u32 => deserialize_u32, u64 => deserialize_u64, i8 => deserialize_i8, i16 => deserialize_i16, i32 => deserialize_i32, i64 => deserialize_i64, f32 => deserialize_f32, f64 => deserialize_f64, } } serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/src/de/parse.rs000066400000000000000000000616061460457264500231100ustar00rootroot00000000000000use crate::utils::{replace_space, QS_ENCODE_SET}; use super::*; use percent_encoding::percent_encode; use serde::de; use std::borrow::Cow; use std::iter::Iterator; use std::slice::Iter; use std::str; macro_rules! tu { ($x:expr) => { match $x { Some(x) => *x, None => return Err(de::Error::custom("query string ended before expected")), } }; } impl<'a> Level<'a> { /// If this `Level` value is indeed a map, then attempt to insert /// `value` for key `key`. /// Returns error if `self` is not a map, or already has an entry for that /// key. fn insert_map_value(&mut self, key: Cow<'a, str>, value: Cow<'a, str>) { if let Level::Nested(ref mut map) = *self { match map.entry(key) { Entry::Occupied(mut o) => { let key = o.key(); let error = if key.contains('[') { let newkey = percent_encode(key.as_bytes(), QS_ENCODE_SET) .map(replace_space) .collect::(); format!("Multiple values for one key: \"{}\"\nInvalid field contains an encoded bracket -- did you mean to use non-strict mode?\n https://docs.rs/serde_qs/latest/serde_qs/#strict-vs-non-strict-modes", newkey) } else { format!("Multiple values for one key: \"{}\"", key) }; // Throw away old result; map is now invalid anyway. let _ = o.insert(Level::Invalid(error)); } Entry::Vacant(vm) => { // Map is empty, result is None let _ = vm.insert(Level::Flat(value)); } } } else if let Level::Uninitialised = *self { let mut map = BTreeMap::default(); let _ = map.insert(key, Level::Flat(value)); *self = Level::Nested(map); } else { *self = Level::Invalid( "Attempted to insert map value into \ non-map structure" .to_string(), ); } } /// If this `Level` value is indeed a seq, then push a new value fn insert_ord_seq_value(&mut self, key: usize, value: Cow<'a, str>) { if let Level::OrderedSeq(ref mut map) = *self { match map.entry(key) { Entry::Occupied(mut o) => { // Throw away old result; map is now invalid anyway. let _ = o.insert(Level::Invalid("Multiple values for one key".to_string())); } Entry::Vacant(vm) => { // Map is empty, result is None let _ = vm.insert(Level::Flat(value)); } } } else if let Level::Uninitialised = *self { // To reach here, self is either an OrderedSeq or nothing. let mut map = BTreeMap::default(); let _ = map.insert(key, Level::Flat(value)); *self = Level::OrderedSeq(map); } else { *self = Level::Invalid( "Attempted to insert seq value into \ non-seq structure" .to_string(), ); } } /// If this `Level` value is indeed a seq, then attempt to insert /// `value` for key `key`. /// Returns error if `self` is not a seq, or already has an entry for that /// key. fn insert_seq_value(&mut self, value: Cow<'a, str>) { // Reached the end of the key string if let Level::Sequence(ref mut seq) = *self { seq.push(Level::Flat(value)); } else if let Level::Uninitialised = *self { let seq = vec![Level::Flat(value)]; *self = Level::Sequence(seq); } else { *self = Level::Invalid( "Attempted to insert seq value into \ non-seq structure" .to_string(), ); } } } /// The `Parser` struct is a stateful querystring parser. /// It iterates over a slice of bytes, with a range to track the current /// start/end points of a value. /// The parser additionally supports peeking values, which allows them to be /// re-used (precisely once, unlike with `Peekable` from `std::iter`). pub struct Parser<'a> { inner: &'a [u8], iter: Iter<'a, u8>, index: usize, acc: (usize, usize), peeked: Option<&'a u8>, depth: usize, // stores the current depth, for use in bounded-depth parsing strict: bool, state: ParsingState, } /// The parsing logic varies slightly based on whether it is a key or a value /// (determines how encoded brackets are parse in non-strict mode) /// This tracks the state. enum ParsingState { Init, Key, Value, } impl<'a> Iterator for Parser<'a> { type Item = &'a u8; #[inline] fn next(&mut self) -> Option { let preparse_brackets = match self.state { ParsingState::Value => false, _ => !self.strict, }; if preparse_brackets { // in non-strict mode, we will happily decode any bracket match self.peeked.take() { Some(v) => Some(v), None => { self.index += 1; self.acc.1 += 1; match self.iter.next() { Some(v) if v == &b'%' && self.iter.len() >= 2 => { match &self.iter.as_slice()[..2] { b"5B" => { // skip the next two characters let _ = self.iter.next(); let _ = self.iter.next(); self.index += 2; Some(&b'[') } b"5D" => { // skip the next two characters let _ = self.iter.next(); let _ = self.iter.next(); self.index += 2; Some(&b']') } _ => Some(v), } } Some(v) => Some(v), None => None, } } } } else { match self.peeked.take() { Some(v) => Some(v), None => { self.index += 1; self.acc.1 += 1; self.iter.next() } } } } } impl<'a> Parser<'a> { #[inline] fn peek(&mut self) -> Option<::Item> { if self.peeked.is_some() { self.peeked } else if let Some(x) = self.next() { self.peeked = Some(x); Some(x) } else { None } } } /// Replace b'+' with b' ' /// Copied from [`form_urlencoded`](https://github.com/servo/rust-url/blob/380be29859adb859e861c2d765897c22ec878e01/src/form_urlencoded.rs#L125). fn replace_plus(input: &[u8]) -> Cow<[u8]> { match input.iter().position(|&b| b == b'+') { None => Cow::Borrowed(input), Some(first_position) => { let mut replaced = input.to_owned(); replaced[first_position] = b' '; for byte in &mut replaced[first_position + 1..] { if *byte == b'+' { *byte = b' '; } } Cow::Owned(replaced) } } } impl<'a> Parser<'a> { pub fn new(encoded: &'a [u8], depth: usize, strict: bool) -> Self { Parser { inner: encoded, iter: encoded.iter(), acc: (0, 0), index: 0, peeked: None, depth, strict, state: ParsingState::Init, } } /// Resets the accumulator range by setting `(start, end)` to `(end, end)`. fn clear_acc(&mut self) { self.acc = (self.index, self.index); } /// Extracts a string from the internal byte slice from the range tracked by /// the parser. /// Avoids allocations when neither percent encoded, nor `'+'` values are /// present. fn collect_str(&mut self) -> Result> { let replaced = replace_plus(&self.inner[self.acc.0..self.acc.1 - 1]); let decoder = percent_encoding::percent_decode(&replaced); let maybe_decoded = if self.strict { decoder.decode_utf8()? } else { decoder.decode_utf8_lossy() }; let ret: Result> = match maybe_decoded { Cow::Borrowed(_) => { match replaced { Cow::Borrowed(_) => { // In this case, neither method made replacements, so we // reuse the original bytes let res = str::from_utf8(&self.inner[self.acc.0..self.acc.1 - 1])?; Ok(Cow::Borrowed(res)) } Cow::Owned(owned) => { let res = String::from_utf8(owned)?; Ok(Cow::Owned(res)) } } } Cow::Owned(owned) => Ok(Cow::Owned(owned)), }; self.clear_acc(); ret.map_err(Error::from) } /// In some ways the main way to use a `Parser`, this runs the parsing step /// and outputs a simple `Deserializer` over the parsed map. pub(crate) fn as_deserializer(&mut self) -> Result> { let map = BTreeMap::default(); let mut root = Level::Nested(map); // Parses all top level nodes into the `root` map. while self.parse(&mut root)? {} let iter = match root { Level::Nested(map) => map.into_iter(), _ => BTreeMap::default().into_iter(), }; Ok(QsDeserializer { iter, value: None }) } /// This is the top level parsing function. It checks the first character to /// decide the type of key (nested, sequence, etc.) and to call the /// approprate parsing function. /// /// Returns `Ok(false)` when there is no more string to parse. fn parse(&mut self, node: &mut Level<'a>) -> Result { // First character determines parsing type if self.depth == 0 { // Hit the maximum depth level, so parse everything as a key let key = self.parse_key(b'=', false)?; self.parse_map_value(key, node)?; return Ok(true); } match self.next() { Some(x) => { match *x { b'[' => { loop { self.clear_acc(); // Only peek at the next value to determine the key type. match tu!(self.peek()) { // key is of the form "[..=", not really allowed. b'[' => { // If we're in strict mode, error, otherwise just ignore it. if self.strict { return Err(super::Error::parse_err("found another opening bracket before the closed bracket", self.index)); } else { let _ = self.next(); } } // key is simply "[]", so treat as a seq. b']' => { // throw away the bracket let _ = self.next(); self.clear_acc(); self.parse_seq_value(node)?; return Ok(true); } // First character is an integer, attempt to parse it as an integer key b'0'..=b'9' => { let key = self.parse_key(b']', true)?; let key = key.parse().map_err(Error::from)?; self.parse_ord_seq_value(key, node)?; return Ok(true); } // Key is "[a..=" so parse up to the closing "]" 0x20..=0x2f | 0x3a..=0x5a | 0x5c | 0x5e..=0x7e => { let key = self.parse_key(b']', true)?; self.parse_map_value(key, node)?; return Ok(true); } c => { if self.strict { return Err(super::Error::parse_err( format!( "unexpected character: {}", String::from_utf8_lossy(&[c]) ), self.index, )); } else { let _ = self.next(); } } } } } // Skip empty byte sequences (e.g. leading `&`, trailing `&`, `&&`, ...) b'&' => { self.clear_acc(); Ok(true) } // This means the key should be a root key // of the form "abc" or "abc[..=]" // We do actually allow integer keys here since they cannot // be confused with sequences _ => { let key = { self.parse_key(b'[', false)? }; // Root keys are _always_ map values self.parse_map_value(key, node)?; Ok(true) } } } // Ran out of characters to parse None => Ok(false), } } /// The iterator is currently pointing at a key, so parse up until the /// `end_on` value. This will either be `'['` when the key is the root key, /// or `']'` when the key is a nested key. In the former case, `'='` will /// also finish the key parsing. /// /// The `consume` flag determines whether the end character should be /// returned to the buffer to be peeked. This is important when /// parsing keys like `abc[def][ghi]` since the `'['` character is /// needed to for the next iteration of `parse`. fn parse_key(&mut self, end_on: u8, consume: bool) -> Result> { self.state = ParsingState::Key; loop { if let Some(x) = self.next() { match *x { c if c == end_on => { // Add this character back to the buffer for peek. if !consume { self.peeked = Some(x); } return self.collect_str(); } b'=' => { // Allow the '=' byte only when parsing keys within [] if end_on != b']' { // Otherwise, we have reached the end of the key // Add this character back to the buffer for peek. self.peeked = Some(x); return self.collect_str(); } // otherwise do nothing, so '=' is accumulated } b'&' => { // important to keep the `&` character so we know the // key-value is of the form `key&..=` (i.e. no value) self.peeked = Some(&b'&'); return self.collect_str(); } _ => { // for any other character // do nothing, keep adding to key } } } else { // no more string to parse return self.collect_str(); } } } /// The `(key,value)` pair is determined to be corresponding to a map entry, /// so parse it as such. The first part of the `key` has been parsed. fn parse_map_value(&mut self, key: Cow<'a, str>, node: &mut Level<'a>) -> Result<()> { self.state = ParsingState::Key; let res = loop { if let Some(x) = self.peek() { match *x { b'=' => { // Key is finished, parse up until the '&' as the value self.clear_acc(); self.state = ParsingState::Value; for _ in self.take_while(|b| *b != &b'&') {} let value: Cow<'a, str> = self.collect_str()?; node.insert_map_value(key, value); break Ok(()); } b'&' => { // No value node.insert_map_value(key, Cow::Borrowed("")); break Ok(()); } b'[' => { // The key continues to another level of nested. // Add a new unitialised level for this node and continue. if let Level::Uninitialised = *node { *node = Level::Nested(BTreeMap::default()); } if let Level::Nested(ref mut map) = *node { // By parsing we drop down another level self.depth -= 1; // Either take the existing entry, or add a new // unitialised level // Use this new node to keep parsing let _ = self.parse(map.entry(key).or_insert(Level::Uninitialised))?; break Ok(()); } else { // We expected to parse into a map here. break Err(super::Error::parse_err( format!( "tried to insert a \ new key into {:?}", node ), self.index, )); } } c => { // Anything else is unexpected since we just finished // parsing a key. if self.strict { break Err(super::Error::parse_err( format!( "Unexpected character: '{}' found when parsing", String::from_utf8_lossy(&[c]) ), self.index, )); } else { let _ = self.next(); } } } } else { // The string has ended, so the value is empty. node.insert_map_value(key, Cow::Borrowed("")); break Ok(()); } }; // We have finished parsing this level, so go back up a level. self.depth += 1; res } /// The `(key,value)` pair is determined to be corresponding to an /// ordered sequence. /// Basically the same as the above, but we insert into `OrderedSeq` /// Can potentially be merged? fn parse_ord_seq_value(&mut self, key: usize, node: &mut Level<'a>) -> Result<()> { self.state = ParsingState::Key; let res = loop { if let Some(x) = self.peek() { match *x { b'=' => { // Key is finished, parse up until the '&' as the value self.clear_acc(); self.state = ParsingState::Value; for _ in self.take_while(|b| *b != &b'&') {} let value = self.collect_str()?; // Reached the end of the key string node.insert_ord_seq_value(key, value); break Ok(()); } b'&' => { // No value node.insert_ord_seq_value(key, Cow::Borrowed("")); break Ok(()); } b'[' => { // The key continues to another level of nested. // Add a new unitialised level for this node and continue. if let Level::Uninitialised = *node { *node = Level::OrderedSeq(BTreeMap::default()); } if let Level::OrderedSeq(ref mut map) = *node { // By parsing we drop down another level self.depth -= 1; let _ = self.parse( // Either take the existing entry, or add a new // unitialised level // Use this new node to keep parsing map.entry(key).or_insert(Level::Uninitialised), )?; break Ok(()); } else { // We expected to parse into a seq here. break Err(super::Error::parse_err( format!( "tried to insert a \ new key into {:?}", node ), self.index, )); } } c => { // Anything else is unexpected since we just finished // parsing a key. if self.strict { break Err(super::Error::parse_err( format!("Unexpected character: {:?} found when parsing", c), self.index, )); } else { let _ = self.next(); } } } } else { // The string has ended, so the value is empty. node.insert_ord_seq_value(key, Cow::Borrowed("")); break Ok(()); } }; // We have finished parsing this level, so go back up a level. self.depth += 1; res } /// The `(key,value)` pair is determined to be corresponding to an /// unordered sequence. /// This must be the final level of nesting, so assume we have a value fn parse_seq_value(&mut self, node: &mut Level<'a>) -> Result<()> { self.state = ParsingState::Key; let res = match self.peek() { Some(x) => { match *x { b'=' => { // Key is finished, parse up until the '&' as the value self.clear_acc(); self.state = ParsingState::Value; for _ in self.take_while(|b| *b != &b'&') {} let value = self.collect_str()?; node.insert_seq_value(value); Ok(()) } b'&' => { // key value is empty node.insert_seq_value(Cow::Borrowed("")); Ok(()) } _ => Err(super::Error::parse_err( "non-indexed sequence of \ structs not supported", self.index, )), } } None => { // The string has ended, so the value is empty. node.insert_seq_value(Cow::Borrowed("")); Ok(()) } }; // We have finished parsing this level, so go back up a level. self.depth += 1; res } } serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/src/error.rs000066400000000000000000000032061460457264500225270ustar00rootroot00000000000000use serde::de; use std::fmt::Display; use std::io; use std::num; use std::str; use std::string; /// Error type for `serde_qs`. #[derive(thiserror::Error, Debug)] pub enum Error { /// Custom string-based error #[error("{0}")] Custom(String), /// Parse error at a specified position in the query string #[error("parsing failed with error: '{0}' at position: {1}")] Parse(String, usize), /// Unsupported type that `serde_qs` can't serialize into a query string #[error("unsupported type for serialization")] Unsupported, /// Error proessing UTF-8 for a `String` #[error(transparent)] FromUtf8(#[from] string::FromUtf8Error), /// I/O error #[error(transparent)] Io(#[from] io::Error), /// Error parsing a number #[error(transparent)] ParseInt(#[from] num::ParseIntError), /// Error processing UTF-8 for a `str` #[error(transparent)] Utf8(#[from] str::Utf8Error), } impl Error { /// Generate error to show top-level type cannot be deserialized. pub fn top_level(object: &'static str) -> Self { Error::Custom(format!( "cannot deserialize {} at the top level.\ Try deserializing into a struct.", object )) } /// Generate a parsing error message with position. pub fn parse_err(msg: T, position: usize) -> Self where T: Display, { Error::Parse(msg.to_string(), position) } } impl de::Error for Error { fn custom(msg: T) -> Self where T: Display, { Error::Custom(msg.to_string()) } } pub type Result = core::result::Result; serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/src/lib.rs000066400000000000000000000163461460457264500221550ustar00rootroot00000000000000//! Serde support for querystring-style strings //! //! Querystrings are not formally defined and loosely take the form of //! _nested_ urlencoded queries. //! //! This library aims for compatability with the syntax of //! [qs](https://github.com/ljharb/qs) and also of the //! [`Rack::Utils::parse_nested_query`](http://www.rubydoc.info/github/rack/rack/Rack/Utils#parse_nested_query-class_method) //! implementation. //! //! For users who do *not* require nested URL parameters, it is highly //! recommended that the `serde_urlencoded` crate is used instead, which //! will almost certainly perform better for deserializing simple inputs. //! //! ## Supported Types //! //! At the **top level**, `serde_qs` only supports `struct`, `map`, and `enum`. //! These are the only top-level structs which can be de/serialized since //! Querystrings rely on having a (key, value) pair for each field, which //! necessitates this kind of structure. //! //! However, after the top level you should find all supported types can be //! de/serialized. //! //! Note that integer keys are reserved for array indices. That is, a string of //! the form `a[0]=1&a[1]=3` will deserialize to the ordered sequence `a = //! [1,3]`. //! //! ## Usage //! //! See the examples folder for a more detailed introduction. //! //! Serializing/Deserializing is designed to work with maps and structs. //! //! ``` //! #[macro_use] //! extern crate serde_derive; //! extern crate serde_qs as qs; //! //! #[derive(Debug, PartialEq, Deserialize, Serialize)] //! struct Address { //! city: String, //! postcode: String, //! } //! #[derive(Debug, PartialEq, Deserialize, Serialize)] //! struct QueryParams { //! id: u8, //! name: String, //! address: Address, //! phone: u32, //! user_ids: Vec, //! } //! //! # fn main() { //! let 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], //! }; //! let rec_params: QueryParams = qs::from_str("\ //! name=Acme&id=42&phone=12345&address[postcode]=12345&\ //! address[city]=Carrot+City&user_ids[0]=1&user_ids[1]=2&\ //! user_ids[2]=3&user_ids[3]=4") //! .unwrap(); //! assert_eq!(rec_params, params); //! //! # } //! ``` //! //! ## Strict vs Non-Strict modes //! //! `serde_qs` supports two operating modes, which can be specified using //! [`Config`](struct.Config.html). //! Strict mode has two parts: //! - how `serde_qs` handles square brackets //! - how `serde_qs` handles invalid UTF-8 percent decoded characters //! //! ### Square Brackets //! //! Technically, square brackets should be encoded in URLs as `%5B` and `%5D`. //! However, they are often used in their raw format to specify querystrings //! such as `a[b]=123`. //! //! In strict mode, `serde_qs` will only tolerate unencoded square brackets //! to denote nested keys. So `a[b]=123` will decode as `{"a": {"b": 123}}`. //! This means that encoded square brackets can actually be part of the key. //! `a[b%5Bc%5D]=123` becomes `{"a": {"b[c]": 123}}`. //! //! However, since some implementations will automatically encode everything //! in the URL, we also have a non-strict mode. This means that `serde_qs` //! will assume that any encoded square brackets in the string were meant to //! be taken as nested keys. From the example before, `a[b%5Bc%5D]=123` will //! now become `{"a": {"b": {"c": 123 }}}`. //! //! Non-strict mode can be useful when, as said before, some middleware //! automatically encodes the brackets. But care must be taken to avoid //! using keys with square brackets in them, or unexpected things can //! happen. //! //! ### Invalid UTF-8 Percent Encodings //! //! Sometimes querystrings may have percent-encoded data which does not decode //! to UTF-8. In some cases it is useful for this to cause errors, which is how //! `serde_qs` works in strict mode (the default). Whereas in other cases it //! can be useful to just replace such data with the unicode replacement //! character (� `U+FFFD`), which is how `serde_qs` works in non-strict mode. //! //! ## Flatten workaround //! //! A current [known limitation](https://github.com/serde-rs/serde/issues/1183) //! in `serde` is deserializing `#[serde(flatten)]` structs for formats which //! are not self-describing. This includes query strings: `12` can be an integer //! or a string, for example. //! //! We suggest the following workaround: //! //! ``` //! extern crate serde; //! #[macro_use] //! extern crate serde_derive; //! extern crate serde_qs as qs; //! extern crate serde_with; //! //! use serde_with::{serde_as, DisplayFromStr}; //! //! #[derive(Deserialize, Serialize, Debug, PartialEq)] //! struct Query { //! a: u8, //! #[serde(flatten)] //! common: CommonParams, //! } //! //! #[serde_as] //! #[derive(Deserialize, Serialize, Debug, PartialEq)] //! struct CommonParams { //! #[serde_as(as = "DisplayFromStr")] //! limit: u64, //! #[serde_as(as = "DisplayFromStr")] //! offset: u64, //! #[serde_as(as = "DisplayFromStr")] //! remaining: bool, //! } //! //! fn main() { //! let params = "a=1&limit=100&offset=50&remaining=true"; //! let query = Query { a: 1, common: CommonParams { limit: 100, offset: 50, remaining: true } }; //! let rec_query: Result = qs::from_str(params); //! assert_eq!(rec_query.unwrap(), query); //! } //! ``` //! //! ## Use with `actix_web` extractors //! //! The `actix4`, `actix3` or `actix2` features enable the use of `serde_qs::actix::QsQuery`, which //! is a direct substitute for the `actix_web::Query` and can be used as an extractor: //! //! ```ignore //! fn index(info: QsQuery) -> Result { //! Ok(format!("Welcome {}!", info.username)) //! } //! ``` //! //! Support for `actix-web 4.0` is available via the `actix4` feature. //! Support for `actix-web 3.0` is available via the `actix3` feature. //! Support for `actix-web 2.0` is available via the `actix2` feature. //! //! ## Use with `warp` filters //! //! The `warp` feature enables the use of `serde_qs::warp::query()`, which //! is a substitute for the `warp::query::query()` filter and can be used like this: //! //! ```ignore //! serde_qs::warp::query(Config::default()) //! .and_then(|info| async move { //! Ok::<_, Rejection>(format!("Welcome {}!", info.username)) //! }) //! .recover(serde_qs::warp::recover_fn); //! ``` //! #[macro_use] extern crate serde; #[cfg(any(feature = "actix4", feature = "actix3"))] pub mod actix; #[cfg(feature = "actix")] compile_error!( r#"The `actix` feature was removed in v0.9 due to the proliferation of actix versions. You must now specify the desired actix version by number. E.g. serde_qs = { version = "0.9", features = ["actix4"] } "# ); #[cfg(feature = "actix2")] compile_error!( r#"The `actix2` feature was removed in v0.13 due to CI issues and minimal interest in continuing support"# ); mod de; mod error; mod ser; pub(crate) mod utils; #[doc(inline)] pub use de::{from_bytes, from_str}; #[doc(inline)] pub use de::{Config, QsDeserializer as Deserializer}; pub use error::Error; #[doc(inline)] pub use ser::{to_string, to_writer, Serializer}; #[cfg(feature = "axum")] pub mod axum; #[cfg(feature = "warp")] pub mod warp; serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/src/ser.rs000066400000000000000000000470621460457264500221770ustar00rootroot00000000000000//! Serialization support for querystrings. 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; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::sync::Arc; /// Serializes a value into a querystring. /// /// ``` /// # #[macro_use] /// # extern crate serde_derive; /// # extern crate serde_qs; /// #[derive(Deserialize, Serialize)] /// struct Query { /// name: String, /// age: u8, /// occupation: String, /// } /// /// # fn main(){ /// let q = Query { /// name: "Alice".to_owned(), /// age: 24, /// occupation: "Student".to_owned(), /// }; /// /// /// assert_eq!( /// serde_qs::to_string(&q).unwrap(), /// "name=Alice&age=24&occupation=Student"); /// # } /// ``` pub fn to_string(input: &T) -> Result { let mut buffer = Vec::new(); input.serialize(&mut Serializer::new(&mut buffer))?; String::from_utf8(buffer).map_err(Error::from) } /// Serializes a value into a generic writer object. /// /// ``` /// # #[macro_use] /// # extern crate serde_derive; /// # extern crate serde_qs; /// #[derive(Deserialize, Serialize)] /// struct Query { /// name: String, /// age: u8, /// occupation: String, /// } /// /// # fn main(){ /// let q = Query { /// name: "Alice".to_owned(), /// age: 24, /// occupation: "Student".to_owned(), /// }; /// /// let mut buffer = Vec::new(); /// serde_qs::to_writer(&q, &mut buffer).unwrap(); /// assert_eq!( /// String::from_utf8(buffer).unwrap(), /// "name=Alice&age=24&occupation=Student"); /// # } /// ``` pub fn to_writer(input: &T, writer: &mut W) -> Result<()> { input.serialize(&mut Serializer::new(writer)) } pub struct Serializer { writer: W, } impl Serializer { pub fn new(writer: W) -> Self { Self { writer } } fn as_qs_serializer(&mut self) -> QsSerializer { 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 { let qs_serializer = self.as_qs_serializer(); qs_serializer.$meth(v) } )* }; (Qs $($ty:ty => $meth:ident,)*) => { $( fn $meth(mut self, v: $ty) -> Result { self.write_value(&v.to_string().as_bytes()) } )* }; ($($ty:ty => $meth:ident,)*) => { $( fn $meth(self, v: $ty) -> Result { Ok(v.to_string()) } )* }; } impl<'a, W: Write> ser::Serializer for &'a mut Serializer { 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.as_qs_serializer().serialize_bytes(value) } fn serialize_unit(self) -> Result { self.as_qs_serializer().serialize_unit() } fn serialize_unit_struct(self, name: &'static str) -> Result { self.as_qs_serializer().serialize_unit_struct(name) } fn serialize_unit_variant( self, name: &'static str, variant_index: u32, variant: &'static str, ) -> Result { self.as_qs_serializer() .serialize_unit_variant(name, variant_index, variant) } fn serialize_newtype_struct( self, name: &'static str, value: &T, ) -> Result { self.as_qs_serializer() .serialize_newtype_struct(name, value) } fn serialize_newtype_variant( self, name: &'static str, variant_index: u32, variant: &'static str, value: &T, ) -> Result { self.as_qs_serializer() .serialize_newtype_variant(name, variant_index, variant, value) } fn serialize_none(self) -> Result { self.as_qs_serializer().serialize_none() } fn serialize_some(self, value: &T) -> Result { self.as_qs_serializer().serialize_some(value) } fn serialize_seq(self, len: Option) -> Result { self.as_qs_serializer().serialize_seq(len) } fn serialize_tuple(self, len: usize) -> Result { self.as_qs_serializer().serialize_tuple(len) } fn serialize_tuple_struct( self, name: &'static str, len: usize, ) -> Result { 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.as_qs_serializer() .serialize_tuple_variant(name, variant_index, variant, len) } fn serialize_map(self, len: Option) -> Result { self.as_qs_serializer().serialize_map(len) } fn serialize_struct(self, name: &'static str, len: usize) -> Result { 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.as_qs_serializer() .serialize_struct_variant(name, variant_index, variant, len) } } /// A serializer for the querystring format. /// /// * Supported top-level inputs are structs and maps. /// /// * Supported values are currently most primitive types, structs, maps and /// 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>, writer: &'a mut W, first: Arc, } impl<'a, W: 'a + Write> QsSerializer<'a, W> { fn extend_key(&mut self, newkey: &str) { let newkey = percent_encode(newkey.as_bytes(), QS_ENCODE_SET) .map(replace_space) .collect::(); let key = if let Some(ref key) = self.key { format!("{}[{}]", key, newkey) } else { newkey }; self.key = Some(Cow::Owned(key)) } fn write_value(&mut self, value: &[u8]) -> Result<()> { if let Some(ref key) = self.key { let amp = !self.first.swap(false, Ordering::Relaxed); write!( self.writer, "{}{}={}", amp.then_some("&").unwrap_or_default(), key, percent_encode(value, QS_ENCODE_SET) .map(replace_space) .collect::() ) .map_err(Error::from) } else { Err(Error::no_key()) } } fn write_unit(&mut self) -> Result<()> { let amp = !self.first.swap(false, Ordering::Relaxed); if let Some(ref key) = self.key { write!( self.writer, "{}{}=", amp.then_some("&").unwrap_or_default(), key, ) .map_err(Error::from) } else { // For top level unit types write!(self.writer, "{}", amp.then_some("&").unwrap_or_default(),).map_err(Error::from) } } /// 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.clone(), } } } impl Error { fn no_key() -> Self { let msg = "tried to serialize a value before serializing key"; Error::Custom(msg.into()) } } impl<'a, W: Write> ser::Serializer for QsSerializer<'a, 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 = Self; type SerializeStructVariant = Self; serialize_as_string! { Qs 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(mut self, value: &[u8]) -> Result { self.write_value(value) } fn serialize_unit(mut self) -> Result { self.write_unit() } fn serialize_unit_struct(mut self, _: &'static str) -> Result { self.write_unit() } fn serialize_unit_variant( mut self, _name: &'static str, _variant_index: u32, variant: &'static str, ) -> Result { self.write_value(variant.as_bytes()) } fn serialize_newtype_struct( self, _name: &'static str, value: &T, ) -> Result { value.serialize(self) } fn serialize_newtype_variant( mut self, _name: &'static str, _variant_index: u32, variant: &'static str, value: &T, ) -> Result { self.extend_key(variant); value.serialize(self) } fn serialize_none(self) -> Result { Ok(()) } fn serialize_some(self, value: &T) -> Result { value.serialize(self) } fn serialize_seq(self, _len: Option) -> Result { Ok(QsSeq(self, 0)) } fn serialize_tuple(self, _len: usize) -> Result { Ok(QsSeq(self, 0)) } fn serialize_tuple_struct( self, _name: &'static str, _len: usize, ) -> Result { Ok(QsSeq(self, 0)) } fn serialize_tuple_variant( mut self, _name: &'static str, _variant_index: u32, variant: &'static str, _len: usize, ) -> Result { self.extend_key(variant); Ok(QsSeq(self, 0)) } fn serialize_map(self, _len: Option) -> Result { Ok(QsMap(self, None)) } fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { Ok(self) } fn serialize_struct_variant( mut self, _name: &'static str, _variant_index: u32, variant: &'static str, _len: usize, ) -> Result { self.extend_key(variant); Ok(self) } } impl ser::Error for Error { fn custom(msg: T) -> Self where T: Display, { Error::Custom(msg.to_string()) } } #[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>); impl<'a, W: Write> ser::SerializeTuple for QsSeq<'a, W> { type Ok = (); type Error = Error; fn serialize_element(&mut self, value: &T) -> Result<()> where T: ser::Serialize, { let key = self.1.to_string(); self.1 += 1; let mut serializer = QsSerializer::new_from_ref(&mut self.0); serializer.extend_key(&key); value.serialize(serializer) } fn end(self) -> Result { Ok(()) } } impl<'a, W: Write> ser::SerializeSeq for QsSeq<'a, W> { type Ok = (); type Error = Error; fn serialize_element(&mut self, value: &T) -> Result<()> where T: ser::Serialize, { let mut serializer = QsSerializer::new_from_ref(&mut self.0); serializer.extend_key(&self.1.to_string()); self.1 += 1; value.serialize(serializer) } fn end(self) -> Result { Ok(()) } } impl<'a, W: Write> ser::SerializeStruct for QsSerializer<'a, W> { type Ok = (); type Error = Error; fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> where T: ser::Serialize, { let mut serializer = QsSerializer::new_from_ref(self); serializer.extend_key(key); value.serialize(serializer) } fn end(self) -> Result { Ok(()) } } impl<'a, W: Write> ser::SerializeStructVariant for QsSerializer<'a, W> { type Ok = (); type Error = Error; fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> where T: ser::Serialize, { let mut serializer = QsSerializer::new_from_ref(self); serializer.extend_key(key); value.serialize(serializer) } fn end(self) -> Result { Ok(()) } } impl<'a, W: Write> ser::SerializeTupleVariant for QsSeq<'a, W> { type Ok = (); type Error = Error; fn serialize_field(&mut self, value: &T) -> Result<()> where T: ser::Serialize, { let mut serializer = QsSerializer::new_from_ref(&mut self.0); serializer.extend_key(&self.1.to_string()); self.1 += 1; value.serialize(serializer) } fn end(self) -> Result { Ok(()) } } impl<'a, W: Write> ser::SerializeTupleStruct for QsSeq<'a, W> { type Ok = (); type Error = Error; fn serialize_field(&mut self, value: &T) -> Result<()> where T: ser::Serialize, { let mut serializer = QsSerializer::new_from_ref(&mut self.0); serializer.extend_key(&self.1.to_string()); self.1 += 1; value.serialize(serializer) } fn end(self) -> Result { Ok(()) } } impl<'a, W: Write> ser::SerializeMap for QsMap<'a, W> { type Ok = (); type Error = Error; fn serialize_key(&mut self, key: &T) -> Result<()> where T: ser::Serialize, { self.1 = Some(Cow::from(key.serialize(StringSerializer)?)); Ok(()) } fn serialize_value(&mut self, value: &T) -> Result<()> where T: ser::Serialize, { 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(serializer) } fn end(self) -> Result { Ok(()) } fn serialize_entry(&mut self, key: &K, value: &V) -> Result<()> where K: ser::Serialize, V: ser::Serialize, { let mut serializer = QsSerializer::new_from_ref(&mut self.0); serializer.extend_key(&key.serialize(StringSerializer)?); value.serialize(serializer) } } struct StringSerializer; impl ser::Serializer for StringSerializer { type Ok = String; type Error = Error; type SerializeSeq = ser::Impossible; type SerializeTuple = ser::Impossible; type SerializeTupleStruct = ser::Impossible; type SerializeTupleVariant = ser::Impossible; type SerializeMap = ser::Impossible; type SerializeStruct = ser::Impossible; type SerializeStructVariant = ser::Impossible; serialize_as_string! { 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 { Ok(String::from_utf8_lossy(value).to_string()) } /// Returns an error. fn serialize_unit(self) -> Result { Err(Error::Unsupported) } /// Returns an error. fn serialize_unit_struct(self, _name: &'static str) -> Result { Err(Error::Unsupported) } fn serialize_unit_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, ) -> Result { Ok(variant.to_string()) } /// Returns an error. fn serialize_newtype_struct( self, _name: &'static str, _value: &T, ) -> Result { Err(Error::Unsupported) } /// Returns an error. fn serialize_newtype_variant( self, _name: &'static str, _variant_index: u32, _variant: &'static str, _value: &T, ) -> Result { Err(Error::Unsupported) } /// Returns an error. fn serialize_none(self) -> Result { Err(Error::Unsupported) } /// Returns an error. fn serialize_some(self, _value: &T) -> Result { Err(Error::Unsupported) } /// Returns an error. fn serialize_seq(self, _len: Option) -> Result { Err(Error::Unsupported) } fn serialize_tuple(self, _len: usize) -> Result { Err(Error::Unsupported) } /// Returns an error. fn serialize_tuple_struct( self, _name: &'static str, _len: usize, ) -> Result { Err(Error::Unsupported) } fn serialize_tuple_variant( self, _name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize, ) -> Result { Err(Error::Unsupported) } fn serialize_map(self, _len: Option) -> Result { Err(Error::Unsupported) } fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { Err(Error::Unsupported) } fn serialize_struct_variant( self, _name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize, ) -> Result { Err(Error::Unsupported) } } serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/src/utils.rs000066400000000000000000000014231460457264500225350ustar00rootroot00000000000000use 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 { 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")) } } } serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/src/warp.rs000066400000000000000000000043151460457264500223510ustar00rootroot00000000000000//! Functionality for using `serde_qs` with `warp`. //! //! Enable with the `warp` feature. extern crate warp_framework as warp; use crate::{de::Config as QsConfig, error}; use serde::de; use std::sync::Arc; use warp::{http::StatusCode, reject::Reject, Filter, Rejection, Reply}; impl Reject for error::Error {} /// Extract typed information from from the request's query. /// /// ## Example /// /// ```rust /// # extern crate warp_framework as warp; /// # #[macro_use] extern crate serde_derive; /// use warp::Filter; /// use serde_qs::Config; /// /// #[derive(Deserialize)] /// pub struct UsersFilter { /// id: Vec, /// } /// /// fn main() { /// let filter = serde_qs::warp::query(Config::default()) /// .and_then(|info: UsersFilter| async move { /// Ok::<_, warp::Rejection>( /// info.id.iter().map(|i| i.to_string()).collect::>().join(", ") /// ) /// }) /// .recover(serde_qs::warp::recover_fn); /// } /// ``` pub fn query(config: QsConfig) -> impl Filter + Clone where T: de::DeserializeOwned + Send + 'static, { let config = Arc::new(config); warp::query::raw() .or_else(|_| async { tracing::debug!("route was called without a query string, defaulting to empty"); Ok::<_, Rejection>((String::new(),)) }) .and_then(move |query: String| { let config = Arc::clone(&config); async move { config.deserialize_str(query.as_str()).map_err(|err| { tracing::debug!("failed to decode query string '{}': {:?}", query, err); Rejection::from(err) }) } }) } /// Use this as the function for a `.recover()` after assembled filter /// /// This is not strictly required but changes the response from a /// "500 Internal Server Error" to a "400 Bad Request" pub async fn recover_fn(rejection: Rejection) -> Result { if let Some(err) = rejection.find::() { Ok(warp::reply::with_status( err.to_string(), StatusCode::BAD_REQUEST, )) } else { Err(rejection) } } serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/tests/000077500000000000000000000000001460457264500214025ustar00rootroot00000000000000serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/tests/test_actix.rs000066400000000000000000000114731460457264500241250ustar00rootroot00000000000000#![cfg(any(feature = "actix4", feature = "actix3", feature = "actix2"))] #[cfg(feature = "actix2")] extern crate actix_web2 as actix_web; #[cfg(feature = "actix3")] extern crate actix_web3 as actix_web; #[cfg(feature = "actix4")] extern crate actix_web4 as actix_web; extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_qs as qs; use actix_web::error::InternalError; use actix_web::http::StatusCode; use actix_web::test::TestRequest; use actix_web::{FromRequest, HttpResponse}; use qs::actix::{QsForm, QsQuery, QsQueryConfig}; use qs::Config as QsConfig; use serde::de::Error; fn from_str<'de, D, S>(deserializer: D) -> Result where D: serde::Deserializer<'de>, S: std::str::FromStr, { let s = <&str as serde::Deserialize>::deserialize(deserializer)?; S::from_str(&s).map_err(|_| D::Error::custom("could not parse string")) } #[derive(Deserialize, Serialize, Debug, PartialEq)] struct Query { foo: u64, bars: Vec, #[serde(flatten)] common: CommonParams, } #[derive(Deserialize, Serialize, Debug, PartialEq)] struct CommonParams { #[serde(deserialize_with = "from_str")] limit: u64, #[serde(deserialize_with = "from_str")] offset: u64, #[serde(deserialize_with = "from_str")] remaining: bool, } #[test] fn test_default_error_handler() { futures::executor::block_on(async { let req = TestRequest::with_uri("/test").to_srv_request(); let (req, mut pl) = req.into_parts(); let e = QsQuery::::from_request(&req, &mut pl) .await .unwrap_err(); assert_eq!( e.as_response_error().error_response().status(), StatusCode::BAD_REQUEST ); }) } #[test] fn test_custom_error_handler() { futures::executor::block_on(async { let req = TestRequest::with_uri("/test") .app_data(QsQueryConfig::default().error_handler(|e, _| { let resp = HttpResponse::UnprocessableEntity().finish(); dbg!(&resp); InternalError::from_response(e, resp).into() })) .to_srv_request(); let (req, mut pl) = req.into_parts(); let query = QsQuery::::from_request(&req, &mut pl).await; assert!(query.is_err()); assert_eq!( query .unwrap_err() .as_response_error() .error_response() .status(), StatusCode::UNPROCESSABLE_ENTITY ); }) } #[test] fn test_composite_querystring_extractor() { futures::executor::block_on(async { let req = TestRequest::with_uri( "/test?foo=1&bars[]=0&bars[]=1&limit=100&offset=50&remaining=true", ) .to_srv_request(); let (req, mut pl) = req.into_parts(); let s = QsQuery::::from_request(&req, &mut pl).await.unwrap(); assert_eq!(s.foo, 1); assert_eq!(s.bars, vec![0, 1]); assert_eq!(s.common.limit, 100); assert_eq!(s.common.offset, 50); assert_eq!(s.common.remaining, true); }) } #[test] fn test_default_qs_config() { futures::executor::block_on(async { let req = TestRequest::with_uri("/test?foo=1&bars%5B%5D=3&limit=100&offset=50&remaining=true") .to_srv_request(); let (req, mut pl) = req.into_parts(); let e = QsQuery::::from_request(&req, &mut pl) .await .unwrap_err(); assert_eq!( e.as_response_error().error_response().status(), StatusCode::BAD_REQUEST ); }) } #[test] fn test_custom_qs_config() { futures::executor::block_on(async { let req = TestRequest::with_uri("/test?foo=1&bars%5B%5D=3&limit=100&offset=50&remaining=true") .app_data(QsQueryConfig::default().qs_config(QsConfig::new(5, false))) .to_srv_request(); let (req, mut pl) = req.into_parts(); let s = QsQuery::::from_request(&req, &mut pl).await.unwrap(); assert_eq!(s.foo, 1); assert_eq!(s.bars, vec![3]); assert_eq!(s.common.limit, 100); assert_eq!(s.common.offset, 50); assert_eq!(s.common.remaining, true); }) } #[test] fn test_form_extractor() { futures::executor::block_on(async { let test_data = Query { foo: 1, bars: vec![0, 1], common: CommonParams { limit: 100, offset: 50, remaining: true, }, }; let req = TestRequest::with_uri("/test") .set_payload(serde_qs::to_string(&test_data).unwrap()) .to_srv_request(); let (req, mut pl) = req.into_parts(); let s = QsForm::::from_request(&req, &mut pl).await.unwrap(); assert_eq!(s.into_inner(), test_data); }) } serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/tests/test_axum.rs000066400000000000000000000077161460457264500237740ustar00rootroot00000000000000#![cfg(feature = "axum")] extern crate serde; #[macro_use] extern crate serde_derive; extern crate axum_framework as axum; extern crate serde_qs as qs; use axum::{extract::FromRequestParts, http::StatusCode, response::IntoResponse}; use qs::axum::{QsQuery, QsQueryConfig, QsQueryRejection}; use serde::de::Error; fn from_str<'de, D, S>(deserializer: D) -> Result where D: serde::Deserializer<'de>, S: std::str::FromStr, { let s = <&str as serde::Deserialize>::deserialize(deserializer)?; S::from_str(s).map_err(|_| D::Error::custom("could not parse string")) } #[derive(Deserialize, Serialize, Debug, PartialEq)] struct Query { foo: u64, bars: Vec, #[serde(flatten)] common: CommonParams, } #[derive(Deserialize, Serialize, Debug, PartialEq)] struct CommonParams { #[serde(deserialize_with = "from_str")] limit: u64, #[serde(deserialize_with = "from_str")] offset: u64, #[serde(deserialize_with = "from_str")] remaining: bool, } #[test] fn test_default_error_handler() { futures::executor::block_on(async { let req = axum::http::Request::builder() .uri("/test") .body(()) .unwrap(); let (mut req_parts, _) = req.into_parts(); let e = QsQuery::::from_request_parts(&mut req_parts, &()) .await .unwrap_err(); assert_eq!(e.into_response().status(), StatusCode::BAD_REQUEST); }) } #[test] fn test_custom_error_handler() { futures::executor::block_on(async { let req = axum::http::Request::builder() .uri("/test?foo=1&bars%5B%5D=3&limit=100&offset=50&remaining=true") .extension(QsQueryConfig::default().error_handler(|err| { QsQueryRejection::new(err, StatusCode::UNPROCESSABLE_ENTITY) })) .body(()) .unwrap(); let (mut req_parts, _) = req.into_parts(); let query = QsQuery::::from_request_parts(&mut req_parts, &()).await; assert!(query.is_err()); assert_eq!( query.unwrap_err().into_response().status(), StatusCode::UNPROCESSABLE_ENTITY ); }) } #[test] fn test_composite_querystring_extractor() { futures::executor::block_on(async { let req = axum::http::Request::builder() .uri("/test?foo=1&bars[]=0&bars[]=1&limit=100&offset=50&remaining=true") .body(()) .unwrap(); let (mut req_parts, _) = req.into_parts(); let s = QsQuery::::from_request_parts(&mut req_parts, &()) .await .unwrap(); assert_eq!(s.foo, 1); assert_eq!(s.bars, vec![0, 1]); assert_eq!(s.common.limit, 100); assert_eq!(s.common.offset, 50); assert!(s.common.remaining); }) } #[test] fn test_default_qs_config() { futures::executor::block_on(async { let req = axum::http::Request::builder() .uri("/test?foo=1&bars%5B%5D=3&limit=100&offset=50&remaining=true") .body(()) .unwrap(); let (mut req_parts, _) = req.into_parts(); let e = QsQuery::::from_request_parts(&mut req_parts, &()) .await .unwrap_err(); assert_eq!(e.into_response().status(), StatusCode::BAD_REQUEST); }) } #[test] fn test_custom_qs_config() { futures::executor::block_on(async { let req = axum::http::Request::builder() .uri("/test?foo=1&bars%5B%5D=3&limit=100&offset=50&remaining=true") .extension(QsQueryConfig::new(5, false)) .body(()) .unwrap(); let (mut req_parts, _) = req.into_parts(); let s = QsQuery::::from_request_parts(&mut req_parts, &()) .await .unwrap(); assert_eq!(s.foo, 1); assert_eq!(s.bars, vec![3]); assert_eq!(s.common.limit, 100); assert_eq!(s.common.offset, 50); assert!(s.common.remaining); }) } serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/tests/test_chrono.rs000066400000000000000000000024171460457264500243030ustar00rootroot00000000000000extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_qs as qs; #[test] fn test_dates() { use chrono::prelude::*; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] struct Params { date_time: DateTime, } #[allow(deprecated)] let params = Params { date_time: FixedOffset::east(9 * 3600) .ymd(2014, 11, 28) .and_hms_nano(21, 45, 59, 324310806), }; let s = qs::to_string(¶ms).unwrap(); assert_eq!(s, "date_time=2014-11-28T21%3A45%3A59.324310806%2B09%3A00"); let data: Params = qs::from_str(&s).unwrap(); assert_eq!(data, params); } /// Curious what happens if we _don't_ urlencode the string parameter #[test] #[should_panic] fn test_improperly_encoded_dates() { use chrono::prelude::*; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] struct Params { date_time: DateTime, } #[allow(deprecated)] let _expected = Params { date_time: FixedOffset::east(9 * 3600) .ymd(2014, 11, 28) .and_hms_nano(21, 45, 59, 324310806), }; let s = "date_time=2014-11-28T21:45:59.324310806+09:00"; let _data: Params = qs::from_str(s).unwrap(); // assert_eq!(data, params); } serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/tests/test_deserialize.rs000066400000000000000000000500761460457264500253170ustar00rootroot00000000000000extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_qs as qs; 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, } // Compares a map generated by hash_to_map with the map returned by // qs::from_str. All types are inferred by the compiler. macro_rules! map_test { ($string:expr, $($mapvars:tt)*) => { for config in vec![qs::Config::new(5, true), qs::Config::new(5, false)] { let testmap: HashMap<_, _> = config.deserialize_str($string).unwrap(); let expected_map = hash_to_map!(New $($mapvars)*); assert_eq!(testmap, expected_map); } } } // Macro used to quickly generate a nested HashMap from a string. macro_rules! hash_to_map { // Base case: a map with no inputs, do nothing. ($map:expr, ) => (); //{} // This parses a single map entry, with a value explicitly an expression. ($map:expr, $k:tt[e $v:expr] $($rest:tt)*) => {{ $map.insert($k.to_owned(), $v.to_owned()); hash_to_map!($map, $($rest)*); }}; // This parses a single map entry, plus the rest of the values. ($map:expr, $k:tt[$v:tt] $($rest:tt)*) => {{ $map.insert($k.to_owned(), $v.to_owned()); hash_to_map!($map, $($rest)*); }}; // This parses the first entry as a nested entry, and tail calls the // remaining in rest. ($map:expr, $k:tt[$($inner:tt)*] $($rest:tt)*) => {{ let mut inner_map = HashMap::new(); hash_to_map!(inner_map, $($inner)*); $map.insert($k.to_owned(), inner_map); hash_to_map!($map, $($rest)*); }}; // Constructs the map and then runs the macro. This infers the types for the // hashmap. (New $($rest:tt)*) => {{ let mut map = HashMap::new(); hash_to_map!(map, $($rest)*); map }} } #[test] fn deserialize_struct() { let 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], }; for config in vec![qs::Config::new(5, true), qs::Config::new(5, false)] { // standard parameters let rec_params: QueryParams = config .deserialize_str( "\ name=Acme&id=42&phone=12345&address[postcode]=12345&\ address[city]=Carrot+City&user_ids[0]=1&user_ids[1]=2&\ user_ids[2]=3&user_ids[3]=4", ) .unwrap(); assert_eq!(rec_params, params); // unindexed arrays let rec_params: QueryParams = config .deserialize_str( "\ name=Acme&id=42&phone=12345&address[postcode]=12345&\ address[city]=Carrot+City&user_ids[]=1&user_ids[]=2&\ user_ids[]=3&user_ids[]=4", ) .unwrap(); assert_eq!(rec_params, params); // ordering doesn't matter let rec_params: QueryParams = config .deserialize_str( "\ address[city]=Carrot+City&user_ids[]=1&user_ids[]=2&\ name=Acme&id=42&phone=12345&address[postcode]=12345&\ user_ids[]=3&user_ids[]=4", ) .unwrap(); assert_eq!(rec_params, params); } } #[test] fn qs_test_simple() { // test('parse()', function (t) { // t.test('parses a simple string', function (st) { // st.deepEqual(qs.parse('0=foo'), { 0: 'foo' }); map_test!("0=foo", 0["foo"]); // st.deepEqual(qs.parse('&0=foo'), { 0: 'foo' }); map_test!("&0=foo", 0["foo"]); // st.deepEqual(qs.parse('0=foo&'), { 0: 'foo' }); map_test!("0=foo&", 0["foo"]); // st.deepEqual(qs.parse('foo=c++'), { foo: 'c ' }); map_test!("foo=c++", "foo"["c "]); // st.deepEqual(qs.parse('a[>=]=23'), { a: { '>=': '23' } }); map_test!("a[>=]=23", "a"[">="[23]]); // st.deepEqual(qs.parse('a[<=>]==23'), { a: { '<=>': '=23' } }); map_test!("a[<=>]==23", "a"["<=>"["=23"]]); // st.deepEqual(qs.parse('a[==]=23'), { a: { '==': '23' } }); map_test!("a[==]=23", "a"["=="[23]]); // st.deepEqual(qs.parse('foo', { strictNullHandling: true }), // { foo: null }); let none: Option = Option::None; map_test!("foo", "foo"[none]); // st.deepEqual(qs.parse('foo'), { foo: '' }); map_test!("foo", "foo"[""]); // st.deepEqual(qs.parse('foo='), { foo: '' }); map_test!("foo=", "foo"[""]); // st.deepEqual(qs.parse('foo=bar'), { foo: 'bar' }); map_test!("foo=bar", "foo"["bar"]); // st.deepEqual(qs.parse(' foo = bar = baz '), { ' foo ': ' bar = baz ' }); map_test!(" foo = bar = baz ", " foo "[" bar = baz "]); // st.deepEqual(qs.parse('foo=bar=baz'), { foo: 'bar=baz' }); map_test!("foo=bar=baz", "foo"["bar=baz"]); // st.deepEqual(qs.parse('foo=bar&bar=baz'), { foo: 'bar', bar: 'baz' }); map_test!("foo=bar&bar=baz", "foo"["bar"] "bar"["baz"]); // st.deepEqual(qs.parse('foo=bar&&bar=baz'), { foo: 'bar', bar: 'baz' }); map_test!("foo=bar&&bar=baz", "foo"["bar"] "bar"["baz"]); // st.deepEqual(qs.parse('foo2=bar2&baz2='), { foo2: 'bar2', baz2: '' }); map_test!("foo2=bar2&baz2=", "foo2"["bar2"] "baz2"[""]); // st.deepEqual(qs.parse('foo=bar&baz', { strictNullHandling: true }), { // foo: 'bar', baz: null }); map_test!("foo=bar&baz", "foo"[e Some("bar".to_string())] "baz"[e None]); // st.deepEqual(qs.parse('foo=bar&baz'), { foo: 'bar', baz: '' }); map_test!("foo=bar&baz", "foo"["bar"] "baz"[""]); // st.deepEqual(qs.parse('cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World'), // { // cht: 'p3', // chd: 't:60,40', // chs: '250x100', // chl: 'Hello|World' // }); map_test!("cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World", "cht"["p3"] "chd"["t:60,40"] "chs"["250x100"] "chl"["Hello|World"] ); // st.end(); // }); } #[test] fn no_panic_on_parse_error() { #[derive(Debug, Serialize, Deserialize, PartialEq)] struct Query { vec: Vec, } let params: Result = qs::from_str("vec[]=a&vec[]=2"); assert!(params.is_err()) } #[test] fn qs_nesting() { // t.deepEqual(qs.parse('a[b]=c'), { a: { b: 'c' } }, 'parses a single // nested string'); map_test!("a[b]=c", "a"["b"["c"]]); // t.deepEqual(qs.parse('a[b][c]=d'), { a: { b: { c: 'd' } } }, 'parses a // double nested string'); map_test!("a[b][c]=d", "a"["b"["c"["d"]]]); // t.deepEqual( // qs.parse('a[b][c][d][e][f][g][h]=i'), // { a: { b: { c: { d: { e: { f: { '[g][h]': 'i' } } } } } } }, // 'defaults to a depth of 5' // ); // This looks like depth 6 to me? Tweaked test to make it 5. map_test!( "a[b][c][d][e][f][g][h]=i", "a"["b"["c"["d"["e"["[f][g][h]"["i"]]]]]] ); } #[test] fn optional_seq() { #[derive(Debug, Serialize, Deserialize, PartialEq)] struct Query { vec: Option>, } let params = ""; let query = Query { vec: None }; let rec_params: Query = qs::from_str(params).unwrap(); assert_eq!(rec_params, query); let params = "vec="; let query = Query { vec: None }; let rec_params: Query = qs::from_str(params).unwrap(); assert_eq!(rec_params, query); let params = "vec[0]=1&vec[1]=2"; let query = Query { vec: Some(vec![1, 2]), }; let rec_params: Query = qs::from_str(params).unwrap(); assert_eq!(rec_params, query); } #[test] fn optional_struct() { #[derive(Debug, Serialize, Deserialize, PartialEq)] struct Query { address: Option
, } let params = ""; let query = Query { address: None }; let rec_params: Query = qs::from_str(params).unwrap(); assert_eq!(rec_params, query); let params = "address="; let query = Query { address: None }; let rec_params: Query = qs::from_str(params).unwrap(); assert_eq!(rec_params, query); let params = "address[city]=Carrot+City&address[postcode]=12345"; let query = Query { address: Some(Address { city: "Carrot City".to_string(), postcode: "12345".to_string(), }), }; let rec_params: Query = qs::from_str(params).unwrap(); assert_eq!(rec_params, query); } #[test] fn deserialize_enum_untagged() { #[derive(Deserialize, Debug, PartialEq)] #[serde(untagged)] enum E { B(bool), S(String), } #[derive(Deserialize, Debug, PartialEq)] struct Query { e: E, } let params = "e=true"; let rec_params: Query = qs::from_str(params).unwrap(); assert_eq!( rec_params, Query { e: E::S("true".to_string()) } ); } #[test] fn deserialize_enum_adjacently() { #[derive(Deserialize, Debug, PartialEq)] #[serde(tag = "type", content = "val")] enum E { B(bool), S(String), } #[derive(Deserialize, Debug, PartialEq)] #[serde(tag = "type", content = "val")] enum V { V1 { x: u8, y: u16 }, V2(String), } #[derive(Deserialize, Debug, PartialEq)] struct Query { e: E, v: Option, } let params = "e[type]=B&e[val]=true&v[type]=V1&v[val][x]=12&v[val][y]=300"; let rec_params: Query = qs::from_str(params).unwrap(); assert_eq!( rec_params, Query { e: E::B(true), v: Some(V::V1 { x: 12, y: 300 }), } ); let params = "e[type]=S&e[val]=other"; let rec_params: Query = qs::from_str(params).unwrap(); assert_eq!( rec_params, Query { e: E::S("other".to_string()), v: None, } ); } #[test] fn deserialize_enum() { #[derive(Deserialize, Debug, PartialEq)] struct NewU8(u8); #[derive(Deserialize, Debug, PartialEq)] enum E { B, S(String), } #[derive(Deserialize, Debug, PartialEq)] enum V { V1 { x: u8, y: u16 }, V2(String), } #[derive(Deserialize, Debug, PartialEq)] struct Query { e: E, v: Option, u: NewU8, } let params = "e=B&v[V1][x]=12&v[V1][y]=300&u=12"; let rec_params: Query = qs::from_str(params).unwrap(); assert_eq!( rec_params, Query { e: E::B, v: Some(V::V1 { x: 12, y: 300 }), u: NewU8(12), } ); let params = "e[S]=other&u=1"; let rec_params: Query = qs::from_str(params).unwrap(); assert_eq!( rec_params, Query { e: E::S("other".to_string()), v: None, u: NewU8(1), } ); let params = "B="; let rec_params: E = qs::from_str(params).unwrap(); assert_eq!(rec_params, E::B); let params = "S=Hello+World"; let rec_params: E = qs::from_str(params).unwrap(); assert_eq!(rec_params, E::S("Hello World".to_string())); } #[test] fn seq_of_struct() { #[derive(Deserialize, Debug, PartialEq)] struct Test { a: u8, b: u8, } #[derive(Deserialize, Debug, PartialEq)] struct Query { elements: Vec, } let params = "elements[0][a]=1&elements[0][b]=3&elements[1][a]=2&elements[1][b]=4"; let rec_params: Query = qs::from_str(params).unwrap(); assert_eq!( rec_params, Query { elements: vec![Test { a: 1, b: 3 }, Test { a: 2, b: 4 }] } ); } #[should_panic] #[test] fn unsupported_seq_of_struct() { #[derive(Deserialize, Debug, PartialEq)] struct Test { a: u8, b: u8, } #[derive(Deserialize, Debug, PartialEq)] struct Query { elements: Vec, } let params = "elements[][a]=1&elements[][b]=3&elements[][a]=2&elements[][b]=4"; let rec_params: Query = qs::from_str(params).unwrap(); assert_eq!( rec_params, Query { elements: vec![Test { a: 1, b: 3 }, Test { a: 2, b: 4 }] } ); } #[test] fn correct_decoding() { map_test!("foo=%24", "foo"["$"]); map_test!("foo=%26", "foo"["&"]); } #[test] fn returns_errors() { #[derive(Debug, Serialize, Deserialize, PartialEq)] struct Query { vec: Vec, } let params: Result = qs::from_str("vec[[]=1&vec[]=2"); assert!(params.is_err()); println!("{}", params.unwrap_err()); let params: Result = qs::from_str("vec[\x00[]=1&vec[]=2"); assert!(params.is_err()); println!("{}", params.unwrap_err()); let params: Result = qs::from_str("vec[0]=1&vec[0]=2"); assert!(params.is_err()); println!("{}", params.unwrap_err()); } #[test] fn strict_mode() { #[derive(Deserialize, Serialize, Debug, PartialEq)] struct Test { a: u8, } #[derive(Debug, Serialize, Deserialize, PartialEq)] #[serde(deny_unknown_fields)] struct Query { vec: Vec, } let strict_config = qs::Config::default(); let params: Result = strict_config.deserialize_str("vec%5B0%5D%5Ba%5D=1&vec[1][a]=2"); assert!(params.is_err()); println!("{}", params.unwrap_err()); let loose_config = qs::Config::new(5, false); let params: Result = 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 }] } ); let params: Result = 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 }] } ); let params: Result = 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 }] } ); let params: Result = 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!("Encoded as: {}", enc_params); let rec_params: Result = 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 = loose_config.deserialize_str(&enc_params); assert!(rec_params.is_err()); println!("{}", rec_params.unwrap_err()); // Test that we don't panic let malformed_params: Result = loose_config.deserialize_str("%"); assert!(malformed_params.is_err()); #[derive(Debug, Serialize, Deserialize, PartialEq)] struct Query2 { vec: Vec, } let repeated_key: Result = strict_config.deserialize_str("vec%5B%5D=1&vec%5B%5D=2"); assert!(repeated_key.is_err()); println!("{}", repeated_key.unwrap_err()); let params: Query2 = loose_config .deserialize_str("vec%5B%5D=1&vec%5B%5D=2") .unwrap(); assert_eq!(params.vec, vec![1, 2]); #[derive(Debug, Serialize, Deserialize, PartialEq)] struct StringQueryParam { field: String, } // Ensure strict mode produces an error for invalid UTF-8 percent encoded characters. let invalid_utf8: Result = strict_config.deserialize_str("field=%E9"); assert!(invalid_utf8.is_err()); // Ensure loose mode invalid UTF-8 percent encoded characters become � U+FFFD. let valid_utf8: StringQueryParam = loose_config.deserialize_str("field=%E9").unwrap(); assert_eq!(valid_utf8.field, "�"); } #[test] fn square_brackets_in_values() { map_test!("foo=%5BHello%5D", "foo"["[Hello]"]); } #[test] #[ignore] fn deserialize_flatten() { #[derive(Deserialize, Serialize, Debug, PartialEq)] struct Query { a: u8, #[serde(flatten)] common: CommonParams, } #[derive(Deserialize, Serialize, Debug, PartialEq)] struct CommonParams { limit: u64, offset: u64, remaining: bool, } let params = "a=1&limit=100&offset=50&remaining=true"; let query = Query { a: 1, common: CommonParams { limit: 100, offset: 50, remaining: true, }, }; let rec_query: Result = qs::from_str(params); assert_eq!(rec_query.unwrap(), query); } #[test] fn deserialize_flatten_workaround() { #[derive(Deserialize, Serialize, Debug, PartialEq)] struct Query { a: u8, #[serde(flatten)] common: CommonParams, } #[derive(Deserialize, Serialize, Debug, PartialEq)] struct CommonParams { #[serde(deserialize_with = "from_str")] limit: u64, #[serde(deserialize_with = "from_str")] offset: u64, #[serde(deserialize_with = "from_str")] remaining: bool, } let params = "a=1&limit=100&offset=50&remaining=true"; let query = Query { a: 1, common: CommonParams { limit: 100, offset: 50, remaining: true, }, }; let rec_query: Result = qs::from_str(params); assert_eq!(rec_query.unwrap(), query); } use serde::de::Error; fn from_str<'de, D, S>(deserializer: D) -> Result where D: serde::Deserializer<'de>, S: std::str::FromStr, { let s = <&str as serde::Deserialize>::deserialize(deserializer)?; S::from_str(s).map_err(|_| D::Error::custom("could not parse string")) } #[test] fn deserialize_plus() { #[derive(Deserialize)] struct Test { email: String, } let test: Test = serde_qs::from_str("email=a%2Bb%40c.com").unwrap(); assert_eq!(test.email, "a+b@c.com"); } #[test] fn deserialize_map_with_unit_enum_keys() { #[derive(Deserialize, Eq, PartialEq, Hash)] enum Operator { Lt, Gt, } #[derive(Deserialize)] struct Filter { point: HashMap, } let test: Filter = serde_qs::from_str("point[Gt]=123&point[Lt]=321").unwrap(); assert_eq!(test.point[&Operator::Gt], 123); assert_eq!(test.point[&Operator::Lt], 321); } #[test] fn deserialize_map_with_int_keys() { #[derive(Debug, Deserialize)] struct Mapping { mapping: HashMap, } let test: Mapping = serde_qs::from_str("mapping[1]=2&mapping[3]=4").unwrap(); assert_eq!(test.mapping.get(&1).cloned(), Some(2)); assert_eq!(test.mapping.get(&3).cloned(), Some(4)); assert_eq!(test.mapping.get(&2).cloned(), None); serde_qs::from_str::("mapping[1]=2&mapping[1]=4") .expect_err("should error with repeated key"); } #[test] fn deserialize_unit_types() { // allow these clippy lints cause I like how explicit the test is #![allow(clippy::let_unit_value)] #![allow(clippy::unit_cmp)] #[derive(Debug, Deserialize, PartialEq)] struct A; #[derive(Debug, Deserialize, PartialEq)] struct B<'a> { t: (), a: &'a str, } let test: () = serde_qs::from_str("").unwrap(); assert_eq!(test, ()); let test: A = serde_qs::from_str("").unwrap(); assert_eq!(test, A); let test: B = serde_qs::from_str("a=test&t=").unwrap(); assert_eq!(test, B { t: (), a: "test" }); let test: B = serde_qs::from_str("t=&a=test").unwrap(); assert_eq!(test, B { t: (), a: "test" }); } #[test] fn serialization_roundtrip() { #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] struct Data { #[serde(default)] values: Vec, } let data = Data { values: Vec::new() }; let serialized = serde_qs::to_string(&data).unwrap(); dbg!(&serialized); let deserialized = serde_qs::from_str::(&serialized).unwrap(); assert_eq!(deserialized, data); } serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/tests/test_regression.rs000066400000000000000000000007571460457264500252000ustar00rootroot00000000000000extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_qs as qs; #[test] fn double_encoding_keys() { #[derive(Debug, Serialize, Deserialize, PartialEq)] struct Human { #[serde(rename = "full name")] name: String, } let human = Human { name: "John Doe".to_string(), }; let encoded = serde_qs::to_string(&human).unwrap(); print!("{}", encoded); assert_eq!(serde_qs::from_str::(&encoded).unwrap(), human); } serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/tests/test_serialize.rs000066400000000000000000000146511460457264500250050ustar00rootroot00000000000000#[macro_use] extern crate serde_derive; extern crate serde_qs as qs; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] struct Address { city: String, street: String, postcode: String, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] struct QueryParams { id: u8, name: String, phone: u32, address: Address, user_ids: Vec, } #[test] fn serialize_struct() { 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], }; assert_eq!( qs::to_string(¶ms).unwrap(), "\ id=42&name=Acme&phone=12345&address[city]=Carrot+City&\ address[street]=Special-Street*+No.+11&\ address[postcode]=12345&user_ids[0]=1&user_ids[1]=2&\ user_ids[2]=3&user_ids[3]=4" ); } #[test] fn serialize_option() { #[derive(Debug, Serialize, Deserialize, PartialEq)] struct Query { vec: Option>, } let params = ""; let query = Query { vec: None }; let rec_params = qs::to_string(&query).unwrap(); assert_eq!(rec_params, params); let params = "vec[0]=1&vec[1]=2"; let query = Query { vec: Some(vec![1, 2]), }; let rec_params = qs::to_string(&query).unwrap(); assert_eq!(rec_params, params); } #[test] fn serialize_enum() { #[derive(Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "lowercase")] enum TestEnum { A, B(bool), C { x: u8, y: u8 }, D(u8, u8), } #[derive(Debug, Serialize, Deserialize, PartialEq)] struct Query { e: TestEnum, } 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 = "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 = "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 = "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); } #[test] fn serialize_flatten() { #[derive(Deserialize, Serialize, Debug, PartialEq)] struct Query { a: u8, #[serde(flatten)] common: CommonParams, } #[derive(Deserialize, Serialize, Debug, PartialEq)] struct CommonParams { limit: u64, offset: u64, } let params = "a=1&limit=100&offset=50"; let query = Query { a: 1, common: CommonParams { limit: 100, offset: 50, }, }; let rec_params = qs::to_string(&query).unwrap(); assert_eq!(rec_params, params); } #[test] fn serialize_map_with_unit_enum_keys() { use std::collections::HashMap; #[derive(Serialize, Eq, PartialEq, Hash)] enum Operator { Lt, Gt, } #[derive(Serialize)] struct Filter { point: HashMap, } let mut map = HashMap::new(); map.insert(Operator::Gt, 123); map.insert(Operator::Lt, 321); let test = Filter { point: map }; let query = qs::to_string(&test).unwrap(); assert!(query == "point[Lt]=321&point[Gt]=123" || query == "point[Gt]=123&point[Lt]=321"); } #[test] fn serialize_bytes() { struct Bytes(&'static [u8]); #[derive(Serialize)] struct Query { bytes: Bytes, } impl serde::Serialize for Bytes { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_bytes(self.0) } } let bytes = Bytes(b"hello, world!"); let s = qs::to_string(&Query { bytes }).unwrap(); assert_eq!(s, "bytes=hello%2C+world%21"); } #[test] fn serialize_hashmap_keys() { // Issue: https://github.com/samscott89/serde_qs/issues/45 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] struct HashParams { attrs: std::collections::HashMap, } let data = HashParams { attrs: vec![ ("key 1!".to_owned(), "val 1".to_owned()), ("key 2!".to_owned(), "val 2".to_owned()), ] .into_iter() .collect(), }; let s = qs::to_string(&data).unwrap(); assert!( s == "attrs[key+1%21]=val+1&attrs[key+2%21]=val+2" || 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, 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"); } #[test] fn test_serializer_unit() { use serde::Serialize; #[derive(Serialize)] struct A; #[derive(Serialize)] struct B { t: (), } let mut writer = Vec::new(); { let serializer = &mut qs::Serializer::new(&mut writer); // allow this clippy lints cause I like how explicit the test is #[allow(clippy::let_unit_value)] let q = (); q.serialize(serializer).unwrap(); } assert_eq!(writer, b"", "we are testing ()"); writer.clear(); { let serializer = &mut qs::Serializer::new(&mut writer); let q = A; q.serialize(serializer).unwrap(); } assert_eq!(writer, b"", "we are testing A"); writer.clear(); { let serializer = &mut qs::Serializer::new(&mut writer); let q = B { t: () }; q.serialize(serializer).unwrap(); } assert_eq!(writer, b"t=", "we are testing B{{t: ()}}"); } serde_qs-c6e5914a31e0d602695a3ea601f6976a1ab07d0e/tests/test_warp.rs000066400000000000000000000054651460457264500237720ustar00rootroot00000000000000#![cfg(feature = "warp")] extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_qs as qs; extern crate warp_framework as warp; use qs::Config as QsConfig; use serde::de::Error; use warp::{http::StatusCode, Filter}; fn from_str<'de, D, S>(deserializer: D) -> Result where D: serde::Deserializer<'de>, S: std::str::FromStr, { let s = <&str as serde::Deserialize>::deserialize(deserializer)?; S::from_str(&s).map_err(|_| D::Error::custom("could not parse string")) } #[derive(Deserialize, Serialize, Debug, PartialEq)] struct Query { foo: u64, bars: Vec, #[serde(flatten)] common: CommonParams, } #[derive(Deserialize, Serialize, Debug, PartialEq)] struct CommonParams { #[serde(deserialize_with = "from_str")] limit: u64, #[serde(deserialize_with = "from_str")] offset: u64, #[serde(deserialize_with = "from_str")] remaining: bool, } #[test] fn test_default_error_handler() { futures::executor::block_on(async { let filter = qs::warp::query::(QsConfig::default()) .map(|_| "") .recover(qs::warp::recover_fn); let resp = warp::test::request().path("/test").reply(&filter).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); }) } #[test] fn test_composite_querystring_extractor() { futures::executor::block_on(async { let filter = qs::warp::query::(QsConfig::default()); let s = warp::test::request() .path("/test?foo=1&bars[]=0&bars[]=1&limit=100&offset=50&remaining=true") .filter(&filter) .await .unwrap(); assert_eq!(s.foo, 1); assert_eq!(s.bars, vec![0, 1]); assert_eq!(s.common.limit, 100); assert_eq!(s.common.offset, 50); assert_eq!(s.common.remaining, true); }) } #[test] fn test_default_qs_config() { futures::executor::block_on(async { let filter = qs::warp::query::(QsConfig::default()) .map(|_| "") .recover(qs::warp::recover_fn); let resp = warp::test::request() .path("/test?foo=1&bars%5B%5D=3&limit=100&offset=50&remaining=true") .reply(&filter) .await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); }) } #[test] fn test_custom_qs_config() { futures::executor::block_on(async { let filter = qs::warp::query::(QsConfig::new(5, false)); let s = warp::test::request() .path("/test?foo=1&bars%5B%5D=3&limit=100&offset=50&remaining=true") .filter(&filter) .await .unwrap(); assert_eq!(s.foo, 1); assert_eq!(s.bars, vec![3]); assert_eq!(s.common.limit, 100); assert_eq!(s.common.offset, 50); assert_eq!(s.common.remaining, true); }) }