Skip to content

Commit b6f53cf

Browse files
authored
Merge pull request #15 from Mordil/redis-protocols
Refactor `RedisConnection` and `RedisPipeline` into protocols.
2 parents 6825451 + 6b30127 commit b6f53cf

9 files changed

+170
-104
lines changed

Sources/NIORedis/Commands/BasicCommands.swift

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
import Foundation
22
import NIO
33

4-
extension RedisConnection {
4+
extension RedisCommandExecutor {
55
/// Select the Redis logical database having the specified zero-based numeric index.
6-
/// New connections always use the database 0.
6+
/// New connections always use the database `0`.
77
///
88
/// [https://redis.io/commands/select](https://redis.io/commands/select)
9-
public func select(_ id: Int) -> EventLoopFuture<Void> {
10-
return command("SELECT", arguments: [RESPValue(bulk: id.description)])
9+
public func select(database id: Int) -> EventLoopFuture<Void> {
10+
return send(command: "SELECT", with: [id.description])
1111
.map { _ in return () }
1212
}
1313

1414
/// Request for authentication in a password-protected Redis server.
1515
///
1616
/// [https://redis.io/commands/auth](https://redis.io/commands/auth)
1717
public func authorize(with password: String) -> EventLoopFuture<Void> {
18-
return command("AUTH", arguments: [RESPValue(bulk: password)])
18+
return send(command: "AUTH", with: [password])
1919
.map { _ in return () }
2020
}
2121

@@ -24,8 +24,7 @@ extension RedisConnection {
2424
/// [https://redis.io/commands/del](https://redis.io/commands/del)
2525
/// - Returns: A future number of keys that were removed.
2626
public func delete(_ keys: String...) -> EventLoopFuture<Int> {
27-
let keyArgs = keys.map { RESPValue(bulk: $0) }
28-
return command("DEL", arguments: keyArgs)
27+
return send(command: "DEL", with: keys)
2928
.flatMapThrowing { res in
3029
guard let count = res.int else {
3130
throw RedisError(identifier: "delete", reason: "Unexpected response: \(res)")
@@ -42,7 +41,7 @@ extension RedisConnection {
4241
/// - after: The lifetime (in seconds) the key will expirate at.
4342
/// - Returns: A future bool indicating if the expiration was set or not.
4443
public func expire(_ key: String, after deadline: Int) -> EventLoopFuture<Bool> {
45-
return command("EXPIRE", arguments: [RESPValue(bulk: key), RESPValue(bulk: deadline.description)])
44+
return send(command: "EXPIRE", with: [key, deadline.description])
4645
.flatMapThrowing { res in
4746
guard let value = res.int else {
4847
throw RedisError(identifier: "expire", reason: "Unexpected response: \(res)")
@@ -57,7 +56,7 @@ extension RedisConnection {
5756
///
5857
/// [https://redis.io/commands/get](https://redis.io/commands/get)
5958
public func get(_ key: String) -> EventLoopFuture<String?> {
60-
return command("GET", arguments: [RESPValue(bulk: key)])
59+
return send(command: "GET", with: [key])
6160
.map { return $0.string }
6261
}
6362

@@ -67,7 +66,7 @@ extension RedisConnection {
6766
///
6867
/// [https://redis.io/commands/set](https://redis.io/commands/set)
6968
public func set(_ key: String, to value: String) -> EventLoopFuture<Void> {
70-
return command("SET", arguments: [RESPValue(bulk: key), RESPValue(bulk: value)])
69+
return send(command: "SET", with: [key, value])
7170
.map { _ in return () }
7271
}
7372

Sources/NIORedis/Commands/SetCommands.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Foundation
22
import NIO
33

4-
extension RedisConnection {
4+
extension RedisCommandExecutor {
55
/// Returns the all of the elements of the set stored at key.
66
///
77
/// Ordering of results are stable between multiple calls of this method to the same set.
@@ -10,7 +10,7 @@ extension RedisConnection {
1010
///
1111
/// [https://redis.io/commands/smembers](https://redis.io/commands/smembers)
1212
public func smembers(_ key: String) -> EventLoopFuture<RESPValue> {
13-
return command("SMEMBERS", arguments: [RESPValue(bulk: key)])
13+
return send(command: "SMEMBERS", with: [key])
1414
}
1515

1616
/// Checks if the provided item is included in the set stored at key.
@@ -29,7 +29,7 @@ extension RedisConnection {
2929
///
3030
/// [https://redis.io/commands/scard](https://redis.io/commands/scard)
3131
public func scard(_ key: String) -> EventLoopFuture<Int> {
32-
return command("SCARD", arguments: [RESPValue(bulk: key)])
32+
return send(command: "SCARD", with: [key])
3333
.flatMapThrowing {
3434
guard let count = $0.int else { throw RedisError.respConversion(to: Int.self) }
3535
return count
@@ -68,7 +68,7 @@ extension RedisConnection {
6868
///
6969
/// [https://redis.io/commands/spop](https://redis.io/commands/spop)
7070
public func spop(_ key: String) -> EventLoopFuture<RESPValue> {
71-
return command("SPOP", arguments: [RESPValue(bulk: key)])
71+
return send(command: "SPOP", with: [key])
7272
}
7373

7474
/// Randomly selects elements from the set stored at string, up to the `count` provided.
@@ -82,14 +82,14 @@ extension RedisConnection {
8282
public func srandmember(_ key: String, max count: Int = 1) -> EventLoopFuture<RESPValue> {
8383
assert(count != 0, "A count of zero is a noop for selecting a random element.")
8484

85-
return command("SRANDMEMBER", arguments: [RESPValue(bulk: key), RESPValue(bulk: count.description)])
85+
return send(command: "SRANDMEMBER", with: [key, count.description])
8686
}
8787

8888
/// Returns the members of the set resulting from the difference between the first set and all the successive sets.
8989
///
9090
/// [https://redis.io/commands/sdiff](https://redis.io/commands/sdiff)
9191
public func sdiff(_ keys: String...) -> EventLoopFuture<[RESPValue]> {
92-
return command("SDIFF", arguments: keys.map(RESPValue.init(bulk:)))
92+
return send(command: "SDIFF", with: keys)
9393
.flatMapThrowing {
9494
guard let elements = $0.array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
9595
return elements
@@ -102,7 +102,7 @@ extension RedisConnection {
102102
/// [https://redis.io/commands/sdiffstore](https://redis.io/commands/sdiffstore)
103103
/// - Important: If the `destination` key already exists, it is overwritten.
104104
public func sdiffstore(destination dest: String, _ keys: String...) -> EventLoopFuture<Int> {
105-
return command("SDIFFSTORE", arguments: [RESPValue(bulk: dest)] + keys.map(RESPValue.init(bulk:)))
105+
return send(command: "SDIFFSTORE", with: [dest] + keys)
106106
.flatMapThrowing {
107107
guard let count = $0.int else { throw RedisError.respConversion(to: Int.self) }
108108
return count
@@ -113,7 +113,7 @@ extension RedisConnection {
113113
///
114114
/// [https://redis.io/commands/sinter](https://redis.io/commands/sinter)
115115
public func sinter(_ keys: String...) -> EventLoopFuture<[RESPValue]> {
116-
return command("SINTER", arguments: keys.map(RESPValue.init(bulk:)))
116+
return send(command: "SINTER", with: keys)
117117
.flatMapThrowing {
118118
guard let elements = $0.array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
119119
return elements
@@ -126,7 +126,7 @@ extension RedisConnection {
126126
/// [https://redis.io/commands/sinterstore](https://redis.io/commands/sinterstore)
127127
/// - Important: If the `destination` key already exists, it is overwritten.
128128
public func sinterstore(destination dest: String, _ keys: String...) -> EventLoopFuture<Int> {
129-
return command("SINTERSTORE", arguments: [RESPValue(bulk: dest)] + keys.map(RESPValue.init(bulk:)))
129+
return send(command: "SINTERSTORE", with: [dest] + keys)
130130
.flatMapThrowing {
131131
guard let count = $0.int else { throw RedisError.respConversion(to: Int.self) }
132132
return count
@@ -149,7 +149,7 @@ extension RedisConnection {
149149
///
150150
/// [https://redis.io/commands/sunion](https://redis.io/commands/sunion)
151151
public func sunion(_ keys: String...) -> EventLoopFuture<[RESPValue]> {
152-
return command("SUNION", arguments: keys.map(RESPValue.init(bulk:)))
152+
return send(command: "SUNION", with: keys)
153153
.flatMapThrowing {
154154
guard let elements = $0.array else { throw RedisError.respConversion(to: Array<RESPValue>.self) }
155155
return elements
@@ -162,7 +162,7 @@ extension RedisConnection {
162162
/// [https://redis.io/commands/sunionstore](https://redis.io/commands/sunionstore)
163163
/// - Important: If the `destination` key already exists, it is overwritten.
164164
public func sunionstore(destination dest: String, _ keys: String...) -> EventLoopFuture<Int> {
165-
return command("SUNIONSTORE", arguments: [RESPValue(bulk: dest)] + keys.map(RESPValue.init(bulk:)))
165+
return send(command: "SUNIONSTORE", with: [dest] + keys)
166166
.flatMapThrowing {
167167
guard let count = $0.int else { throw RedisError.respConversion(to: Int.self) }
168168
return count
Lines changed: 59 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,62 @@
11
import NIO
22
import NIOConcurrencyHelpers
33

4-
/// A connection to a Redis database instance, with the ability to send and receive commands.
4+
/// An object capable of sending commands and receiving responses.
55
///
6-
/// let result = connection.send(command: "GET", arguments: ["my_key"]
6+
/// let executor = ...
7+
/// let result = executor.send(command: "GET", arguments: ["my_key"]
78
/// // result == EventLoopFuture<RESPValue>
89
///
910
/// See [https://redis.io/commands](https://redis.io/commands)
10-
public final class RedisConnection {
11+
public protocol RedisCommandExecutor {
12+
/// The `EventLoop` that this executor operates on.
13+
var eventLoop: EventLoop { get }
14+
15+
/// Sends the desired command with the specified arguments.
16+
/// - Parameters:
17+
/// - command: The command to execute.
18+
/// - arguments: The arguments, if any, to be sent with the command.
19+
/// - Returns: An `EventLoopFuture` that will resolve with the Redis command response.
20+
func send(command: String, with arguments: [RESPValueConvertible]) -> EventLoopFuture<RESPValue>
21+
}
22+
23+
extension RedisCommandExecutor {
24+
/// Sends the desired command without arguments.
25+
/// - Parameter command: The command keyword to execute.
26+
/// - Returns: An `EventLoopFuture` that will resolve with the Redis command response.
27+
func send(command: String) -> EventLoopFuture<RESPValue> {
28+
return self.send(command: command, with: [])
29+
}
30+
}
31+
32+
/// An individual connection to a Redis database instance for executing commands or building `RedisPipeline`s.
33+
///
34+
/// See `RedisCommandExecutor`.
35+
public protocol RedisConnection: AnyObject, RedisCommandExecutor {
1136
/// The `Channel` this connection is associated with.
37+
var channel: Channel { get }
38+
/// Has the connection been closed?
39+
var isClosed: Bool { get }
40+
41+
/// Creates a `RedisPipeline` for executing a batch of commands.
42+
func makePipeline() -> RedisPipeline
43+
44+
/// Closes the connection to Redis.
45+
/// - Returns: An `EventLoopFuture` that resolves when the connection has been closed.
46+
@discardableResult
47+
func close() -> EventLoopFuture<Void>
48+
}
49+
50+
extension RedisConnection {
51+
public var eventLoop: EventLoop { return self.channel.eventLoop }
52+
}
53+
54+
/// A basic `RedisConnection`.
55+
public final class NIORedisConnection: RedisConnection {
56+
/// See `RedisConnection.channel`.
1257
public let channel: Channel
1358

14-
/// Has the connection been closed?
59+
/// See `RedisConnection.isClosed`.
1560
public var isClosed: Bool { return _isClosed.load() }
1661
private var _isClosed = Atomic<Bool>(value: false)
1762

@@ -24,8 +69,7 @@ public final class RedisConnection {
2469
self.channel = channel
2570
}
2671

27-
/// Closes the connection to Redis.
28-
/// - Returns: An `EventLoopFuture` that resolves when the connection has been closed.
72+
/// See `RedisConnection.close()`.
2973
@discardableResult
3074
public func close() -> EventLoopFuture<Void> {
3175
guard !_isClosed.exchange(with: true) else { return channel.eventLoop.makeSucceededFuture(()) }
@@ -38,40 +82,25 @@ public final class RedisConnection {
3882
}
3983
}
4084

41-
/// Sends the desired command with the specified arguments.
42-
/// - Parameters:
43-
/// - command: The command to execute.
44-
/// - arguments: The arguments to be sent with the command.
45-
/// - Returns: An `EventLoopFuture` that will resolve with the Redis command response.
46-
public func send(command: String, with arguments: [RESPValueConvertible] = []) -> EventLoopFuture<RESPValue> {
47-
let args = arguments.map { $0.convertedToRESPValue() }
48-
return self.command(command, arguments: args)
85+
/// See `RedisConnection.makePipeline()`.
86+
public func makePipeline() -> RedisPipeline {
87+
return NIORedisPipeline(channel: channel)
4988
}
5089

51-
/// Invokes a command against Redis with the provided arguments.
52-
/// - Important: Arguments should be stored as `.bulkString`.
53-
/// - Parameters:
54-
/// - command: The command to execute.
55-
/// - arguments: The arguments to be sent with the command.
56-
/// - Returns: An `EventLoopFuture` that will resolve with the Redis command response.
57-
public func command(_ command: String, arguments: [RESPValue] = []) -> EventLoopFuture<RESPValue> {
58-
guard !_isClosed.load() else {
59-
return channel.eventLoop.makeFailedFuture(RedisError.connectionClosed)
60-
}
90+
/// See `RedisCommandExecutor.send(command:with:)`.
91+
public func send(command: String, with arguments: [RESPValueConvertible] = []) -> EventLoopFuture<RESPValue> {
92+
guard !_isClosed.load() else { return channel.eventLoop.makeFailedFuture(RedisError.connectionClosed) }
93+
94+
let args = arguments.map { $0.convertedToRESPValue() }
6195

6296
let promise = channel.eventLoop.makePromise(of: RESPValue.self)
6397
let context = RedisCommandContext(
64-
command: .array([RESPValue(bulk: command)] + arguments),
98+
command: .array([RESPValue(bulk: command)] + args),
6599
promise: promise
66100
)
67101

68102
_ = channel.writeAndFlush(context)
69103

70104
return promise.futureResult
71105
}
72-
73-
/// Creates a `RedisPipeline` for executing a batch of commands.
74-
public func makePipeline() -> RedisPipeline {
75-
return .init(channel: channel)
76-
}
77106
}

Sources/NIORedis/RedisDriver.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ public final class RedisDriver {
5151
hostname: String = "localhost",
5252
port: Int = 6379,
5353
password: String? = nil
54-
) -> EventLoopFuture<RedisConnection> {
54+
) -> EventLoopFuture<NIORedisConnection> {
5555
let bootstrap = ClientBootstrap.makeForRedis(using: eventLoopGroup)
5656

5757
return bootstrap.connect(host: hostname, port: port)
58-
.map { return RedisConnection(channel: $0) }
58+
.map { return NIORedisConnection(channel: $0) }
5959
.flatMap { connection in
6060
guard let pw = password else {
6161
return self.eventLoopGroup.next().makeSucceededFuture(connection)

0 commit comments

Comments
 (0)