Skip to main content

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