Commit 5a35a918 authored by Nathan Harris's avatar Nathan Harris

Add extension to `EventLoopFuture` for `RESPValueConvertible` Values for easier mapping

parent e4e32fd5
......@@ -25,12 +25,7 @@ extension RedisCommandExecutor {
/// - Returns: A future number of keys that were removed.
public func delete(_ keys: String...) -> EventLoopFuture<Int> {
return send(command: "DEL", with: keys)
.flatMapThrowing { res in
guard let count = res.int else {
throw RedisError(identifier: "delete", reason: "Unexpected response: \(res)")
}
return count
}
.mapFromRESP()
}
/// Set a timeout on key. After the timeout has expired, the key will automatically be deleted.
......@@ -42,12 +37,8 @@ extension RedisCommandExecutor {
/// - Returns: A future bool indicating if the expiration was set or not.
public func expire(_ key: String, after deadline: Int) -> EventLoopFuture<Bool> {
return send(command: "EXPIRE", with: [key, deadline.description])
.flatMapThrowing { res in
guard let value = res.int else {
throw RedisError(identifier: "expire", reason: "Unexpected response: \(res)")
}
return value == 1
}
.mapFromRESP(to: Int.self)
.map { return $0 == 1 }
}
/// Get the value of a key.
......@@ -71,38 +62,36 @@ extension RedisCommandExecutor {
}
/// Echos the provided message through the Redis instance.
///
/// See [https://redis.io/commands/echo](https://redis.io/commands/echo)
/// - Parameter message: The message to echo.
/// - Returns: The message sent with the command.
public func echo(_ message: String) -> EventLoopFuture<String> {
return send(command: "ECHO", with: [message])
.flatMapThrowing {
guard let response = $0.string else { throw RedisError.respConversion(to: String.self) }
return response
}
.mapFromRESP()
}
/// Pings the server, which will respond with a message.
///
/// See [https://redis.io/commands/ping](https://redis.io/commands/ping)
/// - Parameter with: The optional message that the server should respond with.
/// - Returns: The provided message or Redis' default response of `"PONG"`.
public func ping(with message: String? = nil) -> EventLoopFuture<String> {
let arg = message != nil ? [message] : []
return send(command: "PING", with: arg)
.flatMapThrowing {
guard let response = $0.string else { throw RedisError.respConversion(to: String.self) }
return response
}
.mapFromRESP()
}
/// Swaps the data of two Redis database by their index ID.
///
/// See [https://redis.io/commands/swapdb](https://redis.io/commands/swapdb)
/// - Parameters:
/// - firstIndex: The index of the first database.
/// - secondIndex: The index of the second database.
/// - Returns: `true` if the swap was successful.
public func swapdb(firstIndex: Int, secondIndex: Int) -> EventLoopFuture<Bool> {
return send(command: "SWAPDB", with: [firstIndex, secondIndex])
.flatMapThrowing {
guard let response = $0.string else { throw RedisError.respConversion(to: String.self) }
return response == "OK"
}
.mapFromRESP(to: String.self)
.map { return $0 == "OK" }
}
}
......@@ -19,10 +19,8 @@ extension RedisCommandExecutor {
/// - Parameter item: The element to look in the set for, stored as a `bulkString`.
public func sismember(_ key: String, item: RESPValueConvertible) -> EventLoopFuture<Bool> {
return send(command: "SISMEMBER", with: [key, item])
.flatMapThrowing {
guard let result = $0.int else { throw RedisError.respConversion(to: Int.self) }
return result == 1
}
.mapFromRESP(to: Int.self)
.map { return $0 == 1 }
}
/// Returns the total count of elements in the set stored at key.
......@@ -30,10 +28,7 @@ extension RedisCommandExecutor {
/// [https://redis.io/commands/scard](https://redis.io/commands/scard)
public func scard(_ key: String) -> EventLoopFuture<Int> {
return send(command: "SCARD", with: [key])
.flatMapThrowing {
guard let count = $0.int else { throw RedisError.respConversion(to: Int.self) }
return count
}
.mapFromRESP()
}
/// Adds the provided items to the set stored at key, returning the count of items added.
......@@ -44,10 +39,7 @@ extension RedisCommandExecutor {
assert(items.count > 0, "There must be at least 1 item to add.")
return send(command: "SADD", with: [key] + items)
.flatMapThrowing {
guard let result = $0.int else { throw RedisError.respConversion(to: Int.self) }
return result
}
.mapFromRESP()
}
/// Removes the provided items from the set stored at key, returning the count of items removed.
......@@ -58,10 +50,7 @@ extension RedisCommandExecutor {
assert(items.count > 0, "There must be at least 1 item listed to remove.")
return send(command: "SREM", with: [key] + items)
.flatMapThrowing {
guard let result = $0.int else { throw RedisError.respConversion(to: Int.self) }
return result
}
.mapFromRESP()
}
/// Randomly selects an item from the set stored at key, and removes it.
......@@ -90,10 +79,7 @@ extension RedisCommandExecutor {
/// [https://redis.io/commands/sdiff](https://redis.io/commands/sdiff)
public func sdiff(_ keys: String...) -> EventLoopFuture<[RESPValue]> {
return send(command: "SDIFF", with: keys)
.flatMapThrowing {
guard let elements = $0.array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
return elements
}
.mapFromRESP()
}
/// Functionally equivalent to `sdiff`, but instead stores the resulting set at the `destination` key
......@@ -103,10 +89,7 @@ extension RedisCommandExecutor {
/// - Important: If the `destination` key already exists, it is overwritten.
public func sdiffstore(destination dest: String, _ keys: String...) -> EventLoopFuture<Int> {
return send(command: "SDIFFSTORE", with: [dest] + keys)
.flatMapThrowing {
guard let count = $0.int else { throw RedisError.respConversion(to: Int.self) }
return count
}
.mapFromRESP()
}
/// Returns the members of the set resulting from the intersection of all the given sets.
......@@ -114,10 +97,7 @@ extension RedisCommandExecutor {
/// [https://redis.io/commands/sinter](https://redis.io/commands/sinter)
public func sinter(_ keys: String...) -> EventLoopFuture<[RESPValue]> {
return send(command: "SINTER", with: keys)
.flatMapThrowing {
guard let elements = $0.array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
return elements
}
.mapFromRESP()
}
/// Functionally equivalent to `sinter`, but instead stores the resulting set at the `destination` key
......@@ -127,10 +107,7 @@ extension RedisCommandExecutor {
/// - Important: If the `destination` key already exists, it is overwritten.
public func sinterstore(destination dest: String, _ keys: String...) -> EventLoopFuture<Int> {
return send(command: "SINTERSTORE", with: [dest] + keys)
.flatMapThrowing {
guard let count = $0.int else { throw RedisError.respConversion(to: Int.self) }
return count
}
.mapFromRESP()
}
/// Moves the `item` from the source key to the destination key.
......@@ -139,10 +116,8 @@ extension RedisCommandExecutor {
/// - Important: This will resolve to `true` as long as it was successfully removed from the `source` key.
public func smove(item: RESPValueConvertible, fromKey source: String, toKey dest: String) -> EventLoopFuture<Bool> {
return send(command: "SMOVE", with: [source, dest, item])
.flatMapThrowing {
guard let result = $0.int else { throw RedisError.respConversion(to: Int.self) }
return result == 1
}
.mapFromRESP()
.map { return $0 == 1 }
}
/// Returns the members of the set resulting from the union of all the given keys.
......@@ -150,10 +125,7 @@ extension RedisCommandExecutor {
/// [https://redis.io/commands/sunion](https://redis.io/commands/sunion)
public func sunion(_ keys: String...) -> EventLoopFuture<[RESPValue]> {
return send(command: "SUNION", with: keys)
.flatMapThrowing {
guard let elements = $0.array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
return elements
}
.mapFromRESP()
}
/// Functionally equivalent to `sunion`, but instead stores the resulting set at the `destination` key
......@@ -163,10 +135,7 @@ extension RedisCommandExecutor {
/// - Important: If the `destination` key already exists, it is overwritten.
public func sunionstore(destination dest: String, _ keys: String...) -> EventLoopFuture<Int> {
return send(command: "SUNIONSTORE", with: [dest] + keys)
.flatMapThrowing {
guard let count = $0.int else { throw RedisError.respConversion(to: Int.self) }
return count
}
.mapFromRESP()
}
/// Incrementally iterates over a set, returning a cursor position for additional calls with a limited collection
......@@ -193,16 +162,18 @@ extension RedisCommandExecutor {
args.append(c)
}
return send(command: "SSCAN", with: args)
.flatMapThrowing {
guard let response = $0.array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
guard
let position = response[0].string,
let newPosition = Int(position)
else { throw RedisError.respConversion(to: Int.self) }
guard let elements = response[1].array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
return (newPosition, elements)
}
let response = send(command: "SSCAN", with: args).mapFromRESP(to: [RESPValue].self)
let position = response.flatMapThrowing { result -> Int in
guard
let value = result[0].string,
let position = Int(value)
else { throw RedisError(identifier: #function, reason: "Unexpected value in response: \(result[0])") }
return position
}
let elements = response
.map { return $0[1] }
.mapFromRESP(to: [RESPValue].self)
return position.and(elements)
}
}
import NIO
extension EventLoopFuture where Value == RESPValue {
/// Attempts to convert the `RESPValue` to the desired `RESPValueConvertible` type.
/// If the `RESPValueConvertible.init(_:)` returns `nil`, then the `EventLoopFuture` will fail.
/// - Parameter to: The desired type to convert to.
/// - Returns: An `EventLoopFuture` that resolves a value of the desired type.
@inlinable
public func mapFromRESP<T>(
to type: T.Type = T.self,
file: StaticString = #function,
function: StaticString = #function,
line: UInt = #line
) -> EventLoopFuture<T> where T: RESPValueConvertible
{
return self.flatMapThrowing {
guard let value = T($0) else {
throw RedisError(
identifier: #function,
reason: "Failed to convert RESP to \(String(describing: type))",
file: file,
function: function,
line: line
)
}
return value
}
}
}
......@@ -8,14 +8,13 @@ public struct RedisError: CustomDebugStringConvertible, CustomStringConvertible,
public init(
identifier: String,
reason: String,
file: String = #file,
function: String = #function,
line: UInt = #line,
column: UInt = #column
file: StaticString = #file,
function: StaticString = #function,
line: UInt = #line
) {
let name = String(describing: type(of: self))
description = "⚠️ [\(name).\(identifier): \(reason)]"
debugDescription = "⚠️ Redis Error: \(reason)\n- id: \(name).\(identifier)\n\n\(Thread.callStackSymbols)"
debugDescription = "⚠️ Redis Error: \(reason)\n- id: \(name).\(identifier)\n\n\(file): L\(line) - \(function)\n\n\(Thread.callStackSymbols)"
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment