Skip to main content

kv_pairs/
lib.rs

1//! Key-value pair builder for API params and form data.
2//!
3//! This crate provides [`KVPairs`], an ordered collection of key-value pairs,
4//! whose values can be either borrowed or owned, that can be used as HTTP query
5//! strings or form bodies. The [`kv_pairs!`] macro can be used to build
6//! `KVPairs` instances with a literal-like syntax.
7//!
8//! [`KVPairs`] accepts values that implement [`IntoValue`], this includes most
9//! primitive and standard library types like `&str`, `String`, `bool`, and
10//! common integer types. The `impl_into_value_by_*` macros can be used to
11//! implement [`IntoValue`] for more types.
12//!
13//! [`KVPairs`] also accepts values that implement [`IntoValues`], to insert 0,
14//! 1, or more values for a single key. This is useful for optional or
15//! multi-value parameters. The keys are **NEVER** automatically suffixed with
16//! `[]` when inserting values via [`IntoValues`].
17//!
18//! # Examples
19//!
20//! Basic usage with the macro:
21//!
22//! ```rust
23//! use kv_pairs::{kv_pairs, KVPairs};
24//!
25//! let params = kv_pairs![
26//!     "mode" => "day",
27//!     "page" => 1_u32,
28//! ];
29//! assert_eq!(params.content.len(), 2);
30//! ```
31//!
32//! Optional parameters with `Option` (key omitted when `None`):
33//!
34//! ```rust
35//! use kv_pairs::kv_pairs;
36//!
37//! let p = kv_pairs![
38//!     "q" => Some("search"),
39//!     "filter" => None::<&str>,
40//! ];
41//! assert_eq!(p.content.len(), 1);
42//! assert_eq!(p.content[0], ("q", std::borrow::Cow::Borrowed("search")));
43//! ```
44//!
45//! Multiple values for one key (e.g. `tag=a&tag=b`) via slice or array:
46//!
47//! ```rust
48//! use kv_pairs::kv_pairs;
49//!
50//! let p = kv_pairs!["tag" => ["a", "b"].as_slice()];
51//! assert_eq!(p.content.len(), 2);
52//! assert_eq!(p.content[0].0, "tag"); // No auto-suffixed `[]` for slice values
53//! assert_eq!(p.content[0].1.as_ref(), "a");
54//! assert_eq!(p.content[1].1.as_ref(), "b");
55//!
56//! let p2 = kv_pairs!["x" => ["one", "two"]];
57//! assert_eq!(p2.content.len(), 2);
58//! ```
59//!
60//! Building programmatically with `push` and `push_one`:
61//!
62//! ```rust
63//! use kv_pairs::KVPairs;
64//!
65//! let mut p = KVPairs::new();
66//! p.push_one("k", "v");
67//! p.push("opt", Some(42_u32));
68//! p.push("tags", ["a", "b"].as_slice());
69//! assert_eq!(p.content.len(), 4);
70//! ```
71
72#![cfg_attr(not(feature = "std"), no_std)]
73
74extern crate alloc;
75
76use alloc::borrow::{Borrow, Cow};
77use alloc::string::{String, ToString};
78use alloc::vec::{IntoIter as VecIntoIter, Vec};
79use core::array::IntoIter as ArrayIntoIter;
80use core::iter::{once, Map, Once};
81use core::ops::{Deref, DerefMut, Index, IndexMut};
82use core::option::IntoIter as OptionIntoIter;
83use core::slice::{Iter as SliceIter, IterMut as SliceIterMut};
84use serde::{Deserialize, Serialize};
85
86/// A list of key-value pairs for API query or form parameters.
87///
88/// Internally a `Vec<(&str, Cow<str>)>`, supporting borrowed keys/values (zero allocation) and
89/// owned values when needed. Implements [`Serialize`]/[`Deserialize`] for use with serde (e.g.
90/// with `reqwest`’s `.query(&p.content)` or `.form(&p.content)`).
91///
92/// # Example
93///
94/// ```
95/// use kv_pairs::KVPairs;
96///
97/// let mut p = KVPairs::new();
98/// p.push_one("key", "value");
99/// p.push("optional", Some("v"));
100/// assert_eq!(p.len(), 2);
101/// ```
102///
103/// See the [crate documentation](crate) for more details.
104#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
105#[serde(bound(deserialize = "'de: 'a"))]
106#[serde(transparent)]
107pub struct KVPairs<'a> {
108    /// The key-value entries; can be passed directly to `reqwest`’s `.query()` or `.form()`.
109    pub content: Vec<KVPair<'a>>,
110}
111
112/// Type alias for the inner pair type.
113pub type KVPair<'a> = (&'a str, Cow<'a, str>);
114
115#[doc(hidden)]
116pub mod __private {
117    pub use alloc::borrow::Cow;
118}
119
120impl Default for KVPairs<'_> {
121    fn default() -> Self {
122        Self::new()
123    }
124}
125
126impl<'a> KVPairs<'a> {
127    /// Creates an empty key-value list.
128    #[must_use]
129    pub fn new() -> Self {
130        Self {
131            content: Vec::new(),
132        }
133    }
134
135    /// Appends a single key-value pair; `value` is converted via [`IntoValue`] to `Cow<str>` (borrowed or owned).
136    pub fn push_one<'b: 'a, V: IntoValue<'b>>(&mut self, key: &'a str, value: V) {
137        self.content.push((key, value.into_value()));
138    }
139
140    /// Appends key-value pair(s) for each value yielded by `value` via [`IntoValues`]; for optional or multi-value parameters.
141    pub fn push<'b: 'a, V: IntoValues<'b>>(&mut self, key: &'a str, value: V) {
142        for v in value.into_values() {
143            self.content.push((key, v));
144        }
145    }
146
147    /// Appends a key-value pair with a value that implements `AsRef<str>`, using a borrow to avoid allocation.
148    pub fn push_str<'b: 'a, V: AsRef<str> + ?Sized>(&mut self, key: &'a str, value: &'b V) {
149        self.content.push((key, Cow::Borrowed(value.as_ref())));
150    }
151
152    /// Appends a key-value pair with an owned value (allocates and stores the string).
153    pub fn push_owned<V: Into<String>>(&mut self, key: &'a str, value: V) {
154        self.content.push((key, Cow::Owned(value.into())));
155    }
156}
157
158impl<'a> AsRef<[KVPair<'a>]> for KVPairs<'a> {
159    fn as_ref(&self) -> &[KVPair<'a>] {
160        self.content.as_ref()
161    }
162}
163
164impl<'a> AsRef<Vec<KVPair<'a>>> for KVPairs<'a> {
165    fn as_ref(&self) -> &Vec<KVPair<'a>> {
166        &self.content
167    }
168}
169
170impl<'a> AsMut<[KVPair<'a>]> for KVPairs<'a> {
171    fn as_mut(&mut self) -> &mut [KVPair<'a>] {
172        self.content.as_mut()
173    }
174}
175
176impl<'a> AsMut<Vec<KVPair<'a>>> for KVPairs<'a> {
177    fn as_mut(&mut self) -> &mut Vec<KVPair<'a>> {
178        &mut self.content
179    }
180}
181
182impl<'a> Borrow<[KVPair<'a>]> for KVPairs<'a> {
183    fn borrow(&self) -> &[KVPair<'a>] {
184        self.content.as_ref()
185    }
186}
187
188impl<'a> Borrow<Vec<KVPair<'a>>> for KVPairs<'a> {
189    fn borrow(&self) -> &Vec<KVPair<'a>> {
190        &self.content
191    }
192}
193
194impl<'a> Deref for KVPairs<'a> {
195    type Target = Vec<KVPair<'a>>;
196
197    fn deref(&self) -> &Self::Target {
198        &self.content
199    }
200}
201
202impl DerefMut for KVPairs<'_> {
203    fn deref_mut(&mut self) -> &mut Self::Target {
204        &mut self.content
205    }
206}
207
208impl<'a, I> Index<I> for KVPairs<'a>
209where
210    Vec<KVPair<'a>>: Index<I>,
211{
212    type Output = <Vec<KVPair<'a>> as Index<I>>::Output;
213
214    fn index(&self, index: I) -> &Self::Output {
215        &self.content[index]
216    }
217}
218
219impl<'a, I> IndexMut<I> for KVPairs<'a>
220where
221    Vec<KVPair<'a>>: IndexMut<I>,
222{
223    fn index_mut(&mut self, index: I) -> &mut Self::Output {
224        &mut self.content[index]
225    }
226}
227
228impl<'a> FromIterator<KVPair<'a>> for KVPairs<'a> {
229    fn from_iter<T: IntoIterator<Item = KVPair<'a>>>(iter: T) -> Self {
230        Self {
231            content: iter.into_iter().collect(),
232        }
233    }
234}
235
236impl<'a> IntoIterator for KVPairs<'a> {
237    type Item = KVPair<'a>;
238    type IntoIter = <Vec<KVPair<'a>> as IntoIterator>::IntoIter;
239
240    fn into_iter(self) -> Self::IntoIter {
241        self.content.into_iter()
242    }
243}
244
245impl<'a, 'b> IntoIterator for &'b KVPairs<'a> {
246    type Item = &'b KVPair<'a>;
247    type IntoIter = SliceIter<'b, KVPair<'a>>;
248
249    fn into_iter(self) -> Self::IntoIter {
250        self.content.iter()
251    }
252}
253
254impl<'a, 'b> IntoIterator for &'b mut KVPairs<'a> {
255    type Item = &'b mut KVPair<'a>;
256    type IntoIter = SliceIterMut<'b, KVPair<'a>>;
257
258    fn into_iter(self) -> Self::IntoIter {
259        self.content.iter_mut()
260    }
261}
262
263/// Converts a value into `Cow<'a, str>` for use with [`KVPairs::push_one`].
264///
265/// Implemented for `&str`, `String`, `bool`, and common integer types; use the macros to implement for more types.
266///
267/// # Examples
268///
269/// ```
270/// use kv_pairs::IntoValue;
271///
272/// assert_eq!("hi".into_value().as_ref(), "hi");
273/// assert_eq!(String::from("owned").into_value().as_ref(), "owned");
274/// assert_eq!(true.into_value().as_ref(), "true");
275/// assert_eq!(42_u32.into_value().as_ref(), "42");
276/// ```
277pub trait IntoValue<'a> {
278    /// Converts into a borrowed or owned string.
279    fn into_value(self) -> Cow<'a, str>;
280}
281
282/// Yields an iterator of `Cow<'a, str>` for use with [`KVPairs::push`].
283///
284/// For `Option<T>`, yields zero or one item (pair added only when `Some`); for `T: IntoValue`, yields one item.
285/// Also implemented for `&[T]`, `[T; N]`, `Vec<T>`, and `&Vec<T>` to add multiple pairs for one key.
286///
287/// # Examples
288///
289/// ```
290/// use kv_pairs::IntoValues;
291/// use std::borrow::Cow;
292///
293/// // Option yields 0 or 1 value
294/// let out: Vec<Cow<str>> = Some("x").into_values().collect();
295/// assert_eq!(out, vec![Cow::Borrowed("x")]);
296/// let out: Vec<Cow<str>> = None::<&str>.into_values().collect();
297/// assert!(out.is_empty());
298///
299/// // Slice yields one value per element
300/// let out: Vec<Cow<str>> = ["a", "b"].as_slice().into_values().collect();
301/// assert_eq!(out.len(), 2);
302/// assert_eq!(out[0].as_ref(), "a");
303/// assert_eq!(out[1].as_ref(), "b");
304/// ```
305pub trait IntoValues<'a> {
306    /// Iterator over the string value(s).
307    type Iter: Iterator<Item = Cow<'a, str>>;
308
309    /// Returns an iterator of borrowed or owned strings.
310    fn into_values(self) -> Self::Iter;
311}
312
313impl<'a> IntoValue<'a> for &'a str {
314    fn into_value(self) -> Cow<'a, str> {
315        Cow::Borrowed(self)
316    }
317}
318
319impl<'a, 'b> IntoValue<'a> for &'b &'a str
320where
321    'a: 'b,
322{
323    fn into_value(self) -> Cow<'a, str> {
324        Cow::Borrowed(*self)
325    }
326}
327
328impl<'a> IntoValue<'a> for String {
329    fn into_value(self) -> Cow<'a, str> {
330        Cow::Owned(self)
331    }
332}
333
334impl<'a> IntoValue<'a> for &'a String {
335    fn into_value(self) -> Cow<'a, str> {
336        Cow::Borrowed(self.as_str())
337    }
338}
339
340impl<'a> IntoValue<'a> for bool {
341    fn into_value(self) -> Cow<'a, str> {
342        Cow::Borrowed(if self { "true" } else { "false" })
343    }
344}
345
346impl<'a, T: IntoValue<'a>> IntoValues<'a> for T {
347    type Iter = Once<Cow<'a, str>>;
348
349    fn into_values(self) -> Self::Iter {
350        once(self.into_value())
351    }
352}
353
354impl<'a, T: IntoValue<'a>> IntoValues<'a> for Option<T> {
355    type Iter = Map<OptionIntoIter<T>, fn(T) -> Cow<'a, str>>;
356
357    fn into_values(self) -> Self::Iter {
358        self.into_iter().map(IntoValue::into_value)
359    }
360}
361
362impl<'a, T> IntoValues<'a> for &'a [T]
363where
364    &'a T: IntoValue<'a>,
365{
366    type Iter = Map<SliceIter<'a, T>, fn(&'a T) -> Cow<'a, str>>;
367
368    fn into_values(self) -> Self::Iter {
369        self.iter().map(IntoValue::into_value)
370    }
371}
372
373impl<'a, T, const N: usize> IntoValues<'a> for &'a [T; N]
374where
375    &'a T: IntoValue<'a>,
376{
377    type Iter = Map<SliceIter<'a, T>, fn(&'a T) -> Cow<'a, str>>;
378
379    fn into_values(self) -> Self::Iter {
380        self.iter().map(IntoValue::into_value)
381    }
382}
383
384impl<'a, T> IntoValues<'a> for &'a Vec<T>
385where
386    &'a T: IntoValue<'a>,
387{
388    type Iter = Map<SliceIter<'a, T>, fn(&'a T) -> Cow<'a, str>>;
389
390    fn into_values(self) -> Self::Iter {
391        self.iter().map(IntoValue::into_value)
392    }
393}
394
395impl<'a, T, const N: usize> IntoValues<'a> for [T; N]
396where
397    T: IntoValue<'a>,
398{
399    type Iter = Map<ArrayIntoIter<T, N>, fn(T) -> Cow<'a, str>>;
400
401    fn into_values(self) -> Self::Iter {
402        self.into_iter().map(IntoValue::into_value)
403    }
404}
405
406impl<'a, T> IntoValues<'a> for Vec<T>
407where
408    T: IntoValue<'a>,
409{
410    type Iter = Map<VecIntoIter<T>, fn(T) -> Cow<'a, str>>;
411
412    fn into_values(self) -> Self::Iter {
413        self.into_iter().map(IntoValue::into_value)
414    }
415}
416
417/// Implements [`IntoValue`] for types that yield `&str` via `AsRef<str>` (borrowed, no allocation).
418///
419/// Usage: `impl_into_value_by_as_ref_str! { MyType, OtherType }`
420#[macro_export]
421macro_rules! impl_into_value_by_as_ref_str {
422    ($($type:ty),* $(,)?) => {
423        $(
424            impl<'a> $crate::IntoValue<'a> for &'a $type where $type: AsRef<str> {
425                fn into_value(self) -> $crate::__private::Cow<'a, str> {
426                    $crate::__private::Cow::Borrowed(self.as_ref())
427                }
428            }
429        )*
430    };
431}
432
433/// Implements [`IntoValue`] for types that implement `Into<&'a str>` (borrowed; useful for enums).
434///
435/// Usage: `impl_into_value_by_into_str_ref! { MyEnum, OtherEnum }`
436#[macro_export]
437macro_rules! impl_into_value_by_into_str_ref {
438    ($($type:ty),* $(,)?) => {
439        $(
440            impl<'a> $crate::IntoValue<'a> for $type where $type: Into<&'a str> {
441                fn into_value(self) -> $crate::__private::Cow<'a, str> {
442                    $crate::__private::Cow::Borrowed(self.into())
443                }
444            }
445        )*
446    };
447}
448
449/// Implements [`IntoValue`] for types that implement `ToString` (owned, allocates).
450///
451/// Usage: `impl_into_value_by_to_string! { u64, i32, MyType }`
452#[macro_export]
453macro_rules! impl_into_value_by_to_string {
454    ($($type:ty),* $(,)?) => {
455        $(
456            impl<'a> $crate::IntoValue<'a> for $type where $type: ToString {
457                fn into_value(self) -> $crate::__private::Cow<'a, str> {
458                    $crate::__private::Cow::Owned(self.to_string())
459                }
460            }
461        )*
462    };
463}
464
465/// Builds [`KVPairs`] with literal-like syntax.
466///
467/// Syntax: `kv_pairs! [ "key" => value, ... ]` where each `value` must implement [`IntoValues`] (e.g. [`IntoValue`] or `Option<T>`).
468///
469/// # Examples
470///
471/// ```rust
472/// use kv_pairs::{kv_pairs, KVPairs};
473///
474/// let empty: KVPairs = kv_pairs![];
475/// let one = kv_pairs![ "a" => "b" ];
476/// let two = kv_pairs![ "x" => 1_u32, "y" => "z" ];
477/// ```
478///
479/// With optional and multi-value params:
480///
481/// ```rust
482/// use kv_pairs::kv_pairs;
483///
484/// let p = kv_pairs![
485///     "q" => Some("query"),
486///     "page" => 1_u32,
487///     "tag" => ["a", "b"].as_slice(),
488/// ];
489/// assert_eq!(p.content.len(), 4);
490/// ```
491#[macro_export]
492macro_rules! kv_pairs {
493    () => {
494        $crate::KVPairs::new()
495    };
496    (@inner, $result: expr $(,)?) => {};
497    (@inner, $result: expr, $key:expr => $value:expr $(, $($($rest:tt)+)?)?) => {
498        $result.push($key, $value);
499        $($(kv_pairs!(@inner, $result, $($rest)+);)?)?
500    };
501    ($($input:tt)*) => {
502        {
503            let mut result = $crate::KVPairs::new();
504            kv_pairs!(@inner, result, $($input)*);
505            result
506        }
507    };
508}
509
510impl_into_value_by_to_string! {
511    u64,
512    u32,
513    u16,
514    u8,
515    i64,
516    i32,
517    i16,
518    i8,
519}
520
521#[cfg(test)]
522mod tests {
523    use super::*;
524    use std::ops::DerefMut;
525
526    #[test]
527    fn new_and_default_are_empty() {
528        let p: KVPairs = KVPairs::new();
529        assert!(p.content.is_empty());
530        let q: KVPairs = KVPairs::default();
531        assert!(q.content.is_empty());
532    }
533
534    #[test]
535    fn push_str_borrows() {
536        let mut p = KVPairs::new();
537        let s = String::from("v");
538        p.push_str("k", &s);
539        assert_eq!(p.content.len(), 1);
540        assert_eq!(p.content[0].0, "k");
541        assert_eq!(p.content[0].1.as_ref(), "v");
542    }
543
544    #[test]
545    fn push_owned_allocates() {
546        let mut p = KVPairs::new();
547        p.push_owned("key", "value");
548        assert_eq!(p.content[0].0, "key");
549        assert_eq!(p.content[0].1.as_ref(), "value");
550    }
551
552    #[test]
553    fn push_one_with_into_value() {
554        let mut p = KVPairs::new();
555        p.push_one("a", "b");
556        p.push_one("n", 42_u32);
557        p.push_one("flag", true);
558        assert_eq!(p.content.len(), 3);
559        assert_eq!(p.content[0], ("a", Cow::Borrowed("b")));
560        assert_eq!(p.content[1].1.as_ref(), "42");
561        assert_eq!(p.content[2].1.as_ref(), "true");
562    }
563
564    #[test]
565    fn push_some_adds() {
566        let mut p = KVPairs::new();
567        p.push("opt", Some("v"));
568        assert_eq!(p.content.len(), 1);
569        assert_eq!(p.content[0].1.as_ref(), "v");
570    }
571
572    #[test]
573    fn push_none_skips() {
574        let mut p = KVPairs::new();
575        p.push("opt", None::<&str>);
576        assert!(p.content.is_empty());
577    }
578
579    #[test]
580    fn macro_empty() {
581        let p = kv_pairs![];
582        assert!(p.content.is_empty());
583    }
584
585    #[test]
586    fn macro_single() {
587        let p = kv_pairs![ "k" => "v" ];
588        assert_eq!(p.content.len(), 1);
589        assert_eq!(p.content[0].0, "k");
590        assert_eq!(p.content[0].1.as_ref(), "v");
591    }
592
593    #[test]
594    fn macro_multiple() {
595        let p = kv_pairs![
596            "a" => "1",
597            "b" => 2_u64,
598            "c" => false,
599        ];
600        assert_eq!(p.content.len(), 3);
601        assert_eq!(p.content[0], ("a", Cow::Borrowed("1")));
602        assert_eq!(p.content[1].1.as_ref(), "2");
603        assert_eq!(p.content[2].1.as_ref(), "false");
604    }
605
606    #[test]
607    fn into_values_for_option() {
608        let some_val: Option<&str> = Some("x");
609        let out: Vec<Cow<str>> = some_val.into_values().collect();
610        assert_eq!(out.len(), 1);
611        assert_eq!(out[0].as_ref(), "x");
612
613        let none_val: Option<&str> = None;
614        let out2: Vec<Cow<str>> = none_val.into_values().collect();
615        assert!(out2.is_empty());
616    }
617
618    #[test]
619    fn into_value_str_string_bool() {
620        assert_eq!("hi".into_value().as_ref(), "hi");
621        assert_eq!(String::from("owned").into_value().as_ref(), "owned");
622        let s = String::from("borrowed");
623        assert_eq!((&s).into_value().as_ref(), "borrowed");
624        assert_eq!(true.into_value().as_ref(), "true");
625        assert_eq!(false.into_value().as_ref(), "false");
626    }
627
628    #[test]
629    fn into_value_integers() {
630        assert_eq!(0_u8.into_value().as_ref(), "0");
631        assert_eq!(1_u16.into_value().as_ref(), "1");
632        assert_eq!(42_u32.into_value().as_ref(), "42");
633        assert_eq!(100_u64.into_value().as_ref(), "100");
634        assert_eq!((-1_i8).into_value().as_ref(), "-1");
635        assert_eq!(2_i16.into_value().as_ref(), "2");
636        assert_eq!((-99_i32).into_value().as_ref(), "-99");
637        assert_eq!(1000_i64.into_value().as_ref(), "1000");
638    }
639
640    #[test]
641    fn into_values_slice() {
642        let out: Vec<Cow<str>> = ["a", "b"].as_slice().into_values().collect();
643        assert_eq!(out.len(), 2);
644        assert_eq!(out[0].as_ref(), "a");
645        assert_eq!(out[1].as_ref(), "b");
646    }
647
648    #[test]
649    fn into_values_array() {
650        let out: Vec<Cow<str>> = ["x", "y"].into_values().collect();
651        assert_eq!(out.len(), 2);
652        assert_eq!(out[0].as_ref(), "x");
653        assert_eq!(out[1].as_ref(), "y");
654    }
655
656    #[test]
657    fn into_values_vec() {
658        let out: Vec<Cow<str>> = vec!["p", "q"].into_values().collect();
659        assert_eq!(out.len(), 2);
660        assert_eq!(out[0].as_ref(), "p");
661        assert_eq!(out[1].as_ref(), "q");
662    }
663
664    #[test]
665    fn into_values_vec_ref() {
666        let v = vec!["m", "n"];
667        let out: Vec<Cow<str>> = (&v).into_values().collect();
668        assert_eq!(out.len(), 2);
669        assert_eq!(out[0].as_ref(), "m");
670        assert_eq!(out[1].as_ref(), "n");
671    }
672
673    #[test]
674    fn into_values_option_integers() {
675        let out: Vec<Cow<str>> = Some(10_u32).into_values().collect();
676        assert_eq!(out.len(), 1);
677        assert_eq!(out[0].as_ref(), "10");
678        let out: Vec<Cow<str>> = None::<u32>.into_values().collect();
679        assert!(out.is_empty());
680    }
681
682    #[test]
683    fn macro_with_option_some_none() {
684        let p = kv_pairs![
685            "q" => Some("search"),
686            "filter" => None::<&str>,
687        ];
688        assert_eq!(p.content.len(), 1);
689        assert_eq!(p.content[0].0, "q");
690        assert_eq!(p.content[0].1.as_ref(), "search");
691    }
692
693    #[test]
694    fn macro_with_slice() {
695        let p = kv_pairs!["tag" => ["a", "b"].as_slice()];
696        assert_eq!(p.content.len(), 2);
697        assert_eq!(p.content[0], ("tag", Cow::Borrowed("a")));
698        assert_eq!(p.content[1], ("tag", Cow::Borrowed("b")));
699    }
700
701    #[test]
702    fn macro_with_array() {
703        let p = kv_pairs!["x" => ["one", "two"]];
704        assert_eq!(p.content.len(), 2);
705        assert_eq!(p.content[0].1.as_ref(), "one");
706        assert_eq!(p.content[1].1.as_ref(), "two");
707    }
708
709    #[test]
710    fn macro_with_vec() {
711        let p = kv_pairs!["ids" => vec![1_u32, 2_u32]];
712        assert_eq!(p.content.len(), 2);
713        assert_eq!(p.content[0].1.as_ref(), "1");
714        assert_eq!(p.content[1].1.as_ref(), "2");
715    }
716
717    #[test]
718    fn push_with_slice_multi_value() {
719        let mut p = KVPairs::new();
720        p.push("tag", ["a", "b"].as_slice());
721        assert_eq!(p.content.len(), 2);
722        assert_eq!(p.content[0], ("tag", Cow::Borrowed("a")));
723        assert_eq!(p.content[1], ("tag", Cow::Borrowed("b")));
724    }
725
726    #[test]
727    fn push_with_vec_multi_value() {
728        let mut p = KVPairs::new();
729        p.push("n", vec![10_u32, 20_u32]);
730        assert_eq!(p.content.len(), 2);
731        assert_eq!(p.content[0].1.as_ref(), "10");
732        assert_eq!(p.content[1].1.as_ref(), "20");
733    }
734
735    #[test]
736    fn serialize_roundtrip() {
737        let p = kv_pairs![ "a" => "b", "c" => 1_i32 ];
738        let json = serde_json::to_string(&p.content).unwrap();
739        let back: Vec<(String, String)> = serde_json::from_str(&json).unwrap();
740        assert_eq!(back.len(), 2);
741        assert_eq!((back[0].0.as_str(), back[0].1.as_str()), ("a", "b"));
742        assert_eq!((back[1].0.as_str(), back[1].1.as_str()), ("c", "1"));
743    }
744
745    #[test]
746    fn eq_and_hash() {
747        let a = kv_pairs!["x" => "1", "y" => "2"];
748        let b = kv_pairs!["x" => "1", "y" => "2"];
749        let c = kv_pairs!["x" => "1"];
750        assert_eq!(a, b);
751        assert_ne!(a, c);
752        use std::collections::hash_map::DefaultHasher;
753        use std::hash::{Hash, Hasher};
754        let mut hasher = DefaultHasher::new();
755        a.hash(&mut hasher);
756        let _ = hasher.finish();
757    }
758
759    #[test]
760    fn as_ref_as_mut_borrow() {
761        let p = kv_pairs!["a" => "b"];
762        let slice: &[KVPair] = p.as_ref();
763        assert_eq!(slice.len(), 1);
764        let vec_ref: &Vec<KVPair> = p.as_ref();
765        assert_eq!(vec_ref.len(), 1);
766        let slice_borrow: &[KVPair] = p.borrow();
767        assert_eq!(slice_borrow, slice);
768        let vec_borrow: &Vec<KVPair> = p.borrow();
769        assert_eq!(vec_borrow.len(), 1);
770        let mut q = kv_pairs!["k" => "v"];
771        let slice_mut: &mut [KVPair] = q.as_mut();
772        slice_mut[0].1 = Cow::Borrowed("v2");
773        assert_eq!(q[0].1.as_ref(), "v2");
774    }
775
776    #[test]
777    fn deref_deref_mut() {
778        let p = kv_pairs!["a" => "1", "b" => "2"];
779        assert_eq!(p.len(), 2);
780        assert!(!p.is_empty());
781        let mut q = kv_pairs!["x" => "y"];
782        q.deref_mut().push(("z", Cow::Borrowed("w")));
783        assert_eq!(q.len(), 2);
784    }
785
786    #[test]
787    fn index_index_mut() {
788        let mut p = kv_pairs!["a" => "b", "c" => "d"];
789        assert_eq!(p[0].0, "a");
790        assert_eq!(p[1].1.as_ref(), "d");
791        p[0].1 = Cow::Borrowed("b2");
792        assert_eq!(p[0].1.as_ref(), "b2");
793    }
794
795    #[test]
796    fn from_iterator_into_iterator() {
797        let pairs: Vec<KVPair> = vec![("k1", Cow::Borrowed("v1")), ("k2", Cow::Borrowed("v2"))];
798        let p: KVPairs = pairs.into_iter().collect();
799        assert_eq!(p.len(), 2);
800        let back: Vec<KVPair> = p.into_iter().collect();
801        assert_eq!(back.len(), 2);
802        assert_eq!(back[0].0, "k1");
803        let p2 = kv_pairs!["a" => "b"];
804        let ref_iter: Vec<_> = (&p2).into_iter().collect();
805        assert_eq!(ref_iter.len(), 1);
806        assert_eq!(ref_iter[0].0, "a");
807    }
808}