Skip to main content

ploidy_util/
query.rs

1//! OpenAPI query parameter serialization.
2//!
3//! This module provides Serde-based serialization for OpenAPI 3.x
4//! query parameters, and supports all standard query styles:
5//! `form`, `deepObject`, `spaceDelimited`, and `pipeDelimited`.
6//!
7//! # Examples
8//!
9//! ```
10//! use url::Url;
11//! use serde::Serialize;
12//! use ploidy_util::query::{QuerySerializer, QueryStyle};
13//! # use ploidy_util::query::QueryParamError;
14//!
15//! # fn main() -> Result<(), QueryParamError> {
16//! #[derive(Serialize)]
17//! #[serde(rename_all = "lowercase")]
18//! enum Kind {
19//!     Dog,
20//!     Cat,
21//!     Fish,
22//!     Bunny,
23//! }
24//!
25//! // Serialize a struct's fields as query parameters, using
26//! // the default exploded `form` style for each parameter.
27//! #[derive(Serialize)]
28//! struct PetQuery {
29//!     kind: Vec<Kind>,
30//!     limit: i32,
31//! }
32//!
33//! let url = Url::parse("https://api.example.com/pets").unwrap();
34//! let query = PetQuery { kind: vec![Kind::Dog, Kind::Cat], limit: 10 };
35//! let url = query.serialize(QuerySerializer::new(url, &[]))?;
36//! assert_eq!(url.as_str(), "https://api.example.com/pets?kind=dog&kind=cat&limit=10");
37//!
38//! // Fields can use different styles. Here, `filter` uses
39//! // `deepObject` while `limit` uses the default `form` style.
40//! #[derive(Serialize)]
41//! struct Filter {
42//!     kind: Vec<Kind>,
43//!     term: String,
44//!     max_price: u32,
45//! }
46//!
47//! #[derive(Serialize)]
48//! struct SearchQuery {
49//!     filter: Filter,
50//!     limit: i32,
51//! }
52//!
53//! let url = Url::parse("https://api.example.com/search").unwrap();
54//! let query = SearchQuery {
55//!     filter: Filter {
56//!         kind: vec![Kind::Dog, Kind::Cat, Kind::Bunny],
57//!         term: "chow".to_owned(),
58//!         max_price: 30,
59//!     },
60//!     limit: 10,
61//! };
62//! let url = query.serialize(QuerySerializer::new(url, &[
63//!     ("filter", QueryStyle::DeepObject),
64//! ]))?;
65//! assert!(url.as_str().starts_with(
66//!     "https://api.example.com/search?filter%5Bkind%5D%5B0%5D=dog"
67//! ));
68//! # Ok(())
69//! # }
70//! ```
71
72use std::{borrow::Cow, fmt::Display};
73
74use itertools::Itertools;
75use percent_encoding::{AsciiSet, CONTROLS, PercentEncode};
76use serde::{
77    Serialize,
78    ser::{Impossible, SerializeMap, SerializeSeq, SerializeStruct, SerializeTuple, Serializer},
79};
80use url::Url;
81
82/// Styles that describe how to format URL query parameters.
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum QueryStyle {
85    /// Multiple values formatted as repeated parameters (if exploded),
86    /// or comma-separated values (if non-exploded).
87    ///
88    /// The exploded `form` style is the default.
89    Form { exploded: bool },
90
91    /// Multiple values separated by spaces.
92    SpaceDelimited,
93
94    /// Multiple values separated by pipes.
95    PipeDelimited,
96
97    /// Bracket notation for nested structures.
98    DeepObject,
99}
100
101impl Default for QueryStyle {
102    fn default() -> Self {
103        Self::Form { exploded: true }
104    }
105}
106
107/// A [`Serializer`] that formats and appends OpenAPI-style
108/// query parameters to a URL.
109pub struct QuerySerializer<'a> {
110    url: Url,
111    styles: &'a [(&'a str, QueryStyle)],
112}
113
114impl<'a> QuerySerializer<'a> {
115    /// Creates a new serializer.
116    pub fn new(url: Url, styles: &'a [(&'a str, QueryStyle)]) -> Self {
117        Self { url, styles }
118    }
119}
120
121impl<'a> Serializer for QuerySerializer<'a> {
122    type Ok = Url;
123    type Error = QueryParamError;
124
125    type SerializeSeq = Impossible<Self::Ok, Self::Error>;
126    type SerializeTuple = Impossible<Self::Ok, Self::Error>;
127    type SerializeTupleStruct = Impossible<Self::Ok, Self::Error>;
128    type SerializeTupleVariant = Impossible<Self::Ok, Self::Error>;
129    type SerializeMap = Impossible<Self::Ok, Self::Error>;
130    type SerializeStruct = Self;
131    type SerializeStructVariant = Impossible<Self::Ok, Self::Error>;
132
133    fn serialize_struct(
134        self,
135        _: &'static str,
136        _: usize,
137    ) -> Result<Self::SerializeStruct, Self::Error> {
138        Ok(self)
139    }
140
141    fn serialize_bool(self, _: bool) -> Result<Self::Ok, Self::Error> {
142        Err(QueryParamError::ExpectedStruct)
143    }
144
145    fn serialize_i8(self, _: i8) -> Result<Self::Ok, Self::Error> {
146        Err(QueryParamError::ExpectedStruct)
147    }
148
149    fn serialize_i16(self, _: i16) -> Result<Self::Ok, Self::Error> {
150        Err(QueryParamError::ExpectedStruct)
151    }
152
153    fn serialize_i32(self, _: i32) -> Result<Self::Ok, Self::Error> {
154        Err(QueryParamError::ExpectedStruct)
155    }
156
157    fn serialize_i64(self, _: i64) -> Result<Self::Ok, Self::Error> {
158        Err(QueryParamError::ExpectedStruct)
159    }
160
161    fn serialize_u8(self, _: u8) -> Result<Self::Ok, Self::Error> {
162        Err(QueryParamError::ExpectedStruct)
163    }
164
165    fn serialize_u16(self, _: u16) -> Result<Self::Ok, Self::Error> {
166        Err(QueryParamError::ExpectedStruct)
167    }
168
169    fn serialize_u32(self, _: u32) -> Result<Self::Ok, Self::Error> {
170        Err(QueryParamError::ExpectedStruct)
171    }
172
173    fn serialize_u64(self, _: u64) -> Result<Self::Ok, Self::Error> {
174        Err(QueryParamError::ExpectedStruct)
175    }
176
177    fn serialize_f32(self, _: f32) -> Result<Self::Ok, Self::Error> {
178        Err(QueryParamError::ExpectedStruct)
179    }
180
181    fn serialize_f64(self, _: f64) -> Result<Self::Ok, Self::Error> {
182        Err(QueryParamError::ExpectedStruct)
183    }
184
185    fn serialize_char(self, _: char) -> Result<Self::Ok, Self::Error> {
186        Err(QueryParamError::ExpectedStruct)
187    }
188
189    fn serialize_str(self, _: &str) -> Result<Self::Ok, Self::Error> {
190        Err(QueryParamError::ExpectedStruct)
191    }
192
193    fn serialize_bytes(self, _: &[u8]) -> Result<Self::Ok, Self::Error> {
194        Err(QueryParamError::ExpectedStruct)
195    }
196
197    fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
198        Err(QueryParamError::ExpectedStruct)
199    }
200
201    fn serialize_some<T: ?Sized + Serialize>(self, _: &T) -> Result<Self::Ok, Self::Error> {
202        Err(QueryParamError::ExpectedStruct)
203    }
204
205    fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
206        Err(QueryParamError::ExpectedStruct)
207    }
208
209    fn serialize_unit_struct(self, _: &'static str) -> Result<Self::Ok, Self::Error> {
210        Err(QueryParamError::ExpectedStruct)
211    }
212
213    fn serialize_unit_variant(
214        self,
215        _: &'static str,
216        _: u32,
217        _: &'static str,
218    ) -> Result<Self::Ok, Self::Error> {
219        Err(QueryParamError::ExpectedStruct)
220    }
221
222    fn serialize_newtype_struct<T: ?Sized + Serialize>(
223        self,
224        _: &'static str,
225        _: &T,
226    ) -> Result<Self::Ok, Self::Error> {
227        Err(QueryParamError::ExpectedStruct)
228    }
229
230    fn serialize_newtype_variant<T: ?Sized + Serialize>(
231        self,
232        _: &'static str,
233        _: u32,
234        _: &'static str,
235        _: &T,
236    ) -> Result<Self::Ok, Self::Error> {
237        Err(QueryParamError::ExpectedStruct)
238    }
239
240    fn serialize_seq(self, _: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
241        Err(QueryParamError::ExpectedStruct)
242    }
243
244    fn serialize_tuple(self, _: usize) -> Result<Self::SerializeTuple, Self::Error> {
245        Err(QueryParamError::ExpectedStruct)
246    }
247
248    fn serialize_tuple_struct(
249        self,
250        _: &'static str,
251        _: usize,
252    ) -> Result<Self::SerializeTupleStruct, Self::Error> {
253        Err(QueryParamError::ExpectedStruct)
254    }
255
256    fn serialize_tuple_variant(
257        self,
258        _: &'static str,
259        _: u32,
260        _: &'static str,
261        _: usize,
262    ) -> Result<Self::SerializeTupleVariant, Self::Error> {
263        Err(QueryParamError::ExpectedStruct)
264    }
265
266    fn serialize_map(self, _: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
267        Err(QueryParamError::ExpectedStruct)
268    }
269
270    fn serialize_struct_variant(
271        self,
272        _: &'static str,
273        _: u32,
274        _: &'static str,
275        _: usize,
276    ) -> Result<Self::SerializeStructVariant, Self::Error> {
277        Err(QueryParamError::ExpectedStruct)
278    }
279}
280
281impl SerializeStruct for QuerySerializer<'_> {
282    type Ok = Url;
283    type Error = QueryParamError;
284
285    fn serialize_field<T: ?Sized + Serialize>(
286        &mut self,
287        key: &'static str,
288        value: &T,
289    ) -> Result<(), Self::Error> {
290        let style = self
291            .styles
292            .iter()
293            .find(|&&(name, _)| name == key)
294            .map(|&(_, style)| style)
295            .unwrap_or_default();
296        let mut path = KeyPath::new(key);
297        let mut serializer = QueryParamSerializer::new(
298            &mut self.url,
299            &mut path,
300            ParamSerializerState::for_style(style),
301        );
302        value.serialize(&mut serializer)?;
303        serializer.flush();
304        Ok(())
305    }
306
307    fn end(self) -> Result<Self::Ok, Self::Error> {
308        Ok(self.url)
309    }
310}
311
312#[derive(Debug)]
313enum ParamSerializerState {
314    /// Non-exploded `spaceDelimited` or `pipeDelimited` style.
315    Delimited(&'static str, Vec<String>),
316    /// Exploded `form` style.
317    ExplodedForm,
318    /// Non-exploded `form` style.
319    NonExplodedForm(Vec<String>),
320    /// Exploded `deepObject` style.
321    DeepObject,
322}
323
324impl ParamSerializerState {
325    /// Creates the serializer state for a given query style.
326    fn for_style(style: QueryStyle) -> Self {
327        match style {
328            QueryStyle::DeepObject => Self::DeepObject,
329            QueryStyle::Form { exploded: true } => Self::ExplodedForm,
330            QueryStyle::Form { exploded: false } => Self::NonExplodedForm(vec![]),
331            QueryStyle::PipeDelimited => Self::Delimited("|", vec![]),
332            QueryStyle::SpaceDelimited => Self::Delimited(" ", vec![]),
333        }
334    }
335}
336
337#[derive(Clone, Debug)]
338struct KeyPath<'a>(Cow<'a, str>, Vec<Cow<'a, str>>);
339
340impl<'a> KeyPath<'a> {
341    fn new(head: impl Into<Cow<'a, str>>) -> Self {
342        Self(head.into(), vec![])
343    }
344
345    fn len(&self) -> usize {
346        self.1.len() + 1
347    }
348
349    fn push(&mut self, segment: impl Into<Cow<'a, str>>) {
350        self.1.push(segment.into());
351    }
352
353    fn pop(&mut self) -> Cow<'_, str> {
354        self.1.pop().unwrap_or_else(|| Cow::Borrowed(&self.0))
355    }
356
357    fn first(&self) -> &str {
358        &self.0
359    }
360
361    fn last(&self) -> &str {
362        self.1.last().unwrap_or(&self.0)
363    }
364
365    fn split_first(&self) -> (&str, &[Cow<'a, str>]) {
366        (&self.0, &self.1)
367    }
368}
369
370/// The [component percent-encode set][component], as defined by
371/// the WHATWG URL Standard.
372///
373/// This is the [userinfo percent-encode set][userinfo] and
374/// U+0024 (`$`) to U+0026 (`&`), inclusive; U+002B (`+`); and U+002C (`,`).
375/// It gives identical results to JavaScript's `encodeURIComponent()`
376/// function.
377///
378/// [component]: https://url.spec.whatwg.org/#component-percent-encode-set
379/// [userinfo]: https://url.spec.whatwg.org/#userinfo-percent-encode-set
380const COMPONENT: &AsciiSet = &CONTROLS
381    .add(b' ')
382    .add(b'"')
383    .add(b'#')
384    .add(b'<')
385    .add(b'>')
386    .add(b'?')
387    .add(b'`')
388    .add(b'^')
389    .add(b'{')
390    .add(b'}')
391    .add(b'/')
392    .add(b':')
393    .add(b';')
394    .add(b'=')
395    .add(b'@')
396    .add(b'[')
397    .add(b'\\')
398    .add(b']')
399    .add(b'|')
400    .add(b'$')
401    .add(b'%')
402    .add(b'&')
403    .add(b'+')
404    .add(b',');
405
406#[derive(Clone, Debug)]
407enum EncodedOrRaw<'a> {
408    Encoded(PercentEncode<'a>),
409    Raw(&'a str),
410}
411
412impl<'a> EncodedOrRaw<'a> {
413    fn encode(input: &'a str) -> Self {
414        Self::Encoded(percent_encoding::utf8_percent_encode(input, COMPONENT))
415    }
416}
417
418impl Display for EncodedOrRaw<'_> {
419    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
420        match self {
421            Self::Encoded(s) => write!(f, "{s}"),
422            Self::Raw(s) => f.write_str(s),
423        }
424    }
425}
426
427#[derive(Debug)]
428struct PercentEncodeDelimited<'a, T>(&'a [T], EncodedOrRaw<'a>);
429
430impl<T: AsRef<str>> Display for PercentEncodeDelimited<'_, T> {
431    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
432        write!(
433            f,
434            "{}",
435            // Use the fully qualified syntax as suggested by
436            // rust-lang/rust#79524.
437            Itertools::intersperse(
438                self.0
439                    .iter()
440                    .map(|input| input.as_ref())
441                    .map(EncodedOrRaw::encode),
442                self.1.clone()
443            )
444            .format("")
445        )
446    }
447}
448
449/// A [`Serializer`] for a single query parameter.
450#[derive(Debug)]
451struct QueryParamSerializer<'a> {
452    /// A mutable reference to the URL being constructed.
453    url: &'a mut Url,
454    /// The current key path, starting with the parameter name.
455    /// The serializer pushes and pops additional segments for
456    /// nested structures.
457    path: &'a mut KeyPath<'a>,
458    state: ParamSerializerState,
459}
460
461impl<'a> QueryParamSerializer<'a> {
462    /// Creates a new query parameter serializer.
463    fn new(url: &'a mut Url, path: &'a mut KeyPath<'a>, state: ParamSerializerState) -> Self {
464        Self { url, path, state }
465    }
466
467    /// Computes the key for the current value, accounting for nesting.
468    fn key(&self) -> Cow<'_, str> {
469        use ParamSerializerState::*;
470        match &self.state {
471            DeepObject => {
472                // `deepObject` style uses `base[field1][field2]...`.
473                match self.path.split_first() {
474                    (head, []) => head.into(),
475                    (head, rest) => format!("{head}[{}]", rest.iter().format("][")).into(),
476                }
477            }
478            ExplodedForm => {
479                // Exploded `form` style uses the field name directly.
480                self.path.last().into()
481            }
482            NonExplodedForm(_) | Delimited(_, _) => {
483                // Non-exploded styles use the base parameter name directly.
484                self.path.first().into()
485            }
486        }
487    }
488
489    /// Appends an unencoded value, either to the buffer or directly to the URL.
490    fn append<'b>(&mut self, value: impl Into<Cow<'b, str>>) {
491        use ParamSerializerState::*;
492        let value = value.into();
493        match &mut self.state {
494            NonExplodedForm(buf) | Delimited(_, buf) => {
495                buf.push(value.into_owned());
496            }
497            DeepObject | ExplodedForm => {
498                // For exploded styles, append the key and value directly to the URL.
499                // This encodes them using `form-urlencoded` rules, not percent-encoding;
500                // OpenAPI allows both here.
501                let key = self.key().into_owned();
502                self.url.query_pairs_mut().append_pair(&key, &value);
503            }
504        }
505    }
506
507    /// Flushes any buffered values to the URL.
508    ///
509    /// This is called by compound serializers when they finish collecting values,
510    /// and by [`Serializer::append`] to write top-level values.
511    fn flush(&mut self) {
512        use ParamSerializerState::*;
513        let (delimiter, buf) = match &mut self.state {
514            NonExplodedForm(buf) => (
515                // For the non-exploded `form` style, commas aren't encoded.
516                EncodedOrRaw::Raw(","),
517                std::mem::take(buf),
518            ),
519            Delimited(delimiter, buf) => (
520                // For `spaceDelimited` and `pipeDelimited`, delimiters are encoded.
521                EncodedOrRaw::encode(delimiter),
522                std::mem::take(buf),
523            ),
524            _ => return,
525        };
526        if buf.is_empty() {
527            return;
528        }
529
530        let key = self.key();
531        let key = EncodedOrRaw::encode(&key);
532        let value = PercentEncodeDelimited(&buf, delimiter);
533
534        // Append the percent-encoded key and value to the existing query string.
535        // We avoid `query_pairs_mut()` here, because it uses `form-urlencoded` rules,
536        // while OpenAPI requires percent-encoding for "non-RFC6570 query string styles".
537        let new_query = match self.url.query().map(|q| q.trim_end_matches('&')) {
538            Some(query) if !query.is_empty() => format!("{query}&{key}={value}"),
539            _ => format!("{key}={value}"),
540        };
541        self.url.set_query(Some(&new_query));
542    }
543}
544
545impl<'a, 'b> Serializer for &'a mut QueryParamSerializer<'b> {
546    type Ok = ();
547    type Error = QueryParamError;
548
549    type SerializeSeq = QuerySeqSerializer<'a, 'b>;
550    type SerializeTuple = QuerySeqSerializer<'a, 'b>;
551    type SerializeTupleStruct = Impossible<Self::Ok, Self::Error>;
552    type SerializeTupleVariant = Impossible<Self::Ok, Self::Error>;
553    type SerializeMap = QueryStructSerializer<'a, 'b>;
554    type SerializeStruct = QueryStructSerializer<'a, 'b>;
555    type SerializeStructVariant = Impossible<Self::Ok, Self::Error>;
556
557    fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
558        self.append(if v { "true" } else { "false" });
559        Ok(())
560    }
561
562    fn serialize_i8(self, v: i8) -> Result<Self::Ok, Self::Error> {
563        self.append(v.to_string());
564        Ok(())
565    }
566
567    fn serialize_i16(self, v: i16) -> Result<Self::Ok, Self::Error> {
568        self.append(v.to_string());
569        Ok(())
570    }
571
572    fn serialize_i32(self, v: i32) -> Result<Self::Ok, Self::Error> {
573        self.append(v.to_string());
574        Ok(())
575    }
576
577    fn serialize_i64(self, v: i64) -> Result<Self::Ok, Self::Error> {
578        self.append(v.to_string());
579        Ok(())
580    }
581
582    fn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> {
583        self.append(v.to_string());
584        Ok(())
585    }
586
587    fn serialize_u16(self, v: u16) -> Result<Self::Ok, Self::Error> {
588        self.append(v.to_string());
589        Ok(())
590    }
591
592    fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> {
593        self.append(v.to_string());
594        Ok(())
595    }
596
597    fn serialize_u64(self, v: u64) -> Result<Self::Ok, Self::Error> {
598        self.append(v.to_string());
599        Ok(())
600    }
601
602    fn serialize_f32(self, v: f32) -> Result<Self::Ok, Self::Error> {
603        self.append(v.to_string());
604        Ok(())
605    }
606
607    fn serialize_f64(self, v: f64) -> Result<Self::Ok, Self::Error> {
608        self.append(v.to_string());
609        Ok(())
610    }
611
612    fn serialize_char(self, v: char) -> Result<Self::Ok, Self::Error> {
613        self.append(v.to_string());
614        Ok(())
615    }
616
617    fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
618        self.append(v);
619        Ok(())
620    }
621
622    fn serialize_bytes(self, _: &[u8]) -> Result<Self::Ok, Self::Error> {
623        Err(UnsupportedTypeError::Bytes)?
624    }
625
626    fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
627        // Don't emit query parameters for `None`.
628        Ok(())
629    }
630
631    fn serialize_some<T: ?Sized + Serialize>(self, value: &T) -> Result<Self::Ok, Self::Error> {
632        value.serialize(self)
633    }
634
635    fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
636        Err(UnsupportedTypeError::Unit)?
637    }
638
639    fn serialize_unit_struct(self, name: &'static str) -> Result<Self::Ok, Self::Error> {
640        Err(UnsupportedTypeError::UnitStruct(name))?
641    }
642
643    fn serialize_unit_variant(
644        self,
645        _name: &'static str,
646        _index: u32,
647        variant: &'static str,
648    ) -> Result<Self::Ok, Self::Error> {
649        self.append(variant);
650        Ok(())
651    }
652
653    fn serialize_newtype_struct<T: ?Sized + Serialize>(
654        self,
655        _name: &'static str,
656        value: &T,
657    ) -> Result<Self::Ok, Self::Error> {
658        value.serialize(self)
659    }
660
661    fn serialize_newtype_variant<T: ?Sized + Serialize>(
662        self,
663        name: &'static str,
664        _index: u32,
665        variant: &'static str,
666        _value: &T,
667    ) -> Result<Self::Ok, Self::Error> {
668        Err(UnsupportedTypeError::NewtypeVariant(name, variant))?
669    }
670
671    fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
672        Ok(QuerySeqSerializer {
673            serializer: self,
674            index: 0,
675        })
676    }
677
678    fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Self::Error> {
679        Ok(QuerySeqSerializer {
680            serializer: self,
681            index: 0,
682        })
683    }
684
685    fn serialize_tuple_struct(
686        self,
687        name: &'static str,
688        _len: usize,
689    ) -> Result<Self::SerializeTupleStruct, Self::Error> {
690        Err(UnsupportedTypeError::TupleStruct(name))?
691    }
692
693    fn serialize_tuple_variant(
694        self,
695        name: &'static str,
696        _index: u32,
697        variant: &'static str,
698        _len: usize,
699    ) -> Result<Self::SerializeTupleVariant, Self::Error> {
700        Err(UnsupportedTypeError::TupleVariant(name, variant))?
701    }
702
703    fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
704        Ok(QueryStructSerializer { serializer: self })
705    }
706
707    fn serialize_struct(
708        self,
709        _name: &'static str,
710        _len: usize,
711    ) -> Result<Self::SerializeStruct, Self::Error> {
712        Ok(QueryStructSerializer { serializer: self })
713    }
714
715    fn serialize_struct_variant(
716        self,
717        name: &'static str,
718        _index: u32,
719        variant: &'static str,
720        _len: usize,
721    ) -> Result<Self::SerializeStructVariant, Self::Error> {
722        Err(UnsupportedTypeError::StructVariant(name, variant))?
723    }
724}
725
726/// A serializer for sequences (arrays) and tuples.
727pub struct QuerySeqSerializer<'a, 'b> {
728    serializer: &'a mut QueryParamSerializer<'b>,
729    index: usize,
730}
731
732impl<'a, 'b> SerializeSeq for QuerySeqSerializer<'a, 'b> {
733    type Ok = ();
734    type Error = QueryParamError;
735
736    fn serialize_element<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<(), Self::Error> {
737        use ParamSerializerState::*;
738        match &mut self.serializer.state {
739            DeepObject if self.serializer.path.len() == 1 => {
740                // OpenAPI doesn't define `deepObject` for top-level arrays; and
741                // we know we're at the top level if the key path has just one segment.
742                return Err(QueryParamError::UnspecifiedStyleExploded);
743            }
744            DeepObject => {
745                // Otherwise, we're inside a nested structure.
746                self.serializer.path.push(self.index.to_string());
747                value.serialize(&mut *self.serializer)?;
748                self.serializer.path.pop();
749            }
750            _ => value.serialize(&mut *self.serializer)?,
751        }
752        self.index += 1;
753        Ok(())
754    }
755
756    fn end(self) -> Result<Self::Ok, Self::Error> {
757        self.serializer.flush();
758        Ok(())
759    }
760}
761
762impl<'a, 'b> SerializeTuple for QuerySeqSerializer<'a, 'b> {
763    type Ok = ();
764    type Error = QueryParamError;
765
766    fn serialize_element<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<(), Self::Error> {
767        SerializeSeq::serialize_element(self, value)
768    }
769
770    fn end(self) -> Result<Self::Ok, Self::Error> {
771        SerializeSeq::end(self)
772    }
773}
774
775/// A serializer for structs and maps (objects).
776pub struct QueryStructSerializer<'a, 'b> {
777    serializer: &'a mut QueryParamSerializer<'b>,
778}
779
780impl<'a, 'b> SerializeStruct for QueryStructSerializer<'a, 'b> {
781    type Ok = ();
782    type Error = QueryParamError;
783
784    fn serialize_field<T: ?Sized + Serialize>(
785        &mut self,
786        key: &'static str,
787        value: &T,
788    ) -> Result<(), Self::Error> {
789        use ParamSerializerState::*;
790        if let NonExplodedForm(buf) | Delimited(_, buf) = &mut self.serializer.state {
791            // For non-exploded styles, insert the key before the value.
792            // This creates alternating key-value pairs.
793            buf.push(key.to_owned());
794        };
795
796        self.serializer.path.push(key);
797        value.serialize(&mut *self.serializer)?;
798        self.serializer.path.pop();
799        Ok(())
800    }
801
802    fn end(self) -> Result<Self::Ok, Self::Error> {
803        self.serializer.flush();
804        Ok(())
805    }
806}
807
808impl<'a, 'b> SerializeMap for QueryStructSerializer<'a, 'b> {
809    type Ok = ();
810    type Error = QueryParamError;
811
812    fn serialize_key<T: ?Sized + Serialize>(&mut self, key: &T) -> Result<(), Self::Error> {
813        self.serializer.path.push(key.serialize(KeyExtractor)?);
814        Ok(())
815    }
816
817    fn serialize_value<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<(), Self::Error> {
818        use ParamSerializerState::*;
819        if let NonExplodedForm(buf) | Delimited(_, buf) = &mut self.serializer.state {
820            // For non-exploded styles, insert the key before the value
821            // (`serialize_key()` already added the key to the path).
822            buf.push(self.serializer.path.last().to_owned());
823        };
824
825        value.serialize(&mut *self.serializer)?;
826        self.serializer.path.pop();
827        Ok(())
828    }
829
830    fn end(self) -> Result<Self::Ok, Self::Error> {
831        self.serializer.flush();
832        Ok(())
833    }
834}
835
836/// A helper [`Serializer`] for extracting string keys from maps.
837struct KeyExtractor;
838
839impl Serializer for KeyExtractor {
840    type Ok = String;
841    type Error = QueryParamError;
842
843    type SerializeSeq = Impossible<Self::Ok, Self::Error>;
844    type SerializeTuple = Impossible<Self::Ok, Self::Error>;
845    type SerializeTupleStruct = Impossible<Self::Ok, Self::Error>;
846    type SerializeTupleVariant = Impossible<Self::Ok, Self::Error>;
847    type SerializeMap = Impossible<Self::Ok, Self::Error>;
848    type SerializeStruct = Impossible<Self::Ok, Self::Error>;
849    type SerializeStructVariant = Impossible<Self::Ok, Self::Error>;
850
851    fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
852        Ok(v.to_owned())
853    }
854
855    fn serialize_bool(self, _: bool) -> Result<Self::Ok, Self::Error> {
856        Err(QueryParamError::ExpectedStringKey)
857    }
858
859    fn serialize_i8(self, _: i8) -> Result<Self::Ok, Self::Error> {
860        Err(QueryParamError::ExpectedStringKey)
861    }
862
863    fn serialize_i16(self, _: i16) -> Result<Self::Ok, Self::Error> {
864        Err(QueryParamError::ExpectedStringKey)
865    }
866
867    fn serialize_i32(self, _: i32) -> Result<Self::Ok, Self::Error> {
868        Err(QueryParamError::ExpectedStringKey)
869    }
870
871    fn serialize_i64(self, _: i64) -> Result<Self::Ok, Self::Error> {
872        Err(QueryParamError::ExpectedStringKey)
873    }
874
875    fn serialize_u8(self, _: u8) -> Result<Self::Ok, Self::Error> {
876        Err(QueryParamError::ExpectedStringKey)
877    }
878
879    fn serialize_u16(self, _: u16) -> Result<Self::Ok, Self::Error> {
880        Err(QueryParamError::ExpectedStringKey)
881    }
882
883    fn serialize_u32(self, _: u32) -> Result<Self::Ok, Self::Error> {
884        Err(QueryParamError::ExpectedStringKey)
885    }
886
887    fn serialize_u64(self, _: u64) -> Result<Self::Ok, Self::Error> {
888        Err(QueryParamError::ExpectedStringKey)
889    }
890
891    fn serialize_f32(self, _: f32) -> Result<Self::Ok, Self::Error> {
892        Err(QueryParamError::ExpectedStringKey)
893    }
894
895    fn serialize_f64(self, _: f64) -> Result<Self::Ok, Self::Error> {
896        Err(QueryParamError::ExpectedStringKey)
897    }
898
899    fn serialize_char(self, _: char) -> Result<Self::Ok, Self::Error> {
900        Err(QueryParamError::ExpectedStringKey)
901    }
902
903    fn serialize_bytes(self, _: &[u8]) -> Result<Self::Ok, Self::Error> {
904        Err(QueryParamError::ExpectedStringKey)
905    }
906
907    fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
908        Err(QueryParamError::ExpectedStringKey)
909    }
910
911    fn serialize_some<T: ?Sized + Serialize>(self, _: &T) -> Result<Self::Ok, Self::Error> {
912        Err(QueryParamError::ExpectedStringKey)
913    }
914
915    fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
916        Err(QueryParamError::ExpectedStringKey)
917    }
918
919    fn serialize_unit_struct(self, _: &'static str) -> Result<Self::Ok, Self::Error> {
920        Err(QueryParamError::ExpectedStringKey)
921    }
922
923    fn serialize_unit_variant(
924        self,
925        _: &'static str,
926        _: u32,
927        _: &'static str,
928    ) -> Result<Self::Ok, Self::Error> {
929        Err(QueryParamError::ExpectedStringKey)
930    }
931
932    fn serialize_newtype_struct<T: ?Sized + Serialize>(
933        self,
934        _: &'static str,
935        _: &T,
936    ) -> Result<Self::Ok, Self::Error> {
937        Err(QueryParamError::ExpectedStringKey)
938    }
939
940    fn serialize_newtype_variant<T: ?Sized + Serialize>(
941        self,
942        _: &'static str,
943        _: u32,
944        _: &'static str,
945        _: &T,
946    ) -> Result<Self::Ok, Self::Error> {
947        Err(QueryParamError::ExpectedStringKey)
948    }
949
950    fn serialize_seq(self, _: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
951        Err(QueryParamError::ExpectedStringKey)
952    }
953
954    fn serialize_tuple(self, _: usize) -> Result<Self::SerializeTuple, Self::Error> {
955        Err(QueryParamError::ExpectedStringKey)
956    }
957
958    fn serialize_tuple_struct(
959        self,
960        _: &'static str,
961        _: usize,
962    ) -> Result<Self::SerializeTupleStruct, Self::Error> {
963        Err(QueryParamError::ExpectedStringKey)
964    }
965
966    fn serialize_tuple_variant(
967        self,
968        _: &'static str,
969        _: u32,
970        _: &'static str,
971        _: usize,
972    ) -> Result<Self::SerializeTupleVariant, Self::Error> {
973        Err(QueryParamError::ExpectedStringKey)
974    }
975
976    fn serialize_map(self, _: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
977        Err(QueryParamError::ExpectedStringKey)
978    }
979
980    fn serialize_struct(
981        self,
982        _: &'static str,
983        _: usize,
984    ) -> Result<Self::SerializeStruct, Self::Error> {
985        Err(QueryParamError::ExpectedStringKey)
986    }
987
988    fn serialize_struct_variant(
989        self,
990        _: &'static str,
991        _: u32,
992        _: &'static str,
993        _: usize,
994    ) -> Result<Self::SerializeStructVariant, Self::Error> {
995        Err(QueryParamError::ExpectedStringKey)
996    }
997}
998
999/// An error that occurs during query parameter serialization.
1000#[derive(Debug, thiserror::Error)]
1001pub enum QueryParamError {
1002    #[error("can't serialize {0} as query parameter")]
1003    UnsupportedType(#[from] UnsupportedTypeError),
1004    #[error("style-exploded combination not defined by OpenAPI")]
1005    UnspecifiedStyleExploded,
1006    #[error("map keys must serialize as strings")]
1007    ExpectedStringKey,
1008    #[error("query parameters must serialize as a struct")]
1009    ExpectedStruct,
1010    #[error("{0}")]
1011    Custom(String),
1012}
1013
1014impl serde::ser::Error for QueryParamError {
1015    fn custom<T: std::fmt::Display>(err: T) -> Self {
1016        Self::Custom(err.to_string())
1017    }
1018}
1019
1020#[derive(Debug, thiserror::Error)]
1021pub enum UnsupportedTypeError {
1022    #[error("bytes")]
1023    Bytes,
1024    #[error("unit")]
1025    Unit,
1026    #[error("unit struct `{0}`")]
1027    UnitStruct(&'static str),
1028    #[error("tuple struct `{0}`")]
1029    TupleStruct(&'static str),
1030    #[error("newtype variant `{1}` of `{0}`")]
1031    NewtypeVariant(&'static str, &'static str),
1032    #[error("tuple variant `{1}` of `{0}`")]
1033    TupleVariant(&'static str, &'static str),
1034    #[error("struct variant `{1}` of `{0}`")]
1035    StructVariant(&'static str, &'static str),
1036}
1037
1038#[cfg(test)]
1039mod tests {
1040    use super::*;
1041    use serde::Serialize;
1042    use url::Url;
1043
1044    // MARK: Scalar types
1045
1046    #[test]
1047    fn test_integer() {
1048        #[derive(Serialize)]
1049        struct Q {
1050            limit: i32,
1051        }
1052
1053        let url = Q { limit: 42 }
1054            .serialize(QuerySerializer::new(
1055                Url::parse("http://example.com/").unwrap(),
1056                &[],
1057            ))
1058            .unwrap();
1059        assert_eq!(url.query(), Some("limit=42"));
1060    }
1061
1062    #[test]
1063    fn test_string() {
1064        #[derive(Serialize)]
1065        struct Q {
1066            name: &'static str,
1067        }
1068
1069        let url = Q { name: "Alice" }
1070            .serialize(QuerySerializer::new(
1071                Url::parse("http://example.com/").unwrap(),
1072                &[],
1073            ))
1074            .unwrap();
1075        assert_eq!(url.query(), Some("name=Alice"));
1076    }
1077
1078    #[test]
1079    fn test_bool() {
1080        #[derive(Serialize)]
1081        struct Q {
1082            active: bool,
1083        }
1084
1085        let url = Q { active: true }
1086            .serialize(QuerySerializer::new(
1087                Url::parse("http://example.com/").unwrap(),
1088                &[],
1089            ))
1090            .unwrap();
1091        assert_eq!(url.query(), Some("active=true"));
1092    }
1093
1094    #[test]
1095    fn test_option_some() {
1096        #[derive(Serialize)]
1097        struct Q {
1098            limit: Option<i32>,
1099        }
1100
1101        let url = Q { limit: Some(42) }
1102            .serialize(QuerySerializer::new(
1103                Url::parse("http://example.com/").unwrap(),
1104                &[],
1105            ))
1106            .unwrap();
1107        assert_eq!(url.query(), Some("limit=42"));
1108    }
1109
1110    #[test]
1111    fn test_option_none() {
1112        #[derive(Serialize)]
1113        struct Q {
1114            limit: Option<i32>,
1115        }
1116
1117        let url = Q { limit: None }
1118            .serialize(QuerySerializer::new(
1119                Url::parse("http://example.com/").unwrap(),
1120                &[],
1121            ))
1122            .unwrap();
1123        assert_eq!(url.query(), None);
1124    }
1125
1126    #[test]
1127    fn test_string_with_special_chars() {
1128        #[derive(Serialize)]
1129        struct Q {
1130            name: &'static str,
1131        }
1132
1133        let url = Q {
1134            name: "John Doe & Co.",
1135        }
1136        .serialize(QuerySerializer::new(
1137            Url::parse("http://example.com/").unwrap(),
1138            &[],
1139        ))
1140        .unwrap();
1141        assert_eq!(url.query(), Some("name=John+Doe+%26+Co."));
1142    }
1143
1144    #[test]
1145    fn test_unicode_string() {
1146        #[derive(Serialize)]
1147        struct Q {
1148            name: &'static str,
1149        }
1150
1151        let url = Q { name: "日本語" }
1152            .serialize(QuerySerializer::new(
1153                Url::parse("http://example.com/").unwrap(),
1154                &[],
1155            ))
1156            .unwrap();
1157        assert_eq!(url.query(), Some("name=%E6%97%A5%E6%9C%AC%E8%AA%9E"));
1158    }
1159
1160    #[test]
1161    fn test_unit_variant_enum() {
1162        #[derive(Serialize)]
1163        #[allow(dead_code)]
1164        enum Status {
1165            Active,
1166            Inactive,
1167        }
1168
1169        #[derive(Serialize)]
1170        struct Q {
1171            status: Status,
1172        }
1173
1174        let url = Q {
1175            status: Status::Active,
1176        }
1177        .serialize(QuerySerializer::new(
1178            Url::parse("http://example.com/").unwrap(),
1179            &[],
1180        ))
1181        .unwrap();
1182        assert_eq!(url.query(), Some("status=Active"));
1183    }
1184
1185    // MARK: Arrays
1186
1187    #[test]
1188    fn test_array_form_exploded() {
1189        #[derive(Serialize)]
1190        struct Q {
1191            ids: Vec<i32>,
1192        }
1193
1194        let url = Q { ids: vec![1, 2, 3] }
1195            .serialize(QuerySerializer::new(
1196                Url::parse("http://example.com/").unwrap(),
1197                &[],
1198            ))
1199            .unwrap();
1200        assert_eq!(url.query(), Some("ids=1&ids=2&ids=3"));
1201    }
1202
1203    #[test]
1204    fn test_array_form_non_exploded() {
1205        #[derive(Serialize)]
1206        struct Q {
1207            ids: Vec<i32>,
1208        }
1209
1210        let url = Q { ids: vec![1, 2, 3] }
1211            .serialize(QuerySerializer::new(
1212                Url::parse("http://example.com/").unwrap(),
1213                &[("ids", QueryStyle::Form { exploded: false })],
1214            ))
1215            .unwrap();
1216        assert_eq!(url.query(), Some("ids=1,2,3"));
1217    }
1218
1219    #[test]
1220    fn test_array_space_delimited() {
1221        #[derive(Serialize)]
1222        struct Q {
1223            ids: Vec<i32>,
1224        }
1225
1226        let url = Q { ids: vec![1, 2, 3] }
1227            .serialize(QuerySerializer::new(
1228                Url::parse("http://example.com/").unwrap(),
1229                &[("ids", QueryStyle::SpaceDelimited)],
1230            ))
1231            .unwrap();
1232        assert_eq!(url.query(), Some("ids=1%202%203"));
1233    }
1234
1235    #[test]
1236    fn test_array_pipe_delimited() {
1237        #[derive(Serialize)]
1238        struct Q {
1239            ids: Vec<i32>,
1240        }
1241
1242        let url = Q { ids: vec![1, 2, 3] }
1243            .serialize(QuerySerializer::new(
1244                Url::parse("http://example.com/").unwrap(),
1245                &[("ids", QueryStyle::PipeDelimited)],
1246            ))
1247            .unwrap();
1248        assert_eq!(url.query(), Some("ids=1%7C2%7C3"));
1249    }
1250
1251    #[test]
1252    fn test_empty_array() {
1253        #[derive(Serialize)]
1254        struct Q {
1255            ids: Vec<i32>,
1256        }
1257
1258        let url = Q { ids: vec![] }
1259            .serialize(QuerySerializer::new(
1260                Url::parse("http://example.com/").unwrap(),
1261                &[],
1262            ))
1263            .unwrap();
1264        assert_eq!(url.query(), None);
1265    }
1266
1267    #[test]
1268    fn test_array_of_strings_with_special_chars() {
1269        #[derive(Serialize)]
1270        struct Q {
1271            tags: Vec<&'static str>,
1272        }
1273
1274        let url = Q {
1275            tags: vec!["hello world", "foo&bar"],
1276        }
1277        .serialize(QuerySerializer::new(
1278            Url::parse("http://example.com/").unwrap(),
1279            &[("tags", QueryStyle::Form { exploded: false })],
1280        ))
1281        .unwrap();
1282        assert_eq!(url.query(), Some("tags=hello%20world,foo%26bar"));
1283    }
1284
1285    #[test]
1286    fn test_deep_object_rejects_top_level_arrays() {
1287        #[derive(Serialize)]
1288        struct Q {
1289            ids: Vec<i32>,
1290        }
1291
1292        let result = Q { ids: vec![1, 2, 3] }.serialize(QuerySerializer::new(
1293            Url::parse("http://example.com/").unwrap(),
1294            &[("ids", QueryStyle::DeepObject)],
1295        ));
1296        assert!(matches!(
1297            result,
1298            Err(QueryParamError::UnspecifiedStyleExploded)
1299        ));
1300    }
1301
1302    // MARK: Tuples
1303
1304    #[test]
1305    fn test_tuple_form_exploded() {
1306        #[derive(Serialize)]
1307        struct Q {
1308            coords: (i32, i32, i32),
1309        }
1310
1311        let url = Q {
1312            coords: (42, 24, 10),
1313        }
1314        .serialize(QuerySerializer::new(
1315            Url::parse("http://example.com/").unwrap(),
1316            &[],
1317        ))
1318        .unwrap();
1319        assert_eq!(url.query(), Some("coords=42&coords=24&coords=10"));
1320    }
1321
1322    #[test]
1323    fn test_tuple_form_non_exploded() {
1324        #[derive(Serialize)]
1325        struct Q {
1326            coords: (i32, i32, i32),
1327        }
1328
1329        let url = Q {
1330            coords: (42, 24, 10),
1331        }
1332        .serialize(QuerySerializer::new(
1333            Url::parse("http://example.com/").unwrap(),
1334            &[("coords", QueryStyle::Form { exploded: false })],
1335        ))
1336        .unwrap();
1337        assert_eq!(url.query(), Some("coords=42,24,10"));
1338    }
1339
1340    #[test]
1341    fn test_tuple_space_delimited() {
1342        #[derive(Serialize)]
1343        struct Q {
1344            coords: (i32, i32, i32),
1345        }
1346
1347        let url = Q {
1348            coords: (42, 24, 10),
1349        }
1350        .serialize(QuerySerializer::new(
1351            Url::parse("http://example.com/").unwrap(),
1352            &[("coords", QueryStyle::SpaceDelimited)],
1353        ))
1354        .unwrap();
1355        assert_eq!(url.query(), Some("coords=42%2024%2010"));
1356    }
1357
1358    #[test]
1359    fn test_tuple_pipe_delimited() {
1360        #[derive(Serialize)]
1361        struct Q {
1362            coords: (i32, i32, i32),
1363        }
1364
1365        let url = Q {
1366            coords: (42, 24, 10),
1367        }
1368        .serialize(QuerySerializer::new(
1369            Url::parse("http://example.com/").unwrap(),
1370            &[("coords", QueryStyle::PipeDelimited)],
1371        ))
1372        .unwrap();
1373        assert_eq!(url.query(), Some("coords=42%7C24%7C10"));
1374    }
1375
1376    // MARK: Objects
1377
1378    #[test]
1379    fn test_object_form_exploded() {
1380        #[derive(Serialize)]
1381        struct Q {
1382            first_name: String,
1383            last_name: String,
1384        }
1385
1386        let url = Q {
1387            first_name: "John".to_owned(),
1388            last_name: "Doe".to_owned(),
1389        }
1390        .serialize(QuerySerializer::new(
1391            Url::parse("http://example.com/").unwrap(),
1392            &[
1393                ("first_name", QueryStyle::Form { exploded: true }),
1394                ("last_name", QueryStyle::Form { exploded: true }),
1395            ],
1396        ))
1397        .unwrap();
1398        assert_eq!(url.query(), Some("first_name=John&last_name=Doe"));
1399    }
1400
1401    #[test]
1402    fn test_object_form_non_exploded() {
1403        #[derive(Serialize)]
1404        struct Person {
1405            first_name: String,
1406            last_name: String,
1407        }
1408
1409        #[derive(Serialize)]
1410        struct Q {
1411            person: Person,
1412        }
1413
1414        let url = Q {
1415            person: Person {
1416                first_name: "John".to_owned(),
1417                last_name: "Doe".to_owned(),
1418            },
1419        }
1420        .serialize(QuerySerializer::new(
1421            Url::parse("http://example.com/").unwrap(),
1422            &[("person", QueryStyle::Form { exploded: false })],
1423        ))
1424        .unwrap();
1425        assert_eq!(url.query(), Some("person=first_name,John,last_name,Doe"));
1426    }
1427
1428    #[test]
1429    fn test_object_deep_object() {
1430        #[derive(Serialize)]
1431        struct Filter {
1432            #[serde(rename = "type")]
1433            type_field: String,
1434            location: String,
1435        }
1436
1437        #[derive(Serialize)]
1438        struct Q {
1439            filter: Filter,
1440        }
1441
1442        let url = Q {
1443            filter: Filter {
1444                type_field: "cocktail".to_owned(),
1445                location: "bar".to_owned(),
1446            },
1447        }
1448        .serialize(QuerySerializer::new(
1449            Url::parse("http://example.com/").unwrap(),
1450            &[("filter", QueryStyle::DeepObject)],
1451        ))
1452        .unwrap();
1453        assert_eq!(
1454            url.query(),
1455            Some("filter%5Btype%5D=cocktail&filter%5Blocation%5D=bar")
1456        );
1457    }
1458
1459    #[test]
1460    fn test_space_delimited_object() {
1461        #[derive(Serialize)]
1462        struct Color {
1463            r: u32,
1464            g: u32,
1465            b: u32,
1466        }
1467
1468        #[derive(Serialize)]
1469        struct Q {
1470            color: Color,
1471        }
1472
1473        let url = Q {
1474            color: Color {
1475                r: 100,
1476                g: 200,
1477                b: 150,
1478            },
1479        }
1480        .serialize(QuerySerializer::new(
1481            Url::parse("http://example.com/").unwrap(),
1482            &[("color", QueryStyle::SpaceDelimited)],
1483        ))
1484        .unwrap();
1485        assert_eq!(url.query(), Some("color=r%20100%20g%20200%20b%20150"));
1486    }
1487
1488    #[test]
1489    fn test_pipe_delimited_object() {
1490        #[derive(Serialize)]
1491        struct Color {
1492            r: u32,
1493            g: u32,
1494            b: u32,
1495        }
1496
1497        #[derive(Serialize)]
1498        struct Q {
1499            color: Color,
1500        }
1501
1502        let url = Q {
1503            color: Color {
1504                r: 100,
1505                g: 200,
1506                b: 150,
1507            },
1508        }
1509        .serialize(QuerySerializer::new(
1510            Url::parse("http://example.com/").unwrap(),
1511            &[("color", QueryStyle::PipeDelimited)],
1512        ))
1513        .unwrap();
1514        assert_eq!(url.query(), Some("color=r%7C100%7Cg%7C200%7Cb%7C150"));
1515    }
1516
1517    #[test]
1518    fn test_nested_deep_object() {
1519        #[derive(Serialize)]
1520        struct Address {
1521            city: String,
1522            country: String,
1523        }
1524
1525        #[derive(Serialize)]
1526        struct Person {
1527            name: String,
1528            address: Address,
1529        }
1530
1531        #[derive(Serialize)]
1532        struct Q {
1533            person: Person,
1534        }
1535
1536        let url = Q {
1537            person: Person {
1538                name: "Alice".to_owned(),
1539                address: Address {
1540                    city: "Paris".to_owned(),
1541                    country: "France".to_owned(),
1542                },
1543            },
1544        }
1545        .serialize(QuerySerializer::new(
1546            Url::parse("http://example.com/").unwrap(),
1547            &[("person", QueryStyle::DeepObject)],
1548        ))
1549        .unwrap();
1550        assert_eq!(
1551            url.query(),
1552            Some(
1553                "person%5Bname%5D=Alice&person%5Baddress%5D%5Bcity%5D=Paris&person%5Baddress%5D%5Bcountry%5D=France"
1554            )
1555        );
1556    }
1557
1558    #[test]
1559    fn test_deep_object_with_array_field() {
1560        #[derive(Serialize)]
1561        struct Filter {
1562            category: String,
1563            tags: Vec<String>,
1564        }
1565
1566        #[derive(Serialize)]
1567        struct Q {
1568            filter: Filter,
1569        }
1570
1571        let url = Q {
1572            filter: Filter {
1573                category: "electronics".to_owned(),
1574                tags: vec!["new".to_owned(), "sale".to_owned()],
1575            },
1576        }
1577        .serialize(QuerySerializer::new(
1578            Url::parse("http://example.com/").unwrap(),
1579            &[("filter", QueryStyle::DeepObject)],
1580        ))
1581        .unwrap();
1582        assert_eq!(
1583            url.query(),
1584            Some(
1585                "filter%5Bcategory%5D=electronics&filter%5Btags%5D%5B0%5D=new&filter%5Btags%5D%5B1%5D=sale"
1586            )
1587        );
1588    }
1589
1590    // MARK: Struct-level serialization
1591
1592    #[test]
1593    fn test_multiple_params() {
1594        #[derive(Serialize)]
1595        struct Q {
1596            limit: i32,
1597            tags: Vec<&'static str>,
1598        }
1599
1600        let url = Q {
1601            limit: 10,
1602            tags: vec!["dog", "cat"],
1603        }
1604        .serialize(QuerySerializer::new(
1605            Url::parse("http://example.com/").unwrap(),
1606            &[
1607                ("limit", QueryStyle::Form { exploded: true }),
1608                ("tags", QueryStyle::Form { exploded: true }),
1609            ],
1610        ))
1611        .unwrap();
1612        assert_eq!(url.query(), Some("limit=10&tags=dog&tags=cat"));
1613    }
1614
1615    #[test]
1616    fn test_serde_skip_if() {
1617        #[derive(Serialize)]
1618        struct Q {
1619            required: i32,
1620            #[serde(skip_serializing_if = "Option::is_none")]
1621            optional: Option<String>,
1622        }
1623
1624        let url = Q {
1625            required: 42,
1626            optional: None,
1627        }
1628        .serialize(QuerySerializer::new(
1629            Url::parse("http://example.com/").unwrap(),
1630            &[
1631                ("required", QueryStyle::Form { exploded: true }),
1632                ("optional", QueryStyle::Form { exploded: true }),
1633            ],
1634        ))
1635        .unwrap();
1636        assert_eq!(url.query(), Some("required=42"));
1637    }
1638
1639    #[test]
1640    fn test_mixed_styles() {
1641        #[derive(Serialize)]
1642        struct Filter {
1643            #[serde(rename = "type")]
1644            type_field: String,
1645        }
1646
1647        #[derive(Serialize)]
1648        struct Q {
1649            filter: Filter,
1650            limit: i32,
1651        }
1652
1653        let url = Q {
1654            filter: Filter {
1655                type_field: "cocktail".to_owned(),
1656            },
1657            limit: 10,
1658        }
1659        .serialize(QuerySerializer::new(
1660            Url::parse("http://example.com/").unwrap(),
1661            &[
1662                ("filter", QueryStyle::DeepObject),
1663                ("limit", QueryStyle::Form { exploded: true }),
1664            ],
1665        ))
1666        .unwrap();
1667        assert_eq!(url.query(), Some("filter%5Btype%5D=cocktail&limit=10"));
1668    }
1669
1670    #[test]
1671    fn test_unlisted_field_uses_default_style() {
1672        #[derive(Serialize)]
1673        struct Q {
1674            limit: i32,
1675        }
1676
1677        // Fields without an explicitly specified style should
1678        // fall back to the default style.
1679        let url = Q { limit: 42 }
1680            .serialize(QuerySerializer::new(
1681                Url::parse("http://example.com/").unwrap(),
1682                &[],
1683            ))
1684            .unwrap();
1685        assert_eq!(url.query(), Some("limit=42"));
1686    }
1687}