Skip to main content

rustis/commands/
string_commands.rs

1use crate::{
2    client::{PreparedCommand, prepare_command},
3    commands::{RequestPolicy, ResponsePolicy},
4    resp::{FastPathCommandBuilder, cmd, serialize_flag},
5};
6use serde::de::DeserializeOwned;
7use serde::{
8    Deserialize, Deserializer, Serialize,
9    de::{self, SeqAccess, Visitor},
10};
11use std::fmt;
12
13/// A group of Redis commands related to [`Strings`](https://redis.io/docs/data-types/strings/)
14/// # See Also
15/// [Redis Generic Commands](https://redis.io/commands/?group=string)
16pub trait StringCommands<'a>: Sized {
17    /// If key already exists and is a string,
18    /// this command appends the value at the end of the string.
19    /// If key does not exist it is created and set as an empty string,
20    /// so APPEND will be similar to SET in this special case.
21    ///
22    /// # Return
23    /// the length of the string after the append operation.
24    ///
25    /// # See Also
26    /// [<https://redis.io/commands/append/>](https://redis.io/commands/append/)
27    #[must_use]
28    fn append(
29        self,
30        key: impl Serialize,
31        value: impl Serialize,
32    ) -> PreparedCommand<'a, Self, usize> {
33        prepare_command(self, cmd("APPEND").key(key).arg(value))
34    }
35
36    /// Decrements the number stored at key by one.
37    ///
38    /// If the key does not exist, it is set to 0 before performing the operation.
39    /// An error is returned if the key contains a value of the wrong type or contains
40    /// a string that can not be represented as integer.
41    /// This operation is limited to 64 bit signed integers.
42    ///
43    /// # Return
44    /// the value of key after the decrement
45    ///
46    /// # See Also
47    /// [<https://redis.io/commands/decr/>](https://redis.io/commands/decr/)
48    #[must_use]
49    fn decr(self, key: impl Serialize) -> PreparedCommand<'a, Self, i64> {
50        prepare_command(self, cmd("DECR").key(key))
51    }
52
53    /// Decrements the number stored at key by one.
54    ///
55    /// If the key does not exist, it is set to 0 before performing the operation.
56    /// An error is returned if the key contains a value of the wrong type or contains
57    /// a string that can not be represented as integer.
58    /// This operation is limited to 64 bit signed integers.
59    ///
60    /// # Return
61    /// the value of key after the decrement
62    ///
63    /// # See Also
64    /// [<https://redis.io/commands/decrby/>](https://redis.io/commands/decrby/)
65    #[must_use]
66    fn decrby(self, key: impl Serialize, decrement: i64) -> PreparedCommand<'a, Self, i64> {
67        prepare_command(self, cmd("DECRBY").key(key).arg(decrement))
68    }
69
70    /// Get the value of key.
71    ///
72    /// Get the value of key. If the key does not exist the special
73    /// value nil is returned. An error is returned if the value
74    /// stored at key is not a string, because GET only handles
75    /// string values.
76    ///
77    /// # Return
78    /// the value of key, or `nil` when key does not exist.
79    ///
80    /// # Warning
81    /// A missing key answers `nil`, which no scalar `R` can hold: `""` and `0` are
82    /// values a present key holds. Declare `R` as an [`Option`] to accept it. See
83    /// [Command results](crate::resp#command-results).
84    ///
85    /// # Example
86    /// ```
87    /// use rustis::{
88    ///     client::{Client, ClientPreparedCommand},
89    ///     commands::{FlushingMode, ServerCommands, StringCommands},
90    ///     resp::{cmd},
91    ///     Result
92    /// };
93    ///
94    /// #[tokio::main]
95    /// async fn main() -> Result<()> {
96    ///     let client = Client::connect("127.0.0.1:6379").await?;
97    ///     client.flushall(FlushingMode::Sync).await?;
98    ///
99    ///     // an Option accepts the absence...
100    ///     let value: Option<String> = client.get("key").await?;
101    ///     assert_eq!(None, value);
102    ///
103    ///     // ... while a bare type has no honest value for it
104    ///     assert!(client.get::<String>("key").await.is_err());
105    ///     assert!(client.get::<i64>("counter").await.is_err());
106    ///
107    ///     client.set("key", "value").await?;
108    ///     let value: String = client.get("key").await?;
109    ///     assert_eq!("value", value);
110    ///
111    ///     Ok(())
112    /// }
113    /// ```
114    ///
115    /// # See Also
116    /// [<https://redis.io/commands/get/>](https://redis.io/commands/get/)
117    #[must_use]
118    fn get<R: DeserializeOwned>(self, key: impl Serialize) -> PreparedCommand<'a, Self, R> {
119        prepare_command(self, FastPathCommandBuilder::get(key))
120    }
121
122    /// Get the value of key and delete the key.
123    ///
124    /// This command is similar to GET, except for the fact that it also deletes the key on success
125    /// (if and only if the key's value type is a string).
126    ///
127    /// # Return
128    /// the value of key, `nil` when key does not exist, or an error if the key's value type isn't a string.
129    ///
130    /// # See Also
131    /// [<https://redis.io/commands/getdel/>](https://redis.io/commands/getdel/)
132    #[must_use]
133    fn getdel<R: DeserializeOwned>(self, key: impl Serialize) -> PreparedCommand<'a, Self, R> {
134        prepare_command(self, cmd("GETDEL").key(key))
135    }
136
137    /// Returns the hash digest of a string value as a hexadecimal string.
138    ///
139    /// The digest is stable for a given value, so it can be captured and later
140    /// passed to [`delex`](StringCommands::delex) or `SET`'s `IFDEQ`/`IFDNE`
141    /// conditions for compare-and-delete / compare-and-set flows.
142    ///
143    /// # Return
144    /// the hexadecimal digest of the value, or `nil` when the key does not exist.
145    ///
146    /// # See Also
147    /// [<https://redis.io/commands/digest/>](https://redis.io/commands/digest/)
148    #[must_use]
149    fn digest<R: DeserializeOwned>(self, key: impl Serialize) -> PreparedCommand<'a, Self, R> {
150        prepare_command(self, cmd("DIGEST").key(key).readonly())
151    }
152
153    /// Conditionally removes `key` based on a value or digest comparison.
154    ///
155    /// With no condition the key is deleted unconditionally (like `DEL` on a
156    /// single key). With a [`DelexCondition`] the key is deleted only if its
157    /// current value (or its digest) satisfies the comparison.
158    ///
159    /// # Return
160    /// * `1` if the key was deleted.
161    /// * `0` if the key does not exist or the condition was not met.
162    ///
163    /// # See Also
164    /// [<https://redis.io/commands/delex/>](https://redis.io/commands/delex/)
165    #[must_use]
166    fn delex<'b>(
167        self,
168        key: impl Serialize,
169        condition: impl Into<Option<DelexCondition<'b>>>,
170    ) -> PreparedCommand<'a, Self, i64> {
171        prepare_command(self, cmd("DELEX").key(key).arg(condition.into()))
172    }
173
174    /// Get the value of key and optionally set its expiration. GETEX is similar to GET, but is a write command with additional options.
175    ///
176    /// Decrements the number stored at key by decrement.
177    /// If the key does not exist, it is set to 0 before performing the operation.
178    /// An error is returned if the key contains a value of the wrong type
179    /// or contains a string that can not be represented as integer.
180    /// This operation is limited to 64 bit signed integers.
181    ///
182    /// # Return
183    /// the value of key, or `nil` when key does not exist.
184    ///
185    /// # Example
186    /// ```
187    /// use rustis::{
188    ///     client::{Client, ClientPreparedCommand},
189    ///     commands::{FlushingMode, GetExOptions, GenericCommands, ServerCommands, StringCommands},
190    ///     resp::cmd,
191    ///     Result,
192    /// };
193    ///
194    /// #[tokio::main]
195    /// async fn main() -> Result<()> {
196    ///     let client = Client::connect("127.0.0.1:6379").await?;
197    ///     client.flushall(FlushingMode::Sync).await?;
198    ///
199    ///     client.set("key", "value").await?;
200    ///     let value: String = client.getex("key", GetExOptions::Ex(60)).await?;
201    ///     assert_eq!("value", value);
202    ///
203    ///     let ttl = client.ttl("key").await?;
204    ///     assert!(59 <= ttl && ttl <= 60);
205    ///
206    ///     Ok(())
207    /// }
208    /// ```
209    ///
210    /// # See Also
211    /// [<https://redis.io/commands/getex/>](https://redis.io/commands/getex/)
212    #[must_use]
213    fn getex<R: DeserializeOwned>(
214        self,
215        key: impl Serialize,
216        options: GetExOptions,
217    ) -> PreparedCommand<'a, Self, R> {
218        prepare_command(self, cmd("GETEX").key(key).arg(options))
219    }
220
221    /// Returns the substring of the string value stored at key, determined by the offsets start and end (both are inclusive).
222    ///
223    /// Negative offsets can be used in order to provide an offset starting from the end of the string.
224    /// So -1 means the last character, -2 the penultimate and so forth.
225    ///
226    /// The function handles out of range requests by limiting the resulting range to the actual length of the string.
227    ///
228    /// # Example
229    /// ```
230    /// use rustis::{
231    ///     client::Client,
232    ///     commands::{FlushingMode, ServerCommands, StringCommands},
233    ///     Result,
234    /// };
235    ///
236    /// #[tokio::main]
237    /// async fn main() -> Result<()> {
238    ///     let client = Client::connect("127.0.0.1:6379").await?;
239    ///     client.flushall(FlushingMode::Sync).await?;
240    ///     client.set("mykey", "This is a string").await?;
241    ///
242    ///     let value: String = client.getrange("mykey", 0, 3).await?;
243    ///     assert_eq!("This", value);
244    ///     let value: String = client.getrange("mykey", -3, -1).await?;
245    ///     assert_eq!("ing", value);
246    ///     let value: String = client.getrange("mykey", 0, -1).await?;
247    ///     assert_eq!("This is a string", value);
248    ///     let value: String = client.getrange("mykey", 10, 100).await?;
249    ///     assert_eq!("string", value);
250    ///     Ok(())
251    /// }
252    /// ```
253    ///
254    /// # See Also
255    /// [<https://redis.io/commands/getrange/>](https://redis.io/commands/getrange/)
256    #[must_use]
257    fn getrange<R: DeserializeOwned>(
258        self,
259        key: impl Serialize,
260        start: isize,
261        end: isize,
262    ) -> PreparedCommand<'a, Self, R> {
263        prepare_command(
264            self,
265            cmd("GETRANGE").key(key).arg(start).arg(end).readonly(),
266        )
267    }
268
269    /// Increments the number stored at key by one.
270    ///
271    /// If the key does not exist, it is set to 0 before performing the operation.
272    /// An error is returned if the key contains a value of the wrong type
273    /// or contains a string that can not be represented as integer.
274    /// This operation is limited to 64 bit signed integers.
275    ///
276    /// Note: this is a string operation because Redis does not have a dedicated integer type.
277    /// The string stored at the key is interpreted as a base-10 64 bit signed integer to execute the operation.
278    ///
279    /// Redis stores integers in their integer representation, so for string values that actually hold an integer,
280    /// there is no overhead for storing the string representation of the integer.
281    ///
282    /// # Return
283    /// the value of key after the increment
284    ///
285    /// # See Also
286    /// [<https://redis.io/commands/incr/>](https://redis.io/commands/incr/)
287    #[must_use]
288    fn incr(self, key: impl Serialize) -> PreparedCommand<'a, Self, i64> {
289        prepare_command(self, cmd("INCR").key(key))
290    }
291
292    /// Increments the number stored at key by increment.
293    ///
294    /// If the key does not exist, it is set to 0 before performing the operation.
295    /// An error is returned if the key contains a value of the wrong type
296    /// or contains a string that can not be represented as integer.
297    /// This operation is limited to 64 bit signed integers.
298    ///
299    /// See [incr](StringCommands::incr) for extra information on increment/decrement operations.
300    ///
301    /// # Return
302    /// the value of key after the increment
303    ///
304    /// # See Also
305    /// [<https://redis.io/commands/incrby/>](https://redis.io/commands/incrby/)
306    #[must_use]
307    fn incrby(self, key: impl Serialize, increment: i64) -> PreparedCommand<'a, Self, i64> {
308        prepare_command(self, cmd("INCRBY").key(key).arg(increment))
309    }
310
311    /// Increment the value at `key`, bounded, and set its expiration, atomically.
312    ///
313    /// The key is created at `0` when it does not exist. Without an increment
314    /// it is bumped by `1` in integer mode.
315    ///
316    /// Where [`incr`](StringCommands::incr) and [`expire`](crate::commands::GenericCommands::expire)
317    /// would need a Lua script to be atomic, this is one command — which is
318    /// what makes it a window-counter rate limiter:
319    /// [`ubound_int`](IncrExOptions::ubound_int) is the cap and
320    /// [`enx`](IncrExOptions::enx) starts the window only once.
321    ///
322    /// # Return
323    /// A pair of the value after the operation and the increment actually
324    /// applied. The applied increment is `0` when a bound stopped the operation,
325    /// and smaller than requested when [`saturate`](IncrExOptions::saturate)
326    /// capped it. Both are integers in integer mode, doubles under
327    /// [`by_float`](IncrExOptions::by_float) — so `(i64, i64)` or `(f64, f64)`.
328    ///
329    /// # See Also
330    /// [<https://redis.io/commands/increx/>](https://redis.io/commands/increx/)
331    #[must_use]
332    fn increx<R: DeserializeOwned>(
333        self,
334        key: impl Serialize,
335        options: IncrExOptions,
336    ) -> PreparedCommand<'a, Self, R> {
337        prepare_command(self, cmd("INCREX").key(key).arg(options))
338    }
339
340    ///Increment the string representing a floating point number stored at key by the specified increment.
341    /// By using a negative increment value, the result is that the value stored at the key is decremented (by the obvious properties of addition).
342    /// If the key does not exist, it is set to 0 before performing the operation.
343    /// An error is returned if one of the following conditions occur:
344    ///
345    /// - The key contains a value of the wrong type (not a string).
346    ///
347    /// - The current key content or the specified increment are not parsable as a double precision floating point number.
348    ///
349    /// If the command is successful the new incremented value is stored as the new value of the key (replacing the old one),
350    /// and returned to the caller as a string.
351    ///   
352    /// Both the value already contained in the string key and the increment argument can be optionally provided in exponential notation,
353    /// however the value computed after the increment is stored consistently in the same format, that is,
354    /// an integer number followed (if needed) by a dot, and a variable number of digits representing the decimal part of the number.
355    /// Trailing zeroes are always removed.
356    ///    
357    /// The precision of the output is fixed at 17 digits after the decimal point
358    /// regardless of the actual internal precision of the computation.
359    ///
360    /// # Return
361    /// the value of key after the increment
362    ///
363    /// # See Also
364    /// [<https://redis.io/commands/incrbyfloat/>](https://redis.io/commands/incrbyfloat/)
365    #[must_use]
366    fn incrbyfloat(self, key: impl Serialize, increment: f64) -> PreparedCommand<'a, Self, f64> {
367        prepare_command(self, cmd("INCRBYFLOAT").key(key).arg(increment))
368    }
369
370    /// The LCS command implements the longest common subsequence algorithm
371    ///
372    /// # Return
373    /// The string representing the longest common substring.
374    ///
375    /// # See Also
376    /// [<https://redis.io/commands/lcs/>](https://redis.io/commands/lcs/)
377    #[must_use]
378    fn lcs<R: DeserializeOwned>(
379        self,
380        key1: impl Serialize,
381        key2: impl Serialize,
382    ) -> PreparedCommand<'a, Self, R> {
383        prepare_command(self, cmd("LCS").key(key1).key(key2).readonly())
384    }
385
386    /// The LCS command implements the longest common subsequence algorithm
387    ///
388    /// # Return
389    /// The length of the longest common substring.
390    ///
391    /// # See Also
392    /// [<https://redis.io/commands/lcs/>](https://redis.io/commands/lcs/)
393    #[must_use]
394    fn lcs_len(
395        self,
396        key1: impl Serialize,
397        key2: impl Serialize,
398    ) -> PreparedCommand<'a, Self, usize> {
399        prepare_command(self, cmd("LCS").key(key1).key(key2).arg("LEN").readonly())
400    }
401
402    /// The LCS command implements the longest common subsequence algorithm
403    ///
404    /// # Return
405    /// An array with the LCS length and all the ranges in both the strings,
406    /// start and end offset for each string, where there are matches.
407    /// When `with_match_len` is given each match will also have the length of the match
408    ///
409    /// # See Also
410    /// [<https://redis.io/commands/lcs/>](https://redis.io/commands/lcs/)
411    #[must_use]
412    fn lcs_idx(
413        self,
414        key1: impl Serialize,
415        key2: impl Serialize,
416        min_match_len: Option<usize>,
417        with_match_len: bool,
418    ) -> PreparedCommand<'a, Self, LcsResult> {
419        prepare_command(
420            self,
421            cmd("LCS")
422                .key(key1)
423                .key(key2)
424                .arg("IDX")
425                .arg(min_match_len.map(|len| ("MINMATCHLEN", len)))
426                .arg_if(with_match_len, "WITHMATCHLEN")
427                .readonly(),
428        )
429    }
430
431    /// Returns the values of all specified keys.
432    ///
433    /// For every key that does not hold a string value or does not exist,
434    /// the special value nil is returned. Because of this, the operation never fails.
435    ///
436    /// # Return
437    /// Array reply: list of values at the specified keys.
438    ///
439    /// # See Also
440    /// [<https://redis.io/commands/mget/>](https://redis.io/commands/mget/)
441    #[must_use]
442    fn mget<R: DeserializeOwned>(self, keys: impl Serialize) -> PreparedCommand<'a, Self, R> {
443        prepare_command(
444            self,
445            cmd("MGET")
446                .keys(keys)
447                .cluster_info(RequestPolicy::MultiShard, None, 1)
448                .readonly(),
449        )
450    }
451
452    /// Sets the given keys to their respective values.
453    ///
454    /// # Return
455    /// always OK since MSET can't fail.
456    ///
457    /// # See Also
458    /// [<https://redis.io/commands/mset/>](https://redis.io/commands/mset/)
459    #[must_use]
460    fn mset(self, items: impl Serialize) -> PreparedCommand<'a, Self, ()> {
461        prepare_command(
462            self,
463            cmd("MSET").key_with_step(items, 2).cluster_info(
464                RequestPolicy::MultiShard,
465                ResponsePolicy::AllSucceeded,
466                2,
467            ),
468        )
469    }
470
471    /// Atomically sets multiple string keys with an optional shared expiration in a single operation.
472    ///
473    /// # Return
474    /// * `false` - if none of the keys were set
475    /// * `true` - if all of the keys were set.
476    ///
477    /// # Cluster
478    /// In cluster mode all keys must hash to the same slot, otherwise the
479    /// command fails client-side with a mismatched-slot error.
480    ///
481    /// # See Also
482    /// [<https://redis.io/commands/msetex/>](https://redis.io/commands/msetex/)
483    #[must_use]
484    fn msetex<'b>(
485        self,
486        items: impl Serialize,
487        condition: impl Into<Option<SetCondition<'b>>>,
488        expiration: impl Into<Option<SetExpiration>>,
489    ) -> PreparedCommand<'a, Self, bool> {
490        prepare_command(
491            self,
492            cmd("MSETEX")
493                .key_with_count_and_step(items, 2)
494                .arg(condition.into())
495                .arg(expiration.into())
496                .cluster_info(RequestPolicy::MultiShard, ResponsePolicy::AllSucceeded, 2),
497        )
498    }
499
500    /// Sets the given keys to their respective values.
501    /// MSETNX will not perform any operation at all even if just a single key already exists.
502    ///
503    /// Because of this semantic MSETNX can be used in order to set different keys representing
504    /// different fields of a unique logic object in a way that ensures that either
505    /// all the fields or none at all are set.
506    ///
507    /// MSETNX is atomic, so all given keys are set at once. It is not possible for
508    /// clients to see that some of the keys were updated while others are unchanged.
509    ///
510    /// # Return
511    /// specifically:
512    /// - 1 if the all the keys were set.
513    /// - 0 if no key was set (at least one key already existed).
514    ///
515    /// # Cluster
516    /// Unlike [`mset`](StringCommands::mset), MSETNX is routed to a single node:
517    /// its all-or-nothing atomicity cannot be preserved if the keys were split
518    /// across shards. All keys must therefore hash to the same slot in cluster
519    /// mode, otherwise the command fails client-side with a mismatched-slot error.
520    ///
521    /// # See Also
522    /// [<https://redis.io/commands/msetnx/>](https://redis.io/commands/msetnx/)
523    #[must_use]
524    fn msetnx(self, items: impl Serialize) -> PreparedCommand<'a, Self, bool> {
525        prepare_command(
526            self,
527            cmd("MSETNX")
528                .key_with_step(items, 2)
529                .cluster_info(None, None, 2),
530        )
531    }
532
533    ///Set key to hold the string value.
534    ///
535    /// If key already holds a value, it is overwritten, regardless of its type.
536    /// Any previous time to live associated with the key is discarded on successful SET operation.
537    ///
538    /// # See Also
539    /// [<https://redis.io/commands/set/>](https://redis.io/commands/set/)
540    #[must_use]
541    fn set(self, key: impl Serialize, value: impl Serialize) -> PreparedCommand<'a, Self, ()> {
542        prepare_command(self, FastPathCommandBuilder::set(key, value))
543    }
544
545    /// Set key to hold the string value.
546    ///
547    /// # Return
548    /// * `true` if SET was executed correctly.
549    /// * `false` if the SET operation was not performed because the user
550    ///   specified the NX or XX option but the condition was not met.
551    ///
552    /// # See Also
553    /// [<https://redis.io/commands/set/>](https://redis.io/commands/set/)
554    #[must_use]
555    fn set_with_options<'b>(
556        self,
557        key: impl Serialize,
558        value: impl Serialize,
559        condition: impl Into<Option<SetCondition<'b>>>,
560        expiration: impl Into<Option<SetExpiration>>,
561    ) -> PreparedCommand<'a, Self, bool> {
562        prepare_command(
563            self,
564            cmd("SET")
565                .key(key)
566                .arg(value)
567                .arg(condition.into())
568                .arg(expiration.into()),
569        )
570    }
571
572    /// Set key to hold the string value wit GET option enforced
573    ///
574    /// # See Also
575    /// [<https://redis.io/commands/set/>](https://redis.io/commands/set/)
576    #[must_use]
577    fn set_get_with_options<'b, R: DeserializeOwned>(
578        self,
579        key: impl Serialize,
580        value: impl Serialize,
581        condition: impl Into<Option<SetCondition<'b>>>,
582        expiration: impl Into<Option<SetExpiration>>,
583    ) -> PreparedCommand<'a, Self, R> {
584        prepare_command(
585            self,
586            cmd("SET")
587                .key(key)
588                .arg(value)
589                .arg(condition.into())
590                .arg("GET")
591                .arg(expiration.into()),
592        )
593    }
594
595    /// Overwrites part of the string stored at key,
596    /// starting at the specified offset,
597    /// for the entire length of value.
598    ///
599    /// # Return
600    /// the length of the string after it was modified by the command.
601    ///
602    /// # See Also
603    /// [<https://redis.io/commands/setrange/>](https://redis.io/commands/setrange/)
604    #[must_use]
605    fn setrange(
606        self,
607        key: impl Serialize,
608        offset: usize,
609        value: impl Serialize,
610    ) -> PreparedCommand<'a, Self, usize> {
611        prepare_command(self, cmd("SETRANGE").key(key).arg(offset).arg(value))
612    }
613
614    /// Returns the length of the string value stored at key.
615    ///
616    /// An error is returned when key holds a non-string value.
617    ///
618    /// # Return
619    /// the length of the string at key, or 0 when key does not exist.
620    ///
621    /// # See Also
622    /// [<https://redis.io/commands/strlen/>](https://redis.io/commands/strlen/)
623    #[must_use]
624    fn strlen(self, key: impl Serialize) -> PreparedCommand<'a, Self, usize> {
625        prepare_command(self, cmd("STRLEN").key(key).readonly())
626    }
627}
628
629/// Options for the [`getex`](StringCommands::getex) and the [`hgetex`](crate::commands::HashCommands::hgetex) commands
630#[derive(Serialize)]
631#[serde(rename_all = "UPPERCASE")]
632#[non_exhaustive]
633pub enum GetExOptions {
634    /// Set the specified expire time, in seconds.
635    Ex(u64),
636    /// Set the specified expire time, in milliseconds.
637    Px(u64),
638    /// Set the specified Unix time at which the key will expire, in seconds.
639    Exat(u64),
640    /// Set the specified Unix time at which the key will expire, in milliseconds.
641    Pxat(u64),
642    /// Remove the time to live associated with the key.
643    Persist,
644}
645
646/// Part of the result for the [`lcs`](StringCommands::lcs) command
647#[derive(Debug, PartialEq, Eq)]
648#[non_exhaustive]
649pub struct LcsMatch(pub (usize, usize), pub (usize, usize), pub Option<usize>);
650
651impl<'de> Deserialize<'de> for LcsMatch {
652    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
653    where
654        D: Deserializer<'de>,
655    {
656        struct LcsMatchVisitor;
657
658        impl<'de> Visitor<'de> for LcsMatchVisitor {
659            type Value = LcsMatch;
660
661            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
662                formatter.write_str("LcsMatch")
663            }
664
665            fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
666            where
667                A: SeqAccess<'de>,
668            {
669                let Some(first): Option<(usize, usize)> = seq.next_element()? else {
670                    return Err(de::Error::invalid_length(0, &"fewer elements in tuple"));
671                };
672
673                let Some(second): Option<(usize, usize)> = seq.next_element()? else {
674                    return Err(de::Error::invalid_length(1, &"fewer elements in tuple"));
675                };
676
677                let match_len: Option<usize> = seq.next_element()?;
678
679                Ok(LcsMatch(first, second, match_len))
680            }
681        }
682
683        deserializer.deserialize_seq(LcsMatchVisitor)
684    }
685}
686
687/// Result for the [`lcs`](StringCommands::lcs) command
688#[derive(Debug, Deserialize)]
689#[non_exhaustive]
690pub struct LcsResult {
691    pub matches: Vec<LcsMatch>,
692    pub len: usize,
693}
694
695/// Options for the [`increx`](StringCommands::increx) command
696#[derive(Default, Serialize)]
697#[serde(rename_all = "UPPERCASE")]
698pub struct IncrExOptions {
699    #[serde(skip_serializing_if = "Option::is_none")]
700    byint: Option<i64>,
701    #[serde(skip_serializing_if = "Option::is_none")]
702    byfloat: Option<f64>,
703    #[serde(skip_serializing_if = "Option::is_none")]
704    lbound: Option<IncrExBound>,
705    #[serde(skip_serializing_if = "Option::is_none")]
706    ubound: Option<IncrExBound>,
707    #[serde(
708        skip_serializing_if = "std::ops::Not::not",
709        serialize_with = "serialize_flag"
710    )]
711    saturate: bool,
712    #[serde(rename = "", skip_serializing_if = "Option::is_none")]
713    expiration: Option<GetExOptions>,
714    #[serde(
715        skip_serializing_if = "std::ops::Not::not",
716        serialize_with = "serialize_flag"
717    )]
718    enx: bool,
719}
720
721/// A bound of the [`increx`](StringCommands::increx) command, in whichever mode
722/// the increment put it in.
723#[derive(Serialize)]
724#[serde(untagged)]
725enum IncrExBound {
726    Int(i64),
727    Float(f64),
728}
729
730impl IncrExOptions {
731    /// Increment by a 64-bit signed integer. Negative decrements.
732    ///
733    /// The stored value must be integer-typed: a stored `"1.5"` cannot be read
734    /// back as an integer, exactly as with [`incrby`](StringCommands::incrby).
735    #[must_use]
736    pub fn by_int(increment: i64) -> Self {
737        Self {
738            byint: Some(increment),
739            ..Default::default()
740        }
741    }
742
743    /// Increment by a floating-point value.
744    ///
745    /// The stored value may be an integer or a float, since integers promote to
746    /// floats losslessly. A result of NaN or infinity is rejected.
747    #[must_use]
748    pub fn by_float(increment: f64) -> Self {
749        Self {
750            byfloat: Some(increment),
751            ..Default::default()
752        }
753    }
754
755    /// Lower bound, in integer mode.
756    #[must_use]
757    pub fn lbound_int(mut self, lower_bound: i64) -> Self {
758        self.lbound = Some(IncrExBound::Int(lower_bound));
759        self
760    }
761
762    /// Lower bound, in [`by_float`](IncrExOptions::by_float) mode.
763    #[must_use]
764    pub fn lbound_float(mut self, lower_bound: f64) -> Self {
765        self.lbound = Some(IncrExBound::Float(lower_bound));
766        self
767    }
768
769    /// Upper bound, in integer mode.
770    #[must_use]
771    pub fn ubound_int(mut self, upper_bound: i64) -> Self {
772        self.ubound = Some(IncrExBound::Int(upper_bound));
773        self
774    }
775
776    /// Upper bound, in [`by_float`](IncrExOptions::by_float) mode.
777    #[must_use]
778    pub fn ubound_float(mut self, upper_bound: f64) -> Self {
779        self.ubound = Some(IncrExBound::Float(upper_bound));
780        self
781    }
782
783    /// Cap an out-of-bounds result at the bound instead of skipping the
784    /// operation. Without it, a bound violation leaves the key and its TTL
785    /// untouched and reports a zero increment.
786    #[must_use]
787    pub fn saturate(mut self) -> Self {
788        self.saturate = true;
789        self
790    }
791
792    /// Set the expiration, in seconds.
793    #[must_use]
794    pub fn ex(mut self, seconds: u64) -> Self {
795        self.expiration = Some(GetExOptions::Ex(seconds));
796        self
797    }
798
799    /// Set the expiration, in milliseconds.
800    #[must_use]
801    pub fn px(mut self, milliseconds: u64) -> Self {
802        self.expiration = Some(GetExOptions::Px(milliseconds));
803        self
804    }
805
806    /// Set the Unix time at which the key expires, in seconds.
807    #[must_use]
808    pub fn exat(mut self, unix_time_seconds: u64) -> Self {
809        self.expiration = Some(GetExOptions::Exat(unix_time_seconds));
810        self
811    }
812
813    /// Set the Unix time at which the key expires, in milliseconds.
814    #[must_use]
815    pub fn pxat(mut self, unix_time_milliseconds: u64) -> Self {
816        self.expiration = Some(GetExOptions::Pxat(unix_time_milliseconds));
817        self
818    }
819
820    /// Remove the expiration of the key.
821    #[must_use]
822    pub fn persist(mut self) -> Self {
823        self.expiration = Some(GetExOptions::Persist);
824        self
825    }
826
827    /// Set the expiration only when the key has none. An existing TTL is kept
828    /// as it is, while the increment still applies. Requires one of
829    /// [`ex`](IncrExOptions::ex), [`px`](IncrExOptions::px),
830    /// [`exat`](IncrExOptions::exat) or [`pxat`](IncrExOptions::pxat), and is
831    /// incompatible with [`persist`](IncrExOptions::persist).
832    #[must_use]
833    pub fn enx(mut self) -> Self {
834        self.enx = true;
835        self
836    }
837}
838
839/// Expiration option for the [`set_with_options`](StringCommands::set_with_options) and [`hsetex`](crate::commands::HashCommands::hsetex) commands
840#[derive(Serialize)]
841#[serde(rename_all = "UPPERCASE")]
842#[non_exhaustive]
843pub enum SetExpiration {
844    /// Set the specified expire time, in seconds.
845    Ex(u64),
846    /// Set the specified expire time, in milliseconds.
847    Px(u64),
848    /// Set the specified Unix time at which the key will expire, in seconds.
849    Exat(u64),
850    /// Set the specified Unix time at which the key will expire, in milliseconds.
851    Pxat(u64),
852    /// Retain the time to live associated with the key.
853    KeepTtl,
854}
855
856/// Condition option for the [`delex`](StringCommands::delex) command.
857///
858/// Mirrors the `IFEQ`/`IFNE`/`IFDEQ`/`IFDNE` value/digest comparisons of
859/// [`SetCondition`], without the `NX`/`XX` existence conditions, which `DELEX`
860/// does not accept.
861#[derive(Serialize)]
862#[serde(rename_all = "UPPERCASE")]
863#[non_exhaustive]
864pub enum DelexCondition<'a> {
865    /// Delete only if the current value is equal to the provided value.
866    IFEQ(&'a str),
867    /// Delete only if the current value is not equal to the provided value.
868    IFNE(&'a str),
869    /// Delete only if the digest of the current value is equal to the provided digest.
870    IFDEQ(&'a str),
871    /// Delete only if the digest of the current value is not equal to the provided digest.
872    IFDNE(&'a str),
873}
874
875/// Condition option for the [`set_with_options`](StringCommands::set_with_options) command
876#[derive(Serialize)]
877#[serde(rename_all = "UPPERCASE")]
878#[non_exhaustive]
879pub enum SetCondition<'a> {
880    /// Only set the key if it does not already exist.
881    NX,
882    /// Only set the key if it already exist.
883    XX,
884    /// Set the key’s value and expiration only if its current value is equal to the provided value.
885    /// If the key doesn’t exist, it won’t be created.
886    IFEQ(&'a str),
887    /// Set the key’s value and expiration only if its current value is not equal to the provided value.
888    /// If the key doesn’t exist, it will be created.
889    IFNE(&'a str),
890    /// Set the key’s value and expiration only if the hash digest of its current value is equal to the provided digest.
891    /// If the key doesn’t exist, it won’t be created.
892    IFDEQ(&'a str),
893    /// Set the key’s value and expiration only if the hash digest of its current value is not equal to the provided digest.
894    /// If the key doesn’t exist, it will be created.
895    IFDNE(&'a str),
896}