Commit fa227b0e authored by Nathan Harris's avatar Nathan Harris

59 -- Use `RESPValueConvertible` as Generic Constraint

Motivation:

Johannes continues to provide great insight, and correctly pointed out that `RESPValueConvertible` was being used as an "existential" in all cases.

This can cause unexpected type-erasure and introduce unnecessary cost overhead with dynamic dispatch when in most cases we know the exact value we want for `RESPValue` to execute commands.

Modifications:

- Add new extensions to `Array where Element == RESPValue` for appending and adding elements into them
- Change `RedisClient.send(command:with:)` to require `[RESPValue]` instead of `[RESPValueConvertible]` as the `with` argument type
- Change all instances of `RESPValueConvertible` being an "existential" type for method arguments to instead be a generic constraint

Result:

The library should be safeguarded from a class of bugs, with the use of `send` being a bit more straight forward, with some new convenience methods for `[RESPValue]` types.
parent e964ba04
Pipeline #69350304 passed with stages
in 10 minutes and 55 seconds
...@@ -22,7 +22,8 @@ extension RedisClient { ...@@ -22,7 +22,8 @@ extension RedisClient {
/// - Returns: The message sent with the command. /// - Returns: The message sent with the command.
@inlinable @inlinable
public func echo(_ message: String) -> EventLoopFuture<String> { public func echo(_ message: String) -> EventLoopFuture<String> {
return send(command: "ECHO", with: [message]) let args = [RESPValue(bulk: message)]
return send(command: "ECHO", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
...@@ -33,8 +34,10 @@ extension RedisClient { ...@@ -33,8 +34,10 @@ extension RedisClient {
/// - Returns: The provided message or Redis' default response of `"PONG"`. /// - Returns: The provided message or Redis' default response of `"PONG"`.
@inlinable @inlinable
public func ping(with message: String? = nil) -> EventLoopFuture<String> { public func ping(with message: String? = nil) -> EventLoopFuture<String> {
let arg = message != nil ? [message] : [] let args: [RESPValue] = message != nil
return send(command: "PING", with: arg) ? [.init(bulk: message!)] // safe because we did a nil pre-check
: []
return send(command: "PING", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
...@@ -46,7 +49,8 @@ extension RedisClient { ...@@ -46,7 +49,8 @@ extension RedisClient {
/// - Returns: An `EventLoopFuture` that resolves when the operation has succeeded, or fails with a `RedisError`. /// - Returns: An `EventLoopFuture` that resolves when the operation has succeeded, or fails with a `RedisError`.
@inlinable @inlinable
public func select(database index: Int) -> EventLoopFuture<Void> { public func select(database index: Int) -> EventLoopFuture<Void> {
return send(command: "SELECT", with: [index]) let args = [RESPValue(bulk: index)]
return send(command: "SELECT", with: args)
.map { _ in return () } .map { _ in return () }
} }
...@@ -59,8 +63,11 @@ extension RedisClient { ...@@ -59,8 +63,11 @@ extension RedisClient {
/// - Returns: `true` if the swap was successful. /// - Returns: `true` if the swap was successful.
@inlinable @inlinable
public func swapDatabase(_ first: Int, with second: Int) -> EventLoopFuture<Bool> { public func swapDatabase(_ first: Int, with second: Int) -> EventLoopFuture<Bool> {
/// connection.swapDatabase(index: 0, withIndex: 10) let args: [RESPValue] = [
return send(command: "SWAPDB", with: [first, second]) .init(bulk: first),
.init(bulk: second)
]
return send(command: "SWAPDB", with: args)
.convertFromRESPValue(to: String.self) .convertFromRESPValue(to: String.self)
.map { return $0 == "OK" } .map { return $0 == "OK" }
} }
...@@ -74,7 +81,8 @@ extension RedisClient { ...@@ -74,7 +81,8 @@ extension RedisClient {
public func delete(_ keys: [String]) -> EventLoopFuture<Int> { public func delete(_ keys: [String]) -> EventLoopFuture<Int> {
guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture(0) } guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture(0) }
return send(command: "DEL", with: keys) let args = keys.map(RESPValue.init)
return send(command: "DEL", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
...@@ -89,7 +97,11 @@ extension RedisClient { ...@@ -89,7 +97,11 @@ extension RedisClient {
@inlinable @inlinable
public func expire(_ key: String, after timeout: TimeAmount) -> EventLoopFuture<Bool> { public func expire(_ key: String, after timeout: TimeAmount) -> EventLoopFuture<Bool> {
let amount = timeout.nanoseconds / 1_000_000_000 let amount = timeout.nanoseconds / 1_000_000_000
return send(command: "EXPIRE", with: [key, amount]) let args: [RESPValue] = [
.init(bulk: key),
.init(bulk: amount.description)
]
return send(command: "EXPIRE", with: args)
.convertFromRESPValue(to: Int.self) .convertFromRESPValue(to: Int.self)
.map { return $0 == 1 } .map { return $0 == 1 }
} }
...@@ -116,7 +128,7 @@ extension RedisClient { ...@@ -116,7 +128,7 @@ extension RedisClient {
} }
@usableFromInline @usableFromInline
func _scan<T>( internal func _scan<T>(
command: String, command: String,
resultType: T.Type = T.self, resultType: T.Type = T.self,
_ key: String?, _ key: String?,
...@@ -127,19 +139,19 @@ extension RedisClient { ...@@ -127,19 +139,19 @@ extension RedisClient {
where where
T: RESPValueConvertible T: RESPValueConvertible
{ {
var args: [RESPValueConvertible] = [pos] var args: [RESPValue] = [.init(bulk: pos)]
if let k = key { if let k = key {
args.insert(k, at: 0) args.insert(.init(bulk: k), at: 0)
} }
if let m = match { if let m = match {
args.append("match") args.append(.init(bulk: "match"))
args.append(m) args.append(.init(bulk: m))
} }
if let c = count { if let c = count {
args.append("count") args.append(.init(bulk: "count"))
args.append(c) args.append(.init(bulk: c))
} }
let response = send(command: command, with: args).convertFromRESPValue(to: [RESPValue].self) let response = send(command: command, with: args).convertFromRESPValue(to: [RESPValue].self)
......
...@@ -18,7 +18,7 @@ import NIO ...@@ -18,7 +18,7 @@ import NIO
extension RedisClient { extension RedisClient {
@usableFromInline @usableFromInline
static func _mapHashResponse(_ values: [String]) -> [String: String] { internal static func _mapHashResponse(_ values: [String]) -> [String: String] {
guard values.count > 0 else { return [:] } guard values.count > 0 else { return [:] }
var result: [String: String] = [:] var result: [String: String] = [:]
...@@ -48,8 +48,11 @@ extension RedisClient { ...@@ -48,8 +48,11 @@ extension RedisClient {
@inlinable @inlinable
public func hdel(_ fields: [String], from key: String) -> EventLoopFuture<Int> { public func hdel(_ fields: [String], from key: String) -> EventLoopFuture<Int> {
guard fields.count > 0 else { return self.eventLoop.makeSucceededFuture(0) } guard fields.count > 0 else { return self.eventLoop.makeSucceededFuture(0) }
var args: [RESPValue] = [.init(bulk: key)]
args.append(convertingContentsOf: fields)
return send(command: "HDEL", with: [key] + fields) return send(command: "HDEL", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
...@@ -62,7 +65,11 @@ extension RedisClient { ...@@ -62,7 +65,11 @@ extension RedisClient {
/// - Returns: `true` if the hash contains the field, `false` if either the key or field do not exist. /// - Returns: `true` if the hash contains the field, `false` if either the key or field do not exist.
@inlinable @inlinable
public func hexists(_ field: String, in key: String) -> EventLoopFuture<Bool> { public func hexists(_ field: String, in key: String) -> EventLoopFuture<Bool> {
return send(command: "HEXISTS", with: [key, field]) let args: [RESPValue] = [
.init(bulk: key),
.init(bulk: field)
]
return send(command: "HEXISTS", with: args)
.convertFromRESPValue(to: Int.self) .convertFromRESPValue(to: Int.self)
.map { return $0 == 1 } .map { return $0 == 1 }
} }
...@@ -74,7 +81,8 @@ extension RedisClient { ...@@ -74,7 +81,8 @@ extension RedisClient {
/// - Returns: The number of fields in the hash, or `0` if the key doesn't exist. /// - Returns: The number of fields in the hash, or `0` if the key doesn't exist.
@inlinable @inlinable
public func hlen(of key: String) -> EventLoopFuture<Int> { public func hlen(of key: String) -> EventLoopFuture<Int> {
return send(command: "HLEN", with: [key]) let args = [RESPValue(bulk: key)]
return send(command: "HLEN", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
...@@ -87,7 +95,11 @@ extension RedisClient { ...@@ -87,7 +95,11 @@ extension RedisClient {
/// - Returns: The string length of the hash field's value, or `0` if the field or hash do not exist. /// - Returns: The string length of the hash field's value, or `0` if the field or hash do not exist.
@inlinable @inlinable
public func hstrlen(of field: String, in key: String) -> EventLoopFuture<Int> { public func hstrlen(of field: String, in key: String) -> EventLoopFuture<Int> {
return send(command: "HSTRLEN", with: [key, field]) let args: [RESPValue] = [
.init(bulk: key),
.init(bulk: field)
]
return send(command: "HSTRLEN", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
...@@ -98,7 +110,8 @@ extension RedisClient { ...@@ -98,7 +110,8 @@ extension RedisClient {
/// - Returns: A list of field names stored within the hash. /// - Returns: A list of field names stored within the hash.
@inlinable @inlinable
public func hkeys(in key: String) -> EventLoopFuture<[String]> { public func hkeys(in key: String) -> EventLoopFuture<[String]> {
return send(command: "HKEYS", with: [key]) let args = [RESPValue(bulk: key)]
return send(command: "HKEYS", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
...@@ -109,7 +122,8 @@ extension RedisClient { ...@@ -109,7 +122,8 @@ extension RedisClient {
/// - Returns: A list of all values stored in a hash. /// - Returns: A list of all values stored in a hash.
@inlinable @inlinable
public func hvals(in key: String) -> EventLoopFuture<[RESPValue]> { public func hvals(in key: String) -> EventLoopFuture<[RESPValue]> {
return send(command: "HVALS", with: [key]) let args = [RESPValue(bulk: key)]
return send(command: "HVALS", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
...@@ -150,12 +164,17 @@ extension RedisClient { ...@@ -150,12 +164,17 @@ extension RedisClient {
/// - key: The key that holds the hash. /// - key: The key that holds the hash.
/// - Returns: `true` if the hash was created, `false` if it was updated. /// - Returns: `true` if the hash was created, `false` if it was updated.
@inlinable @inlinable
public func hset( public func hset<Value: RESPValueConvertible>(
_ field: String, _ field: String,
to value: RESPValueConvertible, to value: Value,
in key: String in key: String
) -> EventLoopFuture<Bool> { ) -> EventLoopFuture<Bool> {
return send(command: "HSET", with: [key, field, value]) let args: [RESPValue] = [
.init(bulk: key),
.init(bulk: field),
value.convertedToRESPValue()
]
return send(command: "HSET", with: args)
.convertFromRESPValue(to: Int.self) .convertFromRESPValue(to: Int.self)
.map { return $0 == 1 } .map { return $0 == 1 }
} }
...@@ -170,12 +189,17 @@ extension RedisClient { ...@@ -170,12 +189,17 @@ extension RedisClient {
/// - key: The key that holds the hash. /// - key: The key that holds the hash.
/// - Returns: `true` if the hash was created. /// - Returns: `true` if the hash was created.
@inlinable @inlinable
public func hsetnx( public func hsetnx<Value: RESPValueConvertible>(
_ field: String, _ field: String,
to value: RESPValueConvertible, to value: Value,
in key: String in key: String
) -> EventLoopFuture<Bool> { ) -> EventLoopFuture<Bool> {
return send(command: "HSETNX", with: [key, field, value]) let args: [RESPValue] = [
.init(bulk: key),
.init(bulk: field),
value.convertedToRESPValue()
]
return send(command: "HSETNX", with: args)
.convertFromRESPValue(to: Int.self) .convertFromRESPValue(to: Int.self)
.map { return $0 == 1 } .map { return $0 == 1 }
} }
...@@ -188,18 +212,19 @@ extension RedisClient { ...@@ -188,18 +212,19 @@ extension RedisClient {
/// - key: The key that holds the hash. /// - key: The key that holds the hash.
/// - Returns: An `EventLoopFuture` that resolves when the operation has succeeded, or fails with a `RedisError`. /// - Returns: An `EventLoopFuture` that resolves when the operation has succeeded, or fails with a `RedisError`.
@inlinable @inlinable
public func hmset( public func hmset<Value: RESPValueConvertible>(
_ fields: [String: RESPValueConvertible], _ fields: [String: Value],
in key: String in key: String
) -> EventLoopFuture<Void> { ) -> EventLoopFuture<Void> {
assert(fields.count > 0, "At least 1 key-value pair should be specified") assert(fields.count > 0, "At least 1 key-value pair should be specified")
let args: [RESPValueConvertible] = fields.reduce(into: [], { (result, element) in var args: [RESPValue] = [.init(bulk: key)]
result.append(element.key) args.add(contentsOf: fields, overestimatedCountBeingAdded: fields.count * 2) { (array, element) in
result.append(element.value) array.append(.init(bulk: element.key))
}) array.append(element.value.convertedToRESPValue())
}
return send(command: "HMSET", with: [key] + args) return send(command: "HMSET", with: args)
.map { _ in () } .map { _ in () }
} }
} }
...@@ -216,7 +241,11 @@ extension RedisClient { ...@@ -216,7 +241,11 @@ extension RedisClient {
/// - Returns: The value of the hash field, or `nil` if either the key or field does not exist. /// - Returns: The value of the hash field, or `nil` if either the key or field does not exist.
@inlinable @inlinable
public func hget(_ field: String, from key: String) -> EventLoopFuture<String?> { public func hget(_ field: String, from key: String) -> EventLoopFuture<String?> {
return send(command: "HGET", with: [key, field]) let args: [RESPValue] = [
.init(bulk: key),
.init(bulk: field)
]
return send(command: "HGET", with: args)
.map { return String(fromRESP: $0) } .map { return String(fromRESP: $0) }
} }
...@@ -230,8 +259,11 @@ extension RedisClient { ...@@ -230,8 +259,11 @@ extension RedisClient {
@inlinable @inlinable
public func hmget(_ fields: [String], from key: String) -> EventLoopFuture<[String?]> { public func hmget(_ fields: [String], from key: String) -> EventLoopFuture<[String?]> {
guard fields.count > 0 else { return self.eventLoop.makeSucceededFuture([]) } guard fields.count > 0 else { return self.eventLoop.makeSucceededFuture([]) }
var args: [RESPValue] = [.init(bulk: key)]
args.append(convertingContentsOf: fields)
return send(command: "HMGET", with: [key] + fields) return send(command: "HMGET", with: args)
.convertFromRESPValue(to: [RESPValue].self) .convertFromRESPValue(to: [RESPValue].self)
.map { return $0.map(String.init) } .map { return $0.map(String.init) }
} }
...@@ -243,7 +275,8 @@ extension RedisClient { ...@@ -243,7 +275,8 @@ extension RedisClient {
/// - Returns: A key-value pair list of fields and their values. /// - Returns: A key-value pair list of fields and their values.
@inlinable @inlinable
public func hgetall(from key: String) -> EventLoopFuture<[String: String]> { public func hgetall(from key: String) -> EventLoopFuture<[String: String]> {
return send(command: "HGETALL", with: [key]) let args = [RESPValue(bulk: key)]
return send(command: "HGETALL", with: args)
.convertFromRESPValue(to: [String].self) .convertFromRESPValue(to: [String].self)
.map(Self._mapHashResponse) .map(Self._mapHashResponse)
} }
...@@ -262,9 +295,7 @@ extension RedisClient { ...@@ -262,9 +295,7 @@ extension RedisClient {
/// - Returns: The new value of the hash field. /// - Returns: The new value of the hash field.
@inlinable @inlinable
public func hincrby(_ amount: Int, field: String, in key: String) -> EventLoopFuture<Int> { public func hincrby(_ amount: Int, field: String, in key: String) -> EventLoopFuture<Int> {
/// connection.hincrby(20, field: "foo", in: "key") return _hincr(command: "HINCRBY", amount, field, key)
return send(command: "HINCRBY", with: [key, field, amount])
.convertFromRESPValue()
} }
/// Increments a hash field's value and returns the new value. /// Increments a hash field's value and returns the new value.
...@@ -276,12 +307,27 @@ extension RedisClient { ...@@ -276,12 +307,27 @@ extension RedisClient {
/// - key: The key of the hash the field is stored in. /// - key: The key of the hash the field is stored in.
/// - Returns: The new value of the hash field. /// - Returns: The new value of the hash field.
@inlinable @inlinable
public func hincrbyfloat<T>(_ amount: T, field: String, in key: String) -> EventLoopFuture<T> public func hincrbyfloat<Value>(_ amount: Value, field: String, in key: String) -> EventLoopFuture<Value>
where where
T: BinaryFloatingPoint, Value: BinaryFloatingPoint,
T: RESPValueConvertible Value: RESPValueConvertible
{ {
return send(command: "HINCRBYFLOAT", with: [key, field, amount]) return _hincr(command: "HINCRBYFLOAT", amount, field, key)
}
@usableFromInline
internal func _hincr<Value: RESPValueConvertible>(
command: String,
_ amount: Value,
_ field: String,
_ key: String
) -> EventLoopFuture<Value> {
let args: [RESPValue] = [
.init(bulk: key),
.init(bulk: field),
amount.convertedToRESPValue()
]
return send(command: command, with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
} }
...@@ -27,7 +27,8 @@ extension RedisClient { ...@@ -27,7 +27,8 @@ extension RedisClient {
/// - Returns: A list of elements found within the set. /// - Returns: A list of elements found within the set.
@inlinable @inlinable
public func smembers(of key: String) -> EventLoopFuture<[RESPValue]> { public func smembers(of key: String) -> EventLoopFuture<[RESPValue]> {
return send(command: "SMEMBERS", with: [key]) let args = [RESPValue(bulk: key)]
return send(command: "SMEMBERS", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
...@@ -39,8 +40,12 @@ extension RedisClient { ...@@ -39,8 +40,12 @@ extension RedisClient {
/// - key: The key of the set to look in. /// - key: The key of the set to look in.
/// - Returns: `true` if the element is in the set. /// - Returns: `true` if the element is in the set.
@inlinable @inlinable
public func sismember(_ element: RESPValueConvertible, of key: String) -> EventLoopFuture<Bool> { public func sismember<Value: RESPValueConvertible>(_ element: Value, of key: String) -> EventLoopFuture<Bool> {
return send(command: "SISMEMBER", with: [key, element]) let args: [RESPValue] = [
.init(bulk: key),
element.convertedToRESPValue()
]
return send(command: "SISMEMBER", with: args)
.convertFromRESPValue(to: Int.self) .convertFromRESPValue(to: Int.self)
.map { return $0 == 1 } .map { return $0 == 1 }
} }
...@@ -52,7 +57,8 @@ extension RedisClient { ...@@ -52,7 +57,8 @@ extension RedisClient {
/// - Returns: The total count of elements in the set. /// - Returns: The total count of elements in the set.
@inlinable @inlinable
public func scard(of key: String) -> EventLoopFuture<Int> { public func scard(of key: String) -> EventLoopFuture<Int> {
return send(command: "SCARD", with: [key]) let args = [RESPValue(bulk: key)]
return send(command: "SCARD", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
...@@ -64,10 +70,13 @@ extension RedisClient { ...@@ -64,10 +70,13 @@ extension RedisClient {
/// - key: The key of the set to insert into. /// - key: The key of the set to insert into.
/// - Returns: The number of elements that were added to the set. /// - Returns: The number of elements that were added to the set.
@inlinable @inlinable
public func sadd(_ elements: [RESPValueConvertible], to key: String) -> EventLoopFuture<Int> { public func sadd<Value: RESPValueConvertible>(_ elements: [Value], to key: String) -> EventLoopFuture<Int> {
guard elements.count > 0 else { return self.eventLoop.makeSucceededFuture(0) } guard elements.count > 0 else { return self.eventLoop.makeSucceededFuture(0) }
var args: [RESPValue] = [.init(bulk: key)]
args.append(convertingContentsOf: elements)
return send(command: "SADD", with: [key] + elements) return send(command: "SADD", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
...@@ -79,10 +88,13 @@ extension RedisClient { ...@@ -79,10 +88,13 @@ extension RedisClient {
/// - key: The key of the set to remove from. /// - key: The key of the set to remove from.
/// - Returns: The number of elements that were removed from the set. /// - Returns: The number of elements that were removed from the set.
@inlinable @inlinable
public func srem(_ elements: [RESPValueConvertible], from key: String) -> EventLoopFuture<Int> { public func srem<Value: RESPValueConvertible>(_ elements: [Value], from key: String) -> EventLoopFuture<Int> {
guard elements.count > 0 else { return self.eventLoop.makeSucceededFuture(0) } guard elements.count > 0 else { return self.eventLoop.makeSucceededFuture(0) }
return send(command: "SREM", with: [key] + elements) var args: [RESPValue] = [.init(bulk: key)]
args.append(convertingContentsOf: elements)
return send(command: "SREM", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
...@@ -98,8 +110,12 @@ extension RedisClient { ...@@ -98,8 +110,12 @@ extension RedisClient {
assert(count >= 0, "A negative max count is nonsense.") assert(count >= 0, "A negative max count is nonsense.")
guard count > 0 else { return self.eventLoop.makeSucceededFuture([]) } guard count > 0 else { return self.eventLoop.makeSucceededFuture([]) }
return send(command: "SPOP", with: [key, count]) let args: [RESPValue] = [
.init(bulk: key),
.init(bulk: count)
]
return send(command: "SPOP", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
...@@ -118,7 +134,11 @@ extension RedisClient { ...@@ -118,7 +134,11 @@ extension RedisClient {
public func srandmember(from key: String, max count: Int = 1) -> EventLoopFuture<[RESPValue]> { public func srandmember(from key: String, max count: Int = 1) -> EventLoopFuture<[RESPValue]> {
guard count != 0 else { return self.eventLoop.makeSucceededFuture([]) } guard count != 0 else { return self.eventLoop.makeSucceededFuture([]) }
return send(command: "SRANDMEMBER", with: [key, count]) let args: [RESPValue] = [
.init(bulk: key),
.init(bulk: count)
]
return send(command: "SRANDMEMBER", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
...@@ -131,14 +151,19 @@ extension RedisClient { ...@@ -131,14 +151,19 @@ extension RedisClient {
/// - destKey: The key of the destination set. /// - destKey: The key of the destination set.
/// - Returns: `true` if the element was successfully removed from the source set. /// - Returns: `true` if the element was successfully removed from the source set.
@inlinable @inlinable
public func smove( public func smove<Value: RESPValueConvertible>(
_ element: RESPValueConvertible, _ element: Value,
from sourceKey: String, from sourceKey: String,
to destKey: String to destKey: String
) -> EventLoopFuture<Bool> { ) -> EventLoopFuture<Bool> {
guard sourceKey != destKey else { return self.eventLoop.makeSucceededFuture(true) } guard sourceKey != destKey else { return self.eventLoop.makeSucceededFuture(true) }
return send(command: "SMOVE", with: [sourceKey, destKey, element]) let args: [RESPValue] = [
.init(bulk: sourceKey),
.init(bulk: destKey),
element.convertedToRESPValue()
]
return send(command: "SMOVE", with: args)
.convertFromRESPValue() .convertFromRESPValue()
.map { return $0 == 1 } .map { return $0 == 1 }
} }
...@@ -175,7 +200,8 @@ extension RedisClient { ...@@ -175,7 +200,8 @@ extension RedisClient {
public func sdiff(of keys: [String]) -> EventLoopFuture<[RESPValue]> { public func sdiff(of keys: [String]) -> EventLoopFuture<[RESPValue]> {
guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture([]) } guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture([]) }
return send(command: "SDIFF", with: keys) let args = keys.map(RESPValue.init)
return send(command: "SDIFF", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
...@@ -191,7 +217,10 @@ extension RedisClient { ...@@ -191,7 +217,10 @@ extension RedisClient {
public func sdiffstore(as destination: String, sources keys: [String]) -> EventLoopFuture<Int> { public func sdiffstore(as destination: String, sources keys: [String]) -> EventLoopFuture<Int> {
assert(keys.count > 0, "At least 1 key should be provided.") assert(keys.count > 0, "At least 1 key should be provided.")
return send(command: "SDIFFSTORE", with: [destination] + keys) var args: [RESPValue] = [.init(bulk: destination)]
args.append(convertingContentsOf: keys)
return send(command: "SDIFFSTORE", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
} }
...@@ -208,7 +237,8 @@ extension RedisClient { ...@@ -208,7 +237,8 @@ extension RedisClient {
public func sinter(of keys: [String]) -> EventLoopFuture<[RESPValue]> { public func sinter(of keys: [String]) -> EventLoopFuture<[RESPValue]> {
guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture([]) } guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture([]) }
return send(command: "SINTER", with: keys) let args = keys.map(RESPValue.init)
return send(command: "SINTER", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
...@@ -224,7 +254,10 @@ extension RedisClient { ...@@ -224,7 +254,10 @@ extension RedisClient {
public func sinterstore(as destination: String, sources keys: [String]) -> EventLoopFuture<Int> { public func sinterstore(as destination: String, sources keys: [String]) -> EventLoopFuture<Int> {
assert(keys.count > 0, "At least 1 key should be provided.") assert(keys.count > 0, "At least 1 key should be provided.")
return send(command: "SINTERSTORE", with: [destination] + keys) var args: [RESPValue] = [.init(bulk: destination)]
args.append(convertingContentsOf: keys)
return send(command: "SINTERSTORE", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
} }
...@@ -241,7 +274,8 @@ extension RedisClient { ...@@ -241,7 +274,8 @@ extension RedisClient {
public func sunion(of keys: [String]) -> EventLoopFuture<[RESPValue]> { public func sunion(of keys: [String]) -> EventLoopFuture<[RESPValue]> {
guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture([]) } guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture([]) }
return send(command: "SUNION", with: keys) let args = keys.map(RESPValue.init)
return send(command: "SUNION", with: args)
.convertFromRESPValue() .convertFromRESPValue()
} }
...@@ -257,7 +291,10 @@ extension RedisClient { ...@@ -257,7 +291,10 @@ extension RedisClient {
public func sunionstore(as destination: String, sources keys: [String]) -> EventLoopFuture<Int> { public func sunionstore(as destination: String, sources keys: [String]) -> EventLoopFuture<Int> {
assert(keys.count > 0, "At least 1 key should be provided.") assert(keys.count > 0, "At least 1 key should be provided.")
return send(command: "SUNIONSTORE", with: [destination] + keys) var args: [RESPValue] = [.init(bulk: destination)]