From 76c77f9620038da4401d317f4add7c5e75a8b8c8 Mon Sep 17 00:00:00 2001
From: Nathan Harris <nathan@mordil.info>
Date: Tue, 19 Apr 2022 23:10:59 -0500
Subject: [PATCH 1/3] Update to RediStack Alpha 4

---
 Package.swift                             |  4 +-
 Sources/Redis/Application+Redis.swift     | 37 ++++++++-----
 Sources/Redis/Redis+Cache.swift           |  2 +-
 Sources/Redis/Redis+Concurrency.swift     | 67 ++++++++++++++---------
 Sources/Redis/RedisClient+Codable.swift   | 35 ------------
 Sources/Redis/Request+Redis.swift         | 37 ++++++++-----
 Tests/RedisTests/MultipleRedisTests.swift | 22 ++++----
 Tests/RedisTests/RedisTests.swift         | 34 +++++++-----
 8 files changed, 118 insertions(+), 120 deletions(-)
 delete mode 100644 Sources/Redis/RedisClient+Codable.swift

diff --git a/Package.swift b/Package.swift
index 82f0946..9b0dfa4 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,4 +1,4 @@
-// swift-tools-version:5.2
+// swift-tools-version:5.3
 import PackageDescription
 
 let package = Package(
@@ -10,7 +10,7 @@ let package = Package(
         .library(name: "Redis", targets: ["Redis"])
     ],
     dependencies: [
-        .package(url: "https://gitlab.com/mordil/RediStack.git", from: "1.1.0"),
+        .package(url: "https://gitlab.com/mordil/RediStack.git", .revisionItem("9da5773e7a45f3fc38293ae1d66849a3f9093f56")),
         .package(url: "https://github.com/vapor/vapor.git", from: "4.50.0"),
     ],
     targets: [
diff --git a/Sources/Redis/Application+Redis.swift b/Sources/Redis/Application+Redis.swift
index 62ece9e..49c69eb 100644
--- a/Sources/Redis/Application+Redis.swift
+++ b/Sources/Redis/Application+Redis.swift
@@ -21,9 +21,8 @@ extension Application {
 
 // MARK: RedisClient
 extension Application.Redis: RedisClient {
-    public var eventLoop: EventLoop {
-        self.application.eventLoopGroup.next()
-    }
+    public var eventLoop: EventLoop { self.application.eventLoopGroup.next() }
+    public var defaultLogger: Logger? { self.application.logger }
 
     public func logging(to logger: Logger) -> RedisClient {
         self.application.redis(self.id)
@@ -31,49 +30,57 @@ extension Application.Redis: RedisClient {
             .logging(to: logger)
     }
 
-    public func send(command: String, with arguments: [RESPValue]) -> EventLoopFuture<RESPValue> {
+    public func send<CommandResult>(
+        _ command: RedisCommand<CommandResult>,
+        eventLoop: EventLoop? = nil,
+        logger: Logger? = nil
+    ) -> EventLoopFuture<CommandResult> {
         self.application.redis(self.id)
             .pool(for: self.eventLoop)
             .logging(to: self.application.logger)
-            .send(command: command, with: arguments)
+            .send(command, eventLoop: eventLoop, logger: logger)
     }
     
     public func subscribe(
         to channels: [RedisChannelName],
+        eventLoop: EventLoop? = nil,
+        logger: Logger? = nil,
         messageReceiver receiver: @escaping RedisSubscriptionMessageReceiver,
-        onSubscribe subscribeHandler: RedisSubscriptionChangeHandler?,
-        onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler?
+        onSubscribe subscribeHandler: RedisSubscribeHandler?,
+        onUnsubscribe unsubscribeHandler: RedisUnsubscribeHandler?
     ) -> EventLoopFuture<Void> {
         self.application.redis(self.id)
             .pubsubClient
             .logging(to: self.application.logger)
-            .subscribe(to: channels, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler)
+            .subscribe(to: channels, eventLoop: eventLoop, logger: logger, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler)
     }
     
-    public func unsubscribe(from channels: [RedisChannelName]) -> EventLoopFuture<Void> {
+    public func unsubscribe(from channels: [RedisChannelName], eventLoop: EventLoop? = nil, logger: Logger? = nil) -> EventLoopFuture<Void> {
         self.application.redis(self.id)
             .pubsubClient
             .logging(to: self.application.logger)
-            .unsubscribe(from: channels)
+            .unsubscribe(from: channels, eventLoop: eventLoop, logger: logger)
     }
     
     public func psubscribe(
         to patterns: [String],
+        eventLoop: EventLoop? = nil,
+        logger: Logger? = nil,
         messageReceiver receiver: @escaping RedisSubscriptionMessageReceiver,
-        onSubscribe subscribeHandler: RedisSubscriptionChangeHandler?,
-        onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler?
+        onSubscribe subscribeHandler: RedisSubscribeHandler?,
+        onUnsubscribe unsubscribeHandler: RedisUnsubscribeHandler?
     ) -> EventLoopFuture<Void> {
         self.application.redis(self.id)
             .pubsubClient
             .logging(to: self.application.logger)
-            .psubscribe(to: patterns, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler)
+            .psubscribe(to: patterns, eventLoop: eventLoop, logger: logger, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler)
     }
     
-    public func punsubscribe(from patterns: [String]) -> EventLoopFuture<Void> {
+    public func punsubscribe(from patterns: [String], eventLoop: EventLoop? = nil, logger: Logger? = nil) -> EventLoopFuture<Void> {
         self.application.redis(self.id)
             .pubsubClient
             .logging(to: self.application.logger)
-            .punsubscribe(from: patterns)
+            .punsubscribe(from: patterns, eventLoop: eventLoop, logger: logger)
     }
 }
 
diff --git a/Sources/Redis/Redis+Cache.swift b/Sources/Redis/Redis+Cache.swift
index b53f44a..c739362 100644
--- a/Sources/Redis/Redis+Cache.swift
+++ b/Sources/Redis/Redis+Cache.swift
@@ -82,7 +82,7 @@ private struct RedisCache<CacheEncoder: RedisCacheEncoder, CacheDecoder: RedisCa
             .tryFuture { try self.encoder.encode(value) }
             .flatMap {
                 if let expirationTime = expirationTime {
-                    return self.client.setex(RedisKey(key), to: $0, expirationInSeconds: expirationTime.seconds)
+                    return self.client.send(.setex(RedisKey(key), to: $0, expirationInSeconds: expirationTime.seconds), eventLoop: nil, logger: nil)
                 } else {
                     return self.client.set(RedisKey(key), to: $0)
                 }
diff --git a/Sources/Redis/Redis+Concurrency.swift b/Sources/Redis/Redis+Concurrency.swift
index 904963a..d35982f 100644
--- a/Sources/Redis/Redis+Concurrency.swift
+++ b/Sources/Redis/Redis+Concurrency.swift
@@ -4,52 +4,61 @@ import Vapor
 
 @available(macOS 12, iOS 15, watchOS 8, tvOS 15, *)
 extension Application.Redis {
-    public func send(command: String, with arguments: [RESPValue]) async throws -> RESPValue {
+    public func send<CommandResult>(
+        _ command: RedisCommand<CommandResult>,
+        eventLoop: EventLoop? = nil,
+        logger: Logger? = nil
+    ) async throws -> CommandResult {
         try await self.application.redis(self.id)
             .pool(for: self.eventLoop)
             .logging(to: self.application.logger)
-            .send(command: command, with: arguments).get()
+            .send(command, eventLoop: eventLoop, logger: logger)
+            .get()
     }
     
     public func subscribe(
         to channels: [RedisChannelName],
+        eventLoop: EventLoop? = nil,
+        logger: Logger? = nil,
         messageReceiver receiver: @escaping RedisSubscriptionMessageReceiver,
-        onSubscribe subscribeHandler: RedisSubscriptionChangeHandler?,
-        onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler?
+        onSubscribe subscribeHandler: RedisSubscribeHandler?,
+        onUnsubscribe unsubscribeHandler: RedisUnsubscribeHandler?
     ) async throws {
         try await self.application.redis(self.id)
             .pubsubClient
             .logging(to: self.application.logger)
-            .subscribe(to: channels, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler)
+            .subscribe(to: channels, eventLoop: eventLoop, logger: logger, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler)
             .get()
     }
     
-    public func unsubscribe(from channels: [RedisChannelName]) async throws {
+    public func unsubscribe(from channels: [RedisChannelName], eventLoop: EventLoop?, logger: Logger?) async throws {
         try await self.application.redis(self.id)
             .pubsubClient
             .logging(to: self.application.logger)
-            .unsubscribe(from: channels)
+            .unsubscribe(from: channels, eventLoop: eventLoop, logger: logger)
             .get()
     }
     
     public func psubscribe(
         to patterns: [String],
+        eventLoop: EventLoop? = nil,
+        logger: Logger? = nil,
         messageReceiver receiver: @escaping RedisSubscriptionMessageReceiver,
-        onSubscribe subscribeHandler: RedisSubscriptionChangeHandler?,
-        onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler?
+        onSubscribe subscribeHandler: RedisSubscribeHandler?,
+        onUnsubscribe unsubscribeHandler: RedisUnsubscribeHandler?
     ) async throws {
         try await self.application.redis(self.id)
             .pubsubClient
             .logging(to: self.application.logger)
-            .psubscribe(to: patterns, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler)
+            .psubscribe(to: patterns, eventLoop: eventLoop, logger: logger, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler)
             .get()
     }
     
-    public func punsubscribe(from patterns: [String]) async throws {
+    public func punsubscribe(from patterns: [String], eventLoop: EventLoop? = nil, logger: Logger? = nil) async throws {
         try await self.application.redis(self.id)
             .pubsubClient
             .logging(to: self.application.logger)
-            .punsubscribe(from: patterns)
+            .punsubscribe(from: patterns, eventLoop: eventLoop, logger: logger)
             .get()
     }
 }
@@ -75,59 +84,67 @@ extension RedisClient {
     public func setex<E>(_ key: RedisKey, toJSON entity: E, expirationInSeconds expiration: Int) async throws
         where E: Encodable
     {
-        try await self.setex(key, to: JSONEncoder().encode(entity), expirationInSeconds: expiration).get()
+        try await self.send(.setex(key, to: JSONEncoder().encode(entity), expirationInSeconds: expiration), eventLoop: nil, logger: nil).get()
     }
 }
 
 @available(macOS 12, iOS 15, watchOS 8, tvOS 15, *)
 extension Request.Redis {
-    public func send(command: String, with arguments: [RESPValue]) async throws -> RESPValue {
+    public func send<CommandResult>(
+        _ command: RedisCommand<CommandResult>,
+        eventLoop: EventLoop? = nil,
+        logger: Logger? = nil
+    ) async throws -> CommandResult {
         try await self.request.application.redis(self.id)
             .pool(for: self.eventLoop)
             .logging(to: self.request.logger)
-            .send(command: command, with: arguments)
+            .send(command, eventLoop: eventLoop, logger: logger)
             .get()
     }
     
     public func subscribe(
         to channels: [RedisChannelName],
+        eventLoop: EventLoop? = nil,
+        logger: Logger? = nil,
         messageReceiver receiver: @escaping RedisSubscriptionMessageReceiver,
-        onSubscribe subscribeHandler: RedisSubscriptionChangeHandler?,
-        onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler?
+        onSubscribe subscribeHandler: RedisSubscribeHandler?,
+        onUnsubscribe unsubscribeHandler: RedisUnsubscribeHandler?
     ) async throws {
         try await self.request.application.redis(self.id)
             .pubsubClient
             .logging(to: self.request.logger)
-            .subscribe(to: channels, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler)
+            .subscribe(to: channels, eventLoop: eventLoop, logger: logger, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler)
             .get()
     }
     
-    public func unsubscribe(from channels: [RedisChannelName]) async throws {
+    public func unsubscribe(from channels: [RedisChannelName], eventLoop: EventLoop? = nil, logger: Logger? = nil) async throws {
         try await self.request.application.redis(self.id)
             .pubsubClient
             .logging(to: self.request.logger)
-            .unsubscribe(from: channels)
+            .unsubscribe(from: channels, eventLoop: eventLoop, logger: logger)
             .get()
     }
     
     public func psubscribe(
         to patterns: [String],
+        eventLoop: EventLoop? = nil,
+        logger: Logger? = nil,
         messageReceiver receiver: @escaping RedisSubscriptionMessageReceiver,
-        onSubscribe subscribeHandler: RedisSubscriptionChangeHandler?,
-        onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler?
+        onSubscribe subscribeHandler: RedisSubscribeHandler?,
+        onUnsubscribe unsubscribeHandler: RedisUnsubscribeHandler?
     ) async throws {
         try await self.request.application.redis(self.id)
             .pubsubClient
             .logging(to: self.request.logger)
-            .psubscribe(to: patterns, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler)
+            .psubscribe(to: patterns, eventLoop: eventLoop, logger: logger, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler)
             .get()
     }
     
-    public func punsubscribe(from patterns: [String]) async throws {
+    public func punsubscribe(from patterns: [String], eventLoop: EventLoop? = nil, logger: Logger? = nil) async throws {
         try await self.request.application.redis(self.id)
             .pubsubClient
             .logging(to: self.request.logger)
-            .punsubscribe(from: patterns)
+            .punsubscribe(from: patterns, eventLoop: eventLoop, logger: logger)
             .get()
     }
 }
diff --git a/Sources/Redis/RedisClient+Codable.swift b/Sources/Redis/RedisClient+Codable.swift
deleted file mode 100644
index 21647d4..0000000
--- a/Sources/Redis/RedisClient+Codable.swift
+++ /dev/null
@@ -1,35 +0,0 @@
-import AsyncKit
-import Foundation
-
-extension RedisClient {
-    /// Gets the provided key as a decodable type.
-    public func get<D>(_ key: RedisKey, asJSON type: D.Type) -> EventLoopFuture<D?>
-        where D: Decodable
-    {
-        return self.get(key, as: Data.self).flatMapThrowing { data in
-            return try data.flatMap { try JSONDecoder().decode(D.self, from: $0) }
-        }
-    }
-
-    /// Sets key to an encodable item.
-    public func set<E>(_ key: RedisKey, toJSON entity: E) -> EventLoopFuture<Void>
-        where E: Encodable
-    {
-        do {
-            return try self.set(key, to: JSONEncoder().encode(entity))
-        } catch {
-            return self.eventLoop.makeFailedFuture(error)
-        }
-    }
-    
-    /// Sets key to an encodable item with an expiration time.
-    public func setex<E>(_ key: RedisKey, toJSON entity: E, expirationInSeconds expiration: Int) -> EventLoopFuture<Void>
-        where E: Encodable
-    {
-        do {
-            return try self.setex(key, to: JSONEncoder().encode(entity), expirationInSeconds: expiration)
-        } catch {
-            return self.eventLoop.makeFailedFuture(error)
-        }
-    }
-}
diff --git a/Sources/Redis/Request+Redis.swift b/Sources/Redis/Request+Redis.swift
index e4cbad4..2624f54 100644
--- a/Sources/Redis/Request+Redis.swift
+++ b/Sources/Redis/Request+Redis.swift
@@ -16,9 +16,8 @@ extension Request {
 
 // MARK: RedisClient
 extension Request.Redis: RedisClient {
-    public var eventLoop: EventLoop {
-        self.request.eventLoop
-    }
+    public var eventLoop: EventLoop { self.request.eventLoop }
+    public var defaultLogger: Logger? { self.request.logger }
 
     public func logging(to logger: Logger) -> RedisClient {
         self.request.application.redis(self.id)
@@ -26,49 +25,57 @@ extension Request.Redis: RedisClient {
             .logging(to: logger)
     }
 
-    public func send(command: String, with arguments: [RESPValue]) -> EventLoopFuture<RESPValue> {
+    public func send<CommandResult>(
+        _ command: RedisCommand<CommandResult>,
+        eventLoop: EventLoop? = nil,
+        logger: Logger? = nil
+    ) -> EventLoopFuture<CommandResult> {
         self.request.application.redis(self.id)
             .pool(for: self.eventLoop)
             .logging(to: self.request.logger)
-            .send(command: command, with: arguments)
+            .send(command, eventLoop: eventLoop, logger: logger)
     }
     
     public func subscribe(
         to channels: [RedisChannelName],
+        eventLoop: EventLoop? = nil,
+        logger: Logger? = nil,
         messageReceiver receiver: @escaping RedisSubscriptionMessageReceiver,
-        onSubscribe subscribeHandler: RedisSubscriptionChangeHandler?,
-        onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler?
+        onSubscribe subscribeHandler: RedisSubscribeHandler?,
+        onUnsubscribe unsubscribeHandler: RedisUnsubscribeHandler?
     ) -> EventLoopFuture<Void> {
         self.request.application.redis(self.id)
             .pubsubClient
             .logging(to: self.request.logger)
-            .subscribe(to: channels, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler)
+            .subscribe(to: channels, eventLoop: eventLoop, logger: logger, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler)
     }
     
-    public func unsubscribe(from channels: [RedisChannelName]) -> EventLoopFuture<Void> {
+    public func unsubscribe(from channels: [RedisChannelName], eventLoop: EventLoop? = nil, logger: Logger? = nil) -> EventLoopFuture<Void> {
         self.request.application.redis(self.id)
             .pubsubClient
             .logging(to: self.request.logger)
-            .unsubscribe(from: channels)
+            .unsubscribe(from: channels, eventLoop: eventLoop, logger: logger)
     }
     
     public func psubscribe(
         to patterns: [String],
+        eventLoop: EventLoop? = nil,
+        logger: Logger? = nil,
         messageReceiver receiver: @escaping RedisSubscriptionMessageReceiver,
-        onSubscribe subscribeHandler: RedisSubscriptionChangeHandler?,
-        onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler?
+        onSubscribe subscribeHandler: RedisSubscribeHandler?,
+        onUnsubscribe unsubscribeHandler: RedisUnsubscribeHandler?
     ) -> EventLoopFuture<Void> {
         self.request.application.redis(self.id)
             .pubsubClient
             .logging(to: self.request.logger)
-            .psubscribe(to: patterns, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler)
+            .psubscribe(to: patterns, eventLoop: eventLoop, logger: logger, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler)
     }
     
-    public func punsubscribe(from patterns: [String]) -> EventLoopFuture<Void> {
+    public func punsubscribe(from patterns: [String], eventLoop: EventLoop? = nil, logger: Logger? = nil) -> EventLoopFuture<Void> {
         self.request.application.redis(self.id)
             .pubsubClient
             .logging(to: self.request.logger)
-            .punsubscribe(from: patterns)
+            .punsubscribe(from: patterns, eventLoop: eventLoop, logger: logger)
     }
 }
 
diff --git a/Tests/RedisTests/MultipleRedisTests.swift b/Tests/RedisTests/MultipleRedisTests.swift
index 3824ef5..7835fdf 100644
--- a/Tests/RedisTests/MultipleRedisTests.swift
+++ b/Tests/RedisTests/MultipleRedisTests.swift
@@ -34,11 +34,13 @@ class MultipleRedisTests: XCTestCase {
 
         try app.boot()
 
-        let info1 = try app.redis(.one).send(command: "INFO").wait()
-        XCTAssertContains(info1.string, "redis_version")
+        let infoCommand = RedisCommand<String>(keyword: "INFO", arguments: [])
 
-        let info2 = try app.redis(.two).send(command: "INFO").wait()
-        XCTAssertContains(info2.string, "redis_version")
+        let info1 = try app.redis(.one).send(infoCommand).wait()
+        XCTAssertContains(info1, "redis_version")
+
+        let info2 = try app.redis(.two).send(infoCommand).wait()
+        XCTAssertContains(info2, "redis_version")
     }
 
     func testSetAndGet() throws {
@@ -49,14 +51,10 @@ class MultipleRedisTests: XCTestCase {
         app.redis(.two).configuration = redisConfig2
 
         app.get("test1") { req in
-            req.redis(.one).get("name").map {
-                $0.description
-            }
+            req.redis(.one).get("name").map { $0 ?? "nil" }
         }
         app.get("test2") { req in
-            req.redis(.two).get("name").map {
-                $0.description
-            }
+            req.redis(.two).get("name").map { $0 ?? "nil" }
         }
 
         try app.boot()
@@ -72,7 +70,7 @@ class MultipleRedisTests: XCTestCase {
             XCTAssertEqual(res.body.string, "redis2")
         }
 
-        XCTAssertEqual("redis1", try app.redis(.one).get("name").wait().string)
-        XCTAssertEqual("redis2", try app.redis(.two).get("name").wait().string)
+        XCTAssertEqual("redis1", try app.redis(.one).get("name").wait())
+        XCTAssertEqual("redis2", try app.redis(.two).get("name").wait())
     }
 }
diff --git a/Tests/RedisTests/RedisTests.swift b/Tests/RedisTests/RedisTests.swift
index ff89bfd..2c77049 100644
--- a/Tests/RedisTests/RedisTests.swift
+++ b/Tests/RedisTests/RedisTests.swift
@@ -24,6 +24,11 @@ final class RedisTests: XCTestCase {
 }
 
 // MARK: Core RediStack integration
+
+extension RedisCommand {
+    fileprivate static var info: RedisCommand<String> { .init(keyword: "INFO", arguments: []) }
+}
+
 extension RedisTests {
     func testApplicationRedis() throws {
         let app = Application()
@@ -32,8 +37,9 @@ extension RedisTests {
         app.redis.configuration = redisConfig
         try app.boot()
 
-        let info = try app.redis.send(command: "INFO").wait()
-        XCTAssertContains(info.string, "redis_version")
+
+        let info = try app.redis.send(.info).wait()
+        XCTAssertContains(info, "redis_version")
     }
 
     func testRouteHandlerRedis() throws {
@@ -43,9 +49,7 @@ extension RedisTests {
         app.redis.configuration = redisConfig
 
         app.get("test") { req in
-            req.redis.send(command: "INFO").map {
-                $0.description
-            }
+            req.redis.send(.info)
         }
 
         try app.test(.GET, "test") { res in
@@ -102,11 +106,11 @@ extension RedisTests {
         app.get("test") {
             $0.redis
                 .withBorrowedClient { client in
-                    return client.send(command: "MULTI")
-                        .flatMap { _ in client.send(command: "PING") }
+                    return client.send(RedisCommand<String>(keyword: "MULTI", arguments: []), eventLoop: nil, logger: nil)
+                        .flatMap { _ in client.send(.ping(), eventLoop: nil, logger: nil) }
                         .flatMap { queuedResponse -> EventLoopFuture<RESPValue> in
-                            XCTAssertEqual(queuedResponse.string, "QUEUED")
-                            return client.send(command: "EXEC")
+                            XCTAssertEqual(queuedResponse, "QUEUED")
+                            return client.send(RedisCommand<RESPValue>(keyword: "EXEC", arguments: []), eventLoop: nil, logger: nil)
                         }
                 }
                 .map { result -> [String] in
@@ -129,12 +133,12 @@ extension RedisTests {
 
         let result = try app.redis
             .withBorrowedConnection { client in
-                return client.send(command: "MULTI")
-                  .flatMap { _ in client.send(command: "PING") }
-                  .flatMap { queuedResponse -> EventLoopFuture<RESPValue> in
-                      XCTAssertEqual(queuedResponse.string, "QUEUED")
-                      return client.send(command: "EXEC")
-                  }
+                return client.send(RedisCommand<String>(keyword: "MULTI", arguments: []), eventLoop: nil, logger: nil)
+                    .flatMap { _ in client.send(.ping(), eventLoop: nil, logger: nil) }
+                    .flatMap { queuedResponse -> EventLoopFuture<RESPValue> in
+                        XCTAssertEqual(queuedResponse, "QUEUED")
+                        return client.send(RedisCommand<RESPValue>(keyword: "EXEC", arguments: []), eventLoop: nil, logger: nil)
+                    }
             }
             .map { result -> [String] in
                 guard let response = result.array else { return [] }

From 1bb611e3976f3375b7828d85dbf1da0fa82f0b94 Mon Sep 17 00:00:00 2001
From: Nathan Harris <nathan@mordil.info>
Date: Sun, 13 Nov 2022 14:22:57 -0600
Subject: [PATCH 2/3] [Core] Bump minimum supported Swift version to 5.5

---
 Package.swift | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Package.swift b/Package.swift
index 9b0dfa4..8395b8c 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,4 +1,4 @@
-// swift-tools-version:5.3
+// swift-tools-version:5.5
 import PackageDescription
 
 let package = Package(

From e4b31372f4684ac96cefe991a53bba2da91d26ab Mon Sep 17 00:00:00 2001
From: Nathan Harris <nathan@mordil.info>
Date: Sun, 13 Nov 2022 14:23:56 -0600
Subject: [PATCH 3/3] Update to RediStack Beta 1

---
 Package.swift                          |   2 +-
 Sources/Redis/Redis+Concurrency.swift  |   3 +-
 Sources/Redis/RedisConfiguration.swift | 156 ++++++++++++++-----------
 Sources/Redis/RedisStorage.swift       |   5 +-
 Tests/RedisTests/RedisTests.swift      |   4 +-
 5 files changed, 98 insertions(+), 72 deletions(-)

diff --git a/Package.swift b/Package.swift
index 8395b8c..da2c428 100644
--- a/Package.swift
+++ b/Package.swift
@@ -10,7 +10,7 @@ let package = Package(
         .library(name: "Redis", targets: ["Redis"])
     ],
     dependencies: [
-        .package(url: "https://gitlab.com/mordil/RediStack.git", .revisionItem("9da5773e7a45f3fc38293ae1d66849a3f9093f56")),
+        .package(url: "https://gitlab.com/mordil/RediStack.git", .revisionItem("0465b34ef3f45c45d751100ef572c1afb4b1b50c")),
         .package(url: "https://github.com/vapor/vapor.git", from: "4.50.0"),
     ],
     targets: [
diff --git a/Sources/Redis/Redis+Concurrency.swift b/Sources/Redis/Redis+Concurrency.swift
index d35982f..6873f3d 100644
--- a/Sources/Redis/Redis+Concurrency.swift
+++ b/Sources/Redis/Redis+Concurrency.swift
@@ -1,4 +1,4 @@
-#if compiler(>=5.5) && canImport(_Concurrency)
+#if canImport(_Concurrency)
 import NIOCore
 import Vapor
 
@@ -149,5 +149,4 @@ extension Request.Redis {
     }
 }
 
-
 #endif
diff --git a/Sources/Redis/RedisConfiguration.swift b/Sources/Redis/RedisConfiguration.swift
index 7f76697..79573ba 100644
--- a/Sources/Redis/RedisConfiguration.swift
+++ b/Sources/Redis/RedisConfiguration.swift
@@ -3,43 +3,68 @@
 @_exported import struct NIO.TimeAmount
 import enum NIO.SocketAddress
 
-/// Configuration for connecting to a Redis instance
-public struct RedisConfiguration {
+/// Configuration for connection to one or more Redis instances with Vapor.
+public typealias RedisConfiguration = RedisConnectionPool.Configuration
+
+extension RedisConfiguration {
     public typealias ValidationError = RedisConnection.Configuration.ValidationError
 
-    public var serverAddresses: [SocketAddress]
-    public var password: String?
-    public var database: Int?
-    public var pool: PoolOptions
+    public static var defaultConnectionCountBehavior: RedisConnectionPool.ConnectionCountBehavior {
+        return .strict(maximumConnectionCount: 2, minimumConnectionCount: 0)
+    }
+    public static var defaultRetryStrategy: RedisConnectionPool.PoolConnectionRetryStrategy { .exponentialBackoff() }
+}
 
-    public struct PoolOptions {
-        public var maximumConnectionCount: RedisConnectionPoolSize
-        public var minimumConnectionCount: Int
-        public var connectionBackoffFactor: Float32
-        public var initialConnectionBackoffDelay: TimeAmount
-        public var connectionRetryTimeout: TimeAmount?
+// MARK: Convenience Initializers
 
-        public init(
-            maximumConnectionCount: RedisConnectionPoolSize = .maximumActiveConnections(2),
-            minimumConnectionCount: Int = 0,
-            connectionBackoffFactor: Float32 = 2,
-            initialConnectionBackoffDelay: TimeAmount = .milliseconds(100),
-            connectionRetryTimeout: TimeAmount? = nil
-        ) {
-            self.maximumConnectionCount = maximumConnectionCount
-            self.minimumConnectionCount = minimumConnectionCount
-            self.connectionBackoffFactor = connectionBackoffFactor
-            self.initialConnectionBackoffDelay = initialConnectionBackoffDelay
-            self.connectionRetryTimeout = connectionRetryTimeout
-        }
+extension RedisConfiguration {
+    public init(
+        hostname: String,
+        port: Int = RedisConnection.Configuration.defaultPort,
+        password: String? = nil,
+        database: Int? = nil,
+        connectionCountBehavior: RedisConnectionPool.ConnectionCountBehavior = Self.defaultConnectionCountBehavior,
+        connectionRetryStrategy: RedisConnectionPool.PoolConnectionRetryStrategy = Self.defaultRetryStrategy,
+        poolDefaultLogger: Logger? = nil,
+        connectionDefaultLogger: Logger? = nil
+    ) throws {
+        self.init(
+            initialServerConnectionAddresses: [try .makeAddressResolvingHost(hostname, port: port)],
+            connectionCountBehavior: connectionCountBehavior,
+            connectionConfiguration: .init(
+                initialDatabase: database,
+                password: password,
+                defaultLogger: connectionDefaultLogger
+            ),
+            retryStrategy: connectionRetryStrategy,
+            poolDefaultLogger: poolDefaultLogger
+        )
     }
 
-    public init(url string: String, pool: PoolOptions = .init()) throws {
+    public init(
+        url string: String,
+        connectionCountBehavior: RedisConnectionPool.ConnectionCountBehavior = Self.defaultConnectionCountBehavior,
+        connectionRetryStrategy: RedisConnectionPool.PoolConnectionRetryStrategy = Self.defaultRetryStrategy,
+        poolDefaultLogger: Logger? = nil,
+        connectionDefaultLogger: Logger? = nil
+    ) throws {
         guard let url = URL(string: string) else { throw ValidationError.invalidURLString }
-        try self.init(url: url, pool: pool)
+        try self.init(
+            url: url,
+            connectionCountBehavior: connectionCountBehavior,
+            connectionRetryStrategy: connectionRetryStrategy,
+            poolDefaultLogger: poolDefaultLogger,
+            connectionDefaultLogger: connectionDefaultLogger
+        )
     }
 
-    public init(url: URL, pool: PoolOptions = .init()) throws {
+    public init(
+        url: URL,
+        connectionCountBehavior: RedisConnectionPool.ConnectionCountBehavior = Self.defaultConnectionCountBehavior,
+        connectionRetryStrategy: RedisConnectionPool.PoolConnectionRetryStrategy = Self.defaultRetryStrategy,
+        poolDefaultLogger: Logger? = nil,
+        connectionDefaultLogger: Logger? = nil
+    ) throws {
         guard
             let scheme = url.scheme,
             !scheme.isEmpty
@@ -52,56 +77,57 @@ public struct RedisConfiguration {
             port: url.port ?? RedisConnection.Configuration.defaultPort,
             password: url.password,
             database: Int(url.lastPathComponent),
-            pool: pool
+            connectionCountBehavior: connectionCountBehavior,
+            connectionRetryStrategy: connectionRetryStrategy,
+            poolDefaultLogger: poolDefaultLogger,
+            connectionDefaultLogger: connectionDefaultLogger
         )
     }
 
     public init(
-        hostname: String,
-        port: Int = RedisConnection.Configuration.defaultPort,
+        serverAddresses: [SocketAddress],
         password: String? = nil,
         database: Int? = nil,
-        pool: PoolOptions = .init()
-    ) throws {
-        if database != nil && database! < 0 { throw ValidationError.outOfBoundsDatabaseID }
-
-        try self.init(
-            serverAddresses: [.makeAddressResolvingHost(hostname, port: port)],
-            password: password,
-            database: database,
-            pool: pool
+        connectionCountBehavior: RedisConnectionPool.ConnectionCountBehavior = Self.defaultConnectionCountBehavior,
+        connectionRetryStrategy: RedisConnectionPool.PoolConnectionRetryStrategy = Self.defaultRetryStrategy,
+        poolDefaultLogger: Logger? = nil,
+        connectionDefaultLogger: Logger? = nil
+    ) {
+        self.init(
+            initialServerConnectionAddresses: serverAddresses,
+            connectionCountBehavior: connectionCountBehavior,
+            connectionConfiguration: .init(
+                initialDatabase: database,
+                password: password,
+                defaultLogger: connectionDefaultLogger
+            ),
+            retryStrategy: connectionRetryStrategy,
+            poolDefaultLogger: poolDefaultLogger
         )
     }
+}
 
-    public init(
-        serverAddresses: [SocketAddress],
-        password: String? = nil,
-        database: Int? = nil,
-        pool: PoolOptions = .init()
-    ) throws {
-        self.serverAddresses = serverAddresses
-        self.password = password
-        self.database = database
-        self.pool = pool
+// MARK: Internal Configuration Creation
+
+extension RedisConnectionPool.PoolConnectionConfiguration {
+    internal func logging(to newLogger: Logger) -> Self {
+        return .init(
+            initialDatabase: self.initialDatabase,
+            password: self.password,
+            defaultLogger: newLogger,
+            tcpClient: self.tcpClient
+        )
     }
 }
 
 extension RedisConnectionPool.Configuration {
-    internal init(_ config: RedisConfiguration, defaultLogger: Logger) {
-        self.init(
-            initialServerConnectionAddresses: config.serverAddresses,
-            maximumConnectionCount: config.pool.maximumConnectionCount,
-            connectionFactoryConfiguration: .init(
-                connectionInitialDatabase: config.database,
-                connectionPassword: config.password,
-                connectionDefaultLogger: defaultLogger,
-                tcpClient: nil
-            ),
-            minimumConnectionCount: config.pool.minimumConnectionCount,
-            connectionBackoffFactor: config.pool.connectionBackoffFactor,
-            initialConnectionBackoffDelay: config.pool.initialConnectionBackoffDelay,
-            connectionRetryTimeout: config.pool.connectionRetryTimeout,
-            poolDefaultLogger: defaultLogger
+    internal func logging(to newLogger: Logger) -> Self {
+        return .init(
+            initialServerConnectionAddresses: self.initialConnectionAddresses,
+            connectionCountBehavior: self.connectionCountBehavior,
+            connectionConfiguration: self.connectionConfiguration.logging(to: newLogger),
+            retryStrategy: self.retryStrategy,
+            poolDefaultLogger: newLogger
         )
     }
 }
diff --git a/Sources/Redis/RedisStorage.swift b/Sources/Redis/RedisStorage.swift
index 5d1c637..0b1a501 100644
--- a/Sources/Redis/RedisStorage.swift
+++ b/Sources/Redis/RedisStorage.swift
@@ -1,3 +1,4 @@
+import struct NIOConcurrencyHelpers.NIOLock
 import Vapor
 
 extension Application {
@@ -17,7 +18,7 @@ extension Application {
 }
 
 final class RedisStorage {
-    private var lock: Lock
+    private var lock: NIOLock
     private var configurations: [RedisID: RedisConfiguration]
     fileprivate var pools: [PoolKey: RedisConnectionPool] {
         willSet {
@@ -75,7 +76,7 @@ extension RedisStorage {
                     let newKey: PoolKey = PoolKey(eventLoopKey: eventLoop.key, redisID: redisID)
 
                     let newPool = RedisConnectionPool(
-                        configuration: .init(configuration, defaultLogger: application.logger),
+                        configuration: configuration.logging(to: application.logger),
                         boundEventLoop: eventLoop)
 
                     newPools[newKey] = newPool
diff --git a/Tests/RedisTests/RedisTests.swift b/Tests/RedisTests/RedisTests.swift
index 2c77049..31d7ce1 100644
--- a/Tests/RedisTests/RedisTests.swift
+++ b/Tests/RedisTests/RedisTests.swift
@@ -66,8 +66,8 @@ extension RedisTests {
         
         let redisConfiguration = try RedisConfiguration(url: urlStr!)
         
-        XCTAssertEqual(redisConfiguration.password, "password")
-        XCTAssertEqual(redisConfiguration.database, 0)
+        XCTAssertEqual(redisConfiguration.connectionConfiguration.password, "password")
+        XCTAssertEqual(redisConfiguration.connectionConfiguration.initialDatabase, 0)
     }
 }