Skip to main content

glide/
types.rs

1// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
2//! Typed reply types returned by [`crate::AsyncTypedCommands`] and [`crate::TypedCommands`].
3
4use crate::ValkeyResult;
5use crate::value::FromValkeyValue;
6use crate::value::ValkeyValue;
7use crate::value::to_glide_error;
8
9// --- ValueType ----------------------------------------------------------------------------------
10
11/// The type of the value stored at a key.
12///
13/// Mirrors redis-rs's `ValueType`.
14#[derive(Debug, Clone, PartialEq)]
15#[non_exhaustive]
16pub enum ValueType {
17    /// The key does not exist (`none`).
18    None,
19    /// A string (`string`).
20    String,
21    /// A list (`list`).
22    List,
23    /// A set (`set`).
24    Set,
25    /// A sorted set (`zset`).
26    ZSet,
27    /// A hash (`hash`).
28    Hash,
29    /// A stream (`stream`).
30    Stream,
31    /// Any other type name.
32    Unknown(String),
33}
34
35impl<T: AsRef<str>> From<T> for ValueType {
36    fn from(s: T) -> Self {
37        match s.as_ref() {
38            "none" => Self::None,
39            "string" => Self::String,
40            "list" => Self::List,
41            "set" => Self::Set,
42            "zset" => Self::ZSet,
43            "hash" => Self::Hash,
44            "stream" => Self::Stream,
45            s => Self::Unknown(s.to_string()),
46        }
47    }
48}
49
50impl From<ValueType> for String {
51    fn from(v: ValueType) -> Self {
52        match v {
53            ValueType::None => "none".to_string(),
54            ValueType::String => "string".to_string(),
55            ValueType::List => "list".to_string(),
56            ValueType::Set => "set".to_string(),
57            ValueType::ZSet => "zset".to_string(),
58            ValueType::Hash => "hash".to_string(),
59            ValueType::Stream => "stream".to_string(),
60            ValueType::Unknown(s) => s,
61        }
62    }
63}
64
65impl FromValkeyValue for ValueType {
66    fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
67        match value {
68            ValkeyValue::SimpleString(s) => Ok(s.into()),
69            other => Err(to_glide_error(
70                other,
71                "Value type should be a simple string.",
72            )),
73        }
74    }
75}
76
77#[cfg(test)]
78mod value_type_tests {
79    use super::*;
80
81    #[test]
82    fn value_type_round_trips_known_names() {
83        for name in ["none", "string", "list", "set", "zset", "hash", "stream"] {
84            let value_type = ValueType::from(name);
85            assert!(!matches!(value_type, ValueType::Unknown(_)), "{name}");
86            assert_eq!(String::from(value_type), name);
87        }
88    }
89
90    #[test]
91    fn value_type_keeps_unknown_names() {
92        assert_eq!(
93            ValueType::from("vectorset"),
94            ValueType::Unknown("vectorset".to_string())
95        );
96        assert_eq!(String::from(ValueType::from("vectorset")), "vectorset");
97    }
98
99    #[test]
100    fn value_type_decodes_simple_string_only() {
101        let decoded =
102            ValueType::from_owned_valkey_value(ValkeyValue::SimpleString("zset".into())).unwrap();
103        assert_eq!(decoded, ValueType::ZSet);
104        assert!(
105            ValueType::from_owned_valkey_value(ValkeyValue::BulkString("zset".into())).is_err()
106        );
107    }
108}
109
110// --- IntegerReplyOrNoOp -------------------------------------------------------------------------
111
112/// A reply that is either a non-negative integer,
113/// or a negative sentinel indicating a no-op.
114///
115/// Mirrors redis-rs's `IntegerReplyOrNoOp`.
116#[derive(Debug, PartialEq, Clone)]
117#[non_exhaustive]
118pub enum IntegerReplyOrNoOp {
119    /// A non-negative integer reply.
120    IntegerReply(usize),
121    /// The key or field exists but the operation does not apply to it (`-1`).
122    ExistsButNotRelevant,
123    /// The key or field does not exist (`-2`).
124    NotExists,
125}
126
127impl IntegerReplyOrNoOp {
128    /// Returns the raw integer reply.
129    pub fn raw(&self) -> isize {
130        match self {
131            Self::IntegerReply(s) => *s as isize,
132            Self::ExistsButNotRelevant => -1,
133            Self::NotExists => -2,
134        }
135    }
136
137    /// Returns whether this reply equals the signed integer `other`, without truncating or wrapping.
138    fn eq_signed(&self, other: i64) -> bool {
139        match self {
140            Self::IntegerReply(s) => usize::try_from(other).is_ok_and(|o| o == *s),
141            Self::ExistsButNotRelevant => other == -1,
142            Self::NotExists => other == -2,
143        }
144    }
145}
146
147impl FromValkeyValue for IntegerReplyOrNoOp {
148    fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
149        match value {
150            ValkeyValue::Int(-2) => Ok(Self::NotExists),
151            ValkeyValue::Int(-1) => Ok(Self::ExistsButNotRelevant),
152            ValkeyValue::Int(s) => usize::try_from(s).map(Self::IntegerReply).map_err(|_| {
153                to_glide_error(
154                    ValkeyValue::Int(s),
155                    "Value should be a non-negative integer, -1, or -2.",
156                )
157            }),
158            other => Err(to_glide_error(other, "Value should be an integer.")),
159        }
160    }
161}
162
163impl PartialEq<isize> for IntegerReplyOrNoOp {
164    fn eq(&self, other: &isize) -> bool {
165        i64::try_from(*other).is_ok_and(|o| self.eq_signed(o))
166    }
167}
168
169impl PartialEq<usize> for IntegerReplyOrNoOp {
170    fn eq(&self, other: &usize) -> bool {
171        matches!(self, Self::IntegerReply(s) if s == other)
172    }
173}
174
175impl PartialEq<i32> for IntegerReplyOrNoOp {
176    fn eq(&self, other: &i32) -> bool {
177        self.eq_signed(i64::from(*other))
178    }
179}
180
181impl PartialEq<u32> for IntegerReplyOrNoOp {
182    fn eq(&self, other: &u32) -> bool {
183        matches!(self, Self::IntegerReply(s) if usize::try_from(*other).is_ok_and(|o| o == *s))
184    }
185}
186
187#[cfg(test)]
188mod integer_reply_or_noop_tests {
189    use super::*;
190
191    #[test]
192    fn integer_reply_or_no_op_decodes_sentinels() {
193        assert_eq!(decode(-2), IntegerReplyOrNoOp::NotExists);
194        assert_eq!(decode(-1), IntegerReplyOrNoOp::ExistsButNotRelevant);
195        assert_eq!(decode(0), IntegerReplyOrNoOp::IntegerReply(0));
196        assert_eq!(decode(42), IntegerReplyOrNoOp::IntegerReply(42));
197        assert!(IntegerReplyOrNoOp::from_owned_valkey_value(ValkeyValue::Nil).is_err());
198    }
199
200    #[test]
201    fn integer_reply_or_no_op_rejects_other_negatives() {
202        for i in [-3, i64::MIN] {
203            assert!(IntegerReplyOrNoOp::from_owned_valkey_value(ValkeyValue::Int(i)).is_err());
204        }
205    }
206
207    #[test]
208    fn integer_reply_or_no_op_raw() {
209        assert_eq!(IntegerReplyOrNoOp::NotExists.raw(), -2);
210        assert_eq!(IntegerReplyOrNoOp::ExistsButNotRelevant.raw(), -1);
211        assert_eq!(IntegerReplyOrNoOp::IntegerReply(7).raw(), 7);
212    }
213
214    #[test]
215    fn integer_reply_or_no_op_comparisons() {
216        // Comparisons against each integer type.
217        assert_eq!(IntegerReplyOrNoOp::NotExists, -2isize);
218        assert_eq!(IntegerReplyOrNoOp::ExistsButNotRelevant, -1i32);
219        assert_eq!(IntegerReplyOrNoOp::IntegerReply(7), 7usize);
220        assert_eq!(IntegerReplyOrNoOp::IntegerReply(7), 7u32);
221        assert_ne!(IntegerReplyOrNoOp::NotExists, 2usize);
222
223        // Comparisons do not truncate to 32 bits.
224        let large = IntegerReplyOrNoOp::IntegerReply((1usize << 32) + 7);
225        assert_ne!(large, 7u32);
226        assert_ne!(large, 7i32);
227        assert_eq!(large, (1usize << 32) + 7);
228        assert_eq!(large, (1isize << 32) + 7);
229
230        // Comparisons do not wrap large replies onto the sentinels.
231        let max = IntegerReplyOrNoOp::IntegerReply(usize::MAX);
232        assert_ne!(max, -1isize);
233        assert_ne!(max, -1i32);
234    }
235
236    fn decode(i: i64) -> IntegerReplyOrNoOp {
237        IntegerReplyOrNoOp::from_owned_valkey_value(ValkeyValue::Int(i)).unwrap()
238    }
239}