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}