@@ -93,6 +93,106 @@ extension RedisClient {
9393
9494// MARK: Set
9595
96+ /// A condition which must hold true in order for a key to be set.
97+ ///
98+ /// See [https://redis.io/commands/set](https://redis.io/commands/set)
99+ public struct RedisSetCommandCondition : Hashable {
100+ private enum Condition : String , Hashable {
101+ case keyExists = " XX "
102+ case keyDoesNotExist = " NX "
103+ }
104+
105+ private let condition : Condition ?
106+ private init ( _ condition: Condition ? ) {
107+ self . condition = condition
108+ }
109+
110+ /// The `RESPValue` representation of the condition.
111+ @usableFromInline
112+ internal var commandArgument : RESPValue ? {
113+ return self . condition. map { RESPValue ( from: $0. rawValue) }
114+ }
115+ }
116+
117+ extension RedisSetCommandCondition {
118+ /// No condition is required to be met in order to set the key's value.
119+ public static let none = RedisSetCommandCondition ( . none)
120+
121+ /// Only set the key if it already exists.
122+ ///
123+ /// Redis documentation refers to this as the option "XX".
124+ public static let keyExists = RedisSetCommandCondition ( . keyExists)
125+
126+ /// Only set the key if it does not already exist.
127+ ///
128+ /// Redis documentation refers to this as the option "NX".
129+ public static let keyDoesNotExist = RedisSetCommandCondition ( . keyDoesNotExist)
130+ }
131+
132+ /// The expiration to apply when setting a key.
133+ ///
134+ /// See [https://redis.io/commands/set](https://redis.io/commands/set)
135+ public struct RedisSetCommandExpiration : Hashable {
136+ private enum Expiration : Hashable {
137+ case keepExisting
138+ case seconds( Int )
139+ case milliseconds( Int )
140+ }
141+
142+ private let expiration : Expiration
143+ private init ( _ expiration: Expiration ) {
144+ self . expiration = expiration
145+ }
146+
147+ /// An array of `RESPValue`s representing this expiration.
148+ @usableFromInline
149+ internal func asCommandArguments( ) -> [ RESPValue ] {
150+ switch self . expiration {
151+ case . keepExisting:
152+ return [ RESPValue ( from: " KEEPTTL " ) ]
153+ case . seconds( let amount) :
154+ return [ RESPValue ( from: " EX " ) , amount. convertedToRESPValue ( ) ]
155+ case . milliseconds( let amount) :
156+ return [ RESPValue ( from: " PX " ) , amount. convertedToRESPValue ( ) ]
157+ }
158+ }
159+ }
160+
161+ extension RedisSetCommandExpiration {
162+ /// Retain the existing expiration associated with the key, if one exists.
163+ ///
164+ /// Redis documentation refers to this as "KEEPTTL".
165+ /// - Important: This is option is only available in Redis 6.0+. An error will be returned if this value is sent in lower versions of Redis.
166+ public static let keepExisting = RedisSetCommandExpiration ( . keepExisting)
167+
168+ /// Expire the key after the given number of seconds.
169+ ///
170+ /// Redis documentation refers to this as the option "EX".
171+ /// - Important: The actual amount used will be the specified value or `1`, whichever is larger.
172+ public static func seconds( _ amount: Int ) -> RedisSetCommandExpiration {
173+ return RedisSetCommandExpiration ( . seconds( max ( amount, 1 ) ) )
174+ }
175+
176+ /// Expire the key after the given number of milliseconds.
177+ ///
178+ /// Redis documentation refers to this as the option "PX".
179+ /// - Important: The actual amount used will be the specified value or `1`, whichever is larger.
180+ public static func milliseconds( _ amount: Int ) -> RedisSetCommandExpiration {
181+ return RedisSetCommandExpiration ( . milliseconds( max ( amount, 1 ) ) )
182+ }
183+ }
184+
185+ /// The result of a `SET` command.
186+ public enum RedisSetCommandResult : Hashable {
187+ /// The command completed successfully.
188+ case ok
189+
190+ /// The command was not performed because a condition was not met.
191+ ///
192+ /// See `RedisSetCommandCondition`.
193+ case conditionNotMet
194+ }
195+
96196extension RedisClient {
97197 /// Append a value to the end of an existing entry.
98198 /// - Note: If the key does not exist, it is created and set as an empty string, so `APPEND` will be similar to `SET` in this special case.
@@ -133,6 +233,45 @@ extension RedisClient {
133233 . map { _ in ( ) }
134234 }
135235
236+ /// Sets the key to the provided value with options to control how it is set.
237+ ///
238+ /// [https://redis.io/commands/set](https://redis.io/commands/set)
239+ /// - Important: Regardless of the type of data stored at the key, it will be overwritten to a "string" data type.
240+ ///
241+ /// ie. If the key is a reference to a Sorted Set, its value will be overwritten to be a "string" data type.
242+ ///
243+ /// - Parameters:
244+ /// - key: The key to use to uniquely identify this value.
245+ /// - value: The value to set the key to.
246+ /// - condition: The condition under which the key should be set.
247+ /// - expiration: The expiration to use when setting the key. No expiration is set if `nil`.
248+ /// - Returns: A `NIO.EventLoopFuture` indicating the result of the operation;
249+ /// `.ok` if the operation was successful and `.conditionNotMet` if the specified `condition` was not met.
250+ ///
251+ /// If the condition `.none` was used, then the result value will always be `.ok`.
252+ public func set< Value: RESPValueConvertible > (
253+ _ key: RedisKey ,
254+ to value: Value ,
255+ onCondition condition: RedisSetCommandCondition ,
256+ expiration: RedisSetCommandExpiration ? = nil
257+ ) -> EventLoopFuture < RedisSetCommandResult > {
258+ var args : [ RESPValue ] = [
259+ . init( from: key) ,
260+ value. convertedToRESPValue ( )
261+ ]
262+
263+ if let conditionArgument = condition. commandArgument {
264+ args. append ( conditionArgument)
265+ }
266+
267+ if let expiration = expiration {
268+ args. append ( contentsOf: expiration. asCommandArguments ( ) )
269+ }
270+
271+ return self . send ( command: " SET " , with: args)
272+ . map { return $0. isNull ? . conditionNotMet : . ok }
273+ }
274+
136275 /// Sets the key to the provided value if the key does not exist.
137276 ///
138277 /// [https://redis.io/commands/setnx](https://redis.io/commands/setnx)
0 commit comments