redis_oxide/core/
value.rs

1//! RESP (`REdis` Serialization Protocol) value types
2
3use crate::core::error::{RedisError, RedisResult};
4use bytes::Bytes;
5
6/// RESP protocol value
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum RespValue {
9    /// Simple string: +OK\r\n
10    SimpleString(String),
11    /// Error: -ERR message\r\n
12    Error(String),
13    /// Integer: :1000\r\n
14    Integer(i64),
15    /// Bulk string: $6\r\nfoobar\r\n
16    BulkString(Bytes),
17    /// Null bulk string: $-1\r\n
18    Null,
19    /// Array: *2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n
20    Array(Vec<RespValue>),
21}
22
23impl RespValue {
24    /// Convert to a string if possible
25    ///
26    /// # Errors
27    ///
28    /// Returns an error if the value cannot be converted to a string.
29    pub fn as_string(&self) -> RedisResult<String> {
30        match self {
31            Self::SimpleString(s) => Ok(s.clone()),
32            Self::BulkString(b) => String::from_utf8(b.to_vec())
33                .map_err(|e| RedisError::Type(format!("Invalid UTF-8: {e}"))),
34            Self::Null => Err(RedisError::Type("Value is null".to_string())),
35            _ => Err(RedisError::Type(format!(
36                "Cannot convert {self:?} to string"
37            ))),
38        }
39    }
40
41    /// Convert to an integer if possible
42    ///
43    /// # Errors
44    ///
45    /// Returns an error if the value cannot be converted to an integer.
46    pub fn as_int(&self) -> RedisResult<i64> {
47        match self {
48            Self::Integer(i) => Ok(*i),
49            Self::BulkString(b) => {
50                let s = String::from_utf8(b.to_vec())
51                    .map_err(|e| RedisError::Type(format!("Invalid UTF-8: {e}")))?;
52                s.parse::<i64>()
53                    .map_err(|e| RedisError::Type(format!("Cannot parse integer: {e}")))
54            }
55            _ => Err(RedisError::Type(format!(
56                "Cannot convert {self:?} to integer"
57            ))),
58        }
59    }
60
61    /// Convert to bytes if possible
62    ///
63    /// # Errors
64    ///
65    /// Returns an error if the value cannot be converted to bytes.
66    pub fn as_bytes(&self) -> RedisResult<Bytes> {
67        match self {
68            Self::BulkString(b) => Ok(b.clone()),
69            Self::SimpleString(s) => Ok(Bytes::from(s.as_bytes().to_vec())),
70            Self::Null => Err(RedisError::Type("Value is null".to_string())),
71            _ => Err(RedisError::Type(format!(
72                "Cannot convert {self:?} to bytes"
73            ))),
74        }
75    }
76
77    /// Convert to an array if possible
78    ///
79    /// # Errors
80    ///
81    /// Returns an error if the value cannot be converted to an array.
82    pub fn as_array(&self) -> RedisResult<Vec<Self>> {
83        match self {
84            Self::Array(arr) => Ok(arr.clone()),
85            _ => Err(RedisError::Type(format!(
86                "Cannot convert {self:?} to array"
87            ))),
88        }
89    }
90
91    /// Check if this is a null value
92    #[must_use]
93    pub const fn is_null(&self) -> bool {
94        matches!(self, Self::Null)
95    }
96
97    /// Check if this is an error
98    #[must_use]
99    pub const fn is_error(&self) -> bool {
100        matches!(self, Self::Error(_))
101    }
102
103    /// Convert to a boolean if possible
104    ///
105    /// # Errors
106    ///
107    /// Returns an error if the value cannot be converted to a boolean.
108    pub fn as_bool(&self) -> RedisResult<bool> {
109        match self {
110            Self::Integer(1) => Ok(true),
111            Self::Integer(0) => Ok(false),
112            Self::SimpleString(s) if s == "OK" => Ok(true),
113            Self::BulkString(b) => {
114                let s = String::from_utf8(b.to_vec())
115                    .map_err(|e| RedisError::Type(format!("Invalid UTF-8: {e}")))?;
116                Ok(s == "1" || s.to_lowercase() == "true")
117            }
118            _ => Err(RedisError::Type(format!(
119                "Cannot convert {self:?} to boolean"
120            ))),
121        }
122    }
123
124    /// Extract error message if this is an error
125    #[must_use]
126    pub fn into_error(self) -> Option<String> {
127        match self {
128            Self::Error(msg) => Some(msg),
129            _ => None,
130        }
131    }
132}
133
134impl From<String> for RespValue {
135    fn from(s: String) -> Self {
136        Self::BulkString(Bytes::from(s.into_bytes()))
137    }
138}
139impl From<&str> for RespValue {
140    fn from(s: &str) -> Self {
141        Self::BulkString(Bytes::from(s.as_bytes().to_vec()))
142    }
143}
144impl From<i64> for RespValue {
145    fn from(i: i64) -> Self {
146        Self::Integer(i)
147    }
148}
149impl From<Vec<u8>> for RespValue {
150    fn from(b: Vec<u8>) -> Self {
151        Self::BulkString(Bytes::from(b))
152    }
153}
154impl From<Bytes> for RespValue {
155    fn from(b: Bytes) -> Self {
156        Self::BulkString(b)
157    }
158}
159
160impl TryFrom<RespValue> for String {
161    type Error = RedisError;
162
163    fn try_from(value: RespValue) -> Result<Self, Self::Error> {
164        value.as_string()
165    }
166}
167
168impl TryFrom<RespValue> for i64 {
169    type Error = RedisError;
170
171    fn try_from(value: RespValue) -> Result<Self, Self::Error> {
172        value.as_int()
173    }
174}
175
176impl TryFrom<RespValue> for bool {
177    type Error = RedisError;
178
179    fn try_from(value: RespValue) -> Result<Self, Self::Error> {
180        match value {
181            RespValue::Integer(1) => Ok(true),
182            RespValue::Integer(0) => Ok(false),
183            RespValue::SimpleString(s) if s == "OK" => Ok(true),
184            _ => Err(RedisError::Type(format!(
185                "Cannot convert {:?} to bool",
186                value
187            ))),
188        }
189    }
190}
191
192impl TryFrom<RespValue> for Option<String> {
193    type Error = RedisError;
194
195    fn try_from(value: RespValue) -> Result<Self, Self::Error> {
196        match value {
197            RespValue::BulkString(b) => String::from_utf8(b.to_vec())
198                .map(Some)
199                .map_err(|e| RedisError::Type(format!("Invalid UTF-8: {e}"))),
200            RespValue::SimpleString(s) => Ok(Some(s)),
201            RespValue::Null => Ok(None),
202            _ => Err(RedisError::Type(format!(
203                "Cannot convert {:?} to Option<String>",
204                value
205            ))),
206        }
207    }
208}
209
210impl TryFrom<RespValue> for Vec<String> {
211    type Error = RedisError;
212
213    fn try_from(value: RespValue) -> Result<Self, Self::Error> {
214        match value {
215            RespValue::Array(items) => {
216                let mut result = Self::new();
217                for item in items {
218                    match item {
219                        RespValue::BulkString(b) => {
220                            let s = String::from_utf8(b.to_vec())
221                                .map_err(|e| RedisError::Type(format!("Invalid UTF-8: {e}")))?;
222                            result.push(s);
223                        }
224                        RespValue::SimpleString(s) => result.push(s),
225                        RespValue::Null => {} // Skip null values
226                        _ => {
227                            return Err(RedisError::Type(format!(
228                                "Cannot convert array item {:?} to string",
229                                item
230                            )))
231                        }
232                    }
233                }
234                Ok(result)
235            }
236            _ => Err(RedisError::Type(format!(
237                "Cannot convert {:?} to Vec<String>",
238                value
239            ))),
240        }
241    }
242}
243
244impl TryFrom<RespValue> for Vec<i64> {
245    type Error = RedisError;
246
247    fn try_from(value: RespValue) -> Result<Self, Self::Error> {
248        match value {
249            RespValue::Array(items) => {
250                let mut result = Self::new();
251                for item in items {
252                    let i = item.as_int()?;
253                    result.push(i);
254                }
255                Ok(result)
256            }
257            _ => Err(RedisError::Type(format!(
258                "Cannot convert {:?} to Vec<i64>",
259                value
260            ))),
261        }
262    }
263}