Skip to main content

glide/
value.rs

1// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
2//! Valkey reply value types.
3//!
4//! [`ValkeyValue`] represents a value returned by the Valkey server,
5//! which [`FromValkeyValue`] decodes it into concrete Rust types.
6
7use crate::ValkeyResult;
8use crate::error::GlideError;
9use bytes::Bytes;
10use num_bigint::BigInt;
11use redis::{Value, VerbatimFormat};
12
13/// A value returned by the Valkey server.
14/// Covers all RESP2/RESP3 value types.
15/// Decode into concrete Rust types via [`FromValkeyValue`].
16///
17/// Mirrors redis-rs's `Value`.
18#[derive(Clone, Debug, PartialEq)]
19pub enum ValkeyValue {
20    /// A nil reply.
21    Nil,
22
23    /// An integer reply.
24    Int(i64),
25
26    /// Binary-safe string data.
27    BulkString(Bytes),
28
29    /// An array of nested values.
30    Array(Vec<ValkeyValue>),
31
32    /// A simple string, not binary-safe.
33    SimpleString(String),
34
35    /// The status reply `OK`.
36    Okay,
37
38    /// An unordered key/value map (RESP3).
39    Map(Vec<(ValkeyValue, ValkeyValue)>),
40
41    /// An unordered set (RESP3).
42    Set(Vec<ValkeyValue>),
43
44    /// A double-precision float reply (RESP3).
45    Double(f64),
46
47    /// A boolean reply (RESP3).
48    Boolean(bool),
49
50    /// A verbatim string carrying a format hint (RESP3).
51    VerbatimString {
52        /// The declared text format.
53        format: ValkeyVerbatimFormat,
54        /// The string contents.
55        text: String,
56    },
57
58    /// A number outside the range of a signed 64-bit integer (RESP3).
59    BigNumber(BigInt),
60
61    /// An error reply from the server (e.g. a failed element within a reply).
62    ServerError(ValkeyServerError),
63}
64
65/// The text format of a [`ValkeyValue::VerbatimString`].
66///
67/// Mirrors redis-rs's `VerbatimFormat` type.
68#[derive(Clone, Debug, PartialEq, Eq)]
69pub enum ValkeyVerbatimFormat {
70    /// A format string other than the ones below.
71    Unknown(String),
72
73    /// Markdown (`mkd`).
74    Markdown,
75
76    /// Plain text (`txt`).
77    Text,
78}
79
80/// An error reply.
81///
82/// Mirrors redis-rs's `ServerError`.
83#[derive(Clone, Debug, PartialEq, Eq)]
84pub struct ValkeyServerError {
85    code: String,
86    detail: Option<String>,
87}
88
89impl ValkeyServerError {
90    /// The error code (e.g. `WRONGTYPE`, `MOVED`).
91    pub fn err_code(&self) -> &str {
92        &self.code
93    }
94
95    /// The human-readable detail.
96    pub fn details(&self) -> Option<&str> {
97        self.detail.as_deref()
98    }
99}
100
101impl ValkeyVerbatimFormat {
102    fn from_redis(format: VerbatimFormat) -> Self {
103        match format {
104            VerbatimFormat::Unknown(s) => ValkeyVerbatimFormat::Unknown(s),
105            VerbatimFormat::Markdown => ValkeyVerbatimFormat::Markdown,
106            VerbatimFormat::Text => ValkeyVerbatimFormat::Text,
107        }
108    }
109}
110
111impl ValkeyValue {
112    pub(crate) fn from_redis(value: Value) -> ValkeyResult<Self> {
113        let pairs = |ps: Vec<(Value, Value)>| -> ValkeyResult<Vec<(ValkeyValue, ValkeyValue)>> {
114            ps.into_iter()
115                .map(|(k, v)| Ok((ValkeyValue::from_redis(k)?, ValkeyValue::from_redis(v)?)))
116                .collect()
117        };
118        Ok(match value {
119            Value::Nil => ValkeyValue::Nil,
120            Value::Int(i) => ValkeyValue::Int(i),
121            Value::BulkString(b) => ValkeyValue::BulkString(b),
122            Value::Array(items) => ValkeyValue::Array(
123                items
124                    .into_iter()
125                    .map(ValkeyValue::from_redis)
126                    .collect::<ValkeyResult<_>>()?,
127            ),
128            Value::SimpleString(s) => ValkeyValue::SimpleString(s),
129            Value::Okay => ValkeyValue::Okay,
130            Value::Map(ps) => ValkeyValue::Map(pairs(ps)?),
131            Value::Set(items) => ValkeyValue::Set(
132                items
133                    .into_iter()
134                    .map(ValkeyValue::from_redis)
135                    .collect::<ValkeyResult<_>>()?,
136            ),
137            Value::Double(d) => ValkeyValue::Double(d),
138            Value::Boolean(b) => ValkeyValue::Boolean(b),
139            Value::VerbatimString { format, text } => ValkeyValue::VerbatimString {
140                format: ValkeyVerbatimFormat::from_redis(format),
141                text,
142            },
143            Value::BigNumber(n) => ValkeyValue::BigNumber(n),
144            Value::ServerError(e) => ValkeyValue::ServerError(ValkeyServerError {
145                code: e.err_code().to_string(),
146                detail: e.details().map(str::to_string),
147            }),
148
149            // There a currently no known cases where a response would include
150            // an attribute value, so we match redis-rs's behavior: return the
151            // attribute data and drop the corresponding metadata.
152            Value::Attribute { data, .. } => ValkeyValue::from_redis(*data)?,
153
154            // Push values should never occur in command replies (they should be handled
155            // by glide-core), so raise an error if we encounter one.
156            Value::Push { .. } => {
157                return Err(GlideError::Request(
158                    "unexpected Push value in command reply".into(),
159                ));
160            }
161        })
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use redis::{ErrorKind, RedisError};
169
170    // ---- ValkeyValue::from_redis ----------------------------------------
171
172    #[test]
173    fn from_redis_maps_variants() {
174        let rv = Value::Array(vec![
175            Value::Nil,
176            Value::Int(-5),
177            Value::BulkString(Bytes::from_static(b"hi")),
178            Value::SimpleString("s".into()),
179            Value::Okay,
180            Value::Double(1.5),
181            Value::Boolean(true),
182            Value::BigNumber(BigInt::from(i64::MAX) + 1),
183            Value::VerbatimString {
184                format: VerbatimFormat::Markdown,
185                text: "md".into(),
186            },
187            Value::Map(vec![(Value::Int(1), Value::Int(2))]),
188            Value::Set(vec![Value::Int(9)]),
189            Value::Attribute {
190                data: Box::new(Value::Int(42)),
191                attributes: vec![(Value::SimpleString("k".into()), Value::Int(1))],
192            },
193        ]);
194        let vv = ValkeyValue::from_redis(rv).unwrap();
195        assert_eq!(
196            vv,
197            ValkeyValue::Array(vec![
198                ValkeyValue::Nil,
199                ValkeyValue::Int(-5),
200                ValkeyValue::BulkString(Bytes::from_static(b"hi")),
201                ValkeyValue::SimpleString("s".into()),
202                ValkeyValue::Okay,
203                ValkeyValue::Double(1.5),
204                ValkeyValue::Boolean(true),
205                ValkeyValue::BigNumber(BigInt::from(i64::MAX) + 1),
206                ValkeyValue::VerbatimString {
207                    format: ValkeyVerbatimFormat::Markdown,
208                    text: "md".into(),
209                },
210                ValkeyValue::Map(vec![(ValkeyValue::Int(1), ValkeyValue::Int(2))]),
211                ValkeyValue::Set(vec![ValkeyValue::Int(9)]),
212                ValkeyValue::Int(42),
213            ])
214        );
215    }
216
217    #[test]
218    fn from_redis_reads_server_error_code_and_detail() {
219        let error = RedisError::from((ErrorKind::ResponseError, "boom", "detail".to_string()));
220        let v = Value::ServerError(error.into());
221        match ValkeyValue::from_redis(v).unwrap() {
222            ValkeyValue::ServerError(e) => {
223                assert_eq!(e.err_code(), "ERR");
224                assert_eq!(e.details(), Some("boom detail"));
225            }
226            other => panic!("expected ServerError, got {other:?}"),
227        }
228    }
229
230    #[test]
231    fn from_redis_push_value_error() {
232        let v = Value::Push {
233            kind: redis::PushKind::Message,
234            data: vec![Value::Int(1)],
235        };
236        assert!(ValkeyValue::from_redis(v).is_err());
237    }
238}
239
240// ==== FromValkeyValue ====================================================
241
242/// Converts a [`ValkeyValue`] reply into a Rust type.
243pub trait FromValkeyValue: Sized {
244    /// Converts an owned [`ValkeyValue`] into `Self`.
245    fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self>;
246
247    /// Converts a borrowed [`ValkeyValue`] into `Self`.
248    fn from_valkey_value(value: &ValkeyValue) -> ValkeyResult<Self> {
249        Self::from_owned_valkey_value(value.clone())
250    }
251
252    /// Converts a `Vec<ValkeyValue>` into a `Vec<Self>`.
253    #[doc(hidden)]
254    fn from_owned_valkey_values(items: Vec<ValkeyValue>) -> ValkeyResult<Vec<Self>> {
255        items
256            .into_iter()
257            .map(Self::from_owned_valkey_value)
258            .collect()
259    }
260
261    /// Converts a `Vec<u8>` into a `Vec<Self>`.
262    #[doc(hidden)]
263    fn from_owned_byte_vec(bytes: Vec<u8>) -> ValkeyResult<Vec<Self>> {
264        Self::from_owned_valkey_value(ValkeyValue::BulkString(Bytes::from(bytes))).map(|v| vec![v])
265    }
266}
267
268/// Build a error for the given `ValkeyValue` and message.
269pub(crate) fn to_glide_error(value: ValkeyValue, msg: &str) -> GlideError {
270    GlideError::Request(format!("{msg} (response was {value:?})"))
271}
272
273/// Decodes a `ValkeyValue` into a map.
274/// Returns an error with the given target type on failure.
275fn into_map<K, V, M>(value: ValkeyValue, target_type: &str) -> ValkeyResult<M>
276where
277    K: FromValkeyValue,
278    V: FromValkeyValue,
279    M: FromIterator<(K, V)>,
280{
281    // Normalize the reply into key/value pairs.
282    let pairs: Vec<(ValkeyValue, ValkeyValue)> = match value {
283        ValkeyValue::Nil => Vec::new(),
284        ValkeyValue::Map(pairs) => pairs,
285        ValkeyValue::Array(items) if items.len() % 2 == 0 => {
286            let mut it = items.into_iter();
287            let mut pairs = Vec::with_capacity(it.len() / 2);
288            while let (Some(k), Some(v)) = (it.next(), it.next()) {
289                pairs.push((k, v));
290            }
291            pairs
292        }
293        other => return Err(to_glide_error(other, target_type)),
294    };
295
296    pairs
297        .into_iter()
298        .map(|(k, v)| {
299            Ok((
300                K::from_owned_valkey_value(k)?,
301                V::from_owned_valkey_value(v)?,
302            ))
303        })
304        .collect()
305}
306
307/// Decodes a `ValkeyValue` into a set.
308/// Returns an error with the given target type on failure.
309fn into_set<T, C>(value: ValkeyValue, target_type: &str) -> ValkeyResult<C>
310where
311    T: FromValkeyValue,
312    C: FromIterator<T>,
313{
314    let items = match value {
315        ValkeyValue::Array(items) => items,
316        ValkeyValue::Set(items) => items,
317        ValkeyValue::Nil => Vec::new(),
318        other => return Err(to_glide_error(other, target_type)),
319    };
320
321    items.into_iter().map(T::from_owned_valkey_value).collect()
322}
323
324// Integer decoding diverges from redis-rs.
325// See `migration.md#integer-decoding-is-strict`.
326macro_rules! decode_valkey_int {
327    ($t:ty, $value:expr) => {
328        match $value {
329            ValkeyValue::Boolean(b) => Ok(<$t>::from(b)),
330            #[allow(clippy::unnecessary_fallible_conversions)]
331            ValkeyValue::Int(v) => <$t>::try_from(v).map_err(|_| {
332                to_glide_error(
333                    ValkeyValue::Int(v),
334                    concat!("Response integer out of range for ", stringify!($t), "."),
335                )
336            }),
337            // `$t::MAX as f64` may round up (e.g. to 2^64 for `u64`), so compare
338            // against the exclusive bound `MAX + 1`.
339            ValkeyValue::Double(v)
340                if v.is_finite()
341                    && v.fract() == 0.0
342                    && v >= <$t>::MIN as f64
343                    && v < <$t>::MAX as f64 + 1.0 =>
344            {
345                Ok(v as $t)
346            }
347            ValkeyValue::Double(v) => Err(to_glide_error(
348                ValkeyValue::Double(v),
349                concat!(
350                    "Response double is not an integer in range for ",
351                    stringify!($t),
352                    "."
353                ),
354            )),
355            ValkeyValue::SimpleString(s) => {
356                let parsed = s.parse::<$t>().ok();
357                parsed.ok_or_else(|| {
358                    to_glide_error(
359                        ValkeyValue::SimpleString(s),
360                        concat!("Could not convert string to ", stringify!($t), "."),
361                    )
362                })
363            }
364            ValkeyValue::BulkString(bytes) => {
365                let parsed = std::str::from_utf8(&bytes)
366                    .ok()
367                    .and_then(|s| s.parse::<$t>().ok());
368                parsed.ok_or_else(|| {
369                    to_glide_error(
370                        ValkeyValue::BulkString(bytes),
371                        concat!("Could not convert string to ", stringify!($t), "."),
372                    )
373                })
374            }
375            other => Err(to_glide_error(
376                other,
377                concat!("Response type not convertible to ", stringify!($t), "."),
378            )),
379        }
380    };
381}
382
383macro_rules! impl_from_valkey_int {
384    ($($t:ty),* $(,)?) => {$(
385        impl FromValkeyValue for $t {
386            fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<$t> {
387                decode_valkey_int!($t, value)
388            }
389        }
390    )*};
391}
392
393impl_from_valkey_int!(i8, i16, i32, i64, i128, u16, u32, u64, u128, isize, usize);
394
395macro_rules! impl_from_valkey_float {
396    ($($t:ty),* $(,)?) => {$(
397        impl FromValkeyValue for $t {
398            fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<$t> {
399                match value {
400                    ValkeyValue::Boolean(b) => Ok(<$t>::from(b)),
401                    ValkeyValue::Int(v) => Ok(v as $t),
402                    ValkeyValue::Double(v) => Ok(v as $t),
403                    ValkeyValue::SimpleString(s) => {
404                        let parsed = s.parse::<$t>().ok();
405                        parsed.ok_or_else(|| {
406                            to_glide_error(
407                                ValkeyValue::SimpleString(s),
408                                concat!("Could not convert string to ", stringify!($t), "."),
409                            )
410                        })
411                    }
412                    ValkeyValue::BulkString(bytes) => {
413                        let parsed = std::str::from_utf8(&bytes)
414                            .ok()
415                            .and_then(|s| s.parse::<$t>().ok());
416                        parsed.ok_or_else(|| {
417                            to_glide_error(
418                                ValkeyValue::BulkString(bytes),
419                                concat!("Could not convert string to ", stringify!($t), "."),
420                            )
421                        })
422                    }
423                    other => Err(to_glide_error(
424                        other,
425                        concat!("Response type not convertible to ", stringify!($t), "."),
426                    )),
427                }
428            }
429        }
430    )*};
431}
432
433impl_from_valkey_float!(f32, f64);
434
435impl FromValkeyValue for u8 {
436    fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<u8> {
437        decode_valkey_int!(u8, value)
438    }
439
440    // Specialization that makes `Vec<u8>` consume raw bulk-string bytes directly.
441    fn from_owned_byte_vec(bytes: Vec<u8>) -> ValkeyResult<Vec<u8>> {
442        Ok(bytes)
443    }
444}
445
446/// Converts a `ValkeyValue` to a boolean.
447impl FromValkeyValue for bool {
448    fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<bool> {
449        match value {
450            ValkeyValue::Nil => Ok(false),
451            ValkeyValue::Int(v) => Ok(v != 0),
452            ValkeyValue::Boolean(b) => Ok(b),
453            ValkeyValue::Okay => Ok(true),
454            ValkeyValue::SimpleString(ref s) if s == "1" => Ok(true),
455            ValkeyValue::SimpleString(ref s) if s == "0" => Ok(false),
456            ValkeyValue::BulkString(ref b) if b.as_ref() == b"1" => Ok(true),
457            ValkeyValue::BulkString(ref b) if b.as_ref() == b"0" => Ok(false),
458            other => Err(to_glide_error(other, "Response type not bool compatible.")),
459        }
460    }
461}
462
463/// Converts a `ValkeyValue` to a string.
464impl FromValkeyValue for String {
465    fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<String> {
466        match value {
467            ValkeyValue::BulkString(bytes) => String::from_utf8(bytes.to_vec())
468                .map_err(|_| GlideError::Request("Response was not valid UTF-8.".into())),
469            ValkeyValue::Okay => Ok("OK".to_string()),
470            ValkeyValue::SimpleString(s) => Ok(s),
471            ValkeyValue::VerbatimString { text, .. } => Ok(text),
472            ValkeyValue::Double(v) => Ok(v.to_string()),
473            ValkeyValue::Int(v) => Ok(v.to_string()),
474            other => Err(to_glide_error(
475                other,
476                "Response type not string compatible.",
477            )),
478        }
479    }
480}
481
482/// Converts a `ValkeyValue` into bytes.
483impl FromValkeyValue for Bytes {
484    fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Bytes> {
485        match value {
486            ValkeyValue::BulkString(bytes) => Ok(bytes),
487            ValkeyValue::SimpleString(s) => Ok(Bytes::from(s.into_bytes())),
488            ValkeyValue::VerbatimString { text, .. } => Ok(Bytes::from(text.into_bytes())),
489            other => Err(to_glide_error(other, "Response type not byte compatible.")),
490        }
491    }
492}
493
494impl FromValkeyValue for () {
495    fn from_owned_valkey_value(_value: ValkeyValue) -> ValkeyResult<()> {
496        Ok(())
497    }
498}
499
500/// Returns the `ValkeyValue` unchanged.
501impl FromValkeyValue for ValkeyValue {
502    fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<ValkeyValue> {
503        Ok(value)
504    }
505}
506
507/// Converts a `ValkeyValue` to an `Option<T>` value.
508impl<T: FromValkeyValue> FromValkeyValue for Option<T> {
509    fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Option<T>> {
510        match value {
511            ValkeyValue::Nil => Ok(None),
512            other => Ok(Some(T::from_owned_valkey_value(other)?)),
513        }
514    }
515}
516
517/// Converts a `ValkeyValue` to an `Vec<T>` value.
518impl<T: FromValkeyValue> FromValkeyValue for Vec<T> {
519    fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Vec<T>> {
520        match value {
521            ValkeyValue::BulkString(bytes) => T::from_owned_byte_vec(bytes.to_vec()),
522            ValkeyValue::Array(items) => T::from_owned_valkey_values(items),
523            ValkeyValue::Set(items) => T::from_owned_valkey_values(items),
524            ValkeyValue::Map(pairs) => {
525                // Each pair decodes as one element (used when `T` is a tuple).
526                let mut out = Vec::with_capacity(pairs.len());
527                for (k, v) in pairs {
528                    out.push(T::from_owned_valkey_value(ValkeyValue::Map(vec![(k, v)]))?);
529                }
530                Ok(out)
531            }
532            ValkeyValue::Nil => Ok(Vec::new()),
533            other => Err(to_glide_error(
534                other,
535                "Response type not vector compatible.",
536            )),
537        }
538    }
539}
540
541/// Converts a `ValkeyValue` to a `HashMap<K, V, S>` value.
542impl<K, V, S> FromValkeyValue for std::collections::HashMap<K, V, S>
543where
544    K: FromValkeyValue + std::cmp::Eq + std::hash::Hash,
545    V: FromValkeyValue,
546    S: std::hash::BuildHasher + Default,
547{
548    fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
549        into_map(value, "Response type not hashmap compatible")
550    }
551}
552
553/// Converts a `ValkeyValue` to a `HashSet<T, S>` value.
554impl<T, S> FromValkeyValue for std::collections::HashSet<T, S>
555where
556    T: FromValkeyValue + std::cmp::Eq + std::hash::Hash,
557    S: std::hash::BuildHasher + Default,
558{
559    fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
560        into_set(value, "Response type not hashset compatible")
561    }
562}
563
564/// Converts a `ValkeyValue` to a `BTreeMap<K, V>` value.
565impl<K, V> FromValkeyValue for std::collections::BTreeMap<K, V>
566where
567    K: FromValkeyValue + std::cmp::Ord,
568    V: FromValkeyValue,
569{
570    fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
571        into_map(value, "Response type not btreemap compatible")
572    }
573}
574
575/// Converts a `ValkeyValue` to a `BTreeSet<T>` value.
576impl<T> FromValkeyValue for std::collections::BTreeSet<T>
577where
578    T: FromValkeyValue + std::cmp::Ord,
579{
580    fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
581        into_set(value, "Response type not btreeset compatible")
582    }
583}
584
585/// Converts a `ValkeyValue` to a fixed-size `[T; N]` array.
586impl<T: FromValkeyValue, const N: usize> FromValkeyValue for [T; N] {
587    fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<[T; N]> {
588        let items = Vec::<T>::from_owned_valkey_value(value)?;
589        let len = items.len();
590        items.try_into().map_err(|_| {
591            GlideError::Request(format!(
592                "Array response of wrong dimension (expected {N}, got {len})"
593            ))
594        })
595    }
596}
597
598/// Converts a `ValkeyValue` to a boxed slice `Box<[T]>`.
599impl<T: FromValkeyValue> FromValkeyValue for Box<[T]> {
600    fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Box<[T]>> {
601        Ok(Vec::<T>::from_owned_valkey_value(value)?.into_boxed_slice())
602    }
603}
604
605/// Converts a `ValkeyValue` to a reference-counted slice `Arc<[T]>`.
606impl<T: FromValkeyValue> FromValkeyValue for std::sync::Arc<[T]> {
607    fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<std::sync::Arc<[T]>> {
608        Ok(Vec::<T>::from_owned_valkey_value(value)?.into())
609    }
610}
611
612/// Converts a `ValkeyValue` to a tuple.
613macro_rules! impl_from_valkey_tuple {
614    ($( ($($name:ident),+) ),+ $(,)?) => {$(
615        #[doc(hidden)]
616        impl<$($name: FromValkeyValue),*> FromValkeyValue for ($($name,)*) {
617            #[allow(non_snake_case, unused_variables)]
618            fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<($($name,)*)> {
619                match value {
620                    ValkeyValue::Array(mut items) => {
621                        let mut n = 0;
622                        $(let $name = (); n += 1;)*
623                        if items.len() != n {
624                            return Err(GlideError::Request("Array response of wrong dimension".into()));
625                        }
626                        let mut i = 0;
627                        Ok(($({ let $name = (); $name::from_owned_valkey_value(
628                            std::mem::replace(&mut items[{ i += 1; i - 1 }], ValkeyValue::Nil))? },)*))
629                    }
630                    ValkeyValue::Map(items) => {
631                        let mut n = 0;
632                        $(let $name = (); n += 1;)*
633
634                        // Only support decoding a map with one key-value pair as a 2-element tuple.
635                        if n != 2 || items.len() != 1 {
636                            return Err(GlideError::Request("Map response of wrong dimension".into()));
637                        }
638
639                        let (k, v) = items.into_iter().next().expect("exactly one entry checked above");
640                        let mut flat = [k, v];
641                        let mut i = 0;
642                        Ok(($({ let $name = (); $name::from_owned_valkey_value(
643                            std::mem::replace(&mut flat[{ i += 1; i - 1 }], ValkeyValue::Nil))? },)*))
644                    }
645                    other => Err(to_glide_error(other, "Not an Array response")),
646                }
647            }
648            #[allow(non_snake_case, unused_variables)]
649            fn from_owned_valkey_values(items: Vec<ValkeyValue>) -> ValkeyResult<Vec<($($name,)*)>> {
650                let mut n = 0;
651                $(let $name = (); n += 1;)*
652
653                if items.is_empty() {
654                    return Ok(Vec::new());
655                }
656
657                let mut items = items;
658
659                // First try array-of-arrays: each element is itself an N-array.
660                if items
661                    .iter()
662                    .all(|it| matches!(it, ValkeyValue::Array(ch) if ch.len() == n))
663                {
664                    let mut rv = Vec::with_capacity(items.len());
665                    for item in &mut items {
666                        if let ValkeyValue::Array(ch) = item {
667                            if let [$($name),*] = &mut ch[..] {
668                                rv.push(($($name::from_owned_valkey_value(
669                                    std::mem::replace($name, ValkeyValue::Nil))?,)*));
670                            }
671                        }
672                    }
673
674                    return Ok(rv);
675                }
676
677                // Otherwise treat the flat sequence as chunks of N.
678                if items.len() % n != 0 {
679                    return Err(GlideError::Request("Array response of wrong dimension".into()));
680                }
681
682                let mut rv = Vec::with_capacity(items.len() / n);
683                for chunk in items.chunks_mut(n) {
684                    if let [$($name),*] = chunk {
685                        rv.push(($($name::from_owned_valkey_value(
686                            std::mem::replace($name, ValkeyValue::Nil))?,)*));
687                    }
688                }
689
690                Ok(rv)
691            }
692        }
693    )+};
694}
695
696impl_from_valkey_tuple! {
697    (T1),
698    (T1, T2),
699    (T1, T2, T3),
700    (T1, T2, T3, T4),
701    (T1, T2, T3, T4, T5),
702    (T1, T2, T3, T4, T5, T6),
703    (T1, T2, T3, T4, T5, T6, T7),
704    (T1, T2, T3, T4, T5, T6, T7, T8),
705    (T1, T2, T3, T4, T5, T6, T7, T8, T9),
706    (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10),
707    (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11),
708    (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12),
709}
710
711#[cfg(test)]
712mod from_valkey_value_tests {
713    use super::*;
714    use std::collections::{HashMap, HashSet};
715
716    // `ValkeyValue`s constants for testing.
717    const NIL: ValkeyValue = ValkeyValue::Nil;
718    const INT: ValkeyValue = ValkeyValue::Int(7);
719    const OKAY: ValkeyValue = ValkeyValue::Okay;
720    const DOUBLE: ValkeyValue = ValkeyValue::Double(1.5);
721    const BOOLEAN: ValkeyValue = ValkeyValue::Boolean(true);
722    const BULK: ValkeyValue = ValkeyValue::BulkString(Bytes::from_static(b"hi"));
723    const BYTES_A: ValkeyValue = ValkeyValue::BulkString(Bytes::from_static(b"a"));
724    const BYTES_B: ValkeyValue = ValkeyValue::BulkString(Bytes::from_static(b"b"));
725    const BYTES_1: ValkeyValue = ValkeyValue::BulkString(Bytes::from_static(b"1"));
726    const BYTES_2: ValkeyValue = ValkeyValue::BulkString(Bytes::from_static(b"2"));
727
728    // `ValkeyValue`s functions for testing.
729    // (`SimpleString`/`VerbatimString` carry a `String`, so they can't be `const`.)
730    fn simple() -> ValkeyValue {
731        ValkeyValue::SimpleString("s".into())
732    }
733    fn verbatim() -> ValkeyValue {
734        ValkeyValue::VerbatimString {
735            format: ValkeyVerbatimFormat::Text,
736            text: "vt".into(),
737        }
738    }
739
740    fn decode<T: FromValkeyValue>(v: ValkeyValue) -> T {
741        T::from_owned_valkey_value(v).unwrap()
742    }
743
744    #[test]
745    fn from_owned_valkey_value_integer() {
746        // Boolean.
747        assert_eq!(decode::<i64>(BOOLEAN), 1);
748        assert_eq!(decode::<u8>(ValkeyValue::Boolean(false)), 0);
749        assert_eq!(decode::<usize>(BOOLEAN), 1);
750
751        // Integer.
752        assert_eq!(decode::<i64>(INT), 7);
753        assert_eq!(decode::<u64>(INT), 7);
754        assert_eq!(decode::<u8>(ValkeyValue::Int(255)), 255);
755        assert_eq!(decode::<i128>(ValkeyValue::Int(i64::MIN)), i64::MIN as i128);
756        assert!(i128::from_owned_valkey_value(ValkeyValue::BigNumber(BigInt::from(1))).is_err());
757        assert!(u128::from_owned_valkey_value(ValkeyValue::BigNumber(BigInt::from(1))).is_err());
758
759        // Out-of-range integer.
760        assert!(usize::from_owned_valkey_value(ValkeyValue::Int(-2)).is_err());
761        assert!(u8::from_owned_valkey_value(ValkeyValue::Int(300)).is_err());
762        assert!(i32::from_owned_valkey_value(ValkeyValue::Int(i64::MAX)).is_err());
763
764        // Double.
765        assert_eq!(decode::<i64>(ValkeyValue::Double(10.0)), 10);
766        assert_eq!(decode::<i8>(ValkeyValue::Double(-128.0)), i8::MIN);
767        assert_eq!(decode::<i8>(ValkeyValue::Double(127.0)), i8::MAX);
768        assert_eq!(
769            decode::<i64>(ValkeyValue::Double(-9223372036854775808.0)),
770            i64::MIN
771        );
772
773        // Non-integral double.
774        assert!(i64::from_owned_valkey_value(DOUBLE).is_err());
775        assert!(isize::from_owned_valkey_value(ValkeyValue::Double(10.6)).is_err());
776
777        // Non-finite double.
778        assert!(i64::from_owned_valkey_value(ValkeyValue::Double(f64::NAN)).is_err());
779        assert!(i64::from_owned_valkey_value(ValkeyValue::Double(f64::INFINITY)).is_err());
780
781        // Out-of-range double.
782        assert!(i64::from_owned_valkey_value(ValkeyValue::Double(1e30)).is_err());
783        assert!(u64::from_owned_valkey_value(ValkeyValue::Double(-1.0)).is_err());
784        assert!(i8::from_owned_valkey_value(ValkeyValue::Double(128.0)).is_err());
785        assert!(u64::from_owned_valkey_value(ValkeyValue::Double(18446744073709551616.0)).is_err());
786        assert!(i64::from_owned_valkey_value(ValkeyValue::Double(9223372036854775807.0)).is_err());
787
788        // Numeric string.
789        assert_eq!(
790            decode::<i64>(ValkeyValue::BulkString(Bytes::from_static(b"42"))),
791            42
792        );
793        assert_eq!(decode::<i64>(ValkeyValue::SimpleString("100".into())), 100);
794        assert_eq!(
795            decode::<u64>(ValkeyValue::BulkString(Bytes::from_static(
796                b"18446744073709551615"
797            ))),
798            u64::MAX
799        );
800        assert_eq!(
801            decode::<i64>(ValkeyValue::BulkString(Bytes::from_static(b"+5"))),
802            5
803        );
804
805        // Non-numeric.
806        assert!(i64::from_owned_valkey_value(OKAY).is_err());
807        assert!(i64::from_owned_valkey_value(BULK).is_err());
808
809        // Out-of-range or non-integral string.
810        let bulk = |s: &'static str| ValkeyValue::BulkString(Bytes::from_static(s.as_bytes()));
811        assert!(u8::from_owned_valkey_value(bulk("300")).is_err());
812        assert!(usize::from_owned_valkey_value(bulk("-1")).is_err());
813        assert!(i64::from_owned_valkey_value(bulk("1.5")).is_err());
814        assert!(i64::from_owned_valkey_value(bulk(" 5")).is_err());
815        assert!(i64::from_owned_valkey_value(bulk("1e3")).is_err());
816        assert!(u8::from_owned_valkey_value(ValkeyValue::SimpleString("256".into())).is_err());
817    }
818
819    #[test]
820    fn from_owned_valkey_value_double() {
821        let bulk = |s: &'static str| ValkeyValue::BulkString(Bytes::from_static(s.as_bytes()));
822
823        // Boolean.
824        assert_eq!(decode::<f64>(BOOLEAN), 1.0);
825        assert_eq!(decode::<f32>(ValkeyValue::Boolean(false)), 0.0);
826
827        // Integer.
828        assert_eq!(decode::<f64>(INT), 7.0);
829        assert_eq!(
830            decode::<f64>(ValkeyValue::Int((1 << 53) + 1)),
831            9007199254740992.0
832        );
833
834        // Double.
835        assert_eq!(decode::<f64>(DOUBLE), 1.5);
836        assert_eq!(decode::<f32>(DOUBLE), 1.5);
837        assert_eq!(decode::<f32>(ValkeyValue::Double(1e300)), f32::INFINITY);
838
839        // Numeric string.
840        assert_eq!(decode::<f64>(bulk("4.25")), 4.25);
841        assert_eq!(
842            decode::<f64>(ValkeyValue::SimpleString("-2.5".into())),
843            -2.5
844        );
845        assert_eq!(decode::<f64>(bulk("inf")), f64::INFINITY);
846        assert!(decode::<f64>(bulk("nan")).is_nan());
847
848        // Non-numeric.
849        assert!(f64::from_owned_valkey_value(NIL).is_err());
850        assert!(f64::from_owned_valkey_value(OKAY).is_err());
851        assert!(f64::from_owned_valkey_value(BULK).is_err());
852        assert!(f64::from_owned_valkey_value(simple()).is_err());
853    }
854
855    #[test]
856    fn from_owned_valkey_value_bool() {
857        let bulk_string_bool = ValkeyValue::BulkString(Bytes::from_static(b"0"));
858
859        assert!(decode::<bool>(INT)); // any non-zero int is true
860        assert!(!decode::<bool>(ValkeyValue::Int(0)));
861        assert!(decode::<bool>(BOOLEAN));
862        assert!(decode::<bool>(OKAY));
863        assert!(!decode::<bool>(NIL));
864        assert!(decode::<bool>(ValkeyValue::SimpleString("1".into())));
865        assert!(!decode::<bool>(bulk_string_bool));
866    }
867
868    #[test]
869    fn from_owned_valkey_value_string() {
870        assert_eq!(decode::<String>(BULK), "hi");
871        assert_eq!(decode::<String>(simple()), "s");
872        assert_eq!(decode::<String>(OKAY), "OK");
873        assert_eq!(decode::<String>(INT), "7");
874        assert_eq!(decode::<String>(DOUBLE), "1.5");
875        assert_eq!(decode::<String>(verbatim()), "vt");
876    }
877
878    #[test]
879    fn from_owned_valkey_value_bytes() {
880        assert_eq!(decode::<Bytes>(BULK), Bytes::from_static(b"hi"));
881        assert_eq!(decode::<Bytes>(simple()), Bytes::from_static(b"s"));
882        assert_eq!(decode::<Bytes>(verbatim()), Bytes::from_static(b"vt"));
883    }
884
885    #[test]
886    fn from_owned_valkey_value_u8() {
887        assert_eq!(decode::<u8>(INT), 7);
888
889        // `Vec<u8>` from a bulk string consumes the raw bytes directly.
890        assert_eq!(decode::<Vec<u8>>(BULK), b"hi".to_vec());
891        assert_eq!(
892            decode::<Vec<u8>>(ValkeyValue::BulkString(Bytes::from_static(b""))),
893            Vec::<u8>::new()
894        );
895
896        // `Vec<u8>` from an array decodes element-wise.
897        assert_eq!(
898            decode::<Vec<u8>>(ValkeyValue::Array(vec![INT, INT])),
899            vec![7u8, 7u8]
900        );
901    }
902
903    #[test]
904    fn from_owned_valkey_value_vec() {
905        let v = ValkeyValue::Array(vec![BYTES_A, BYTES_B]);
906        assert_eq!(
907            decode::<Vec<String>>(v),
908            vec!["a".to_string(), "b".to_string()]
909        );
910
911        assert_eq!(decode::<Vec<String>>(BULK), vec!["hi".to_string()]);
912        assert_eq!(decode::<Vec<String>>(NIL), Vec::<String>::new());
913    }
914
915    #[test]
916    fn from_owned_valkey_value_array() {
917        let v = ValkeyValue::Array(vec![BYTES_A, BYTES_B]);
918        let a: [String; 2] = decode(v.clone());
919        assert_eq!(a, ["a".to_string(), "b".to_string()]);
920
921        // A length mismatch is a decode error.
922        assert!(<[String; 3]>::from_owned_valkey_value(v).is_err());
923    }
924
925    #[test]
926    fn from_owned_valkey_value_boxed_slice() {
927        let v = ValkeyValue::Array(vec![BYTES_A, BYTES_B]);
928        let b: Box<[String]> = decode(v);
929        assert_eq!(&b[..], ["a".to_string(), "b".to_string()]);
930    }
931
932    #[test]
933    fn from_owned_valkey_value_arc_slice() {
934        let v = ValkeyValue::Array(vec![BYTES_A, BYTES_B]);
935        let a: std::sync::Arc<[String]> = decode(v);
936        assert_eq!(&a[..], ["a".to_string(), "b".to_string()]);
937    }
938
939    #[test]
940    fn from_owned_valkey_value_option() {
941        let none: Option<String> = decode(NIL);
942        assert_eq!(none, None);
943
944        let some: Option<String> = decode(BULK);
945        assert_eq!(some.as_deref(), Some("hi"));
946    }
947
948    #[test]
949    fn from_owned_valkey_value_unit() {
950        let _: () = decode(OKAY);
951        let _: () = decode(NIL);
952    }
953
954    #[test]
955    fn from_owned_valkey_value_identity() {
956        assert_eq!(decode::<ValkeyValue>(INT), INT);
957        assert_eq!(decode::<ValkeyValue>(NIL), NIL);
958        assert_eq!(decode::<ValkeyValue>(simple()), simple());
959    }
960
961    #[test]
962    fn from_owned_valkey_value_hashmap() {
963        let from_map = ValkeyValue::Map(vec![(BULK, BULK)]);
964        let m: HashMap<String, String> = decode(from_map);
965        assert_eq!(m.get("hi").map(String::as_str), Some("hi"));
966
967        let from_flat = ValkeyValue::Array(vec![BYTES_A, BYTES_1, BYTES_B, BYTES_2]);
968        let m: HashMap<String, i64> = decode(from_flat);
969        assert_eq!(m.get("a"), Some(&1));
970        assert_eq!(m.get("b"), Some(&2));
971
972        let empty: HashMap<String, String> = decode(NIL);
973        assert!(empty.is_empty());
974    }
975
976    #[test]
977    fn from_owned_valkey_value_btreemap() {
978        use std::collections::BTreeMap;
979
980        let from_map = ValkeyValue::Map(vec![(BYTES_A, BYTES_1), (BYTES_B, BYTES_2)]);
981        let m: BTreeMap<String, i64> = decode(from_map);
982        assert_eq!(m.get("a"), Some(&1));
983        assert_eq!(m.get("b"), Some(&2));
984
985        let from_array = ValkeyValue::Array(vec![BYTES_A, BYTES_1, BYTES_B, BYTES_2]);
986        let m: BTreeMap<String, i64> = decode(from_array);
987        assert_eq!(m.get("a"), Some(&1));
988        assert_eq!(m.get("b"), Some(&2));
989
990        let empty: BTreeMap<String, String> = decode(NIL);
991        assert!(empty.is_empty());
992    }
993
994    #[test]
995    fn from_owned_valkey_value_hashset() {
996        let v = ValkeyValue::Set(vec![BYTES_A, BYTES_B]);
997        let s: HashSet<String> = decode(v);
998        assert_eq!(s, HashSet::from(["a".to_string(), "b".to_string()]));
999    }
1000
1001    #[test]
1002    fn from_owned_valkey_value_btreeset() {
1003        use std::collections::BTreeSet;
1004
1005        let v = ValkeyValue::Set(vec![BYTES_A, BYTES_B]);
1006        let s: BTreeSet<String> = decode(v);
1007        assert_eq!(s, BTreeSet::from(["a".to_string(), "b".to_string()]));
1008    }
1009
1010    #[test]
1011    fn from_owned_valkey_value_tuple() {
1012        let t: (String, i64) = decode(ValkeyValue::Array(vec![BULK, INT]));
1013        assert_eq!(t, ("hi".to_string(), 7));
1014
1015        let t: (String, i64, f64) = decode(ValkeyValue::Array(vec![BULK, INT, DOUBLE]));
1016        assert_eq!(t, ("hi".to_string(), 7, 1.5));
1017
1018        // Conversion from array with unexpected number of entries fails.
1019        assert!(<(String, i64)>::from_owned_valkey_value(ValkeyValue::Array(vec![INT])).is_err());
1020
1021        let t: (String, i64) = decode(ValkeyValue::Map(vec![(BULK, INT)]));
1022        assert_eq!(t, ("hi".to_string(), 7));
1023
1024        // Conversion from map with unexpected number of entries fails.
1025        let map_2 = ValkeyValue::Map(vec![(BULK, INT), (BYTES_A, INT)]);
1026        assert!(<(String, i64)>::from_owned_valkey_value(map_2).is_err());
1027    }
1028
1029    #[test]
1030    fn from_owned_valkey_value_vec_of_pairs() {
1031        // Nested array
1032        let nested = ValkeyValue::Array(vec![
1033            ValkeyValue::Array(vec![BULK, DOUBLE]),
1034            ValkeyValue::Array(vec![BULK, DOUBLE]),
1035        ]);
1036        let pairs: Vec<(String, f64)> = decode(nested);
1037        assert_eq!(
1038            pairs,
1039            vec![("hi".to_string(), 1.5), ("hi".to_string(), 1.5)]
1040        );
1041
1042        // Flat array
1043        let flat = ValkeyValue::Array(vec![BULK, DOUBLE, BULK, DOUBLE]);
1044        let pairs: Vec<(String, f64)> = decode(flat);
1045        assert_eq!(
1046            pairs,
1047            vec![("hi".to_string(), 1.5), ("hi".to_string(), 1.5)]
1048        );
1049
1050        // Conversion from flat array with odd number of entries fails.
1051        assert!(
1052            <Vec<(String, f64)>>::from_owned_valkey_value(ValkeyValue::Array(vec![
1053                BULK, DOUBLE, BULK
1054            ]))
1055            .is_err()
1056        );
1057    }
1058
1059    #[test]
1060    fn from_owned_valkey_value_server_error() {
1061        let server_err = ValkeyServerError {
1062            code: "WRONGTYPE".into(),
1063            detail: Some("nope".into()),
1064        };
1065        let value = ValkeyValue::ServerError(server_err);
1066        let request_err = i64::from_owned_valkey_value(value).unwrap_err();
1067
1068        assert_eq!(request_err.class_name(), "RequestError");
1069        assert!(request_err.message().contains("WRONGTYPE"));
1070    }
1071}