redis_oxide/commands/
set.rs

1//! Command builders for Redis Set operations
2
3use crate::commands::Command;
4use crate::core::{
5    error::{RedisError, RedisResult},
6    value::RespValue,
7};
8use crate::pipeline::PipelineCommand;
9use std::collections::HashSet;
10
11/// Represents the `SADD` command.
12#[derive(Debug, Clone)]
13pub struct SAddCommand {
14    key: String,
15    members: Vec<String>,
16}
17
18impl SAddCommand {
19    /// Create a new `SADD` command.
20    #[must_use]
21    pub fn new(key: impl Into<String>, members: Vec<String>) -> Self {
22        Self {
23            key: key.into(),
24            members,
25        }
26    }
27}
28
29impl Command for SAddCommand {
30    type Output = i64;
31
32    fn command_name(&self) -> &str {
33        "SADD"
34    }
35
36    fn args(&self) -> Vec<RespValue> {
37        let mut args = vec![RespValue::from(self.key.clone())];
38        for member in &self.members {
39            args.push(RespValue::from(member.clone()));
40        }
41        args
42    }
43
44    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output> {
45        response.as_int()
46    }
47
48    fn keys(&self) -> Vec<&[u8]> {
49        vec![self.key.as_bytes()]
50    }
51}
52
53impl PipelineCommand for SAddCommand {
54    fn name(&self) -> &str {
55        self.command_name()
56    }
57
58    fn args(&self) -> Vec<RespValue> {
59        <Self as Command>::args(self)
60    }
61
62    fn key(&self) -> Option<String> {
63        Some(self.key.clone())
64    }
65}
66
67/// Represents the `SREM` command.
68#[derive(Debug, Clone)]
69pub struct SRemCommand {
70    key: String,
71    members: Vec<String>,
72}
73
74impl SRemCommand {
75    /// Create a new `SREM` command.
76    #[must_use]
77    pub fn new(key: impl Into<String>, members: Vec<String>) -> Self {
78        Self {
79            key: key.into(),
80            members,
81        }
82    }
83}
84
85impl Command for SRemCommand {
86    type Output = i64;
87
88    fn command_name(&self) -> &str {
89        "SREM"
90    }
91
92    fn args(&self) -> Vec<RespValue> {
93        let mut args = vec![RespValue::from(self.key.clone())];
94        for member in &self.members {
95            args.push(RespValue::from(member.clone()));
96        }
97        args
98    }
99
100    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output> {
101        response.as_int()
102    }
103
104    fn keys(&self) -> Vec<&[u8]> {
105        vec![self.key.as_bytes()]
106    }
107}
108
109impl PipelineCommand for SRemCommand {
110    fn name(&self) -> &str {
111        self.command_name()
112    }
113
114    fn args(&self) -> Vec<RespValue> {
115        <Self as Command>::args(self)
116    }
117
118    fn key(&self) -> Option<String> {
119        Some(self.key.clone())
120    }
121}
122
123/// Represents the `SMEMBERS` command.
124#[derive(Debug, Clone)]
125pub struct SMembersCommand {
126    key: String,
127}
128
129impl SMembersCommand {
130    /// Create a new `SMEMBERS` command.
131    #[must_use]
132    pub fn new(key: impl Into<String>) -> Self {
133        Self { key: key.into() }
134    }
135}
136
137impl Command for SMembersCommand {
138    type Output = HashSet<String>;
139
140    fn command_name(&self) -> &str {
141        "SMEMBERS"
142    }
143
144    fn args(&self) -> Vec<RespValue> {
145        vec![RespValue::from(self.key.clone())]
146    }
147
148    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output> {
149        match response {
150            RespValue::Array(items) => {
151                let mut result = HashSet::new();
152                for item in items {
153                    match item {
154                        RespValue::BulkString(b) => {
155                            let s = String::from_utf8(b.to_vec())
156                                .map_err(|e| RedisError::Type(format!("Invalid UTF-8: {e}")))?;
157                            result.insert(s);
158                        }
159                        RespValue::Null => {
160                            // Skip null values
161                        }
162                        _ => {
163                            return Err(RedisError::Type(format!(
164                                "Unexpected item type in SMEMBERS response: {:?}",
165                                item
166                            )))
167                        }
168                    }
169                }
170                Ok(result)
171            }
172            _ => Err(RedisError::Type(format!(
173                "Unexpected response type for SMEMBERS: {:?}",
174                response
175            ))),
176        }
177    }
178
179    fn keys(&self) -> Vec<&[u8]> {
180        vec![self.key.as_bytes()]
181    }
182}
183
184impl PipelineCommand for SMembersCommand {
185    fn name(&self) -> &str {
186        self.command_name()
187    }
188
189    fn args(&self) -> Vec<RespValue> {
190        <Self as Command>::args(self)
191    }
192
193    fn key(&self) -> Option<String> {
194        Some(self.key.clone())
195    }
196}
197
198/// Represents the `SISMEMBER` command.
199#[derive(Debug, Clone)]
200pub struct SIsMemberCommand {
201    key: String,
202    member: String,
203}
204
205impl SIsMemberCommand {
206    /// Create a new `SISMEMBER` command.
207    #[must_use]
208    pub fn new(key: impl Into<String>, member: impl Into<String>) -> Self {
209        Self {
210            key: key.into(),
211            member: member.into(),
212        }
213    }
214}
215
216impl Command for SIsMemberCommand {
217    type Output = bool;
218
219    fn command_name(&self) -> &str {
220        "SISMEMBER"
221    }
222
223    fn args(&self) -> Vec<RespValue> {
224        vec![
225            RespValue::from(self.key.clone()),
226            RespValue::from(self.member.clone()),
227        ]
228    }
229
230    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output> {
231        match response {
232            RespValue::Integer(1) => Ok(true),
233            RespValue::Integer(0) => Ok(false),
234            _ => Err(RedisError::Type(format!(
235                "Unexpected response type for SISMEMBER: {:?}",
236                response
237            ))),
238        }
239    }
240
241    fn keys(&self) -> Vec<&[u8]> {
242        vec![self.key.as_bytes()]
243    }
244}
245
246impl PipelineCommand for SIsMemberCommand {
247    fn name(&self) -> &str {
248        self.command_name()
249    }
250
251    fn args(&self) -> Vec<RespValue> {
252        <Self as Command>::args(self)
253    }
254
255    fn key(&self) -> Option<String> {
256        Some(self.key.clone())
257    }
258}
259
260/// Represents the `SCARD` command.
261#[derive(Debug, Clone)]
262pub struct SCardCommand {
263    key: String,
264}
265
266impl SCardCommand {
267    /// Create a new `SCARD` command.
268    #[must_use]
269    pub fn new(key: impl Into<String>) -> Self {
270        Self { key: key.into() }
271    }
272}
273
274impl Command for SCardCommand {
275    type Output = i64;
276
277    fn command_name(&self) -> &str {
278        "SCARD"
279    }
280
281    fn args(&self) -> Vec<RespValue> {
282        vec![RespValue::from(self.key.clone())]
283    }
284
285    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output> {
286        response.as_int()
287    }
288
289    fn keys(&self) -> Vec<&[u8]> {
290        vec![self.key.as_bytes()]
291    }
292}
293
294impl PipelineCommand for SCardCommand {
295    fn name(&self) -> &str {
296        self.command_name()
297    }
298
299    fn args(&self) -> Vec<RespValue> {
300        <Self as Command>::args(self)
301    }
302
303    fn key(&self) -> Option<String> {
304        Some(self.key.clone())
305    }
306}
307
308/// Represents the `SPOP` command.
309#[derive(Debug, Clone)]
310pub struct SPopCommand {
311    key: String,
312}
313
314impl SPopCommand {
315    /// Create a new `SPOP` command.
316    #[must_use]
317    pub fn new(key: impl Into<String>) -> Self {
318        Self { key: key.into() }
319    }
320}
321
322impl Command for SPopCommand {
323    type Output = Option<String>;
324
325    fn command_name(&self) -> &str {
326        "SPOP"
327    }
328
329    fn args(&self) -> Vec<RespValue> {
330        vec![RespValue::from(self.key.clone())]
331    }
332
333    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output> {
334        match response {
335            RespValue::BulkString(b) => String::from_utf8(b.to_vec())
336                .map(Some)
337                .map_err(|e| RedisError::Type(format!("Invalid UTF-8: {e}"))),
338            RespValue::Null => Ok(None),
339            _ => Err(RedisError::Type(format!(
340                "Unexpected response type for SPOP: {:?}",
341                response
342            ))),
343        }
344    }
345
346    fn keys(&self) -> Vec<&[u8]> {
347        vec![self.key.as_bytes()]
348    }
349}
350
351impl PipelineCommand for SPopCommand {
352    fn name(&self) -> &str {
353        self.command_name()
354    }
355
356    fn args(&self) -> Vec<RespValue> {
357        <Self as Command>::args(self)
358    }
359
360    fn key(&self) -> Option<String> {
361        Some(self.key.clone())
362    }
363}
364
365/// Represents the `SRANDMEMBER` command.
366#[derive(Debug, Clone)]
367pub struct SRandMemberCommand {
368    key: String,
369}
370
371impl SRandMemberCommand {
372    /// Create a new `SRANDMEMBER` command.
373    #[must_use]
374    pub fn new(key: impl Into<String>) -> Self {
375        Self { key: key.into() }
376    }
377}
378
379impl Command for SRandMemberCommand {
380    type Output = Option<String>;
381
382    fn command_name(&self) -> &str {
383        "SRANDMEMBER"
384    }
385
386    fn args(&self) -> Vec<RespValue> {
387        vec![RespValue::from(self.key.clone())]
388    }
389
390    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output> {
391        match response {
392            RespValue::BulkString(b) => String::from_utf8(b.to_vec())
393                .map(Some)
394                .map_err(|e| RedisError::Type(format!("Invalid UTF-8: {e}"))),
395            RespValue::Null => Ok(None),
396            _ => Err(RedisError::Type(format!(
397                "Unexpected response type for SRANDMEMBER: {:?}",
398                response
399            ))),
400        }
401    }
402
403    fn keys(&self) -> Vec<&[u8]> {
404        vec![self.key.as_bytes()]
405    }
406}
407
408impl PipelineCommand for SRandMemberCommand {
409    fn name(&self) -> &str {
410        self.command_name()
411    }
412
413    fn args(&self) -> Vec<RespValue> {
414        <Self as Command>::args(self)
415    }
416
417    fn key(&self) -> Option<String> {
418        Some(self.key.clone())
419    }
420}