embedded_redis/commands/
set.rs

1//! Abstraction of SET command.
2//!
3//! For general information about this command, see the [Redis documentation](<https://redis.io/commands/set/>).
4//!
5//! # Basic usage
6//! ```
7//!# use core::str::FromStr;
8//!# use core::net::SocketAddr;
9//!# use std_embedded_nal::Stack;
10//!# use std_embedded_time::StandardClock;
11//!# use embedded_redis::commands::set::SetCommand;
12//!# use embedded_redis::network::ConnectionHandler;
13//!#
14//! let mut stack = Stack::default();
15//! let clock = StandardClock::default();
16//!
17//! let mut connection_handler = ConnectionHandler::resp2(SocketAddr::from_str("127.0.0.1:6379").unwrap());
18//! let client = connection_handler.connect(&mut stack, Some(&clock)).unwrap();
19//!
20//! let command = SetCommand::new("key", "value");
21//! let _ = client.send(command);
22//! ```
23//!
24//! # Expiration (EX, PX, EXAT, PXAT)
25//! Setting TTL can be achieved in the following way. Fore more details s. [ExpirationPolicy] enum.
26//! ```
27//!# use core::str::FromStr;
28//!# use core::net::SocketAddr;
29//!# use std_embedded_nal::Stack;
30//!# use std_embedded_time::StandardClock;
31//!# use embedded_redis::commands::set::{SetCommand, ExpirationPolicy};
32//!# use embedded_redis::network::ConnectionHandler;
33//!#
34//!# let mut stack = Stack::default();
35//!# let clock = StandardClock::default();
36//!#
37//!# let mut connection_handler = ConnectionHandler::resp2(SocketAddr::from_str("127.0.0.1:6379").unwrap());
38//!# let client = connection_handler.connect(&mut stack, Some(&clock)).unwrap();
39//!#
40//!  // Expires in 120 seconds
41//!  let command = SetCommand::new("key", "value")
42//!      .expires(ExpirationPolicy::Seconds(120));
43//!# let _ = client.send(command);
44//! ```
45//! # Exclusive condition (NX/XX)
46//! Manage set condition. Fore more details s. [Exclusivity] enum.
47//!
48//! Using this options affects the return type. s. [ExclusiveSetResponse]
49//! ```
50//!# use core::str::FromStr;
51//!# use core::net::SocketAddr;
52//!# use std_embedded_nal::Stack;
53//!# use std_embedded_time::StandardClock;
54//!# use embedded_redis::commands::set::{SetCommand, Exclusivity};
55//!# use embedded_redis::network::ConnectionHandler;
56//!#
57//!# let mut stack = Stack::default();
58//!# let clock = StandardClock::default();
59//!#
60//!# let mut connection_handler = ConnectionHandler::resp2(SocketAddr::from_str("127.0.0.1:6379").unwrap());
61//!# let client = connection_handler.connect(&mut stack, Some(&clock)).unwrap();
62//!#
63//!  // Just set the key if its not existing yet
64//!  let command = SetCommand::new("key", "value")
65//!      .set_exclusive(Exclusivity::SetIfMissing);
66//!# let _ = client.send(command);
67//! ```
68//! # Return previous value (!GET)
69//! Returns the previous value stored at the given key.
70//!
71//! Using this options affects the return type. s. [ReturnPreviousResponse]
72//! ```
73//!# use core::str::FromStr;
74//!# use core::net::SocketAddr;
75//!# use std_embedded_nal::Stack;
76//!# use std_embedded_time::StandardClock;
77//!# use embedded_redis::commands::set::{SetCommand};
78//!# use embedded_redis::network::ConnectionHandler;
79//!#
80//!# let mut stack = Stack::default();
81//!# let clock = StandardClock::default();
82//!#
83//!# let mut connection_handler = ConnectionHandler::resp2(SocketAddr::from_str("127.0.0.1:6379").unwrap());
84//!# let client = connection_handler.connect(&mut stack, Some(&clock)).unwrap();
85//!#
86//!  // Just set the key if its not existing yet
87//!  let command = SetCommand::new("key", "value")
88//!      .return_previous();
89//!# let _ = client.send(command);
90//! ```
91//! # Shorthand
92//! [Client](Client#method.set) provides a shorthand method for this command.
93//! ```
94//!# use core::str::FromStr;
95//!# use bytes::Bytes;
96//!# use core::net::SocketAddr;
97//!# use std_embedded_nal::Stack;
98//!# use std_embedded_time::StandardClock;
99//!# use embedded_redis::commands::set::SetCommand;
100//!# use embedded_redis::network::ConnectionHandler;
101//!#
102//!# let mut stack = Stack::default();
103//!# let clock = StandardClock::default();
104//!#
105//!# let mut connection_handler = ConnectionHandler::resp2(SocketAddr::from_str("127.0.0.1:6379").unwrap());
106//!# let client = connection_handler.connect(&mut stack, Some(&clock)).unwrap();
107//!#
108//!# let _ = client.send(SetCommand::new("test_key", "test_value")).unwrap().wait();
109//!#
110//! // Using &str arguments
111//! let _ = client.set("key", "value");
112//!
113//! // Using String arguments
114//! let _ = client.set("key".to_string(), "value".to_string());
115//!
116//! // Using Bytes arguments
117//! let _ = client.set(Bytes::from_static(b"key"), Bytes::from_static(b"value"));
118//! ```
119
120use crate::commands::auth::AuthCommand;
121use crate::commands::builder::{CommandBuilder, IsNullFrame, ToStringBytes, ToStringOption};
122use crate::commands::hello::HelloCommand;
123use crate::commands::{Command, ResponseTypeError};
124use crate::network::client::{Client, CommandErrors};
125use crate::network::future::Future;
126use crate::network::protocol::Protocol;
127use alloc::string::ToString;
128use bytes::Bytes;
129use core::marker::PhantomData;
130use embedded_nal::TcpClientStack;
131use embedded_time::Clock;
132
133pub enum ExpirationPolicy {
134    /// Does not set and expiration option
135    Never,
136    /// EX option
137    Seconds(usize),
138    /// PX option
139    Milliseconds(usize),
140    /// EXAT option
141    TimestampSeconds(usize),
142    /// PXAT option
143    TimestampMilliseconds(usize),
144    /// KEEPTTL option
145    Keep,
146}
147
148pub enum Exclusivity {
149    None,
150    /// NX option
151    SetIfExists,
152    /// XX option
153    SetIfMissing,
154}
155
156pub struct SetCommand<R> {
157    key: Bytes,
158    value: Bytes,
159    expiration: ExpirationPolicy,
160    exclusivity: Exclusivity,
161
162    /// GET option
163    return_old_value: bool,
164
165    response_type: PhantomData<R>,
166}
167
168impl SetCommand<ConfirmationResponse> {
169    pub fn new<K, V>(key: K, value: V) -> Self
170    where
171        Bytes: From<K>,
172        Bytes: From<V>,
173    {
174        SetCommand {
175            key: key.into(),
176            value: value.into(),
177            expiration: ExpirationPolicy::Never,
178            exclusivity: Exclusivity::None,
179            return_old_value: false,
180            response_type: PhantomData,
181        }
182    }
183
184    /// Set expiration (TTL)
185    pub fn expires(mut self, policy: ExpirationPolicy) -> SetCommand<ConfirmationResponse> {
186        self.expiration = policy;
187        self
188    }
189
190    /// Only set key if Exclusivity condition is met
191    pub fn set_exclusive(self, option: Exclusivity) -> SetCommand<ExclusiveSetResponse> {
192        SetCommand {
193            key: self.key,
194            value: self.value,
195            expiration: self.expiration,
196            exclusivity: option,
197            return_old_value: self.return_old_value,
198            response_type: PhantomData,
199        }
200    }
201}
202
203impl<R> SetCommand<R> {
204    /// Returns the previous key by setting the GET option
205    pub fn return_previous(self) -> SetCommand<ReturnPreviousResponse> {
206        SetCommand {
207            key: self.key,
208            value: self.value,
209            expiration: self.expiration,
210            exclusivity: self.exclusivity,
211            return_old_value: true,
212            response_type: PhantomData,
213        }
214    }
215}
216
217/// Regular response if neither !GET or NX/XX option is set.
218/// Indicates that SET operation was successful
219pub type ConfirmationResponse = ();
220
221/// Response if NX/XX option was set.
222///
223/// Some => SET was executed successfully.
224/// None => Operation was not performed, as NX/XX condition was not met.
225pub type ExclusiveSetResponse = Option<()>;
226
227/// Response if !GET option is used.
228///
229/// Some => The old string value stored at key.
230/// None => The key did not exist.
231pub type ReturnPreviousResponse = Option<Bytes>;
232
233impl<F> Command<F> for SetCommand<ConfirmationResponse>
234where
235    F: From<CommandBuilder> + ToStringOption,
236{
237    type Response = ConfirmationResponse;
238
239    fn encode(&self) -> F {
240        self.get_builder().into()
241    }
242
243    fn eval_response(&self, frame: F) -> Result<Self::Response, ResponseTypeError> {
244        if frame.to_string_option().ok_or(ResponseTypeError {})? != "OK" {
245            return Err(ResponseTypeError {});
246        }
247
248        Ok(())
249    }
250}
251
252impl<F> Command<F> for SetCommand<ExclusiveSetResponse>
253where
254    F: From<CommandBuilder> + ToStringOption + IsNullFrame,
255{
256    type Response = ExclusiveSetResponse;
257
258    fn encode(&self) -> F {
259        self.get_builder().into()
260    }
261
262    fn eval_response(&self, frame: F) -> Result<Self::Response, ResponseTypeError> {
263        if frame.is_null_frame() {
264            return Ok(None);
265        }
266
267        if frame.to_string_option().ok_or(ResponseTypeError {})? == "OK" {
268            return Ok(Some(()));
269        }
270
271        Err(ResponseTypeError {})
272    }
273}
274
275impl<F> Command<F> for SetCommand<ReturnPreviousResponse>
276where
277    F: From<CommandBuilder> + IsNullFrame + ToStringBytes,
278{
279    type Response = ReturnPreviousResponse;
280
281    fn encode(&self) -> F {
282        self.get_builder().into()
283    }
284
285    fn eval_response(&self, frame: F) -> Result<Self::Response, ResponseTypeError> {
286        if frame.is_null_frame() {
287            return Ok(None);
288        }
289
290        Ok(Some(frame.to_string_bytes().ok_or(ResponseTypeError {})?))
291    }
292}
293
294impl<R> SetCommand<R> {
295    /// General logic for building the command
296    fn get_builder(&self) -> CommandBuilder {
297        CommandBuilder::new("SET")
298            .arg(&self.key)
299            .arg(&self.value)
300            .arg_static_option(self.expiration_unit())
301            .arg_option(self.expiration_time().as_ref())
302            .arg_static_option(self.exclusive_option())
303            .arg_static_option(self.get_option())
304    }
305
306    /// Returns the expiration time unit argument
307    fn expiration_unit(&self) -> Option<&'static str> {
308        match self.expiration {
309            ExpirationPolicy::Never => None,
310            ExpirationPolicy::Seconds(_) => Some("EX"),
311            ExpirationPolicy::Milliseconds(_) => Some("PX"),
312            ExpirationPolicy::TimestampSeconds(_) => Some("EXAT"),
313            ExpirationPolicy::TimestampMilliseconds(_) => Some("PXAT"),
314            ExpirationPolicy::Keep => Some("KEEPTTL"),
315        }
316    }
317
318    /// Returns the expiration time
319    fn expiration_time(&self) -> Option<Bytes> {
320        match self.expiration {
321            ExpirationPolicy::Never => None,
322            ExpirationPolicy::Seconds(seconds)
323            | ExpirationPolicy::Milliseconds(seconds)
324            | ExpirationPolicy::TimestampSeconds(seconds)
325            | ExpirationPolicy::TimestampMilliseconds(seconds) => Some(seconds.to_string().into()),
326            ExpirationPolicy::Keep => None,
327        }
328    }
329
330    /// Returns the exclusivity argument
331    fn exclusive_option(&self) -> Option<&'static str> {
332        match self.exclusivity {
333            Exclusivity::None => None,
334            Exclusivity::SetIfExists => Some("XX"),
335            Exclusivity::SetIfMissing => Some("NX"),
336        }
337    }
338
339    fn get_option(&self) -> Option<&'static str> {
340        if self.return_old_value {
341            return Some("GET");
342        }
343
344        None
345    }
346}
347
348impl<'a, N: TcpClientStack, C: Clock, P: Protocol> Client<'a, N, C, P>
349where
350    AuthCommand: Command<<P as Protocol>::FrameType>,
351    HelloCommand: Command<<P as Protocol>::FrameType>,
352{
353    /// Shorthand for [SetCommand]
354    /// For using options of SET command, use [SetCommand] directly instead
355    pub fn set<K, V>(
356        &'a self,
357        key: K,
358        value: V,
359    ) -> Result<Future<'a, N, C, P, SetCommand<ConfirmationResponse>>, CommandErrors>
360    where
361        <P as Protocol>::FrameType: ToStringBytes,
362        <P as Protocol>::FrameType: ToStringOption,
363        <P as Protocol>::FrameType: IsNullFrame,
364        <P as Protocol>::FrameType: From<CommandBuilder>,
365        Bytes: From<K>,
366        Bytes: From<V>,
367    {
368        self.send(SetCommand::new(key, value))
369    }
370}