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}