Skip to content

Commit 5a35a91

Browse files
committed
Add extension to EventLoopFuture for RESPValueConvertible Values for easier mapping
1 parent e4e32fd commit 5a35a91

File tree

4 files changed

+72
-84
lines changed

4 files changed

+72
-84
lines changed

Sources/NIORedis/Commands/BasicCommands.swift

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,7 @@ extension RedisCommandExecutor {
2525
/// - Returns: A future number of keys that were removed.
2626
public func delete(_ keys: String...) -> EventLoopFuture<Int> {
2727
return send(command: "DEL", with: keys)
28-
.flatMapThrowing { res in
29-
guard let count = res.int else {
30-
throw RedisError(identifier: "delete", reason: "Unexpected response: \(res)")
31-
}
32-
return count
33-
}
28+
.mapFromRESP()
3429
}
3530

3631
/// Set a timeout on key. After the timeout has expired, the key will automatically be deleted.
@@ -42,12 +37,8 @@ extension RedisCommandExecutor {
4237
/// - Returns: A future bool indicating if the expiration was set or not.
4338
public func expire(_ key: String, after deadline: Int) -> EventLoopFuture<Bool> {
4439
return send(command: "EXPIRE", with: [key, deadline.description])
45-
.flatMapThrowing { res in
46-
guard let value = res.int else {
47-
throw RedisError(identifier: "expire", reason: "Unexpected response: \(res)")
48-
}
49-
return value == 1
50-
}
40+
.mapFromRESP(to: Int.self)
41+
.map { return $0 == 1 }
5142
}
5243

5344
/// Get the value of a key.
@@ -71,38 +62,36 @@ extension RedisCommandExecutor {
7162
}
7263

7364
/// Echos the provided message through the Redis instance.
65+
///
66+
/// See [https://redis.io/commands/echo](https://redis.io/commands/echo)
7467
/// - Parameter message: The message to echo.
7568
/// - Returns: The message sent with the command.
7669
public func echo(_ message: String) -> EventLoopFuture<String> {
7770
return send(command: "ECHO", with: [message])
78-
.flatMapThrowing {
79-
guard let response = $0.string else { throw RedisError.respConversion(to: String.self) }
80-
return response
81-
}
71+
.mapFromRESP()
8272
}
8373

8474
/// Pings the server, which will respond with a message.
75+
///
76+
/// See [https://redis.io/commands/ping](https://redis.io/commands/ping)
8577
/// - Parameter with: The optional message that the server should respond with.
8678
/// - Returns: The provided message or Redis' default response of `"PONG"`.
8779
public func ping(with message: String? = nil) -> EventLoopFuture<String> {
8880
let arg = message != nil ? [message] : []
8981
return send(command: "PING", with: arg)
90-
.flatMapThrowing {
91-
guard let response = $0.string else { throw RedisError.respConversion(to: String.self) }
92-
return response
93-
}
82+
.mapFromRESP()
9483
}
9584

9685
/// Swaps the data of two Redis database by their index ID.
86+
///
87+
/// See [https://redis.io/commands/swapdb](https://redis.io/commands/swapdb)
9788
/// - Parameters:
9889
/// - firstIndex: The index of the first database.
9990
/// - secondIndex: The index of the second database.
10091
/// - Returns: `true` if the swap was successful.
10192
public func swapdb(firstIndex: Int, secondIndex: Int) -> EventLoopFuture<Bool> {
10293
return send(command: "SWAPDB", with: [firstIndex, secondIndex])
103-
.flatMapThrowing {
104-
guard let response = $0.string else { throw RedisError.respConversion(to: String.self) }
105-
return response == "OK"
106-
}
94+
.mapFromRESP(to: String.self)
95+
.map { return $0 == "OK" }
10796
}
10897
}

Sources/NIORedis/Commands/SetCommands.swift

Lines changed: 26 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,16 @@ extension RedisCommandExecutor {
1919
/// - Parameter item: The element to look in the set for, stored as a `bulkString`.
2020
public func sismember(_ key: String, item: RESPValueConvertible) -> EventLoopFuture<Bool> {
2121
return send(command: "SISMEMBER", with: [key, item])
22-
.flatMapThrowing {
23-
guard let result = $0.int else { throw RedisError.respConversion(to: Int.self) }
24-
return result == 1
25-
}
22+
.mapFromRESP(to: Int.self)
23+
.map { return $0 == 1 }
2624
}
2725

2826
/// Returns the total count of elements in the set stored at key.
2927
///
3028
/// [https://redis.io/commands/scard](https://redis.io/commands/scard)
3129
public func scard(_ key: String) -> EventLoopFuture<Int> {
3230
return send(command: "SCARD", with: [key])
33-
.flatMapThrowing {
34-
guard let count = $0.int else { throw RedisError.respConversion(to: Int.self) }
35-
return count
36-
}
31+
.mapFromRESP()
3732
}
3833

3934
/// Adds the provided items to the set stored at key, returning the count of items added.
@@ -44,10 +39,7 @@ extension RedisCommandExecutor {
4439
assert(items.count > 0, "There must be at least 1 item to add.")
4540

4641
return send(command: "SADD", with: [key] + items)
47-
.flatMapThrowing {
48-
guard let result = $0.int else { throw RedisError.respConversion(to: Int.self) }
49-
return result
50-
}
42+
.mapFromRESP()
5143
}
5244

5345
/// Removes the provided items from the set stored at key, returning the count of items removed.
@@ -58,10 +50,7 @@ extension RedisCommandExecutor {
5850
assert(items.count > 0, "There must be at least 1 item listed to remove.")
5951

6052
return send(command: "SREM", with: [key] + items)
61-
.flatMapThrowing {
62-
guard let result = $0.int else { throw RedisError.respConversion(to: Int.self) }
63-
return result
64-
}
53+
.mapFromRESP()
6554
}
6655

6756
/// Randomly selects an item from the set stored at key, and removes it.
@@ -90,10 +79,7 @@ extension RedisCommandExecutor {
9079
/// [https://redis.io/commands/sdiff](https://redis.io/commands/sdiff)
9180
public func sdiff(_ keys: String...) -> EventLoopFuture<[RESPValue]> {
9281
return send(command: "SDIFF", with: keys)
93-
.flatMapThrowing {
94-
guard let elements = $0.array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
95-
return elements
96-
}
82+
.mapFromRESP()
9783
}
9884

9985
/// Functionally equivalent to `sdiff`, but instead stores the resulting set at the `destination` key
@@ -103,21 +89,15 @@ extension RedisCommandExecutor {
10389
/// - Important: If the `destination` key already exists, it is overwritten.
10490
public func sdiffstore(destination dest: String, _ keys: String...) -> EventLoopFuture<Int> {
10591
return send(command: "SDIFFSTORE", with: [dest] + keys)
106-
.flatMapThrowing {
107-
guard let count = $0.int else { throw RedisError.respConversion(to: Int.self) }
108-
return count
109-
}
92+
.mapFromRESP()
11093
}
11194

11295
/// Returns the members of the set resulting from the intersection of all the given sets.
11396
///
11497
/// [https://redis.io/commands/sinter](https://redis.io/commands/sinter)
11598
public func sinter(_ keys: String...) -> EventLoopFuture<[RESPValue]> {
11699
return send(command: "SINTER", with: keys)
117-
.flatMapThrowing {
118-
guard let elements = $0.array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
119-
return elements
120-
}
100+
.mapFromRESP()
121101
}
122102

123103
/// Functionally equivalent to `sinter`, but instead stores the resulting set at the `destination` key
@@ -127,10 +107,7 @@ extension RedisCommandExecutor {
127107
/// - Important: If the `destination` key already exists, it is overwritten.
128108
public func sinterstore(destination dest: String, _ keys: String...) -> EventLoopFuture<Int> {
129109
return send(command: "SINTERSTORE", with: [dest] + keys)
130-
.flatMapThrowing {
131-
guard let count = $0.int else { throw RedisError.respConversion(to: Int.self) }
132-
return count
133-
}
110+
.mapFromRESP()
134111
}
135112

136113
/// Moves the `item` from the source key to the destination key.
@@ -139,21 +116,16 @@ extension RedisCommandExecutor {
139116
/// - Important: This will resolve to `true` as long as it was successfully removed from the `source` key.
140117
public func smove(item: RESPValueConvertible, fromKey source: String, toKey dest: String) -> EventLoopFuture<Bool> {
141118
return send(command: "SMOVE", with: [source, dest, item])
142-
.flatMapThrowing {
143-
guard let result = $0.int else { throw RedisError.respConversion(to: Int.self) }
144-
return result == 1
145-
}
119+
.mapFromRESP()
120+
.map { return $0 == 1 }
146121
}
147122

148123
/// Returns the members of the set resulting from the union of all the given keys.
149124
///
150125
/// [https://redis.io/commands/sunion](https://redis.io/commands/sunion)
151126
public func sunion(_ keys: String...) -> EventLoopFuture<[RESPValue]> {
152127
return send(command: "SUNION", with: keys)
153-
.flatMapThrowing {
154-
guard let elements = $0.array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
155-
return elements
156-
}
128+
.mapFromRESP()
157129
}
158130

159131
/// Functionally equivalent to `sunion`, but instead stores the resulting set at the `destination` key
@@ -163,10 +135,7 @@ extension RedisCommandExecutor {
163135
/// - Important: If the `destination` key already exists, it is overwritten.
164136
public func sunionstore(destination dest: String, _ keys: String...) -> EventLoopFuture<Int> {
165137
return send(command: "SUNIONSTORE", with: [dest] + keys)
166-
.flatMapThrowing {
167-
guard let count = $0.int else { throw RedisError.respConversion(to: Int.self) }
168-
return count
169-
}
138+
.mapFromRESP()
170139
}
171140

172141
/// Incrementally iterates over a set, returning a cursor position for additional calls with a limited collection
@@ -193,16 +162,18 @@ extension RedisCommandExecutor {
193162
args.append(c)
194163
}
195164

196-
return send(command: "SSCAN", with: args)
197-
.flatMapThrowing {
198-
guard let response = $0.array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
199-
guard
200-
let position = response[0].string,
201-
let newPosition = Int(position)
202-
else { throw RedisError.respConversion(to: Int.self) }
203-
guard let elements = response[1].array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
204-
205-
return (newPosition, elements)
206-
}
165+
let response = send(command: "SSCAN", with: args).mapFromRESP(to: [RESPValue].self)
166+
let position = response.flatMapThrowing { result -> Int in
167+
guard
168+
let value = result[0].string,
169+
let position = Int(value)
170+
else { throw RedisError(identifier: #function, reason: "Unexpected value in response: \(result[0])") }
171+
return position
172+
}
173+
let elements = response
174+
.map { return $0[1] }
175+
.mapFromRESP(to: [RESPValue].self)
176+
177+
return position.and(elements)
207178
}
208179
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import NIO
2+
3+
extension EventLoopFuture where Value == RESPValue {
4+
/// Attempts to convert the `RESPValue` to the desired `RESPValueConvertible` type.
5+
/// If the `RESPValueConvertible.init(_:)` returns `nil`, then the `EventLoopFuture` will fail.
6+
/// - Parameter to: The desired type to convert to.
7+
/// - Returns: An `EventLoopFuture` that resolves a value of the desired type.
8+
@inlinable
9+
public func mapFromRESP<T>(
10+
to type: T.Type = T.self,
11+
file: StaticString = #function,
12+
function: StaticString = #function,
13+
line: UInt = #line
14+
) -> EventLoopFuture<T> where T: RESPValueConvertible
15+
{
16+
return self.flatMapThrowing {
17+
guard let value = T($0) else {
18+
throw RedisError(
19+
identifier: #function,
20+
reason: "Failed to convert RESP to \(String(describing: type))",
21+
file: file,
22+
function: function,
23+
line: line
24+
)
25+
}
26+
return value
27+
}
28+
}
29+
}

Sources/NIORedis/RedisError.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ public struct RedisError: CustomDebugStringConvertible, CustomStringConvertible,
88
public init(
99
identifier: String,
1010
reason: String,
11-
file: String = #file,
12-
function: String = #function,
13-
line: UInt = #line,
14-
column: UInt = #column
11+
file: StaticString = #file,
12+
function: StaticString = #function,
13+
line: UInt = #line
1514
) {
1615
let name = String(describing: type(of: self))
1716
description = "⚠️ [\(name).\(identifier): \(reason)]"
18-
debugDescription = "⚠️ Redis Error: \(reason)\n- id: \(name).\(identifier)\n\n\(Thread.callStackSymbols)"
17+
debugDescription = "⚠️ Redis Error: \(reason)\n- id: \(name).\(identifier)\n\n\(file): L\(line) - \(function)\n\n\(Thread.callStackSymbols)"
1918
}
2019
}
2120

0 commit comments

Comments
 (0)