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) } }