Skip to main content

glide/
write.rs

1// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
2//! Valkey command argument encoding.
3//!
4//! [`ToValkeyArgs`] encodes Rust values into command arguments,
5//! using [`ValkeyWrite`] to avoid any intermediate allocation.
6
7use bytes::Bytes;
8
9// --- ValkeyWrite --------------------------------------------------------------------------------
10
11/// A writer that command arguments are written into.
12///
13/// Mirrors redis-rs's `RedisWrite`.
14pub trait ValkeyWrite {
15    /// Append a single argument's bytes.
16    fn write_arg(&mut self, arg: &[u8]);
17
18    /// Append a single argument formatted via [`std::fmt::Display`].
19    fn write_arg_fmt(&mut self, arg: impl std::fmt::Display) {
20        self.write_arg(arg.to_string().as_bytes())
21    }
22}
23
24impl ValkeyWrite for Vec<Vec<u8>> {
25    fn write_arg(&mut self, arg: &[u8]) {
26        self.push(arg.to_owned());
27    }
28
29    fn write_arg_fmt(&mut self, arg: impl std::fmt::Display) {
30        self.push(arg.to_string().into_bytes())
31    }
32}
33
34impl ValkeyWrite for crate::cmd::Cmd {
35    fn write_arg(&mut self, arg: &[u8]) {
36        redis::RedisWrite::write_arg(self.as_redis_mut(), arg);
37    }
38
39    fn write_arg_fmt(&mut self, arg: impl std::fmt::Display) {
40        redis::RedisWrite::write_arg_fmt(self.as_redis_mut(), arg);
41    }
42}
43
44// --- ToValkeyArgs -------------------------------------------------------------------------------
45
46/// Describes how a value behaves in a numeric context.
47///
48/// Mirrors redis-rs's `NumericBehavior` type.
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub enum ValkeyNumericBehavior {
51    /// The value is not a number.
52    NonNumeric,
53    /// The value is an integer.
54    NumberIsInteger,
55    /// The value is a floating-point number.
56    NumberIsFloat,
57}
58
59/// Encodes a value into command arguments.
60///
61/// Implemented for the standard argument types:
62/// - integers
63/// - non-zero integers
64/// - floats
65/// - byte slices
66/// - booleans
67/// - strings
68/// - bytes
69/// - vectors
70/// - slices
71/// - arrays
72/// - options
73/// - references
74/// - standard maps and sets
75/// - tuples
76///
77/// Implement it for your own types to pass them directly as command arguments.
78///
79/// Mirrors redis-rs's `ToRedisArgs`.
80pub trait ToValkeyArgs {
81    /// Writes this value's argument encoding to `out`.
82    fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W);
83
84    /// Returns this value's argument encoding as a vector.
85    fn to_valkey_args(&self) -> Vec<Vec<u8>> {
86        let mut out = Vec::new();
87        self.write_valkey_args(&mut out);
88        out
89    }
90
91    /// The value's numeric behavior.
92    /// Defaults to `NonNumeric`.
93    fn describe_numeric_behavior(&self) -> ValkeyNumericBehavior {
94        ValkeyNumericBehavior::NonNumeric
95    }
96
97    /// The number of command arguments this value encodes to.
98    fn num_of_args(&self) -> usize {
99        let mut counter = ArgCounter(0);
100        self.write_valkey_args(&mut counter);
101        counter.0
102    }
103
104    /// The number of command arguments a slice of `Self` encodes to.
105    #[doc(hidden)]
106    fn num_of_args_from_slice(items: &[Self]) -> usize
107    where
108        Self: Sized,
109    {
110        items.iter().map(Self::num_of_args).sum()
111    }
112
113    /// Writes this slice's argument encoding to `out`.
114    #[doc(hidden)]
115    fn write_valkey_args_from_slice<W: ?Sized + ValkeyWrite>(items: &[Self], out: &mut W)
116    where
117        Self: Sized,
118    {
119        Self::write_valkey_args_from_iter(items.iter(), out);
120    }
121
122    /// Writes this iterator's argument encoding to `out`.
123    #[doc(hidden)]
124    fn write_valkey_args_from_iter<'a, I, W: ?Sized + ValkeyWrite>(items: I, out: &mut W)
125    where
126        I: Iterator<Item = &'a Self>,
127        Self: Sized + 'a,
128    {
129        for item in items {
130            item.write_valkey_args(out);
131        }
132    }
133}
134
135/// Encodes an integer as a command argument.
136macro_rules! impl_to_valkey_args_int {
137    ($($t:ty),* $(,)?) => {$(
138        impl ToValkeyArgs for $t {
139            fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
140                out.write_arg_fmt(self);
141            }
142            fn describe_numeric_behavior(&self) -> ValkeyNumericBehavior {
143                ValkeyNumericBehavior::NumberIsInteger
144            }
145        }
146    )*};
147}
148
149impl_to_valkey_args_int!(i8, i16, u16, i32, u32, i64, u64, isize, usize);
150
151/// Encodes a non-zero integer as a command argument.
152macro_rules! impl_to_valkey_args_nonzero {
153    ($($t:ty),* $(,)?) => {$(
154        impl ToValkeyArgs for $t {
155            fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
156                out.write_arg_fmt(self);
157            }
158            fn describe_numeric_behavior(&self) -> ValkeyNumericBehavior {
159                ValkeyNumericBehavior::NumberIsInteger
160            }
161        }
162    )*};
163}
164
165impl_to_valkey_args_nonzero!(
166    core::num::NonZeroU8,
167    core::num::NonZeroI8,
168    core::num::NonZeroU16,
169    core::num::NonZeroI16,
170    core::num::NonZeroU32,
171    core::num::NonZeroI32,
172    core::num::NonZeroU64,
173    core::num::NonZeroI64,
174    core::num::NonZeroUsize,
175    core::num::NonZeroIsize,
176);
177
178/// Encodes a float as a command argument.
179macro_rules! impl_to_valkey_args_float {
180    ($($t:ty),* $(,)?) => {$(
181        impl ToValkeyArgs for $t {
182            fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
183                let mut buf = ryu::Buffer::new();
184                out.write_arg(buf.format(*self).as_bytes());
185            }
186            fn describe_numeric_behavior(&self) -> ValkeyNumericBehavior {
187                ValkeyNumericBehavior::NumberIsFloat
188            }
189        }
190    )*};
191}
192
193impl_to_valkey_args_float!(f32, f64);
194
195/// Encodes a `u8` as a command argument.
196impl ToValkeyArgs for u8 {
197    fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
198        out.write_arg_fmt(self);
199    }
200
201    // A byte slice encodes as a single binary argument, so `Vec<u8>`/`&[u8]`
202    // pass through unsplit.
203    fn write_valkey_args_from_slice<W: ?Sized + ValkeyWrite>(items: &[u8], out: &mut W) {
204        out.write_arg(items);
205    }
206
207    fn num_of_args_from_slice(_items: &[u8]) -> usize {
208        1
209    }
210}
211
212/// Encodes a `bool` as a command argument.
213impl ToValkeyArgs for bool {
214    fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
215        out.write_arg(if *self { b"1" } else { b"0" });
216    }
217}
218
219/// Encodes a `String` as a command argument.
220impl ToValkeyArgs for String {
221    fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
222        out.write_arg(self.as_bytes());
223    }
224}
225
226/// Encodes a `&str` as a command argument.
227impl ToValkeyArgs for &str {
228    fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
229        out.write_arg(self.as_bytes());
230    }
231}
232
233/// Encodes a `Bytes` as a command argument.
234impl ToValkeyArgs for Bytes {
235    fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
236        out.write_arg(self.as_ref());
237    }
238}
239
240/// Encodes a `Vec` as command arguments.
241impl<T: ToValkeyArgs> ToValkeyArgs for Vec<T> {
242    fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
243        T::write_valkey_args_from_slice(self, out);
244    }
245
246    fn num_of_args(&self) -> usize {
247        T::num_of_args_from_slice(self)
248    }
249}
250
251/// Encodes a slice as command arguments.
252impl<T: ToValkeyArgs> ToValkeyArgs for &[T] {
253    fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
254        T::write_valkey_args_from_slice(self, out);
255    }
256
257    fn num_of_args(&self) -> usize {
258        T::num_of_args_from_slice(self)
259    }
260}
261
262/// Encodes a fixed-size array as command arguments.
263impl<T: ToValkeyArgs, const N: usize> ToValkeyArgs for &[T; N] {
264    fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
265        T::write_valkey_args_from_slice(self.as_slice(), out);
266    }
267
268    fn num_of_args(&self) -> usize {
269        T::num_of_args_from_slice(self.as_slice())
270    }
271}
272
273/// Encodes an `Option` as command arguments.
274impl<T: ToValkeyArgs> ToValkeyArgs for Option<T> {
275    fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
276        if let Some(x) = self {
277            x.write_valkey_args(out);
278        }
279    }
280
281    fn describe_numeric_behavior(&self) -> ValkeyNumericBehavior {
282        match self {
283            Some(x) => x.describe_numeric_behavior(),
284            None => ValkeyNumericBehavior::NonNumeric,
285        }
286    }
287
288    fn num_of_args(&self) -> usize {
289        self.as_ref().map_or(0, ToValkeyArgs::num_of_args)
290    }
291}
292
293/// Encodes a reference as command arguments.
294impl<T: ToValkeyArgs> ToValkeyArgs for &T {
295    fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
296        (*self).write_valkey_args(out);
297    }
298
299    fn describe_numeric_behavior(&self) -> ValkeyNumericBehavior {
300        (*self).describe_numeric_behavior()
301    }
302
303    fn num_of_args(&self) -> usize {
304        (*self).num_of_args()
305    }
306}
307
308/// Encodes a `HashSet`'s members as command arguments.
309impl<T: ToValkeyArgs + std::cmp::Eq + std::hash::Hash, S: std::hash::BuildHasher> ToValkeyArgs
310    for std::collections::HashSet<T, S>
311{
312    fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
313        T::write_valkey_args_from_iter(self.iter(), out);
314    }
315}
316
317/// Encodes a `BTreeSet`'s members as command arguments.
318impl<T: ToValkeyArgs + std::cmp::Eq + std::hash::Hash + Ord> ToValkeyArgs
319    for std::collections::BTreeSet<T>
320{
321    fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
322        T::write_valkey_args_from_iter(self.iter(), out);
323    }
324}
325
326/// Encodes a `BTreeMap` as command arguments.
327/// Panics if a key or value does not encode as exactly one argument.
328impl<K: ToValkeyArgs, V: ToValkeyArgs> ToValkeyArgs for std::collections::BTreeMap<K, V>
329where
330    K: std::cmp::Eq + std::hash::Hash + Ord,
331{
332    fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
333        for (key, value) in self {
334            assert!(key.num_of_args() == 1 && value.num_of_args() == 1);
335            key.write_valkey_args(out);
336            value.write_valkey_args(out);
337        }
338    }
339}
340
341/// Encodes a `HashMap` as command arguments.
342/// Panics if a key or value does not encode as exactly one argument.
343impl<K: ToValkeyArgs, V: ToValkeyArgs, S: std::hash::BuildHasher> ToValkeyArgs
344    for std::collections::HashMap<K, V, S>
345where
346    K: std::cmp::Eq + std::hash::Hash,
347{
348    fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
349        for (key, value) in self {
350            assert!(key.num_of_args() == 1 && value.num_of_args() == 1);
351            key.write_valkey_args(out);
352            value.write_valkey_args(out);
353        }
354    }
355}
356
357/// Encodes a tuple as command arguments.
358macro_rules! impl_to_valkey_args_tuple {
359    ($( ($($name:ident),+) ),+ $(,)?) => {$(
360        #[doc(hidden)]
361        impl<$($name: ToValkeyArgs),*> ToValkeyArgs for ($($name,)*) {
362            #[allow(non_snake_case, unused_variables)]
363            fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
364                let ($(ref $name,)*) = *self;
365                $($name.write_valkey_args(out);)*
366            }
367        }
368    )+};
369}
370
371impl_to_valkey_args_tuple! {
372    (T1),
373    (T1, T2),
374    (T1, T2, T3),
375    (T1, T2, T3, T4),
376    (T1, T2, T3, T4, T5),
377    (T1, T2, T3, T4, T5, T6),
378    (T1, T2, T3, T4, T5, T6, T7),
379    (T1, T2, T3, T4, T5, T6, T7, T8),
380    (T1, T2, T3, T4, T5, T6, T7, T8, T9),
381    (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10),
382    (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11),
383    (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12),
384}
385
386// ---- ToSingleValkeyArg -------------------------------------------------------------------------
387
388/// Encodes a value into exactly one command argument.
389///
390/// Implemented for these standard argument types:
391/// - integers
392/// - non-zero integers
393/// - floats
394/// - byte slices
395/// - booleans
396/// - strings
397/// - bytes
398/// - references
399///
400/// Implement it for your own types to pass them directly as command arguments.
401///
402/// Mirrors redis-rs's `ToSingleRedisArg`.
403pub trait ToSingleValkeyArg: ToValkeyArgs {}
404
405macro_rules! impl_to_single_valkey_arg {
406    ($($t:ty),* $(,)?) => {$(
407        impl ToSingleValkeyArg for $t {}
408    )*};
409}
410
411impl_to_single_valkey_arg!(
412    i8,
413    i16,
414    u16,
415    i32,
416    u32,
417    i64,
418    u64,
419    isize,
420    usize,
421    core::num::NonZeroU8,
422    core::num::NonZeroI8,
423    core::num::NonZeroU16,
424    core::num::NonZeroI16,
425    core::num::NonZeroU32,
426    core::num::NonZeroI32,
427    core::num::NonZeroU64,
428    core::num::NonZeroI64,
429    core::num::NonZeroUsize,
430    core::num::NonZeroIsize,
431    f32,
432    f64,
433    u8,
434    bool,
435    String,
436    &str,
437    Bytes,
438    Vec<u8>,
439    &[u8],
440);
441
442impl<const N: usize> ToSingleValkeyArg for &[u8; N] {}
443
444impl<T: ToSingleValkeyArg> ToSingleValkeyArg for &T {}
445
446// --- ArgCounter ---------------------------------------------------------------------------------
447
448/// A [`ValkeyWrite`] that counts arguments without storing them.
449struct ArgCounter(usize);
450
451impl ValkeyWrite for ArgCounter {
452    fn write_arg(&mut self, _arg: &[u8]) {
453        self.0 += 1;
454    }
455
456    fn write_arg_fmt(&mut self, _arg: impl std::fmt::Display) {
457        self.0 += 1;
458    }
459}
460
461// --- Tests --------------------------------------------------------------------------------------
462
463#[cfg(test)]
464mod tests {
465    use super::*;
466
467    // Values to encode.
468    const TEXT: &[u8] = b"key";
469    const BINARY: &[u8] = &[0u8, 1, 2, 255, 0, 42];
470    const NUMBER: i64 = -7;
471
472    #[test]
473    fn vec_writer() {
474        let mut out: Vec<Vec<u8>> = Vec::new();
475        out.write_arg(TEXT);
476        out.write_arg(BINARY);
477        out.write_arg_fmt(NUMBER);
478
479        assert_eq!(out, vec![TEXT.to_vec(), BINARY.to_vec(), b"-7".to_vec()]);
480    }
481
482    #[test]
483    fn cmd_writer() {
484        let mut out = crate::cmd::Cmd::new();
485        out.write_arg(TEXT);
486        out.write_arg(BINARY);
487        out.write_arg_fmt(NUMBER);
488
489        assert_eq!(
490            out.as_redis().get_packed_command(),
491            crate::cmd::Cmd::new()
492                .arg(TEXT)
493                .arg(BINARY)
494                .arg(NUMBER)
495                .as_redis()
496                .get_packed_command()
497        );
498    }
499}
500
501#[cfg(test)]
502mod to_valkey_args_tests {
503    use super::*;
504    use crate::test_utils::assert_args;
505    use crate::test_utils::assert_args_empty;
506    use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
507    use std::sync::LazyLock;
508
509    static HASH_SET_0: LazyLock<HashSet<&str>> = LazyLock::new(HashSet::new);
510    static HASH_SET_1: LazyLock<HashSet<&str>> = LazyLock::new(|| HashSet::from(["x"]));
511    static HASH_SET_2: LazyLock<HashSet<&str>> = LazyLock::new(|| HashSet::from(["a", "b", "c"]));
512
513    static BTREE_SET_0: LazyLock<BTreeSet<&str>> = LazyLock::new(BTreeSet::new);
514    static BTREE_SET_1: LazyLock<BTreeSet<&str>> = LazyLock::new(|| BTreeSet::from(["x"]));
515    static BTREE_SET_2: LazyLock<BTreeSet<&str>> =
516        LazyLock::new(|| BTreeSet::from(["a", "b", "c"]));
517
518    static HASH_MAP_0: LazyLock<HashMap<&str, i64>> = LazyLock::new(HashMap::new);
519    static HASH_MAP_1: LazyLock<HashMap<&str, i64>> = LazyLock::new(|| HashMap::from([("k", 1)]));
520    static HASH_MAP_2: LazyLock<HashMap<&str, i64>> =
521        LazyLock::new(|| HashMap::from([("f1", 1), ("f2", 2)]));
522
523    static BTREE_MAP_0: LazyLock<BTreeMap<&str, i64>> = LazyLock::new(BTreeMap::new);
524    static BTREE_MAP_1: LazyLock<BTreeMap<&str, i64>> =
525        LazyLock::new(|| BTreeMap::from([("k", 1)]));
526    static BTREE_MAP_2: LazyLock<BTreeMap<&str, i64>> =
527        LazyLock::new(|| BTreeMap::from([("f1", 1), ("f2", 2)]));
528
529    #[test]
530    fn to_valkey_args_matches_redis_encoding() {
531        use redis::ToRedisArgs;
532        macro_rules! same {
533            ($v:expr) => {
534                assert_eq!(
535                    $v.to_valkey_args(),
536                    $v.to_redis_args(),
537                    "encoding of {:?}",
538                    $v
539                );
540            };
541        }
542
543        // Integers
544        same!(1i8);
545        same!(2i16);
546        same!(3i32);
547        same!(4i64);
548        same!(5isize);
549        same!(0u8);
550        same!(255u8);
551        same!(6u16);
552        same!(7u32);
553        same!(8u64);
554        same!(9usize);
555        same!(i64::MAX);
556        same!(i64::MIN);
557        same!(u64::MAX);
558
559        // Non-zero integers
560        same!(core::num::NonZeroI8::new(-1).unwrap());
561        same!(core::num::NonZeroI16::new(-2).unwrap());
562        same!(core::num::NonZeroI32::new(-3).unwrap());
563        same!(core::num::NonZeroI64::new(-4).unwrap());
564        same!(core::num::NonZeroIsize::new(-5).unwrap());
565        same!(core::num::NonZeroU8::new(1).unwrap());
566        same!(core::num::NonZeroU16::new(2).unwrap());
567        same!(core::num::NonZeroU32::new(3).unwrap());
568        same!(core::num::NonZeroU64::new(4).unwrap());
569        same!(core::num::NonZeroUsize::new(5).unwrap());
570
571        // Floats
572        same!(1.5f32);
573        same!(0.0f64);
574        same!(1.5f64);
575        same!(-2.25f64);
576        same!(0.1f64);
577        same!(1e20f64);
578        same!(-1e-20f64);
579        same!(std::f64::consts::PI);
580
581        // Byte slices
582        same!(b"raw".to_vec());
583        same!(&b"raw"[..]);
584
585        // Booleans
586        same!(true);
587        same!(false);
588
589        // Strings
590        same!("hello");
591        same!(String::from("world"));
592
593        // Sequences
594        same!(vec!["a", "b", "c"]);
595        same!(&["a", "b"][..]);
596        same!(&["a", "b"]);
597        same!(&[("f1", 1i64), ("f2", 2i64)][..]);
598
599        // Options
600        same!(Some(5i64));
601        same!(Option::<i64>::None);
602
603        // References
604        same!(&5i64);
605        same!(&"x");
606
607        // Maps and sets
608        same!(&*HASH_SET_0);
609        same!(&*BTREE_SET_0);
610        same!(&*HASH_MAP_0);
611        same!(&*BTREE_MAP_0);
612
613        same!(&*HASH_SET_1);
614        same!(&*BTREE_SET_1);
615        same!(&*HASH_MAP_1);
616        same!(&*BTREE_MAP_1);
617
618        same!(&*HASH_SET_2);
619        same!(&*BTREE_SET_2);
620        same!(&*HASH_MAP_2);
621        same!(&*BTREE_MAP_2);
622
623        // Tuples
624        same!((1,));
625        same!((1, 2));
626        same!((1, 2, 3));
627        same!((1, 2, 3, 4));
628        same!((1, 2, 3, 4, 5));
629        same!((1, 2, 3, 4, 5, 6));
630        same!((1, 2, 3, 4, 5, 6, 7));
631        same!((1, 2, 3, 4, 5, 6, 7, 8));
632        same!((1, 2, 3, 4, 5, 6, 7, 8, 9));
633        same!((1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
634        same!((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11));
635        same!((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12));
636    }
637
638    /// `Bytes` is a GLIDE addition, so it has no `ToRedisArgs` equivalent.
639    #[test]
640    fn to_valkey_args_bytes() {
641        assert_eq!(
642            Bytes::from_static(b"\x00\xff data").to_valkey_args(),
643            vec![b"\x00\xff data".to_vec()]
644        );
645    }
646
647    #[test]
648    fn num_of_args() {
649        // Integers
650        assert_args(1i8, &["1"]);
651        assert_args(-1i64, &["-1"]);
652        assert_args(1usize, &["1"]);
653
654        // Non-zero integers
655        assert_args(core::num::NonZeroU8::new(1).unwrap(), &["1"]);
656
657        // Floats
658        assert_args(1.5f64, &["1.5"]);
659
660        // Byte slices (one binary argument, even when empty)
661        assert_args(b"bytes".to_vec(), &["bytes"]);
662        assert_args(Vec::<u8>::new(), &[""]);
663        assert_args(&b"bytes"[..], &["bytes"]);
664        assert_args(b"bytes", &["bytes"]);
665        assert_args(vec![0xFFu8, 0x00], &[[0xFFu8, 0x00].as_slice()]);
666
667        // Booleans
668        assert_args(true, &["1"]);
669
670        // Strings
671        assert_args("k", &["k"]);
672        assert_args(String::from("k"), &["k"]);
673
674        // Bytes
675        assert_args(Bytes::from_static(b"b"), &["b"]);
676
677        // Sequences
678        assert_args(vec!["one"], &["one"]);
679        assert_args(vec!["a", "b", "c"], &["a", "b", "c"]);
680        assert_args_empty(Vec::<&str>::new());
681        assert_args(&["a", "b"][..], &["a", "b"]);
682        assert_args(&["a", "b"], &["a", "b"]);
683        assert_args(vec![vec!["a", "b"], vec!["c"]], &["a", "b", "c"]);
684        assert_args(&[("f1", 1i64), ("f2", 2i64)][..], &["f1", "1", "f2", "2"]);
685
686        // Options
687        assert_args_empty(Option::<i64>::None);
688        assert_args(Some(1i64), &["1"]);
689        assert_args(Some(vec!["a", "b"]), &["a", "b"]);
690
691        // References. The explicit `&` exercises the `ToValkeyArgs for &T` impl,
692        // so the borrow is deliberate, not `needless_borrows_for_generic_args`.
693        #[allow(clippy::needless_borrows_for_generic_args)]
694        {
695            assert_args(&1i64, &["1"]);
696            assert_args(&&vec!["a", "b"], &["a", "b"]);
697        }
698
699        // Maps and sets
700        assert_args_empty(&*HASH_SET_0);
701        assert_args_empty(&*BTREE_SET_0);
702        assert_args_empty(&*HASH_MAP_0);
703        assert_args_empty(&*BTREE_MAP_0);
704
705        assert_args(&*HASH_SET_1, &["x"]);
706        assert_args(&*BTREE_SET_1, &["x"]);
707        assert_args(&*HASH_MAP_1, &["k", "1"]);
708        assert_args(&*BTREE_MAP_1, &["k", "1"]);
709
710        assert_args(&*BTREE_SET_2, &["a", "b", "c"]);
711        assert_args(&*BTREE_MAP_2, &["f1", "1", "f2", "2"]);
712
713        // Hash-based collections have no fixed order, so only the count is checked.
714        assert_eq!(HASH_SET_2.to_valkey_args().len(), 3);
715        assert_eq!(HASH_SET_2.num_of_args(), 3);
716        assert_eq!(HASH_MAP_2.to_valkey_args().len(), 4);
717        assert_eq!(HASH_MAP_2.num_of_args(), 4);
718
719        // Tuples
720        assert_args((1i64,), &["1"]);
721        assert_args((1i64, 2i64), &["1", "2"]);
722        assert_args(("a", 1, 2.5), &["a", "1", "2.5"]);
723        assert_args((vec!["a", "b"], "c"), &["a", "b", "c"]);
724    }
725
726    #[test]
727    fn describe_numeric_behavior() {
728        use ValkeyNumericBehavior::{NonNumeric, NumberIsFloat, NumberIsInteger};
729        macro_rules! num {
730            ($v:expr, $b:expr) => {
731                assert_eq!(
732                    $v.describe_numeric_behavior(),
733                    $b,
734                    "numeric behavior of {:?}",
735                    $v
736                );
737            };
738        }
739
740        // Integers
741        num!(1i8, NumberIsInteger);
742        num!(2i16, NumberIsInteger);
743        num!(3i32, NumberIsInteger);
744        num!(4i64, NumberIsInteger);
745        num!(5isize, NumberIsInteger);
746        num!(6u16, NumberIsInteger);
747        num!(7u32, NumberIsInteger);
748        num!(8u64, NumberIsInteger);
749        num!(9usize, NumberIsInteger);
750
751        // Non-zero integers
752        num!(core::num::NonZeroI8::new(-1).unwrap(), NumberIsInteger);
753        num!(core::num::NonZeroI16::new(-2).unwrap(), NumberIsInteger);
754        num!(core::num::NonZeroI32::new(-3).unwrap(), NumberIsInteger);
755        num!(core::num::NonZeroI64::new(-4).unwrap(), NumberIsInteger);
756        num!(core::num::NonZeroIsize::new(-5).unwrap(), NumberIsInteger);
757        num!(core::num::NonZeroU8::new(1).unwrap(), NumberIsInteger);
758        num!(core::num::NonZeroU16::new(2).unwrap(), NumberIsInteger);
759        num!(core::num::NonZeroU32::new(3).unwrap(), NumberIsInteger);
760        num!(core::num::NonZeroU64::new(4).unwrap(), NumberIsInteger);
761        num!(core::num::NonZeroUsize::new(5).unwrap(), NumberIsInteger);
762
763        // Floats
764        num!(1.5f32, NumberIsFloat);
765        num!(1.5f64, NumberIsFloat);
766
767        // Byte slices. `u8` is a byte, not an integer (matching redis-rs), so
768        // it is non-numeric.
769        num!(0u8, NonNumeric);
770        num!(b"raw".to_vec(), NonNumeric);
771        num!(&b"raw"[..], NonNumeric);
772
773        // Booleans
774        num!(true, NonNumeric);
775
776        // Strings
777        num!("hello", NonNumeric);
778        num!(String::from("world"), NonNumeric);
779
780        // Bytes
781        num!(Bytes::from_static(b"b"), NonNumeric);
782
783        // Sequences
784        num!(vec!["a", "b"], NonNumeric);
785        num!(&["a", "b"][..], NonNumeric);
786        num!(&["a", "b"], NonNumeric);
787
788        // Options
789        num!(Some(5i64), NumberIsInteger);
790        num!(Some(1.5f64), NumberIsFloat);
791        num!(Some("x"), NonNumeric);
792        num!(Option::<f64>::None, NonNumeric);
793
794        // References
795        num!(&5i64, NumberIsInteger);
796        num!(&1.5f64, NumberIsFloat);
797        num!(&&5i64, NumberIsInteger);
798        num!(&"x", NonNumeric);
799
800        // Maps and sets
801        num!(&*HASH_SET_2, NonNumeric);
802        num!(&*BTREE_SET_2, NonNumeric);
803        num!(&*HASH_MAP_2, NonNumeric);
804        num!(&*BTREE_MAP_2, NonNumeric);
805
806        // Tuples
807        num!((1,), NonNumeric);
808        num!((1, 2), NonNumeric);
809        num!((1, 2, 3), NonNumeric);
810    }
811}
812
813#[cfg(test)]
814mod to_single_arg {
815    use super::*;
816
817    /// Compiles only if `T` implements [`ToSingleValkeyArg`].
818    fn assert_single<T: ToSingleValkeyArg>(value: T) {
819        assert_eq!(value.num_of_args(), 1);
820    }
821
822    #[test]
823    fn to_single_valkey_arg_impls_encode_one_arg() {
824        // Integers
825        assert_single(1i8);
826        assert_single(1i64);
827        assert_single(1usize);
828        assert_single(255u8);
829
830        // Non-zero integers
831        assert_single(core::num::NonZeroU64::new(1).unwrap());
832
833        // Floats
834        assert_single(1.5f32);
835        assert_single(1.5f64);
836
837        // Booleans
838        assert_single(true);
839
840        // Strings
841        assert_single("k");
842        assert_single(String::from("k"));
843
844        // Bytes
845        assert_single(Bytes::from_static(b"b"));
846
847        // Byte slices, vectors, and arrays
848        assert_single(b"raw".to_vec());
849        assert_single(&b"raw"[..]);
850        assert_single(b"raw");
851        assert_single(Vec::<u8>::new());
852
853        // References. The explicit `&` exercises the `ToSingleValkeyArg for &T` impl,
854        // so the borrow is deliberate, not `needless_borrows_for_generic_args`.
855        #[allow(clippy::needless_borrows_for_generic_args)]
856        {
857            assert_single(&"k");
858            assert_single(&&5i64);
859        }
860    }
861}