diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..e832a90f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ +_[One line description of your change]_ + +### Motivation: + +_[Explain here the context, and why you're making that change. What is the problem you're trying to solve.]_ + +### Modifications: + +_[Describe the modifications you've done.]_ + +### Result: + +- Resolves # +- _[After your change, what will change.]_ diff --git a/.github/release.yml b/.github/release.yml index 685efdf3..3b27fb4b 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -15,4 +15,3 @@ changelog: - title: 🚨🚨🚨 Missing Labels - Add labels to the prs listed here and generate the release notes again labels: - "*" - diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 00000000..5a7dcd96 --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,22 @@ +name: PR + +on: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + soundness: + name: Soundness + uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main + with: + license_header_check_project_name: "RediStack" + + unit-tests: + name: Unit tests + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main + with: + linux_exclude_swift_versions: "[{\"swift_version\": \"5.8\"}]" + # since we don't have systemctl, we run the redis server manually in the background + linux_pre_build_command: apt-get update -y && apt-get install redis -y + linux_build_command: bash -c 'nohup redis-server & swift test' + enable_windows_checks: false diff --git a/.github/workflows/pull_request_label.yml b/.github/workflows/pull_request_label.yml new file mode 100644 index 00000000..86f199f3 --- /dev/null +++ b/.github/workflows/pull_request_label.yml @@ -0,0 +1,18 @@ +name: PR label + +on: + pull_request: + types: [labeled, unlabeled, opened, reopened, synchronize] + +jobs: + semver-label-check: + name: Semantic Version label check + runs-on: ubuntu-latest + timeout-minutes: 1 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Check for Semantic Version label + uses: apple/swift-nio/.github/actions/pull_request_semver_label_checker@main diff --git a/.licenseignore b/.licenseignore new file mode 100644 index 00000000..9d3d0aa4 --- /dev/null +++ b/.licenseignore @@ -0,0 +1,38 @@ +.gitignore +**/.gitignore +.licenseignore +.gitattributes +.git-blame-ignore-revs +.mailfilter +.mailmap +.spi.yml +.swift-format +.editorconfig +.github/* +*.md +*.txt +*.yml +*.yaml +*.json +Package.swift +**/Package.swift +Package@-*.swift +**/Package@-*.swift +Package.resolved +**/Package.resolved +Makefile +*.modulemap +**/*.modulemap +**/*.docc/* +*.xcprivacy +**/*.xcprivacy +*.symlink +**/*.symlink +Dockerfile +**/Dockerfile +Snippets/* +dev/git.commit.template +.unacceptablelanguageignore +IntegrationTests/*.sh +scripts/generate_contributors_list.sh +scripts/generate_rediscommandencoder_multi_encode.sh \ No newline at end of file diff --git a/.spi.yml b/.spi.yml index 52f39836..5c3e4776 100644 --- a/.spi.yml +++ b/.spi.yml @@ -1,4 +1,4 @@ version: 1 builder: configs: - - documentation_targets: ['RediStack', 'RedisTypes', 'RediStackTestUtils'] \ No newline at end of file + - documentation_targets: ['RediStack', 'RedisTypes', 'RediStackTestUtils'] diff --git a/.swift-format b/.swift-format new file mode 100644 index 00000000..39344767 --- /dev/null +++ b/.swift-format @@ -0,0 +1,70 @@ +{ + "version" : 1, + "indentation" : { + "spaces" : 4 + }, + "tabWidth" : 4, + "fileScopedDeclarationPrivacy" : { + "accessLevel" : "private" + }, + "spacesAroundRangeFormationOperators" : false, + "indentConditionalCompilationBlocks" : false, + "indentSwitchCaseLabels" : false, + "lineBreakAroundMultilineExpressionChainComponents" : false, + "lineBreakBeforeControlFlowKeywords" : false, + "lineBreakBeforeEachArgument" : true, + "lineBreakBeforeEachGenericRequirement" : true, + "lineLength" : 120, + "maximumBlankLines" : 1, + "respectsExistingLineBreaks" : true, + "prioritizeKeepingFunctionOutputTogether" : true, + "noAssignmentInExpressions" : { + "allowedFunctions" : [ + "XCTAssertNoThrow", + "XCTAssertThrowsError" + ] + }, + "rules" : { + "NoBlockComments" : false, + "DontRepeatTypeInStaticProperties" : false, + "AllPublicDeclarationsHaveDocumentation" : false, + "AlwaysUseLiteralForEmptyCollectionInit" : false, + "AlwaysUseLowerCamelCase" : false, + "AmbiguousTrailingClosureOverload" : true, + "BeginDocumentationCommentWithOneLineSummary" : false, + "DoNotUseSemicolons" : true, + "DontRepeatTypeInStaticProperties" : true, + "FileScopedDeclarationPrivacy" : true, + "FullyIndirectEnum" : true, + "GroupNumericLiterals" : true, + "IdentifiersMustBeASCII" : true, + "NeverForceUnwrap" : false, + "NeverUseForceTry" : false, + "NeverUseImplicitlyUnwrappedOptionals" : false, + "NoAccessLevelOnExtensionDeclaration" : true, + "NoAssignmentInExpressions" : true, + "NoBlockComments" : true, + "NoCasesWithOnlyFallthrough" : true, + "NoEmptyTrailingClosureParentheses" : true, + "NoLabelsInCasePatterns" : true, + "NoLeadingUnderscores" : false, + "NoParensAroundConditions" : true, + "NoVoidReturnOnFunctionSignature" : true, + "OmitExplicitReturns" : true, + "OneCasePerLine" : true, + "OneVariableDeclarationPerLine" : true, + "OnlyOneTrailingClosureArgument" : true, + "OrderedImports" : true, + "ReplaceForEachWithForLoop" : true, + "ReturnVoidInsteadOfEmptyTuple" : true, + "UseEarlyExits" : false, + "UseExplicitNilCheckInConditions" : false, + "UseLetInEveryBoundCaseVariable" : false, + "UseShorthandTypeNames" : true, + "UseSingleLinePropertyGetter" : false, + "UseSynthesizedInitializer" : false, + "UseTripleSlashForDocumentationComments" : true, + "UseWhereClausesInForLoops" : false, + "ValidateDocumentationComments" : false + } + } \ No newline at end of file diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 00000000..52a1770f --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,7 @@ +extends: default + +rules: + line-length: false + document-start: false + truthy: + check-keys: false # Otherwise we get a false positive on GitHub action's `on` key diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b6c0e1b..096d475f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,7 +22,7 @@ When adding a new file to the project, add the following heading to the top of t // // This source file is part of the RediStack open source project // -// Copyright (c) 2019-2020 RediStack project authors +// Copyright (c) 2019-2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -37,7 +37,7 @@ When editing a file, ensure that the copyright header states the year `2019- RESP3Token? { - return try! RESP3Token(consuming: &self.buffer) + try! RESP3Token(consuming: &self.buffer) } } } @@ -194,19 +194,19 @@ public struct RESP3Token: Hashable, Sendable { validated = try buffer.readRESPBooleanSlice() case .some(.blobString), - .some(.verbatimString), - .some(.blobError): + .some(.verbatimString), + .some(.blobError): validated = try buffer.readRESPBlobStringSlice() case .some(.simpleString), - .some(.simpleError): + .some(.simpleError): validated = try buffer.readRESPSimpleStringSlice() case .some(.array), - .some(.push), - .some(.set), - .some(.map), - .some(.attribute): + .some(.push), + .some(.set), + .some(.map), + .some(.attribute): validated = try buffer.readRESPAggregateSlice(depth: depth) case .some(.integer): @@ -514,4 +514,3 @@ extension UInt32 { return value }() } - diff --git a/Sources/RESP3/RESP3TokenDecoder.swift b/Sources/RESP3/RESP3TokenDecoder.swift index f3c03b26..73122dba 100644 --- a/Sources/RESP3/RESP3TokenDecoder.swift +++ b/Sources/RESP3/RESP3TokenDecoder.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2023 RediStack project authors +// Copyright (c) 2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information diff --git a/Sources/RESP3/RESP3TypeIdentifier.swift b/Sources/RESP3/RESP3TypeIdentifier.swift index 8d7bf197..410ea4fe 100644 --- a/Sources/RESP3/RESP3TypeIdentifier.swift +++ b/Sources/RESP3/RESP3TypeIdentifier.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2023 RediStack project authors +// Copyright (c) 2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -13,21 +13,21 @@ //===----------------------------------------------------------------------===// enum RESP3TypeIdentifier: UInt8 { - case integer = 58 // UInt8(ascii: ":") - case double = 44 // UInt8.comma - case simpleString = 43 // UInt8.plus - case simpleError = 45 // UInt8.min - case blobString = 36 // UInt8.dollar - case blobError = 33 // UInt8.exclamationMark - case verbatimString = 61 // UInt8.equals - case boolean = 35 // UInt8.pound - case null = 95 // UInt8.underscore - case bigNumber = 40 // UInt8.leftRoundBracket - case array = 42 // UInt8.asterisk - case map = 37 // UInt8.percent - case set = 126 // UInt8.tilde - case attribute = 124 // UInt8.pipe - case push = 62 // UInt8.rightAngledBracket + case integer = 58 // UInt8(ascii: ":") + case double = 44 // UInt8.comma + case simpleString = 43 // UInt8.plus + case simpleError = 45 // UInt8.min + case blobString = 36 // UInt8.dollar + case blobError = 33 // UInt8.exclamationMark + case verbatimString = 61 // UInt8.equals + case boolean = 35 // UInt8.pound + case null = 95 // UInt8.underscore + case bigNumber = 40 // UInt8.leftRoundBracket + case array = 42 // UInt8.asterisk + case map = 37 // UInt8.percent + case set = 126 // UInt8.tilde + case attribute = 124 // UInt8.pipe + case push = 62 // UInt8.rightAngledBracket } extension UInt8 { diff --git a/Sources/RediStack/ChannelHandlers/RedisByteDecoder.swift b/Sources/RediStack/ChannelHandlers/RedisByteDecoder.swift index 81528c97..cebb11b5 100644 --- a/Sources/RediStack/ChannelHandlers/RedisByteDecoder.swift +++ b/Sources/RediStack/ChannelHandlers/RedisByteDecoder.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019-2022 RediStack project authors +// Copyright (c) 2019-2022 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -21,9 +21,9 @@ import NIOCore public final class RedisByteDecoder: NIOSingleStepByteToMessageDecoder { /// `ByteToMessageDecoder.InboundOut` public typealias InboundOut = RESPValue - + private let parser: RESPTranslator - + public init() { self.parser = RESPTranslator() } diff --git a/Sources/RediStack/ChannelHandlers/RedisCommandHandler.swift b/Sources/RediStack/ChannelHandlers/RedisCommandHandler.swift index 3390ca95..50c77da7 100644 --- a/Sources/RediStack/ChannelHandlers/RedisCommandHandler.swift +++ b/Sources/RediStack/ChannelHandlers/RedisCommandHandler.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019-2020 RediStack project authors +// Copyright (c) 2019-2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -41,7 +41,9 @@ public final class RedisCommandHandler { deinit { if !self.commandResponseQueue.isEmpty { - assertionFailure("Command handler deinit when queue is not empty! Queue size: \(self.commandResponseQueue.count)") + assertionFailure( + "Command handler deinit when queue is not empty! Queue size: \(self.commandResponseQueue.count)" + ) } } @@ -51,7 +53,7 @@ public final class RedisCommandHandler { public init(initialQueueCapacity: Int = 3) { self.commandResponseQueue = CircularBuffer(initialCapacity: initialQueueCapacity) } - + private enum State { case `default` case draining(EventLoopPromise?) @@ -74,7 +76,7 @@ extension RedisCommandHandler: ChannelInboundHandler { self._failCommandQueue(because: error) context.close(promise: nil) } - + /// Invoked by SwiftNIO when the channel's active state has changed, such as when it is closed. The command queue will be drained /// with each promise in the queue being failed from a connection closed error. /// @@ -84,12 +86,14 @@ extension RedisCommandHandler: ChannelInboundHandler { self.state = .error(RedisClientError.connectionClosed) self._failCommandQueue(because: RedisClientError.connectionClosed) } - + private func _failCommandQueue(because error: Error) { self.state = .error(error) let queue = self.commandResponseQueue self.commandResponseQueue.removeAll() - queue.forEach { $0.fail(error) } + for element in queue { + element.fail(error) + } } /// Invoked by SwiftNIO when a read has been fired from earlier in the response chain. @@ -141,14 +145,14 @@ extension RedisCommandHandler: ChannelOutboundHandler { /// See `NIO.ChannelOutboundHandler.write(context:data:promise:)` public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise?) { let commandContext = self.unwrapOutboundIn(data) - + switch self.state { case let .error(e): commandContext.responsePromise.fail(e) case .draining: commandContext.responsePromise.fail(RedisClientError.connectionClosed) - + case .default: self.commandResponseQueue.append(commandContext.responsePromise) context.write( diff --git a/Sources/RediStack/ChannelHandlers/RedisMessageEncoder.swift b/Sources/RediStack/ChannelHandlers/RedisMessageEncoder.swift index 57d535b1..d0f01ab4 100644 --- a/Sources/RediStack/ChannelHandlers/RedisMessageEncoder.swift +++ b/Sources/RediStack/ChannelHandlers/RedisMessageEncoder.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -17,7 +17,7 @@ import NIOCore #if DEBUG // used only for debugging purposes where we build a formatted string for the encoded bytes private func getPrintableString(for buffer: inout ByteBuffer) -> String { - return String(describing: buffer.getString(at: 0, length: buffer.readableBytes)) + String(describing: buffer.getString(at: 0, length: buffer.readableBytes)) .dropFirst(9) .dropLast() .description @@ -31,7 +31,7 @@ public final class RedisMessageEncoder: MessageToByteEncoder { /// See `MessageToByteEncoder.OutboundIn` public typealias OutboundIn = RESPValue - public init() { } + public init() {} /// Encodes the `RedisValue` to bytes, following the RESP specification. /// diff --git a/Sources/RediStack/ChannelHandlers/RedisPubSubHandler.swift b/Sources/RediStack/ChannelHandlers/RedisPubSubHandler.swift index 24ec7a0e..f27044b8 100644 --- a/Sources/RediStack/ChannelHandlers/RedisPubSubHandler.swift +++ b/Sources/RediStack/ChannelHandlers/RedisPubSubHandler.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020 RediStack project authors +// Copyright (c) 2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -52,7 +52,7 @@ public enum RedisSubscriptionTarget: Equatable, CustomDebugStringConvertible { case let .patterns(values): return values } } - + public var debugDescription: String { let values = self.values.joined(separator: ", ") switch self { @@ -60,8 +60,8 @@ public enum RedisSubscriptionTarget: Equatable, CustomDebugStringConvertible { case .patterns: return "Patterns '\(values)'" } } - - public static func ==(lhs: RedisSubscriptionTarget, rhs: RedisSubscriptionTarget) -> Bool { + + public static func == (lhs: RedisSubscriptionTarget, rhs: RedisSubscriptionTarget) -> Bool { switch (lhs, rhs) { case let (.channels(left), .channels(right)): return left == right case let (.patterns(left), .patterns(right)): return left == right @@ -109,12 +109,12 @@ public final class RedisPubSubHandler { private var pendingSubscribes: PendingSubscriptionChangeQueue /// A queue of unsubscribe changes awaiting notification of completion. private var pendingUnsubscribes: PendingSubscriptionChangeQueue - + private let eventLoop: EventLoop - + // we need to be extra careful not to use this context before we know we've initialized private var context: ChannelHandlerContext! - + /// - Parameters: /// - eventLoop: The event loop the `NIO.Channel` that this handler was added to is bound to. /// - queueCapacity: The initial capacity of queues used for processing subscription changes. The initial value is `3`. @@ -126,7 +126,7 @@ public final class RedisPubSubHandler { self.subscriptions = [:] self.pendingSubscribes = [:] self.pendingUnsubscribes = [:] - + self.pendingSubscribes.reserveCapacity(queueCapacity) self.pendingUnsubscribes.reserveCapacity(queueCapacity) } @@ -141,18 +141,18 @@ extension RedisPubSubHandler { keyPrefix: String ) { let prefixedKey = self.prefixKey(subscriptionKey, with: keyPrefix) - + defer { self.pendingSubscribes.removeValue(forKey: prefixedKey)?.succeed(subscriptionCount) } - + guard let subscription = self.subscriptions[prefixedKey] else { return } subscription.onSubscribe?(subscriptionKey, subscriptionCount) - subscription.onSubscribe = nil // nil to free memory + subscription.onSubscribe = nil // nil to free memory self.subscriptions[prefixedKey] = subscription - + subscription.type.gauge.increment() } - + private func handleUnsubscribeMessage( withSubscriptionKey subscriptionKey: String, reportedSubscriptionCount subscriptionCount: Int, @@ -161,7 +161,7 @@ extension RedisPubSubHandler { ) { let prefixedKey = self.prefixKey(subscriptionKey, with: keyPrefix) guard let subscription = self.subscriptions.removeValue(forKey: prefixedKey) else { return } - + subscription.onUnsubscribe?(subscriptionKey, subscriptionCount) subscription.type.gauge.decrement() @@ -169,7 +169,7 @@ extension RedisPubSubHandler { // we found a specific pattern/channel was being removed, so just fulfill the notification case let .some(promise): promise.succeed(subscriptionCount) - + // if one wasn't found, this means a [p]unsubscribe all was issued case .none: // and we want to wait for the subscription count to be 0 before we resolve it's notification @@ -181,7 +181,7 @@ extension RedisPubSubHandler { self.pendingUnsubscribes.removeValue(forKey: unsubscribeFromAllKey)?.succeed(subscriptionCount) } } - + private func handleMessage( _ message: RESPValue, from channel: RedisChannelName, @@ -204,7 +204,8 @@ extension RedisPubSubHandler { /// - receiver: The closure that receives any future pub/sub messages. /// - subscribeHandler: An optional closure to invoke when the subscription first becomes active. /// - unsubscribeHandler: An optional closure to invoke when the subscription becomes inactive. - /// - Returns: A `NIO.EventLoopFuture` that resolves the number of subscriptions the client has after the subscription has been added. + /// - Returns: A `NIO.EventLoopFuture` that resolves the number of subscriptions the client has after the + /// subscription has been added. public func addSubscription( for target: RedisSubscriptionTarget, messageReceiver receiver: @escaping RedisSubscriptionMessageReceiver, @@ -213,7 +214,7 @@ extension RedisPubSubHandler { ) -> EventLoopFuture { guard self.eventLoop.inEventLoop else { return self.eventLoop.flatSubmit { - return self.addSubscription( + self.addSubscription( for: target, messageReceiver: receiver, onSubscribe: subscribeHandler, @@ -226,9 +227,10 @@ extension RedisPubSubHandler { case .removed: return self.eventLoop.makeFailedFuture(RedisClientError.subscriptionModeRaceCondition) case let .error(e): return self.eventLoop.makeFailedFuture(e) - + case .default: - // go through all the target patterns/names and update the map with the new receiver if it's already registered + // go through all the target patterns/names and update the map with the new receiver + // if it's already registered // if it was a new registration, not an update, we keep that name to send to Redis // we do this so that we save on data transfer bandwidth @@ -280,7 +282,7 @@ extension RedisPubSubHandler { guard !target.values.isEmpty else { return self.unsubscribeAll(for: target) } - + return self.sendSubscriptionChange( subscriptionChangeKeyword: target.unsubscribeKeyword, subscriptionTargets: target.values, @@ -288,7 +290,7 @@ extension RedisPubSubHandler { keyPrefix: target.keyPrefix ) } - + private func sendSubscriptionChange( subscriptionChangeKeyword keyword: String, subscriptionTargets targets: [String], @@ -296,10 +298,10 @@ extension RedisPubSubHandler { keyPrefix: String ) -> EventLoopFuture { self.eventLoop.assertInEventLoop() - + var command = [RESPValue(bulk: keyword)] command.append(convertingContentsOf: targets) - + // the command does not respond in a normal command response fashion of the end count of subscriptions // after all of them have been established (or removed) // @@ -307,13 +309,16 @@ extension RedisPubSubHandler { // // so we have to create a top-level future that synchronizes all of the responses // where we take the last response from Redis as the count of active subscriptions - + // create them let pendingSubscriptions: [(String, EventLoopPromise)] = targets.map { - return (self.prefixKey($0, with: keyPrefix), self.eventLoop.makePromise()) + (self.prefixKey($0, with: keyPrefix), self.eventLoop.makePromise()) + } + // add the subscription change handler to the appropriate queue + // for each individual subscription target + for (key, value) in pendingSubscriptions { + self[keyPath: pendingQueue].updateValue(value, forKey: key) } - // add the subscription change handler to the appropriate queue for each individual subscription target - pendingSubscriptions.forEach { self[keyPath: pendingQueue].updateValue($1, forKey: $0) } // synchronize all of the individual subscription changes let subscriptionCountFuture = EventLoopFuture @@ -322,22 +327,30 @@ extension RedisPubSubHandler { on: self.eventLoop ) .flatMapThrowing { (results) -> Int in - // trust the last success response as the most current count - guard let latestSubscriptionCount = results + /* + if we have no success cases, we will still + have at least one response that we can rely on the 'get' + method to throw the error for us, rather than unwrapping it + ourselves + */ + + let latestSubscriptionCount = results .lazy - .reversed() // reverse to save time-complexity, as we just need the last (first) successful value + .reversed() .compactMap({ try? $0.get() }) .first - // if we have no success cases, we will still have at least one response that we can - // rely on the 'get' method to throw the error for us, rather than unwrapping it ourselves - else { return try results.first!.get() } + + // trust the last success response as the most current count + guard let latestSubscriptionCount else { + return try results.first!.get() + } return latestSubscriptionCount } - + return self.context .writeAndFlush(self.wrapOutboundOut(.array(command))) - .flatMap { return subscriptionCountFuture } + .flatMap { subscriptionCountFuture } } private func unsubscribeAll(for target: RedisSubscriptionTarget) -> EventLoopFuture { @@ -360,7 +373,7 @@ extension RedisPubSubHandler { } public func handlerRemoved(context: ChannelHandlerContext) { - self.context = nil // break ref cycles + self.context = nil // break ref cycles } } @@ -381,7 +394,7 @@ extension RedisPubSubHandler: RemovableChannelHandler { extension RedisPubSubHandler: ChannelInboundHandler { public typealias InboundIn = RESPValue public typealias InboundOut = RESPValue - + public func channelRead(context: ChannelHandlerContext, data: NIOAny) { let value = self.unwrapInboundIn(data) @@ -390,7 +403,8 @@ extension RedisPubSubHandler: ChannelInboundHandler { // if it is, we handle it here // Redis defines the format as [messageKeyword: String, channelName: String, message: RESPValue] - // unless the messageType is 'pmessage', in which case it's [messageKeyword, pattern: String, channelName, message] + // unless the messageType is 'pmessage', in which case it's [messageKeyword, pattern: String, + // channelName, message] // these guards extract some of the basic details of a pubsub message guard @@ -402,11 +416,11 @@ extension RedisPubSubHandler: ChannelInboundHandler { context.fireChannelRead(data) return } - + // safe because the array is guaranteed from the guard above to have at least 3 elements // and it is NOT to be used until we match the PubSub message keyword let message = array.last! - + // the last check is to match one of the known pubsub message keywords // if we have a match, we're definitely in a pubsub message and we should handle it @@ -419,11 +433,11 @@ extension RedisPubSubHandler: ChannelInboundHandler { keyPrefix: kSubscriptionKeyPrefixChannel ) - case "pmessage": self.handleMessage( message, - from: .init(array[2].string!), // the channel name is stored as the 3rd element in the array in 'pmessage' streams + from: .init(array[2].string!), + // the channel name is stored as the 3rd element in the array in 'pmessage' streams withSubscriptionKey: channelOrPattern, keyPrefix: kSubscriptionKeyPrefixPattern ) @@ -436,7 +450,7 @@ extension RedisPubSubHandler: ChannelInboundHandler { reportedSubscriptionCount: message.int!, keyPrefix: kSubscriptionKeyPrefixChannel ) - + case "psubscribe": self.handleSubscribeMessage( withSubscriptionKey: channelOrPattern, @@ -451,7 +465,7 @@ extension RedisPubSubHandler: ChannelInboundHandler { unsubscribeFromAllKey: kUnsubscribeAllChannelsKey, keyPrefix: kSubscriptionKeyPrefixChannel ) - + case "punsubscribe": self.handleUnsubscribeMessage( withSubscriptionKey: channelOrPattern, @@ -459,30 +473,30 @@ extension RedisPubSubHandler: ChannelInboundHandler { unsubscribeFromAllKey: kUnsubscribeAllPatternsKey, keyPrefix: kSubscriptionKeyPrefixPattern ) - + // if we don't have a match, fire a channel read to forward to the next handler default: context.fireChannelRead(data) } } - + public func errorCaught(context: ChannelHandlerContext, error: Error) { self.removeAllReceivers(because: error) context.fireErrorCaught(error) } - + public func channelInactive(context: ChannelHandlerContext) { self.removeAllReceivers(because: RedisClientError.connectionClosed) context.fireChannelInactive() } - + private func removeAllReceivers(because error: Error? = nil) { error.map { self.state = .error($0) } - + let receivers = self.subscriptions self.subscriptions.removeAll() - receivers.forEach { - $0.value.onUnsubscribe?($0.key, 0) - $0.value.type.gauge.decrement() + for receiver in receivers { + receiver.value.onUnsubscribe?(receiver.key, 0) + receiver.value.type.gauge.decrement() } } } @@ -492,7 +506,7 @@ extension RedisPubSubHandler: ChannelInboundHandler { extension RedisPubSubHandler: ChannelOutboundHandler { public typealias OutboundIn = RESPValue public typealias OutboundOut = RESPValue - + // the pub/sub handler is a transparent outbound handler // we only conform to the protocol so we're appropriately placed in the pipeline // to bypass the command handler for pub/sub subscription changes @@ -504,9 +518,9 @@ extension RedisPubSubHandler: ChannelOutboundHandler { private let kUnsubscribeAllChannelsKey = "__RS_ALL_CHS" private let kUnsubscribeAllPatternsKey = "__RS_ALL_PNS" -fileprivate enum SubscriptionType { +private enum SubscriptionType { case channel, pattern - + var gauge: RedisMetrics.IncrementalGauge { switch self { case .channel: return RedisMetrics.activeChannelSubscriptions @@ -521,9 +535,9 @@ extension RedisPubSubHandler { fileprivate final class Subscription { let type: SubscriptionType let onMessage: RedisSubscriptionMessageReceiver - var onSubscribe: RedisSubscriptionChangeHandler? // will be set to nil after first call + var onSubscribe: RedisSubscriptionChangeHandler? // will be set to nil after first call let onUnsubscribe: RedisSubscriptionChangeHandler? - + init( type: SubscriptionType, messageReceiver: @escaping RedisSubscriptionMessageReceiver, @@ -538,7 +552,8 @@ extension RedisPubSubHandler { } private enum State { - case `default`, removed, error(Error) + case `default`, removed + case error(Error) } } @@ -572,7 +587,7 @@ extension RedisSubscriptionTarget { case .patterns: return .pattern } } - + fileprivate var subscribeKeyword: String { switch self { case .channels: return "SUBSCRIBE" diff --git a/Sources/RediStack/Cluster/RedisClusterNodeDescriptionProtocol.swift b/Sources/RediStack/Cluster/RedisClusterNodeDescriptionProtocol.swift index 22390ee9..e7f5ea9a 100644 --- a/Sources/RediStack/Cluster/RedisClusterNodeDescriptionProtocol.swift +++ b/Sources/RediStack/Cluster/RedisClusterNodeDescriptionProtocol.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2023 RediStack project authors +// Copyright (c) 2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -35,7 +35,7 @@ public protocol RedisClusterNodeDescriptionProtocol: Sendable, Equatable { extension RedisClusterNodeDescriptionProtocol { func isSame(_ other: Other) -> Bool { - return self.ip == other.ip + self.ip == other.ip && self.port == other.port && self.endpoint == other.endpoint && self.useTLS == other.useTLS diff --git a/Sources/RediStack/Cluster/RedisClusterNodeID.swift b/Sources/RediStack/Cluster/RedisClusterNodeID.swift index a88df0fa..09a1cf01 100644 --- a/Sources/RediStack/Cluster/RedisClusterNodeID.swift +++ b/Sources/RediStack/Cluster/RedisClusterNodeID.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2023 RediStack project authors +// Copyright (c) 2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information diff --git a/Sources/RediStack/Cluster/RedisClusterShardDescriptionProtocol.swift b/Sources/RediStack/Cluster/RedisClusterShardDescriptionProtocol.swift index 19d637e3..f85e2068 100644 --- a/Sources/RediStack/Cluster/RedisClusterShardDescriptionProtocol.swift +++ b/Sources/RediStack/Cluster/RedisClusterShardDescriptionProtocol.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2023 RediStack project authors +// Copyright (c) 2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -17,8 +17,8 @@ public protocol RedisClusterShardDescriptionProtocol: Identifiable, Sendable { associatedtype NodeDescription: RedisClusterNodeDescriptionProtocol - /// The shard's master node - var master: NodeDescription { get } + /// The shard's primary node + var master: NodeDescription { get } // #ignore-unacceptable-language /// The shard's replica nodes var replicas: [NodeDescription] { get } diff --git a/Sources/RediStack/Cluster/RedisHashSlot.swift b/Sources/RediStack/Cluster/RedisHashSlot.swift index 1f474bc1..6d645ae6 100644 --- a/Sources/RediStack/Cluster/RedisHashSlot.swift +++ b/Sources/RediStack/Cluster/RedisHashSlot.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2023 RediStack project authors +// Copyright (c) 2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -93,7 +93,7 @@ extension RedisHashSlot: CustomStringConvertible { } extension RedisHashSlot { - /// Creates a slot for a given `key`. A ``HashSlot`` is used to determine which shard to connect to. + /// Creates a slot for a given `key`. A `HashSlot` is used to determine which shard to connect to. /// - Parameter key: The key used in a redis command public init(key: String) { // Banging is safe because the modulo ensures we are in range diff --git a/Sources/RediStack/Cluster/SwiftPolyfill.swift b/Sources/RediStack/Cluster/SwiftPolyfill.swift index 970e885d..4d74eee3 100644 --- a/Sources/RediStack/Cluster/SwiftPolyfill.swift +++ b/Sources/RediStack/Cluster/SwiftPolyfill.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2023 RediStack project authors +// Copyright (c) 2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information diff --git a/Sources/RediStack/Commands/BasicCommands.swift b/Sources/RediStack/Commands/BasicCommands.swift index b758ab14..357e3ce3 100644 --- a/Sources/RediStack/Commands/BasicCommands.swift +++ b/Sources/RediStack/Commands/BasicCommands.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019-2020 RediStack project authors +// Copyright (c) 2019-2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -32,8 +32,9 @@ extension RedisClient { /// - Parameter message: The optional message that the server should respond with. /// - Returns: The provided message or Redis' default response of `"PONG"`. public func ping(with message: String? = nil) -> EventLoopFuture { - let args: [RESPValue] = message != nil - ? [.init(bulk: message!)] // safe because we did a nil pre-check + let args: [RESPValue] = + message != nil + ? [.init(bulk: message!)] // safe because we did a nil pre-check : [] return send(command: "PING", with: args) .flatMapThrowing { @@ -57,7 +58,7 @@ extension RedisClient { public func select(database index: Int) -> EventLoopFuture { let args = [RESPValue(bulk: index)] return send(command: "SELECT", with: args) - .map { _ in return () } + .map { _ in () } } /// Swaps the data of two Redis databases by their index IDs. @@ -70,13 +71,13 @@ extension RedisClient { public func swapDatabase(_ first: Int, with second: Int) -> EventLoopFuture { let args: [RESPValue] = [ .init(bulk: first), - .init(bulk: second) + .init(bulk: second), ] return send(command: "SWAPDB", with: args) .tryConverting(to: String.self) - .map { return $0 == "OK" } + .map { $0 == "OK" } } - + /// Requests the client to authenticate with Redis to allow other commands to be executed. /// /// [https://redis.io/commands/auth](https://redis.io/commands/auth) @@ -85,7 +86,7 @@ extension RedisClient { public func authorize(with password: String) -> EventLoopFuture { let args = [RESPValue(bulk: password)] return send(command: "AUTH", with: args) - .map { _ in return () } + .map { _ in () } } /// Requests the client to authenticate with Redis to allow other commands to be executed. @@ -98,7 +99,7 @@ extension RedisClient { password: String ) -> EventLoopFuture { let args = [RESPValue(from: username), RESPValue(from: password)] - return self.send(command: "AUTH", with: args).map { _ in return () } + return self.send(command: "AUTH", with: args).map { _ in () } } /// Removes the specified keys. A key is ignored if it does not exist. @@ -108,19 +109,19 @@ extension RedisClient { /// - Returns: The number of keys deleted from the database. public func delete(_ keys: [RedisKey]) -> EventLoopFuture { guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture(0) } - + let args = keys.map(RESPValue.init) return send(command: "DEL", with: args) .tryConverting() } - + /// Removes the specified keys. A key is ignored if it does not exist. /// /// [https://redis.io/commands/del](https://redis.io/commands/del) /// - Parameter keys: A list of keys to delete from the database. /// - Returns: The number of keys deleted from the database. public func delete(_ keys: RedisKey...) -> EventLoopFuture { - return self.delete(keys) + self.delete(keys) } /// Checks the existence of the provided keys in the database. @@ -142,7 +143,7 @@ extension RedisClient { /// - Parameter keys: A list of keys whose existence will be checked for in the database. /// - Returns: The number of provided keys which exist in the database. public func exists(_ keys: RedisKey...) -> EventLoopFuture { - return self.exists(keys) + self.exists(keys) } /// Sets a timeout on key. After the timeout has expired, the key will automatically be deleted. @@ -156,11 +157,11 @@ extension RedisClient { public func expire(_ key: RedisKey, after timeout: TimeAmount) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: key), - .init(bulk: timeout.seconds) + .init(bulk: timeout.seconds), ] return send(command: "EXPIRE", with: args) .tryConverting(to: Int.self) - .map { return $0 == 1 } + .map { $0 == 1 } } } @@ -208,7 +209,7 @@ extension RedisClient { matching match: String? = nil, count: Int? = nil ) -> EventLoopFuture<(Int, [String])> { - return _scan(command: "SCAN", nil, position, match, count) + _scan(command: "SCAN", nil, position, match, count) } @usableFromInline @@ -220,7 +221,7 @@ extension RedisClient { _ match: String?, _ count: Int? ) -> EventLoopFuture<(Int, T)> - where + where T: RESPValueConvertible { var args: [RESPValue] = [.init(bulk: pos)] @@ -248,8 +249,9 @@ extension RedisClient { } return position } - let elements = response - .map { return $0[1] } + let elements = + response + .map { $0[1] } .tryConverting(to: resultType) return position.and(elements) diff --git a/Sources/RediStack/Commands/HashCommands.swift b/Sources/RediStack/Commands/HashCommands.swift index cd4706a4..43e80617 100644 --- a/Sources/RediStack/Commands/HashCommands.swift +++ b/Sources/RediStack/Commands/HashCommands.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -27,13 +27,14 @@ extension RedisClient { repeat { guard let field = String(fromRESP: values[index]) else { throw RedisClientError.assertionFailure( - message: "Received non-string value where string hash field key was expected. Raw Value: \(values[index])" + message: + "Received non-string value where string hash field key was expected. Raw Value: \(values[index])" ) } let value = values[index + 1] result[field] = value index += 2 - } while (index < values.count) + } while index < values.count return result } @@ -51,14 +52,14 @@ extension RedisClient { /// - Returns: The number of fields that were deleted. public func hdel(_ fields: [String], from key: RedisKey) -> EventLoopFuture { guard fields.count > 0 else { return self.eventLoop.makeSucceededFuture(0) } - + var args: [RESPValue] = [.init(from: key)] args.append(convertingContentsOf: fields) return send(command: "HDEL", with: args) .tryConverting() } - + /// Removes the specified fields from a hash. /// /// See [https://redis.io/commands/hdel](https://redis.io/commands/hdel) @@ -67,7 +68,7 @@ extension RedisClient { /// - key: The key of the hash to delete from. /// - Returns: The number of fields that were deleted. public func hdel(_ fields: String..., from key: RedisKey) -> EventLoopFuture { - return self.hdel(fields, from: key) + self.hdel(fields, from: key) } /// Checks if a hash contains the field specified. @@ -80,11 +81,11 @@ extension RedisClient { public func hexists(_ field: String, in key: RedisKey) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: key), - .init(bulk: field) + .init(bulk: field), ] return send(command: "HEXISTS", with: args) .tryConverting(to: Int.self) - .map { return $0 == 1 } + .map { $0 == 1 } } /// Gets the number of fields contained in a hash. @@ -108,7 +109,7 @@ extension RedisClient { public func hstrlen(of field: String, in key: RedisKey) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: key), - .init(bulk: field) + .init(bulk: field), ] return send(command: "HSTRLEN", with: args) .tryConverting() @@ -135,7 +136,7 @@ extension RedisClient { return send(command: "HVALS", with: args) .tryConverting() } - + /// Gets all values stored in a hash. /// /// See [https://redis.io/commands/hvals](https://redis.io/commands/hvals) @@ -145,8 +146,8 @@ extension RedisClient { /// - Returns: A list of all values stored in a hash. @inlinable public func hvals(in key: RedisKey, as type: Value.Type) -> EventLoopFuture<[Value?]> { - return self.hvals(in: key) - .map { return $0.map(Value.init(fromRESP:)) } + self.hvals(in: key) + .map { $0.map(Value.init(fromRESP:)) } } /// Incrementally iterates over all fields in a hash. @@ -167,7 +168,7 @@ extension RedisClient { count: Int? = nil, valueType: Value.Type ) -> EventLoopFuture<(Int, [String: Value?])> { - return self.hscan(key, startingFrom: position, matching: match, count: count) + self.hscan(key, startingFrom: position, matching: match, count: count) .map { (cursor, fields) in let mappedFields = fields.mapValues(Value.init(fromRESP:)) return (cursor, mappedFields) @@ -189,7 +190,7 @@ extension RedisClient { matching match: String? = nil, count: Int? = nil ) -> EventLoopFuture<(Int, [String: RESPValue])> { - return _scan(command: "HSCAN", resultType: [RESPValue].self, key, position, match, count) + _scan(command: "HSCAN", resultType: [RESPValue].self, key, position, match, count) .flatMapThrowing { let values = try Self._mapHashResponse($0.1) return ($0.0, values) @@ -218,11 +219,11 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: key), .init(bulk: field), - value.convertedToRESPValue() + value.convertedToRESPValue(), ] return send(command: "HSET", with: args) .tryConverting(to: Int.self) - .map { return $0 == 1 } + .map { $0 == 1 } } /// Sets a hash field to the value specified only if the field does not currently exist. @@ -243,11 +244,11 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: key), .init(bulk: field), - value.convertedToRESPValue() + value.convertedToRESPValue(), ] return send(command: "HSETNX", with: args) .tryConverting(to: Int.self) - .map { return $0 == 1 } + .map { $0 == 1 } } /// Sets the fields in a hash to the respective values provided. @@ -269,7 +270,7 @@ extension RedisClient { array.append(.init(bulk: element.key)) array.append(element.value.convertedToRESPValue()) } - + return send(command: "HMSET", with: args) .map { _ in () } } @@ -288,11 +289,11 @@ extension RedisClient { public func hget(_ field: String, from key: RedisKey) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: key), - .init(bulk: field) + .init(bulk: field), ] return send(command: "HGET", with: args) } - + /// Gets a hash field's value as the desired type. /// /// See [https://redis.io/commands/hget](https://redis.io/commands/hget) @@ -307,7 +308,7 @@ extension RedisClient { from key: RedisKey, as type: Value.Type ) -> EventLoopFuture { - return self.hget(field, from: key) + self.hget(field, from: key) .map(Value.init(fromRESP:)) } @@ -320,7 +321,7 @@ extension RedisClient { /// - Returns: A list of values in the same order as the `fields` argument. Non-existent fields return `.null` values. public func hmget(_ fields: [String], from key: RedisKey) -> EventLoopFuture<[RESPValue]> { guard fields.count > 0 else { return self.eventLoop.makeSucceededFuture([]) } - + var args: [RESPValue] = [.init(from: key)] args.append(convertingContentsOf: fields) @@ -342,10 +343,10 @@ extension RedisClient { from key: RedisKey, as type: Value.Type ) -> EventLoopFuture<[Value?]> { - return self.hmget(fields, from: key) - .map { return $0.map(Value.init(fromRESP:)) } + self.hmget(fields, from: key) + .map { $0.map(Value.init(fromRESP:)) } } - + /// Gets the values of a hash for the fields specified. /// /// See [https://redis.io/commands/hmget](https://redis.io/commands/hmget) @@ -354,7 +355,7 @@ extension RedisClient { /// - key: The key of the hash being accessed. /// - Returns: A list of values in the same order as the `fields` argument. Non-existent fields return `.null` values. public func hmget(_ fields: String..., from key: RedisKey) -> EventLoopFuture<[RESPValue]> { - return self.hmget(fields, from: key) + self.hmget(fields, from: key) } /// Gets the values of a hash for the fields specified. @@ -371,7 +372,7 @@ extension RedisClient { from key: RedisKey, as type: Value.Type ) -> EventLoopFuture<[Value?]> { - return self.hmget(fields, from: key, as: type) + self.hmget(fields, from: key, as: type) } /// Returns all the fields and values stored in a hash. @@ -398,8 +399,8 @@ extension RedisClient { from key: RedisKey, as type: Value.Type ) -> EventLoopFuture<[String: Value?]> { - return self.hgetall(from: key) - .map { return $0.mapValues(Value.init(fromRESP:)) } + self.hgetall(from: key) + .map { $0.mapValues(Value.init(fromRESP:)) } } } @@ -420,7 +421,7 @@ extension RedisClient { field: String, in key: RedisKey ) -> EventLoopFuture { - return _hincr(command: "HINCRBY", amount, field, key) + _hincr(command: "HINCRBY", amount, field, key) } /// Increments a hash field's value and returns the new value. @@ -437,9 +438,9 @@ extension RedisClient { field: String, in key: RedisKey ) -> EventLoopFuture { - return _hincr(command: "HINCRBYFLOAT", amount, field, key) + _hincr(command: "HINCRBYFLOAT", amount, field, key) } - + @usableFromInline internal func _hincr( command: String, @@ -450,7 +451,7 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: key), .init(bulk: field), - amount.convertedToRESPValue() + amount.convertedToRESPValue(), ] return send(command: command, with: args) .tryConverting() diff --git a/Sources/RediStack/Commands/ListCommands.swift b/Sources/RediStack/Commands/ListCommands.swift index ea2a666b..0fb9bda0 100644 --- a/Sources/RediStack/Commands/ListCommands.swift +++ b/Sources/RediStack/Commands/ListCommands.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -38,11 +38,11 @@ extension RedisClient { public func lindex(_ index: Int, from key: RedisKey) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: key), - .init(bulk: index) + .init(bulk: index), ] return send(command: "LINDEX", with: args) } - + /// Gets the element from a list stored at the provided index position. /// /// See [https://redis.io/commands/lindex](https://redis.io/commands/lindex) @@ -57,7 +57,7 @@ extension RedisClient { from key: RedisKey, as type: Value.Type ) -> EventLoopFuture { - return self.lindex(index, from: key) + self.lindex(index, from: key) .map(Value.init(fromRESP:)) } @@ -78,7 +78,7 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: key), .init(bulk: index), - value.convertedToRESPValue() + value.convertedToRESPValue(), ] return send(command: "LSET", with: args) .map { _ in () } @@ -101,7 +101,7 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: key), .init(bulk: count), - value.convertedToRESPValue() + value.convertedToRESPValue(), ] return send(command: "LREM", with: args) .tryConverting() @@ -123,12 +123,12 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: key), .init(bulk: start), - .init(bulk: stop) + .init(bulk: stop), ] return send(command: "LTRIM", with: args) .map { _ in () } } - + /// Trims a List to only contain elements within the specified inclusive bounds of 0-based indices. /// /// To keep elements 4 through 7: @@ -156,7 +156,7 @@ extension RedisClient { /// - range: The range of indices that should be kept in the List. /// - Returns: A `NIO.EventLoopFuture` that resolves when the operation has succeeded, or fails with a `RedisError`. public func ltrim(_ key: RedisKey, keepingIndices range: ClosedRange) -> EventLoopFuture { - return self.ltrim(key, before: range.lowerBound, after: range.upperBound) + self.ltrim(key, before: range.lowerBound, after: range.upperBound) } /// Trims a List to only contain elements starting from the specified index. @@ -177,9 +177,9 @@ extension RedisClient { /// - range: The range of indices that should be kept in the List. /// - Returns: A `NIO.EventLoopFuture` that resolves when the operation has succeeded, or fails with a `RedisError`. public func ltrim(_ key: RedisKey, keepingIndices range: PartialRangeFrom) -> EventLoopFuture { - return self.ltrim(key, before: range.lowerBound, after: -1) + self.ltrim(key, before: range.lowerBound, after: -1) } - + /// Trims a List to only contain elements before the specified index. /// /// To keep the first 3 elements: @@ -198,9 +198,9 @@ extension RedisClient { /// - range: The range of indices that should be kept in the List. /// - Returns: A `NIO.EventLoopFuture` that resolves when the operation has succeeded, or fails with a `RedisError`. public func ltrim(_ key: RedisKey, keepingIndices range: PartialRangeUpTo) -> EventLoopFuture { - return self.ltrim(key, before: 0, after: range.upperBound - 1) + self.ltrim(key, before: 0, after: range.upperBound - 1) } - + /// Trims a List to only contain elements up to the specified index. /// /// To keep the first 4 elements: @@ -219,9 +219,9 @@ extension RedisClient { /// - range: The range of indices that should be kept in the List. /// - Returns: A `NIO.EventLoopFuture` that resolves when the operation has succeeded, or fails with a `RedisError`. public func ltrim(_ key: RedisKey, keepingIndices range: PartialRangeThrough) -> EventLoopFuture { - return self.ltrim(key, before: 0, after: range.upperBound) + self.ltrim(key, before: 0, after: range.upperBound) } - + /// Trims a List to only contain the elements from the specified index up to the index provided. /// /// To keep the first 4 elements: @@ -244,7 +244,7 @@ extension RedisClient { /// - range: The range of indices that should be kept in the List. /// - Returns: A `NIO.EventLoopFuture` that resolves when the operation has succeeded, or fails with a `RedisError`. public func ltrim(_ key: RedisKey, keepingIndices range: Range) -> EventLoopFuture { - return self.ltrim(key, before: range.lowerBound, after: range.upperBound - 1) + self.ltrim(key, before: range.lowerBound, after: range.upperBound - 1) } } @@ -263,7 +263,7 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: key), .init(bulk: firstIndex), - .init(bulk: lastIndex) + .init(bulk: lastIndex), ] return send(command: "LRANGE", with: args) .tryConverting() @@ -285,10 +285,10 @@ extension RedisClient { lastIndex: Int, as type: Value.Type ) -> EventLoopFuture<[Value?]> { - return self.lrange(from: key, firstIndex: firstIndex, lastIndex: lastIndex) - .map { return $0.map(Value.init(fromRESP:)) } + self.lrange(from: key, firstIndex: firstIndex, lastIndex: lastIndex) + .map { $0.map(Value.init(fromRESP:)) } } - + /// Gets all elements from a List within the specified inclusive bounds of 0-based indices. /// /// To get the elements at index 4 through 7: @@ -321,9 +321,9 @@ extension RedisClient { /// - range: The range of inclusive indices of elements to get. /// - Returns: An array of elements found within the range specified. public func lrange(from key: RedisKey, indices range: ClosedRange) -> EventLoopFuture<[RESPValue]> { - return self.lrange(from: key, firstIndex: range.lowerBound, lastIndex: range.upperBound) + self.lrange(from: key, firstIndex: range.lowerBound, lastIndex: range.upperBound) } - + /// Gets all elements from a List within the specified inclusive bounds of 0-based indices. /// /// See [https://redis.io/commands/lrange](https://redis.io/commands/lrange). @@ -342,10 +342,10 @@ extension RedisClient { indices range: ClosedRange, as type: Value.Type ) -> EventLoopFuture<[Value?]> { - return self.lrange(from: key, indices: range) - .map { return $0.map(Value.init(fromRESP:)) } + self.lrange(from: key, indices: range) + .map { $0.map(Value.init(fromRESP:)) } } - + /// Gets all the elements from a List starting with the first index bound up to, but not including, the element at the last index bound. /// /// To get the elements at index 4 through 7: @@ -378,9 +378,9 @@ extension RedisClient { /// - range: The range of indices (inclusive lower, exclusive upper) elements to get. /// - Returns: An array of elements found within the range specified. public func lrange(from key: RedisKey, indices range: Range) -> EventLoopFuture<[RESPValue]> { - return self.lrange(from: key, firstIndex: range.lowerBound, lastIndex: range.upperBound - 1) + self.lrange(from: key, firstIndex: range.lowerBound, lastIndex: range.upperBound - 1) } - + /// Gets all the elements from a List starting with the first index bound up to, but not including, the element at the last index bound. /// /// See [https://redis.io/commands/lrange](https://redis.io/commands/lrange) @@ -399,8 +399,8 @@ extension RedisClient { indices range: Range, as type: Value.Type ) -> EventLoopFuture<[Value?]> { - return self.lrange(from: key, indices: range) - .map { return $0.map(Value.init(fromRESP:)) } + self.lrange(from: key, indices: range) + .map { $0.map(Value.init(fromRESP:)) } } /// Gets all elements from the index specified to the end of a List. @@ -421,9 +421,9 @@ extension RedisClient { /// - index: The index of the first element that will be in the returned values. /// - Returns: An array of elements from the List between the index and the end. public func lrange(from key: RedisKey, fromIndex index: Int) -> EventLoopFuture<[RESPValue]> { - return self.lrange(from: key, firstIndex: index, lastIndex: -1) + self.lrange(from: key, firstIndex: index, lastIndex: -1) } - + /// Gets all elements from the index specified to the end of a List. /// /// See `lrange(from:indices:)`, `lrange(from:firstIndex:lastIndex:)`, and [https://redis.io/commands/lrange](https://redis.io/commands/lrange) @@ -438,10 +438,10 @@ extension RedisClient { fromIndex index: Int, as type: Value.Type ) -> EventLoopFuture<[Value?]> { - return self.lrange(from: key, fromIndex: index) - .map { return $0.map(Value.init(fromRESP:)) } + self.lrange(from: key, fromIndex: index) + .map { $0.map(Value.init(fromRESP:)) } } - + /// Gets all elements from the the start of a List up to, and including, the element at the index specified. /// /// To get the first 3 elements of a List: @@ -460,9 +460,9 @@ extension RedisClient { /// - index: The index of the last element that will be in the returned values. /// - Returns: An array of elements from the start of a List to the index. public func lrange(from key: RedisKey, throughIndex index: Int) -> EventLoopFuture<[RESPValue]> { - return self.lrange(from: key, firstIndex: 0, lastIndex: index) + self.lrange(from: key, firstIndex: 0, lastIndex: index) } - + /// Gets all elements from the the start of a List up to, and including, the element at the index specified. /// /// See `lrange(from:indices:)`, `lrange(from:firstIndex:lastIndex:)`, and [https://redis.io/commands/lrange](https://redis.io/commands/lrange) @@ -477,10 +477,10 @@ extension RedisClient { throughIndex index: Int, as type: Value.Type ) -> EventLoopFuture<[Value?]> { - return self.lrange(from: key, throughIndex: index) - .map { return $0.map(Value.init(fromRESP:)) } + self.lrange(from: key, throughIndex: index) + .map { $0.map(Value.init(fromRESP:)) } } - + /// Gets all elements from the the start of a List up to, but not including, the element at the index specified. /// /// To get the first 3 elements of a List: @@ -499,9 +499,9 @@ extension RedisClient { /// - index: The index of the element to not include in the returned values. /// - Returns: An array of elements from the start of the List and up to the index. public func lrange(from key: RedisKey, upToIndex index: Int) -> EventLoopFuture<[RESPValue]> { - return self.lrange(from: key, firstIndex: 0, lastIndex: index - 1) + self.lrange(from: key, firstIndex: 0, lastIndex: index - 1) } - + /// Gets all elements from the the start of a List up to, but not including, the element at the index specified. /// /// See `lrange(from:indices:)`, `lrange(from:firstIndex:lastIndex:)`, and [https://redis.io/commands/lrange](https://redis.io/commands/lrange) @@ -516,8 +516,8 @@ extension RedisClient { upToIndex index: Int, as type: Value.Type ) -> EventLoopFuture<[Value?]> { - return self.lrange(from: key, upToIndex: index) - .map { return $0.map(Value.init(fromRESP:)) } + self.lrange(from: key, upToIndex: index) + .map { $0.map(Value.init(fromRESP:)) } } } @@ -534,18 +534,18 @@ extension RedisClient { public func rpoplpush(from source: RedisKey, to dest: RedisKey) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: source), - .init(from: dest) + .init(from: dest), ] return send(command: "RPOPLPUSH", with: args) } - + /// Pops the last element from a source list and pushes it to a destination list. /// /// See [https://redis.io/commands/rpoplpush](https://redis.io/commands/rpoplpush) /// - Parameters: /// - source: The key of the list to pop from. /// - dest: The key of the list to push to. - /// - type: The type to convert the value to. + /// - valueType: Type of the value. /// - Returns: The element that was moved. This value is `nil` if the `RESPValue` conversion failed. @inlinable public func rpoplpush( @@ -553,7 +553,7 @@ extension RedisClient { to dest: RedisKey, valueType: Value.Type ) -> EventLoopFuture { - return self.rpoplpush(from: source, to: dest) + self.rpoplpush(from: source, to: dest) .map(Value.init(fromRESP:)) } @@ -581,11 +581,10 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: source), .init(from: dest), - .init(from: timeout.seconds) + .init(from: timeout.seconds), ] return send(command: "BRPOPLPUSH", with: args) } - /// Pops the last element from a source list and pushes it to a destination list, blocking until /// an element is available from the source list. @@ -602,7 +601,7 @@ extension RedisClient { /// - source: The key of the list to pop from. /// - dest: The key of the list to push to. /// - timeout: The max time to wait for a value to use. `0` seconds means to wait indefinitely. - /// - type: The type to convert the value to. + /// - valueType: Type of the value. /// - Returns: The element popped from the source list and pushed to the destination. /// If the timeout was reached or `RESPValue` conversion failed, the returned value will be `nil`. @inlinable @@ -612,7 +611,7 @@ extension RedisClient { timeout: TimeAmount = .seconds(0), valueType: Value.Type ) -> EventLoopFuture { - return self.brpoplpush(from: source, to: dest, timeout: timeout) + self.brpoplpush(from: source, to: dest, timeout: timeout) .map(Value.init(fromRESP:)) } } @@ -634,7 +633,7 @@ extension RedisClient { into key: RedisKey, before pivot: Value ) -> EventLoopFuture { - return _linsert(pivotKeyword: "BEFORE", element, key, pivot) + _linsert(pivotKeyword: "BEFORE", element, key, pivot) } /// Inserts the element after the first element matching the "pivot" value provided. @@ -651,7 +650,7 @@ extension RedisClient { into key: RedisKey, after pivot: Value ) -> EventLoopFuture { - return _linsert(pivotKeyword: "AFTER", element, key, pivot) + _linsert(pivotKeyword: "AFTER", element, key, pivot) } @usableFromInline @@ -665,7 +664,7 @@ extension RedisClient { .init(from: key), .init(bulk: pivotKeyword), pivot.convertedToRESPValue(), - element.convertedToRESPValue() + element.convertedToRESPValue(), ] return send(command: "LINSERT", with: args) .tryConverting() @@ -684,7 +683,7 @@ extension RedisClient { let args = [RESPValue(from: key)] return send(command: "LPOP", with: args) } - + /// Removes the first element of a list. /// /// See [https://redis.io/commands/lpop](https://redis.io/commands/lpop) @@ -694,7 +693,7 @@ extension RedisClient { /// - Returns: The element that was popped from the list. If the list is empty or the `RESPValue` conversion failed, this value is `nil`. @inlinable public func lpop(from key: RedisKey, as type: Value.Type) -> EventLoopFuture { - return self.lpop(from: key) + self.lpop(from: key) .map(Value.init(fromRESP:)) } @@ -709,14 +708,14 @@ extension RedisClient { @inlinable public func lpush(_ elements: [Value], into key: RedisKey) -> EventLoopFuture { assert(elements.count > 0, "At least 1 element should be provided.") - + var args: [RESPValue] = [.init(from: key)] args.append(convertingContentsOf: elements) - + return send(command: "LPUSH", with: args) .tryConverting() } - + /// Pushes all of the provided elements into a list. /// - Note: This inserts the elements at the head of the list; for the tail see `rpush(_:into:)`. /// @@ -727,7 +726,7 @@ extension RedisClient { /// - Returns: The length of the list after adding the new elements. @inlinable public func lpush(_ elements: Value..., into key: RedisKey) -> EventLoopFuture { - return self.lpush(elements, into: key) + self.lpush(elements, into: key) } /// Pushes an element into a list, but only if the key exists and holds a list. @@ -742,7 +741,7 @@ extension RedisClient { public func lpushx(_ element: Value, into key: RedisKey) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: key), - element.convertedToRESPValue() + element.convertedToRESPValue(), ] return send(command: "LPUSHX", with: args) .tryConverting() @@ -761,15 +760,16 @@ extension RedisClient { let args = [RESPValue(from: key)] return send(command: "RPOP", with: args) } - + /// Removes the last element a list. /// /// See [https://redis.io/commands/rpop](https://redis.io/commands/rpop) /// - Parameter key: The key of the list to pop from. + /// - Parameter type: The type of the value. /// - Returns: The element that was popped from the list. If the list is empty or the `RESPValue` conversion fails, this value is `nil`. @inlinable public func rpop(from key: RedisKey, as type: Value.Type) -> EventLoopFuture { - return self.rpop(from: key) + self.rpop(from: key) .map(Value.init(fromRESP:)) } @@ -786,11 +786,11 @@ extension RedisClient { var args: [RESPValue] = [.init(from: key)] args.append(convertingContentsOf: elements) - + return send(command: "RPUSH", with: args) .tryConverting() } - + /// Pushes all of the provided elements into a list. /// - Note: This inserts the elements at the tail of the list; for the head see `lpush(_:into:)`. /// @@ -800,7 +800,7 @@ extension RedisClient { /// - Returns: The length of the list after adding the new elements. @inlinable public func rpush(_ elements: Value..., into key: RedisKey) -> EventLoopFuture { - return self.rpush(elements, into: key) + self.rpush(elements, into: key) } /// Pushes an element into a list, but only if the key exists and holds a list. @@ -815,7 +815,7 @@ extension RedisClient { public func rpushx(_ element: Value, into key: RedisKey) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: key), - element.convertedToRESPValue() + element.convertedToRESPValue(), ] return send(command: "RPUSHX", with: args) .tryConverting() @@ -840,10 +840,10 @@ extension RedisClient { /// - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely. /// - Returns: The element that was popped from the list, or `.null` if the timeout was reached. public func blpop(from key: RedisKey, timeout: TimeAmount = .seconds(0)) -> EventLoopFuture { - return blpop(from: [key], timeout: timeout) - .map { return $0?.1 ?? .null } + blpop(from: [key], timeout: timeout) + .map { $0?.1 ?? .null } } - + /// Removes the first element of a list, blocking until an element is available. /// /// - Important: @@ -865,7 +865,7 @@ extension RedisClient { as type: Value.Type, timeout: TimeAmount = .seconds(0) ) -> EventLoopFuture { - return self.blpop(from: key, timeout: timeout) + self.blpop(from: key, timeout: timeout) .map(Value.init(fromRESP:)) } @@ -886,10 +886,13 @@ extension RedisClient { /// If timeout was reached, `nil`. /// /// Otherwise, the key of the list the element was removed from and the popped element. - public func blpop(from keys: [RedisKey], timeout: TimeAmount = .seconds(0)) -> EventLoopFuture<(RedisKey, RESPValue)?> { - return _bpop(command: "BLPOP", keys, timeout) + public func blpop( + from keys: [RedisKey], + timeout: TimeAmount = .seconds(0) + ) -> EventLoopFuture<(RedisKey, RESPValue)?> { + _bpop(command: "BLPOP", keys, timeout) } - + /// Removes the first element of a list, blocking until an element is available. /// /// - Important: @@ -914,7 +917,7 @@ extension RedisClient { timeout: TimeAmount = .seconds(0), valueType: Value.Type ) -> EventLoopFuture<(RedisKey, Value)?> { - return self.blpop(from: keys, timeout: timeout) + self.blpop(from: keys, timeout: timeout) .map { guard let result = $0, @@ -923,7 +926,7 @@ extension RedisClient { return (result.0, value) } } - + /// Removes the first element of a list, blocking until an element is available. /// /// - Important: @@ -941,10 +944,13 @@ extension RedisClient { /// If timeout was reached, `nil`. /// /// Otherwise, the key of the list the element was removed from and the popped element. - public func blpop(from keys: RedisKey..., timeout: TimeAmount = .seconds(0)) -> EventLoopFuture<(RedisKey, RESPValue)?> { - return self.blpop(from: keys, timeout: timeout) + public func blpop( + from keys: RedisKey..., + timeout: TimeAmount = .seconds(0) + ) -> EventLoopFuture<(RedisKey, RESPValue)?> { + self.blpop(from: keys, timeout: timeout) } - + /// Removes the first element of a list, blocking until an element is available. /// /// - Important: @@ -969,7 +975,7 @@ extension RedisClient { timeout: TimeAmount = .seconds(0), valueType: Value.Type ) -> EventLoopFuture<(RedisKey, Value)?> { - return self.blpop(from: keys, timeout: timeout, valueType: valueType) + self.blpop(from: keys, timeout: timeout, valueType: valueType) } /// Removes the last element of a list, blocking until an element is available. @@ -987,10 +993,10 @@ extension RedisClient { /// - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely. /// - Returns: The element that was popped from the list, or `.null` if the timeout was reached. public func brpop(from key: RedisKey, timeout: TimeAmount = .seconds(0)) -> EventLoopFuture { - return brpop(from: [key], timeout: timeout) - .map { return $0?.1 ?? .null } + brpop(from: [key], timeout: timeout) + .map { $0?.1 ?? .null } } - + /// Removes the last element of a list, blocking until an element is available. /// /// - Important: @@ -1012,7 +1018,7 @@ extension RedisClient { as type: Value.Type, timeout: TimeAmount = .seconds(0) ) -> EventLoopFuture { - return self.brpop(from: key, timeout: timeout) + self.brpop(from: key, timeout: timeout) .map(Value.init(fromRESP:)) } @@ -1033,8 +1039,11 @@ extension RedisClient { /// If timeout was reached, `nil`. /// /// Otherwise, the key of the list the element was removed from and the popped element. - public func brpop(from keys: [RedisKey], timeout: TimeAmount = .seconds(0)) -> EventLoopFuture<(RedisKey, RESPValue)?> { - return _bpop(command: "BRPOP", keys, timeout) + public func brpop( + from keys: [RedisKey], + timeout: TimeAmount = .seconds(0) + ) -> EventLoopFuture<(RedisKey, RESPValue)?> { + _bpop(command: "BRPOP", keys, timeout) } /// Removes the last element of a list, blocking until an element is available. @@ -1050,6 +1059,7 @@ extension RedisClient { /// - Parameters: /// - keys: The keys of lists in Redis that should be popped from. /// - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely. + /// - valueType: Type of the value. /// - Returns: /// If timeout was reached or `RESPValue` conversion failed, `nil`. /// @@ -1060,7 +1070,7 @@ extension RedisClient { timeout: TimeAmount = .seconds(0), valueType: Value.Type ) -> EventLoopFuture<(RedisKey, Value)?> { - return self.brpop(from: keys, timeout: timeout) + self.brpop(from: keys, timeout: timeout) .map { guard let result = $0, @@ -1087,10 +1097,13 @@ extension RedisClient { /// If timeout was reached, `nil`. /// /// Otherwise, the key of the list the element was removed from and the popped element. - public func brpop(from keys: RedisKey..., timeout: TimeAmount = .seconds(0)) -> EventLoopFuture<(RedisKey, RESPValue)?> { - return self.brpop(from: keys, timeout: timeout) + public func brpop( + from keys: RedisKey..., + timeout: TimeAmount = .seconds(0) + ) -> EventLoopFuture<(RedisKey, RESPValue)?> { + self.brpop(from: keys, timeout: timeout) } - + /// Removes the last element of a list, blocking until an element is available. /// /// - Important: @@ -1104,6 +1117,7 @@ extension RedisClient { /// - Parameters: /// - keys: The keys of lists in Redis that should be popped from. /// - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely. + /// - valueType: The type of the value. /// - Returns: /// If timeout was reached or `RESPValue` conversion failed, `nil`. /// @@ -1114,7 +1128,7 @@ extension RedisClient { timeout: TimeAmount = .seconds(0), valueType: Value.Type ) -> EventLoopFuture<(RedisKey, Value)?> { - return self.brpop(from: keys, timeout: timeout, valueType: valueType) + self.brpop(from: keys, timeout: timeout, valueType: valueType) } @usableFromInline @@ -1125,7 +1139,7 @@ extension RedisClient { ) -> EventLoopFuture<(RedisKey, RESPValue)?> { var args = keys.map(RESPValue.init) args.append(.init(bulk: timeout.seconds)) - + return send(command: command, with: args) .flatMapThrowing { guard !$0.isNull else { return nil } diff --git a/Sources/RediStack/Commands/PubSubCommands.swift b/Sources/RediStack/Commands/PubSubCommands.swift index 7e6dab96..c4c84482 100644 --- a/Sources/RediStack/Commands/PubSubCommands.swift +++ b/Sources/RediStack/Commands/PubSubCommands.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020 RediStack project authors +// Copyright (c) 2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -32,7 +32,7 @@ extension RedisClient { ) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: channel), - message.convertedToRESPValue() + message.convertedToRESPValue(), ] return self.send(command: "PUBLISH", with: args) .tryConverting() @@ -50,13 +50,13 @@ extension RedisClient { /// - Returns: A list of all active channel names. public func activeChannels(matching match: String? = nil) -> EventLoopFuture<[RedisChannelName]> { var args: [RESPValue] = [.init(bulk: "CHANNELS")] - + if let m = match { args.append(.init(bulk: m)) } - + return self.send(command: "PUBSUB", with: args) .tryConverting() } - + /// Resolves the total count of active subscriptions to channels that were made using patterns. /// /// See [PUBSUB NUMPAT](https://redis.io/commands/pubsub#codepubsub-numpatcode) @@ -66,7 +66,7 @@ extension RedisClient { return self.send(command: "PUBSUB", with: args) .tryConverting() } - + /// Resolves a count of (non-pattern) subscribers for each given channel. /// /// See [PUBSUB NUMSUB](https://redis.io/commands/pubsub#codepubsub-numsub-channel-1--channel-ncode) @@ -74,23 +74,27 @@ extension RedisClient { /// - Returns: A mapping of channel names and their (non-pattern) subscriber count. public func subscriberCount(forChannels channels: [RedisChannelName]) -> EventLoopFuture<[RedisChannelName: Int]> { guard channels.count > 0 else { return self.eventLoop.makeSucceededFuture([:]) } - + var args: [RESPValue] = [.init(bulk: "NUMSUB")] args.append(convertingContentsOf: channels) - + return self.send(command: "PUBSUB", with: args) .tryConverting(to: [RESPValue].self) .flatMapThrowing { response in assert(response.count == channels.count * 2, "Unexpected response size!") - + // Redis guarantees that the response format is [channel1Name, channel1Count, channel2Name, ...] // with the order of channels matching the order sent in the request - return try channels + return + try channels .enumerated() .reduce(into: [:]) { (result, next) in let responseOffset = next.offset * 2 - assert(next.element.rawValue == response[responseOffset].string, "Unexpected value in current index!") - + assert( + next.element.rawValue == response[responseOffset].string, + "Unexpected value in current index!" + ) + guard let count = response[responseOffset + 1].int else { throw RedisClientError.assertionFailure( message: "Unexpected value at position \(responseOffset + 1) in \(response)" diff --git a/Sources/RediStack/Commands/SetCommands.swift b/Sources/RediStack/Commands/SetCommands.swift index 9e35e0cc..4260aba2 100644 --- a/Sources/RediStack/Commands/SetCommands.swift +++ b/Sources/RediStack/Commands/SetCommands.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -30,7 +30,7 @@ extension RedisClient { return send(command: "SMEMBERS", with: args) .tryConverting() } - + /// Gets all of the elements contained in a set. /// - Note: Ordering of results are stable between multiple calls of this method to the same set. /// @@ -42,9 +42,12 @@ extension RedisClient { /// - type: The type to convert the values to. /// - Returns: A list of elements found within the set. Elements that fail the `RESPValue` conversion will be `nil`. @inlinable - public func smembers(of key: RedisKey, as type: Value.Type) -> EventLoopFuture<[Value?]> { - return self.smembers(of: key) - .map { return $0.map(Value.init(fromRESP:)) } + public func smembers( + of key: RedisKey, + as type: Value.Type + ) -> EventLoopFuture<[Value?]> { + self.smembers(of: key) + .map { $0.map(Value.init(fromRESP:)) } } /// Checks if the element is included in a set. @@ -58,11 +61,11 @@ extension RedisClient { public func sismember(_ element: Value, of key: RedisKey) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: key), - element.convertedToRESPValue() + element.convertedToRESPValue(), ] return send(command: "SISMEMBER", with: args) .tryConverting(to: Int.self) - .map { return $0 == 1 } + .map { $0 == 1 } } /// Gets the total count of elements within a set. @@ -86,14 +89,14 @@ extension RedisClient { @inlinable public func sadd(_ elements: [Value], to key: RedisKey) -> EventLoopFuture { guard elements.count > 0 else { return self.eventLoop.makeSucceededFuture(0) } - + var args: [RESPValue] = [.init(from: key)] args.append(convertingContentsOf: elements) return send(command: "SADD", with: args) .tryConverting() } - + /// Adds elements to a set. /// /// See [https://redis.io/commands/sadd](https://redis.io/commands/sadd) @@ -103,7 +106,7 @@ extension RedisClient { /// - Returns: The number of elements that were added to the set. @inlinable public func sadd(_ elements: Value..., to key: RedisKey) -> EventLoopFuture { - return self.sadd(elements, to: key) + self.sadd(elements, to: key) } /// Removes elements from a set. @@ -119,11 +122,11 @@ extension RedisClient { var args: [RESPValue] = [.init(from: key)] args.append(convertingContentsOf: elements) - + return send(command: "SREM", with: args) .tryConverting() } - + /// Removes elements from a set. /// /// See [https://redis.io/commands/srem](https://redis.io/commands/srem) @@ -133,7 +136,7 @@ extension RedisClient { /// - Returns: The number of elements that were removed from the set. @inlinable public func srem(_ elements: Value..., from key: RedisKey) -> EventLoopFuture { - return self.srem(elements, from: key) + self.srem(elements, from: key) } /// Randomly selects and removes one or more elements in a set. @@ -147,10 +150,10 @@ extension RedisClient { assert(count >= 0, "A negative max count is nonsense.") guard count > 0 else { return self.eventLoop.makeSucceededFuture([]) } - + let args: [RESPValue] = [ .init(from: key), - .init(bulk: count) + .init(bulk: count), ] return send(command: "SPOP", with: args) .tryConverting() @@ -170,8 +173,8 @@ extension RedisClient { as type: Value.Type, max count: Int = 1 ) -> EventLoopFuture<[Value?]> { - return self.spop(from: key, max: count) - .map { return $0.map(Value.init(fromRESP:)) } + self.spop(from: key, max: count) + .map { $0.map(Value.init(fromRESP:)) } } /// Randomly selects one or more elements in a set. @@ -190,18 +193,18 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: key), - .init(bulk: count) + .init(bulk: count), ] return send(command: "SRANDMEMBER", with: args) .tryConverting() } - + /// Randomly selects one or more elements in a set. /// /// See [https://redis.io/commands/srandmember](https://redis.io/commands/srandmember) /// - Parameters: /// - key: The key of the set. - /// - type; The type to convert the values to. + /// - type: The type to convert the values to. /// - count: The max number of elements to select from the set. /// - Returns: The elements randomly selected from the set. Elements that fail the `RESPValue` conversion will be `nil`. @inlinable @@ -210,8 +213,8 @@ extension RedisClient { as type: Value.Type, max count: Int = 1 ) -> EventLoopFuture<[Value?]> { - return self.srandmember(from: key, max: count) - .map { return $0.map(Value.init(fromRESP:)) } + self.srandmember(from: key, max: count) + .map { $0.map(Value.init(fromRESP:)) } } /// Moves an element from one set to another. @@ -233,11 +236,11 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: sourceKey), .init(from: destKey), - element.convertedToRESPValue() + element.convertedToRESPValue(), ] return send(command: "SMOVE", with: args) .tryConverting() - .map { return $0 == 1 } + .map { $0 == 1 } } /// Incrementally iterates over all values in a set. @@ -255,9 +258,9 @@ extension RedisClient { matching match: String? = nil, count: Int? = nil ) -> EventLoopFuture<(Int, [RESPValue])> { - return _scan(command: "SSCAN", key, position, match, count) + _scan(command: "SSCAN", key, position, match, count) } - + /// Incrementally iterates over all values in a set. /// /// See [https://redis.io/commands/sscan](https://redis.io/commands/sscan) @@ -276,7 +279,7 @@ extension RedisClient { count: Int? = nil, valueType: Value.Type ) -> EventLoopFuture<(Int, [Value?])> { - return self.sscan(key, startingFrom: position, matching: match, count: count) + self.sscan(key, startingFrom: position, matching: match, count: count) .map { (cursor, rawValues) in let values = rawValues.map(Value.init(fromRESP:)) return (cursor, values) @@ -299,7 +302,7 @@ extension RedisClient { return send(command: "SDIFF", with: args) .tryConverting() } - + /// Calculates the difference between two or more sets. /// /// See [https://redis.io/commands/sdiff](https://redis.io/commands/sdiff) @@ -308,20 +311,23 @@ extension RedisClient { /// - valueType: The type to convert the values to. /// - Returns: A list of elements resulting from the difference. Elements that fail the `RESPValue` conversion will be `nil`. @inlinable - public func sdiff(of keys: [RedisKey], valueType: Value.Type) -> EventLoopFuture<[Value?]> { - return self.sdiff(of: keys) - .map { return $0.map(Value.init(fromRESP:)) } + public func sdiff( + of keys: [RedisKey], + valueType: Value.Type + ) -> EventLoopFuture<[Value?]> { + self.sdiff(of: keys) + .map { $0.map(Value.init(fromRESP:)) } } - + /// Calculates the difference between two or more sets. /// /// See [https://redis.io/commands/sdiff](https://redis.io/commands/sdiff) /// - Parameter keys: The source sets to calculate the difference of. /// - Returns: A list of elements resulting from the difference. public func sdiff(of keys: RedisKey...) -> EventLoopFuture<[RESPValue]> { - return self.sdiff(of: keys) + self.sdiff(of: keys) } - + /// Calculates the difference between two or more sets. /// /// See [https://redis.io/commands/sdiff](https://redis.io/commands/sdiff) @@ -330,8 +336,11 @@ extension RedisClient { /// - valueType: The type to convert the values to. /// - Returns: A list of elements resulting from the difference. Elements that fail the `RESPValue` conversion will be `nil`. @inlinable - public func sdiff(of keys: RedisKey..., valueType: Value.Type) -> EventLoopFuture<[Value?]> { - return self.sdiff(of: keys, valueType: valueType) + public func sdiff( + of keys: RedisKey..., + valueType: Value.Type + ) -> EventLoopFuture<[Value?]> { + self.sdiff(of: keys, valueType: valueType) } /// Calculates the difference between two or more sets and stores the result. @@ -340,14 +349,14 @@ extension RedisClient { /// See [https://redis.io/commands/sdiffstore](https://redis.io/commands/sdiffstore) /// - Parameters: /// - destination: The key of the new set from the result. - /// - sources: The list of source sets to calculate the difference of. + /// - keys: The list of source sets to calculate the difference of. /// - Returns: The number of elements in the difference result. public func sdiffstore(as destination: RedisKey, sources keys: [RedisKey]) -> EventLoopFuture { assert(keys.count > 0, "At least 1 key should be provided.") var args: [RESPValue] = [.init(from: destination)] args.append(convertingContentsOf: keys) - + return send(command: "SDIFFSTORE", with: args) .tryConverting() } @@ -368,7 +377,7 @@ extension RedisClient { return send(command: "SINTER", with: args) .tryConverting() } - + /// Calculates the intersection of two or more sets. /// /// See [https://redis.io/commands/sinter](https://redis.io/commands/sinter) @@ -377,20 +386,23 @@ extension RedisClient { /// - valueType: The type to convert all values to. /// - Returns: A list of elements resulting from the intersection. Elements that fail the `RESPValue` conversion will be `nil`. @inlinable - public func sinter(of keys: [RedisKey], valueType: Value.Type) -> EventLoopFuture<[Value?]> { - return self.sinter(of: keys) - .map { return $0.map(Value.init(fromRESP:)) } + public func sinter( + of keys: [RedisKey], + valueType: Value.Type + ) -> EventLoopFuture<[Value?]> { + self.sinter(of: keys) + .map { $0.map(Value.init(fromRESP:)) } } - + /// Calculates the intersection of two or more sets. /// /// See [https://redis.io/commands/sinter](https://redis.io/commands/sinter) /// - Parameter keys: The source sets to calculate the intersection of. /// - Returns: A list of elements resulting from the intersection. public func sinter(of keys: RedisKey...) -> EventLoopFuture<[RESPValue]> { - return self.sinter(of: keys) + self.sinter(of: keys) } - + /// Calculates the intersection of two or more sets. /// /// See [https://redis.io/commands/sinter](https://redis.io/commands/sinter) @@ -399,8 +411,11 @@ extension RedisClient { /// - valueType: The type to convert all values to. /// - Returns: A list of elements resulting from the intersection. Elements that fail the `RESPValue` conversion will be `nil`. @inlinable - public func sinter(of keys: RedisKey..., valueType: Value.Type) -> EventLoopFuture<[Value?]> { - return self.sinter(of: keys, valueType: valueType) + public func sinter( + of keys: RedisKey..., + valueType: Value.Type + ) -> EventLoopFuture<[Value?]> { + self.sinter(of: keys, valueType: valueType) } /// Calculates the intersetion of two or more sets and stores the result. @@ -409,14 +424,14 @@ extension RedisClient { /// See [https://redis.io/commands/sinterstore](https://redis.io/commands/sinterstore) /// - Parameters: /// - destination: The key of the new set from the result. - /// - sources: A list of source sets to calculate the intersection of. + /// - keys: A list of source sets to calculate the intersection of. /// - Returns: The number of elements in the intersection result. public func sinterstore(as destination: RedisKey, sources keys: [RedisKey]) -> EventLoopFuture { assert(keys.count > 0, "At least 1 key should be provided.") var args: [RESPValue] = [.init(from: destination)] args.append(convertingContentsOf: keys) - + return send(command: "SINTERSTORE", with: args) .tryConverting() } @@ -432,12 +447,12 @@ extension RedisClient { /// - Returns: A list of elements resulting from the union. public func sunion(of keys: [RedisKey]) -> EventLoopFuture<[RESPValue]> { guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture([]) } - + let args = keys.map(RESPValue.init) return send(command: "SUNION", with: args) .tryConverting() } - + /// Calculates the union of two or more sets. /// /// See [https://redis.io/commands/sunion](https://redis.io/commands/sunion) @@ -446,20 +461,23 @@ extension RedisClient { /// - valueType: The type to convert all values to. /// - Returns: A list of elements resulting from the union. Elements that fail the `RESPValue` conversion will be `nil`. @inlinable - public func sunion(of keys: [RedisKey], valueType: Value.Type) -> EventLoopFuture<[Value?]> { - return self.sunion(of: keys) - .map { return $0.map(Value.init(fromRESP:)) } + public func sunion( + of keys: [RedisKey], + valueType: Value.Type + ) -> EventLoopFuture<[Value?]> { + self.sunion(of: keys) + .map { $0.map(Value.init(fromRESP:)) } } - + /// Calculates the union of two or more sets. /// /// See [https://redis.io/commands/sunion](https://redis.io/commands/sunion) /// - Parameter keys: The source sets to calculate the union of. /// - Returns: A list of elements resulting from the union. public func sunion(of keys: RedisKey...) -> EventLoopFuture<[RESPValue]> { - return self.sunion(of: keys) + self.sunion(of: keys) } - + /// Calculates the union of two or more sets. /// /// See [https://redis.io/commands/sunion](https://redis.io/commands/sunion) @@ -468,8 +486,11 @@ extension RedisClient { /// - valueType: The type to convert all values to. /// - Returns: A list of elements resulting from the union. Elements that fail the `RESPValue` conversion will be `nil`. @inlinable - public func sunion(of keys: RedisKey..., valueType: Value.Type) -> EventLoopFuture<[Value?]> { - return self.sunion(of: keys, valueType: valueType) + public func sunion( + of keys: RedisKey..., + valueType: Value.Type + ) -> EventLoopFuture<[Value?]> { + self.sunion(of: keys, valueType: valueType) } /// Calculates the union of two or more sets and stores the result. @@ -478,14 +499,14 @@ extension RedisClient { /// See [https://redis.io/commands/sunionstore](https://redis.io/commands/sunionstore) /// - Parameters: /// - destination: The key of the new set from the result. - /// - sources: A list of source sets to calculate the union of. + /// - keys: A list of source sets to calculate the union of. /// - Returns: The number of elements in the union result. public func sunionstore(as destination: RedisKey, sources keys: [RedisKey]) -> EventLoopFuture { assert(keys.count > 0, "At least 1 key should be provided.") var args: [RESPValue] = [.init(from: destination)] args.append(convertingContentsOf: keys) - + return send(command: "SUNIONSTORE", with: args) .tryConverting() } diff --git a/Sources/RediStack/Commands/SortedSetCommands.swift b/Sources/RediStack/Commands/SortedSetCommands.swift index a36dd74d..81766393 100644 --- a/Sources/RediStack/Commands/SortedSetCommands.swift +++ b/Sources/RediStack/Commands/SortedSetCommands.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019-2022 RediStack project authors +// Copyright (c) 2019-2022 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -37,7 +37,7 @@ extension RedisClient { result.append((response[elementIndex], score)) index += 2 - } while (index < response.count) + } while index < response.count return result } @@ -61,7 +61,7 @@ public enum RedisZaddInsertBehavior { case onlyNewElements /// Only update the score of existing elements; do not insert new elements. case onlyExistingElements - + /// Redis representation of this option. @usableFromInline internal var string: String? { @@ -86,7 +86,7 @@ public enum RedisZaddReturnBehavior { case changedElementsCount /// Count only new elements that were inserted into the SortedSet. case insertedElementsCount - + /// Redis representation of this option. @usableFromInline internal var string: String? { @@ -115,7 +115,7 @@ extension RedisClient { returning returnBehavior: RedisZaddReturnBehavior = .insertedElementsCount ) -> EventLoopFuture { var args: [RESPValue] = [.init(from: key)] - + args.append(convertingContentsOf: [insertBehavior.string, returnBehavior.string].compactMap({ $0 })) args.add(contentsOf: elements, overestimatedCountBeingAdded: elements.count * 2) { (array, next) in array.append(.init(bulk: next.score.description)) @@ -125,7 +125,7 @@ extension RedisClient { return self.send(command: "ZADD", with: args) .tryConverting() } - + /// Adds elements to a sorted set, assigning their score to the values provided. /// /// See [https://redis.io/commands/zadd](https://redis.io/commands/zadd) @@ -142,7 +142,7 @@ extension RedisClient { inserting insertBehavior: RedisZaddInsertBehavior = .allElements, returning returnBehavior: RedisZaddReturnBehavior = .insertedElementsCount ) -> EventLoopFuture { - return self.zadd(elements, to: key, inserting: insertBehavior, returning: returnBehavior) + self.zadd(elements, to: key, inserting: insertBehavior, returning: returnBehavior) } /// Adds an element to a sorted set, assigning their score to the value provided. @@ -161,8 +161,8 @@ extension RedisClient { inserting insertBehavior: RedisZaddInsertBehavior = .allElements, returning returnBehavior: RedisZaddReturnBehavior = .insertedElementsCount ) -> EventLoopFuture { - return self.zadd(element, to: key, inserting: insertBehavior, returning: returnBehavior) - .map { return $0 == 1 } + self.zadd(element, to: key, inserting: insertBehavior, returning: returnBehavior) + .map { $0 == 1 } } } @@ -191,10 +191,10 @@ extension RedisClient { public func zscore(of element: Value, in key: RedisKey) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: key), - element.convertedToRESPValue() + element.convertedToRESPValue(), ] return send(command: "ZSCORE", with: args) - .map { return Double(fromRESP: $0) } + .map { Double(fromRESP: $0) } } /// Incrementally iterates over all elements in a sorted set. @@ -212,13 +212,13 @@ extension RedisClient { matching match: String? = nil, count: Int? = nil ) -> EventLoopFuture<(Int, [(RESPValue, Double)])> { - return self._scan(command: "ZSCAN", resultType: [RESPValue].self, key, position, match, count) + self._scan(command: "ZSCAN", resultType: [RESPValue].self, key, position, match, count) .flatMapThrowing { let values = try Self._mapSortedSetResponse($0.1, scoreIsFirst: false) return ($0.0, values) } } - + /// Incrementally iterates over all elements in a sorted set. /// /// See [https://redis.io/commands/zscan](https://redis.io/commands/zscan) @@ -238,7 +238,7 @@ extension RedisClient { count: Int? = nil, valueType: Value.Type ) -> EventLoopFuture<(Int, [(Value, Double)?])> { - return self.zscan(key, startingFrom: position, matching: match, count: count) + self.zscan(key, startingFrom: position, matching: match, count: count) .map { (cursor, elements) in let mappedElements = elements.map { next -> (Value, Double)? in guard let value = Value(fromRESP: next.0) else { return nil } @@ -265,7 +265,7 @@ extension RedisClient { public func zrank(of element: Value, in key: RedisKey) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: key), - element.convertedToRESPValue() + element.convertedToRESPValue(), ] return send(command: "ZRANK", with: args) .tryConverting() @@ -284,7 +284,7 @@ extension RedisClient { public func zrevrank(of element: Value, in key: RedisKey) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: key), - element.convertedToRESPValue() + element.convertedToRESPValue(), ] return send(command: "ZREVRANK", with: args) .tryConverting() @@ -306,7 +306,7 @@ extension RedisClient { public enum RedisZScoreBound { case inclusive(Double) case exclusive(Double) - + /// The underlying raw score value this bound represents. public var rawValue: Double { switch self { @@ -326,7 +326,7 @@ extension RedisZScoreBound: CustomStringConvertible { extension RedisZScoreBound: ExpressibleByFloatLiteral { public typealias FloatLiteralType = Double - + public init(floatLiteral value: Double) { self = .inclusive(value) } @@ -366,12 +366,12 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: key), .init(bulk: range.min.description), - .init(bulk: range.max.description) + .init(bulk: range.max.description), ] return self.send(command: "ZCOUNT", with: args) .tryConverting() } - + /// Returns the count of elements in a SortedSet with a score within the inclusive range specified. /// /// To get a count of elements that have at least the score of 3, but no greater than 10: @@ -385,9 +385,9 @@ extension RedisClient { /// - range: The inclusive range of scores to filter elements to count. /// - Returns: The count of elements in the SortedSet with a score within the range specified. public func zcount(of key: RedisKey, withScores range: ClosedRange) -> EventLoopFuture { - return self.zcount(of: key, withScoresBetween: (.inclusive(range.lowerBound), .inclusive(range.upperBound))) + self.zcount(of: key, withScoresBetween: (.inclusive(range.lowerBound), .inclusive(range.upperBound))) } - + /// Returns the count of elements in a SortedSet with a minimum score up to, but not including, a max score. /// /// To get a count of elements that have at least the score of 3, but less than 10: @@ -401,9 +401,9 @@ extension RedisClient { /// - range: A range with an inclusive lower and exclusive upper bound of scores to filter elements to count. /// - Returns: The count of elements in the SortedSet with a score within the range specified. public func zcount(of key: RedisKey, withScores range: Range) -> EventLoopFuture { - return self.zcount(of: key, withScoresBetween: (.inclusive(range.lowerBound), .exclusive(range.upperBound))) + self.zcount(of: key, withScoresBetween: (.inclusive(range.lowerBound), .exclusive(range.upperBound))) } - + /// Returns the count of elements in a SortedSet whose score is greater than a minimum score value. /// /// By default, the value provided will be treated as _inclusive_, meaning any element that has a score matching the value **will** be counted. @@ -414,9 +414,9 @@ extension RedisClient { /// - minScore: The minimum score bound an element in the SortedSet should have in order to be counted. /// - Returns: The count of elements in the SortedSet above the `minScore` threshold. public func zcount(of key: RedisKey, withMinimumScoreOf minScore: RedisZScoreBound) -> EventLoopFuture { - return self.zcount(of: key, withScoresBetween: (minScore, .inclusive(.infinity))) + self.zcount(of: key, withScoresBetween: (minScore, .inclusive(.infinity))) } - + /// Returns the count of elements in a SortedSet whose score is less than a maximum score value. /// /// By default, the value provided will be treated as _inclusive_, meaning any element that has a score matching the value **will** be counted. @@ -425,10 +425,9 @@ extension RedisClient { /// - Parameters: /// - key: The key of the SortedSet that will be counted. /// - maxScore: The maximum score bound an element in the SortedSet should have in order to be counted. - /// - exclusive: Should the `maxScore` provided be exclusive? If `true`, scores matching the `maxScore` will **not** be counted. /// - Returns: The count of elements in the SortedSet below the `maxScore` threshold. public func zcount(of key: RedisKey, withMaximumScoreOf maxScore: RedisZScoreBound) -> EventLoopFuture { - return self.zcount(of: key, withScoresBetween: (.inclusive(-.infinity), maxScore)) + self.zcount(of: key, withScoresBetween: (.inclusive(-.infinity), maxScore)) } } @@ -495,12 +494,12 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: key), .init(bulk: range.min.description), - .init(bulk: range.max.description) + .init(bulk: range.max.description), ] return self.send(command: "ZLEXCOUNT", with: args) .tryConverting() } - + /// Returns the count of elements in a SortedSet whose lexiographical value is greater than a minimum value. /// /// For example with a SortedSet that contains the values [1, 2, 3, 10] and each a score of 1: @@ -523,9 +522,9 @@ extension RedisClient { of key: RedisKey, withMinimumValueOf minValue: RedisZLexBound ) -> EventLoopFuture { - return self.zlexcount(of: key, withValuesBetween: (minValue, .positiveInfinity)) + self.zlexcount(of: key, withValuesBetween: (minValue, .positiveInfinity)) } - + /// Returns the count of elements in a SortedSet whose lexiographical value is less than a maximum value. /// /// For example with a SortedSet that contains the values [1, 2, 3, 10] and each a score of 1: @@ -548,7 +547,7 @@ extension RedisClient { of key: RedisKey, withMaximumValueOf maxValue: RedisZLexBound ) -> EventLoopFuture { - return self.zlexcount(of: key, withValuesBetween: (.negativeInfinity, maxValue)) + self.zlexcount(of: key, withValuesBetween: (.negativeInfinity, maxValue)) } } @@ -563,7 +562,7 @@ extension RedisClient { /// - count: The max number of elements to pop from the set. /// - Returns: A list of elements popped from the sorted set with their associated score. public func zpopmin(from key: RedisKey, max count: Int) -> EventLoopFuture<[(RESPValue, Double)]> { - return _zpop(command: "ZPOPMIN", count, key) + _zpop(command: "ZPOPMIN", count, key) } /// Removes the element from a sorted set with the lowest score. @@ -572,8 +571,8 @@ extension RedisClient { /// - Parameter key: The key identifying the sorted set in Redis. /// - Returns: The element and its associated score that was popped from the sorted set, or `nil` if set was empty. public func zpopmin(from key: RedisKey) -> EventLoopFuture<(RESPValue, Double)?> { - return _zpop(command: "ZPOPMIN", nil, key) - .map { return $0.count > 0 ? $0[0] : nil } + _zpop(command: "ZPOPMIN", nil, key) + .map { $0.count > 0 ? $0[0] : nil } } /// Removes elements from a sorted set with the highest scores. @@ -584,7 +583,7 @@ extension RedisClient { /// - count: The max number of elements to pop from the set. /// - Returns: A list of elements popped from the sorted set with their associated score. public func zpopmax(from key: RedisKey, max count: Int) -> EventLoopFuture<[(RESPValue, Double)]> { - return _zpop(command: "ZPOPMAX", count, key) + _zpop(command: "ZPOPMAX", count, key) } /// Removes the element from a sorted set with the highest score. @@ -593,8 +592,8 @@ extension RedisClient { /// - Parameter key: The key identifying the sorted set in Redis. /// - Returns: The element and its associated score that was popped from the sorted set, or `nil` if set was empty. public func zpopmax(from key: RedisKey) -> EventLoopFuture<(RESPValue, Double)?> { - return _zpop(command: "ZPOPMAX", nil, key) - .map { return $0.count > 0 ? $0[0] : nil } + _zpop(command: "ZPOPMAX", nil, key) + .map { $0.count > 0 ? $0[0] : nil } } func _zpop( @@ -612,7 +611,7 @@ extension RedisClient { return send(command: command, with: args) .tryConverting(to: [RESPValue].self) - .flatMapThrowing { return try Self._mapSortedSetResponse($0, scoreIsFirst: true) } + .flatMapThrowing { try Self._mapSortedSetResponse($0, scoreIsFirst: true) } } } @@ -640,7 +639,7 @@ extension RedisClient { from key: RedisKey, timeout: TimeAmount = .seconds(0) ) -> EventLoopFuture<(Double, RESPValue)?> { - return bzpopmin(from: [key], timeout: timeout) + bzpopmin(from: [key], timeout: timeout) .map { guard let response = $0 else { return nil } return (response.1, response.2) @@ -670,7 +669,7 @@ extension RedisClient { from keys: [RedisKey], timeout: TimeAmount = .seconds(0) ) -> EventLoopFuture<(String, Double, RESPValue)?> { - return self._bzpop(command: "BZPOPMIN", keys, timeout) + self._bzpop(command: "BZPOPMIN", keys, timeout) } /// Removes the element from a sorted set with the highest score, blocking until an element is @@ -694,7 +693,7 @@ extension RedisClient { from key: RedisKey, timeout: TimeAmount = .seconds(0) ) -> EventLoopFuture<(Double, RESPValue)?> { - return self.bzpopmax(from: [key], timeout: timeout) + self.bzpopmax(from: [key], timeout: timeout) .map { guard let response = $0 else { return nil } return (response.1, response.2) @@ -724,7 +723,7 @@ extension RedisClient { from keys: [RedisKey], timeout: TimeAmount = .seconds(0) ) -> EventLoopFuture<(String, Double, RESPValue)?> { - return self._bzpop(command: "BZPOPMAX", keys, timeout) + self._bzpop(command: "BZPOPMAX", keys, timeout) } func _bzpop( @@ -734,7 +733,7 @@ extension RedisClient { ) -> EventLoopFuture<(String, Double, RESPValue)?> { var args = keys.map(RESPValue.init) args.append(.init(bulk: timeout.seconds)) - + return send(command: command, with: args) // per the Redis docs, // we will receive either a nil response, @@ -776,7 +775,7 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: key), .init(bulk: amount.description), - element.convertedToRESPValue() + element.convertedToRESPValue(), ] return send(command: "ZINCRBY", with: args) .tryConverting() @@ -808,7 +807,7 @@ extension RedisClient { /// - destination: The key of the new sorted set from the result. /// - sources: The list of sorted set keys to treat as the source of the union. /// - weights: The multiplying factor to apply to the corresponding `sources` key based on index of the two parameters. - /// - aggregateMethod: The method of aggregating the values of the union. If one isn't specified, Redis will default to `.sum`. + /// - aggregate: The method of aggregating the values of the union. If one isn't specified, Redis will default to `.sum`. /// - Returns: The number of elements in the new sorted set. public func zunionstore( as destination: RedisKey, @@ -816,7 +815,7 @@ extension RedisClient { weights: [Int]? = nil, aggregateMethod aggregate: RedisSortedSetAggregateMethod? = nil ) -> EventLoopFuture { - return _zopstore(command: "ZUNIONSTORE", sources, destination, weights, aggregate) + _zopstore(command: "ZUNIONSTORE", sources, destination, weights, aggregate) } /// Calculates the intersection of two or more sorted sets and stores the result. @@ -827,7 +826,7 @@ extension RedisClient { /// - destination: The key of the new sorted set from the result. /// - sources: The list of sorted set keys to treat as the source of the intersection. /// - weights: The multiplying factor to apply to the corresponding `sources` key based on index of the two parameters. - /// - aggregateMethod: The method of aggregating the values of the intersection. If one isn't specified, Redis will default to `.sum`. + /// - aggregate: The method of aggregating the values of the intersection. If one isn't specified, Redis will default to `.sum`. /// - Returns: The number of elements in the new sorted set. public func zinterstore( as destination: RedisKey, @@ -835,7 +834,7 @@ extension RedisClient { weights: [Int]? = nil, aggregateMethod aggregate: RedisSortedSetAggregateMethod? = nil ) -> EventLoopFuture { - return _zopstore(command: "ZINTERSTORE", sources, destination, weights, aggregate) + _zopstore(command: "ZINTERSTORE", sources, destination, weights, aggregate) } func _zopstore( @@ -849,7 +848,7 @@ extension RedisClient { var args: [RESPValue] = [ .init(from: destination), - .init(bulk: sources.count) + .init(bulk: sources.count), ] args.append(convertingContentsOf: sources) @@ -884,6 +883,7 @@ extension RedisClient { /// - key: The key of the SortedSet /// - firstIndex: The index of the first element to include in the range of elements returned. /// - lastIndex: The index of the last element to include in the range of elements returned. + /// - includeScores: If to include scores in result value. /// - Returns: An array of elements found within the range specified. public func zrange( from key: RedisKey, @@ -891,9 +891,9 @@ extension RedisClient { lastIndex: Int, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self._zrange(command: "ZRANGE", key, firstIndex, lastIndex, includeScores) + self._zrange(command: "ZRANGE", key, firstIndex, lastIndex, includeScores) } - + /// Gets all elements from a SortedSet within the specified inclusive bounds of 0-based indices. /// /// To get the elements at index 4 through 7: @@ -927,15 +927,21 @@ extension RedisClient { /// - Parameters: /// - key: The key of the SortedSet to return elements from. /// - range: The range of inclusive indices of elements to get. + /// - includeScores: If to include scores in result value. /// - Returns: An array of elements found within the range specified. public func zrange( from key: RedisKey, indices range: ClosedRange, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrange(from: key, firstIndex: range.lowerBound, lastIndex: range.upperBound, includeScoresInResponse: includeScores) + self.zrange( + from: key, + firstIndex: range.lowerBound, + lastIndex: range.upperBound, + includeScoresInResponse: includeScores + ) } - + /// Gets all the elements from a SortedSet starting with the first index bound up to, but not including, the element at the last index bound. /// /// To get the elements at index 4 through 7: @@ -969,15 +975,21 @@ extension RedisClient { /// - Parameters: /// - key: The key of the SortedSet to return elements from. /// - range: The range of indices (inclusive lower, exclusive upper) elements to get. + /// - includeScores: If to include scores in result value. /// - Returns: An array of elements found within the range specified. public func zrange( from key: RedisKey, indices range: Range, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrange(from: key, firstIndex: range.lowerBound, lastIndex: range.upperBound - 1, includeScoresInResponse: includeScores) + self.zrange( + from: key, + firstIndex: range.lowerBound, + lastIndex: range.upperBound - 1, + includeScoresInResponse: includeScores + ) } - + /// Gets all elements from the index specified to the end of a SortedSet. /// /// To get all except the first 2 elements of a SortedSet: @@ -997,15 +1009,16 @@ extension RedisClient { /// - Parameters: /// - key: The key of the SortedSet to return elements from. /// - index: The index of the first element that will be in the returned values. + /// - includeScores: If to include scores in result value. /// - Returns: An array of elements from the SortedSet between the index and the end. public func zrange( from key: RedisKey, fromIndex index: Int, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrange(from: key, firstIndex: index, lastIndex: -1, includeScoresInResponse: includeScores) + self.zrange(from: key, firstIndex: index, lastIndex: -1, includeScoresInResponse: includeScores) } - + /// Gets all elements from the start of a SortedSet up to, and including, the element at the index specified. /// /// To get the first 3 elements of a SortedSet: @@ -1025,15 +1038,16 @@ extension RedisClient { /// - Parameters: /// - key: The key of the SortedSet to return elements from. /// - index: The index of the last element that will be in the returned values. + /// - includeScores: If to include scores in result value. /// - Returns: An array of elements from the start of a SortedSet to the index. public func zrange( from key: RedisKey, throughIndex index: Int, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrange(from: key, firstIndex: 0, lastIndex: index, includeScoresInResponse: includeScores) + self.zrange(from: key, firstIndex: 0, lastIndex: index, includeScoresInResponse: includeScores) } - + /// Gets all elements from the start of a SortedSet up to, but not including, the element at the index specified. /// /// To get the first 3 elements of a List: @@ -1053,15 +1067,16 @@ extension RedisClient { /// - Parameters: /// - key: The key of the SortedSet to return elements from. /// - index: The index of the last element to not include in the returned values. + /// - includeScores: If to include scores in the response. /// - Returns: An array of elements from the start of the SortedSet and up to the index. public func zrange( from key: RedisKey, upToIndex index: Int, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrange(from: key, firstIndex: 0, lastIndex: index - 1, includeScoresInResponse: includeScores) + self.zrange(from: key, firstIndex: 0, lastIndex: index - 1, includeScoresInResponse: includeScores) } - + /// Gets all elements from a SortedSet within the specified inclusive bounds of 0-based indices. /// /// See [https://redis.io/commands/zrevrange](https://redis.io/commands/zrevrange) @@ -1072,6 +1087,7 @@ extension RedisClient { /// - key: The key of the SortedSet /// - firstIndex: The index of the first element to include in the range of elements returned. /// - lastIndex: The index of the last element to include in the range of elements returned. + /// - includeScores: If to include scores in the response. /// - Returns: An array of elements found within the range specified. public func zrevrange( from key: RedisKey, @@ -1079,9 +1095,9 @@ extension RedisClient { lastIndex: Int, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self._zrange(command: "ZREVRANGE", key, firstIndex, lastIndex, includeScores) + self._zrange(command: "ZREVRANGE", key, firstIndex, lastIndex, includeScores) } - + /// Gets all elements from a SortedSet within the specified inclusive bounds of 0-based indices. /// /// To get the elements at index 4 through 7: @@ -1115,15 +1131,21 @@ extension RedisClient { /// - Parameters: /// - key: The key of the SortedSet to return elements from. /// - range: The range of inclusive indices of elements to get. + /// - includeScores: If to include scores in the response. /// - Returns: An array of elements found within the range specified. public func zrevrange( from key: RedisKey, indices range: ClosedRange, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrevrange(from: key, firstIndex: range.lowerBound, lastIndex: range.upperBound, includeScoresInResponse: includeScores) + self.zrevrange( + from: key, + firstIndex: range.lowerBound, + lastIndex: range.upperBound, + includeScoresInResponse: includeScores + ) } - + /// Gets all the elements from a SortedSet starting with the first index bound up to, but not including, the element at the last index bound. /// /// To get the elements at index 4 through 7: @@ -1157,15 +1179,21 @@ extension RedisClient { /// - Parameters: /// - key: The key of the SortedSet to return elements from. /// - range: The range of indices (inclusive lower, exclusive upper) elements to get. + /// - includeScores: If to include scores in the response. /// - Returns: An array of elements found within the range specified. public func zrevrange( from key: RedisKey, indices range: Range, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrevrange(from: key, firstIndex: range.lowerBound, lastIndex: range.upperBound - 1, includeScoresInResponse: includeScores) + self.zrevrange( + from: key, + firstIndex: range.lowerBound, + lastIndex: range.upperBound - 1, + includeScoresInResponse: includeScores + ) } - + /// Gets all elements from the index specified to the end of a SortedSet. /// /// To get all except the first 2 elements of a SortedSet: @@ -1185,15 +1213,16 @@ extension RedisClient { /// - Parameters: /// - key: The key of the SortedSet to return elements from. /// - index: The index of the first element that will be in the returned values. + /// - includeScores: If to include scores in the response. /// - Returns: An array of elements from the SortedSet between the index and the end. public func zrevrange( from key: RedisKey, fromIndex index: Int, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrevrange(from: key, firstIndex: index, lastIndex: -1, includeScoresInResponse: includeScores) + self.zrevrange(from: key, firstIndex: index, lastIndex: -1, includeScoresInResponse: includeScores) } - + /// Gets all elements from the start of a SortedSet up to, and including, the element at the index specified. /// /// To get the first 3 elements of a SortedSet: @@ -1213,15 +1242,16 @@ extension RedisClient { /// - Parameters: /// - key: The key of the SortedSet to return elements from. /// - index: The index of the last element that will be in the returned values. + /// - includeScores: If to include scores in result value. /// - Returns: An array of elements from the start of a SortedSet to the index. public func zrevrange( from key: RedisKey, throughIndex index: Int, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrevrange(from: key, firstIndex: 0, lastIndex: index, includeScoresInResponse: includeScores) + self.zrevrange(from: key, firstIndex: 0, lastIndex: index, includeScoresInResponse: includeScores) } - + /// Gets all elements from the start of a SortedSet up to, but not including, the element at the index specified. /// /// To get the first 3 elements of a List: @@ -1241,13 +1271,14 @@ extension RedisClient { /// - Parameters: /// - key: The key of the SortedSet to return elements from. /// - index: The index of the last element to not include in the returned values. + /// - includeScores: If to include scores in result value. /// - Returns: An array of elements from the start of the SortedSet and up to the index. public func zrevrange( from key: RedisKey, upToIndex index: Int, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrevrange(from: key, firstIndex: 0, lastIndex: index - 1, includeScoresInResponse: includeScores) + self.zrevrange(from: key, firstIndex: 0, lastIndex: index - 1, includeScoresInResponse: includeScores) } func _zrange( @@ -1260,7 +1291,7 @@ extension RedisClient { var args: [RESPValue] = [ .init(from: key), .init(bulk: start), - .init(bulk: stop) + .init(bulk: stop), ] if withScores { args.append(.init(bulk: "WITHSCORES")) } @@ -1291,9 +1322,15 @@ extension RedisClient { limitBy limit: (offset: Int, count: Int)? = nil, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return _zrangebyscore(command: "ZRANGEBYSCORE", key, (range.min.description, range.max.description), includeScores, limit) + _zrangebyscore( + command: "ZRANGEBYSCORE", + key, + (range.min.description, range.max.description), + includeScores, + limit + ) } - + /// Gets all elements from a SortedSet whose score is within the inclusive range specified. /// /// See `RedisZScoreBound` and [https://redis.io/commands/zrangebyscore](https://redis.io/commands/zrangebyscore) @@ -1312,14 +1349,14 @@ extension RedisClient { limitBy limit: (offset: Int, count: Int)? = nil, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrangebyscore( + self.zrangebyscore( from: key, withScoresBetween: (.inclusive(range.lowerBound), .inclusive(range.upperBound)), limitBy: limit, includeScoresInResponse: includeScores ) } - + /// Gets all elements from a SortedSet whose score is at least a minimum score up to, but not including, a max score. /// /// See `RedisZScoreBound` and [https://redis.io/commands/zrangebyscore](https://redis.io/commands/zrangebyscore) @@ -1338,14 +1375,14 @@ extension RedisClient { limitBy limit: (offset: Int, count: Int)? = nil, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrangebyscore( + self.zrangebyscore( from: key, withScoresBetween: (.inclusive(range.lowerBound), .exclusive(range.upperBound)), limitBy: limit, includeScoresInResponse: includeScores ) } - + /// Gets all elements from a SortedSet whose score is greater than a minimum score value. /// /// See `RedisZScoreBound` and [https://redis.io/commands/zrangebyscore](https://redis.io/commands/zrangebyscore) @@ -1354,7 +1391,7 @@ extension RedisClient { /// For the inverse, see `zrevrangebyscore(from:withMinimumScoreOf:limitBy:includeScoresInResponse:)`. /// - Parameters: /// - key: The key of the SortedSet. - /// - range: The minimum score bound an element in the SortedSet should have to be included in the response. + /// - minScore: The minimum score bound an element in the SortedSet should have to be included in the response. /// - limit: The optional offset and count of elements to query. /// - includeScores: Should the response array contain the elements AND their scores? If `true`, the response array will follow the pattern [Item_1, Score_1, Item_2, ...] /// - Returns: An array of elements from the SortedSet that were within the range provided, and optionally their scores. @@ -1364,7 +1401,7 @@ extension RedisClient { limitBy limit: (offset: Int, count: Int)? = nil, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrangebyscore( + self.zrangebyscore( from: key, withScoresBetween: (minScore, .inclusive(.infinity)), limitBy: limit, @@ -1380,7 +1417,7 @@ extension RedisClient { /// For the inverse, see `zrevrangebyscore(from:withMaximumScoreOf:limitBy:includeScoresInResponse:)`. /// - Parameters: /// - key: The key of the SortedSet. - /// - range: The maximum score bound an element in the SortedSet should have to be included in the response. + /// - maxScore: The maximum score bound an element in the SortedSet should have to be included in the response. /// - limit: The optional offset and count of elements to query. /// - includeScores: Should the response array contain the elements AND their scores? If `true`, the response array will follow the pattern [Item_1, Score_1, Item_2, ...] /// - Returns: An array of elements from the SortedSet that were within the range provided, and optionally their scores. @@ -1390,14 +1427,14 @@ extension RedisClient { limitBy limit: (offset: Int, count: Int)? = nil, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrangebyscore( + self.zrangebyscore( from: key, withScoresBetween: (.inclusive(-.infinity), maxScore), limitBy: limit, includeScoresInResponse: includeScores ) } - + /// Gets all elements from a SortedSet whose score is within the range specified. /// /// See `RedisZScoreBound` and [https://redis.io/commands/zrangebyscore](https://redis.io/commands/zrangebyscore) @@ -1416,9 +1453,15 @@ extension RedisClient { limitBy limit: (offset: Int, count: Int)? = nil, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return _zrangebyscore(command: "ZREVRANGEBYSCORE", key, (range.max.description, range.min.description), includeScores, limit) + _zrangebyscore( + command: "ZREVRANGEBYSCORE", + key, + (range.max.description, range.min.description), + includeScores, + limit + ) } - + /// Gets all elements from a SortedSet whose score is within the inclusive range specified. /// /// See `RedisZScoreBound` and [https://redis.io/commands/zrangebyscore](https://redis.io/commands/zrangebyscore) @@ -1437,14 +1480,14 @@ extension RedisClient { limitBy limit: (offset: Int, count: Int)? = nil, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrevrangebyscore( + self.zrevrangebyscore( from: key, withScoresBetween: (.inclusive(range.lowerBound), .inclusive(range.upperBound)), limitBy: limit, includeScoresInResponse: includeScores ) } - + /// Gets all elements from a SortedSet whose score is at least a minimum score up to, but not including, a max score. /// /// See `RedisZScoreBound` and [https://redis.io/commands/zrangebyscore](https://redis.io/commands/zrangebyscore) @@ -1463,14 +1506,14 @@ extension RedisClient { limitBy limit: (offset: Int, count: Int)? = nil, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrevrangebyscore( + self.zrevrangebyscore( from: key, withScoresBetween: (.inclusive(range.lowerBound), .exclusive(range.upperBound)), limitBy: limit, includeScoresInResponse: includeScores ) } - + /// Gets all elements from a SortedSet whose score is greater than a minimum score value. /// /// See `RedisZScoreBound` and [https://redis.io/commands/zrangebyscore](https://redis.io/commands/zrangebyscore) @@ -1479,8 +1522,8 @@ extension RedisClient { /// For the inverse, see `zrangebyscore(from:withMinimumScoreOf:limitBy:includeScoresInResponse:)`. /// - Parameters: /// - key: The key of the SortedSet. - /// - range: The minimum score bound an element in the SortedSet should have to be included in the response. - /// - limit: The optional offset and count of elements to query. + /// - limit: The minimum score bound an element in the SortedSet should have to be included in the response. + /// - minScore: The minimum score to compare against. /// - includeScores: Should the response array contain the elements AND their scores? If `true`, the response array will follow the pattern [Item_1, Score_1, Item_2, ...] /// - Returns: An array of elements from the SortedSet that were within the range provided, and optionally their scores. public func zrevrangebyscore( @@ -1489,14 +1532,14 @@ extension RedisClient { limitBy limit: (offset: Int, count: Int)? = nil, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrevrangebyscore( + self.zrevrangebyscore( from: key, withScoresBetween: (minScore, .inclusive(.infinity)), limitBy: limit, includeScoresInResponse: includeScores ) } - + /// Gets all elements from a SortedSet whose score is less than a maximum score value. /// /// See `RedisZScoreBound` and [https://redis.io/commands/zrangebyscore](https://redis.io/commands/zrangebyscore) @@ -1505,8 +1548,8 @@ extension RedisClient { /// For the inverse, see `zrangebyscore(from:withMaximumScoreOf:limitBy:includeScoresInResponse:)`. /// - Parameters: /// - key: The key of the SortedSet. - /// - range: The maximum score bound an element in the SortedSet should have to be included in the response. - /// - limit: The optional offset and count of elements to query. + /// - maxScore: The max score to compare against. + /// - limit: The maximum score bound an element in the SortedSet should have to be included in the response. /// - includeScores: Should the response array contain the elements AND their scores? If `true`, the response array will follow the pattern [Item_1, Score_1, Item_2, ...] /// - Returns: An array of elements from the SortedSet that were within the range provided, and optionally their scores. public func zrevrangebyscore( @@ -1515,7 +1558,7 @@ extension RedisClient { limitBy limit: (offset: Int, count: Int)? = nil, includeScoresInResponse includeScores: Bool = false ) -> EventLoopFuture<[RESPValue]> { - return self.zrevrangebyscore( + self.zrevrangebyscore( from: key, withScoresBetween: (.inclusive(-.infinity), maxScore), limitBy: limit, @@ -1533,7 +1576,7 @@ extension RedisClient { var args: [RESPValue] = [ .init(from: key), .init(bulk: range.min), - .init(bulk: range.max) + .init(bulk: range.max), ] if withScores { args.append(.init(bulk: "WITHSCORES")) } @@ -1568,7 +1611,7 @@ extension RedisClient { /// - Parameters: /// - key: The key of the SortedSet that will be counted. /// - range: The min and max value bounds for filtering elements by. - /// - limitBy: The optional offset and count of elements to query. + /// - limit: The optional offset and count of elements to query. /// - Returns: An array of elements from the SortedSet that were within the range provided. @inlinable public func zrangebylex( @@ -1576,9 +1619,9 @@ extension RedisClient { withValuesBetween range: (min: RedisZLexBound, max: RedisZLexBound), limitBy limit: (offset: Int, count: Int)? = nil ) -> EventLoopFuture<[RESPValue]> { - return self._zrangebylex(command: "ZRANGEBYLEX", key, (range.min.description, range.max.description), limit) + self._zrangebylex(command: "ZRANGEBYLEX", key, (range.min.description, range.max.description), limit) } - + /// Gets all elements from a SortedSet whose lexiographical value is greater than a minimum value. /// /// ``` @@ -1603,9 +1646,9 @@ extension RedisClient { withMinimumValueOf minValue: RedisZLexBound, limitBy limit: (offset: Int, count: Int)? = nil ) -> EventLoopFuture<[RESPValue]> { - return self.zrangebylex(from: key, withValuesBetween: (minValue, .positiveInfinity), limitBy: limit) + self.zrangebylex(from: key, withValuesBetween: (minValue, .positiveInfinity), limitBy: limit) } - + /// Gets all elements from a SortedSet whose lexiographical value is less than a maximum value. /// /// ``` @@ -1621,7 +1664,7 @@ extension RedisClient { /// For the inverse, see `zrevrangebylex(from:withMaximumValueOf:limitBy:)`. /// - Parameters: /// - key: The key of the SortedSet. - /// - minValue: The maximum lexiographical value an element in the SortedSet should have to be included in the result set. + /// - maxValue: The maximum lexiographical value an element in the SortedSet should have to be included in the result set. /// - limit: The optional offset and count of elements to query /// - Returns: An array of elements from the SortedSet below the `maxValue` threshold. @inlinable @@ -1630,9 +1673,9 @@ extension RedisClient { withMaximumValueOf maxValue: RedisZLexBound, limitBy limit: (offset: Int, count: Int)? = nil ) -> EventLoopFuture<[RESPValue]> { - return self.zrangebylex(from: key, withValuesBetween: (.negativeInfinity, maxValue), limitBy: limit) + self.zrangebylex(from: key, withValuesBetween: (.negativeInfinity, maxValue), limitBy: limit) } - + /// Gets all elements from a SortedSet whose lexiographical values are between the range specified. /// /// For example: @@ -1650,7 +1693,7 @@ extension RedisClient { /// - Parameters: /// - key: The key of the SortedSet that will be counted. /// - range: The min and max value bounds for filtering elements by. - /// - limitBy: The optional offset and count of elements to query. + /// - limit: The optional offset and count of elements to query. /// - Returns: An array of elements from the SortedSet that were within the range provided. @inlinable public func zrevrangebylex( @@ -1658,9 +1701,9 @@ extension RedisClient { withValuesBetween range: (min: RedisZLexBound, max: RedisZLexBound), limitBy limit: (offset: Int, count: Int)? = nil ) -> EventLoopFuture<[RESPValue]> { - return self._zrangebylex(command: "ZREVRANGEBYLEX", key, (range.max.description, range.min.description), limit) + self._zrangebylex(command: "ZREVRANGEBYLEX", key, (range.max.description, range.min.description), limit) } - + /// Gets all elements from a SortedSet whose lexiographical value is greater than a minimum value. /// /// ``` @@ -1685,9 +1728,9 @@ extension RedisClient { withMinimumValueOf minValue: RedisZLexBound, limitBy limit: (offset: Int, count: Int)? = nil ) -> EventLoopFuture<[RESPValue]> { - return self.zrevrangebylex(from: key, withValuesBetween: (minValue, .positiveInfinity), limitBy: limit) + self.zrevrangebylex(from: key, withValuesBetween: (minValue, .positiveInfinity), limitBy: limit) } - + /// Gets all elements from a SortedSet whose lexiographical value is less than a maximum value. /// /// ``` @@ -1703,7 +1746,7 @@ extension RedisClient { /// For the inverse, see `zrangebylex(from:withMaximumValueOf:limitBy:)`. /// - Parameters: /// - key: The key of the SortedSet. - /// - minValue: The maximum lexiographical value an element in the SortedSet should have to be included in the result set. + /// - maxValue: The maximum lexiographical value an element in the SortedSet should have to be included in the result set. /// - limit: The optional offset and count of elements to query /// - Returns: An array of elements from the SortedSet below the `maxValue` threshold. @inlinable @@ -1712,7 +1755,7 @@ extension RedisClient { withMaximumValueOf maxValue: RedisZLexBound, limitBy limit: (offset: Int, count: Int)? = nil ) -> EventLoopFuture<[RESPValue]> { - return self.zrevrangebylex(from: key, withValuesBetween: (.negativeInfinity, maxValue), limitBy: limit) + self.zrevrangebylex(from: key, withValuesBetween: (.negativeInfinity, maxValue), limitBy: limit) } @usableFromInline @@ -1725,11 +1768,11 @@ extension RedisClient { var args: [RESPValue] = [ .init(from: key), .init(bulk: range.min), - .init(bulk: range.max) + .init(bulk: range.max), ] if let l = limit { - args.reserveCapacity(6) // 3 above, plus 3 being added + args.reserveCapacity(6) // 3 above, plus 3 being added args.append(.init(bulk: "LIMIT")) args.append(.init(bulk: l.offset)) args.append(.init(bulk: l.count)) @@ -1756,11 +1799,11 @@ extension RedisClient { var args: [RESPValue] = [.init(from: key)] args.append(convertingContentsOf: elements) - + return send(command: "ZREM", with: args) .tryConverting() } - + /// Removes the specified elements from a sorted set. /// /// See [https://redis.io/commands/zrem](https://redis.io/commands/zrem) @@ -1770,7 +1813,7 @@ extension RedisClient { /// - Returns: The number of elements removed from the set. @inlinable public func zrem(_ elements: Value..., from key: RedisKey) -> EventLoopFuture { - return self.zrem(elements, from: key) + self.zrem(elements, from: key) } } @@ -1800,12 +1843,12 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: key), .init(bulk: range.min.description), - .init(bulk: range.max.description) + .init(bulk: range.max.description), ] return send(command: "ZREMRANGEBYLEX", with: args) .tryConverting() } - + /// Removes elements from a SortedSet whose lexiographical values are greater than a minimum value. /// /// For example: @@ -1826,9 +1869,9 @@ extension RedisClient { from key: RedisKey, withMinimumValueOf minValue: RedisZLexBound ) -> EventLoopFuture { - return self.zremrangebylex(from: key, withValuesBetween: (minValue, .positiveInfinity)) + self.zremrangebylex(from: key, withValuesBetween: (minValue, .positiveInfinity)) } - + /// Removes elements from a SortedSet whose lexiographical values are less than a maximum value. /// /// For example: @@ -1849,7 +1892,7 @@ extension RedisClient { from key: RedisKey, withMaximumValueOf maxValue: RedisZLexBound ) -> EventLoopFuture { - return self.zremrangebylex(from: key, withValuesBetween: (.negativeInfinity, maxValue)) + self.zremrangebylex(from: key, withValuesBetween: (.negativeInfinity, maxValue)) } } @@ -1868,12 +1911,12 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: key), .init(bulk: firstIndex), - .init(bulk: lastIndex) + .init(bulk: lastIndex), ] return self.send(command: "ZREMRANGEBYRANK", with: args) .tryConverting() } - + /// Removes all elements from a SortedSet within the specified inclusive bounds of 0-based indices. /// /// See [https://redis.io/commands/zremrangebyrank](https://redis.io/commands/zremrangebyrank) @@ -1886,9 +1929,9 @@ extension RedisClient { /// - range: The range of inclusive indices of elements to remove. /// - Returns: The count of elements that were removed from the SortedSet. public func zremrangebyrank(from key: RedisKey, indices range: ClosedRange) -> EventLoopFuture { - return self.zremrangebyrank(from: key, firstIndex: range.lowerBound, lastIndex: range.upperBound) + self.zremrangebyrank(from: key, firstIndex: range.lowerBound, lastIndex: range.upperBound) } - + /// Removes all elements from a SortedSet starting with the first index bound up to, but not including, the element at the last index bound. /// /// See [https://redis.io/commands/zremrangebyrank](https://redis.io/commands/zremrangebyrank) @@ -1901,9 +1944,9 @@ extension RedisClient { /// - range: The range of indices (inclusive lower, exclusive upper) elements to remove. /// - Returns: The count of elements that were removed from the SortedSet. public func zremrangebyrank(from key: RedisKey, indices range: Range) -> EventLoopFuture { - return self.zremrangebyrank(from: key, firstIndex: range.lowerBound, lastIndex: range.upperBound - 1) + self.zremrangebyrank(from: key, firstIndex: range.lowerBound, lastIndex: range.upperBound - 1) } - + /// Removes all elements from the index specified to the end of a SortedSet. /// /// See [https://redis.io/commands/zremrangebyrank](https://redis.io/commands/zremrangebyrank) @@ -1912,9 +1955,9 @@ extension RedisClient { /// - index: The index of the first element that will be removed. /// - Returns: The count of elements that were removed from the SortedSet. public func zremrangebyrank(from key: RedisKey, fromIndex index: Int) -> EventLoopFuture { - return self.zremrangebyrank(from: key, firstIndex: index, lastIndex: -1) + self.zremrangebyrank(from: key, firstIndex: index, lastIndex: -1) } - + /// Removes all elements from the start of a SortedSet up to, and including, the element at the index specified. /// /// See [https://redis.io/commands/zremrangebyrank](https://redis.io/commands/zremrangebyrank) @@ -1923,9 +1966,9 @@ extension RedisClient { /// - index: The index of the last element that will be removed. /// - Returns: The count of elements that were removed from the SortedSet. public func zremrangebyrank(from key: RedisKey, throughIndex index: Int) -> EventLoopFuture { - return self.zremrangebyrank(from: key, firstIndex: 0, lastIndex: index) + self.zremrangebyrank(from: key, firstIndex: 0, lastIndex: index) } - + /// Removes all elements from the start of a SortedSet up to, but not including, the element at the index specified. /// /// See [https://redis.io/commands/zremrangebyrank](https://redis.io/commands/zremrangebyrank) @@ -1935,7 +1978,7 @@ extension RedisClient { /// - index: The index of the last element to not remove. /// - Returns: The count of elements that were removed from the SortedSet. public func zremrangebyrank(from key: RedisKey, upToIndex index: Int) -> EventLoopFuture { - return self.zremrangebyrank(from: key, firstIndex: 0, lastIndex: index - 1) + self.zremrangebyrank(from: key, firstIndex: 0, lastIndex: index - 1) } } @@ -1956,12 +1999,12 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: key), .init(bulk: range.min.description), - .init(bulk: range.max.description) + .init(bulk: range.max.description), ] return self.send(command: "ZREMRANGEBYSCORE", with: args) .tryConverting() } - + /// Removes elements from a SortedSet whose score is within the inclusive range specified. /// /// See `RedisZScoreBound` and [https://redis.io/commands/zremrangebyscore](https://redis.io/commands/zremrangebyscore) @@ -1970,9 +2013,12 @@ extension RedisClient { /// - range: The inclusive range of scores to filter elements by. /// - Returns: The count of elements that were removed from the SortedSet. public func zremrangebyscore(from key: RedisKey, withScores range: ClosedRange) -> EventLoopFuture { - return self.zremrangebyscore(from: key, withScoresBetween: (.inclusive(range.lowerBound), .inclusive(range.upperBound))) + self.zremrangebyscore( + from: key, + withScoresBetween: (.inclusive(range.lowerBound), .inclusive(range.upperBound)) + ) } - + /// Removes elements from a SortedSet whose score is at least a minimum score up to, but not including, a max score. /// /// See `RedisZScoreBound` and [https://redis.io/commands/zremrangebyscore](https://redis.io/commands/zremrangebyscore) @@ -1981,9 +2027,12 @@ extension RedisClient { /// - range: A range with an inclusive lower and exclusive upper bound of scores to filter elements by. /// - Returns: The count of elements that were removed from the SortedSet. public func zremrangebyscore(from key: RedisKey, withScores range: Range) -> EventLoopFuture { - return self.zremrangebyscore(from: key, withScoresBetween: (.inclusive(range.lowerBound), .exclusive(range.upperBound))) + self.zremrangebyscore( + from: key, + withScoresBetween: (.inclusive(range.lowerBound), .exclusive(range.upperBound)) + ) } - + /// Removes elements from a SortedSet whose score is greater than a minimum score value. /// /// See `RedisZScoreBound` and [https://redis.io/commands/zremrangebyscore](https://redis.io/commands/zremrangebyscore) @@ -1991,18 +2040,24 @@ extension RedisClient { /// - key: The key of the SortedSet to remove elements from. /// - minScore: The minimum score bound an element in the SortedSet should have to be removed. /// - Returns: The count of elements that were removed from the SortedSet. - public func zremrangebyscore(from key: RedisKey, withMinimumScoreOf minScore: RedisZScoreBound) -> EventLoopFuture { - return self.zremrangebyscore(from: key, withScoresBetween: (minScore, .inclusive(.infinity))) + public func zremrangebyscore( + from key: RedisKey, + withMinimumScoreOf minScore: RedisZScoreBound + ) -> EventLoopFuture { + self.zremrangebyscore(from: key, withScoresBetween: (minScore, .inclusive(.infinity))) } - + /// Removes elements from a SortedSet whose score is less than a maximum score value. /// /// See `RedisZScoreBound` and [https://redis.io/commands/zremrangebyscore](https://redis.io/commands/zremrangebyscore) /// - Parameters: /// - key: The key of the SortedSet to remove elements from. - /// - minScore: The maximum score bound an element in the SortedSet should have to be removed. + /// - maxScore: The maximum score bound an element in the SortedSet should have to be removed. /// - Returns: The count of elements that were removed from the SortedSet. - public func zremrangebyscore(from key: RedisKey, withMaximumScoreOf maxScore: RedisZScoreBound) -> EventLoopFuture { - return self.zremrangebyscore(from: key, withScoresBetween: (.inclusive(-.infinity), maxScore)) + public func zremrangebyscore( + from key: RedisKey, + withMaximumScoreOf maxScore: RedisZScoreBound + ) -> EventLoopFuture { + self.zremrangebyscore(from: key, withScoresBetween: (.inclusive(-.infinity), maxScore)) } } diff --git a/Sources/RediStack/Commands/StringCommands.swift b/Sources/RediStack/Commands/StringCommands.swift index 942e5cf6..692524ec 100644 --- a/Sources/RediStack/Commands/StringCommands.swift +++ b/Sources/RediStack/Commands/StringCommands.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -39,8 +39,8 @@ extension RedisClient { _ key: RedisKey, as type: StoredType.Type ) -> EventLoopFuture { - return self.get(key) - .map { return StoredType(fromRESP: $0) } + self.get(key) + .map { StoredType(fromRESP: $0) } } /// Gets the values of all specified keys, using `.null` to represent non-existant values. @@ -64,9 +64,10 @@ extension RedisClient { /// - type: The type to convert the values to. /// - Returns: The values stored at the keys provided, matching the same order. Values that fail the `RESPValue` conversion will be `nil`. @inlinable - public func mget(_ keys: [RedisKey], as type: Value.Type) -> EventLoopFuture<[Value?]> { - return self.mget(keys) - .map { return $0.map(Value.init(fromRESP:)) } + public func mget(_ keys: [RedisKey], as type: Value.Type) -> EventLoopFuture<[Value?]> + { + self.mget(keys) + .map { $0.map(Value.init(fromRESP:)) } } /// Gets the values of all specified keys, using `.null` to represent non-existant values. @@ -75,7 +76,7 @@ extension RedisClient { /// - Parameter keys: The list of keys to fetch the values from. /// - Returns: The values stored at the keys provided, matching the same order. public func mget(_ keys: RedisKey...) -> EventLoopFuture<[RESPValue]> { - return self.mget(keys) + self.mget(keys) } /// Gets the values of all specified keys, using `.null` to represent non-existant values. @@ -86,8 +87,9 @@ extension RedisClient { /// - type: The type to convert the values to. /// - Returns: The values stored at the keys provided, matching the same order. Values that fail the `RESPValue` conversion will be `nil`. @inlinable - public func mget(_ keys: RedisKey..., as type: Value.Type) -> EventLoopFuture<[Value?]> { - return self.mget(keys, as: type) + public func mget(_ keys: RedisKey..., as type: Value.Type) -> EventLoopFuture<[Value?]> + { + self.mget(keys, as: type) } } @@ -110,7 +112,7 @@ public struct RedisSetCommandCondition: Hashable { /// The `RESPValue` representation of the condition. @usableFromInline internal var commandArgument: RESPValue? { - return self.condition.map { RESPValue(from: $0.rawValue) } + self.condition.map { RESPValue(from: $0.rawValue) } } } @@ -170,7 +172,7 @@ extension RedisSetCommandExpiration { /// Redis documentation refers to this as the option "EX". /// - Important: The actual amount used will be the specified value or `1`, whichever is larger. public static func seconds(_ amount: Int) -> RedisSetCommandExpiration { - return RedisSetCommandExpiration(.seconds(max(amount, 1))) + RedisSetCommandExpiration(.seconds(max(amount, 1))) } /// Expire the key after the given number of milliseconds. @@ -178,7 +180,7 @@ extension RedisSetCommandExpiration { /// Redis documentation refers to this as the option "PX". /// - Important: The actual amount used will be the specified value or `1`, whichever is larger. public static func milliseconds(_ amount: Int) -> RedisSetCommandExpiration { - return RedisSetCommandExpiration(.milliseconds(max(amount, 1))) + RedisSetCommandExpiration(.milliseconds(max(amount, 1))) } } @@ -206,7 +208,7 @@ extension RedisClient { public func append(_ value: Value, to key: RedisKey) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: key), - value.convertedToRESPValue() + value.convertedToRESPValue(), ] return send(command: "APPEND", with: args) .tryConverting() @@ -227,7 +229,7 @@ extension RedisClient { public func set(_ key: RedisKey, to value: Value) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: key), - value.convertedToRESPValue() + value.convertedToRESPValue(), ] return send(command: "SET", with: args) .map { _ in () } @@ -257,7 +259,7 @@ extension RedisClient { ) -> EventLoopFuture { var args: [RESPValue] = [ .init(from: key), - value.convertedToRESPValue() + value.convertedToRESPValue(), ] if let conditionArgument = condition.commandArgument { @@ -269,7 +271,7 @@ extension RedisClient { } return self.send(command: "SET", with: args) - .map { return $0.isNull ? .conditionNotMet : .ok } + .map { $0.isNull ? .conditionNotMet : .ok } } /// Sets the key to the provided value if the key does not exist. @@ -286,7 +288,7 @@ extension RedisClient { public func setnx(_ key: RedisKey, to value: Value) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: key), - value.convertedToRESPValue() + value.convertedToRESPValue(), ] return self.send(command: "SETNX", with: args) .tryConverting(to: Int.self) @@ -314,7 +316,7 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: key), .init(from: max(1, expiration)), - value.convertedToRESPValue() + value.convertedToRESPValue(), ] return self.send(command: "SETEX", with: args) .map { _ in () } @@ -341,7 +343,7 @@ extension RedisClient { let args: [RESPValue] = [ .init(from: key), .init(from: max(1, expiration)), - value.convertedToRESPValue() + value.convertedToRESPValue(), ] return self.send(command: "PSETEX", with: args) .map { _ in () } @@ -355,7 +357,7 @@ extension RedisClient { /// - Returns: An `EventLoopFuture` that resolves if the operation was successful. @inlinable public func mset(_ operations: [RedisKey: Value]) -> EventLoopFuture { - return _mset(command: "MSET", operations) + _mset(command: "MSET", operations) .map { _ in () } } @@ -367,9 +369,9 @@ extension RedisClient { /// - Returns: `true` if the operation successfully completed. @inlinable public func msetnx(_ operations: [RedisKey: Value]) -> EventLoopFuture { - return _mset(command: "MSETNX", operations) + _mset(command: "MSETNX", operations) .tryConverting(to: Int.self) - .map { return $0 == 1 } + .map { $0 == 1 } } @usableFromInline @@ -419,7 +421,7 @@ extension RedisClient { ) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: key), - .init(bulk: count) + .init(bulk: count), ] return send(command: "INCRBY", with: args) .tryConverting() @@ -439,7 +441,7 @@ extension RedisClient { ) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: key), - count.convertedToRESPValue() + count.convertedToRESPValue(), ] return send(command: "INCRBYFLOAT", with: args) .tryConverting() @@ -475,7 +477,7 @@ extension RedisClient { ) -> EventLoopFuture { let args: [RESPValue] = [ .init(from: key), - .init(bulk: count) + .init(bulk: count), ] return send(command: "DECRBY", with: args) .tryConverting() diff --git a/Sources/RediStack/ConnectionPool/ConnectionPool.swift b/Sources/RediStack/ConnectionPool/ConnectionPool.swift index 9fa797d7..5539d721 100644 --- a/Sources/RediStack/ConnectionPool/ConnectionPool.swift +++ b/Sources/RediStack/ConnectionPool/ConnectionPool.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020-2023 RediStack project authors +// Copyright (c) 2020-2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -82,7 +82,7 @@ internal final class ConnectionPool { /// The number of connections that are "live": either in the pool, in the process of being created, or /// leased to users. private var activeConnectionCount: Int { - return self.availableConnections.count + self.pendingConnectionCount + self.leasedConnectionCount + self.availableConnections.count + self.pendingConnectionCount + self.leasedConnectionCount } /// Whether a connection can be added into the availableConnections pool when it's returned. This is true @@ -160,7 +160,7 @@ internal final class ConnectionPool { return self._leaseConnection(deadline, logger: logger) } else { return self.loop.flatSubmit { - return self._leaseConnection(deadline, logger: logger) + self._leaseConnection(deadline, logger: logger) } } } @@ -188,9 +188,12 @@ extension ConnectionPool { } var neededConnections = self.minimumConnectionCount - self.activeConnectionCount - logger.trace("refilling connections", metadata: [ - RedisLogging.MetadataKeys.connectionCount: "\(neededConnections)" - ]) + logger.trace( + "refilling connections", + metadata: [ + RedisLogging.MetadataKeys.connectionCount: "\(neededConnections)" + ] + ) while neededConnections > 0 { self._createConnection(backoff: self.initialBackoffDelay, startIn: .nanoseconds(0), logger: logger) neededConnections -= 1 @@ -221,10 +224,13 @@ extension ConnectionPool { private func connectionCreationSucceeded(_ connection: RedisConnection, logger: Logger) { self.loop.assertInEventLoop() - - logger.trace("connection creation succeeded", metadata: [ - RedisLogging.MetadataKeys.connectionID: "\(connection.id)" - ]) + + logger.trace( + "connection creation succeeded", + metadata: [ + RedisLogging.MetadataKeys.connectionID: "\(connection.id)" + ] + ) switch self.state { case .closing: @@ -232,9 +238,12 @@ extension ConnectionPool { self.closeConnectionForShutdown(connection) case .closed: // This is programmer error, we shouldn't have entered this state. - logger.critical("new connection created on a closed pool", metadata: [ - RedisLogging.MetadataKeys.connectionID: "\(connection.id)" - ]) + logger.critical( + "new connection created on a closed pool", + metadata: [ + RedisLogging.MetadataKeys.connectionID: "\(connection.id)" + ] + ) preconditionFailure("In closed while pending connections were outstanding.") case .active: // Great, we want this. We'll be "returning" it to the pool. First, @@ -247,13 +256,16 @@ extension ConnectionPool { private func connectionCreationFailed(_ error: Error, backoff: TimeAmount, logger: Logger) { self.loop.assertInEventLoop() - logger.error("failed to create connection for pool", metadata: [ - RedisLogging.MetadataKeys.error: "\(error)" - ]) + logger.error( + "failed to create connection for pool", + metadata: [ + RedisLogging.MetadataKeys.error: "\(error)" + ] + ) switch self.state { case .active: - break // continue further down + break // continue further down case .closing(let remaining, let promise): if remaining == 1 { @@ -278,10 +290,12 @@ extension ConnectionPool { // 3. For either kind, if the number of active connections is less than the minimum. let shouldReconnect: Bool if self.leaky { - shouldReconnect = (self.connectionWaiters.count > self.pendingConnectionCount) + shouldReconnect = + (self.connectionWaiters.count > self.pendingConnectionCount) || (self.minimumConnectionCount > self.activeConnectionCount) } else { - shouldReconnect = (!self.connectionWaiters.isEmpty && self.maximumConnectionCount > self.activeConnectionCount) + shouldReconnect = + (!self.connectionWaiters.isEmpty && self.maximumConnectionCount > self.activeConnectionCount) || (self.minimumConnectionCount > self.activeConnectionCount) } @@ -292,10 +306,13 @@ extension ConnectionPool { // Ok, we need the new connection. let newBackoff = TimeAmount.nanoseconds(Int64(Float32(backoff.nanoseconds) * self.backoffFactor)) - logger.debug("reconnecting after failed connection attempt", metadata: [ - RedisLogging.MetadataKeys.poolConnectionRetryBackoff: "\(backoff)ns", - RedisLogging.MetadataKeys.poolConnectionRetryNewBackoff: "\(newBackoff)ns" - ]) + logger.debug( + "reconnecting after failed connection attempt", + metadata: [ + RedisLogging.MetadataKeys.poolConnectionRetryBackoff: "\(backoff)ns", + RedisLogging.MetadataKeys.poolConnectionRetryNewBackoff: "\(newBackoff)ns", + ] + ) self._createConnection(backoff: newBackoff, startIn: backoff, logger: logger) } @@ -381,9 +398,12 @@ extension ConnectionPool { // that yet, so double-check. Leave the dead ones there: we'll get them later. while let connection = self.availableConnections.popLast() { if connection.isConnected { - logger.trace("found available connection", metadata: [ - RedisLogging.MetadataKeys.connectionID: "\(connection.id)" - ]) + logger.trace( + "found available connection", + metadata: [ + RedisLogging.MetadataKeys.connectionID: "\(connection.id)" + ] + ) self.leaseConnection(connection, to: waiter) return waiter.futureResult } @@ -511,11 +531,11 @@ extension ConnectionPool { private var result: EventLoopPromise var id: ObjectIdentifier { - return ObjectIdentifier(self.result.futureResult) + ObjectIdentifier(self.result.futureResult) } var futureResult: EventLoopFuture { - return self.result.futureResult + self.result.futureResult } init(result: EventLoopPromise) { diff --git a/Sources/RediStack/ConnectionPool/RedisConnectionPool+Configuration.swift b/Sources/RediStack/ConnectionPool/RedisConnectionPool+Configuration.swift index 841cd8d5..538f0e52 100644 --- a/Sources/RediStack/ConnectionPool/RedisConnectionPool+Configuration.swift +++ b/Sources/RediStack/ConnectionPool/RedisConnectionPool+Configuration.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020-2023 RediStack project authors +// Copyright (c) 2020-2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,9 +12,9 @@ // //===----------------------------------------------------------------------===// +import Logging import NIOCore import NIOPosix -import Logging extension RedisConnectionPool { /// A configuration object for creating Redis connections with a connection pool. @@ -93,7 +93,8 @@ extension RedisConnectionPool { /// The maximum number of connections to for this pool, either to be preserved or as a hard limit. public let maximumConnectionCount: RedisConnectionPoolSize /// The configuration object that controls the connection retry behavior. - public let connectionRetryConfiguration: (backoff: (initialDelay: TimeAmount, factor: Float32), timeout: TimeAmount) + public let connectionRetryConfiguration: + (backoff: (initialDelay: TimeAmount, factor: Float32), timeout: TimeAmount) /// Called when a connection in the pool is closed unexpectedly. public let onUnexpectedConnectionClose: ((RedisConnection) -> Void)? // these need to be var so they can be updated by the pool in some cases @@ -116,6 +117,7 @@ extension RedisConnectionPool { /// Subsequent backoffs are computed by compounding this value by `connectionBackoffFactor`. /// - connectionRetryTimeout: The max time to wait for a connection to be available before failing a particular command or connection operation. /// The default is 60 seconds. + /// - onUnexpectedConnectionClose: Called when the connection unexpectedly is closed. /// - poolDefaultLogger: The `Logger` used by the connection pool itself. public init( initialServerConnectionAddresses: [SocketAddress], @@ -134,7 +136,7 @@ extension RedisConnectionPool { self.minimumConnectionCount = minimumConnectionCount self.connectionRetryConfiguration = ( (initialConnectionBackoffDelay, connectionBackoffFactor), - connectionRetryTimeout ?? .milliseconds(10) // always default to a baseline 10ms + connectionRetryTimeout ?? .milliseconds(10) // always default to a baseline 10ms ) self.onUnexpectedConnectionClose = onUnexpectedConnectionClose self.poolDefaultLogger = poolDefaultLogger ?? .redisBaseConnectionPoolLogger diff --git a/Sources/RediStack/ConnectionPool/RedisConnectionPool.swift b/Sources/RediStack/ConnectionPool/RedisConnectionPool.swift index c6be8b8b..ac00244f 100644 --- a/Sources/RediStack/ConnectionPool/RedisConnectionPool.swift +++ b/Sources/RediStack/ConnectionPool/RedisConnectionPool.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020-2023 RediStack project authors +// Copyright (c) 2020-2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -11,10 +11,12 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// -import struct Foundation.UUID -import NIOCore -import NIOConcurrencyHelpers + import Logging +import NIOConcurrencyHelpers +import NIOCore + +import struct Foundation.UUID /// A `RedisConnectionPool` is an implementation of `RedisClient` backed by a pool of connections to Redis, /// rather than a single one. @@ -152,12 +154,13 @@ extension RedisConnectionPool { /// - Parameter operation: A closure that receives exclusive access to the provided `RedisConnection` for the lifetime of the closure for specialized Redis command chains. /// - Returns: A `NIO.EventLoopFuture` that resolves the value of the `NIO.EventLoopFuture` in the provided closure operation. @inlinable - public func leaseConnection(_ operation: @escaping (RedisConnection) -> EventLoopFuture) -> EventLoopFuture { - return self.forwardOperationToConnection( + public func leaseConnection(_ operation: @escaping (RedisConnection) -> EventLoopFuture) -> EventLoopFuture + { + self.forwardOperationToConnection( { (connection, returnConnection, context) in - return operation(connection) + operation(connection) .always { _ in returnConnection(connection, context) } }, preferredConnection: nil, @@ -177,9 +180,12 @@ extension RedisConnectionPool { /// If one is not provided, the pool will use its default logger. public func updateConnectionAddresses(_ newAddresses: [SocketAddress], logger: Logger? = nil) { self.prepareLoggerForUse(logger) - .info("pool updated with new target addresses", metadata: [ - RedisLogging.MetadataKeys.newConnectionPoolTargetAddresses: "\(newAddresses)" - ]) + .info( + "pool updated with new target addresses", + metadata: [ + RedisLogging.MetadataKeys.newConnectionPoolTargetAddresses: "\(newAddresses)" + ] + ) self.loop.execute { self.serverConnectionAddresses.update(newAddresses) @@ -229,7 +235,8 @@ extension RedisConnectionPool { return targetLoop.makeFailedFuture(error) } - return RedisConnection + return + RedisConnection .make( configuration: connectionConfig, boundEventLoop: targetLoop, @@ -257,11 +264,11 @@ extension RedisConnectionPool: RedisClient { public var eventLoop: EventLoop { self.loop } public func logging(to logger: Logger) -> RedisClient { - return UserContextRedisClient(client: self, logger: self.prepareLoggerForUse(logger)) + UserContextRedisClient(client: self, logger: self.prepareLoggerForUse(logger)) } public func send(command: String, with arguments: [RESPValue]) -> EventLoopFuture { - return self.send(command: command, with: arguments, logger: nil) + self.send(command: command, with: arguments, logger: nil) } public func subscribe( @@ -270,7 +277,7 @@ extension RedisConnectionPool: RedisClient { onSubscribe subscribeHandler: RedisSubscriptionChangeHandler?, onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler? ) -> EventLoopFuture { - return self.subscribe( + self.subscribe( to: channels, messageReceiver: receiver, onSubscribe: subscribeHandler, @@ -285,7 +292,7 @@ extension RedisConnectionPool: RedisClient { onSubscribe subscribeHandler: RedisSubscriptionChangeHandler?, onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler? ) -> EventLoopFuture { - return self.psubscribe( + self.psubscribe( to: patterns, messageReceiver: receiver, onSubscribe: subscribeHandler, @@ -295,23 +302,24 @@ extension RedisConnectionPool: RedisClient { } public func unsubscribe(from channels: [RedisChannelName]) -> EventLoopFuture { - return self.unsubscribe(from: channels, logger: nil) + self.unsubscribe(from: channels, logger: nil) } public func punsubscribe(from patterns: [String]) -> EventLoopFuture { - return self.punsubscribe(from: patterns, logger: nil) + self.punsubscribe(from: patterns, logger: nil) } } // MARK: RedisClientWithUserContext conformance extension RedisConnectionPool: RedisClientWithUserContext { internal func send(command: String, with arguments: [RESPValue], logger: Logger?) -> EventLoopFuture { - return self.forwardOperationToConnection( + self.forwardOperationToConnection( { (connection, returnConnection, context) in connection.sendCommandsImmediately = true - return connection + return + connection .send(command: command, with: arguments, logger: context) .always { _ in returnConnection(connection, context) } }, @@ -327,7 +335,7 @@ extension RedisConnectionPool: RedisClientWithUserContext { onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler?, logger: Logger? ) -> EventLoopFuture { - return self.subscribe( + self.subscribe( using: { $0.subscribe( to: channels, @@ -343,7 +351,7 @@ extension RedisConnectionPool: RedisClientWithUserContext { } internal func unsubscribe(from channels: [RedisChannelName], logger: Logger?) -> EventLoopFuture { - return self.unsubscribe(using: { $0.unsubscribe(from: channels, logger: $1) }, context: logger) + self.unsubscribe(using: { $0.unsubscribe(from: channels, logger: $1) }, context: logger) } internal func psubscribe( @@ -353,7 +361,7 @@ extension RedisConnectionPool: RedisClientWithUserContext { onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler?, logger: Logger? ) -> EventLoopFuture { - return self.subscribe( + self.subscribe( using: { $0.psubscribe( to: patterns, @@ -369,19 +377,20 @@ extension RedisConnectionPool: RedisClientWithUserContext { } internal func punsubscribe(from patterns: [String], logger: Logger?) -> EventLoopFuture { - return self.unsubscribe(using: { $0.punsubscribe(from: patterns, logger: $1) }, context: logger) + self.unsubscribe(using: { $0.punsubscribe(from: patterns, logger: $1) }, context: logger) } private func subscribe( - using operation: @escaping (RedisConnection, @escaping RedisSubscriptionChangeHandler, Logger) -> EventLoopFuture, + using operation: @escaping (RedisConnection, @escaping RedisSubscriptionChangeHandler, Logger) -> + EventLoopFuture, onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler?, context: Logger? ) -> EventLoopFuture { - return self.forwardOperationToConnection( + self.forwardOperationToConnection( { (connection, returnConnection, context) in if self.pubsubConnection == nil { - connection.allowSubscriptions = true // allow pubsub commands which are to come + connection.allowSubscriptions = true // allow pubsub commands which are to come self.pubsubConnection = connection } @@ -393,9 +402,9 @@ extension RedisConnectionPool: RedisClientWithUserContext { let connection = self.pubsubConnection else { return } - connection.allowSubscriptions = false // reset PubSub permissions + connection.allowSubscriptions = false // reset PubSub permissions returnConnection(connection, context) - self.pubsubConnection = nil // break ref cycle + self.pubsubConnection = nil // break ref cycle } return operation(connection, onUnsubscribe, context) @@ -409,9 +418,9 @@ extension RedisConnectionPool: RedisClientWithUserContext { using operation: @escaping (RedisConnection, Logger) -> EventLoopFuture, context: Logger? ) -> EventLoopFuture { - return self.forwardOperationToConnection( + self.forwardOperationToConnection( { (connection, returnConnection, context) in - return operation(connection, context) + operation(connection, context) .always { _ in // we aren't responsible for releasing the connection, subscribing is // so we check if we have pubsub connection has been released, which indicates this might be @@ -430,14 +439,15 @@ extension RedisConnectionPool: RedisClientWithUserContext { @usableFromInline internal func forwardOperationToConnection( - _ operation: @escaping (RedisConnection, @escaping (RedisConnection, Logger) -> Void, Logger) -> EventLoopFuture, + _ operation: @escaping (RedisConnection, @escaping (RedisConnection, Logger) -> Void, Logger) -> + EventLoopFuture, preferredConnection: RedisConnection?, context: Logger? ) -> EventLoopFuture { // Establish event loop context then jump to the in-loop version. guard self.loop.inEventLoop else { return self.loop.flatSubmit { - return self.forwardOperationToConnection( + self.forwardOperationToConnection( operation, preferredConnection: preferredConnection, context: context @@ -454,7 +464,8 @@ extension RedisConnectionPool: RedisClientWithUserContext { let logger = self.prepareLoggerForUse(context) guard let connection = preferredConnection else { - return pool + return + pool .leaseConnection( deadline: .now() + self.configuration.connectionRetryConfiguration.timeout, logger: logger diff --git a/Sources/RediStack/ConnectionPool/RedisConnectionPoolError.swift b/Sources/RediStack/ConnectionPool/RedisConnectionPoolError.swift index f3e5c8f6..f675b074 100644 --- a/Sources/RediStack/ConnectionPool/RedisConnectionPoolError.swift +++ b/Sources/RediStack/ConnectionPool/RedisConnectionPoolError.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020-2023 RediStack project authors +// Copyright (c) 2020-2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information diff --git a/Sources/RediStack/Extensions/StandardLibrary.swift b/Sources/RediStack/Extensions/StandardLibrary.swift index 9eb2fabc..dfbeb89e 100644 --- a/Sources/RediStack/Extensions/StandardLibrary.swift +++ b/Sources/RediStack/Extensions/StandardLibrary.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information diff --git a/Sources/RediStack/Extensions/SwiftNIO.swift b/Sources/RediStack/Extensions/SwiftNIO.swift index 08d0e2c7..68e63635 100644 --- a/Sources/RediStack/Extensions/SwiftNIO.swift +++ b/Sources/RediStack/Extensions/SwiftNIO.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019-2022 RediStack project authors +// Copyright (c) 2019-2022 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -21,7 +21,7 @@ extension TimeAmount { /// The seconds representation of the TimeAmount. @usableFromInline internal var seconds: Int64 { - return self.nanoseconds / 1_000_000_000 + self.nanoseconds / 1_000_000_000 } } @@ -60,14 +60,14 @@ extension ChannelPipeline { let handlers: [(ChannelHandler, name: String)] = [ (MessageToByteHandler(RedisMessageEncoder()), "RediStack.OutgoingHandler"), (ByteToMessageHandler(RedisByteDecoder()), "RediStack.IncomingHandler"), - (RedisCommandHandler(), "RediStack.CommandHandler") + (RedisCommandHandler(), "RediStack.CommandHandler"), ] return .andAllSucceed( handlers.map { self.addHandler($0, name: $1) }, on: self.eventLoop ) } - + /// Adds the channel handler that is responsible for handling everything related to Redis PubSub. /// - Important: The connection that manages this channel is responsible for removing the `RedisPubSubHandler`. /// @@ -110,7 +110,7 @@ extension ChannelPipeline { public func addRedisPubSubHandler() -> EventLoopFuture { // first try to return the handler that already exists in the pipeline - return self.handler(type: RedisPubSubHandler.self) + self.handler(type: RedisPubSubHandler.self) .flatMapError { // if it doesn't exist, add it to the pipeline guard @@ -155,7 +155,7 @@ extension ClientBootstrap { /// - Parameter group: The `EventLoopGroup` to create the `ClientBootstrap` with. /// - Returns: A TCP connection with the base configuration of a `Channel` pipeline for RESP messages. public static func makeRedisTCPClient(group: EventLoopGroup) -> ClientBootstrap { - return ClientBootstrap(group: group) + ClientBootstrap(group: group) .channelOption( ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1 diff --git a/Sources/RediStack/RESP/RESPTranslator.swift b/Sources/RediStack/RESP/RESPTranslator.swift index 5d300628..9298857e 100644 --- a/Sources/RediStack/RESP/RESPTranslator.swift +++ b/Sources/RediStack/RESP/RESPTranslator.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,21 +12,22 @@ // //===----------------------------------------------------------------------===// -import protocol Foundation.LocalizedError import NIOCore +import protocol Foundation.LocalizedError + /// A helper object for translating between raw bytes and Swift types according to the Redis Serialization Protocol (RESP). /// /// See [https://redis.io/topics/protocol](https://redis.io/topics/protocol) public struct RESPTranslator { - public init() { } + public init() {} } // MARK: Writing RESP /// The carriage return and newline escape symbols, used as the standard signal in RESP for a "message" end. /// A "message" in this case is a single data type. -fileprivate let respEnd: StaticString = "\r\n" +private let respEnd: StaticString = "\r\n" extension ByteBuffer { /// Writes the `RESPValue` into the current buffer, following the RESP specification. @@ -39,35 +40,37 @@ extension ByteBuffer { self.writeStaticString("+") self.writeBuffer(&buffer) self.writeStaticString(respEnd) - + case .bulkString(.some(var buffer)): self.writeStaticString("$") self.writeString("\(buffer.readableBytes)") self.writeStaticString(respEnd) self.writeBuffer(&buffer) self.writeStaticString(respEnd) - + case .bulkString(.none): self.writeStaticString("$0\r\n\r\n") - + case .integer(let number): self.writeStaticString(":") self.writeString(number.description) self.writeStaticString(respEnd) - + case .null: self.writeStaticString("$-1\r\n") - + case .error(let error): self.writeStaticString("-") self.writeString(error.message) self.writeStaticString(respEnd) - + case .array(let array): self.writeStaticString("*") self.writeString("\(array.count)") self.writeStaticString(respEnd) - array.forEach { self.writeRESPValue($0) } + for element in array { + self.writeRESPValue(element) + } } } } @@ -108,14 +111,14 @@ extension RESPTranslator { public static let bulkStringSizeMismatch = ParsingError(.bulkStringSizeMismatch) /// A RESP integer did not follow the RESP schema. public static let invalidIntegerFormat = ParsingError(.invalidIntegerFormat) - + public var errorDescription: String? { - return self.base.rawValue + self.base.rawValue } - + private let base: Base private init(_ base: Base) { self.base = base } - + private enum Base: String, Equatable { case invalidToken = "Cannot parse RESP: invalid token" case invalidBulkStringSize = "Cannot parse RESP Bulk String: received invalid size" @@ -136,51 +139,51 @@ extension RESPTranslator { var copy = buffer guard let token = copy.readInteger(as: UInt8.self) else { return nil } - + let result: RESPValue? switch token { case .plus: guard let value = self.parseSimpleString(from: ©) else { return nil } result = .simpleString(value) - + case .colon: guard let value = try self.parseInteger(from: ©) else { return nil } result = .integer(value) - + case .dollar: result = try self.parseBulkString(from: ©) break - + case .asterisk: result = try self.parseArray(from: ©) break - + case .hyphen: guard let stringBuffer = self.parseSimpleString(from: ©), let message = stringBuffer.getString(at: 0, length: stringBuffer.readableBytes) else { return nil } result = .error(RedisError(reason: message)) - + default: throw ParsingError.invalidToken } - + // if we successfully parsed a value, we need to update the original buffer's readerIndex if result != nil { buffer.moveReaderIndex(to: copy.readerIndex) } - + return result } - + /// See [https://redis.io/topics/protocol#resp-simple-strings](https://redis.io/topics/protocol#resp-simple-strings) internal func parseSimpleString(from buffer: inout ByteBuffer) -> ByteBuffer? { let bytes = buffer.readableBytesView guard let newlineIndex = bytes.firstIndex(of: .newline), - newlineIndex - bytes.startIndex >= 1 // strings should at least have a CRLF ending + newlineIndex - bytes.startIndex >= 1 // strings should at least have a CRLF ending else { return nil } - + // grab the bytes that we've determined is the full simple string, // and make sure to move the reader index afterwards defer { @@ -191,7 +194,7 @@ extension RESPTranslator { let endIndex = newlineIndex - bytes.startIndex return buffer.getSlice(at: bytes.startIndex, length: endIndex - 1) } - + /// See [https://redis.io/topics/protocol#resp-integers](https://redis.io/topics/protocol#resp-integers) internal func parseInteger(from buffer: inout ByteBuffer) throws -> Int? { guard @@ -202,61 +205,61 @@ extension RESPTranslator { guard let result = Int(string) else { throw ParsingError.invalidIntegerFormat } return result } - + /// See [https://redis.io/topics/protocol#resp-bulk-strings](https://redis.io/topics/protocol#resp-bulk-strings) internal func parseBulkString(from buffer: inout ByteBuffer) throws -> RESPValue? { guard let size = try self.parseInteger(from: &buffer) else { return nil } - + // only -1 is the only valid negative value for a size guard size >= -1 else { throw ParsingError.invalidBulkStringSize } - + // Redis sends '$-1\r\n' to represent a null bulk string guard size > -1 else { return .null } - + // Verify that we have the entire bulk string message by adding the expected CRLF end bytes // to the parsed size of the message content. // Even if the content is empty, Redis sends '$0\r\n\r\n' let expectedRemainingMessageSize = size + 2 guard buffer.readableBytes >= expectedRemainingMessageSize else { return nil } - + // soundness check that the declared content size matches the actual size. guard buffer.getInteger(at: buffer.readerIndex + expectedRemainingMessageSize - 1, as: UInt8.self) == .newline else { throw ParsingError.bulkStringSizeMismatch } - + // empty content bulk strings are different from null, and represented as .bulkString(nil) guard size > 0 else { buffer.moveReaderIndex(forwardBy: 2) return .bulkString(nil) } - + // move the reader position forward by the size of the total message (including the CRLF ending) defer { buffer.moveReaderIndex(forwardBy: expectedRemainingMessageSize) } - + return .bulkString( buffer.getSlice(at: buffer.readerIndex, length: size) ) } - + /// See [https://redis.io/topics/protocol#resp-arrays](https://redis.io/topics/protocol#resp-arrays) internal func parseArray(from buffer: inout ByteBuffer) throws -> RESPValue? { guard let elementCount = try parseInteger(from: &buffer) else { return nil } - guard elementCount > -1 else { return .null } // '*-1\r\n' - guard elementCount > 0 else { return .array([]) } // '*0\r\n' - + guard elementCount > -1 else { return .null } // '*-1\r\n' + guard elementCount > 0 else { return .array([]) } // '*0\r\n' + var results: [RESPValue] = [] results.reserveCapacity(elementCount) - + for _ in 0.. 0 else { return nil } guard let element = try self.parseBytes(from: &buffer) else { return nil } results.append(element) } - + return .array(results) } } diff --git a/Sources/RediStack/RESP/RESPValue.swift b/Sources/RediStack/RESP/RESPValue.swift index 8e95a734..c990f1c3 100644 --- a/Sources/RediStack/RESP/RESPValue.swift +++ b/Sources/RediStack/RESP/RESPValue.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,9 +12,10 @@ // //===----------------------------------------------------------------------===// -import struct Foundation.Data import NIOCore +import struct Foundation.Data + /// A representation of a Redis Serialization Protocol (RESP) primitive value. /// /// This enum representation should be used only as a temporary intermediate representation of values, and should be sent to a Redis server or converted to Swift @@ -42,7 +43,8 @@ public enum RESPValue { self = value.convertedToRESPValue() } - /// A `NIO.ByteBufferAllocator` for use in creating `.simpleString` and `.bulkString` representations directly, if needed. + /// A `NIO.ByteBufferAllocator` for use in creating `.simpleString` + /// and `.bulkString` representations directly, if needed. internal static let allocator = ByteBufferAllocator() /// Initializes a `bulkString` value. @@ -53,7 +55,7 @@ public enum RESPValue { self = .bulkString(nil) return } - + var buffer = RESPValue.allocator.buffer(capacity: unwrappedValue.count) buffer.writeString(unwrappedValue) self = .bulkString(buffer) @@ -74,15 +76,19 @@ extension RESPValue: CustomStringConvertible { public var description: String { switch self { case let .simpleString(buffer), - let .bulkString(.some(buffer)): - guard let value = String(fromRESP: self) else { return "\(buffer)" } // default to ByteBuffer's representation + let .bulkString(.some(buffer)): + guard let value = String(fromRESP: self) else { + // default to ByteBuffer's representation + return "\(buffer)" + } + return value // .integer, .error, and .bulkString(.none) conversions to String always succeed case .integer, - .bulkString(.none): + .bulkString(.none): return String(fromRESP: self)! - + case .null: return "NULL" case let .error(e): return e.message case let .array(elements): return "[\(elements.map({ $0.description }).joined(separator: ","))]" @@ -95,11 +101,11 @@ extension RESPValue: CustomStringConvertible { extension RESPValue { /// The unwrapped value for `.array` representations. /// - Note: This is a shorthand for `Array.init(fromRESP:)` - public var array: [RESPValue]? { return [RESPValue](fromRESP: self) } + public var array: [RESPValue]? { [RESPValue](fromRESP: self) } /// The unwrapped value as an `Int`. /// - Note: This is a shorthand for `Int(fromRESP:)`. - public var int: Int? { return Int(fromRESP: self) } + public var int: Int? { Int(fromRESP: self) } /// Returns `true` if the unwrapped value is `.null`. public var isNull: Bool { @@ -109,13 +115,14 @@ extension RESPValue { /// The unwrapped `RedisError` that was returned from Redis. /// - Note: This is a shorthand for `RedisError(fromRESP:)`. - public var error: RedisError? { return RedisError(fromRESP: self) } + public var error: RedisError? { RedisError(fromRESP: self) } /// The unwrapped `NIO.ByteBuffer` for `.simpleString` or `.bulkString` representations. public var byteBuffer: ByteBuffer? { switch self { case let .simpleString(buffer), - let .bulkString(.some(buffer)): return buffer + let .bulkString(.some(buffer)): + return buffer default: return nil } @@ -127,15 +134,15 @@ extension RESPValue { extension RESPValue { /// The value as a UTF-8 `String` representation. /// - Note: This is a shorthand for `String.init(fromRESP:)`. - public var string: String? { return String(fromRESP: self) } - + public var string: String? { String(fromRESP: self) } + /// The data stored in either a `.simpleString` or `.bulkString` represented as `Foundation.Data` instead of `NIO.ByteBuffer`. /// - Note: This is a shorthand for `Data.init(fromRESP:)`. - public var data: Data? { return Data(fromRESP: self) } - + public var data: Data? { Data(fromRESP: self) } + /// The raw bytes stored in the `.simpleString` or `.bulkString` representations. /// - Note: This is a shorthand for `Array.init(fromRESP:)`. - public var bytes: [UInt8]? { return [UInt8](fromRESP: self) } + public var bytes: [UInt8]? { [UInt8](fromRESP: self) } } // MARK: Equatable @@ -162,20 +169,21 @@ extension RESPValue: RESPValueConvertible { } public func convertedToRESPValue() -> RESPValue { - return self + self } } // MARK: EventLoopFuture Extensions -import NIOCore - extension EventLoopFuture where Value == RESPValue { /// Attempts to convert the resolved RESPValue to the desired type. /// /// This method is intended to be used much like a precondition in synchronous code, where a value is expected to be available from the `RESPValue`. /// - Important: If the `RESPValueConvertible` initializer fails, then the `NIO.EventLoopFuture` will fail. /// - Parameter to: The desired type to convert to. + /// - Parameter type: The resulting type. + /// - Parameter file: Source file location where the method was called. + /// - Parameter line: Source line location where the method was called. /// - Throws: `RedisClientError.failedRESPConversion(to:)` /// - Returns: A `NIO.EventLoopFuture` that resolves a value of the desired type or fails if the conversion does. @usableFromInline @@ -184,7 +192,7 @@ extension EventLoopFuture where Value == RESPValue { file: StaticString = #file, line: UInt = #line ) -> EventLoopFuture { - return self.flatMapThrowing { + self.flatMapThrowing { guard let value = T(fromRESP: $0) else { throw RedisClientError.failedRESPConversion(to: type) } @@ -200,16 +208,18 @@ extension RangeReplaceableCollection where Element == RESPValue { /// - Note: This method guarantees that only one storage expansion will happen to copy the elements. /// - Parameters elementsToCopy: The collection of elements to convert to `RESPValue` and append to the array. public mutating func append(convertingContentsOf elementsToCopy: ValueCollection) - where + where ValueCollection: Collection, ValueCollection.Element: RESPValueConvertible { guard elementsToCopy.count > 0 else { return } - + self.reserveCapacity(self.count + elementsToCopy.count) - elementsToCopy.forEach { self.append($0.convertedToRESPValue()) } + for element in elementsToCopy { + self.append(element.convertedToRESPValue()) + } } - + /// Adds the elements of a collection to this array, delegating the details of how they are added to the given closure. /// /// When your closure will be doing more than a simple transform of the element value, such as when you're adding both the key _and_ value from a `KeyValuePair`, @@ -250,11 +260,13 @@ extension RangeReplaceableCollection where Element == RESPValue { _ closure: (inout Self, ValueCollection.Element) -> Void ) { guard elementsToCopy.count > 0 else { return } - + let sizeToAdd = overestimatedCountBeingAdded ?? elementsToCopy.count self.reserveCapacity(self.count + sizeToAdd) - - elementsToCopy.forEach { closure(&self, $0) } + + for element in elementsToCopy { + closure(&self, element) + } } } @@ -266,20 +278,19 @@ extension Collection where Element == RESPValue { /// - Returns: An array of the results from the conversions. @inlinable public func map(as t1: T.Type) -> [T?] { - return self.map(T.init(fromRESP:)) + self.map(T.init(fromRESP:)) } - + /// Maps the first element to the type sepcified, with all remaining elements mapped to the second type. @inlinable public func map(firstAs t1: T1.Type, remainingAs t2: T2.Type) -> (T1?, [T2?]) - where T1: RESPValueConvertible, T2: RESPValueConvertible - { + where T1: RESPValueConvertible, T2: RESPValueConvertible { guard self.count > 1 else { return (nil, []) } let first = self.first.map(T1.init(fromRESP:)) ?? nil let remaining = self.dropFirst().map(T2.init(fromRESP:)) return (first, remaining) } - + /// Maps the first and second elements to the types specified, with any remaining mapped to the third type. @inlinable public func map( @@ -287,15 +298,14 @@ extension Collection where Element == RESPValue { _ t2: T2.Type, remainingAs t3: T3.Type ) -> (T1?, T2?, [T3?]) - where T1: RESPValueConvertible, T2: RESPValueConvertible, T3: RESPValueConvertible - { + where T1: RESPValueConvertible, T2: RESPValueConvertible, T3: RESPValueConvertible { guard self.count > 2 else { return (nil, nil, []) } let first = self.first.map(T1.init(fromRESP:)) ?? nil let second = T2.init(fromRESP: self[self.index(after: self.startIndex)]) let remaining = self.dropFirst(2).map(T3.init(fromRESP:)) return (first, second, remaining) } - + /// Maps the first, second, and third elements to the types specified, with any remaining mapped to the fourth type. @inlinable public func map( @@ -304,8 +314,7 @@ extension Collection where Element == RESPValue { _ t3: T3.Type, remainingAs t4: T4.Type ) -> (T1?, T2?, T3?, [T4?]) - where T1: RESPValueConvertible, T2: RESPValueConvertible, T3: RESPValueConvertible, T4: RESPValueConvertible - { + where T1: RESPValueConvertible, T2: RESPValueConvertible, T3: RESPValueConvertible, T4: RESPValueConvertible { guard self.count > 3 else { return (nil, nil, nil, []) } let firstIndex = self.startIndex @@ -320,4 +329,3 @@ extension Collection where Element == RESPValue { return (first, second, third, remaining) } } - diff --git a/Sources/RediStack/RESP/RESPValueConvertible.swift b/Sources/RediStack/RESP/RESPValueConvertible.swift index 31188c26..5660ece3 100644 --- a/Sources/RediStack/RESP/RESPValueConvertible.swift +++ b/Sources/RediStack/RESP/RESPValueConvertible.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,6 +12,8 @@ // //===----------------------------------------------------------------------===// +import struct Foundation.Data + /// An object that is capable of being converted to and from `RESPValue` representations arbitrarily. /// - Important: When conforming your types to be sent to a Redis server, it is expected to always be stored in a `.bulkString` representation. Redis will /// reject any other `RESPValue` type sent to it. @@ -40,7 +42,7 @@ extension String: RESPValueConvertible { public init?(fromRESP value: RESPValue) { switch value { case let .simpleString(buffer), - let .bulkString(.some(buffer)): + let .bulkString(.some(buffer)): guard let string = buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes) else { return nil } @@ -54,7 +56,7 @@ extension String: RESPValueConvertible { } public func convertedToRESPValue() -> RESPValue { - return .init(bulk: self) + .init(bulk: self) } } @@ -77,7 +79,7 @@ extension FixedWidthInteger { } public func convertedToRESPValue() -> RESPValue { - return .init(bulk: self.description) + .init(bulk: self.description) } } @@ -107,7 +109,7 @@ extension Double: RESPValueConvertible { } public func convertedToRESPValue() -> RESPValue { - return .init(bulk: self.description) + .init(bulk: self.description) } } @@ -121,12 +123,12 @@ extension Float: RESPValueConvertible { guard let string = String(fromRESP: value), let float = Float(string) - else { return nil } + else { return nil } self = float } public func convertedToRESPValue() -> RESPValue { - return .init(bulk: self.description) + .init(bulk: self.description) } } @@ -158,10 +160,10 @@ extension Array where Element == UInt8 { public init?(fromRESP value: RESPValue) { switch value { case let .simpleString(buffer), - let .bulkString(.some(buffer)): + let .bulkString(.some(buffer)): guard let bytes = buffer.getBytes(at: buffer.readerIndex, length: buffer.readableBytes) else { return nil } self = bytes - + case .bulkString(.none): self = [] default: return nil } @@ -190,15 +192,13 @@ extension Optional: RESPValueConvertible where Wrapped: RESPValueConvertible { } } -import struct Foundation.Data - extension Data: RESPValueConvertible { public init?(fromRESP value: RESPValue) { switch value { case let .simpleString(buffer), - let .bulkString(.some(buffer)): + let .bulkString(.some(buffer)): self = Data(buffer.readableBytesView) - + case .bulkString(.none): self = Data() default: return nil } diff --git a/Sources/RediStack/RedisChannelName.swift b/Sources/RediStack/RedisChannelName.swift index 85e3b1d8..2f6ba815 100644 --- a/Sources/RediStack/RedisChannelName.swift +++ b/Sources/RediStack/RedisChannelName.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020 RediStack project authors +// Copyright (c) 2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -29,16 +29,16 @@ public struct RedisChannelName: Comparable, Hashable, Codable { public let rawValue: String - + /// Initializes a type-safe representation of a Redis Pub/Sub channel name. /// - Parameter name: The name of the Redis Pub/Sub channel. public init(_ name: String) { self.rawValue = name } - + public var description: String { self.rawValue } public var debugDescription: String { "\(Self.self): \(self.rawValue)" } - + public init?(fromRESP value: RESPValue) { guard let string = value.string else { return nil } self.rawValue = string @@ -49,13 +49,13 @@ public struct RedisChannelName: let container = try decoder.singleValueContainer() self.rawValue = try container.decode(String.self) } - - public static func <(lhs: RedisChannelName, rhs: RedisChannelName) -> Bool { - return lhs.rawValue < rhs.rawValue + + public static func < (lhs: RedisChannelName, rhs: RedisChannelName) -> Bool { + lhs.rawValue < rhs.rawValue } - + public func convertedToRESPValue() -> RESPValue { - return .init(bulk: self.rawValue) + .init(bulk: self.rawValue) } public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() diff --git a/Sources/RediStack/RedisClient.swift b/Sources/RediStack/RedisClient.swift index e9a7c2f5..f8a9c064 100644 --- a/Sources/RediStack/RedisClient.swift +++ b/Sources/RediStack/RedisClient.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019-2020 RediStack project authors +// Copyright (c) 2019-2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,9 +12,10 @@ // //===----------------------------------------------------------------------===// +import NIOCore + import protocol Foundation.LocalizedError import struct Logging.Logger -import NIOCore // - Important: Any RedisClient defined by RediStack should conform to the RedisClientWithUserContext protocol as well @@ -35,12 +36,12 @@ public protocol RedisClient { /// - arguments: The arguments, if any, to be sent with the command. /// - Returns: A `NIO.EventLoopFuture` that will resolve with the Redis command response. func send(command: String, with arguments: [RESPValue]) -> EventLoopFuture - + /// Temporarily overrides the default logger for command logs to the provided instance. /// - Parameter logger: The `Logging.Logger` instance to use for command logs. /// - Returns: A RedisClient with the temporary override for command logging. func logging(to logger: Logger) -> RedisClient - + /// Subscribes the client to the specified Redis channels, invoking the provided message receiver each time a message is published. /// /// See [SUBSCRIBE](https://redis.io/commands/subscribe) @@ -85,7 +86,7 @@ public protocol RedisClient { onSubscribe subscribeHandler: RedisSubscriptionChangeHandler?, onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler? ) -> EventLoopFuture - + /// Unsubscribes the client from a specific Redis channel from receiving any future published messages. /// /// See [UNSUBSCRIBE](https://redis.io/commands/unsubscribe) @@ -99,7 +100,7 @@ public protocol RedisClient { /// - Parameter channels: A list of channel names to be unsubscribed from. /// - Returns: A notification `NIO.EventLoopFuture` that resolves once the subscription(s) have been removed from Redis. func unsubscribe(from channels: [RedisChannelName]) -> EventLoopFuture - + /// Unsubscribes the client from a pattern of Redis channel names from receiving any future published messages. /// /// See [PUNSUBSCRIBE](https://redis.io/commands/punsubscribe) @@ -121,19 +122,19 @@ extension RedisClient { /// - Parameter command: The command keyword to execute. /// - Returns: A `NIO.EventLoopFuture` that will resolve with the Redis command response. public func send(command: String) -> EventLoopFuture { - return self.send(command: command, with: []) + self.send(command: command, with: []) } /// Unsubscribes the client from all active Redis channel name subscriptions. /// - Returns: A `NIO.EventLoopFuture` that resolves when the subscriptions have been removed. public func unsubscribe() -> EventLoopFuture { - return self.unsubscribe(from: []) + self.unsubscribe(from: []) } - + /// Unsubscribes the client from all active Redis channel name patterns subscriptions. /// - Returns: A `NIO.EventLoopFuture` that resolves when the subscriptions have been removed. public func punsubscribe() -> EventLoopFuture { - return self.punsubscribe(from: []) + self.punsubscribe(from: []) } } @@ -141,11 +142,11 @@ extension RedisClient { extension RedisClient { public func unsubscribe(from channels: RedisChannelName...) -> EventLoopFuture { - return self.unsubscribe(from: channels) + self.unsubscribe(from: channels) } public func punsubscribe(from patterns: String...) -> EventLoopFuture { - return self.punsubscribe(from: patterns) + self.punsubscribe(from: patterns) } public func subscribe( @@ -154,7 +155,12 @@ extension RedisClient { onSubscribe subscribeHandler: RedisSubscriptionChangeHandler? = nil, onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler? = nil ) -> EventLoopFuture { - return self.subscribe(to: channels, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler) + self.subscribe( + to: channels, + messageReceiver: receiver, + onSubscribe: subscribeHandler, + onUnsubscribe: unsubscribeHandler + ) } public func subscribe( @@ -163,7 +169,12 @@ extension RedisClient { onSubscribe subscribeHandler: RedisSubscriptionChangeHandler? = nil, onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler? = nil ) -> EventLoopFuture { - return self.subscribe(to: channels, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler) + self.subscribe( + to: channels, + messageReceiver: receiver, + onSubscribe: subscribeHandler, + onUnsubscribe: unsubscribeHandler + ) } public func psubscribe( @@ -172,7 +183,12 @@ extension RedisClient { onSubscribe subscribeHandler: RedisSubscriptionChangeHandler? = nil, onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler? = nil ) -> EventLoopFuture { - return self.psubscribe(to: patterns, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler) + self.psubscribe( + to: patterns, + messageReceiver: receiver, + onSubscribe: subscribeHandler, + onUnsubscribe: unsubscribeHandler + ) } public func psubscribe( @@ -181,7 +197,12 @@ extension RedisClient { onSubscribe subscribeHandler: RedisSubscriptionChangeHandler? = nil, onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler? = nil ) -> EventLoopFuture { - return self.psubscribe(to: patterns, messageReceiver: receiver, onSubscribe: subscribeHandler, onUnsubscribe: unsubscribeHandler) + self.psubscribe( + to: patterns, + messageReceiver: receiver, + onSubscribe: subscribeHandler, + onUnsubscribe: unsubscribeHandler + ) } } @@ -195,21 +216,21 @@ public struct RedisClientError: LocalizedError, Equatable, Hashable { public static var subscriptionModeRaceCondition: RedisClientError { .init(.subscriptionModeRaceCondition) } /// A connection that is not authorized for PubSub subscriptions attempted to create a subscription. public static var pubsubNotAllowed: RedisClientError { .init(.pubsubNotAllowed) } - + /// Conversion from `RESPValue` to the specified type failed. /// /// If this is ever triggered, please capture the original `RESPValue` string sent from Redis for bug reports. public static func failedRESPConversion(to type: Any.Type) -> RedisClientError { - return .init(.failedRESPConversion(to: type)) + .init(.failedRESPConversion(to: type)) } - + /// Expectations of message structures were not met. /// /// If this is ever triggered, please capture the original `RESPValue` string sent from Redis along with the command and arguments sent to Redis for bug reports. public static func assertionFailure(message: String) -> RedisClientError { - return .init(.assertionFailure(message: message)) + .init(.assertionFailure(message: message)) } - + public var errorDescription: String? { let message: String switch self.baseError { @@ -221,31 +242,41 @@ public struct RedisClientError: LocalizedError, Equatable, Hashable { } return "(RediStack) \(message)" } - + public var recoverySuggestion: String? { switch self.baseError { - case .connectionClosed: return "Check that the connection is not closed before invoking commands. With RedisConnection, this can be done with the 'isConnected' property." - case .failedRESPConversion: return "Ensure that the data type being requested is actually what's being returned. If you see this error and are not sure why, capture the original RESPValue string sent from Redis to add to your bug report." - case .assertionFailure: return "This error should in theory never happen. If you trigger this error, capture the original RESPValue string sent from Redis along with the command and arguments that you sent to Redis to add to your bug report." - case .subscriptionModeRaceCondition: return "This is a race condition where the PubSub handler was removed after a subscription was being added, but before it was committed. This can be solved by just retrying the subscription." - case .pubsubNotAllowed: return "When connections are managed by a pool, they are not allowed to create PubSub subscriptions on their own. Use the appropriate PubSub commands on the connection pool itself. If the connection is not managed by a pool, this is a bug and should be reported." + case .connectionClosed: + return + "Check that the connection is not closed before invoking commands. With RedisConnection, this can be done with the 'isConnected' property." + case .failedRESPConversion: + return + "Ensure that the data type being requested is actually what's being returned. If you see this error and are not sure why, capture the original RESPValue string sent from Redis to add to your bug report." + case .assertionFailure: + return + "This error should in theory never happen. If you trigger this error, capture the original RESPValue string sent from Redis along with the command and arguments that you sent to Redis to add to your bug report." + case .subscriptionModeRaceCondition: + return + "This is a race condition where the PubSub handler was removed after a subscription was being added, but before it was committed. This can be solved by just retrying the subscription." + case .pubsubNotAllowed: + return + "When connections are managed by a pool, they are not allowed to create PubSub subscriptions on their own. Use the appropriate PubSub commands on the connection pool itself. If the connection is not managed by a pool, this is a bug and should be reported." } } - + private var baseError: BaseError - + private init(_ baseError: BaseError) { self.baseError = baseError } - - /* Protocol Conformances and Private Type implementation */ - - public static func ==(lhs: RedisClientError, rhs: RedisClientError) -> Bool { - return lhs.localizedDescription == rhs.localizedDescription + + // Protocol Conformances and Private Type implementation + + public static func == (lhs: RedisClientError, rhs: RedisClientError) -> Bool { + lhs.localizedDescription == rhs.localizedDescription } - + public func hash(into hasher: inout Hasher) { hasher.combine(self.localizedDescription) } - + fileprivate enum BaseError { case connectionClosed case failedRESPConversion(to: Any.Type) diff --git a/Sources/RediStack/RedisCommandEncoder-multi-encode.swift b/Sources/RediStack/RedisCommandEncoder-multi-encode.swift index 9ac469a9..6a68f22e 100644 --- a/Sources/RediStack/RedisCommandEncoder-multi-encode.swift +++ b/Sources/RediStack/RedisCommandEncoder-multi-encode.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2023 RediStack project authors +// Copyright (c) 2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -30,7 +30,11 @@ extension RedisCommandEncoder { } @inlinable - mutating func encodeRESPArray(_ t0: T0, _ t1: T1, _ t2: T2) { + mutating func encodeRESPArray< + T0: RESP3BlobStringEncodable, + T1: RESP3BlobStringEncodable, + T2: RESP3BlobStringEncodable + >(_ t0: T0, _ t1: T1, _ t2: T2) { self.buffer.writeBytes("*3\r\n".utf8) t0.encodeRedisBlobString(into: &self.buffer) t1.encodeRedisBlobString(into: &self.buffer) @@ -38,7 +42,12 @@ extension RedisCommandEncoder { } @inlinable - mutating func encodeRESPArray(_ t0: T0, _ t1: T1, _ t2: T2, _ t3: T3) { + mutating func encodeRESPArray< + T0: RESP3BlobStringEncodable, + T1: RESP3BlobStringEncodable, + T2: RESP3BlobStringEncodable, + T3: RESP3BlobStringEncodable + >(_ t0: T0, _ t1: T1, _ t2: T2, _ t3: T3) { self.buffer.writeBytes("*4\r\n".utf8) t0.encodeRedisBlobString(into: &self.buffer) t1.encodeRedisBlobString(into: &self.buffer) @@ -47,7 +56,13 @@ extension RedisCommandEncoder { } @inlinable - mutating func encodeRESPArray(_ t0: T0, _ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4) { + mutating func encodeRESPArray< + T0: RESP3BlobStringEncodable, + T1: RESP3BlobStringEncodable, + T2: RESP3BlobStringEncodable, + T3: RESP3BlobStringEncodable, + T4: RESP3BlobStringEncodable + >(_ t0: T0, _ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4) { self.buffer.writeBytes("*5\r\n".utf8) t0.encodeRedisBlobString(into: &self.buffer) t1.encodeRedisBlobString(into: &self.buffer) @@ -57,7 +72,14 @@ extension RedisCommandEncoder { } @inlinable - mutating func encodeRESPArray(_ t0: T0, _ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4, _ t5: T5) { + mutating func encodeRESPArray< + T0: RESP3BlobStringEncodable, + T1: RESP3BlobStringEncodable, + T2: RESP3BlobStringEncodable, + T3: RESP3BlobStringEncodable, + T4: RESP3BlobStringEncodable, + T5: RESP3BlobStringEncodable + >(_ t0: T0, _ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4, _ t5: T5) { self.buffer.writeBytes("*6\r\n".utf8) t0.encodeRedisBlobString(into: &self.buffer) t1.encodeRedisBlobString(into: &self.buffer) @@ -68,7 +90,15 @@ extension RedisCommandEncoder { } @inlinable - mutating func encodeRESPArray(_ t0: T0, _ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4, _ t5: T5, _ t6: T6) { + mutating func encodeRESPArray< + T0: RESP3BlobStringEncodable, + T1: RESP3BlobStringEncodable, + T2: RESP3BlobStringEncodable, + T3: RESP3BlobStringEncodable, + T4: RESP3BlobStringEncodable, + T5: RESP3BlobStringEncodable, + T6: RESP3BlobStringEncodable + >(_ t0: T0, _ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4, _ t5: T5, _ t6: T6) { self.buffer.writeBytes("*7\r\n".utf8) t0.encodeRedisBlobString(into: &self.buffer) t1.encodeRedisBlobString(into: &self.buffer) diff --git a/Sources/RediStack/RedisCommandEncoder.swift b/Sources/RediStack/RedisCommandEncoder.swift index 2f9063cd..3ddf4f03 100644 --- a/Sources/RediStack/RedisCommandEncoder.swift +++ b/Sources/RediStack/RedisCommandEncoder.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2023 RediStack project authors +// Copyright (c) 2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -26,19 +26,20 @@ protocol RESP3BlobStringEncodable { struct RedisCommandEncoder { var buffer: ByteBuffer -#if swift(>=5.9) - mutating func encodeRESPArray(_ first: some RESP3BlobStringEncodable, _ args: repeat each S) { + #if swift(>=5.9) + mutating func encodeRESPArray( + _ first: some RESP3BlobStringEncodable, + _ args: repeat each S + ) { let count = ComputeParameterPackLength.count(ofPack: repeat each args) self.buffer.writeBytes("*".utf8) self.buffer.writeBytes("\(count + 1)".utf8) self.buffer.writeRESPNewLine() first.encodeRedisBlobString(into: &self.buffer) - repeat ( - (each args).encodeRedisBlobString(into: &self.buffer) - ) + repeat ((each args).encodeRedisBlobString(into: &self.buffer)) } -#endif + #endif } extension String: RESP3BlobStringEncodable { @@ -62,7 +63,6 @@ extension ByteBuffer: RESP3BlobStringEncodable { } } - #if swift(>=5.9) private enum ComputeParameterPackLength { enum BoolConverter { diff --git a/Sources/RediStack/RedisConnection+Configuration.swift b/Sources/RediStack/RedisConnection+Configuration.swift index f32c0971..3f1a6b12 100644 --- a/Sources/RediStack/RedisConnection+Configuration.swift +++ b/Sources/RediStack/RedisConnection+Configuration.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020-2023 RediStack project authors +// Copyright (c) 2020-2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -13,12 +13,13 @@ //===----------------------------------------------------------------------===// import Atomics -import NIOCore +import Logging import NIOConcurrencyHelpers +import NIOCore import NIOPosix -import Logging -import struct Foundation.URL + import protocol Foundation.LocalizedError +import struct Foundation.URL extension RedisConnection { /// A configuration object for creating a single connection to Redis. @@ -32,7 +33,11 @@ extension RedisConnection { get { self._defaultPortAtomic.load(ordering: .acquiring) } - @available(*, deprecated, message: "Setting the default Redis port will be removed in the next major release") + @available( + *, + deprecated, + message: "Setting the default Redis port will be removed in the next major release" + ) set { self._defaultPortAtomic.store(newValue, ordering: .releasing) } @@ -219,8 +224,8 @@ extension RedisConnection.Configuration { private init(_ kind: Kind) { self.kind = kind } - public static func ==(lhs: ValidationError, rhs: ValidationError) -> Bool { - return lhs.kind == rhs.kind + public static func == (lhs: ValidationError, rhs: ValidationError) -> Bool { + lhs.kind == rhs.kind } private enum Kind: LocalizedError { diff --git a/Sources/RediStack/RedisConnection.swift b/Sources/RediStack/RedisConnection.swift index c6584bfc..6d37534f 100644 --- a/Sources/RediStack/RedisConnection.swift +++ b/Sources/RediStack/RedisConnection.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019-2023 RediStack project authors +// Copyright (c) 2019-2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,15 +12,16 @@ // //===----------------------------------------------------------------------===// -import struct Foundation.UUID -import struct Dispatch.DispatchTime import Atomics import Logging import Metrics -import NIOCore import NIOConcurrencyHelpers +import NIOCore import NIOPosix +import struct Dispatch.DispatchTime +import struct Foundation.UUID + extension RedisConnection { /// Creates a new connection with provided configuration and sychronization objects. @@ -57,7 +58,8 @@ extension RedisConnection { ) -> EventLoopFuture { let client = client ?? .makeRedisTCPClient(group: eventLoop) - return client + return + client .connect(to: config.address) .flatMap { let connection = RedisConnection(configuredRESPChannel: $0, backgroundLogger: config.defaultLogger) @@ -86,12 +88,12 @@ extension RedisConnection { public final class RedisConnection: RedisClient, RedisClientWithUserContext { /// A unique identifer to represent this connection. public let id = UUID() - public var eventLoop: EventLoop { return self.channel.eventLoop } + public var eventLoop: EventLoop { self.channel.eventLoop } /// Is the connection to Redis still open? public var isConnected: Bool { // `Channel.isActive` is set to false before the `closeFuture` resolves in cases where the channel might be // closed, or closing, before our state has been updated - return self.channel.isActive && self.state.isConnected + self.channel.isActive && self.state.isConnected } /// Is the connection currently subscribed for PubSub? /// @@ -106,7 +108,7 @@ public final class RedisConnection: RedisClient, RedisClientWithUserContext { /// - Important: Even when set to `true`, the host machine may still choose to delay sending commands. /// - Note: Setting this to `true` will immediately drain the buffer. public var sendCommandsImmediately: Bool { - get { return autoflush.load(ordering: .sequentiallyConsistent) } + get { autoflush.load(ordering: .sequentiallyConsistent) } set(newValue) { if newValue { self.channel.flush() } autoflush.store(newValue, ordering: .sequentiallyConsistent) @@ -122,10 +124,13 @@ public final class RedisConnection: RedisClient, RedisClientWithUserContext { self.allowPubSub.store(newValue, ordering: .sequentiallyConsistent) // if we're subscribed, and we're not allowed to be in pubsub, end our subscriptions guard self.isSubscribed && !self.allowPubSub.load(ordering: .sequentiallyConsistent) else { return } - _ = EventLoopFuture.whenAllComplete([ - self.unsubscribe(), - self.punsubscribe() - ], on: self.eventLoop) + _ = EventLoopFuture.whenAllComplete( + [ + self.unsubscribe(), + self.punsubscribe(), + ], + on: self.eventLoop + ) } } /// A closure to invoke when the connection closes unexpectedly. @@ -142,7 +147,7 @@ public final class RedisConnection: RedisClient, RedisClientWithUserContext { private let _stateLock = NIOLock() private var _state = ConnectionState.open private var state: ConnectionState { - get { return _stateLock.withLock { self._state } } + get { _stateLock.withLock { self._state } } set(newValue) { _stateLock.withLockVoid { self._state = newValue } } } @@ -237,7 +242,7 @@ extension RedisConnection { /// - Returns: A `NIO.EventLoopFuture` that resolves with the command's result stored in a `RESPValue`. /// If a `RedisError` is returned, the future will be failed instead. public func send(command: String, with arguments: [RESPValue]) -> EventLoopFuture { - return self.send(command: command, with: arguments, logger: nil) + self.send(command: command, with: arguments, logger: nil) } internal func send( @@ -270,10 +275,13 @@ extension RedisConnection { } logger.trace("received command request") - logger.debug("sending command", metadata: [ - RedisLogging.MetadataKeys.commandKeyword: "\(command)", - RedisLogging.MetadataKeys.commandArguments: "\(arguments)" - ]) + logger.debug( + "sending command", + metadata: [ + RedisLogging.MetadataKeys.commandKeyword: "\(command)", + RedisLogging.MetadataKeys.commandArguments: "\(arguments)", + ] + ) var message: [RESPValue] = [.init(bulk: command)] message.append(contentsOf: arguments) @@ -292,14 +300,20 @@ extension RedisConnection { // log data based on the result switch result { case let .failure(error): - logger.error("command failed", metadata: [ - RedisLogging.MetadataKeys.error: "\(error.loggableDescription)" - ]) + logger.error( + "command failed", + metadata: [ + RedisLogging.MetadataKeys.error: "\(error.loggableDescription)" + ] + ) case let .success(value): - logger.debug("command succeeded", metadata: [ - RedisLogging.MetadataKeys.commandResult: "\(value)" - ]) + logger.debug( + "command succeeded", + metadata: [ + RedisLogging.MetadataKeys.commandResult: "\(value)" + ] + ) } } @@ -344,9 +358,12 @@ extension RedisConnection { self.channel.triggerUserOutboundEvent(RedisGracefulConnectionCloseEvent(), promise: closePromise) closeFuture.whenFailure { - logger.error("error while closing connection", metadata: [ - RedisLogging.MetadataKeys.error: "\($0)" - ]) + logger.error( + "error while closing connection", + metadata: [ + RedisLogging.MetadataKeys.error: "\($0)" + ] + ) } closeFuture.whenSuccess { logger.trace("connection is now closed") @@ -360,7 +377,7 @@ extension RedisConnection { extension RedisConnection { public func logging(to logger: Logger) -> RedisClient { - return UserContextRedisClient(client: self, logger: self.prepareLoggerForUse(logger)) + UserContextRedisClient(client: self, logger: self.prepareLoggerForUse(logger)) } private func prepareLoggerForUse(_ logger: Logger?) -> Logger { @@ -379,7 +396,7 @@ extension RedisConnection { onSubscribe subscribeHandler: RedisSubscriptionChangeHandler?, onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler? ) -> EventLoopFuture { - return self._subscribe(.channels(channels), receiver, subscribeHandler, unsubscribeHandler, nil) + self._subscribe(.channels(channels), receiver, subscribeHandler, unsubscribeHandler, nil) } public func psubscribe( @@ -388,7 +405,7 @@ extension RedisConnection { onSubscribe subscribeHandler: RedisSubscriptionChangeHandler? = nil, onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler? = nil ) -> EventLoopFuture { - return self._subscribe(.patterns(patterns), receiver, subscribeHandler, unsubscribeHandler, nil) + self._subscribe(.patterns(patterns), receiver, subscribeHandler, unsubscribeHandler, nil) } internal func subscribe( @@ -398,7 +415,7 @@ extension RedisConnection { onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler?, logger: Logger? ) -> EventLoopFuture { - return self._subscribe(.channels(channels), receiver, subscribeHandler, unsubscribeHandler, logger) + self._subscribe(.channels(channels), receiver, subscribeHandler, unsubscribeHandler, logger) } internal func psubscribe( @@ -408,7 +425,7 @@ extension RedisConnection { onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler?, logger: Logger? ) -> EventLoopFuture { - return self._subscribe(.patterns(patterns), receiver, subscribeHandler, unsubscribeHandler, logger) + self._subscribe(.patterns(patterns), receiver, subscribeHandler, unsubscribeHandler, logger) } private func _subscribe( @@ -430,9 +447,12 @@ extension RedisConnection { return self.eventLoop.makeFailedFuture(RedisClientError.pubsubNotAllowed) } - logger.trace("adding subscription", metadata: [ - RedisLogging.MetadataKeys.pubsubTarget: "\(target.debugDescription)" - ]) + logger.trace( + "adding subscription", + metadata: [ + RedisLogging.MetadataKeys.pubsubTarget: "\(target.debugDescription)" + ] + ) // if we're in pubsub mode already, great - add the subscriptions guard case let .pubsub(handler) = self.state else { @@ -442,8 +462,14 @@ extension RedisConnection { .addRedisPubSubHandler() .flatMap { handler in logger.trace("handler added, adding subscription") - return handler - .addSubscription(for: target, messageReceiver: receiver, onSubscribe: onSubscribe, onUnsubscribe: onUnsubscribe) + return + handler + .addSubscription( + for: target, + messageReceiver: receiver, + onSubscribe: onSubscribe, + onUnsubscribe: onUnsubscribe + ) .flatMapError { error in logger.debug( "failed to add subscriptions that triggered pubsub mode. removing handler", @@ -471,8 +497,14 @@ extension RedisConnection { } // add the subscription and just ignore the subscription count - return handler - .addSubscription(for: target, messageReceiver: receiver, onSubscribe: onSubscribe, onUnsubscribe: onUnsubscribe) + return + handler + .addSubscription( + for: target, + messageReceiver: receiver, + onSubscribe: onSubscribe, + onUnsubscribe: onUnsubscribe + ) .map { _ in logger.trace("subscription added") } } } @@ -481,19 +513,19 @@ extension RedisConnection { extension RedisConnection { public func unsubscribe(from channels: [RedisChannelName]) -> EventLoopFuture { - return self._unsubscribe(.channels(channels), nil) + self._unsubscribe(.channels(channels), nil) } public func punsubscribe(from patterns: [String]) -> EventLoopFuture { - return self._unsubscribe(.patterns(patterns), nil) + self._unsubscribe(.patterns(patterns), nil) } internal func unsubscribe(from channels: [RedisChannelName], logger: Logger?) -> EventLoopFuture { - return self._unsubscribe(.channels(channels), logger) + self._unsubscribe(.channels(channels), logger) } internal func punsubscribe(from patterns: [String], logger: Logger?) -> EventLoopFuture { - return self._unsubscribe(.patterns(patterns), logger) + self._unsubscribe(.patterns(patterns), logger) } private func _unsubscribe(_ target: RedisSubscriptionTarget, _ logger: Logger?) -> EventLoopFuture { @@ -507,25 +539,34 @@ extension RedisConnection { // if we're not in pubsub mode, then we just succeed as a no-op guard case let .pubsub(handler) = self.state else { // but we still assert just to give some notification to devs at debug - logger.notice("received request to unsubscribe while not in pubsub mode", metadata: [ - RedisLogging.MetadataKeys.pubsubTarget: "\(target.debugDescription)" - ]) + logger.notice( + "received request to unsubscribe while not in pubsub mode", + metadata: [ + RedisLogging.MetadataKeys.pubsubTarget: "\(target.debugDescription)" + ] + ) return self.eventLoop.makeSucceededFuture(()) } - logger.trace("removing subscription", metadata: [ - RedisLogging.MetadataKeys.pubsubTarget: "\(target.debugDescription)" - ]) + logger.trace( + "removing subscription", + metadata: [ + RedisLogging.MetadataKeys.pubsubTarget: "\(target.debugDescription)" + ] + ) // remove the subscription return handler.removeSubscription(for: target) .flatMap { // if we still have subscriptions, just succeed this request guard $0 == 0 else { - logger.debug("subscription removed, but still have active subscription count", metadata: [ - RedisLogging.MetadataKeys.subscriptionCount: "\($0)", - RedisLogging.MetadataKeys.pubsubTarget: "\(target.debugDescription)" - ]) + logger.debug( + "subscription removed, but still have active subscription count", + metadata: [ + RedisLogging.MetadataKeys.subscriptionCount: "\($0)", + RedisLogging.MetadataKeys.pubsubTarget: "\(target.debugDescription)", + ] + ) return self.eventLoop.makeSucceededFuture(()) } logger.debug("subscription removed, with no current active subscriptions. leaving pubsub mode") diff --git a/Sources/RediStack/RedisError.swift b/Sources/RediStack/RedisError.swift index 1823883f..12a61244 100644 --- a/Sources/RediStack/RedisError.swift +++ b/Sources/RediStack/RedisError.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -19,7 +19,7 @@ public struct RedisError: LocalizedError { /// The error message from Redis, prefixed with `(Redis)` to indicate the message was from Redis itself. public let message: String - public var errorDescription: String? { return message } + public var errorDescription: String? { message } /// Creates a new instance of an error from a Redis instance. /// - Parameter reason: The error reason from Redis. @@ -31,10 +31,10 @@ public struct RedisError: LocalizedError { // MARK: Equatable, Hashable extension RedisError: Equatable, Hashable { - public static func ==(lhs: RedisError, rhs: RedisError) -> Bool { - return lhs.message == rhs.message + public static func == (lhs: RedisError, rhs: RedisError) -> Bool { + lhs.message == rhs.message } - + public func hash(into hasher: inout Hasher) { hasher.combine(self.message) } @@ -52,7 +52,7 @@ extension RedisError: RESPValueConvertible { } public func convertedToRESPValue() -> RESPValue { - return .error(self) + .error(self) } } diff --git a/Sources/RediStack/RedisKey+TTL.swift b/Sources/RediStack/RedisKey+TTL.swift index 0c4a853b..ab798f02 100644 --- a/Sources/RediStack/RedisKey+TTL.swift +++ b/Sources/RediStack/RedisKey+TTL.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020 RediStack project authors +// Copyright (c) 2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -30,12 +30,12 @@ extension RedisKey.Lifetime { } } - public static func <(lhs: Duration, rhs: Duration) -> Bool { - return lhs.timeAmount < rhs.timeAmount + public static func < (lhs: Duration, rhs: Duration) -> Bool { + lhs.timeAmount < rhs.timeAmount } - public static func ==(lhs: Duration, rhs: Duration) -> Bool { - return lhs.timeAmount == rhs.timeAmount + public static func == (lhs: Duration, rhs: Duration) -> Bool { + lhs.timeAmount == rhs.timeAmount } } } @@ -81,4 +81,3 @@ extension RedisKey { } } } - diff --git a/Sources/RediStack/RedisKey.swift b/Sources/RediStack/RedisKey.swift index 9f26a94e..de29a6ab 100644 --- a/Sources/RediStack/RedisKey.swift +++ b/Sources/RediStack/RedisKey.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -30,16 +30,16 @@ public struct RedisKey: Comparable, Hashable, Codable { public let rawValue: String - + /// Initializes a type-safe representation of a key to a value in a Redis instance. /// - Parameter key: The key of a value in a Redis instance. public init(_ key: String) { self.rawValue = key } - - public var description: String { return self.rawValue } - public var debugDescription: String { return "\(String(describing: type(of: self))): \(self.rawValue)" } - + + public var description: String { self.rawValue } + public var debugDescription: String { "\(String(describing: type(of: self))): \(self.rawValue)" } + public init?(fromRESP value: RESPValue) { guard let string = value.string else { return nil } self.rawValue = string @@ -50,13 +50,13 @@ public struct RedisKey: let container = try decoder.singleValueContainer() self.rawValue = try container.decode(String.self) } - - public static func <(lhs: RedisKey, rhs: RedisKey) -> Bool { - return lhs.rawValue < rhs.rawValue + + public static func < (lhs: RedisKey, rhs: RedisKey) -> Bool { + lhs.rawValue < rhs.rawValue } - + public func convertedToRESPValue() -> RESPValue { - return .init(bulk: self.rawValue) + .init(bulk: self.rawValue) } public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() diff --git a/Sources/RediStack/RedisLogging.swift b/Sources/RediStack/RedisLogging.swift index 7872dfba..bc8ce301 100644 --- a/Sources/RediStack/RedisLogging.swift +++ b/Sources/RediStack/RedisLogging.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020-2023 RediStack project authors +// Copyright (c) 2020-2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -29,7 +29,7 @@ public enum RedisLogging { // All public keys should be prefixed with `rdstk` unless there is prior art to not do so (such as 'error') // each key should also be 16 or less characters, as to avoid heap allocations which are expensive in the context // of Logging Metadata - + /// An error that has been tracked. public static var error: String { "error" } /// The ID of the connection that generated the log. @@ -51,7 +51,7 @@ public enum RedisLogging { internal static let pubsubTarget = "rdstk_ps_target" internal static let subscriptionCount = "rdstk_sub_count" } - + public static let baseConnectionLogger = Logger(label: Labels.connection) public static let baseConnectionPoolLogger = Logger(label: Labels.connectionPool) } @@ -96,35 +96,35 @@ internal protocol RedisClientWithUserContext: RedisClient { /// instance to capture command logs within their preferred contexts. internal struct UserContextRedisClient: RedisClient { internal var eventLoop: EventLoop { self.client.eventLoop } - + private let client: Client internal let logger: Logger - + internal init(client: Client, logger: Logger) { self.client = client self.logger = logger } - + // Create a new instance of the custom logging implementation reusing the same client. - + internal func logging(to logger: Logger) -> RedisClient { - return UserContextRedisClient(client: self.client, logger: logger) + UserContextRedisClient(client: self.client, logger: logger) } - + // Forward the commands to the underlying client - + internal func send(command: String, with arguments: [RESPValue]) -> EventLoopFuture { - return self.eventLoop.flatSubmit { - return self.client.send(command: command, with: arguments, logger: self.logger) + self.eventLoop.flatSubmit { + self.client.send(command: command, with: arguments, logger: self.logger) } } - + internal func unsubscribe(from channels: [RedisChannelName]) -> EventLoopFuture { - return self.eventLoop.flatSubmit { self.client.unsubscribe(from: channels, logger: self.logger) } + self.eventLoop.flatSubmit { self.client.unsubscribe(from: channels, logger: self.logger) } } - + internal func punsubscribe(from patterns: [String]) -> EventLoopFuture { - return self.eventLoop.flatSubmit { self.client.punsubscribe(from: patterns, logger: self.logger) } + self.eventLoop.flatSubmit { self.client.punsubscribe(from: patterns, logger: self.logger) } } internal func subscribe( @@ -133,7 +133,7 @@ internal struct UserContextRedisClient: Redi onSubscribe subscribeHandler: RedisSubscriptionChangeHandler?, onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler? ) -> EventLoopFuture { - return self.eventLoop.flatSubmit { + self.eventLoop.flatSubmit { self.client.subscribe( to: channels, messageReceiver: receiver, @@ -143,14 +143,14 @@ internal struct UserContextRedisClient: Redi ) } } - + internal func psubscribe( to patterns: [String], messageReceiver receiver: @escaping RedisSubscriptionMessageReceiver, onSubscribe subscribeHandler: RedisSubscriptionChangeHandler?, onUnsubscribe unsubscribeHandler: RedisSubscriptionChangeHandler? ) -> EventLoopFuture { - return self.eventLoop.flatSubmit { + self.eventLoop.flatSubmit { self.client.psubscribe( to: patterns, messageReceiver: receiver, diff --git a/Sources/RediStack/RedisMetrics.swift b/Sources/RediStack/RedisMetrics.swift index cb007d0c..9ec6394f 100644 --- a/Sources/RediStack/RedisMetrics.swift +++ b/Sources/RediStack/RedisMetrics.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019-2020 RediStack project authors +// Copyright (c) 2019-2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -39,7 +39,7 @@ public struct RedisMetrics { case commandRoundTripTime public var description: String { - return "RediStack.\(self.rawValue)" + "RediStack.\(self.rawValue)" } } @@ -63,7 +63,7 @@ public struct RedisMetrics { /// is first sent through the `NIO.Channel`, to when the response is first resolved. public static let commandRoundTripTime = Timer(label: .commandRoundTripTime) - private init() { } + private init() {} } extension RedisMetrics { @@ -71,28 +71,28 @@ extension RedisMetrics { public class IncrementalGauge { private let gauge: Gauge private let count = ManagedAtomic(0) - + /// The number of the objects that are currently reported as active. - public var currentCount: Int { return count.load(ordering: .sequentiallyConsistent) } - + public var currentCount: Int { count.load(ordering: .sequentiallyConsistent) } + internal init(_ label: Label) { self.gauge = .init(label: label) } - + /// Increments the current count by the amount specified. /// - Parameter amount: The number to increase the current count by. Default is `1`. public func increment(by amount: Int = 1) { self.count.wrappingIncrement(by: amount, ordering: .sequentiallyConsistent) self.gauge.record(self.count.load(ordering: .sequentiallyConsistent)) } - + /// Decrements the current count by the amount specified. /// - Parameter amount: The number to decrease the current count by. Default is `1`. public func decrement(by amount: Int = 1) { self.count.wrappingDecrement(by: amount, ordering: .sequentiallyConsistent) self.gauge.record(self.count.load(ordering: .sequentiallyConsistent)) } - + /// Resets the current count to `0`. public func reset() { _ = self.count.exchange(0, ordering: .sequentiallyConsistent) diff --git a/Sources/RediStack/_Deprecations.swift b/Sources/RediStack/_Deprecations.swift index 028ce47f..f6de814f 100644 --- a/Sources/RediStack/_Deprecations.swift +++ b/Sources/RediStack/_Deprecations.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020-2022 RediStack project authors +// Copyright (c) 2020-2022 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -72,7 +72,7 @@ extension RedisConnection { } catch { return eventLoop.makeFailedFuture(error) } - + return self.make(configuration: config, boundEventLoop: eventLoop, configuredTCPClient: tcpClient) } } @@ -104,8 +104,8 @@ extension RedisConnectionPool { loop: EventLoop, maximumConnectionCount: RedisConnectionPoolSize, minimumConnectionCount: Int = 1, - connectionPassword: String? = nil, // config - connectionLogger: Logger = .redisBaseConnectionLogger, // config + connectionPassword: String? = nil, // config + connectionLogger: Logger = .redisBaseConnectionLogger, // config connectionTCPClient: ClientBootstrap? = nil, poolLogger: Logger = .redisBaseConnectionPoolLogger, connectionBackoffFactor: Float32 = 2, @@ -144,11 +144,11 @@ extension RedisKey.Lifetime { extension Channel { @available(*, deprecated, renamed: "pipeline.addBaseRedisHandlers()") public func addBaseRedisHandlers() -> EventLoopFuture { - return self.pipeline.addBaseRedisHandlers() + self.pipeline.addBaseRedisHandlers() } @available(*, deprecated, renamed: "pipeline.addRedisPubSubHandler()") public func addPubSubHandler() -> EventLoopFuture { - return self.pipeline.addRedisPubSubHandler() + self.pipeline.addRedisPubSubHandler() } } diff --git a/Sources/RediStackTestUtils/EmbeddedMockRedisServer.swift b/Sources/RediStackTestUtils/EmbeddedMockRedisServer.swift index ef5facfe..58fd6eaf 100644 --- a/Sources/RediStackTestUtils/EmbeddedMockRedisServer.swift +++ b/Sources/RediStackTestUtils/EmbeddedMockRedisServer.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020 RediStack project authors +// Copyright (c) 2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,10 +12,10 @@ // //===----------------------------------------------------------------------===// -import RediStack -import XCTest import NIOCore import NIOEmbedded +import RediStack +import XCTest internal enum MockConnectionPoolError: Error { case unexpectedMessage diff --git a/Sources/RediStackTestUtils/Extensions/General.swift b/Sources/RediStackTestUtils/Extensions/General.swift index 1663e019..a9f172ae 100644 --- a/Sources/RediStackTestUtils/Extensions/General.swift +++ b/Sources/RediStackTestUtils/Extensions/General.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -18,8 +18,8 @@ private let allocator = ByteBufferAllocator() extension String { /// The UTF-8 byte representation of the string. - public var bytes: [UInt8] { return .init(self.utf8) } - + public var bytes: [UInt8] { .init(self.utf8) } + /// Creates a `NIO.ByteBuffer` with the string's value written into it. public var byteBuffer: ByteBuffer { var buffer = allocator.buffer(capacity: self.count) diff --git a/Sources/RediStackTestUtils/Extensions/RediStack.swift b/Sources/RediStackTestUtils/Extensions/RediStack.swift index dad09cb1..fef5bac6 100644 --- a/Sources/RediStackTestUtils/Extensions/RediStack.swift +++ b/Sources/RediStackTestUtils/Extensions/RediStack.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019-2020 RediStack project authors +// Copyright (c) 2019-2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information diff --git a/Sources/RediStackTestUtils/RedisConnectionPoolIntegrationTestCase.swift b/Sources/RediStackTestUtils/RedisConnectionPoolIntegrationTestCase.swift index 72ead0b0..a82e7a41 100644 --- a/Sources/RediStackTestUtils/RedisConnectionPoolIntegrationTestCase.swift +++ b/Sources/RediStackTestUtils/RedisConnectionPoolIntegrationTestCase.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020 RediStack project authors +// Copyright (c) 2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -13,9 +13,9 @@ //===----------------------------------------------------------------------===// import NIOCore +import NIOPosix import RediStack import XCTest -import NIOPosix /// A helper `XCTestCase` subclass that does the standard work of creating a connection pool to use in test cases. /// @@ -32,7 +32,7 @@ open class RedisConnectionPoolIntegrationTestCase: XCTestCase { open var redisPort: Int { RedisConnection.Configuration.defaultPort } /// The password to use to connect to Redis. Default is `nil` - no password authentication. - open var redisPassword: String? { return nil } + open var redisPassword: String? { nil } public var pool: RedisConnectionPool! @@ -93,7 +93,8 @@ open class RedisConnectionPoolIntegrationTestCase: XCTestCase { connectionRetryTimeout: TimeAmount?, minimumConnectionCount: Int ) throws -> RedisConnectionPool { - let addresses = try initialAddresses ?? [SocketAddress.makeAddressResolvingHost(self.redisHostname, port: self.redisPort)] + let addresses = + try initialAddresses ?? [SocketAddress.makeAddressResolvingHost(self.redisHostname, port: self.redisPort)] let pool = RedisConnectionPool( configuration: RedisConnectionPool.Configuration( initialServerConnectionAddresses: addresses, diff --git a/Sources/RediStackTestUtils/RedisIntegrationTestCase.swift b/Sources/RediStackTestUtils/RedisIntegrationTestCase.swift index ddab1401..b3f0d984 100644 --- a/Sources/RediStackTestUtils/RedisIntegrationTestCase.swift +++ b/Sources/RediStackTestUtils/RedisIntegrationTestCase.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019-2020 RediStack project authors +// Copyright (c) 2019-2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -13,8 +13,8 @@ //===----------------------------------------------------------------------===// import NIOCore -import RediStack import NIOPosix +import RediStack import XCTest /// A helper `XCTestCase` subclass that does the standard work of creating a connection to use in test cases. @@ -27,17 +27,17 @@ open class RedisIntegrationTestCase: XCTestCase { /// /// This is especially useful to override if you build on Linux & macOS where Redis might be installed locally vs. through Docker. open var redisHostname: String { RedisConnection.Configuration.defaultHostname } - + /// The port to connect over to Redis, defaulting to `RedisConnection.defaultPort`. open var redisPort: Int { RedisConnection.Configuration.defaultPort } - + /// The password to use to connect to Redis. Default is `nil` - no password authentication. - open var redisPassword: String? { return nil } - + open var redisPassword: String? { nil } + public var connection: RedisConnection! - + private let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2) - + deinit { do { try self.eventLoopGroup.syncShutdownGracefully() @@ -45,7 +45,7 @@ open class RedisIntegrationTestCase: XCTestCase { print("Failed to gracefully shutdown ELG: \(error)") } } - + /// Creates a `RediStack.RedisConnection` for the next test case, calling `fatalError` if it was not successful. /// /// See `XCTest.XCTestCase.setUp()` @@ -56,7 +56,7 @@ open class RedisIntegrationTestCase: XCTestCase { fatalError("Failed to make a RedisConnection: \(error)") } } - + /// Sends a "FLUSHALL" command to Redis to clear it of any data from the previous test, then closes the connection. /// /// If any steps fail, a `fatalError` is thrown. @@ -69,19 +69,19 @@ open class RedisIntegrationTestCase: XCTestCase { .flatMap { _ in self.connection.close() } .wait() } - + self.connection = nil } catch { fatalError("Failed to properly cleanup connection: \(error)") } } - + /// Creates a new connection for use in tests. /// /// See `RedisConnection.make(configuration:boundEventLoop:)` /// - Returns: The new `RediStack.RedisConnection`. public func makeNewConnection() throws -> RedisConnection { - return try RedisConnection.make( + try RedisConnection.make( configuration: .init( host: self.redisHostname, port: self.redisPort, diff --git a/Sources/RediStackTestUtils/_Deprecations.swift b/Sources/RediStackTestUtils/_Deprecations.swift index 0c27fef3..4f5e4262 100644 --- a/Sources/RediStackTestUtils/_Deprecations.swift +++ b/Sources/RediStackTestUtils/_Deprecations.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020 RediStack project authors +// Copyright (c) 2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -19,7 +19,7 @@ extension RedisConnection { /// A default hostname of `localhost` to try and connect to Redis at. @available(*, deprecated, message: "Use RedisConnection.Configuration.defaultHostname") public static let defaultHostname = "localhost" - + /// Creates a connection intended for tests using `REDIS_URL` and `REDIS_PW` environment variables if available. /// /// The default URL is `127.0.0.1` while the default port is `RedisConnection.defaultPort`. @@ -27,7 +27,9 @@ extension RedisConnection { /// If `REDIS_PW` is not defined, no authentication will happen on the connection. /// - Parameters: /// - eventLoop: The event loop that the connection should execute on. + /// - host: the host to connect to. /// - port: The port to connect on. + /// - password: The optional password to use while connecting. /// - Returns: A `NIO.EventLoopFuture` that resolves with the new connection. @available(*, deprecated, message: "Use RedisConnection.make(configuration:boundEventLoop:) method") public static func connect( @@ -42,7 +44,7 @@ extension RedisConnection { } catch { return eventLoop.makeFailedFuture(error) } - + return RedisConnection.connect(to: address, on: eventLoop, password: password) } } diff --git a/Sources/RedisTypes/RedisSet.swift b/Sources/RedisTypes/RedisSet.swift index bb237ffc..42cffcee 100644 --- a/Sources/RedisTypes/RedisSet.swift +++ b/Sources/RedisTypes/RedisSet.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020 RediStack project authors +// Copyright (c) 2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -26,7 +26,7 @@ extension RedisClient { /// - type: The Swift type representation of the elements in the set. /// - Returns: A `RedisSet` for repeatedly interacting with a specific Set value in Redis. public func makeSet(key: RedisKey, type: Element.Type = Element.self) -> RedisSet { - return RedisSet(identifier: key, client: self) + RedisSet(identifier: key, client: self) } } @@ -58,11 +58,11 @@ extension RedisError { /// See [https://redis.io/topics/data-types-intro#sets](https://redis.io/topics/data-types-intro#sets) public struct RedisSet where Element: RESPValueConvertible { /// The key in Redis that this instance is a reference to. - public var identifier: RedisKey { return self.id } - + public var identifier: RedisKey { self.id } + private let id: RedisKey private let client: RedisClient - + /// Initializes a new reference to a specific Redis key that holds a Set value type. /// - Parameters: /// - identifier: The key identifier to reference this set. @@ -71,13 +71,13 @@ public struct RedisSet where Element: RESPValueConvertible { self.id = identifier self.client = client } - + /// Resolves the number of elements in the set. /// /// See `RediStack.RedisClient.scard(of:)` - public var count: EventLoopFuture { return self.client.scard(of: self.id) } + public var count: EventLoopFuture { self.client.scard(of: self.id) } /// Resolves a Boolean value that indicates whether the set is empty. - public var isEmpty: EventLoopFuture { return self.count.map { $0 == 0 } } + public var isEmpty: EventLoopFuture { self.count.map { $0 == 0 } } /// Resolves all of elements in the set. /// /// All member elements will be converted into the type `Element`, based on its conformance to `RediStack.RESPValueConvertible`. @@ -85,17 +85,17 @@ public struct RedisSet where Element: RESPValueConvertible { /// /// See `RediStack.RedisClient.smembers(of:)` public var allElements: EventLoopFuture<[Element]> { - return self.client.smembers(of: self.id) + self.client.smembers(of: self.id) .map { $0.compactMap(Element.init) } } - + /// Resolves a Boolean value that indicates whether the given element exists in the set. /// /// See `RediStack.RedisClient.sismember(_:of:)` /// - Parameter member: An element to look for in the set. /// - Returns: A `NIO.EventLoopFuture` resolving `true` if `member` exists in the set; otherwise, `false`. public func contains(_ member: Element) -> EventLoopFuture { - return self.client.sismember(member, of: self.id) + self.client.sismember(member, of: self.id) } } @@ -108,10 +108,10 @@ extension RedisSet { /// - Parameter newMember: An element to insert into the set. /// - Returns: A `NIO.EventLoopFuture` resolving `true` if `newMember` was inserted into the set; otherwise, `false`. public func insert(_ newMember: Element) -> EventLoopFuture { - return self.insert(contentsOf: [newMember]) + self.insert(contentsOf: [newMember]) .map { $0 == 1 } } - + /// Inserts the elements of an array into the set that do not already exist. /// /// See `RediStack.RedisClient.sadd(_:to:)` @@ -134,19 +134,19 @@ extension RedisSet { /// - other:A set of the same type as the current set. /// - Returns: A `NIO.EventLoopFuture` resolving `true` if the element was moved; otherwise, `false`. public func move(_ member: Element, to other: RedisSet) -> EventLoopFuture { - return self.client.smove(member, from: self.id, to: other.id) + self.client.smove(member, from: self.id, to: other.id) } - + /// Removes the given element from the set. /// /// See `RediStack.RedisClient.srem(_:from:)` - /// - Parameter members: The element in the set to remove. + /// - Parameter member: The element in the set to remove. /// - Returns: A `NIO.EventLoopFuture` resolving `true` if `member` was removed from the set; otherwise, `false`. public func remove(_ member: Element) -> EventLoopFuture { - return self.remove([member]) + self.remove([member]) .map { $0 == 1 } } - + /// Removes the given elements from the set. /// /// See `RediStack.RedisClient.srem(_:from:)` @@ -156,13 +156,13 @@ extension RedisSet { guard members.count > 0 else { return self.client.eventLoop.makeSucceededFuture(0) } return self.client.srem(members, from: self.id) } - + /// Removes all elements from the array. /// /// See `RediStack.RedisClient.delete(_:)` /// - Returns: A `NIO.EventLoopFuture` resolving `true` if all elements were removed; otherwise, `false`. public func removeAll() -> EventLoopFuture { - return self.client.delete([self.id]) + self.client.delete([self.id]) .map { $0 == 1 } } } @@ -179,13 +179,13 @@ extension RedisSet { /// /// - Returns: A `NIO.EventLoopFuture` resolving a randomly popped element from the set, or `nil` if the set was empty. public func popRandomElement() -> EventLoopFuture { - return self.client.spop(from: self.id) + self.client.spop(from: self.id) .map { response in guard response.count > 0 else { return nil } return Element(fromRESP: response[0]) } } - + /// Removes and resolves multiple elements from the set, up to the given `max` count. /// /// See `RediStack.RedisClient.spop(from:max:)` @@ -198,9 +198,9 @@ extension RedisSet { guard count >= 0 else { return self.client.eventLoop.makeFailedFuture(RedisError.indexOutOfRange) } guard count >= 1 else { return self.client.eventLoop.makeSucceededFuture([]) } return self.client.spop(from: self.id, max: count) - .map { return $0.compactMap(Element.init) } + .map { $0.compactMap(Element.init) } } - + /// Resolves a random element in the set. /// /// See `RediStack.RedisClient.srandmember(from:max:)` @@ -210,13 +210,13 @@ extension RedisSet { /// /// - Returns: A `NIO.EventLoopFuture` resolving a randoml element from the set, or `nil` if the set was empty. public func randomElement() -> EventLoopFuture { - return self.client.srandmember(from: self.id) + self.client.srandmember(from: self.id) .map { response in guard response.count > 0 else { return nil } return Element(fromRESP: response[0]) } } - + /// Resolves multiple elements from the set, up to the given `max` count. /// /// // assume `intSet` has 3 elements diff --git a/Tests/RESP3Tests/RESP3TokenTests.swift b/Tests/RESP3Tests/RESP3TokenTests.swift index a437509c..9b17419d 100644 --- a/Tests/RESP3Tests/RESP3TokenTests.swift +++ b/Tests/RESP3Tests/RESP3TokenTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2023 RediStack project authors +// Copyright (c) 2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -14,9 +14,10 @@ import NIOCore import NIOTestUtils -@testable import RESP3 import XCTest +@testable import RESP3 + final class RESP3TokenTests: XCTestCase { func testRESPNullToken() { let input = ByteBuffer(string: "_\r\n") @@ -95,7 +96,7 @@ final class RESP3TokenTests: XCTestCase { XCTAssertThrowsError( try ByteToMessageDecoderVerifier.verifyDecoder( inputOutputPairs: [ - (buffer, [RESP3Token(validated: .init())]), + (buffer, [RESP3Token(validated: .init())]) ], decoderFactory: { RESP3TokenDecoder() } ) @@ -158,14 +159,14 @@ final class RESP3TokenTests: XCTestCase { // TODO: this test currently succeeds, even though it has an invalid value func testRESPDoubleInvalid() throws { let invalid = [ - ",.1\r\n", + ",.1\r\n" ] for value in invalid { XCTAssertThrowsError( try ByteToMessageDecoderVerifier.verifyDecoder( inputOutputPairs: [ - (.init(string: value), [RESP3Token(validated: .init())]), + (.init(string: value), [RESP3Token(validated: .init())]) ], decoderFactory: { RESP3TokenDecoder() } ) @@ -178,7 +179,7 @@ final class RESP3TokenTests: XCTestCase { func testRESPBigNumber() { let valid = [ - "123", + "123" ] for value in valid { @@ -187,7 +188,7 @@ final class RESP3TokenTests: XCTestCase { XCTAssertNoThrow( try ByteToMessageDecoderVerifier.verifyDecoder( inputOutputPairs: [ - (token, [RESP3Token(validated: token)]), + (token, [RESP3Token(validated: token)]) ], decoderFactory: { RESP3TokenDecoder() } ), @@ -211,7 +212,7 @@ final class RESP3TokenTests: XCTestCase { XCTAssertThrowsError( try ByteToMessageDecoderVerifier.verifyDecoder( inputOutputPairs: [ - (buffer, [RESP3Token(validated: .init())]), + (buffer, [RESP3Token(validated: .init())]) ], decoderFactory: { RESP3TokenDecoder() } ) @@ -303,18 +304,45 @@ final class RESP3TokenTests: XCTestCase { ) XCTAssertEqual(respEmptyArray.value, .array(.init(count: 0, buffer: .init()))) - XCTAssertEqual(respSimpleStringArray1.value, .array(.init(count: 1, buffer: .init(string: "+aaaabbbbcccc\r\n")))) - XCTAssertEqual(respSimpleStringArray2.value, .array(.init(count: 2, buffer: .init(string: "+aaaa\r\n+bbbb\r\n")))) - XCTAssertEqual(respSimpleStringArray3.value, .array(.init(count: 3, buffer: .init(string: "*0\r\n+a\r\n-b\r\n")))) + XCTAssertEqual( + respSimpleStringArray1.value, + .array(.init(count: 1, buffer: .init(string: "+aaaabbbbcccc\r\n"))) + ) + XCTAssertEqual( + respSimpleStringArray2.value, + .array(.init(count: 2, buffer: .init(string: "+aaaa\r\n+bbbb\r\n"))) + ) + XCTAssertEqual( + respSimpleStringArray3.value, + .array(.init(count: 3, buffer: .init(string: "*0\r\n+a\r\n-b\r\n"))) + ) XCTAssertEqual(respSimpleStringPush3.value, .push(.init(count: 3, buffer: .init(string: "*0\r\n+a\r\n-b\r\n")))) XCTAssertEqual(respSimpleStringSet3.value, .set(.init(count: 3, buffer: .init(string: "*0\r\n+a\r\n#t\r\n")))) XCTAssertEqual(respEmptyArray.testArray, []) XCTAssertEqual(respSimpleStringArray1.testArray, [.simpleString(.init(string: "aaaabbbbcccc"))]) - XCTAssertEqual(respSimpleStringArray2.testArray, [.simpleString(.init(string: "aaaa")), .simpleString(.init(string: "bbbb"))]) - XCTAssertEqual(respSimpleStringArray3.testArray, [.array(.init(count: 0, buffer: .init())), .simpleString(.init(string: "a")), .simpleError(.init(string: "b"))]) - XCTAssertEqual(respSimpleStringPush3.testArray, [.array(.init(count: 0, buffer: .init())), .simpleString(.init(string: "a")), .simpleError(.init(string: "b"))]) - XCTAssertEqual(respSimpleStringSet3.testArray, [.array(.init(count: 0, buffer: .init())), .simpleString(.init(string: "a")), .boolean(true)]) + XCTAssertEqual( + respSimpleStringArray2.testArray, + [.simpleString(.init(string: "aaaa")), .simpleString(.init(string: "bbbb"))] + ) + XCTAssertEqual( + respSimpleStringArray3.testArray, + [ + .array(.init(count: 0, buffer: .init())), .simpleString(.init(string: "a")), + .simpleError(.init(string: "b")), + ] + ) + XCTAssertEqual( + respSimpleStringPush3.testArray, + [ + .array(.init(count: 0, buffer: .init())), .simpleString(.init(string: "a")), + .simpleError(.init(string: "b")), + ] + ) + XCTAssertEqual( + respSimpleStringSet3.testArray, + [.array(.init(count: 0, buffer: .init())), .simpleString(.init(string: "a")), .boolean(true)] + ) } func testDeeplyNestedRESPCantStackOverflow() { @@ -323,7 +351,7 @@ final class RESP3TokenTests: XCTestCase { (">1\r\n", ">0\r\n"), ("~1\r\n", "~0\r\n"), ("%1\r\n#t\r\n", "%0\r\n"), - ("|1\r\n#t\r\n", "|0\r\n") + ("|1\r\n#t\r\n", "|0\r\n"), ] for (nested, final) in pattern { @@ -347,7 +375,7 @@ final class RESP3TokenTests: XCTestCase { XCTAssertNoThrow( try ByteToMessageDecoderVerifier.verifyDecoder( inputOutputPairs: [ - (buffer, [expected]), + (buffer, [expected]) ], decoderFactory: { RESP3TokenDecoder() } ) @@ -379,10 +407,16 @@ final class RESP3TokenTests: XCTestCase { XCTAssertEqual(respEmptyMap.value, .map(.init(count: 0, buffer: .init()))) XCTAssertEqual(respSimpleStringMap1.value, .map(.init(count: 1, buffer: .init(string: "+aaaa\r\n+bbbb\r\n")))) - XCTAssertEqual(respSimpleStringAttributes1.value, .attribute(.init(count: 1, buffer: .init(string: "+aaaa\r\n#f\r\n")))) + XCTAssertEqual( + respSimpleStringAttributes1.value, + .attribute(.init(count: 1, buffer: .init(string: "+aaaa\r\n#f\r\n"))) + ) XCTAssertEqual(respEmptyMap.testDict, [:]) - XCTAssertEqual(respSimpleStringMap1.testDict, [.simpleString(.init(string: "aaaa")): .simpleString(.init(string: "bbbb"))]) + XCTAssertEqual( + respSimpleStringMap1.testDict, + [.simpleString(.init(string: "aaaa")): .simpleString(.init(string: "bbbb"))] + ) XCTAssertEqual(respSimpleStringAttributes1.testDict, [.simpleString(.init(string: "aaaa")): .boolean(false)]) } } diff --git a/Tests/RediStackIntegrationTests/Commands/BasicCommandsTests.swift b/Tests/RediStackIntegrationTests/Commands/BasicCommandsTests.swift index b9261871..6bedea50 100644 --- a/Tests/RediStackIntegrationTests/Commands/BasicCommandsTests.swift +++ b/Tests/RediStackIntegrationTests/Commands/BasicCommandsTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,17 +12,18 @@ // //===----------------------------------------------------------------------===// -@testable import RediStack import RediStackTestUtils import XCTest +@testable import RediStack + final class BasicCommandsTests: RediStackIntegrationTestCase { func test_select() { XCTAssertNoThrow(try connection.select(database: 3).wait()) } func test_delete() throws { - let keys = [ #function + "1", #function + "2", #function + "3" ].map(RedisKey.init(_:)) + let keys = [#function + "1", #function + "2", #function + "3"].map(RedisKey.init(_:)) try connection.set(keys[0], to: "value").wait() try connection.set(keys[1], to: "value").wait() try connection.set(keys[2], to: "value").wait() @@ -58,7 +59,7 @@ final class BasicCommandsTests: RediStackIntegrationTestCase { XCTAssertNotNil(try connection.get(#function).wait()) XCTAssertTrue(try connection.expire(#function, after: .nanoseconds(1)).wait()) XCTAssertEqual(try connection.get(#function).wait(), .null) - + try connection.set(#function, to: "new value").wait() XCTAssertNotNil(try connection.get(#function).wait()) XCTAssertTrue(try connection.expire(#function, after: .seconds(10)).wait()) @@ -166,29 +167,29 @@ final class BasicCommandsTests: RediStackIntegrationTestCase { // TODO: #23 -- Rework Scan Unit Test // This is extremely flakey, and causes non-deterministic failures because of the assert on key counts -// func test_scan() throws { -// var dataset: [RedisKey] = .init(repeating: "", count: 10) -// for index in 1...15 { -// let key = RedisKey("key\(index)\(index % 2 == 0 ? "_even" : "_odd")") -// dataset.append(key) -// _ = try connection.set(key, to: "\(index)").wait() -// } -// -// var (cursor, keys) = try connection.scan(count: 5).wait() -// XCTAssertGreaterThanOrEqual(cursor, 0) -// XCTAssertGreaterThanOrEqual(keys.count, 5) -// -// (_, keys) = try connection.scan(startingFrom: cursor, count: 8).wait() -// XCTAssertGreaterThanOrEqual(keys.count, 8) -// -// (cursor, keys) = try connection.scan(matching: "*_odd").wait() -// XCTAssertGreaterThanOrEqual(cursor, 0) -// XCTAssertGreaterThanOrEqual(keys.count, 1) -// XCTAssertLessThanOrEqual(keys.count, 7) -// -// (cursor, keys) = try connection.scan(matching: "*_even*").wait() -// XCTAssertGreaterThanOrEqual(cursor, 0) -// XCTAssertGreaterThanOrEqual(keys.count, 1) -// XCTAssertLessThanOrEqual(keys.count, 7) -// } + // func test_scan() throws { + // var dataset: [RedisKey] = .init(repeating: "", count: 10) + // for index in 1...15 { + // let key = RedisKey("key\(index)\(index % 2 == 0 ? "_even" : "_odd")") + // dataset.append(key) + // _ = try connection.set(key, to: "\(index)").wait() + // } + // + // var (cursor, keys) = try connection.scan(count: 5).wait() + // XCTAssertGreaterThanOrEqual(cursor, 0) + // XCTAssertGreaterThanOrEqual(keys.count, 5) + // + // (_, keys) = try connection.scan(startingFrom: cursor, count: 8).wait() + // XCTAssertGreaterThanOrEqual(keys.count, 8) + // + // (cursor, keys) = try connection.scan(matching: "*_odd").wait() + // XCTAssertGreaterThanOrEqual(cursor, 0) + // XCTAssertGreaterThanOrEqual(keys.count, 1) + // XCTAssertLessThanOrEqual(keys.count, 7) + // + // (cursor, keys) = try connection.scan(matching: "*_even*").wait() + // XCTAssertGreaterThanOrEqual(cursor, 0) + // XCTAssertGreaterThanOrEqual(keys.count, 1) + // XCTAssertLessThanOrEqual(keys.count, 7) + // } } diff --git a/Tests/RediStackIntegrationTests/Commands/HashCommandsTests.swift b/Tests/RediStackIntegrationTests/Commands/HashCommandsTests.swift index 8d3abb68..b9b2cfd7 100644 --- a/Tests/RediStackIntegrationTests/Commands/HashCommandsTests.swift +++ b/Tests/RediStackIntegrationTests/Commands/HashCommandsTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,10 +12,11 @@ // //===----------------------------------------------------------------------===// -@testable import RediStack import RediStackTestUtils import XCTest +@testable import RediStack + final class HashCommandsTests: RediStackIntegrationTestCase { func test_hset() throws { var result = try connection.hset("test", to: "\(#line)", in: #function).wait() @@ -98,7 +99,7 @@ final class HashCommandsTests: RediStackIntegrationTestCase { func test_hkeys() throws { let dataset: [String: String] = [ "first": "3", - "second": "foo" + "second": "foo", ] _ = try connection.hmset(dataset, in: #function).wait() let keys = try connection.hkeys(in: #function).wait() @@ -109,7 +110,7 @@ final class HashCommandsTests: RediStackIntegrationTestCase { func test_hvals() throws { let dataset = [ "first": "3", - "second": "foo" + "second": "foo", ] _ = try connection.hmset(dataset, in: #function).wait() let values = try connection.hvals(in: #function).wait().compactMap { String(fromRESP: $0) } @@ -134,32 +135,32 @@ final class HashCommandsTests: RediStackIntegrationTestCase { let float = try connection.hincrbyfloat(Float(-10.23523), field: "first", in: #function).wait() XCTAssertEqual(float, -3.95523) } - + // TODO: #23 -- Rework Scan Unit Test // This is extremely flakey, and causes non-deterministic failures because of the assert on key counts -// func test_hscan() throws { -// var dataset: [String: String] = [:] -// for index in 1...15 { -// let key = "key\(index)\(index % 2 == 0 ? "_even" : "_odd")" -// dataset[key] = "\(index)" -// } -// _ = try connection.hmset(dataset, in: #function).wait() -// -// var (cursor, fields) = try connection.hscan(#function, count: 5).wait() -// XCTAssertGreaterThanOrEqual(cursor, 0) -// XCTAssertGreaterThanOrEqual(fields.count, 5) -// -// (_, fields) = try connection.hscan(#function, startingFrom: cursor, count: 8).wait() -// XCTAssertGreaterThanOrEqual(fields.count, 8) -// -// (cursor, fields) = try connection.hscan(#function, matching: "*_odd").wait() -// XCTAssertGreaterThanOrEqual(cursor, 0) -// XCTAssertGreaterThanOrEqual(fields.count, 1) -// XCTAssertLessThanOrEqual(fields.count, 8) -// -// (cursor, fields) = try connection.hscan(#function, matching: "*_ev*").wait() -// XCTAssertGreaterThanOrEqual(cursor, 0) -// XCTAssertGreaterThanOrEqual(fields.count, 1) -// XCTAssertLessThanOrEqual(fields.count, 7) -// } + // func test_hscan() throws { + // var dataset: [String: String] = [:] + // for index in 1...15 { + // let key = "key\(index)\(index % 2 == 0 ? "_even" : "_odd")" + // dataset[key] = "\(index)" + // } + // _ = try connection.hmset(dataset, in: #function).wait() + // + // var (cursor, fields) = try connection.hscan(#function, count: 5).wait() + // XCTAssertGreaterThanOrEqual(cursor, 0) + // XCTAssertGreaterThanOrEqual(fields.count, 5) + // + // (_, fields) = try connection.hscan(#function, startingFrom: cursor, count: 8).wait() + // XCTAssertGreaterThanOrEqual(fields.count, 8) + // + // (cursor, fields) = try connection.hscan(#function, matching: "*_odd").wait() + // XCTAssertGreaterThanOrEqual(cursor, 0) + // XCTAssertGreaterThanOrEqual(fields.count, 1) + // XCTAssertLessThanOrEqual(fields.count, 8) + // + // (cursor, fields) = try connection.hscan(#function, matching: "*_ev*").wait() + // XCTAssertGreaterThanOrEqual(cursor, 0) + // XCTAssertGreaterThanOrEqual(fields.count, 1) + // XCTAssertLessThanOrEqual(fields.count, 7) + // } } diff --git a/Tests/RediStackIntegrationTests/Commands/ListCommandsTests.swift b/Tests/RediStackIntegrationTests/Commands/ListCommandsTests.swift index 99127fbb..38e25f79 100644 --- a/Tests/RediStackIntegrationTests/Commands/ListCommandsTests.swift +++ b/Tests/RediStackIntegrationTests/Commands/ListCommandsTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,10 +12,11 @@ // //===----------------------------------------------------------------------===// -@testable import RediStack import RediStackTestUtils import XCTest +@testable import RediStack + final class ListCommandsTests: RediStackIntegrationTestCase { func test_llen() throws { var length = try connection.llen(of: #function).wait() @@ -62,7 +63,7 @@ final class ListCommandsTests: RediStackIntegrationTestCase { XCTAssertEqual(elements.count, 5) XCTAssertEqual(Int(fromRESP: elements[0]), 1) XCTAssertEqual(Int(fromRESP: elements[4]), 5) - + elements = try connection.lrange(from: #function, fromIndex: 1).wait() XCTAssertEqual(elements.count, 4) elements = try connection.lrange(from: #function, fromIndex: -3).wait() @@ -178,7 +179,7 @@ final class ListCommandsTests: RediStackIntegrationTestCase { size = try connection.lpushx(30, into: #function).wait() XCTAssertEqual(size, 2) let element = try connection.rpop(from: #function) - .map { return Int(fromRESP: $0) } + .map { Int(fromRESP: $0) } .wait() XCTAssertEqual(element, 10) } @@ -237,37 +238,37 @@ final class ListCommandsTests: RediStackIntegrationTestCase { size = try connection.rpushx(30, into: #function).wait() XCTAssertEqual(size, 2) let element = try connection.lpop(from: #function) - .map { return Int(fromRESP: $0) } + .map { Int(fromRESP: $0) } .wait() XCTAssertEqual(element, 10) } - + func test_ltrim() throws { let setup = { _ = try self.connection.delete(#function).wait() _ = try self.connection.lpush([5, 4, 3, 2, 1], into: #function).wait() } - let getElements = { return try self.connection.lrange(from: #function, fromIndex: 0).wait() } - + let getElements = { try self.connection.lrange(from: #function, fromIndex: 0).wait() } + try setup() - + XCTAssertNoThrow(try connection.ltrim(#function, before: 1, after: 3).wait()) XCTAssertNoThrow(try connection.ltrim(#function, keepingIndices: 0...1).wait()) var elements = try getElements() XCTAssertEqual(elements.count, 2) - + try setup() - + XCTAssertNoThrow(try connection.ltrim(#function, keepingIndices: (-3)...).wait()) elements = try getElements() XCTAssertEqual(elements.count, 3) - + try setup() - + XCTAssertNoThrow(try connection.ltrim(#function, keepingIndices: ...(-4)).wait()) elements = try getElements() XCTAssertEqual(elements.count, 2) - + try setup() XCTAssertNoThrow(try connection.ltrim(#function, keepingIndices: ..<(-2)).wait()) elements = try getElements() diff --git a/Tests/RediStackIntegrationTests/Commands/PubSubCommandsTests.swift b/Tests/RediStackIntegrationTests/Commands/PubSubCommandsTests.swift index 94a6c854..806a7165 100644 --- a/Tests/RediStackIntegrationTests/Commands/PubSubCommandsTests.swift +++ b/Tests/RediStackIntegrationTests/Commands/PubSubCommandsTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020-2022 RediStack project authors +// Copyright (c) 2020-2022 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -22,12 +22,12 @@ final class RedisPubSubCommandsTests: RediStackIntegrationTestCase { let subscribeExpectation = self.expectation(description: "subscriber receives initial subscription message") let messageExpectation = self.expectation(description: "subscriber receives published message") let unsubscribeExpectation = self.expectation(description: "subscriber receives unsubscribe message") - + let subscriber = try self.makeNewConnection() defer { try? subscriber.close().wait() } let message = "Hello from Redis!" - + try subscriber.subscribe( to: #function, messageReceiver: { @@ -46,22 +46,22 @@ final class RedisPubSubCommandsTests: RediStackIntegrationTestCase { unsubscribeExpectation.fulfill() } ).wait() - + let subscribersCount = try self.connection.publish(message, to: #function).wait() XCTAssertEqual(subscribersCount, 1) - + try subscriber.unsubscribe(from: #function).wait() - + self.waitForExpectations(timeout: 1) } - + func test_multiChannel() throws { let channelMessageExpectation = self.expectation(description: "subscriber receives channel message") let patternMessageExpectation = self.expectation(description: "subscriber receives pattern message") - + let subscriber = try self.makeNewConnection() defer { try? subscriber.close().wait() } - + let channel = RedisChannelName(#function) let pattern = "\(channel.rawValue.dropLast(channel.rawValue.count / 2))*" @@ -71,45 +71,45 @@ final class RedisPubSubCommandsTests: RediStackIntegrationTestCase { try subscriber .psubscribe(to: pattern) { (_, _) in patternMessageExpectation.fulfill() } .wait() - + let subscriberCount = try self.connection.publish("hello!", to: channel).wait() XCTAssertEqual(subscriberCount, 2) - + self.waitForExpectations(timeout: 1) } - + func test_unsubscribeWithoutSubscriptions() throws { XCTAssertNoThrow(try self.connection.unsubscribe(from: #function).wait()) } - + func test_blockedCommandsThrowInPubSubMode() throws { try self.connection.subscribe(to: #function) { (_, _) in }.wait() defer { try? self.connection.unsubscribe(from: #function).wait() } - + XCTAssertThrowsError(try self.connection.lpush("value", into: "List").wait()) { XCTAssertTrue($0 is RedisError) } } - + func test_pingInPubSub() throws { try self.connection.subscribe(to: #function) { (_, _) in }.wait() defer { try? self.connection.unsubscribe(from: #function).wait() } - + let pong = try self.connection.ping().wait() XCTAssertEqual(pong, "PONG") - + let message = try self.connection.ping(with: "Hello").wait() XCTAssertEqual(message, "Hello") } - + func test_quitInPubSub() throws { try self.connection.subscribe(to: #function) { (_, _) in }.wait() defer { try? self.connection.unsubscribe(from: #function).wait() } - + let value = try self.connection.send(command: "QUIT").wait() XCTAssertEqual(value.string, "OK") } - + func test_unsubscribeFromAllChannels() throws { let subscriber = try self.makeNewConnection() defer { try? subscriber.close().wait() } @@ -125,34 +125,34 @@ final class RedisPubSubCommandsTests: RediStackIntegrationTestCase { onSubscribe: nil, onUnsubscribe: { _, _ in expectation.fulfill() } ).wait() - + XCTAssertTrue(subscriber.isSubscribed) try subscriber.unsubscribe().wait() XCTAssertFalse(subscriber.isSubscribed) - + self.waitForExpectations(timeout: 1) } func test_unsubscribeFromAllPatterns() throws { let subscriber = try self.makeNewConnection() defer { try? subscriber.close().wait() } - + let patterns = (1...3).map { ("*\(#function)\($0)") } - + let expectation = self.expectation(description: "all pattern subscriptions should be cancelled") expectation.expectedFulfillmentCount = patterns.count - + try subscriber.psubscribe( to: patterns, messageReceiver: { _, _ in }, onSubscribe: nil, onUnsubscribe: { _, _ in expectation.fulfill() } ).wait() - + XCTAssertTrue(subscriber.isSubscribed) try subscriber.punsubscribe().wait() XCTAssertFalse(subscriber.isSubscribed) - + self.waitForExpectations(timeout: 1) } @@ -172,7 +172,7 @@ final class RedisPubSubCommandsTests: RediStackIntegrationTestCase { onUnsubscribe: { _, _ in expectation.fulfill() } ).wait() XCTAssertTrue(subscriber.isSubscribed) - + try subscriber.psubscribe( to: "*\(#function)", messageReceiver: { _, _ in }, @@ -183,7 +183,7 @@ final class RedisPubSubCommandsTests: RediStackIntegrationTestCase { try subscriber.unsubscribe().wait() XCTAssertTrue(subscriber.isSubscribed) - + try subscriber.punsubscribe().wait() XCTAssertFalse(subscriber.isSubscribed) @@ -275,12 +275,12 @@ final class RedisPubSubCommandsPoolTests: RediStackConnectionPoolIntegrationTest let subscribeExpectation = self.expectation(description: "subscriber receives initial subscription message") let messageExpectation = self.expectation(description: "subscriber receives published message") let unsubscribeExpectation = self.expectation(description: "subscriber receives unsubscribe message") - + let subscriber = try self.makeNewPool() defer { subscriber.close() } let message = "Hello from Redis!" - + try subscriber.subscribe( to: #function, messageReceiver: { @@ -300,23 +300,23 @@ final class RedisPubSubCommandsPoolTests: RediStackConnectionPoolIntegrationTest } ).wait() XCTAssertEqual(subscriber.leasedConnectionCount, 1) - + let subscribersCount = try self.pool.publish(message, to: #function).wait() XCTAssertEqual(subscribersCount, 1) - + try subscriber.unsubscribe(from: #function).wait() XCTAssertEqual(subscriber.leasedConnectionCount, 0) - + self.waitForExpectations(timeout: 1) } func test_pool_multiChannel() throws { let channelMessageExpectation = self.expectation(description: "subscriber receives channel message") let patternMessageExpectation = self.expectation(description: "subscriber receives pattern message") - + let subscriber = try self.makeNewPool() defer { subscriber.close() } - + let channel = RedisChannelName(#function) let pattern = "\(channel.rawValue.dropLast(channel.rawValue.count / 2))*" @@ -328,10 +328,10 @@ final class RedisPubSubCommandsPoolTests: RediStackConnectionPoolIntegrationTest .psubscribe(to: pattern) { (_, _) in patternMessageExpectation.fulfill() } .wait() XCTAssertEqual(subscriber.leasedConnectionCount, 1) - + let subscriberCount = try self.pool.publish("hello!", to: channel).wait() XCTAssertEqual(subscriberCount, 2) - + self.waitForExpectations(timeout: 1) } @@ -347,9 +347,10 @@ final class RedisPubSubCommandsPoolTests: RediStackConnectionPoolIntegrationTest extension RedisPubSubCommandsTests { func test_pubsub_pipelineChanges_hasNoRaceCondition() throws { func runOperation(_ factory: (RedisChannelName) -> EventLoopFuture) -> EventLoopFuture { - return .andAllSucceed( + .andAllSucceed( (0...100_000).reduce(into: []) { - result, index in + result, + index in result.append(factory("\(#function)-\(index)")) }, diff --git a/Tests/RediStackIntegrationTests/Commands/SetCommandsTests.swift b/Tests/RediStackIntegrationTests/Commands/SetCommandsTests.swift index 350d5c2a..ac63449d 100644 --- a/Tests/RediStackIntegrationTests/Commands/SetCommandsTests.swift +++ b/Tests/RediStackIntegrationTests/Commands/SetCommandsTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,10 +12,11 @@ // //===----------------------------------------------------------------------===// -@testable import RediStack import RediStackTestUtils import XCTest +@testable import RediStack + final class SetCommandsTests: RediStackIntegrationTestCase { func test_sadd() throws { var insertCount = try connection.sadd(1, 2, 3, to: #function).wait() @@ -212,46 +213,46 @@ final class SetCommandsTests: RediStackIntegrationTestCase { XCTAssertEqual(results[2].string, "3") XCTAssertEqual(results[3].string, "4") } - + // TODO: #23 -- Rework Scan Unit Test // This is extremely flakey, and causes non-deterministic failures because of the assert on key counts -// func test_sscan() throws { -// let key: RedisKey = #function -// let dataset = [ -// "Copenhagen, Denmark", -// "Roskilde, Denmark", -// "Herning, Denmark", -// "Kolding, Denmark", -// "Taastrup, Denmark", -// "London, England", -// "Bath, England", -// "Birmingham, England", -// "Cambridge, England", -// "Durham, England", -// "Seattle, United States", -// "Austin, United States", -// "New York City, United States", -// "San Francisco, United States", -// "Honolulu, United States" -// ] -// -// _ = try connection.sadd(dataset, to: key).wait() -// -// var (cursor, results) = try connection.sscan(key, count: 5).wait() -// XCTAssertGreaterThanOrEqual(cursor, 0) -// XCTAssertGreaterThanOrEqual(results.count, 5) -// -// (_, results) = try connection.sscan(key, startingFrom: cursor, count: 8).wait() -// XCTAssertGreaterThanOrEqual(results.count, 8) -// -// (cursor, results) = try connection.sscan(key, matching: "*Denmark").wait() -// XCTAssertGreaterThanOrEqual(cursor, 0) -// XCTAssertGreaterThanOrEqual(results.count, 1) -// XCTAssertLessThanOrEqual(results.count, 5) -// -// (cursor, results) = try connection.sscan(key, matching: "*ing*").wait() -// XCTAssertGreaterThanOrEqual(cursor, 0) -// XCTAssertGreaterThanOrEqual(results.count, 1) -// XCTAssertLessThanOrEqual(results.count, 3) -// } + // func test_sscan() throws { + // let key: RedisKey = #function + // let dataset = [ + // "Copenhagen, Denmark", + // "Roskilde, Denmark", + // "Herning, Denmark", + // "Kolding, Denmark", + // "Taastrup, Denmark", + // "London, England", + // "Bath, England", + // "Birmingham, England", + // "Cambridge, England", + // "Durham, England", + // "Seattle, United States", + // "Austin, United States", + // "New York City, United States", + // "San Francisco, United States", + // "Honolulu, United States" + // ] + // + // _ = try connection.sadd(dataset, to: key).wait() + // + // var (cursor, results) = try connection.sscan(key, count: 5).wait() + // XCTAssertGreaterThanOrEqual(cursor, 0) + // XCTAssertGreaterThanOrEqual(results.count, 5) + // + // (_, results) = try connection.sscan(key, startingFrom: cursor, count: 8).wait() + // XCTAssertGreaterThanOrEqual(results.count, 8) + // + // (cursor, results) = try connection.sscan(key, matching: "*Denmark").wait() + // XCTAssertGreaterThanOrEqual(cursor, 0) + // XCTAssertGreaterThanOrEqual(results.count, 1) + // XCTAssertLessThanOrEqual(results.count, 5) + // + // (cursor, results) = try connection.sscan(key, matching: "*ing*").wait() + // XCTAssertGreaterThanOrEqual(cursor, 0) + // XCTAssertGreaterThanOrEqual(results.count, 1) + // XCTAssertLessThanOrEqual(results.count, 3) + // } } diff --git a/Tests/RediStackIntegrationTests/Commands/SortedSetCommandsTests.swift b/Tests/RediStackIntegrationTests/Commands/SortedSetCommandsTests.swift index c7903490..ee221fd0 100644 --- a/Tests/RediStackIntegrationTests/Commands/SortedSetCommandsTests.swift +++ b/Tests/RediStackIntegrationTests/Commands/SortedSetCommandsTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019-2022 RediStack project authors +// Copyright (c) 2019-2022 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -13,14 +13,15 @@ //===----------------------------------------------------------------------===// import NIOCore -@testable import RediStack import RediStackTestUtils import XCTest +@testable import RediStack + final class SortedSetCommandsTests: RediStackIntegrationTestCase { private static let testKey: RedisKey = "SortedSetCommandsTests" - private var key: RedisKey { return SortedSetCommandsTests.testKey } + private var key: RedisKey { SortedSetCommandsTests.testKey } override func setUp() { super.setUp() @@ -85,27 +86,27 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase { score = try connection.zscore(of: 30, in: #function).wait() XCTAssertEqual(score, 11) } - + // TODO: #23 -- Rework Scan Unit Test // This is extremely flakey, and causes non-deterministic failures because of the assert on key counts -// func test_zscan() throws { -// var (cursor, results) = try connection.zscan(key, count: 5).wait() -// XCTAssertGreaterThanOrEqual(cursor, 0) -// XCTAssertGreaterThanOrEqual(results.count, 5) -// -// (_, results) = try connection.zscan(key, startingFrom: cursor, count: 8).wait() -// XCTAssertGreaterThanOrEqual(results.count, 8) -// -// (cursor, results) = try connection.zscan(key, matching: "1*").wait() -// XCTAssertEqual(cursor, 0) -// XCTAssertEqual(results.count, 2) -// XCTAssertEqual(results[0].1, 1) -// -// (cursor, results) = try connection.zscan(key, matching: "*0").wait() -// XCTAssertEqual(cursor, 0) -// XCTAssertEqual(results.count, 1) -// XCTAssertEqual(results[0].1, 10) -// } + // func test_zscan() throws { + // var (cursor, results) = try connection.zscan(key, count: 5).wait() + // XCTAssertGreaterThanOrEqual(cursor, 0) + // XCTAssertGreaterThanOrEqual(results.count, 5) + // + // (_, results) = try connection.zscan(key, startingFrom: cursor, count: 8).wait() + // XCTAssertGreaterThanOrEqual(results.count, 8) + // + // (cursor, results) = try connection.zscan(key, matching: "1*").wait() + // XCTAssertEqual(cursor, 0) + // XCTAssertEqual(results.count, 2) + // XCTAssertEqual(results[0].1, 1) + // + // (cursor, results) = try connection.zscan(key, matching: "*0").wait() + // XCTAssertEqual(cursor, 0) + // XCTAssertEqual(results.count, 1) + // XCTAssertEqual(results[0].1, 10) + // } func test_zrank() throws { let futures = [ @@ -130,19 +131,19 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase { func test_zcount() throws { var count = try connection.zcount(of: key, withScores: 1...3).wait() XCTAssertEqual(count, 3) - + count = try connection.zcount(of: key, withScoresBetween: (.exclusive(1), .exclusive(3))).wait() XCTAssertEqual(count, 1) - + count = try connection.zcount(of: key, withScores: 3..<8).wait() XCTAssertEqual(count, 5) - + count = try connection.zcount(of: key, withMinimumScoreOf: .exclusive(7)).wait() XCTAssertEqual(count, 3) - + count = try connection.zcount(of: key, withMaximumScoreOf: 10).wait() XCTAssertEqual(count, 10) - + count = try connection.zcount(of: key, withScoresBetween: (3, 0)).wait() XCTAssertEqual(count, 0) } @@ -151,16 +152,16 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase { for i in 1...10 { _ = try connection.zadd((i, 1), to: #function).wait() } - + var count = try connection.zlexcount(of: #function, withValuesBetween: (.inclusive(1), .inclusive(3))).wait() XCTAssertEqual(count, 4) - + count = try connection.zlexcount(of: #function, withValuesBetween: (.exclusive(1), .exclusive(3))).wait() XCTAssertEqual(count, 2) - + count = try connection.zlexcount(of: #function, withMinimumValueOf: .inclusive(2)).wait() XCTAssertEqual(count, 8) - + count = try connection.zlexcount(of: #function, withMaximumValueOf: .exclusive(3)).wait() XCTAssertEqual(count, 3) } @@ -246,7 +247,7 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase { func test_zunionstore() throws { let testKey = RedisKey(#function + #file) - + _ = try connection.zadd([(1, 1), (2, 2)], to: #function).wait() _ = try connection.zadd([(3, 3), (4, 4)], to: #file).wait() @@ -282,24 +283,24 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase { func test_zrange() throws { var elements = try connection.zrange(from: key, indices: 1...3).wait() XCTAssertEqual(elements.count, 3) - + elements = try connection.zrange(from: key, indices: 3..<9).wait() XCTAssertEqual(elements.count, 6) - + elements = try connection.zrange(from: key, upToIndex: 4).wait() XCTAssertEqual(elements.count, 4) - + elements = try connection.zrange(from: key, throughIndex: 4).wait() XCTAssertEqual(elements.count, 5) - + elements = try connection.zrange(from: key, fromIndex: 7).wait() XCTAssertEqual(elements.count, 3) - + elements = try connection.zrange(from: key, firstIndex: 1, lastIndex: 3, includeScoresInResponse: true).wait() XCTAssertEqual(elements.count, 6) let values = try RedisConnection._mapSortedSetResponse(elements, scoreIsFirst: false) - .map { (value, _) in return Int(fromRESP: value) } + .map { (value, _) in Int(fromRESP: value) } XCTAssertEqual(values[0], 2) XCTAssertEqual(values[1], 3) @@ -312,21 +313,22 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase { elements = try connection.zrevrange(from: key, indices: 3..<9).wait() XCTAssertEqual(elements.count, 6) - + elements = try connection.zrevrange(from: key, upToIndex: 4).wait() XCTAssertEqual(elements.count, 4) - + elements = try connection.zrevrange(from: key, throughIndex: 4).wait() XCTAssertEqual(elements.count, 5) - + elements = try connection.zrevrange(from: key, fromIndex: 7).wait() XCTAssertEqual(elements.count, 3) - - elements = try connection.zrevrange(from: key, firstIndex: 1, lastIndex: 3, includeScoresInResponse: true).wait() + + elements = try connection.zrevrange(from: key, firstIndex: 1, lastIndex: 3, includeScoresInResponse: true) + .wait() XCTAssertEqual(elements.count, 6) let values = try RedisConnection._mapSortedSetResponse(elements, scoreIsFirst: false) - .map { (value, _) in return Int(fromRESP: value) } + .map { (value, _) in Int(fromRESP: value) } XCTAssertEqual(values[0], 9) XCTAssertEqual(values[1], 8) @@ -336,21 +338,21 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase { func test_zrangebyscore() throws { var elements = try connection.zrangebyscore(from: key, withScoresBetween: (.exclusive(1), 3)).wait() XCTAssertEqual(elements.count, 2) - + elements = try connection.zrangebyscore(from: key, withScores: 7..<10, limitBy: (offset: 2, count: 3)).wait() XCTAssertEqual(elements.count, 1) - + elements = try connection.zrangebyscore(from: key, withMinimumScoreOf: .exclusive(5)).wait() XCTAssertEqual(elements.count, 5) - + elements = try connection.zrangebyscore(from: key, withMaximumScoreOf: 5).wait() XCTAssertEqual(elements.count, 5) - + elements = try connection.zrangebyscore(from: key, withScores: 1...3, includeScoresInResponse: true).wait() XCTAssertEqual(elements.count, 6) let values = try RedisConnection._mapSortedSetResponse(elements, scoreIsFirst: false) - .map { (_, score) in return score } + .map { (_, score) in score } XCTAssertEqual(values[0], 1.0) XCTAssertEqual(values[1], 2.0) @@ -360,21 +362,21 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase { func test_zrevrangebyscore() throws { var elements = try connection.zrevrangebyscore(from: key, withScoresBetween: (.exclusive(1), 3)).wait() XCTAssertEqual(elements.count, 2) - + elements = try connection.zrevrangebyscore(from: key, withScores: 7..<10, limitBy: (offset: 2, count: 3)).wait() XCTAssertEqual(elements.count, 1) - + elements = try connection.zrevrangebyscore(from: key, withMinimumScoreOf: .exclusive(5)).wait() XCTAssertEqual(elements.count, 5) - + elements = try connection.zrevrangebyscore(from: key, withMaximumScoreOf: 5).wait() XCTAssertEqual(elements.count, 5) - + elements = try connection.zrevrangebyscore(from: key, withScores: 1...3, includeScoresInResponse: true).wait() XCTAssertEqual(elements.count, 6) let values = try RedisConnection._mapSortedSetResponse(elements, scoreIsFirst: false) - .map { (_, score) in return score } + .map { (_, score) in score } XCTAssertEqual(values[0], 3.0) XCTAssertEqual(values[1], 2.0) @@ -385,27 +387,28 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase { for i in 1...10 { _ = try connection.zadd((i, 1), to: #function).wait() } - + var elements = try connection.zrangebylex(from: #function, withMinimumValueOf: .exclusive(10)) .wait() .map(Int.init(fromRESP:)) XCTAssertEqual(elements.count, 8) - + elements = try connection.zrangebylex(from: #function, withMaximumValueOf: .inclusive(5)) .wait() .map(Int.init(fromRESP:)) XCTAssertEqual(elements.count, 6) - + elements = try connection.zrangebylex(from: #function, withValuesBetween: (.inclusive(1), .inclusive(2))) .wait() .map(Int.init(fromRESP:)) - + XCTAssertEqual(elements.count, 3) XCTAssertEqual(elements[0], 1) XCTAssertEqual(elements[1], 10) XCTAssertEqual(elements[2], 2) - elements = try connection + elements = + try connection .zrangebylex( from: #function, withValuesBetween: (.inclusive(1), .exclusive(4)), @@ -421,14 +424,14 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase { for i in 1...10 { _ = try connection.zadd((i, 1), to: #function).wait() } - + var elements = try connection.zrevrangebylex(from: #function, withMinimumValueOf: .inclusive(1)) .wait() .map(Int.init(fromRESP:)) XCTAssertEqual(elements.count, 10) XCTAssertEqual(elements[0], 9) XCTAssertEqual(elements[9], 1) - + elements = try connection.zrevrangebylex(from: #function, withMaximumValueOf: .exclusive(2)) .wait() .map(Int.init(fromRESP:)) @@ -442,7 +445,8 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase { XCTAssertEqual(elements[0], 4) XCTAssertEqual(elements[1], 3) - elements = try connection + elements = + try connection .zrevrangebylex( from: #function, withValuesBetween: (.inclusive(1), .exclusive(4)), @@ -471,9 +475,12 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase { _ = try connection.zadd((value, 0), to: #function).wait() } - var count = try connection.zremrangebylex(from: #function, withValuesBetween: (.exclusive("a"), .inclusive("t"))).wait() + var count = try connection.zremrangebylex( + from: #function, + withValuesBetween: (.exclusive("a"), .inclusive("t")) + ).wait() XCTAssertEqual(count, 2) - + count = try connection.zremrangebylex(from: #function, withMaximumValueOf: .inclusive("t")).wait() XCTAssertEqual(count, 0) @@ -484,19 +491,19 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase { func test_zremrangebyrank() throws { var count = try connection.zremrangebyrank(from: key, fromIndex: 9).wait() XCTAssertEqual(count, 1) - + count = try connection.zremrangebyrank(from: key, indices: 0...1).wait() XCTAssertEqual(count, 2) - + count = try connection.zremrangebyrank(from: key, indices: 0..<2).wait() XCTAssertEqual(count, 2) - + count = try connection.zremrangebyrank(from: key, upToIndex: 1).wait() XCTAssertEqual(count, 1) - + count = try connection.zremrangebyrank(from: key, throughIndex: 1).wait() XCTAssertEqual(count, 2) - + count = try connection.zremrangebyrank(from: key, upToIndex: 0).wait() XCTAssertEqual(count, 2) } @@ -504,16 +511,16 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase { func test_zremrangebyscore() throws { var count = try connection.zremrangebyscore(from: key, withScoresBetween: (.exclusive(8), 10)).wait() XCTAssertEqual(count, 2) - + count = try connection.zremrangebyscore(from: key, withScores: 4..<7).wait() XCTAssertEqual(count, 3) - + count = try connection.zremrangebyscore(from: key, withScores: 2...3).wait() XCTAssertEqual(count, 2) - + count = try connection.zremrangebyscore(from: key, withMinimumScoreOf: .exclusive(1)).wait() XCTAssertEqual(count, 2) - + count = try connection.zremrangebyscore(from: key, withMaximumScoreOf: .inclusive(1)).wait() XCTAssertEqual(count, 1) } @@ -524,19 +531,22 @@ final class SortedSetCommandsTests: RediStackIntegrationTestCase { extension SortedSetCommandsTests { func test_zrange_realworld() throws { struct Keys { - static let first = "1E4FD2C5-C32E-4E3F-91B3-45478BCF0185" + static let first = "1E4FD2C5-C32E-4E3F-91B3-45478BCF0185" static let second = "1E4FD2C5-C32E-4E3F-91B3-45478BCF0186" - static let third = "1E4FD2C5-C32E-4E3F-91B3-45478BCF0187" + static let third = "1E4FD2C5-C32E-4E3F-91B3-45478BCF0187" static let fourth = "1E4FD2C5-C32E-4E3F-91B3-45478BCF0188" - static let fifth = "1E4FD2C5-C32E-4E3F-91B3-45478BCF0189" + static let fifth = "1E4FD2C5-C32E-4E3F-91B3-45478BCF0189" } - _ = try self.connection.zadd([ - (Keys.first, 1), - (Keys.second, 1), - (Keys.third, 1), - (Keys.fourth, 1), - (Keys.fifth, 1), - ], to: #function).wait() + _ = try self.connection.zadd( + [ + (Keys.first, 1), + (Keys.second, 1), + (Keys.third, 1), + (Keys.fourth, 1), + (Keys.fifth, 1), + ], + to: #function + ).wait() let elements = try self.connection .zrange(from: #function, fromIndex: 0) @@ -549,19 +559,22 @@ extension SortedSetCommandsTests { func test_zrevrange_realworld() throws { struct Keys { - static let first = "1E4FD2C5-C32E-4E3F-91B3-45478BCF0185" + static let first = "1E4FD2C5-C32E-4E3F-91B3-45478BCF0185" static let second = "1E4FD2C5-C32E-4E3F-91B3-45478BCF0186" - static let third = "1E4FD2C5-C32E-4E3F-91B3-45478BCF0187" + static let third = "1E4FD2C5-C32E-4E3F-91B3-45478BCF0187" static let fourth = "1E4FD2C5-C32E-4E3F-91B3-45478BCF0188" - static let fifth = "1E4FD2C5-C32E-4E3F-91B3-45478BCF0189" + static let fifth = "1E4FD2C5-C32E-4E3F-91B3-45478BCF0189" } - _ = try self.connection.zadd([ - (Keys.first, 1), - (Keys.second, 1), - (Keys.third, 1), - (Keys.fourth, 1), - (Keys.fifth, 1), - ], to: #function).wait() + _ = try self.connection.zadd( + [ + (Keys.first, 1), + (Keys.second, 1), + (Keys.third, 1), + (Keys.fourth, 1), + (Keys.fifth, 1), + ], + to: #function + ).wait() let elements = try self.connection .zrevrange(from: #function, fromIndex: 0) diff --git a/Tests/RediStackIntegrationTests/Commands/StringCommandsTests.swift b/Tests/RediStackIntegrationTests/Commands/StringCommandsTests.swift index 6c04cab6..3ce98069 100644 --- a/Tests/RediStackIntegrationTests/Commands/StringCommandsTests.swift +++ b/Tests/RediStackIntegrationTests/Commands/StringCommandsTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,10 +12,11 @@ // //===----------------------------------------------------------------------===// -@testable import RediStack import RediStackTestUtils import XCTest +@testable import RediStack + final class StringCommandsTests: RediStackIntegrationTestCase { private static let testKey = "SortedSetCommandsTests" @@ -35,7 +36,9 @@ final class StringCommandsTests: RediStackIntegrationTestCase { func test_mget() throws { let keys = ["one", "two"].map(RedisKey.init(_:)) - try keys.forEach { _ = try connection.set($0, to: $0).wait() } + for key in keys { + _ = try connection.set(key, to: key).wait() + } let values = try connection.mget(keys + ["empty"]).wait() XCTAssertEqual(values.count, 3) @@ -55,7 +58,10 @@ final class StringCommandsTests: RediStackIntegrationTestCase { func test_set_condition() throws { XCTAssertEqual(try connection.set(#function, to: "value", onCondition: .keyExists).wait(), .conditionNotMet) XCTAssertEqual(try connection.set(#function, to: "value", onCondition: .keyDoesNotExist).wait(), .ok) - XCTAssertEqual(try connection.set(#function, to: "value", onCondition: .keyDoesNotExist).wait(), .conditionNotMet) + XCTAssertEqual( + try connection.set(#function, to: "value", onCondition: .keyDoesNotExist).wait(), + .conditionNotMet + ) XCTAssertEqual(try connection.set(#function, to: "value", onCondition: .keyExists).wait(), .ok) XCTAssertEqual(try connection.set(#function, to: "value", onCondition: .none).wait(), .ok) } @@ -157,7 +163,7 @@ final class StringCommandsTests: RediStackIntegrationTestCase { func test_mset() throws { let data: [RedisKey: Int] = [ "first": 1, - "second": 2 + "second": 2, ] XCTAssertNoThrow(try connection.mset(data).wait()) let values = try connection.mget(["first", "second"]).wait().compactMap { $0.string } @@ -173,7 +179,7 @@ final class StringCommandsTests: RediStackIntegrationTestCase { func test_msetnx() throws { let data: [RedisKey: Int] = [ "first": 1, - "second": 2 + "second": 2, ] var success = try connection.msetnx(data).wait() XCTAssertEqual(success, true) diff --git a/Tests/RediStackIntegrationTests/RediStackIntegrationTestCase.swift b/Tests/RediStackIntegrationTests/RediStackIntegrationTestCase.swift index 82caa968..96acee8b 100644 --- a/Tests/RediStackIntegrationTests/RediStackIntegrationTestCase.swift +++ b/Tests/RediStackIntegrationTests/RediStackIntegrationTestCase.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,23 +12,24 @@ // //===----------------------------------------------------------------------===// -import class Foundation.ProcessInfo import RediStackTestUtils +import class Foundation.ProcessInfo + class RediStackIntegrationTestCase: RedisIntegrationTestCase { override var redisHostname: String { - return ProcessInfo.processInfo.environment["REDIS_URL"] ?? "localhost" + ProcessInfo.processInfo.environment["REDIS_URL"] ?? "localhost" } override var redisPassword: String? { - return ProcessInfo.processInfo.environment["REDIS_PW"] + ProcessInfo.processInfo.environment["REDIS_PW"] } } class RediStackConnectionPoolIntegrationTestCase: RedisConnectionPoolIntegrationTestCase { override var redisHostname: String { - return ProcessInfo.processInfo.environment["REDIS_URL"] ?? "localhost" + ProcessInfo.processInfo.environment["REDIS_URL"] ?? "localhost" } override var redisPassword: String? { - return ProcessInfo.processInfo.environment["REDIS_PW"] + ProcessInfo.processInfo.environment["REDIS_PW"] } } diff --git a/Tests/RediStackIntegrationTests/RedisConnectionPoolTests.swift b/Tests/RediStackIntegrationTests/RedisConnectionPoolTests.swift index 4809d938..68320499 100644 --- a/Tests/RediStackIntegrationTests/RedisConnectionPoolTests.swift +++ b/Tests/RediStackIntegrationTests/RedisConnectionPoolTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020 RediStack project authors +// Copyright (c) 2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,12 +12,13 @@ // //===----------------------------------------------------------------------===// -import NIOCore import Logging -@testable import RediStack +import NIOCore import RediStackTestUtils import XCTest +@testable import RediStack + final class RedisConnectionPoolTests: RediStackConnectionPoolIntegrationTestCase { func test_basicPooledOperation() throws { // We're going to insert a bunch of elements into a set, and then when all is done confirm that every @@ -37,7 +38,7 @@ final class RedisConnectionPoolTests: RediStackConnectionPoolIntegrationTestCase XCTAssertEqual(error as? RedisConnectionPoolError, .poolClosed) } } - + func test_nilConnectionRetryTimeoutStillWorks() throws { let pool = try self.makeNewPool(connectionRetryTimeout: nil) defer { pool.close() } @@ -47,7 +48,12 @@ final class RedisConnectionPoolTests: RediStackConnectionPoolIntegrationTestCase func test_noConnectionAttemptsUntilAddressesArePresent() throws { // Note the config here: we have no initial addresses, the connecton backoff delay is 10 seconds, and the retry timeout is only 5 seconds. // The effect of this config is that if we fail a connection attempt, we'll fail it forever. - let pool = try self.makeNewPool(initialAddresses: [], initialConnectionBackoffDelay: .seconds(10), connectionRetryTimeout: .seconds(5), minimumConnectionCount: 0) + let pool = try self.makeNewPool( + initialAddresses: [], + initialConnectionBackoffDelay: .seconds(10), + connectionRetryTimeout: .seconds(5), + minimumConnectionCount: 0 + ) defer { pool.close() } // As above we're gonna try to insert a bunch of elements into a set. This time, @@ -58,7 +64,9 @@ final class RedisConnectionPoolTests: RediStackConnectionPoolIntegrationTestCase } // Now that we've kicked those off, let's hand over a new address. - try pool.updateConnectionAddresses([SocketAddress.makeAddressResolvingHost(self.redisHostname, port: self.redisPort)]) + try pool.updateConnectionAddresses([ + SocketAddress.makeAddressResolvingHost(self.redisHostname, port: self.redisPort) + ]) // We should get the results. let results = try EventLoopFuture.whenAllSucceed(operations, on: self.eventLoopGroup.next()).wait() @@ -68,7 +76,12 @@ final class RedisConnectionPoolTests: RediStackConnectionPoolIntegrationTestCase func testDelayedConnectionsFailOnClose() throws { // Note the config here: we have no initial addresses, the connecton backoff delay is 10 seconds, and the retry timeout is only 5 seconds. // The effect of this config is that if we fail a connection attempt, we'll fail it forever. - let pool = try self.makeNewPool(initialAddresses: [], initialConnectionBackoffDelay: .seconds(10), connectionRetryTimeout: .seconds(5), minimumConnectionCount: 0) + let pool = try self.makeNewPool( + initialAddresses: [], + initialConnectionBackoffDelay: .seconds(10), + connectionRetryTimeout: .seconds(5), + minimumConnectionCount: 0 + ) defer { pool.close() } // As above we're gonna try to insert a bunch of elements into a set. This time, @@ -107,43 +120,45 @@ extension RedisConnectionPoolTests { _ = try pool.ping().wait() let promise = pool.eventLoop.makePromise(of: Void.self) - + XCTAssertEqual(pool.availableConnectionCount, maxConnectionCount) defer { XCTAssertEqual(pool.availableConnectionCount, maxConnectionCount) } let future = pool.leaseConnection { _ in promise.futureResult } - + promise.fail(TestError.expected) XCTAssertThrowsError(try future.wait()) { XCTAssertTrue($0 is TestError) } } - + func test_borrowedConnectionClosureHasExclusiveAccess() throws { let maxConnectionCount = 4 let pool = try self.makeNewPool(minimumConnectionCount: maxConnectionCount) defer { pool.close() } // populate the connection pool _ = try pool.ping().wait() - + // assert that we have the max number of connections available, XCTAssertEqual(pool.availableConnectionCount, maxConnectionCount) // borrow a connection, asserting that we've taken the connection out of the pool while we do "something" with it // and then assert afterwards that it's back in the pool - + let promises: [EventLoopPromise] = [pool.eventLoop.makePromise(), pool.eventLoop.makePromise()] let futures = promises.indices .map { index in - return pool + pool .leaseConnection { connection -> EventLoopFuture in XCTAssertTrue(pool.availableConnectionCount < maxConnectionCount) - + return promises[index].futureResult } } - - promises.forEach { $0.succeed(()) } + + for promise in promises { + promise.succeed(()) + } _ = try EventLoopFuture .whenAllSucceed(futures, on: pool.eventLoop) .always { _ in diff --git a/Tests/RediStackIntegrationTests/RedisConnectionTests.swift b/Tests/RediStackIntegrationTests/RedisConnectionTests.swift index 79e8e7bc..ba9bc706 100644 --- a/Tests/RediStackIntegrationTests/RedisConnectionTests.swift +++ b/Tests/RediStackIntegrationTests/RedisConnectionTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019-2020 RediStack project authors +// Copyright (c) 2019-2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,24 +12,25 @@ // //===----------------------------------------------------------------------===// -@testable import RediStack import RediStackTestUtils import XCTest +@testable import RediStack + final class RedisConnectionTests: RediStackIntegrationTestCase { func test_unexpectedChannelClose() throws { XCTAssertTrue(self.connection.isConnected) try self.connection.channel.close().wait() XCTAssertFalse(self.connection.isConnected) } - + func test_callingCloseMultipleTimes() throws { let first = self.connection.close() let second = self.connection.close() XCTAssertNotEqual(first, self.connection.channel.closeFuture) XCTAssertEqual(second, self.connection.channel.closeFuture) } - + func test_sendingCommandAfterClosing() throws { self.connection.close() do { @@ -65,13 +66,13 @@ extension RedisConnectionTests { _ = try connection.subscribe( to: #function, - messageReceiver: { _, _ in }, + messageReceiver: { _, _ in }, onSubscribe: nil, onUnsubscribe: { _, _ in subscriptionClosedExpectation.fulfill() } ).wait() _ = try connection.psubscribe( to: #function, - messageReceiver: { _, _ in }, + messageReceiver: { _, _ in }, onSubscribe: nil, onUnsubscribe: { _, _ in subscriptionClosedExpectation.fulfill() } ).wait() diff --git a/Tests/RediStackIntegrationTests/RedisLoggingTests.swift b/Tests/RediStackIntegrationTests/RedisLoggingTests.swift index 26452202..8c7b2710 100644 --- a/Tests/RediStackIntegrationTests/RedisLoggingTests.swift +++ b/Tests/RediStackIntegrationTests/RedisLoggingTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020 RediStack project authors +// Copyright (c) 2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -20,18 +20,18 @@ import XCTest final class RedisLoggingTests: RediStackIntegrationTestCase { func test_connectionUsesCustomLogger() throws { let handler = TestLogHandler() - let logger = Logger(label: #function, factory: { _ in return handler }) + let logger = Logger(label: #function, factory: { _ in handler }) _ = try self.connection .logging(to: logger) .ping() .wait() XCTAssertFalse(handler.messages.isEmpty) } - + func test_connectionLoggerMetadata() throws { let handler = TestLogHandler() - let logger = Logger(label: #function, factory: { _ in return handler }) - + let logger = Logger(label: #function, factory: { _ in handler }) + _ = try self.connection .logging(to: logger) .ping() @@ -41,14 +41,16 @@ final class RedisLoggingTests: RediStackIntegrationTestCase { .string(self.connection.id.description) ) } - + func test_poolLoggerMetadata() throws { let handler = TestLogHandler() - let logger = Logger(label: #function, factory: { _ in return handler }) - + let logger = Logger(label: #function, factory: { _ in handler }) + let pool = RedisConnectionPool( configuration: .init( - initialServerConnectionAddresses: [try .makeAddressResolvingHost(self.redisHostname, port: self.redisPort)], + initialServerConnectionAddresses: [ + try .makeAddressResolvingHost(self.redisHostname, port: self.redisPort) + ], maximumConnectionCount: .maximumActiveConnections(1), connectionFactoryConfiguration: .init(connectionPassword: self.redisPassword) ), @@ -56,8 +58,9 @@ final class RedisLoggingTests: RediStackIntegrationTestCase { ) defer { pool.close() } pool.activate() - - _ = try pool + + _ = + try pool .logging(to: logger) .ping() .wait() @@ -80,7 +83,14 @@ final class TestLogHandler: LogHandler { self.logLevel = .trace } - func log(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?, file: String, function: String, line: UInt) { + func log( + level: Logger.Level, + message: Logger.Message, + metadata: Logger.Metadata?, + file: String, + function: String, + line: UInt + ) { self.messages.append(message) } diff --git a/Tests/RediStackTests/ChannelHandlers/RedisByteDecoderTests.swift b/Tests/RediStackTests/ChannelHandlers/RedisByteDecoderTests.swift index 55119cac..5f5da4a8 100644 --- a/Tests/RediStackTests/ChannelHandlers/RedisByteDecoderTests.swift +++ b/Tests/RediStackTests/ChannelHandlers/RedisByteDecoderTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -15,9 +15,10 @@ import NIOCore import NIOEmbedded import NIOTestUtils -@testable import RediStack import XCTest +@testable import RediStack + final class RedisByteDecoderTests: XCTestCase { private var decoder = RedisByteDecoder() private var allocator = ByteBufferAllocator() @@ -94,36 +95,42 @@ extension RedisByteDecoderTests { func testArrays() throws { func runArrayTest(_ input: String) throws -> [RESPValue]? { - return try runTest(input)?.array + try runTest(input)?.array } XCTAssertNil(try runArrayTest("*0\r")) XCTAssertNil(try runArrayTest("*1\r\n+OK\r")) XCTAssertEqual(try runArrayTest("*0\r\n")?.count, 0) - XCTAssertTrue(arraysAreEqual( - try runArrayTest("*1\r\n$3\r\nfoo\r\n"), - expected: [.init(bulk: "foo")] - )) - XCTAssertTrue(arraysAreEqual( - try runArrayTest("*3\r\n+foo\r\n$3\r\nbar\r\n:3\r\n"), - expected: [.simpleString("foo".byteBuffer), .bulkString("bar".byteBuffer), .integer(3)] - )) - XCTAssertTrue(arraysAreEqual( - try runArrayTest("*1\r\n*2\r\n+OK\r\n:1\r\n"), - expected: [.array([ .simpleString("OK".byteBuffer), .integer(1) ])] - )) + XCTAssertTrue( + arraysAreEqual( + try runArrayTest("*1\r\n$3\r\nfoo\r\n"), + expected: [.init(bulk: "foo")] + ) + ) + XCTAssertTrue( + arraysAreEqual( + try runArrayTest("*3\r\n+foo\r\n$3\r\nbar\r\n:3\r\n"), + expected: [.simpleString("foo".byteBuffer), .bulkString("bar".byteBuffer), .integer(3)] + ) + ) + XCTAssertTrue( + arraysAreEqual( + try runArrayTest("*1\r\n*2\r\n+OK\r\n:1\r\n"), + expected: [.array([.simpleString("OK".byteBuffer), .integer(1)])] + ) + ) } private func runTest(_ input: String) throws -> RESPValue? { - return try runTest(input.bytes) + try runTest(input.bytes) } private func runTest(_ input: [UInt8]) throws -> RESPValue? { - return try runTest(input).0 + try runTest(input).0 } private func runTest(_ input: String) throws -> (RESPValue?, RESPValue?) { - return try runTest(input.bytes) + try runTest(input.bytes) } private func runTest(_ input: [UInt8]) throws -> (RESPValue?, RESPValue?) { @@ -148,8 +155,8 @@ extension RedisByteDecoderTests { var arraysMatch = true - left.enumerated().forEach { - let (offset, decodedElement) = $0 + for item in left.enumerated() { + let (offset, decodedElement) = item switch (decodedElement, right[offset]) { case (let .bulkString(decoded), let .bulkString(expected)): arraysMatch = decoded == expected @@ -187,7 +194,7 @@ extension RedisByteDecoderTests { "*3\r\n+\(expectedString)\r\n$2\r\n\(expectedBulkString)\r\n:\(expectedInteger)\r\n", "*1\r\n*1\r\n:\(expectedInteger)\r\n", "*0\r\n", - "*-1\r\n" + "*-1\r\n", ] } @@ -222,20 +229,24 @@ extension RedisByteDecoderTests { XCTAssertEqual(results[5]?.string, "") XCTAssertEqual(results[6]?.array?.count, 3) - XCTAssertTrue(arraysAreEqual( - results[6]?.array, - expected: [ - .simpleString(AllData.expectedString.byteBuffer), - .bulkString(AllData.expectedBulkString.byteBuffer), - .integer(AllData.expectedInteger) - ] - )) + XCTAssertTrue( + arraysAreEqual( + results[6]?.array, + expected: [ + .simpleString(AllData.expectedString.byteBuffer), + .bulkString(AllData.expectedBulkString.byteBuffer), + .integer(AllData.expectedInteger), + ] + ) + ) XCTAssertEqual(results[7]?.array?.count, 1) - XCTAssertTrue(arraysAreEqual( - results[7]?.array, - expected: [.array([.integer(AllData.expectedInteger)])] - )) + XCTAssertTrue( + arraysAreEqual( + results[7]?.array, + expected: [.array([.integer(AllData.expectedInteger)])] + ) + ) XCTAssertEqual(results[8]?.array?.count, 0) XCTAssertEqual(results[9]?.isNull, true) @@ -264,7 +275,7 @@ extension RedisByteDecoderTests { "*2\r\n:1\r\n:2\r\n", "*2\r\n*1\r\n:1\r\n:2\r\n", "-ERR test\r\n", - ":2\r\n" + ":2\r\n", ] func test_complete_continues() throws { @@ -309,8 +320,10 @@ extension RedisByteDecoderTests { let inputExpectedOutputPairs: [(String, [RedisByteDecoder.InboundOut])] = [ (":1000\r\n:1000\r\n", [.integer(1000), .integer(1000)]), (":0\r\n", [.integer(0)]), - ("*3\r\n+foo\r\n$3\r\nbar\r\n:3\r\n", - [.array([.simpleString("foo".byteBuffer), .bulkString("bar".byteBuffer), .integer(3)])]), + ( + "*3\r\n+foo\r\n$3\r\nbar\r\n:3\r\n", + [.array([.simpleString("foo".byteBuffer), .bulkString("bar".byteBuffer), .integer(3)])] + ), ("+👩🏼‍✈️\r\n++\r\n", [.simpleString("👩🏼‍✈️".byteBuffer), .simpleString("+".byteBuffer)]), ("*2\r\n:1\r\n:2\r\n", [.array([.integer(1), .integer(2)])]), ("*2\r\n*1\r\n:1\r\n:2\r\n", [.array([.array([.integer(1)]), .integer(2)])]), @@ -319,12 +332,14 @@ extension RedisByteDecoderTests { ("$-1\r\n", [.null]), (":00000\r\n:\(Int.max)\r\n:\(Int.min)\r\n", [.integer(0), .integer(Int.max), .integer(Int.min)]), ] - XCTAssertNoThrow(try ByteToMessageDecoderVerifier.verifyDecoder( - stringInputOutputPairs: inputExpectedOutputPairs, - decoderFactory: RedisByteDecoder.init - )) + XCTAssertNoThrow( + try ByteToMessageDecoderVerifier.verifyDecoder( + stringInputOutputPairs: inputExpectedOutputPairs, + decoderFactory: RedisByteDecoder.init + ) + ) } - + func test_validatesBasicAssumptions_withNonStringRepresentables() throws { var buffer = self.allocator.buffer(capacity: 128) var incompleteUTF8CodeUnitsAsSimpleAndBulkString: (ByteBuffer, [RESPValue]) { @@ -368,9 +383,11 @@ extension RedisByteDecoderTests { incompleteUTF8CodeUnitsAsSimpleAndBulkString, boms, ] - XCTAssertNoThrow(try ByteToMessageDecoderVerifier.verifyDecoder( - inputOutputPairs: inputExpectedOutputPairs, - decoderFactory: RedisByteDecoder.init - )) + XCTAssertNoThrow( + try ByteToMessageDecoderVerifier.verifyDecoder( + inputOutputPairs: inputExpectedOutputPairs, + decoderFactory: RedisByteDecoder.init + ) + ) } } diff --git a/Tests/RediStackTests/ChannelHandlers/RedisCommandHandlerTests.swift b/Tests/RediStackTests/ChannelHandlers/RedisCommandHandlerTests.swift index 03c01716..ed5951ec 100644 --- a/Tests/RediStackTests/ChannelHandlers/RedisCommandHandlerTests.swift +++ b/Tests/RediStackTests/ChannelHandlers/RedisCommandHandlerTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019-2020 RediStack project authors +// Copyright (c) 2019-2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,32 +12,33 @@ // //===----------------------------------------------------------------------===// +import Atomics import NIOCore -import NIOPosix import NIOEmbedded -import Atomics -@testable import RediStack +import NIOPosix import XCTest +@testable import RediStack + final class RedisCommandHandlerTests: XCTestCase { func test_whenRemoteConnectionCloses_handlerFailsCommandQueue() throws { let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { try? group.syncShutdownGracefully() } let socketAddress = try SocketAddress.makeAddressResolvingHost("localhost", port: 8080) - + let server = try ServerBootstrap(group: group) .serverChannelOption(ChannelOptions.socket(.init(SOL_SOCKET), .init(SO_REUSEADDR)), value: 1) .childChannelInitializer { $0.pipeline.addHandler(RemoteCloseHandler()) } .bind(to: socketAddress) .wait() defer { try? server.close().wait() } - + let connection = try RedisConnection.make( configuration: .init(hostname: "localhost", port: 8080), boundEventLoop: group.next() ).wait() defer { try? connection.close().wait() } - + XCTAssertThrowsError(try connection.ping().wait()) { guard let error = $0 as? RedisClientError else { XCTFail("Wrong error type thrown") @@ -127,7 +128,7 @@ final class RedisCommandHandlerTests: XCTestCase { private final class RemoteCloseHandler: ChannelInboundHandler { typealias InboundIn = ByteBuffer - + func channelRead(context: ChannelHandlerContext, data: NIOAny) { context.close(promise: nil) } diff --git a/Tests/RediStackTests/ChannelHandlers/RedisMessageEncoderTests.swift b/Tests/RediStackTests/ChannelHandlers/RedisMessageEncoderTests.swift index 1c579eec..1826d4a7 100644 --- a/Tests/RediStackTests/ChannelHandlers/RedisMessageEncoderTests.swift +++ b/Tests/RediStackTests/ChannelHandlers/RedisMessageEncoderTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -14,10 +14,11 @@ import NIOCore import NIOEmbedded -@testable import RediStack import RediStackTestUtils import XCTest +@testable import RediStack + final class RedisMessageEncoderTests: XCTestCase { private var encoder: RedisMessageEncoder! private var allocator: ByteBufferAllocator! @@ -83,13 +84,15 @@ final class RedisMessageEncoderTests: XCTestCase { try runEncodePass(with: a2) { XCTAssertEqual($0.readableBytes, 14) } XCTAssertNoThrow(try self.channel.writeOutbound(a2)) - let bytes: [UInt8] = [ 0x0a, 0x1a, 0x1b, 0xff ] + let bytes: [UInt8] = [0x0a, 0x1a, 0x1b, 0xff] var buffer = allocator.buffer(capacity: bytes.count) buffer.writeBytes(bytes) - let a3: RESPValue = .array([.array([ - .integer(3), - .bulkString(buffer) - ])]) + let a3: RESPValue = .array([ + .array([ + .integer(3), + .bulkString(buffer), + ]) + ]) try runEncodePass(with: a3) { XCTAssertEqual($0.readableBytes, 22) } XCTAssertNoThrow(try self.channel.writeOutbound(a3)) } diff --git a/Tests/RediStackTests/Cluster/RedisClusterNodeDescriptionProtocolTests.swift b/Tests/RediStackTests/Cluster/RedisClusterNodeDescriptionProtocolTests.swift index ba06c845..c140a747 100644 --- a/Tests/RediStackTests/Cluster/RedisClusterNodeDescriptionProtocolTests.swift +++ b/Tests/RediStackTests/Cluster/RedisClusterNodeDescriptionProtocolTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2023 RediStack project authors +// Copyright (c) 2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import XCTest + @testable import RediStack final class RedisClusterNodeDescriptionProtocolTests: XCTestCase { diff --git a/Tests/RediStackTests/ConfigurationTests.swift b/Tests/RediStackTests/ConfigurationTests.swift index b37e6b9d..fac28825 100644 --- a/Tests/RediStackTests/ConfigurationTests.swift +++ b/Tests/RediStackTests/ConfigurationTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020 RediStack project authors +// Copyright (c) 2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -25,7 +25,7 @@ final class ConfigurationTests: XCTestCase { XCTAssertThrowsError(try RedisConnection.Configuration(url: "redis://:6379")) XCTAssertThrowsError(try RedisConnection.Configuration(url: "redis://localhost/-1")) } - + func test_connectionConfiguration_urlComponents() throws { let configuration = try RedisConnection.Configuration(url: "redis://:password@localhost:6666/1") XCTAssertEqual(configuration.hostname, "localhost") @@ -33,9 +33,12 @@ final class ConfigurationTests: XCTestCase { XCTAssertEqual(configuration.port, 6666) XCTAssertEqual(configuration.initialDatabase, 1) } - + func test_connectionConfiguration_databaseIndex() throws { - let address = try SocketAddress.makeAddressResolvingHost("localhost", port: RedisConnection.Configuration.defaultPort) + let address = try SocketAddress.makeAddressResolvingHost( + "localhost", + port: RedisConnection.Configuration.defaultPort + ) XCTAssertThrowsError(try RedisConnection.Configuration(address: address, initialDatabase: -1)) { XCTAssertEqual($0 as? RedisConnection.Configuration.ValidationError, .outOfBoundsDatabaseID) } diff --git a/Tests/RediStackTests/ConnectionPoolTests.swift b/Tests/RediStackTests/ConnectionPoolTests.swift index 3f224d34..ed2e707b 100644 --- a/Tests/RediStackTests/ConnectionPoolTests.swift +++ b/Tests/RediStackTests/ConnectionPoolTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020-2023 RediStack project authors +// Copyright (c) 2020-2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,11 +12,12 @@ // //===----------------------------------------------------------------------===// -@testable import RediStack -@testable import RediStackTestUtils -import XCTest import NIOCore import NIOEmbedded +import XCTest + +@testable import RediStack +@testable import RediStackTestUtils enum ConnectionPoolTestError: Error { case connectionFailedForSomeReason @@ -41,19 +42,24 @@ final class ConnectionPoolTests: XCTestCase { } func createPool(maximumConnectionCount: Int, minimumConnectionCount: Int, leaky: Bool) -> ConnectionPool { - return ConnectionPool( + ConnectionPool( maximumConnectionCount: maximumConnectionCount, minimumConnectionCount: minimumConnectionCount, leaky: leaky, loop: self.server.loop, backgroundLogger: .redisBaseConnectionPoolLogger ) { loop in - return loop.makeSucceededFuture(self.createAConnection()) + loop.makeSucceededFuture(self.createAConnection()) } } - func createPool(maximumConnectionCount: Int, minimumConnectionCount: Int, leaky: Bool, connectionFactory: @escaping (EventLoop) -> EventLoopFuture) -> ConnectionPool { - return ConnectionPool( + func createPool( + maximumConnectionCount: Int, + minimumConnectionCount: Int, + leaky: Bool, + connectionFactory: @escaping (EventLoop) -> EventLoopFuture + ) -> ConnectionPool { + ConnectionPool( maximumConnectionCount: maximumConnectionCount, minimumConnectionCount: minimumConnectionCount, leaky: leaky, @@ -139,7 +145,11 @@ final class ConnectionPoolTests: XCTestCase { } XCTAssertNoThrow(try self.server.runWhileActive()) XCTAssertEqual(self.server.channels.count, 8) - XCTAssertTrue(self.server.channels.allMatch(ArraySlice(indicesAndChannels.compactMap { $0.1.channel as? EmbeddedChannel }))) + XCTAssertTrue( + self.server.channels.allMatch( + ArraySlice(indicesAndChannels.compactMap { $0.1.channel as? EmbeddedChannel }) + ) + ) XCTAssertEqual(Array(0..<8), indicesAndChannels.map { $0.0 }) // Now we're going to try to take out another 8 leases. The pool is not leaky, so these will queue. @@ -150,7 +160,11 @@ final class ConnectionPoolTests: XCTestCase { } XCTAssertNoThrow(try self.server.runWhileActive()) XCTAssertEqual(self.server.channels.count, 8) - XCTAssertTrue(self.server.channels.allMatch(ArraySlice(indicesAndChannels.compactMap { $0.1.channel as? EmbeddedChannel }))) + XCTAssertTrue( + self.server.channels.allMatch( + ArraySlice(indicesAndChannels.compactMap { $0.1.channel as? EmbeddedChannel }) + ) + ) XCTAssertEqual(Array(0..<8), indicesAndChannels.map { $0.0 }) // Let's return 4 leases to the pool. These will be recycled in order. @@ -162,8 +176,16 @@ final class ConnectionPoolTests: XCTestCase { XCTAssertEqual(indicesAndChannels.count, 8) // The first 4 connections are now the last 4 returned. - XCTAssertTrue(self.server.channels.prefix(4).allMatch(ArraySlice(indicesAndChannels.suffix(4).compactMap { $0.1.channel as? EmbeddedChannel }))) - XCTAssertTrue(self.server.channels.suffix(4).allMatch(ArraySlice(indicesAndChannels.prefix(4).compactMap { $0.1.channel as? EmbeddedChannel }))) + XCTAssertTrue( + self.server.channels.prefix(4).allMatch( + ArraySlice(indicesAndChannels.suffix(4).compactMap { $0.1.channel as? EmbeddedChannel }) + ) + ) + XCTAssertTrue( + self.server.channels.suffix(4).allMatch( + ArraySlice(indicesAndChannels.prefix(4).compactMap { $0.1.channel as? EmbeddedChannel }) + ) + ) XCTAssertEqual(Array(4..<12), indicesAndChannels.map { $0.0 }) // Let's do that again. @@ -175,7 +197,11 @@ final class ConnectionPoolTests: XCTestCase { XCTAssertEqual(indicesAndChannels.count, 8) // The channels are back to being in order again. - XCTAssertTrue(self.server.channels.allMatch(ArraySlice(indicesAndChannels.compactMap { $0.1.channel as? EmbeddedChannel }))) + XCTAssertTrue( + self.server.channels.allMatch( + ArraySlice(indicesAndChannels.compactMap { $0.1.channel as? EmbeddedChannel }) + ) + ) XCTAssertEqual(Array(8..<16), indicesAndChannels.map { $0.0 }) } @@ -198,7 +224,11 @@ final class ConnectionPoolTests: XCTestCase { } XCTAssertNoThrow(try self.server.runWhileActive()) XCTAssertEqual(self.server.channels.count, 8) - XCTAssertTrue(self.server.channels.allMatch(ArraySlice(indicesAndChannels.compactMap { $0.1.channel as? EmbeddedChannel }))) + XCTAssertTrue( + self.server.channels.allMatch( + ArraySlice(indicesAndChannels.compactMap { $0.1.channel as? EmbeddedChannel }) + ) + ) XCTAssertEqual(Array(0..<8), indicesAndChannels.map { $0.0 }) // Now we're going to try to take out another 8 leases. The pool is leaky, so these will not queue: they all get connections. @@ -209,7 +239,11 @@ final class ConnectionPoolTests: XCTestCase { } XCTAssertNoThrow(try self.server.runWhileActive()) XCTAssertEqual(self.server.channels.count, 16) - XCTAssertTrue(self.server.channels.allMatch(ArraySlice(indicesAndChannels.compactMap { $0.1.channel as? EmbeddedChannel }))) + XCTAssertTrue( + self.server.channels.allMatch( + ArraySlice(indicesAndChannels.compactMap { $0.1.channel as? EmbeddedChannel }) + ) + ) XCTAssertEqual(Array(0..<16), indicesAndChannels.map { $0.0 }) // Let's return all the leases to the pool. 8 of them, the last 8 to be returned, get closed. @@ -242,7 +276,7 @@ final class ConnectionPoolTests: XCTestCase { XCTAssertEqual(self.server.channels.count, 1) // Lease this connection and close it, then return it. We're gonna queue up 2 leases. - var leased = Array() + var leased = [RedisConnection]() for _ in 0..<2 { pool.leaseConnection(deadline: .distantFuture).whenSuccess { connection in leased.append(connection) @@ -263,7 +297,9 @@ final class ConnectionPoolTests: XCTestCase { pool.returnConnection(leased.first!) XCTAssertNoThrow(try self.server.runWhileActive()) XCTAssertEqual(self.server.channels.count, 1) - XCTAssertTrue(self.server.channels.allMatch(leased.dropFirst().compactMap { ($0.channel) as? EmbeddedChannel }[...])) + XCTAssertTrue( + self.server.channels.allMatch(leased.dropFirst().compactMap { ($0.channel) as? EmbeddedChannel }[...]) + ) } func testLeasingFromClosedPoolsFails() throws { @@ -297,7 +333,7 @@ final class ConnectionPoolTests: XCTestCase { XCTAssertEqual(self.server.channels.count, 1) // We're going to lease the connection. - var leased = Array() + var leased = [RedisConnection]() pool.leaseConnection(deadline: .distantFuture).whenSuccess { connection in leased.append(connection) } @@ -490,7 +526,6 @@ final class ConnectionPoolTests: XCTestCase { XCTAssertNoThrow(try closePromise.futureResult.wait()) } - func testNonLeakyBucketWillKeepConnectingIfThereIsSpaceAndWaiters() throws { var connectionPromise: EventLoopPromise? = nil let pool = self.createPool(maximumConnectionCount: 1, minimumConnectionCount: 0, leaky: false) { loop in @@ -599,7 +634,7 @@ final class ConnectionPoolTests: XCTestCase { // Lease a connection and return it, in a loop. This will not succeed immediately because we delay connection // establishment. - var results = Array?>(repeating: nil, count: 10) + var results = [Result?](repeating: nil, count: 10) for i in 0..<10 { // Just to stress the code a bit we're going to retire these in backwards order. pool.leaseConnection(deadline: .uptimeNanoseconds(UInt64(10 - i))).whenComplete { result in @@ -736,7 +771,9 @@ final class ConnectionPoolTests: XCTestCase { // We expect 4 connections still to be open, and for those to match the _last 4_ of the connections we were leased. XCTAssertEqual(self.server.channels.count, 4) - XCTAssertTrue(connections.suffix(4).compactMap { $0.channel as? EmbeddedChannel }.allMatch(self.server.channels)) + XCTAssertTrue( + connections.suffix(4).compactMap { $0.channel as? EmbeddedChannel }.allMatch(self.server.channels) + ) } } @@ -802,15 +839,15 @@ extension ConnectionPoolTests { extension ConnectionPool { func activate() { self.activate(logger: .redisBaseConnectionPoolLogger) } - + func leaseConnection(deadline: NIODeadline) -> EventLoopFuture { - return self.leaseConnection(deadline: deadline, logger: .redisBaseConnectionPoolLogger) + self.leaseConnection(deadline: deadline, logger: .redisBaseConnectionPoolLogger) } - + func returnConnection(_ connection: RedisConnection) { self.returnConnection(connection, logger: .redisBaseConnectionPoolLogger) } - + func close(promise: EventLoopPromise? = nil) { self.close(promise: promise, logger: .redisBaseConnectionPoolLogger) } @@ -844,7 +881,6 @@ extension RandomAccessCollection where SubSequence == Self { } } - extension Optional where Wrapped == Result { var isNil: Bool { switch self { diff --git a/Tests/RediStackTests/Helpers/MockNodeDescription.swift b/Tests/RediStackTests/Helpers/MockNodeDescription.swift index 12859d33..f57ceea9 100644 --- a/Tests/RediStackTests/Helpers/MockNodeDescription.swift +++ b/Tests/RediStackTests/Helpers/MockNodeDescription.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2023 RediStack project authors +// Copyright (c) 2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,8 +12,8 @@ // //===----------------------------------------------------------------------===// -import RediStack import NIOCore +import RediStack struct MockNodeDescription: RedisClusterNodeDescriptionProtocol, Hashable { var host: String? diff --git a/Tests/RediStackTests/Helpers/RedisErrorTests.swift b/Tests/RediStackTests/Helpers/RedisErrorTests.swift index 3ef99ae9..7c93189c 100644 --- a/Tests/RediStackTests/Helpers/RedisErrorTests.swift +++ b/Tests/RediStackTests/Helpers/RedisErrorTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) YEARS RediStack project authors +// Copyright (c) 2024 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,9 +12,10 @@ // //===----------------------------------------------------------------------===// -@testable import RediStack import XCTest +@testable import RediStack + final class RedisErrorTests: XCTestCase { func testLoggableDescriptionLocalized() { let error = RedisError(reason: "test") diff --git a/Tests/RediStackTests/RESPTranslatorTests.swift b/Tests/RediStackTests/RESPTranslatorTests.swift index 33bbdcf1..a216a098 100644 --- a/Tests/RediStackTests/RESPTranslatorTests.swift +++ b/Tests/RediStackTests/RESPTranslatorTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,11 +12,13 @@ // //===----------------------------------------------------------------------===// -import struct Foundation.Data import NIOCore -@testable import RediStack import XCTest +import struct Foundation.Data + +@testable import RediStack + final class RESPTranslatorTests: XCTestCase { private let allocator = ByteBufferAllocator() private let parser = RESPTranslator() @@ -29,7 +31,7 @@ extension RESPTranslatorTests { XCTAssertTrue(writingTestPass(input: .simpleString("Test1".byteBuffer), expected: "+Test1\r\n")) XCTAssertTrue(writingTestPass(input: .simpleString("®in§³¾".byteBuffer), expected: "+®in§³¾\r\n")) } - + func testWriting_bulkStrings() { let bytes: [UInt8] = [0x01, 0x02, 0x0a, 0x1b, 0xaa] var buffer = allocator.buffer(capacity: 5) @@ -39,65 +41,69 @@ extension RESPTranslatorTests { XCTAssertTrue(writingTestPass(input: .init(bulk: ""), expected: "$0\r\n\r\n")) XCTAssertTrue(writingTestPass(input: .init(bulk: Optional.none), expected: "$0\r\n\r\n")) } - + func testWriting_integers() { XCTAssertTrue(writingTestPass(input: .integer(Int.min), expected: ":\(Int.min)\r\n")) XCTAssertTrue(writingTestPass(input: .integer(0), expected: ":0\r\n")) } - + func testWriting_arrays() { XCTAssertTrue(writingTestPass(input: .array([]), expected: "*0\r\n")) - XCTAssertTrue(writingTestPass( - input: .array([ .integer(3), .simpleString("foo".byteBuffer) ]), - expected: "*2\r\n:3\r\n+foo\r\n" - )) - let bytes: [UInt8] = [ 0x0a, 0x1a, 0x1b, 0xff ] + XCTAssertTrue( + writingTestPass( + input: .array([.integer(3), .simpleString("foo".byteBuffer)]), + expected: "*2\r\n:3\r\n+foo\r\n" + ) + ) + let bytes: [UInt8] = [0x0a, 0x1a, 0x1b, 0xff] var buffer = allocator.buffer(capacity: 4) buffer.writeBytes(bytes) - XCTAssertTrue(writingTestPass( - input: .array([ .array([ .integer(10), .bulkString(buffer) ]) ]), - expected: "*1\r\n*2\r\n:10\r\n$4\r\n".bytes + bytes + "\r\n".bytes - )) + XCTAssertTrue( + writingTestPass( + input: .array([.array([.integer(10), .bulkString(buffer)])]), + expected: "*1\r\n*2\r\n:10\r\n$4\r\n".bytes + bytes + "\r\n".bytes + ) + ) } - + func testWriting_errors() { let error = RedisError(reason: "Manual error") XCTAssertTrue(writingTestPass(input: .error(error), expected: "-\(error.message)\r\n")) } - + func testWriting_null() { XCTAssertTrue(writingTestPass(input: .null, expected: "$-1\r\n")) } - + func testWriting_foundationData() { let name = #function let data = Data(name.bytes).convertedToRESPValue() XCTAssertTrue(writingTestPass(input: data, expected: "$\(name.count)\r\n\(name)\r\n")) } - + private func writingTestPass(input: RESPValue, expected: [UInt8]) -> Bool { var buffer = allocator.buffer(capacity: expected.count) buffer.writeRESPValue(input) - + let result = buffer.getBytes(at: 0, length: buffer.readableBytes) - + return result == expected } - + private func writingTestPass(input: RESPValue, expected: String) -> Bool { var buffer = allocator.buffer(capacity: expected.count) buffer.writeRESPValue(input) - + let result = buffer.getString(at: 0, length: buffer.readableBytes) - + return result == expected } } // MARK: READING -fileprivate extension ByteBuffer { - mutating func mimicTokenParse() { +extension ByteBuffer { + fileprivate mutating func mimicTokenParse() { self.moveReaderIndex(forwardBy: 1) } } @@ -112,33 +118,33 @@ extension RESPTranslatorTests { XCTAssertEqual(error as? RESPTranslator.ParsingError, .invalidToken) } } - + func testParsing_invalidSymbols() { let testRESP = "&3\r\n" var buffer = allocator.buffer(capacity: testRESP.count) buffer.writeString(testRESP) - + XCTAssertThrowsError(try parser.parseBytes(from: &buffer)) XCTAssertEqual(buffer.readerIndex, 0) } - + func testParsing_simpleString() throws { let value = try parseTest(inputRESP: "+OK\r\n") XCTAssertEqual(value?.string, "OK") } - + func testParsing_simpleString_chunked() throws { let values = try parseTest(withChunks: ["+OK\r", "\n+&t®in§³¾\r", "\n"]) XCTAssertEqual(values.count, 2) XCTAssertEqual(values[0].string, "OK") XCTAssertEqual(values[1].string, "&t®in§³¾") } - + func testParsing_integer() throws { let value = try parseTest(inputRESP: ":300\r\n") XCTAssertEqual(value?.int, 300) } - + func testParsing_integer_chunked() throws { let values = try parseTest(withChunks: [":300\r", "\n:\(Int.min)\r", "\n:", "\(Int.max)", "\r", "\n"]) XCTAssertEqual(values.count, 3) @@ -146,7 +152,7 @@ extension RESPTranslatorTests { XCTAssertEqual(values[1].int, Int.min) XCTAssertEqual(values[2].int, Int.max) } - + func testParsing_bulkStrings() throws { XCTAssertEqual(try parseTest(inputRESP: "$-1\r\n")?.isNull, true) XCTAssertEqual(try parseTest(inputRESP: "$0\r\n\r\n")?.string, "") @@ -160,7 +166,7 @@ extension RESPTranslatorTests { [0xba] ) } - + func testParsing_bulkStrings_chunked() throws { let t1 = try parseTest(withChunks: ["$3\r", "\naaa\r\n$", "4\r\nnio!\r\n"]) XCTAssertEqual(t1.count, 2) @@ -170,20 +176,20 @@ extension RESPTranslatorTests { let chunks: [[UInt8]] = [ "$3\r".bytes, "\n".bytes + [0xAA, 0xA3, 0xFF] + "\r\n$".bytes, - "4\r\n".bytes + [0xbb, 0x3a, 0xba, 0xFF] + "\r\n".bytes + "4\r\n".bytes + [0xbb, 0x3a, 0xba, 0xFF] + "\r\n".bytes, ] let t2 = try parseTest(withChunks: chunks) XCTAssertTrue(t2[0].bytes?.count == 3) XCTAssertTrue(t2[1].bytes?.count == 4) } - + func testParsing_arrays() throws { XCTAssertEqual(try parseTest(inputRESP: "*1\r\n+!\r\n")?.array?.count, 1) XCTAssertEqual(try parseTest(inputRESP: "*2\r\n*1\r\n:1\r\n:3\r\n")?.array?.count, 2) XCTAssertEqual(try parseTest(input: "*0\r\n".bytes)?.array?.count, 0) XCTAssertNil(try parseTest(input: "*-1\r\n".bytes)?.array) } - + func testParsing_arrays_chunked() throws { let t1 = try parseTest(withChunks: ["*2\r", "\n+a\r\n+c\r\n*", "0\r\n"]) XCTAssertEqual(t1.count, 2) @@ -191,64 +197,64 @@ extension RESPTranslatorTests { XCTAssertEqual(t1[0].array?[0].string, "a") XCTAssertEqual(t1[0].array?[1].string, "c") XCTAssertTrue(t1[1].array?.count == 0) - + let t2 = try parseTest(withChunks: [ "*-1\r".bytes, "\n".bytes, "*1\r".bytes, - "\n+£\r\n".bytes + "\n+£\r\n".bytes, ]) XCTAssertEqual(t2.count, 2) XCTAssertTrue(t2[0].isNull) XCTAssertTrue(t2[1].array?.count == 1) XCTAssertEqual(t2[1].array?[0].string, "£") } - + func testParsing_error() throws { let expectedContent = "ERR unknown command 'foobar'" let testString = "-\(expectedContent)\r\n" - + var buffer = allocator.buffer(capacity: expectedContent.count) buffer.writeString(testString) - + guard let value = try parser.parseBytes(from: &buffer) else { return XCTFail("Failed to parse error") } - + XCTAssertEqual(value.error?.message.contains(expectedContent), true) } private func parseTest(inputRESP: String) throws -> RESPValue? { - return try parseTest(input: inputRESP.bytes) + try parseTest(input: inputRESP.bytes) } private func parseTest(input: [UInt8]) throws -> RESPValue? { var buffer = allocator.buffer(capacity: input.count) buffer.writeBytes(input) - + let result = try parser.parseBytes(from: &buffer) assert(buffer.readerIndex == buffer.writerIndex) - + return result } - + private func parseTest(withChunks messageChunks: [String]) throws -> [RESPValue] { - return try parseTest(withChunks: messageChunks.map({ $0.bytes })) + try parseTest(withChunks: messageChunks.map({ $0.bytes })) } - + private func parseTest(withChunks messageChunks: [[UInt8]]) throws -> [RESPValue] { var buffer = allocator.buffer(capacity: messageChunks.joined().count) - + var results = [RESPValue]() - + for chunk in messageChunks { buffer.writeBytes(chunk) - + guard let result = try parser.parseBytes(from: &buffer) else { continue } - + results.append(result) } - + assert(buffer.readerIndex == buffer.writerIndex) - + return results } } @@ -260,43 +266,43 @@ extension RESPTranslatorTests { XCTAssertNil(simpleStringParseTest("+OK")) XCTAssertNil(simpleStringParseTest("+OK\r")) } - + func testParsing_simpleString_withNoContent() { XCTAssertEqual(simpleStringParseTest("+\r\n"), "") } - + func testParsing_simpleString_withContent() { XCTAssertEqual(simpleStringParseTest("+ \r\n"), " ") XCTAssertEqual(simpleStringParseTest("+OK\r\n"), "OK") XCTAssertEqual(simpleStringParseTest("+OK\r\n+OTHER STRING\r\n"), "OK") XCTAssertEqual(simpleStringParseTest("+&t®in§³¾\r\n"), "&t®in§³¾") } - + func testParsing_simpleString_handlesRecursion() { let testString = "+OK\r\n+OTHER STRING\r\n" - + var buffer = allocator.buffer(capacity: testString.count) buffer.writeString(testString) - + buffer.mimicTokenParse() var first = parser.parseSimpleString(from: &buffer)! - XCTAssertEqual(buffer.readerIndex, 5) // position of the 2nd '+' + XCTAssertEqual(buffer.readerIndex, 5) // position of the 2nd '+' XCTAssertEqual(first.readBytes(length: first.readableBytes), "OK".bytes) - + buffer.mimicTokenParse() var second = parser.parseSimpleString(from: &buffer)! XCTAssertEqual(buffer.readerIndex, 20) XCTAssertEqual(second.readBytes(length: second.readableBytes), "OTHER STRING".bytes) } - + private func simpleStringParseTest(_ inputRESP: String) -> String? { var buffer = allocator.buffer(capacity: inputRESP.count) buffer.writeString(inputRESP) - + buffer.mimicTokenParse() - + guard let stringBuffer = parser.parseSimpleString(from: &buffer) else { return nil } - + return RESPValue.simpleString(stringBuffer).string } @@ -322,7 +328,7 @@ extension RESPTranslatorTests { XCTAssertEqual(try integerParseTest(":\(Int.min)\r\n"), Int.min) XCTAssertEqual(try integerParseTest(":\(Int.max)\r\n"), Int.max) } - + func testParsing_integer_missingBytes() { XCTAssertNil(try integerParseTest(":\r")) XCTAssertNil(try integerParseTest(":")) @@ -331,18 +337,18 @@ extension RESPTranslatorTests { XCTAssertEqual(error as? RESPTranslator.ParsingError, .invalidIntegerFormat) } } - + func testParsing_integer_recursively() throws { let testString = ":1\r\n:300\r\n" - + var buffer = allocator.buffer(capacity: testString.count) buffer.writeString(testString) - + buffer.mimicTokenParse() let first = try parser.parseInteger(from: &buffer) - XCTAssertEqual(buffer.readerIndex, 4) // position of the 2nd ':' + XCTAssertEqual(buffer.readerIndex, 4) // position of the 2nd ':' XCTAssertEqual(first, 1) - + buffer.mimicTokenParse() let second = try parser.parseInteger(from: &buffer) XCTAssertEqual(buffer.readerIndex, 10) @@ -354,7 +360,7 @@ extension RESPTranslatorTests { XCTAssertEqual(error as? RESPTranslator.ParsingError, .invalidIntegerFormat) } } - + private func integerParseTest(_ inputRESP: String) throws -> Int? { var buffer = allocator.buffer(capacity: inputRESP.count) buffer.writeString(inputRESP) @@ -374,7 +380,7 @@ extension RESPTranslatorTests { XCTAssertEqual(error as? RESPTranslator.ParsingError, .bulkStringSizeMismatch) } } - + func testParsing_bulkString_invalidNegativeSize() { var buffer = self.allocator.buffer(capacity: 128) buffer.writeString("$-4\r\nwhat\r\n") @@ -383,7 +389,7 @@ extension RESPTranslatorTests { XCTAssertEqual(error as? RESPTranslator.ParsingError, .invalidBulkStringSize) } } - + func testParsing_bulkString_SizeIsNaN() { var buffer = self.allocator.buffer(capacity: 128) buffer.writeString("$FOO\r\nwhat\r\n") @@ -392,65 +398,65 @@ extension RESPTranslatorTests { XCTAssertEqual(error as? RESPTranslator.ParsingError, .invalidIntegerFormat) } } - + func testParsing_bulkString_missingEndings() { for message in ["$6", "$6\r\n", "$6\r\nabcdef", "$0\r\n"] { XCTAssertNil(bulkStringParseTest(inputRESP: message)) } } - + func testParsing_bulkString_withNoSize() { let result = bulkStringParseTest(inputRESP: "$0\r\n\r\n") XCTAssertEqual(result?.bytes?.count, 0) } - + func testParsing_bulkString_withContent() { let result = bulkStringParseTest(inputRESP: "$1\r\n:\r\n")?.bytes XCTAssertEqual(result?.count, 1) XCTAssertEqual(result?[0], .colon) } - + func testParsing_bulkString_null() { let result = bulkStringParseTest(inputRESP: "$-1\r\n") XCTAssertEqual(result?.isNull, true) } - + func testParsing_bulkString_rawBytes() { let bytes: [UInt8] = [0x00, 0x01, 0x02, 0x03, 0x0A, 0xFF] let allBytes = "$\(bytes.count)\r\n".bytes + bytes + "\r\n".bytes - + let result = bulkStringParseTest(input: allBytes) XCTAssertEqual(result?.bytes?.count, bytes.count) } - + func testParsing_bulkString_recursively() { let testString = "$0\r\n\r\n$5\r\nredis\r\n" - + var buffer = allocator.buffer(capacity: testString.count) buffer.writeString(testString) - + buffer.mimicTokenParse() let first = try? parser.parseBulkString(from: &buffer) - XCTAssertEqual(buffer.readerIndex, 6) // position of the 2nd '$' - + XCTAssertEqual(buffer.readerIndex, 6) // position of the 2nd '$' + buffer.mimicTokenParse() let second = try? parser.parseBulkString(from: &buffer) XCTAssertEqual(buffer.readerIndex, 17) - + XCTAssertEqual(first?.string, "") XCTAssertEqual(second?.string, "redis") } - + private func bulkStringParseTest(inputRESP: String) -> RESPValue? { - return bulkStringParseTest(input: inputRESP.bytes) + bulkStringParseTest(input: inputRESP.bytes) } - + private func bulkStringParseTest(input: [UInt8]) -> RESPValue? { var buffer = allocator.buffer(capacity: input.count) buffer.writeBytes(input) buffer.mimicTokenParse() - + guard let result = try? parser.parseBulkString(from: &buffer) else { return nil } return result } @@ -463,12 +469,12 @@ extension RESPTranslatorTests { let result = try arrayParseTest(inputRESP: "*-1\r\n") XCTAssertEqual(result?.isNull, true) } - + func testParsing_array_whenEmpty() throws { let result = try arrayParseTest(inputRESP: "*0\r\n") XCTAssertEqual(result?.array?.count, 0) } - + func testParsing_array_withMixedTypes() throws { let result = try arrayParseTest(inputRESP: "*3\r\n:3\r\n+OK\r\n$1\r\na\r\n")?.array XCTAssertEqual(result?.count, 3) @@ -476,7 +482,7 @@ extension RESPTranslatorTests { XCTAssertEqual(result?[1].string, "OK") XCTAssertEqual(result?[2].bytes?.count, 1) } - + func testParsing_array_withNullElements() throws { let result = try arrayParseTest(inputRESP: "*3\r\n:3\r\n$-1\r\n:30\r\n")?.array XCTAssertEqual(result?.count, 3) @@ -484,7 +490,7 @@ extension RESPTranslatorTests { XCTAssertEqual(result?[1].isNull, true) XCTAssertEqual(result?[2].int, 30) } - + func testParsing_array_nested() throws { let result = try arrayParseTest(inputRESP: "*2\r\n:3\r\n*2\r\n:30\r\n:15\r\n")?.array XCTAssertEqual(result?.count, 2) @@ -494,42 +500,42 @@ extension RESPTranslatorTests { XCTAssertEqual(nested?[0].int, 30) XCTAssertEqual(nested?[1].int, 15) } - + func testParsing_array_recursively() throws { let testString = "*1\r\n:3\r\n*2\r\n+OK\r\n$3\r\nabc\r\n" - + var buffer = allocator.buffer(capacity: testString.count) buffer.writeString(testString) - + buffer.mimicTokenParse() let first = try parser.parseArray(from: &buffer) - XCTAssertEqual(buffer.readerIndex, 8) // position of the 2nd '$' - + XCTAssertEqual(buffer.readerIndex, 8) // position of the 2nd '$' + buffer.mimicTokenParse() let second = try parser.parseArray(from: &buffer) XCTAssertEqual(buffer.readerIndex, 26) - + guard let array1 = first?.array, let array2 = second?.array else { return XCTFail("Failed to parse both values") } - + XCTAssertEqual(array1.count, 1) XCTAssertEqual(array1[0].int, 3) XCTAssertEqual(array2.count, 2) XCTAssertEqual(array2[0].string, "OK") XCTAssertEqual(array2[1].string, "abc") } - + private func arrayParseTest(inputRESP: String) throws -> RESPValue? { - return try arrayParseTest(input: inputRESP.bytes) + try arrayParseTest(input: inputRESP.bytes) } - + private func arrayParseTest(input: [UInt8]) throws -> RESPValue? { var buffer = allocator.buffer(capacity: input.count) buffer.writeBytes(input) buffer.mimicTokenParse() - + guard let result = try parser.parseArray(from: &buffer) else { return nil } return result } diff --git a/Tests/RediStackTests/RESPValueTests.swift b/Tests/RediStackTests/RESPValueTests.swift index 37e7b9cd..fa6af593 100644 --- a/Tests/RediStackTests/RESPValueTests.swift +++ b/Tests/RediStackTests/RESPValueTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2019 RediStack project authors +// Copyright (c) 2019 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -18,43 +18,43 @@ import XCTest final class RESPValueTests: XCTestCase { func test_equatable() { let redisError = RedisError(reason: "testing") - + let null = RESPValue.null let error = RESPValue.error(redisError) let array = RESPValue.array([.null, error]) let integer = RESPValue.integer(3) let simpleString = RESPValue.simpleString("OK".byteBuffer) let bulkString = RESPValue.bulkString(nil) - + XCTAssertEqual(null, .null) XCTAssertNotEqual(null, error) XCTAssertNotEqual(null, array) XCTAssertNotEqual(null, integer) XCTAssertNotEqual(null, simpleString) XCTAssertNotEqual(null, bulkString) - + XCTAssertEqual(error, .error(redisError)) XCTAssertNotEqual(error, .error(RedisError(reason: "failure"))) XCTAssertNotEqual(error, integer) XCTAssertNotEqual(error, simpleString) XCTAssertNotEqual(error, bulkString) XCTAssertNotEqual(error, array) - + XCTAssertEqual(array, .array([.null, error])) XCTAssertNotEqual(array, .array([integer])) XCTAssertNotEqual(array, integer) XCTAssertNotEqual(array, simpleString) XCTAssertNotEqual(array, bulkString) - + XCTAssertEqual(integer, .integer(3)) XCTAssertNotEqual(integer, .integer(Int.max)) XCTAssertNotEqual(integer, simpleString) XCTAssertNotEqual(integer, bulkString) - + XCTAssertEqual(simpleString, .simpleString("OK".byteBuffer)) XCTAssertNotEqual(simpleString, .simpleString(#function.byteBuffer)) XCTAssertNotEqual(simpleString, bulkString) - + XCTAssertEqual(bulkString, .bulkString(nil)) XCTAssertNotEqual(bulkString, .bulkString("OK".byteBuffer)) } diff --git a/Tests/RediStackTests/RedisCommandEncoderTests.swift b/Tests/RediStackTests/RedisCommandEncoderTests.swift index a03ef9c1..4add2320 100644 --- a/Tests/RediStackTests/RedisCommandEncoderTests.swift +++ b/Tests/RediStackTests/RedisCommandEncoderTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2023 RediStack project authors +// Copyright (c) 2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -13,9 +13,10 @@ //===----------------------------------------------------------------------===// import NIOCore -@testable import RediStack import XCTest +@testable import RediStack + final class RedisCommandEncoderTests: XCTestCase { var encoder: RedisCommandEncoder! @@ -61,7 +62,6 @@ final class RedisCommandEncoderTests: XCTestCase { self.encoder.encodeRESPArray("SET", "key", "value", "NX", "GET", "EX", "60") var buffer = self.encoder.buffer - var resp: RESPValue? XCTAssertNoThrow(resp = try RESPTranslator().parseBytes(from: &buffer)) let expected = RESPValue.array([ @@ -71,7 +71,7 @@ final class RedisCommandEncoderTests: XCTestCase { .bulkString(.init(string: "NX")), .bulkString(.init(string: "GET")), .bulkString(.init(string: "EX")), - .bulkString(.init(string: "60")) + .bulkString(.init(string: "60")), ]) XCTAssertEqual(resp, expected) } diff --git a/Tests/RediStackTests/RedisConnection+ConfigurationTests.swift b/Tests/RediStackTests/RedisConnection+ConfigurationTests.swift index 9be625e4..3f71d9d6 100644 --- a/Tests/RediStackTests/RedisConnection+ConfigurationTests.swift +++ b/Tests/RediStackTests/RedisConnection+ConfigurationTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2023 RediStack project authors +// Copyright (c) 2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -31,4 +31,3 @@ final class RedisConnection_ConfigurationTest: XCTestCase { RedisConnection.Configuration.defaultPort = 6379 } } - diff --git a/Tests/RediStackTests/RedisConnectionTests.swift b/Tests/RediStackTests/RedisConnectionTests.swift index 16c8e2a7..ad0aa8f0 100644 --- a/Tests/RediStackTests/RedisConnectionTests.swift +++ b/Tests/RediStackTests/RedisConnectionTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2021-2023 RediStack project authors +// Copyright (c) 2021-2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -15,9 +15,10 @@ import Logging import NIOCore import NIOEmbedded -@testable import RediStack import XCTest +@testable import RediStack + final class RedisConnectionTests: XCTestCase { var logger: Logger { @@ -55,7 +56,9 @@ final class RedisConnectionTests: XCTestCase { XCTAssertNoThrow(maybeSocketAddress = try SocketAddress.makeAddressResolvingHost("localhost", port: 0)) guard let socketAddress = maybeSocketAddress else { return XCTFail("Expected a socketAddress") } var maybeConfiguration: RedisConnection.Configuration? - XCTAssertNoThrow(maybeConfiguration = try .init(address: socketAddress, username: "username", password: "password")) + XCTAssertNoThrow( + maybeConfiguration = try .init(address: socketAddress, username: "username", password: "password") + ) guard let configuration = maybeConfiguration else { return XCTFail("Expected a configuration") } let channel = EmbeddedChannel(handlers: [RedisCommandHandler()]) diff --git a/Tests/RediStackTests/RedisHashSlotTests.swift b/Tests/RediStackTests/RedisHashSlotTests.swift index 5b28cce4..8d13b0d6 100644 --- a/Tests/RediStackTests/RedisHashSlotTests.swift +++ b/Tests/RediStackTests/RedisHashSlotTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2023 RediStack project authors +// Copyright (c) 2023 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,9 +12,10 @@ // //===----------------------------------------------------------------------===// -@testable import RediStack import XCTest +@testable import RediStack + final class RedisHashSlotTests: XCTestCase { func testEdgeValues() { XCTAssertEqual(RedisHashSlot.min.rawValue, 0) @@ -51,7 +52,11 @@ final class RedisHashSlotTests: XCTestCase { } func testHashTagComputation() { - XCTAssert(RedisHashSlot.hashTag(forKey: "{user1000}.following").elementsEqual(RedisHashSlot.hashTag(forKey: "{user1000}.followers"))) + XCTAssert( + RedisHashSlot.hashTag(forKey: "{user1000}.following").elementsEqual( + RedisHashSlot.hashTag(forKey: "{user1000}.followers") + ) + ) XCTAssert(RedisHashSlot.hashTag(forKey: "{user1000}.following").elementsEqual("user1000".utf8)) XCTAssert(RedisHashSlot.hashTag(forKey: "{user1000}.followers").elementsEqual("user1000".utf8)) diff --git a/Tests/RediStackTests/RedisKeyLifetime.swift b/Tests/RediStackTests/RedisKeyLifetime.swift index 4da0959c..6f4680d1 100644 --- a/Tests/RediStackTests/RedisKeyLifetime.swift +++ b/Tests/RediStackTests/RedisKeyLifetime.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020 RediStack project authors +// Copyright (c) 2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,9 +12,10 @@ // //===----------------------------------------------------------------------===// -@testable import RediStack import XCTest +@testable import RediStack + final class RedisKeyLifetimeTests: XCTestCase { func test_initFromSeconds() { XCTAssertEqual(RedisKey.Lifetime(seconds: -2), .keyDoesNotExist) diff --git a/Tests/RedisTypesTests/RedisSetTests.swift b/Tests/RedisTypesTests/RedisSetTests.swift index 89046372..65b24dfe 100644 --- a/Tests/RedisTypesTests/RedisSetTests.swift +++ b/Tests/RedisTypesTests/RedisSetTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020 RediStack project authors +// Copyright (c) 2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,11 +12,12 @@ // //===----------------------------------------------------------------------===// -@testable import RediStack import RediStackTestUtils import RedisTypes import XCTest +@testable import RediStack + final class RedisSetTests: RedisTypesIntegrationTestCase { func testInit() { let firstSet = RedisSet(identifier: #function, client: self.connection) @@ -27,116 +28,116 @@ final class RedisSetTests: RedisTypesIntegrationTestCase { let set = self.connection.makeSet(key: #function, type: Int.self) XCTAssertNoThrow(try set.insert(3).wait()) XCTAssertNoThrow(try set.insert(contentsOf: [1, 2, 3]).wait()) - + let insertFuture = set.insert(contentsOf: [3, 4]) XCTAssertNoThrow(try insertFuture.wait()) XCTAssertEqual(try insertFuture.wait(), 1) } - + func testCount() throws { let set = self.connection.makeSet(key: #function, type: String.self) XCTAssertEqual(try set.count.wait(), 0) - + let string = "Hello, Redis Types!" XCTAssertNoThrow(try set.insert(string).wait()) XCTAssertEqual(try set.count.wait(), 1) XCTAssertEqual(try set.allElements.wait(), [string]) } - + func testMove() throws { let firstSet = self.connection.makeSet(key: .init("\(#function)_1"), type: Int.self) let secondSet = self.connection.makeSet(key: .init("\(#function)_2"), type: Int.self) - + _ = try firstSet.insert(3).wait() _ = try secondSet.insert(4).wait() - + XCTAssertTrue(try firstSet.move(3, to: secondSet).wait()) XCTAssertTrue(try firstSet.isEmpty.wait()) XCTAssertEqual(try secondSet.count.wait(), 2) - + XCTAssertFalse(try firstSet.contains(3).wait()) XCTAssertFalse(try firstSet.move(3, to: secondSet).wait()) } - + func testRemove() throws { let set = self.connection.makeSet(key: #function, type: Int.self) - + _ = try set.insert(contentsOf: [1, 2, 3]).wait() - + XCTAssertFalse(try set.remove(4).wait()) XCTAssertTrue(try set.remove(3).wait()) - + XCTAssertNoThrow(try set.remove([]).wait()) - + let removed = try set.remove([1, 2]).wait() XCTAssertEqual(removed, 2) } - + func testRemoveAll() throws { let set = self.connection.makeSet(key: #function, type: Int.self) - + XCTAssertNoThrow(try set.insert(contentsOf: []).wait()) - + let inserted = try set.insert(contentsOf: [1, 2, 3]).wait() XCTAssertEqual(inserted, 3) - + XCTAssertTrue(try set.removeAll().wait()) XCTAssertEqual(try set.count.wait(), 0) } - + func testPopRandomElement() throws { let set = self.connection.makeSet(key: #function, type: String.self) - + XCTAssertNil(try set.popRandomElement().wait()) - + _ = try set.insert(contentsOf: ["Hello", ",", "World", "!"]).wait() - + XCTAssertNotNil(try set.popRandomElement().wait()) XCTAssertEqual(try set.count.wait(), 3) } - + func testPopRandomElements() throws { let set = self.connection.makeSet(key: #function, type: Int.self) - + XCTAssertNoThrow(try set.insert(contentsOf: [1, 2, 3]).wait()) - + XCTAssertThrowsError(try set.popRandomElements(max: -1).wait()) - + var elements = try set.popRandomElements(max: 0).wait() XCTAssertEqual(elements.count, 0) - + elements = try set.popRandomElements(max: 2).wait() XCTAssertEqual(elements.count, 2) XCTAssertEqual(try set.count.wait(), 1) - + elements = try set.popRandomElements(max: 2).wait() XCTAssertEqual(elements.count, 1) XCTAssertEqual(try set.count.wait(), 0) } - + func testRandomElement() throws { let set = self.connection.makeSet(key: #function, type: String.self) - + XCTAssertNil(try set.randomElement().wait()) - + let values = ["RediStack", "SSWG", "Swift"] - + XCTAssertNoThrow(try set.insert(contentsOf: values).wait()) - + let randomElement = try set.randomElement().wait() XCTAssertNotNil(randomElement) XCTAssertTrue(values.contains(randomElement ?? "nope")) XCTAssertEqual(try set.count.wait(), 3) } - + func testRandomElements() throws { let set = self.connection.makeSet(key: #function, type: Int.self) - + _ = try set.insert(contentsOf: [1, 2, 3]).wait() - + var elements = try set.randomElements(max: 4).wait() XCTAssertEqual(elements.count, 3) - + elements = try set.randomElements(max: 4, allowDuplicates: true).wait() XCTAssertEqual(elements.count, 4) } diff --git a/Tests/RedisTypesTests/RedisTypesIntegrationTestCase.swift b/Tests/RedisTypesTests/RedisTypesIntegrationTestCase.swift index 75bd8f84..09076190 100644 --- a/Tests/RedisTypesTests/RedisTypesIntegrationTestCase.swift +++ b/Tests/RedisTypesTests/RedisTypesIntegrationTestCase.swift @@ -2,7 +2,7 @@ // // This source file is part of the RediStack open source project // -// Copyright (c) 2020 RediStack project authors +// Copyright (c) 2020 Apple Inc. and the RediStack project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,14 +12,15 @@ // //===----------------------------------------------------------------------===// -import class Foundation.ProcessInfo import RediStackTestUtils +import class Foundation.ProcessInfo + class RedisTypesIntegrationTestCase: RedisIntegrationTestCase { override var redisHostname: String { - return ProcessInfo.processInfo.environment["REDIS_URL"] ?? "localhost" + ProcessInfo.processInfo.environment["REDIS_URL"] ?? "localhost" } override var redisPassword: String? { - return ProcessInfo.processInfo.environment["REDIS_PW"] + ProcessInfo.processInfo.environment["REDIS_PW"] } } diff --git a/docker/docker-compose.2004.56.yaml b/docker/docker-compose.2004.56.yaml index 7554f6bd..0b8e8067 100644 --- a/docker/docker-compose.2004.56.yaml +++ b/docker/docker-compose.2004.56.yaml @@ -15,7 +15,6 @@ services: test: image: redistack:20.04-5.6 environment: [] - #- SANITIZER_ARG=--sanitize=thread shell: image: redistack:20.04-5.6 diff --git a/docker/docker-compose.2204.510.yaml b/docker/docker-compose.2204.510.yaml index 4bce1025..a59e3f92 100644 --- a/docker/docker-compose.2204.510.yaml +++ b/docker/docker-compose.2204.510.yaml @@ -15,7 +15,6 @@ services: image: redistack:22.04-5.10 environment: - IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error - #- SANITIZER_ARG=--sanitize=thread shell: image: redistack:22.04-5.10 diff --git a/docker/docker-compose.2204.57.yaml b/docker/docker-compose.2204.57.yaml index b958f0ce..32d5439b 100644 --- a/docker/docker-compose.2204.57.yaml +++ b/docker/docker-compose.2204.57.yaml @@ -15,7 +15,6 @@ services: test: image: redistack:22.04-5.7 environment: [] - #- SANITIZER_ARG=--sanitize=thread shell: image: redistack:22.04-5.7 diff --git a/docker/docker-compose.2204.58.yaml b/docker/docker-compose.2204.58.yaml index 3e824c35..892950ac 100644 --- a/docker/docker-compose.2204.58.yaml +++ b/docker/docker-compose.2204.58.yaml @@ -16,7 +16,6 @@ services: image: redistack:22.04-5.8 environment: - IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error - #- SANITIZER_ARG=--sanitize=thread shell: image: redistack:22.04-5.8 diff --git a/docker/docker-compose.2204.59.yaml b/docker/docker-compose.2204.59.yaml index 5352a200..8ac42d8f 100644 --- a/docker/docker-compose.2204.59.yaml +++ b/docker/docker-compose.2204.59.yaml @@ -16,7 +16,6 @@ services: image: redistack:22.04-5.9 environment: - IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error - #- SANITIZER_ARG=--sanitize=thread shell: image: redistack:22.04-5.9 diff --git a/docker/docker-compose.2204.main.yaml b/docker/docker-compose.2204.main.yaml index c67a7a39..f0bdd0f4 100644 --- a/docker/docker-compose.2204.main.yaml +++ b/docker/docker-compose.2204.main.yaml @@ -15,7 +15,6 @@ services: image: redistack:22.04-main environment: - IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error - #- SANITIZER_ARG=--sanitize=thread shell: image: redistack:22.04-main diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 7cee7f66..68eda0fb 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -29,7 +29,7 @@ services: documentation-check: <<: *common command: /bin/bash -xcl "./scripts/check-docs.sh" - + test: <<: *common depends_on: [runtime-setup, redis] diff --git a/scripts/check_no_api_breakages.sh b/scripts/check_no_api_breakages.sh deleted file mode 100755 index 34c1d695..00000000 --- a/scripts/check_no_api_breakages.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## This source file is part of the RediStack open source project -## -## Copyright (c) 2023 RediStack project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of RediStack project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## -##===----------------------------------------------------------------------===## -## -## This source file is part of the SwiftNIO open source project -## -## Copyright (c) 2017-2020 Apple Inc. and the SwiftNIO project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of SwiftNIO project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## - -set -eu - -function usage() { - echo >&2 "Usage: $0 REPO-GITHUB-URL NEW-VERSION OLD-VERSIONS..." - echo >&2 - echo >&2 "This script requires a Swift 5.6+ toolchain." - echo >&2 - echo >&2 "Examples:" - echo >&2 - echo >&2 "Check between main and tag 1.4.1 of redistack:" - echo >&2 " $0 https://github.com/swift-server/redistack main 1.4.1" - echo >&2 - echo >&2 "Check between HEAD and commit 64cf63d7 using the provided toolchain:" - echo >&2 " xcrun --toolchain org.swift.5120190702a $0 ../some-local-repo HEAD 64cf63d7" -} - -if [[ $# -lt 3 ]]; then - usage - exit 1 -fi - -tmpdir=$(mktemp -d /tmp/.check-api_XXXXXX) -repo_url=$1 -new_tag=$2 -shift 2 - -repodir="$tmpdir/repo" -git clone "$repo_url" "$repodir" -git -C "$repodir" fetch -q origin '+refs/pull/*:refs/remotes/origin/pr/*' -cd "$repodir" -git checkout -q "$new_tag" - -for old_tag in "$@"; do - echo "Checking public API breakages from $old_tag to $new_tag" - - swift package diagnose-api-breaking-changes "$old_tag" -done - -echo done diff --git a/scripts/generate_rediscommandencoder_multi_encode.sh b/scripts/generate_rediscommandencoder_multi_encode.sh index 4fa0b90c..7176d095 100755 --- a/scripts/generate_rediscommandencoder_multi_encode.sh +++ b/scripts/generate_rediscommandencoder_multi_encode.sh @@ -26,20 +26,20 @@ function genWithContextParameter() { echo " @inlinable" echo -n " mutating func encodeRESPArray(_ t0: T0" - for ((n = 1; n<$how_many; n +=1)); do - echo -n ", _ t$(($n)): T$(($n))" + for ((n = 1; n Checking for unacceptable language... " -# This greps for unacceptable terminology. The square bracket[s] are so that -# "git grep" doesn't find the lines that greps :). -unacceptable_terms=( - -e blacklis[t] - -e whitelis[t] - -e slav[e] - -e sanit[y] -) -if git grep --color=never -i "${unacceptable_terms[@]}" > /dev/null; then - printf "\033[0;31mUnacceptable language found.\033[0m\n" - git grep -i "${unacceptable_terms[@]}" - exit 1 -fi -printf "\033[0;32mokay.\033[0m\n" - -#printf "=> Checking format... " -#FIRST_OUT="$(git status --porcelain)" -#swiftformat . > /dev/null 2>&1 -#SECOND_OUT="$(git status --porcelain)" -#if [[ "$FIRST_OUT" != "$SECOND_OUT" ]]; then -# printf "\033[0;31mformatting issues!\033[0m\n" -# git --no-pager diff -# exit 1 -#else -# printf "\033[0;32mokay.\033[0m\n" -#fi - -printf "=> Checking license headers\n" -tmp=$(mktemp /tmp/.redistack-soundness_XXXXXX) - -for language in swift-or-c bash dtrace; do - printf " * $language... " - declare -a matching_files - declare -a exceptions - expections=( ) - matching_files=( -name '*' ) - case "$language" in - swift-or-c) - exceptions=( -name Package.swift -o -name 'Package@swift*.swift' ) - matching_files=( -name '*.swift' -o -name '*.c' -o -name '*.h' ) - cat > "$tmp" <<"EOF" -//===----------------------------------------------------------------------===// -// -// This source file is part of the RediStack open source project -// -// Copyright (c) YEARS RediStack project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of RediStack project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// -EOF - ;; - bash) - matching_files=( -name '*.sh' ) - cat > "$tmp" <<"EOF" -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## This source file is part of the RediStack open source project -## -## Copyright (c) YEARS RediStack project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of RediStack project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## -EOF - ;; - dtrace) - matching_files=( -name '*.d' ) - cat > "$tmp" <<"EOF" -#!/usr/sbin/dtrace -q -s -/*===----------------------------------------------------------------------===* - * - * This source file is part of the RediStack open source project - * - * Copyright (c) YEARS RediStack project authors - * Licensed under Apache License v2.0 - * - * See LICENSE.txt for license information - * See CONTRIBUTORS.txt for the list of RediStack project authors - * - * SPDX-License-Identifier: Apache-2.0 - * - *===----------------------------------------------------------------------===*/ -EOF - ;; - *) - echo >&2 "ERROR: unknown language '$language'" - ;; - esac - - expected_lines=$(cat "$tmp" | wc -l) - expected_sha=$(cat "$tmp" | shasum) - - ( - cd "$here/.." - find . \ - \( \! -path './.build/*' -a \ - \( "${matching_files[@]}" \) -a \ - \( \! \( "${exceptions[@]}" \) \) \) | while read line; do - if [[ "$(cat "$line" | replace_acceptable_years | head -n $expected_lines | shasum)" != "$expected_sha" ]]; then - printf "\033[0;31mmissing headers in file '$line'!\033[0m\n" - diff -u <(cat "$line" | replace_acceptable_years | head -n $expected_lines) "$tmp" - exit 1 - fi - done - printf "\033[0;32mokay.\033[0m\n" - ) -done - -rm "$tmp" - -# This checks for the umbrella NIO module. -printf "=> Checking for imports of umbrella NIO module... " -if git grep --color=never -i "^[ \t]*import \+NIO[ \t]*$" > /dev/null; then - printf "\033[0;31mUmbrella imports found.\033[0m\n" - git grep -i "^[ \t]*import \+NIO[ \t]*$" - exit 1 -fi -printf "\033[0;32mokay.\033[0m\n"