mirror of
https://github.com/swift-server/RediStack.git
synced 2026-06-02 07:37:33 +00:00
Add extension to EventLoopFuture for RESPValueConvertible Values for easier mapping
This commit is contained in:
@@ -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) }
|
||||
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 (newPosition, elements)
|
||||
}
|
||||
return position.and(elements)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
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)"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user