diff --git a/.swift-format.json b/.swift-format.json new file mode 100644 index 000000000..42142705e --- /dev/null +++ b/.swift-format.json @@ -0,0 +1,70 @@ +{ + "fileScopedDeclarationPrivacy" : { + "accessLevel" : "private" + }, + "indentConditionalCompilationBlocks" : true, + "indentSwitchCaseLabels" : true, + "indentation" : { + "spaces" : 4 + }, + "lineBreakAroundMultilineExpressionChainComponents" : false, + "lineBreakBeforeControlFlowKeywords" : false, + "lineBreakBeforeEachArgument" : false, + "lineBreakBeforeEachGenericRequirement" : false, + "lineLength" : 100, + "maximumBlankLines" : 1, + "multiElementCollectionTrailingCommas" : true, + "noAssignmentInExpressions" : { + "allowedFunctions" : [ + "XCTAssertNoThrow" + ] + }, + "prioritizeKeepingFunctionOutputTogether" : false, + "respectsExistingLineBreaks" : true, + "rules" : { + "AllPublicDeclarationsHaveDocumentation" : false, + "AlwaysUseLiteralForEmptyCollectionInit" : false, + "AlwaysUseLowerCamelCase" : true, + "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, + "NoPlaygroundLiterals" : true, + "NoVoidReturnOnFunctionSignature" : true, + "OmitExplicitReturns" : false, + "OneCasePerLine" : true, + "OneVariableDeclarationPerLine" : true, + "OnlyOneTrailingClosureArgument" : true, + "OrderedImports" : true, + "ReplaceForEachWithForLoop" : true, + "ReturnVoidInsteadOfEmptyTuple" : true, + "TypeNamesShouldBeCapitalized" : true, + "UseEarlyExits" : false, + "UseExplicitNilCheckInConditions" : true, + "UseLetInEveryBoundCaseVariable" : true, + "UseShorthandTypeNames" : true, + "UseSingleLinePropertyGetter" : true, + "UseSynthesizedInitializer" : true, + "UseTripleSlashForDocumentationComments" : true, + "UseWhereClausesInForLoops" : false, + "ValidateDocumentationComments" : false + }, + "spacesAroundRangeFormationOperators" : false, + "tabWidth" : 8, + "version" : 1 + } \ No newline at end of file diff --git a/Package.swift b/Package.swift index c50f30f90..1d125e3f8 100644 --- a/Package.swift +++ b/Package.swift @@ -13,16 +13,13 @@ let package = Package(name: "Afluent", .package(url: "https://github.com/apple/swift-atomics.git", from: "1.2.0"), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), .package(url: "https://github.com/AliSoftware/OHHTTPStubs.git", from: "9.1.0"), - .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.52.11"), .package(url: "https://github.com/pointfreeco/swift-clocks.git", from: "1.0.2"), .package(url: "https://github.com/pointfreeco/swift-concurrency-extras.git", from: "1.1.0"), - .package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.0.0"), ], targets: [ .target(name: "Afluent", dependencies: [ .product(name: "Atomics", package: "swift-atomics"), - .product(name: "AsyncAlgorithms", package: "swift-async-algorithms"), ], swiftSettings: [ .enableExperimentalFeature("StrictConcurrency=complete"), diff --git a/Package@swift-5.10.swift b/Package@swift-5.10.swift index 5609ab71f..c29173498 100644 --- a/Package@swift-5.10.swift +++ b/Package@swift-5.10.swift @@ -13,17 +13,14 @@ let package = Package(name: "Afluent", .package(url: "https://github.com/apple/swift-atomics.git", from: "1.2.0"), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), .package(url: "https://github.com/AliSoftware/OHHTTPStubs.git", from: "9.1.0"), - .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.52.11"), .package(url: "https://github.com/pointfreeco/swift-clocks.git", from: "1.0.2"), .package(url: "https://github.com/pointfreeco/swift-concurrency-extras.git", from: "1.1.0"), .package(url: "https://github.com/apple/swift-testing.git", from: "0.7.0"), - .package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.0.0"), ], targets: [ .target(name: "Afluent", dependencies: [ .product(name: "Atomics", package: "swift-atomics"), - .product(name: "AsyncAlgorithms", package: "swift-async-algorithms"), ], swiftSettings: [ .enableExperimentalFeature("StrictConcurrency=complete"), diff --git a/Package@swift-5.8.swift b/Package@swift-5.8.swift index 7834bcbdb..11073a553 100644 --- a/Package@swift-5.8.swift +++ b/Package@swift-5.8.swift @@ -13,16 +13,13 @@ let package = Package(name: "Afluent", .package(url: "https://github.com/apple/swift-atomics.git", from: "1.2.0"), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), .package(url: "https://github.com/AliSoftware/OHHTTPStubs.git", from: "9.1.0"), - .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.52.11"), .package(url: "https://github.com/pointfreeco/swift-clocks.git", from: "1.0.2"), .package(url: "https://github.com/pointfreeco/swift-concurrency-extras.git", from: "1.1.0"), - .package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.0.0"), ], targets: [ .target(name: "Afluent", dependencies: [ .product(name: "Atomics", package: "swift-atomics"), - .product(name: "AsyncAlgorithms", package: "swift-async-algorithms"), ], swiftSettings: [ .enableExperimentalFeature("StrictConcurrency=complete"), diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift index 7796d8ab3..4900d3289 100644 --- a/Package@swift-5.9.swift +++ b/Package@swift-5.9.swift @@ -13,16 +13,13 @@ let package = Package(name: "Afluent", .package(url: "https://github.com/apple/swift-atomics.git", from: "1.2.0"), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), .package(url: "https://github.com/AliSoftware/OHHTTPStubs.git", from: "9.1.0"), - .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.52.11"), .package(url: "https://github.com/pointfreeco/swift-clocks.git", from: "1.0.2"), .package(url: "https://github.com/pointfreeco/swift-concurrency-extras.git", from: "1.1.0"), - .package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.0.0"), ], targets: [ .target(name: "Afluent", dependencies: [ .product(name: "Atomics", package: "swift-atomics"), - .product(name: "AsyncAlgorithms", package: "swift-async-algorithms"), ], swiftSettings: [ .enableExperimentalFeature("StrictConcurrency=complete"), diff --git a/Sources/Afluent/Additions/QueueExecutor.swift b/Sources/Afluent/Additions/QueueExecutor.swift index d89dc2a73..89147bc5d 100644 --- a/Sources/Afluent/Additions/QueueExecutor.swift +++ b/Sources/Afluent/Additions/QueueExecutor.swift @@ -8,69 +8,76 @@ import Foundation #if swift(>=6) -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public final class QueueExecutor: TaskExecutor, Sendable, CustomStringConvertible { - let queue: DispatchQueue + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + public final class QueueExecutor: TaskExecutor, Sendable, CustomStringConvertible { + let queue: DispatchQueue - init(queue: DispatchQueue) { - self.queue = queue - } - - public func enqueue(_ job: consuming ExecutorJob) { - let job = UnownedJob(job) - queue.async { - job.runSynchronously(on: self.asUnownedTaskExecutor()) + init(queue: DispatchQueue) { + self.queue = queue } - } - public var description: String { - "\(Self.self)\(ObjectIdentifier(self))" - } + public func enqueue(_ job: consuming ExecutorJob) { + let job = UnownedJob(job) + queue.async { + job.runSynchronously(on: self.asUnownedTaskExecutor()) + } + } - fileprivate static let main = QueueExecutor(queue: .main) - fileprivate static let globalBackground = QueueExecutor(queue: .global(qos: .background)) - fileprivate static let globalUtility = QueueExecutor(queue: .global(qos: .utility)) - fileprivate static let globalDefault = QueueExecutor(queue: .global(qos: .default)) - fileprivate static let globalUserInitiated = QueueExecutor(queue: .global(qos: .userInitiated)) - fileprivate static let globalUserInteractive = QueueExecutor(queue: .global(qos: .userInteractive)) - fileprivate static let globalUnspecified = QueueExecutor(queue: .global(qos: .unspecified)) -} + public var description: String { + "\(Self.self)\(ObjectIdentifier(self))" + } -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension TaskExecutor where Self == QueueExecutor { - public static var mainQueue: QueueExecutor { - QueueExecutor.main + fileprivate static let main = QueueExecutor(queue: .main) + fileprivate static let globalBackground = QueueExecutor(queue: .global(qos: .background)) + fileprivate static let globalUtility = QueueExecutor(queue: .global(qos: .utility)) + fileprivate static let globalDefault = QueueExecutor(queue: .global(qos: .default)) + fileprivate static let globalUserInitiated = QueueExecutor( + queue: .global(qos: .userInitiated)) + fileprivate static let globalUserInteractive = QueueExecutor( + queue: .global(qos: .userInteractive)) + fileprivate static let globalUnspecified = QueueExecutor(queue: .global(qos: .unspecified)) } - public static func globalQueue(qos: DispatchQoS.QoSClass = .default) -> QueueExecutor { - switch qos { - case .background: - return .globalBackground - case .utility: - return .globalUtility - case .default: - return .globalDefault - case .userInitiated: - return .globalUserInitiated - case .userInteractive: - return .globalUserInteractive - case .unspecified: - return .globalUnspecified - @unknown default: - return .init(queue: .global(qos: qos)) + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + extension TaskExecutor where Self == QueueExecutor { + public static var mainQueue: QueueExecutor { + QueueExecutor.main } - } - public static func queue(label: String, - qos: DispatchQoS = .unspecified, - attributes: DispatchQueue.Attributes = [], - autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit, - target: DispatchQueue? = nil) -> QueueExecutor { - queue(DispatchQueue(label: label, qos: qos, attributes: attributes, autoreleaseFrequency: autoreleaseFrequency, target: target)) - } + public static func globalQueue(qos: DispatchQoS.QoSClass = .default) -> QueueExecutor { + switch qos { + case .background: + return .globalBackground + case .utility: + return .globalUtility + case .default: + return .globalDefault + case .userInitiated: + return .globalUserInitiated + case .userInteractive: + return .globalUserInteractive + case .unspecified: + return .globalUnspecified + @unknown default: + return .init(queue: .global(qos: qos)) + } + } - public static func queue(_ queue: DispatchQueue) -> QueueExecutor { - QueueExecutor(queue: queue) + public static func queue( + label: String, + qos: DispatchQoS = .unspecified, + attributes: DispatchQueue.Attributes = [], + autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit, + target: DispatchQueue? = nil + ) -> QueueExecutor { + queue( + DispatchQueue( + label: label, qos: qos, attributes: attributes, + autoreleaseFrequency: autoreleaseFrequency, target: target)) + } + + public static func queue(_ queue: DispatchQueue) -> QueueExecutor { + QueueExecutor(queue: queue) + } } -} #endif diff --git a/Sources/Afluent/Additions/URLSessionAdditions.swift b/Sources/Afluent/Additions/URLSessionAdditions.swift index 034e79158..bc9febf91 100644 --- a/Sources/Afluent/Additions/URLSessionAdditions.swift +++ b/Sources/Afluent/Additions/URLSessionAdditions.swift @@ -1,23 +1,27 @@ #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(visionOS) -// + // // URLSessionAdditions.swift -// -// + // + // // Created by Tyler Thompson on 10/29/23. -// + // import Foundation extension URLSession { /// Returns a deferred data task that wraps a URL session data task for a given URL. - public func deferredDataTask(from url: URL) -> some AsynchronousUnitOfWork<(data: Data, response: URLResponse)> { + public func deferredDataTask(from url: URL) -> some AsynchronousUnitOfWork< + (data: Data, response: URLResponse) + > { DeferredTask { try await data(from: url) } } /// Returns a deferred data task that wraps a URL session data task for a given URL. - public func deferredDataTask(for urlRequest: URLRequest) -> some AsynchronousUnitOfWork<(data: Data, response: URLResponse)> { + public func deferredDataTask(for urlRequest: URLRequest) -> some AsynchronousUnitOfWork< + (data: Data, response: URLResponse) + > { DeferredTask { try await data(for: urlRequest) } diff --git a/Sources/Afluent/AsyncSequenceCache.swift b/Sources/Afluent/AsyncSequenceCache.swift new file mode 100644 index 000000000..57b7f8b5b --- /dev/null +++ b/Sources/Afluent/AsyncSequenceCache.swift @@ -0,0 +1,6 @@ +// +// AsyncSequenceCache.swift +// Afluent +// +// Created by Tyler Thompson on 10/13/24. +// diff --git a/Sources/Afluent/AsynchronousUnitOfWorkCache.swift b/Sources/Afluent/AsynchronousUnitOfWorkCache.swift index d2fbdfa64..138309c01 100644 --- a/Sources/Afluent/AsynchronousUnitOfWorkCache.swift +++ b/Sources/Afluent/AsynchronousUnitOfWorkCache.swift @@ -14,9 +14,11 @@ public final class AsynchronousUnitOfWorkCache: @unchecked Sendable { let lock = NSRecursiveLock() var cache = [Int: any AsynchronousUnitOfWork & AnySendableReference]() - public init() { } + public init() {} - func retrieveOrCreate(unitOfWork: A, keyedBy key: Int) -> A { + func retrieveOrCreate( + unitOfWork: A, keyedBy key: Int + ) -> A { lock.lock() if let fromCache: A = cache[key] as? A { lock.unlock() diff --git a/Sources/Afluent/CurrentValueSubject.swift b/Sources/Afluent/CurrentValueSubject.swift index 862f282c4..6b168f5db 100644 --- a/Sources/Afluent/CurrentValueSubject.swift +++ b/Sources/Afluent/CurrentValueSubject.swift @@ -8,7 +8,8 @@ /// A subject that broadcasts its current value and all subsequent values to multiple consumers. /// It can also handle completion events, including normal termination and failure with an error. /// This is an `AsyncSequence` that allows multiple tasks to asynchronously consume values and mimics Combine's CurrentValueSubject. -@_spi(Experimental) public final class CurrentValueSubject: AsyncSequence, @unchecked Sendable { +@_spi(Experimental) +public final class CurrentValueSubject: AsyncSequence, @unchecked Sendable { class State: @unchecked Sendable { private let lock = Lock.allocate() private var _finishedResult: Result? @@ -16,28 +17,32 @@ get { lock.withLock { _finishedResult } } set { lock.withLockVoid { _finishedResult = newValue } } } - + var _value: Element var value: Element { get { lock.withLock { _value } } set { lock.withLockVoid { _value = newValue } } } - + init(_ value: Element) { _value = value } } private let continuation: AsyncThrowingStream.Continuation - private let streamIterator: () -> AsyncBroadcastSequence>.AsyncIterator + private let streamIterator: + () -> AsyncBroadcastSequence>.AsyncIterator private let state: State - + /// The current value of the subject. This property is thread-safe. /// Updating this property will also broadcast the new value to all active consumers. public var value: Element { get { state.value } - set { state.value = newValue; continuation.yield(newValue) } + set { + state.value = newValue + continuation.yield(newValue) + } } - + /// Creates a `CurrentValueSubject` with an initial value. /// - Parameter value: The initial value that will be broadcast to consumers. public init(_ value: Element) { @@ -58,12 +63,15 @@ let state: State private var sentCurrentValue = false - init(upstream: AsyncBroadcastSequence>.AsyncIterator, state: State) { + init( + upstream: AsyncBroadcastSequence>.AsyncIterator, + state: State + ) { self.upstream = upstream self.finished = state.finishedResult self.state = state } - + public mutating func next() async throws -> Element? { guard finished == nil else { return try finished?.get() } guard sentCurrentValue else { @@ -73,37 +81,37 @@ return try await upstream.next() } } - + public func makeAsyncIterator() -> Iterator { .init(upstream: streamIterator(), state: state) } - + /// Sends a new value to all current and future consumers. /// - Parameter element: The new value to broadcast. public func send(_ element: Element) { guard state.finishedResult == nil else { return } value = element } - + /// Sends a value to consumers when the subject's `Element` is `Void`. /// This is useful for signaling purposes rather than data transmission. public func send() where Element == Void { guard state.finishedResult == nil else { return } value = () } - + /// Completes the subject, preventing any further values from being sent. /// Once completed, all current consumers will receive the completion, and no further values can be emitted. /// - Parameter completion: The completion event, either `.finished` or `.failure(Error)`. public func send(completion: Completion) { guard state.finishedResult == nil else { return } switch completion { - case .finished: - defer { state.finishedResult = .success(nil) } - continuation.finish() - case .failure(let error): - defer { state.finishedResult = .failure(error) } - continuation.finish(throwing: error) + case .finished: + defer { state.finishedResult = .success(nil) } + continuation.finish() + case .failure(let error): + defer { state.finishedResult = .failure(error) } + continuation.finish(throwing: error) } } } diff --git a/Sources/Afluent/DeferredTask.swift b/Sources/Afluent/DeferredTask.swift index 2581ee26c..c130cae7f 100644 --- a/Sources/Afluent/DeferredTask.swift +++ b/Sources/Afluent/DeferredTask.swift @@ -22,7 +22,10 @@ public actor DeferredTask: AsynchronousUnitOfWork { /// /// - Parameters: /// - operation: The asynchronous operation that this task will execute. The operation should be a throwing, async closure that returns a value of type `Success`. - public init(@_inheritActorContext @_implicitSelfCapture operation: @Sendable @escaping () async throws -> Success) { + public init( + @_inheritActorContext @_implicitSelfCapture operation: @Sendable @escaping () async throws + -> Success + ) { self.operation = operation } diff --git a/Sources/Afluent/Extensions/ErrorExtensions.swift b/Sources/Afluent/Extensions/ErrorExtensions.swift index 05e8d6dce..d777e3b1e 100644 --- a/Sources/Afluent/Extensions/ErrorExtensions.swift +++ b/Sources/Afluent/Extensions/ErrorExtensions.swift @@ -16,7 +16,7 @@ extension Error { throw self } } - + @discardableResult func throwIf(_ error: E) throws -> Self { if let unwrappedError = (self as? E) { if unwrappedError == error { @@ -25,7 +25,7 @@ extension Error { } return self } - + @discardableResult func throwIf(_ error: E.Type) throws -> Self { if let unwrappedError = (self as? E) { throw unwrappedError diff --git a/Sources/Afluent/Extensions/KeyPathExtensions.swift b/Sources/Afluent/Extensions/KeyPathExtensions.swift index 5fc93cbd5..3cc627aef 100644 --- a/Sources/Afluent/Extensions/KeyPathExtensions.swift +++ b/Sources/Afluent/Extensions/KeyPathExtensions.swift @@ -8,8 +8,8 @@ import Foundation #if swift(<6) - extension KeyPath: @unchecked Sendable where Value: Sendable { } + extension KeyPath: @unchecked Sendable where Value: Sendable {} #else // https://forums.swift.org/t/sendablekeypath/67195 - extension KeyPath: @unchecked @retroactive Sendable where Value: Sendable { } + extension KeyPath: @unchecked @retroactive Sendable where Value: Sendable {} #endif diff --git a/Sources/Afluent/PassthroughSubject.swift b/Sources/Afluent/PassthroughSubject.swift index c34102c88..76e6f72d3 100644 --- a/Sources/Afluent/PassthroughSubject.swift +++ b/Sources/Afluent/PassthroughSubject.swift @@ -9,7 +9,8 @@ /// Unlike `CurrentValueSubject`, `PassthroughSubject` does not retain the most recent value. /// It only sends values as they are emitted, meaning consumers will only receive values that are sent after they start listening. /// This is an `AsyncSequence` that allows multiple tasks to asynchronously consume values and mimics Combine's PassthroughSubject. -@_spi(Experimental) public final class PassthroughSubject: AsyncSequence, @unchecked Sendable { +@_spi(Experimental) +public final class PassthroughSubject: AsyncSequence, @unchecked Sendable { private class State: @unchecked Sendable { private let lock = Lock.allocate() private var _finishedResult: Result? @@ -19,9 +20,10 @@ } } private let continuation: AsyncThrowingStream.Continuation - private let streamIterator: () -> AsyncBroadcastSequence>.AsyncIterator + private let streamIterator: + () -> AsyncBroadcastSequence>.AsyncIterator private let state = State() - + /// Creates a `PassthroughSubject`. public init() { let (s, c) = AsyncThrowingStream.makeStream() @@ -29,47 +31,47 @@ let shared = s.share() streamIterator = { shared.makeAsyncIterator() } } - + public struct Iterator: AsyncIteratorProtocol, @unchecked Sendable { var upstream: AsyncBroadcastSequence>.AsyncIterator let finished: Result? - + public mutating func next() async throws -> Element? { guard finished == nil else { return try finished?.get() } return try await upstream.next() } } - + public func makeAsyncIterator() -> Iterator { .init(upstream: streamIterator(), finished: state.finishedResult) } - + /// Sends a new value to all current and future consumers. /// - Parameter element: The new value to broadcast. public func send(_ element: Element) { guard state.finishedResult == nil else { return } continuation.yield(element) } - + /// Sends a value to consumers when the subject's `Element` is `Void`. /// This is useful for signaling purposes rather than data transmission. public func send() where Element == Void { guard state.finishedResult == nil else { return } continuation.yield() } - + /// Completes the subject, preventing any further values from being sent. /// Once completed, all current consumers will receive the completion, and no further values can be emitted. /// - Parameter completion: The completion event, either `.finished` or `.failure(Error)`. public func send(completion: Completion) { guard state.finishedResult == nil else { return } switch completion { - case .finished: - defer { state.finishedResult = .success(nil) } - continuation.finish() - case .failure(let error): - defer { state.finishedResult = .failure(error) } - continuation.finish(throwing: error) + case .finished: + defer { state.finishedResult = .success(nil) } + continuation.finish() + case .failure(let error): + defer { state.finishedResult = .failure(error) } + continuation.finish(throwing: error) } } } diff --git a/Sources/Afluent/Protocols/AsynchronousUnitOfWork.swift b/Sources/Afluent/Protocols/AsynchronousUnitOfWork.swift index 816723db6..f7848564e 100644 --- a/Sources/Afluent/Protocols/AsynchronousUnitOfWork.swift +++ b/Sources/Afluent/Protocols/AsynchronousUnitOfWork.swift @@ -21,16 +21,18 @@ public protocol AsynchronousUnitOfWork: Sendable where Success: Sendabl /// - Returns: The result of the task. @discardableResult func execute(priority: TaskPriority?) async throws -> Success -#if swift(>=6) - /// Executes the task with an optional task executor and priority. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - func run(executorPreference taskExecutor: (any TaskExecutor)?, priority: TaskPriority?) - - /// Executes the task with an optional task executor and priority and waits for the result. - /// - Returns: The result of the task. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - @discardableResult func execute(executorPreference taskExecutor: (any TaskExecutor)?, priority: TaskPriority?) async throws -> Success -#endif + #if swift(>=6) + /// Executes the task with an optional task executor and priority. + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + func run(executorPreference taskExecutor: (any TaskExecutor)?, priority: TaskPriority?) + + /// Executes the task with an optional task executor and priority and waits for the result. + /// - Returns: The result of the task. + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + @discardableResult func execute( + executorPreference taskExecutor: (any TaskExecutor)?, priority: TaskPriority? + ) async throws -> Success + #endif /// Only useful when creating operators, defines the async function that should execute when the operator executes @Sendable func _operation() async throws -> AsynchronousOperation @@ -62,25 +64,32 @@ extension AsynchronousUnitOfWork { } } -#if swift(>=6) - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public func run(executorPreference taskExecutor: (any TaskExecutor)?, priority: TaskPriority? = nil) { - state.createTask(taskExecutor: taskExecutor, - priority: priority, - operation: operation) - } + #if swift(>=6) + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + public func run( + executorPreference taskExecutor: (any TaskExecutor)?, priority: TaskPriority? = nil + ) { + state.createTask( + taskExecutor: taskExecutor, + priority: priority, + operation: operation) + } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - @discardableResult public func execute(executorPreference taskExecutor: (any TaskExecutor)?, priority: TaskPriority? = nil) async throws -> Success { - try await withTaskCancellationHandler { - try await state.createTask(taskExecutor: taskExecutor, - priority: priority, - operation: operation).value - } onCancel: { - cancel() + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + @discardableResult public func execute( + executorPreference taskExecutor: (any TaskExecutor)?, priority: TaskPriority? = nil + ) async throws -> Success { + try await withTaskCancellationHandler { + try await state.createTask( + taskExecutor: taskExecutor, + priority: priority, + operation: operation + ).value + } onCancel: { + cancel() + } } - } -#endif + #endif public func cancel() { state.cancel() @@ -116,28 +125,34 @@ public final class TaskState: @unchecked Sendable { _isCancelled.load(ordering: .sequentiallyConsistent) } - public init() { } - -#if swift(>=6) - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - @discardableResult func createTask(taskExecutor: (any TaskExecutor)?, - priority: TaskPriority?, - operation: @Sendable @escaping () async throws -> Success) -> Task { - guard !isCancelled else { - let task = Task { throw CancellationError() } - task.cancel() + public init() {} + + #if swift(>=6) + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + @discardableResult func createTask( + taskExecutor: (any TaskExecutor)?, + priority: TaskPriority?, + operation: @Sendable @escaping () async throws -> Success + ) -> Task { + guard !isCancelled else { + let task = Task { throw CancellationError() } + task.cancel() + return task + } + let task = Task(executorPreference: taskExecutor, priority: priority) { + try await operation() + } + lock.protect { + tasks.append(task) + } return task } - let task = Task(executorPreference: taskExecutor, priority: priority) { try await operation() } - lock.protect { - tasks.append(task) - } - return task - } -#endif + #endif - @discardableResult func createTask(priority: TaskPriority?, - operation: @Sendable @escaping () async throws -> Success) -> Task { + @discardableResult func createTask( + priority: TaskPriority?, + operation: @Sendable @escaping () async throws -> Success + ) -> Task { guard !isCancelled else { let task = Task { throw CancellationError() } task.cancel() diff --git a/Sources/Afluent/Race.swift b/Sources/Afluent/Race.swift index d6c6d0cb9..79c7a198d 100644 --- a/Sources/Afluent/Race.swift +++ b/Sources/Afluent/Race.swift @@ -45,7 +45,10 @@ /// /// ## Important /// A thrown error is considered to have won the race. Additionally, task groups don't guarantee parallelism, they guarantee concurrency. Consequently, while this is useful for lots of real world scenarios if you have strict parallelism requirements you may need to reach for GCD. [more information](https://forums.swift.org/t/taskgroup-and-parallelism/51039/1) -public func Race(cancelAllOnWin: Bool = true, _ firstTask: @Sendable () async throws -> T, against tasks: (@Sendable () async throws -> T)...) async throws -> T { +public func Race( + cancelAllOnWin: Bool = true, _ firstTask: @Sendable () async throws -> T, + against tasks: (@Sendable () async throws -> T)... +) async throws -> T { try await withoutActuallyEscaping(firstTask) { firstTask in try await withThrowingTaskGroup(of: T.self) { group in group.addTask(operation: firstTask) diff --git a/Sources/Afluent/Rethrow.swift b/Sources/Afluent/Rethrow.swift index 2a8ae5401..ebbee642c 100644 --- a/Sources/Afluent/Rethrow.swift +++ b/Sources/Afluent/Rethrow.swift @@ -32,10 +32,10 @@ extension _ErrorMechanism { _ = try _rethrowGet() fatalError("materialized error without being in a throwing context") } - + internal func _rethrowGet() rethrows -> Output { return try get() } } -extension Result: _ErrorMechanism { } +extension Result: _ErrorMechanism {} diff --git a/Sources/Afluent/RetryStrategies/RetryByBackoffStrategy.swift b/Sources/Afluent/RetryStrategies/RetryByBackoffStrategy.swift index efa42e5d2..ec7f7faf2 100644 --- a/Sources/Afluent/RetryStrategies/RetryByBackoffStrategy.swift +++ b/Sources/Afluent/RetryStrategies/RetryByBackoffStrategy.swift @@ -11,14 +11,17 @@ import Foundation public typealias ClockDurationUnit = @Sendable (T) -> C.Duration @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension RetryStrategy where Self == RetryByBackoffStrategy> { +extension RetryStrategy +where Self == RetryByBackoffStrategy> { /// Creates a retry strategy using the provided backoff strategy and a continuous clock. /// /// This extension provides a convenient method to create a `RetryByBackoffStrategy` using a `ContinuousClock`. /// /// - Parameter strategy: The backoff strategy to use for retrying operations. /// - Returns: A `RetryByBackoffStrategy` configured with the provided `BackoffStrategy` and a `ContinuousClock`. - public static func backoff(_ strategy: ExponentialBackoffStrategy) -> RetryByBackoffStrategy> { + public static func backoff(_ strategy: ExponentialBackoffStrategy) + -> RetryByBackoffStrategy> + { RetryByBackoffStrategy(strategy, clock: ContinuousClock(), durationUnit: Duration.seconds) } } @@ -43,13 +46,18 @@ public actor RetryByBackoffStrategy: RetryStrategy { /// - Parameters: /// - strategy: The backoff strategy used to determine how to back off between retries. /// - clock: The clock used to measure the time between retries. - public init(_ strategy: Strategy, clock: Strategy.Clock, durationUnit: @escaping ClockDurationUnit) { + public init( + _ strategy: Strategy, clock: Strategy.Clock, + durationUnit: @escaping ClockDurationUnit + ) { self.strategy = strategy self.clock = clock self.durationUnit = durationUnit } - public func handle(error err: Error, beforeRetry: @Sendable (Error) async throws -> Void) async throws -> Bool { + public func handle(error err: Error, beforeRetry: @Sendable (Error) async throws -> Void) + async throws -> Bool + { try await strategy.backoff(clock: clock, durationUnit: durationUnit) } } @@ -72,7 +80,8 @@ public protocol BackoffStrategy: Sendable where Clock: _Concurrency.Clock /// /// - Returns: A Boolean value indicating whether a retry should be attempted (`true`) or not (`false`). /// - Throws: Any error encountered during the backoff process. - func backoff(clock: Clock, durationUnit: ClockDurationUnit) async throws -> Bool + func backoff(clock: Clock, durationUnit: ClockDurationUnit) + async throws -> Bool } @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) @@ -84,10 +93,12 @@ extension BackoffStrategy where Self == ExponentialBackoffStrategy ExponentialBackoffStrategy { + public static func exponential( + base: UInt, maxCount: UInt, maxDelay: ContinuousClock.Duration = .seconds(Int.max) + ) -> ExponentialBackoffStrategy { ExponentialBackoffStrategy(base: base, maxCount: maxCount, maxDelay: maxDelay) } - + /// Creates a binary exponential backoff strategy with a maximum retry count. /// /// The base duration for this strategy is set to 2, meaning the delay will double with each retry. @@ -96,7 +107,9 @@ extension BackoffStrategy where Self == ExponentialBackoffStrategy ExponentialBackoffStrategy { + public static func binaryExponential( + maxCount: UInt, maxDelay: ContinuousClock.Duration = .seconds(Int.max) + ) -> ExponentialBackoffStrategy { ExponentialBackoffStrategy(base: 2, maxCount: maxCount, maxDelay: maxDelay) } } @@ -111,7 +124,7 @@ public actor ExponentialBackoffStrategy: BackoffStrat var count = 1 let maxCount: UInt let maxDelay: Clock.Duration - + /// Creates a new exponential backoff strategy with the given base and maximum retry count. /// /// - Parameters: @@ -123,20 +136,23 @@ public actor ExponentialBackoffStrategy: BackoffStrat self.maxCount = maxCount self.maxDelay = maxDelay } - + /// Creates a new exponential backoff strategy with the given base and maximum retry count. /// /// - Parameters: /// - base: The base duration for the backoff, which will increase exponentially with each retry. /// - maxCount: The maximum number of retries allowed. /// - maxDelay: The maximum duration to wait. - public init(base: UInt, maxCount: UInt, maxDelay: Clock.Duration = .seconds(Int.max)) where Clock.Duration == Duration { + public init(base: UInt, maxCount: UInt, maxDelay: Clock.Duration = .seconds(Int.max)) + where Clock.Duration == Duration { self.base = base self.maxCount = maxCount self.maxDelay = maxDelay } - - public func backoff(clock: Clock, durationUnit: ClockDurationUnit) async throws -> Bool { + + public func backoff(clock: Clock, durationUnit: ClockDurationUnit) + async throws -> Bool + { guard count < maxCount else { return false } try await clock.sleep(for: min(durationUnit(T(pow(Double(base), Double(count)))), maxDelay)) count += 1 diff --git a/Sources/Afluent/RetryStrategies/RetryByCountStrategy.swift b/Sources/Afluent/RetryStrategies/RetryByCountStrategy.swift index 3848e9c9a..a2215e1a5 100644 --- a/Sources/Afluent/RetryStrategies/RetryByCountStrategy.swift +++ b/Sources/Afluent/RetryStrategies/RetryByCountStrategy.swift @@ -25,7 +25,7 @@ extension RetryStrategy where Self == RetryByCountStrategy { public actor RetryByCountStrategy: RetryStrategy { /// The number of retries remaining. var retryCount: UInt - + /// Creates a new `RetryByCountStrategy` with the specified retry count. /// /// - Parameter retryCount: The maximum number of retries allowed. @@ -33,16 +33,18 @@ public actor RetryByCountStrategy: RetryStrategy { self.retryCount = retryCount } - public func handle(error err: Error, beforeRetry: @Sendable (Error) async throws -> Void) async throws -> Bool { + public func handle(error err: Error, beforeRetry: @Sendable (Error) async throws -> Void) + async throws -> Bool + { guard retryCount > 0 else { return false } - + try await beforeRetry(err) decrementRetry() return true } - + func decrementRetry() { guard retryCount > 0 else { return } retryCount -= 1 diff --git a/Sources/Afluent/RetryStrategies/RetryStrategy.swift b/Sources/Afluent/RetryStrategies/RetryStrategy.swift index 53a39f0bb..84ef93f77 100644 --- a/Sources/Afluent/RetryStrategies/RetryStrategy.swift +++ b/Sources/Afluent/RetryStrategies/RetryStrategy.swift @@ -22,7 +22,8 @@ public protocol RetryStrategy: Sendable { /// - Returns: A Boolean value indicating whether a retry should be attempted (`true`) or not (`false`). /// /// - Throws: An error if either the retry strategy itself fails or if the `beforeRetry` closure encounters an error. - func handle(error: Error, beforeRetry: @Sendable (Error) async throws -> Void) async throws -> Bool + func handle(error: Error, beforeRetry: @Sendable (Error) async throws -> Void) async throws + -> Bool } extension RetryStrategy { diff --git a/Sources/Afluent/SequenceOperators/AnyAsyncSequence.swift b/Sources/Afluent/SequenceOperators/AnyAsyncSequence.swift index e88d4db74..24a93f005 100644 --- a/Sources/Afluent/SequenceOperators/AnyAsyncSequence.swift +++ b/Sources/Afluent/SequenceOperators/AnyAsyncSequence.swift @@ -12,7 +12,8 @@ extension AsyncSequences { public struct AnyAsyncSequence: AsyncSequence, Sendable { let makeIterator: @Sendable () -> AnyAsyncIterator - public init(erasing sequence: S) where S.Element == Element, S.Element: Sendable { + public init(erasing sequence: S) + where S.Element == Element, S.Element: Sendable { makeIterator = { AnyAsyncIterator(erasing: sequence.makeAsyncIterator()) } } @@ -30,7 +31,8 @@ extension AsyncSequences { lock.lock() defer { lock.unlock() } return _iterator - } set { + } + set { lock.lock() defer { lock.unlock() } _iterator = newValue diff --git a/Sources/Afluent/SequenceOperators/AsyncSequences.swift b/Sources/Afluent/SequenceOperators/AsyncSequences.swift index ad8bde4a4..e00108c68 100644 --- a/Sources/Afluent/SequenceOperators/AsyncSequences.swift +++ b/Sources/Afluent/SequenceOperators/AsyncSequences.swift @@ -11,4 +11,4 @@ import Foundation /// /// The `AsyncSequences` enum itself doesn't contain values, serving solely as a container for nested types and functionalities to keep them organized. /// For example, it might contain static methods, nested types, or enums that deal with specific aspects of asynchronous work. -public enum AsyncSequences { } +public enum AsyncSequences {} diff --git a/Sources/Afluent/SequenceOperators/BreakpointSequence.swift b/Sources/Afluent/SequenceOperators/BreakpointSequence.swift index b76a0f81d..cab099e25 100644 --- a/Sources/Afluent/SequenceOperators/BreakpointSequence.swift +++ b/Sources/Afluent/SequenceOperators/BreakpointSequence.swift @@ -18,16 +18,21 @@ extension AsyncSequence where Self: Sendable { /// - receiveError: A closure that takes any error produced by the sequence. If this closure returns `true`, a breakpoint is triggered. Default is `nil`. /// /// - Returns: An asynchronous unit of work with the breakpoint conditions applied. - @_transparent @_alwaysEmitIntoClient @inlinable public func breakpoint(receiveOutput: (@Sendable (Element) async throws -> Bool)? = nil, receiveError: (@Sendable (Error) async throws -> Bool)? = nil) -> AsyncSequences.HandleEvents { - handleEvents(receiveOutput: { output in - if try await receiveOutput?(output) == true { - raise(SIGTRAP) - } - }, receiveError: { error in - if try await receiveError?(error) == true { - raise(SIGTRAP) - } - }) + @_transparent @_alwaysEmitIntoClient @inlinable public func breakpoint( + receiveOutput: (@Sendable (Element) async throws -> Bool)? = nil, + receiveError: (@Sendable (Error) async throws -> Bool)? = nil + ) -> AsyncSequences.HandleEvents { + handleEvents( + receiveOutput: { output in + if try await receiveOutput?(output) == true { + raise(SIGTRAP) + } + }, + receiveError: { error in + if try await receiveError?(error) == true { + raise(SIGTRAP) + } + }) } /// Introduces a breakpoint into the async sequence when an error occurs. @@ -35,7 +40,9 @@ extension AsyncSequence where Self: Sendable { /// This function triggers a `SIGTRAP` signal, pausing execution in a debugger, whenever the async sequence produces an error. /// /// - Returns: An `AsyncSequence` with the breakpoint-on-error condition applied. - @_transparent @_alwaysEmitIntoClient @inlinable public func breakpointOnError() -> AsyncSequences.HandleEvents { + @_transparent @_alwaysEmitIntoClient @inlinable public func breakpointOnError() + -> AsyncSequences.HandleEvents + { breakpoint(receiveError: { _ in true }) } } diff --git a/Sources/Afluent/SequenceOperators/CatchSequence.swift b/Sources/Afluent/SequenceOperators/CatchSequence.swift index e0b4d492a..801df3edc 100644 --- a/Sources/Afluent/SequenceOperators/CatchSequence.swift +++ b/Sources/Afluent/SequenceOperators/CatchSequence.swift @@ -8,12 +8,18 @@ import Foundation extension AsyncSequences { - public struct Catch: AsyncSequence, Sendable where Upstream.Element == Downstream.Element { + public struct Catch: + AsyncSequence, Sendable + where Upstream.Element == Downstream.Element { public typealias Element = Upstream.Element let upstream: Upstream let handler: @Sendable (Error) async throws -> Downstream - init(upstream: Upstream, @_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (Error) async throws -> Downstream) { + init( + upstream: Upstream, + @_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (Error) + async throws -> Downstream + ) { self.upstream = upstream self.handler = handler } @@ -40,8 +46,9 @@ extension AsyncSequences { } public func makeAsyncIterator() -> AsyncIterator { - AsyncIterator(upstreamIterator: upstream.makeAsyncIterator(), - handler: handler) + AsyncIterator( + upstreamIterator: upstream.makeAsyncIterator(), + handler: handler) } } } @@ -53,7 +60,10 @@ extension AsyncSequence where Self: Sendable { /// - handler: A closure that takes an `Error` and returns an `AsyncSequence`. /// /// - Returns: An `AsyncSequence` that will catch and handle any errors emitted by the upstream sequence. - public func `catch`(@_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (Error) async -> D) -> AsyncSequences.Catch { + public func `catch`( + @_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (Error) async -> + D + ) -> AsyncSequences.Catch { AsyncSequences.Catch(upstream: self, handler) } @@ -64,10 +74,14 @@ extension AsyncSequence where Self: Sendable { /// - handler: A closure that takes an `Error` and returns an `AsyncSequence`. /// /// - Returns: An `AsyncSequence` that will catch and handle the specific error. - public func `catch`(_ error: E, @_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (E) async -> D) -> AsyncSequences.Catch { + public func `catch`( + _ error: E, + @_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (E) async -> D + ) -> AsyncSequences.Catch { tryCatch { err in guard let unwrappedError = (err as? E), - unwrappedError == error else { throw err } + unwrappedError == error + else { throw err } return await handler(unwrappedError) } } @@ -78,7 +92,10 @@ extension AsyncSequence where Self: Sendable { /// - handler: A closure that takes an `Error` and returns an `AsyncSequence`, potentially throwing an error. /// /// - Returns: An `AsyncSequence` that will try to catch and handle any errors emitted by the upstream sequence. - public func tryCatch(@_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (Error) async throws -> D) -> AsyncSequences.Catch { + public func tryCatch( + @_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (Error) + async throws -> D + ) -> AsyncSequences.Catch { AsyncSequences.Catch(upstream: self, handler) } @@ -89,10 +106,15 @@ extension AsyncSequence where Self: Sendable { /// - handler: A closure that takes an `Error` and returns an `AsyncSequence`, potentially throwing an error. /// /// - Returns: An `AsyncSequence` that will try to catch and handle the specific error. - public func tryCatch(_ error: E, @_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (E) async throws -> D) -> AsyncSequences.Catch { + public func tryCatch( + _ error: E, + @_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (E) async throws + -> D + ) -> AsyncSequences.Catch { tryCatch { err in guard let unwrappedError = (err as? E), - unwrappedError == error else { throw err } + unwrappedError == error + else { throw err } return try await handler(unwrappedError) } } diff --git a/Sources/Afluent/SequenceOperators/DecodeSequence.swift b/Sources/Afluent/SequenceOperators/DecodeSequence.swift index c297af7a1..a2e018aa7 100644 --- a/Sources/Afluent/SequenceOperators/DecodeSequence.swift +++ b/Sources/Afluent/SequenceOperators/DecodeSequence.swift @@ -8,7 +8,10 @@ import Foundation extension AsyncSequences { - public struct Decode: AsyncSequence, Sendable where Upstream.Element == Decoder.Input { + public struct Decode< + Upstream: AsyncSequence & Sendable, Decoder: TopLevelDecoder, + DecodedType: Decodable & Sendable + >: AsyncSequence, Sendable where Upstream.Element == Decoder.Input { public typealias Element = DecodedType final class State: @unchecked Sendable { @@ -46,15 +49,18 @@ extension AsyncSequences { } public func makeAsyncIterator() -> AsyncIterator { - AsyncIterator(upstreamIterator: upstream.makeAsyncIterator(), - state: state) + AsyncIterator( + upstreamIterator: upstream.makeAsyncIterator(), + state: state) } } } extension AsyncSequence where Self: Sendable { /// Decodes the output from the upstream using a specified decoder. - public func decode(type _: T.Type, decoder: D) -> AsyncSequences.Decode where Element == D.Input { + public func decode(type _: T.Type, decoder: D) + -> AsyncSequences.Decode where Element == D.Input + { AsyncSequences.Decode(upstream: self, decoder: decoder) } } diff --git a/Sources/Afluent/SequenceOperators/Deferred.swift b/Sources/Afluent/SequenceOperators/Deferred.swift index 358c2edd2..18854b672 100644 --- a/Sources/Afluent/SequenceOperators/Deferred.swift +++ b/Sources/Afluent/SequenceOperators/Deferred.swift @@ -67,4 +67,4 @@ extension AsyncSequences { public typealias Deferred = AsyncSequences.Deferred -extension Deferred.AsyncIterator: Sendable where Upstream.AsyncIterator: Sendable { } +extension Deferred.AsyncIterator: Sendable where Upstream.AsyncIterator: Sendable {} diff --git a/Sources/Afluent/SequenceOperators/DelaySequence.swift b/Sources/Afluent/SequenceOperators/DelaySequence.swift index 642943c85..c16d75a14 100644 --- a/Sources/Afluent/SequenceOperators/DelaySequence.swift +++ b/Sources/Afluent/SequenceOperators/DelaySequence.swift @@ -9,7 +9,8 @@ import Foundation extension AsyncSequences { @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, *) - public struct Delay: AsyncSequence, Sendable where Upstream.Element: Sendable { + public struct Delay: AsyncSequence, Sendable + where Upstream.Element: Sendable { public typealias Element = Upstream.Element let upstream: Upstream let interval: C.Duration @@ -56,7 +57,8 @@ extension AsyncSequences { } public func makeAsyncIterator() -> AsyncIterator { - AsyncIterator(upstream: upstream, interval: interval, clock: clock, tolerance: tolerance) + AsyncIterator( + upstream: upstream, interval: interval, clock: clock, tolerance: tolerance) } } } @@ -65,14 +67,18 @@ extension AsyncSequence where Self: Sendable, Element: Sendable { /// Delays delivery of all output to the downstream receiver by a specified amount of time /// - Parameter interval: The amount of time to delay. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, *) - public func delay(for interval: Duration, tolerance: Duration) -> AsyncSequences.Delay { + public func delay(for interval: Duration, tolerance: Duration) + -> AsyncSequences.Delay + { delay(for: interval, tolerance: tolerance, clock: SuspendingClock()) } /// Delays delivery of all output to the downstream receiver by a specified amount of time /// - Parameter interval: The amount of time to delay. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, *) - public func delay(for interval: C.Duration, tolerance: C.Duration? = nil, clock: C) -> AsyncSequences.Delay { + public func delay(for interval: C.Duration, tolerance: C.Duration? = nil, clock: C) + -> AsyncSequences.Delay + { AsyncSequences.Delay(upstream: self, interval: interval, clock: clock, tolerance: tolerance) } } diff --git a/Sources/Afluent/SequenceOperators/DematerializeSequence.swift b/Sources/Afluent/SequenceOperators/DematerializeSequence.swift index efc4e9088..fd36114a6 100644 --- a/Sources/Afluent/SequenceOperators/DematerializeSequence.swift +++ b/Sources/Afluent/SequenceOperators/DematerializeSequence.swift @@ -8,7 +8,9 @@ import Foundation extension AsyncSequences { - public struct Dematerialize: AsyncSequence, Sendable where Upstream.Element == AsyncSequences.Event { + public struct Dematerialize: + AsyncSequence, Sendable + where Upstream.Element == AsyncSequences.Event { let upstream: Upstream public struct AsyncIterator: AsyncIteratorProtocol { @@ -43,7 +45,8 @@ extension AsyncSequence where Self: Sendable { /// /// - Returns: An `AsyncSequences.Dematerialize` instance that represents the original `AsyncSequence` with its elements and errors. /// - Throws: Re-throws any errors that were encapsulated in the `Event.failure` cases. - public func dematerialize() -> AsyncSequences.Dematerialize where Element == AsyncSequences.Event { + public func dematerialize() -> AsyncSequences.Dematerialize + where Element == AsyncSequences.Event { AsyncSequences.Dematerialize(upstream: self) } } diff --git a/Sources/Afluent/SequenceOperators/EncodeSequence.swift b/Sources/Afluent/SequenceOperators/EncodeSequence.swift index 939e9594a..638d41d33 100644 --- a/Sources/Afluent/SequenceOperators/EncodeSequence.swift +++ b/Sources/Afluent/SequenceOperators/EncodeSequence.swift @@ -8,7 +8,9 @@ import Foundation extension AsyncSequences { - public struct Encode: AsyncSequence, Sendable where Upstream.Element: Encodable { + public struct Encode: + AsyncSequence, Sendable + where Upstream.Element: Encodable { public typealias Element = Encoder.Output final class State: @unchecked Sendable { @@ -46,15 +48,17 @@ extension AsyncSequences { } public func makeAsyncIterator() -> AsyncIterator { - AsyncIterator(upstreamIterator: upstream.makeAsyncIterator(), - state: state) + AsyncIterator( + upstreamIterator: upstream.makeAsyncIterator(), + state: state) } } } extension AsyncSequence where Self: Sendable { /// Encodes the output from upstream using a specified encoder. - public func encode(encoder: E) -> AsyncSequences.Encode where Element: Encodable { + public func encode(encoder: E) -> AsyncSequences.Encode + where Element: Encodable { AsyncSequences.Encode(upstream: self, encoder: encoder) } } diff --git a/Sources/Afluent/SequenceOperators/FlatMapSequence.swift b/Sources/Afluent/SequenceOperators/FlatMapSequence.swift index 6e9111afa..49762cd30 100644 --- a/Sources/Afluent/SequenceOperators/FlatMapSequence.swift +++ b/Sources/Afluent/SequenceOperators/FlatMapSequence.swift @@ -9,7 +9,9 @@ import Atomics import Foundation extension AsyncSequences { - public struct FlatMap: AsyncSequence, Sendable where Upstream.Element: Sendable, SegmentOfResult.Element: Sendable { + public struct FlatMap< + Upstream: AsyncSequence & Sendable, SegmentOfResult: AsyncSequence & Sendable + >: AsyncSequence, Sendable where Upstream.Element: Sendable, SegmentOfResult.Element: Sendable { public typealias Element = SegmentOfResult.Element let upstream: Upstream let maxSubscriptons: SubscriptionDemand @@ -26,7 +28,8 @@ extension AsyncSequences { switch maxSubscriptons { case .unlimited: if iterator == nil { - iterator = AsyncThrowingStream { [upstream, transform] continuation in + iterator = AsyncThrowingStream { + [upstream, transform] continuation in Task { [transform] in do { try Task.checkCancellation() @@ -59,14 +62,19 @@ extension AsyncSequences { } public func makeAsyncIterator() -> AsyncIterator { - AsyncIterator(upstream: upstream, maxSubscriptons: maxSubscriptons, transform: transform) + AsyncIterator( + upstream: upstream, maxSubscriptons: maxSubscriptons, transform: transform) } } } extension AsyncSequence { - public func flatMap(maxSubscriptions: SubscriptionDemand, _ transform: @Sendable @escaping (Self.Element) async throws -> SegmentOfResult) -> AsyncSequences.FlatMap { - AsyncSequences.FlatMap(upstream: self, maxSubscriptons: maxSubscriptions, transform: transform) + public func flatMap( + maxSubscriptions: SubscriptionDemand, + _ transform: @Sendable @escaping (Self.Element) async throws -> SegmentOfResult + ) -> AsyncSequences.FlatMap { + AsyncSequences.FlatMap( + upstream: self, maxSubscriptons: maxSubscriptions, transform: transform) } } diff --git a/Sources/Afluent/SequenceOperators/GroupBySequence.swift b/Sources/Afluent/SequenceOperators/GroupBySequence.swift index 72d970f7a..65f8091c2 100644 --- a/Sources/Afluent/SequenceOperators/GroupBySequence.swift +++ b/Sources/Afluent/SequenceOperators/GroupBySequence.swift @@ -8,7 +8,9 @@ import Foundation extension AsyncSequences { - public struct GroupBy: AsyncSequence, Sendable where Upstream.Element: Sendable { + public struct GroupBy: AsyncSequence, + Sendable + where Upstream.Element: Sendable { public typealias Element = (key: Key, stream: AsyncThrowingStream) let upstream: Upstream let keySelector: @Sendable (Upstream.Element) async -> Key @@ -22,7 +24,12 @@ extension AsyncSequences { var upstream: Upstream.AsyncIterator let keySelector: (Upstream.Element) async -> Key - var keyedSequences = [Key: (stream: AsyncThrowingStream, continuation: AsyncThrowingStream.Continuation)]() + var keyedSequences = [ + Key: ( + stream: AsyncThrowingStream, + continuation: AsyncThrowingStream.Continuation + ) + ]() public mutating func next() async throws -> Element? { do { @@ -37,7 +44,8 @@ extension AsyncSequences { existing.continuation.yield(element) return try await next() } else { - let (stream, continuation) = AsyncThrowingStream.makeStream() + let (stream, continuation) = AsyncThrowingStream + .makeStream() keyedSequences[key] = (stream: stream, continuation: continuation) defer { continuation.yield(element) } return (key: key, stream: stream) @@ -56,7 +64,9 @@ extension AsyncSequences { } extension AsyncSequence where Self: Sendable, Element: Sendable { - public func groupBy(keySelector: @Sendable @escaping (Element) async -> Key) -> AsyncSequences.GroupBy { + public func groupBy(keySelector: @Sendable @escaping (Element) async -> Key) + -> AsyncSequences.GroupBy + { AsyncSequences.GroupBy(upstream: self, keySelector: keySelector) } } diff --git a/Sources/Afluent/SequenceOperators/HandleEventsSequence.swift b/Sources/Afluent/SequenceOperators/HandleEventsSequence.swift index 7caa9034b..32cd82991 100644 --- a/Sources/Afluent/SequenceOperators/HandleEventsSequence.swift +++ b/Sources/Afluent/SequenceOperators/HandleEventsSequence.swift @@ -50,12 +50,13 @@ extension AsyncSequences { public func makeAsyncIterator() -> AsyncIterator { receiveMakeIterator?() - return AsyncIterator(upstream: upstream.makeAsyncIterator(), - receiveNext: receiveNext, - receiveOutput: receiveOutput, - receiveError: receiveError, - receiveComplete: receiveComplete, - receiveCancel: receiveCancel) + return AsyncIterator( + upstream: upstream.makeAsyncIterator(), + receiveNext: receiveNext, + receiveOutput: receiveOutput, + receiveError: receiveError, + receiveComplete: receiveComplete, + receiveCancel: receiveCancel) } } } @@ -73,7 +74,27 @@ extension AsyncSequence where Self: Sendable { /// - Returns: An `AsynchronousUnitOfWork` that performs the side-effects for the specified receiving events. /// /// - Note: The returned `AsynchronousUnitOfWork` forwards all receiving events from the upstream unit of work. - public func handleEvents(@_implicitSelfCapture receiveMakeIterator: (@Sendable () -> Void)? = nil, @_inheritActorContext @_implicitSelfCapture receiveNext: (@Sendable () async throws -> Void)? = nil, @_inheritActorContext @_implicitSelfCapture receiveOutput: (@Sendable (Element) async throws -> Void)? = nil, @_inheritActorContext @_implicitSelfCapture receiveError: (@Sendable (Error) async throws -> Void)? = nil, @_inheritActorContext @_implicitSelfCapture receiveComplete: (@Sendable () async throws -> Void)? = nil, @_inheritActorContext @_implicitSelfCapture receiveCancel: (@Sendable () async throws -> Void)? = nil) -> AsyncSequences.HandleEvents { - AsyncSequences.HandleEvents(upstream: self, receiveMakeIterator: receiveMakeIterator, receiveNext: receiveNext, receiveOutput: receiveOutput, receiveError: receiveError, receiveComplete: receiveComplete, receiveCancel: receiveCancel) + public func handleEvents( + @_implicitSelfCapture receiveMakeIterator: (@Sendable () -> Void)? = nil, + @_inheritActorContext @_implicitSelfCapture receiveNext: ( + @Sendable () async throws -> Void + )? = nil, + @_inheritActorContext @_implicitSelfCapture receiveOutput: ( + @Sendable (Element) async throws -> Void + )? = nil, + @_inheritActorContext @_implicitSelfCapture receiveError: ( + @Sendable (Error) async throws -> Void + )? = nil, + @_inheritActorContext @_implicitSelfCapture receiveComplete: ( + @Sendable () async throws -> Void + )? = nil, + @_inheritActorContext @_implicitSelfCapture receiveCancel: ( + @Sendable () async throws -> Void + )? = nil + ) -> AsyncSequences.HandleEvents { + AsyncSequences.HandleEvents( + upstream: self, receiveMakeIterator: receiveMakeIterator, receiveNext: receiveNext, + receiveOutput: receiveOutput, receiveError: receiveError, + receiveComplete: receiveComplete, receiveCancel: receiveCancel) } } diff --git a/Sources/Afluent/SequenceOperators/MapErrorSequence.swift b/Sources/Afluent/SequenceOperators/MapErrorSequence.swift index eccdd6eb8..e9866a236 100644 --- a/Sources/Afluent/SequenceOperators/MapErrorSequence.swift +++ b/Sources/Afluent/SequenceOperators/MapErrorSequence.swift @@ -28,8 +28,9 @@ extension AsyncSequences { } public func makeAsyncIterator() -> AsyncIterator { - AsyncIterator(upstreamIterator: upstream.makeAsyncIterator(), - transform: transform) + AsyncIterator( + upstreamIterator: upstream.makeAsyncIterator(), + transform: transform) } } } @@ -42,7 +43,9 @@ extension AsyncSequence where Self: Sendable { /// - Parameter transform: A closure that takes the original error and returns a transformed error. /// /// - Returns: An `AsyncSequence` that produces the transformed error. - public func mapError(_ transform: @Sendable @escaping (Error) -> Error) -> AsyncSequences.MapError { + public func mapError(_ transform: @Sendable @escaping (Error) -> Error) + -> AsyncSequences.MapError + { AsyncSequences.MapError(upstream: self, transform: transform) } @@ -55,7 +58,9 @@ extension AsyncSequence where Self: Sendable { /// - transform: A closure that takes the matched error and returns a transformed error. /// /// - Returns: An `AsyncSequence` that produces the transformed error. - public func mapError(_ error: E, _ transform: @Sendable @escaping (Error) -> Error) -> AsyncSequences.MapError { + public func mapError( + _ error: E, _ transform: @Sendable @escaping (Error) -> Error + ) -> AsyncSequences.MapError { mapError { if let e = $0 as? E, e == error { return transform(e) } return $0 diff --git a/Sources/Afluent/SequenceOperators/MaterializeSequence.swift b/Sources/Afluent/SequenceOperators/MaterializeSequence.swift index 7314704d3..e13d2c39c 100644 --- a/Sources/Afluent/SequenceOperators/MaterializeSequence.swift +++ b/Sources/Afluent/SequenceOperators/MaterializeSequence.swift @@ -18,7 +18,8 @@ extension AsyncSequences { case complete } - public struct Materialize: AsyncSequence, Sendable where Upstream.Element: Sendable { + public struct Materialize: AsyncSequence, Sendable + where Upstream.Element: Sendable { public typealias Element = Event let upstream: Upstream diff --git a/Sources/Afluent/SequenceOperators/PlatformLock.swift b/Sources/Afluent/SequenceOperators/PlatformLock.swift index e73d275a5..e276f7048 100644 --- a/Sources/Afluent/SequenceOperators/PlatformLock.swift +++ b/Sources/Afluent/SequenceOperators/PlatformLock.swift @@ -17,84 +17,84 @@ // #if canImport(Darwin) -@_implementationOnly import Darwin + @_implementationOnly import Darwin #elseif canImport(Glibc) -@_implementationOnly import Glibc + @_implementationOnly import Glibc #elseif canImport(WinSDK) -@_implementationOnly import WinSDK + @_implementationOnly import WinSDK #endif internal struct Lock { -#if canImport(Darwin) - typealias Primitive = os_unfair_lock -#elseif canImport(Glibc) - typealias Primitive = pthread_mutex_t -#elseif canImport(WinSDK) - typealias Primitive = SRWLOCK -#endif - + #if canImport(Darwin) + typealias Primitive = os_unfair_lock + #elseif canImport(Glibc) + typealias Primitive = pthread_mutex_t + #elseif canImport(WinSDK) + typealias Primitive = SRWLOCK + #endif + typealias PlatformLock = UnsafeMutablePointer let platformLock: PlatformLock - + private init(_ platformLock: PlatformLock) { self.platformLock = platformLock } - + fileprivate static func initialize(_ platformLock: PlatformLock) { -#if canImport(Darwin) - platformLock.initialize(to: os_unfair_lock()) -#elseif canImport(Glibc) - pthread_mutex_init(platformLock, nil) -#elseif canImport(WinSDK) - InitializeSRWLock(platformLock) -#endif + #if canImport(Darwin) + platformLock.initialize(to: os_unfair_lock()) + #elseif canImport(Glibc) + pthread_mutex_init(platformLock, nil) + #elseif canImport(WinSDK) + InitializeSRWLock(platformLock) + #endif } - + fileprivate static func deinitialize(_ platformLock: PlatformLock) { -#if canImport(Glibc) - pthread_mutex_destroy(platformLock) -#endif + #if canImport(Glibc) + pthread_mutex_destroy(platformLock) + #endif platformLock.deinitialize(count: 1) } - + fileprivate static func lock(_ platformLock: PlatformLock) { -#if canImport(Darwin) - os_unfair_lock_lock(platformLock) -#elseif canImport(Glibc) - pthread_mutex_lock(platformLock) -#elseif canImport(WinSDK) - AcquireSRWLockExclusive(platformLock) -#endif + #if canImport(Darwin) + os_unfair_lock_lock(platformLock) + #elseif canImport(Glibc) + pthread_mutex_lock(platformLock) + #elseif canImport(WinSDK) + AcquireSRWLockExclusive(platformLock) + #endif } - + fileprivate static func unlock(_ platformLock: PlatformLock) { -#if canImport(Darwin) - os_unfair_lock_unlock(platformLock) -#elseif canImport(Glibc) - pthread_mutex_unlock(platformLock) -#elseif canImport(WinSDK) - ReleaseSRWLockExclusive(platformLock) -#endif + #if canImport(Darwin) + os_unfair_lock_unlock(platformLock) + #elseif canImport(Glibc) + pthread_mutex_unlock(platformLock) + #elseif canImport(WinSDK) + ReleaseSRWLockExclusive(platformLock) + #endif } - + static func allocate() -> Lock { let platformLock = PlatformLock.allocate(capacity: 1) initialize(platformLock) return Lock(platformLock) } - + func deinitialize() { Lock.deinitialize(platformLock) } - + func lock() { Lock.lock(platformLock) } - + func unlock() { Lock.unlock(platformLock) } - + /// Acquire the lock for the duration of the given block. /// /// This convenience method should be preferred to `lock` and `unlock` in @@ -110,9 +110,9 @@ internal struct Lock { } return try body() } - + // specialise Void return (for performance) - func withLockVoid(_ body: () throws -> Void) rethrows -> Void { + func withLockVoid(_ body: () throws -> Void) rethrows { try self.withLock(body) } } @@ -123,16 +123,16 @@ struct ManagedCriticalState { withUnsafeMutablePointerToElements { Lock.deinitialize($0) } } } - + private var buffer: ManagedBuffer - + init(_ initial: State) { buffer = LockedBuffer.create(minimumCapacity: 1) { buffer in buffer.withUnsafeMutablePointerToElements { Lock.initialize($0) } return initial } } - + func withCriticalRegion(_ critical: (inout State) throws -> R) rethrows -> R { try buffer.withUnsafeMutablePointers { header, lock in Lock.lock(lock) @@ -140,10 +140,10 @@ struct ManagedCriticalState { return try critical(&header.pointee) } } - + mutating func isKnownUniquelyReferenced() -> Bool { Swift.isKnownUniquelyReferenced(&buffer) } } -extension ManagedCriticalState: @unchecked Sendable where State: Sendable { } +extension ManagedCriticalState: @unchecked Sendable where State: Sendable {} diff --git a/Sources/Afluent/SequenceOperators/ReplaceErrorSequence.swift b/Sources/Afluent/SequenceOperators/ReplaceErrorSequence.swift index 8529e23b2..68cf3a021 100644 --- a/Sources/Afluent/SequenceOperators/ReplaceErrorSequence.swift +++ b/Sources/Afluent/SequenceOperators/ReplaceErrorSequence.swift @@ -8,7 +8,9 @@ import Foundation extension AsyncSequences { - public struct ReplaceError: AsyncSequence, Sendable where Upstream.Element == Output { + public struct ReplaceError: AsyncSequence, + Sendable + where Upstream.Element == Output { public typealias Element = Upstream.Element let upstream: Upstream let newOutput: Output @@ -29,8 +31,9 @@ extension AsyncSequences { } public func makeAsyncIterator() -> AsyncIterator { - AsyncIterator(upstreamIterator: upstream.makeAsyncIterator(), - newOutput: newOutput) + AsyncIterator( + upstreamIterator: upstream.makeAsyncIterator(), + newOutput: newOutput) } } } @@ -41,7 +44,8 @@ extension AsyncSequence where Self: Sendable { /// - Parameter value: The value to emit upon encountering an error. /// /// - Returns: An `AsyncSequence` that emits the specified value instead of failing when the upstream fails. - public func replaceError(with value: Element) -> AsyncSequences.ReplaceError where Element: Sendable { + public func replaceError(with value: Element) -> AsyncSequences.ReplaceError + where Element: Sendable { AsyncSequences.ReplaceError(upstream: self, newOutput: value) } } diff --git a/Sources/Afluent/SequenceOperators/ReplaceNilSequence.swift b/Sources/Afluent/SequenceOperators/ReplaceNilSequence.swift index 22d74cf35..a945144c6 100644 --- a/Sources/Afluent/SequenceOperators/ReplaceNilSequence.swift +++ b/Sources/Afluent/SequenceOperators/ReplaceNilSequence.swift @@ -8,7 +8,9 @@ import Foundation extension AsyncSequences { - public struct ReplaceNil: AsyncSequence, Sendable where Upstream.Element == Output? { + public struct ReplaceNil: AsyncSequence, + Sendable + where Upstream.Element == Output? { public typealias Element = Upstream.Element let upstream: Upstream let newOutput: Output @@ -28,8 +30,9 @@ extension AsyncSequences { } public func makeAsyncIterator() -> AsyncIterator { - AsyncIterator(upstreamIterator: upstream.makeAsyncIterator(), - newOutput: newOutput) + AsyncIterator( + upstreamIterator: upstream.makeAsyncIterator(), + newOutput: newOutput) } } } @@ -40,7 +43,8 @@ extension AsyncSequence where Self: Sendable { /// - Parameter value: The value to emit when the upstream emits `nil`. /// /// - Returns: An `AsyncSequence` that emits the specified value instead of `nil` when the upstream emits `nil`. - public func replaceNil(with value: E) -> AsyncSequences.ReplaceNil where Element == E? { + public func replaceNil(with value: E) -> AsyncSequences.ReplaceNil + where Element == E? { AsyncSequences.ReplaceNil(upstream: self, newOutput: value) } } diff --git a/Sources/Afluent/SequenceOperators/RetryAfterFlatMappingSequence.swift b/Sources/Afluent/SequenceOperators/RetryAfterFlatMappingSequence.swift index 39b92b8f3..dc5c1fcba 100644 --- a/Sources/Afluent/SequenceOperators/RetryAfterFlatMappingSequence.swift +++ b/Sources/Afluent/SequenceOperators/RetryAfterFlatMappingSequence.swift @@ -9,7 +9,11 @@ import Atomics import Foundation extension AsyncSequences { - public final actor RetryAfterFlatMapping: AsyncSequence, AsyncIteratorProtocol, Sendable where Upstream.Element == Downstream.Element, Upstream.Element: Sendable { + public final actor RetryAfterFlatMapping< + Upstream: AsyncSequence & Sendable, Downstream: AsyncSequence & Sendable, + Strategy: RetryStrategy + >: AsyncSequence, AsyncIteratorProtocol, Sendable + where Upstream.Element == Downstream.Element, Upstream.Element: Sendable { public typealias Element = Upstream.Element private final class State: @unchecked Sendable { let lock = NSRecursiveLock() @@ -27,14 +31,18 @@ extension AsyncSequences { return val } return _iterator - } set { + } + set { lock.lock() defer { lock.unlock() } _iterator = newValue } } - init(upstream: Upstream, transform: @Sendable @escaping (Error) async throws -> Downstream) { + init( + upstream: Upstream, + transform: @Sendable @escaping (Error) async throws -> Downstream + ) { self.upstream = upstream self.transform = transform } @@ -43,13 +51,19 @@ extension AsyncSequences { private let state: State private let strategy: Strategy - init(upstream: Upstream, strategy: Strategy, transform: @Sendable @escaping (Error) async throws -> Downstream) { - state = State(upstream: upstream, - transform: transform) + init( + upstream: Upstream, strategy: Strategy, + transform: @Sendable @escaping (Error) async throws -> Downstream + ) { + state = State( + upstream: upstream, + transform: transform) self.strategy = strategy } - private nonisolated func advanceAndSet(iterator: Upstream.AsyncIterator) async throws -> Upstream.Element? { + private nonisolated func advanceAndSet(iterator: Upstream.AsyncIterator) async throws + -> Upstream.Element? + { var copy = iterator let next = try await copy.next() state.iterator = copy @@ -63,9 +77,12 @@ extension AsyncSequences { } catch { try error.throwIf(CancellationError.self) - if try await strategy.handle(error: error, beforeRetry: { - for try await _ in try await state.transform($0) { } - }) { + if try await strategy.handle( + error: error, + beforeRetry: { + for try await _ in try await state.transform($0) {} + }) + { state.iterator = state.upstream.makeAsyncIterator() return try await next() } else { @@ -74,10 +91,16 @@ extension AsyncSequences { } } - public nonisolated func makeAsyncIterator() -> RetryAfterFlatMapping { self } + public nonisolated func makeAsyncIterator() -> RetryAfterFlatMapping< + Upstream, Downstream, Strategy + > { self } } - public final actor RetryOnAfterFlatMapping: AsyncSequence, AsyncIteratorProtocol, Sendable where Upstream.Element == Downstream.Element, Upstream.Element: Sendable { + public final actor RetryOnAfterFlatMapping< + Upstream: AsyncSequence & Sendable, Failure: Error & Equatable, + Downstream: AsyncSequence & Sendable, Strategy: RetryStrategy + >: AsyncSequence, AsyncIteratorProtocol, Sendable + where Upstream.Element == Downstream.Element, Upstream.Element: Sendable { public typealias Element = Upstream.Element private final class State: @unchecked Sendable { let lock = NSRecursiveLock() @@ -96,14 +119,18 @@ extension AsyncSequences { return val } return _iterator - } set { + } + set { lock.lock() defer { lock.unlock() } _iterator = newValue } } - init(upstream: Upstream, error: Failure, transform: @Sendable @escaping (Failure) async throws -> Downstream) { + init( + upstream: Upstream, error: Failure, + transform: @Sendable @escaping (Failure) async throws -> Downstream + ) { self.upstream = upstream self.error = error self.transform = transform @@ -113,14 +140,20 @@ extension AsyncSequences { private let state: State private let strategy: Strategy - init(upstream: Upstream, strategy: Strategy, error: Failure, transform: @Sendable @escaping (Failure) async throws -> Downstream) { - state = State(upstream: upstream, - error: error, - transform: transform) + init( + upstream: Upstream, strategy: Strategy, error: Failure, + transform: @Sendable @escaping (Failure) async throws -> Downstream + ) { + state = State( + upstream: upstream, + error: error, + transform: transform) self.strategy = strategy } - private nonisolated func advanceAndSet(iterator: Upstream.AsyncIterator) async throws -> Upstream.Element? { + private nonisolated func advanceAndSet(iterator: Upstream.AsyncIterator) async throws + -> Upstream.Element? + { var copy = iterator let next = try await copy.next() state.iterator = copy @@ -135,9 +168,13 @@ extension AsyncSequences { try error.throwIf(CancellationError.self) .throwIf(not: state.error) - if try await strategy.handle(error: error, beforeRetry: { err in - for try await _ in try await state.transform(err.throwIf(not: state.error)) { } - }) { + if try await strategy.handle( + error: error, + beforeRetry: { err in + for try await _ in try await state.transform(err.throwIf(not: state.error)) + {} + }) + { state.iterator = state.upstream.makeAsyncIterator() return try await next() } else { @@ -146,7 +183,9 @@ extension AsyncSequences { } } - public nonisolated func makeAsyncIterator() -> RetryOnAfterFlatMapping { self } + public nonisolated func makeAsyncIterator() -> RetryOnAfterFlatMapping< + Upstream, Failure, Downstream, Strategy + > { self } } } @@ -158,8 +197,11 @@ extension AsyncSequence where Self: Sendable { /// - transform: An async closure that takes the error from the upstream and returns a new `AsyncSequence`. /// /// - Returns: An `AsyncSequence` that emits the same output as the upstream but retries on failure up to the specified number of times, with the applied transformation. - public func retry(_ strategy: S, _ transform: @Sendable @escaping (Error) async throws -> D) -> AsyncSequences.RetryAfterFlatMapping { - AsyncSequences.RetryAfterFlatMapping(upstream: self, strategy: strategy, transform: transform) + public func retry( + _ strategy: S, _ transform: @Sendable @escaping (Error) async throws -> D + ) -> AsyncSequences.RetryAfterFlatMapping { + AsyncSequences.RetryAfterFlatMapping( + upstream: self, strategy: strategy, transform: transform) } /// Retries the upstream `AsyncSequence` up to a specified number of times while applying a transformation on error. @@ -169,8 +211,11 @@ extension AsyncSequence where Self: Sendable { /// - transform: An async closure that takes the error from the upstream and returns a new `AsyncSequence`. /// /// - Returns: An `AsyncSequence` that emits the same output as the upstream but retries on failure up to the specified number of times, with the applied transformation. - public func retry(_ retries: UInt = 1, _ transform: @Sendable @escaping (Error) async throws -> D) -> AsyncSequences.RetryAfterFlatMapping { - AsyncSequences.RetryAfterFlatMapping(upstream: self, strategy: .byCount(retries), transform: transform) + public func retry( + _ retries: UInt = 1, _ transform: @Sendable @escaping (Error) async throws -> D + ) -> AsyncSequences.RetryAfterFlatMapping { + AsyncSequences.RetryAfterFlatMapping( + upstream: self, strategy: .byCount(retries), transform: transform) } /// Retries the upstream `AsynchronousUnitOfWork` up to a specified number of times only when a specific error occurs, while applying a transformation on error. @@ -181,10 +226,13 @@ extension AsyncSequence where Self: Sendable { /// - transform: An async closure that takes the error from the upstream and returns a new `AsyncSequence`. /// /// - Returns: An `AsyncSequence` that emits the same output as the upstream but retries on the specified error up to the specified number of times, with the applied transformation. - public func retry(_ retries: UInt = 1, on error: E, _ transform: @Sendable @escaping (E) async throws -> D) -> AsyncSequences.RetryOnAfterFlatMapping { - AsyncSequences.RetryOnAfterFlatMapping(upstream: self, strategy: .byCount(retries), error: error, transform: transform) + public func retry( + _ retries: UInt = 1, on error: E, _ transform: @Sendable @escaping (E) async throws -> D + ) -> AsyncSequences.RetryOnAfterFlatMapping { + AsyncSequences.RetryOnAfterFlatMapping( + upstream: self, strategy: .byCount(retries), error: error, transform: transform) } - + /// Retries the upstream `AsynchronousUnitOfWork` up to a specified number of times only when a specific error occurs, while applying a transformation on error. /// /// - Parameters: @@ -193,7 +241,10 @@ extension AsyncSequence where Self: Sendable { /// - transform: An async closure that takes the error from the upstream and returns a new `AsyncSequence`. /// /// - Returns: An `AsyncSequence` that emits the same output as the upstream but retries on the specified error up to the specified number of times, with the applied transformation. - public func retry(_ strategy: S, on error: E, _ transform: @Sendable @escaping (E) async throws -> D) -> AsyncSequences.RetryOnAfterFlatMapping { - AsyncSequences.RetryOnAfterFlatMapping(upstream: self, strategy: strategy, error: error, transform: transform) + public func retry( + _ strategy: S, on error: E, _ transform: @Sendable @escaping (E) async throws -> D + ) -> AsyncSequences.RetryOnAfterFlatMapping { + AsyncSequences.RetryOnAfterFlatMapping( + upstream: self, strategy: strategy, error: error, transform: transform) } } diff --git a/Sources/Afluent/SequenceOperators/RetrySequence.swift b/Sources/Afluent/SequenceOperators/RetrySequence.swift index 28671e2f1..3c347cf70 100644 --- a/Sources/Afluent/SequenceOperators/RetrySequence.swift +++ b/Sources/Afluent/SequenceOperators/RetrySequence.swift @@ -9,7 +9,9 @@ import Atomics import Foundation extension AsyncSequences { - public final actor Retry: AsyncSequence, AsyncIteratorProtocol, Sendable where Upstream.Element: Sendable { + public final actor Retry: + AsyncSequence, AsyncIteratorProtocol, Sendable + where Upstream.Element: Sendable { private final class State: @unchecked Sendable { let lock = NSRecursiveLock() let upstream: Upstream @@ -25,7 +27,8 @@ extension AsyncSequences { return val } return _iterator - } set { + } + set { lock.lock() defer { lock.unlock() } _iterator = newValue @@ -46,7 +49,9 @@ extension AsyncSequences { self.strategy = strategy } - private nonisolated func advanceAndSet(iterator: Upstream.AsyncIterator) async throws -> Upstream.Element? { + private nonisolated func advanceAndSet(iterator: Upstream.AsyncIterator) async throws + -> Upstream.Element? + { var copy = iterator let next = try await copy.next() state.iterator = copy @@ -71,14 +76,16 @@ extension AsyncSequences { public nonisolated func makeAsyncIterator() -> Retry { self } } - - public final actor RetryOn: AsyncSequence, AsyncIteratorProtocol, Sendable where Upstream.Element: Sendable { + + public final actor RetryOn< + Upstream: AsyncSequence & Sendable, Failure: Error & Equatable, Strategy: RetryStrategy + >: AsyncSequence, AsyncIteratorProtocol, Sendable where Upstream.Element: Sendable { public typealias Element = Upstream.Element private final class State: @unchecked Sendable { let lock = NSRecursiveLock() let upstream: Upstream let error: Failure - + private var _iterator: Upstream.AsyncIterator? var iterator: Upstream.AsyncIterator { get { @@ -90,35 +97,39 @@ extension AsyncSequences { return val } return _iterator - } set { + } + set { lock.lock() defer { lock.unlock() } _iterator = newValue } } - + init(upstream: Upstream, failure: Failure) { self.upstream = upstream error = failure } } - + private let state: State private let strategy: Strategy - + init(upstream: Upstream, strategy: Strategy, error: Failure) { - state = State(upstream: upstream, - failure: error) + state = State( + upstream: upstream, + failure: error) self.strategy = strategy } - - private nonisolated func advanceAndSet(iterator: Upstream.AsyncIterator) async throws -> Upstream.Element? { + + private nonisolated func advanceAndSet(iterator: Upstream.AsyncIterator) async throws + -> Upstream.Element? + { var copy = iterator let next = try await copy.next() state.iterator = copy return next } - + public func next() async throws -> Upstream.Element? { do { try Task.checkCancellation() @@ -135,7 +146,7 @@ extension AsyncSequences { } } } - + public nonisolated func makeAsyncIterator() -> RetryOn { self } } } @@ -151,7 +162,7 @@ extension AsyncSequence where Self: Sendable, Element: Sendable { public func retry(_ strategy: S) -> AsyncSequences.Retry { AsyncSequences.Retry(upstream: self, strategy: strategy) } - + /// Retries the upstream `AsyncSequence` up to a specified number of times. /// /// - Parameter retries: The maximum number of times to retry the upstream, defaulting to 1. @@ -172,7 +183,9 @@ extension AsyncSequence where Self: Sendable, Element: Sendable { /// - Returns: An `AsyncSequence` that emits the same output as the upstream but retries on the specified error up to the specified number of times. /// - Important: Not every `AsyncSequence` can be retried, for this to work the sequence has to implement an iterator that doesn't preserve state across various creations. /// - Note: `AsyncStream` and `AsyncThrowingStream` are notable sequences which cannot be retried on their own. - public func retry(_ retries: UInt = 1, on error: E) -> AsyncSequences.RetryOn { + public func retry(_ retries: UInt = 1, on error: E) + -> AsyncSequences.RetryOn + { AsyncSequences.RetryOn(upstream: self, strategy: .byCount(retries), error: error) } } diff --git a/Sources/Afluent/SequenceOperators/ShareSequence.swift b/Sources/Afluent/SequenceOperators/ShareSequence.swift index 3e149fd80..501bf1af0 100644 --- a/Sources/Afluent/SequenceOperators/ShareSequence.swift +++ b/Sources/Afluent/SequenceOperators/ShareSequence.swift @@ -16,31 +16,29 @@ // Modified by Tyler Thompson on 9/29/24. // - -import DequeModule - @_spi(Experimental) extension AsyncSequence where Self: Sendable, Element: Sendable { public func broadcast() -> AsyncBroadcastSequence { AsyncBroadcastSequence(self) } - + public func share() -> AsyncBroadcastSequence { AsyncBroadcastSequence(self) } } -@_spi(Experimental) public struct AsyncBroadcastSequence: Sendable where Base: Sendable, Base.Element: Sendable { - struct State : Sendable { +@_spi(Experimental) public struct AsyncBroadcastSequence: Sendable +where Base: Sendable, Base.Element: Sendable { + struct State: Sendable { enum Terminal { case failure(Error) case finished } - + struct Side { - var buffer = Deque() + var buffer = [Element]() var terminal: Terminal? var continuation: UnsafeContinuation, Never>? - + mutating func drain() { if !buffer.isEmpty, let continuation { let element = buffer.removeFirst() @@ -48,102 +46,106 @@ import DequeModule self.continuation = nil } else if let terminal, let continuation { switch terminal { - case .failure(let error): - self.terminal = .finished - continuation.resume(returning: .failure(error)) - case .finished: - continuation.resume(returning: .success(nil)) + case .failure(let error): + self.terminal = .finished + continuation.resume(returning: .failure(error)) + case .finished: + continuation.resume(returning: .success(nil)) } self.continuation = nil } } - + mutating func cancel() { buffer.removeAll() terminal = .finished drain() } - + mutating func next(_ continuation: UnsafeContinuation, Never>) { - assert(self.continuation == nil) // presume that the sides are NOT sendable iterators... + assert(self.continuation == nil) // presume that the sides are NOT sendable iterators... self.continuation = continuation drain() } - + mutating func emit(_ result: Result) { switch result { - case .success(let element): - if let element { - buffer.append(element) - } else { - terminal = .finished - } - case .failure(let error): - terminal = .failure(error) + case .success(let element): + if let element { + buffer.append(element) + } else { + terminal = .finished + } + case .failure(let error): + terminal = .failure(error) } drain() } } - + var id = 0 var sides = [Int: Side]() - - init() { } - + + init() {} + mutating func establish() -> Int { defer { id += 1 } sides[id] = Side() return id } - + static func establish(_ state: ManagedCriticalState) -> Int { state.withCriticalRegion { $0.establish() } } - + mutating func cancel(_ id: Int) { if var side = sides.removeValue(forKey: id) { side.cancel() } } - + static func cancel(_ state: ManagedCriticalState, id: Int) { state.withCriticalRegion { $0.cancel(id) } } - - mutating func next(_ id: Int, continuation: UnsafeContinuation, Never>) { + + mutating func next( + _ id: Int, continuation: UnsafeContinuation, Never> + ) { sides[id]?.next(continuation) } - - static func next(_ state: ManagedCriticalState, id: Int) async -> Result { + + static func next(_ state: ManagedCriticalState, id: Int) async -> Result< + Element?, Error + > { await withUnsafeContinuation { continuation in state.withCriticalRegion { $0.next(id, continuation: continuation) } } } - + mutating func emit(_ result: Result) { for id in sides.keys { sides[id]?.emit(result) } } - + static func emit(_ state: ManagedCriticalState, result: Result) { state.withCriticalRegion { $0.emit(result) } } } - + struct Iteration { enum Status { case initial(Base) case iterating(Task) case terminal } - + var status: Status - + init(_ base: Base) { status = .initial(base) } - + static func task(_ state: ManagedCriticalState, base: Base) -> Task { Task { do { @@ -156,70 +158,72 @@ import DequeModule } } } - + mutating func start(_ state: ManagedCriticalState) -> Bool { switch status { - case .terminal: - return false - case .initial(let base): - status = .iterating(Iteration.task(state, base: base)) - default: - break + case .terminal: + return false + case .initial(let base): + status = .iterating(Iteration.task(state, base: base)) + default: + break } return true } - + mutating func cancel() { switch status { - case .iterating(let task): - task.cancel() - default: - break + case .iterating(let task): + task.cancel() + default: + break } status = .terminal } - - static func start(_ iteration: ManagedCriticalState, state: ManagedCriticalState) -> Bool { + + static func start( + _ iteration: ManagedCriticalState, state: ManagedCriticalState + ) -> Bool { iteration.withCriticalRegion { $0.start(state) } } - + static func cancel(_ iteration: ManagedCriticalState) { iteration.withCriticalRegion { $0.cancel() } } } - + let state: ManagedCriticalState let iteration: ManagedCriticalState - + init(_ base: Base) { state = ManagedCriticalState(State()) iteration = ManagedCriticalState(Iteration(base)) } } - @_spi(Experimental) extension AsyncBroadcastSequence: AsyncSequence { public typealias Element = Base.Element - + public struct Iterator: AsyncIteratorProtocol { final class Context { let state: ManagedCriticalState var iteration: ManagedCriticalState let id: Int - - init(_ state: ManagedCriticalState, _ iteration: ManagedCriticalState) { + + init(_ state: ManagedCriticalState, _ iteration: ManagedCriticalState) + { self.state = state self.iteration = iteration self.id = State.establish(state) } - + deinit { State.cancel(state, id: id) if iteration.isKnownUniquelyReferenced() { Iteration.cancel(iteration) } } - + func next() async rethrows -> Element? { guard Iteration.start(iteration, state: state) else { return nil @@ -237,22 +241,22 @@ import DequeModule } } } - + let context: Context - + init(_ state: ManagedCriticalState, _ iteration: ManagedCriticalState) { context = Context(state, iteration) } - + public mutating func next() async rethrows -> Element? { try await context.next() } } - + public func makeAsyncIterator() -> Iterator { Iterator(state, iteration) } } @available(*, unavailable) -@_spi(Experimental) extension AsyncBroadcastSequence.Iterator: Sendable { } +@_spi(Experimental) extension AsyncBroadcastSequence.Iterator: Sendable {} diff --git a/Sources/Afluent/SequenceOperators/ThrottleSequence.swift b/Sources/Afluent/SequenceOperators/ThrottleSequence.swift index 7af15f293..5fa1fdc1c 100644 --- a/Sources/Afluent/SequenceOperators/ThrottleSequence.swift +++ b/Sources/Afluent/SequenceOperators/ThrottleSequence.swift @@ -9,7 +9,8 @@ import Foundation @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, *) extension AsyncSequences { - public struct Throttle: AsyncSequence, Sendable where Upstream.Element: Sendable { + public struct Throttle: AsyncSequence, Sendable + where Upstream.Element: Sendable { public typealias Element = Upstream.Element let upstream: Upstream let interval: C.Duration @@ -31,7 +32,8 @@ extension AsyncSequences { try Task.checkCancellation() if iterator == nil { - iterator = AsyncThrowingStream<(Element?, Element?), Error> { [clock, interval, upstream, state] continuation in + iterator = AsyncThrowingStream<(Element?, Element?), Error> { + [clock, interval, upstream, state] continuation in let intervalTask = DeferredTask { guard let intervalStartInstant = state.startInstant else { return } @@ -109,7 +111,9 @@ extension AsyncSequence where Self: Sendable, Element: Sendable { /// - Parameter interval: The interval of time in which to observe and emit either the first or latest element. /// - Parameter latest: If `true`, emits the latest element in the time interval. If `false`, emits the first element in the time interval. /// - Note: The first element in upstream will always be returned immediately. Once a second element is received, then the clock will begin for the given time interval and return the first or latest element once completed. - public func throttle(for interval: C.Duration, clock: C, latest: Bool) -> AsyncSequences.Throttle { + public func throttle(for interval: C.Duration, clock: C, latest: Bool) + -> AsyncSequences.Throttle + { AsyncSequences.Throttle(upstream: self, interval: interval, clock: clock, latest: latest) } } diff --git a/Sources/Afluent/SerialTaskQueue.swift b/Sources/Afluent/SerialTaskQueue.swift index 92fd27a17..0a103536a 100644 --- a/Sources/Afluent/SerialTaskQueue.swift +++ b/Sources/Afluent/SerialTaskQueue.swift @@ -18,7 +18,8 @@ import Foundation /// This actor is useful in scenarios where tasks must be executed in a specific order or when you need to ensure that only one task is executed at a time to avoid race conditions. public final class SerialTaskQueue: @unchecked Sendable { private var subscribers = Set() - private let (stream, deferredTaskContinuation) = AsyncStream>.makeStream() + private let (stream, deferredTaskContinuation) = AsyncStream> + .makeStream() public init() { DeferredTask { @@ -38,17 +39,25 @@ public final class SerialTaskQueue: @unchecked Sendable { /// - Parameter task: The asynchronous task to be queued and executed. /// - Returns: The result of the task. /// - Throws: An error if the task throws an error. - public func queue(_ task: @Sendable @escaping () async throws -> T) async throws -> T { + public func queue(_ task: @Sendable @escaping () async throws -> T) async throws + -> T + { return try await withUnsafeThrowingContinuation { [weak self] continuation in - guard let self else { continuation.resume(throwing: CancellationError()); return } + guard let self else { + continuation.resume(throwing: CancellationError()) + return + } self.deferredTaskContinuation.yield( DeferredTask { try continuation.resume(returning: await task()) - }.handleEvents(receiveError: { error in - continuation.resume(throwing: error) - }, receiveCancel: { - continuation.resume(throwing: CancellationError()) - }) + }.handleEvents( + receiveError: { error in + continuation.resume(throwing: error) + }, + receiveCancel: { + continuation.resume(throwing: CancellationError()) + } + ) .eraseToAnyUnitOfWork() ) } diff --git a/Sources/Afluent/Subscription/AnyCancellable.swift b/Sources/Afluent/Subscription/AnyCancellable.swift index 6305f6423..39fb05ae1 100644 --- a/Sources/Afluent/Subscription/AnyCancellable.swift +++ b/Sources/Afluent/Subscription/AnyCancellable.swift @@ -50,14 +50,16 @@ extension AsynchronousUnitOfWork { return AnyCancellable(self) } -#if swift(>=6) - /// Executes the current asynchronous unit of work and returns an AnyCancellable token to cancel the subscription - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public func subscribe(executorPreference taskExecutor: (any TaskExecutor)?, priority: TaskPriority? = nil) -> AnyCancellable { - defer { run(executorPreference: taskExecutor, priority: priority) } - return AnyCancellable(self) - } -#endif + #if swift(>=6) + /// Executes the current asynchronous unit of work and returns an AnyCancellable token to cancel the subscription + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + public func subscribe( + executorPreference taskExecutor: (any TaskExecutor)?, priority: TaskPriority? = nil + ) -> AnyCancellable { + defer { run(executorPreference: taskExecutor, priority: priority) } + return AnyCancellable(self) + } + #endif } extension AsyncSequence where Self: Sendable { @@ -67,8 +69,10 @@ extension AsyncSequence where Self: Sendable { /// - receiveCompletion: A function that is executed when the stream has completed normally with `nil` or an error. /// - receiveOutput: A function that is executed when output is received from the sequence. /// If this function throws an error, then the stream is completed. - public func sink(receiveCompletion: (@Sendable (AsyncSequences.Completion) async -> Void)? = nil, - receiveOutput: (@Sendable (Element) async throws -> Void)? = nil) -> AnyCancellable { + public func sink( + receiveCompletion: (@Sendable (AsyncSequences.Completion) async -> Void)? = nil, + receiveOutput: (@Sendable (Element) async throws -> Void)? = nil + ) -> AnyCancellable { DeferredTask { do { for try await output in self { diff --git a/Sources/Afluent/Workers/AssertNoFailure.swift b/Sources/Afluent/Workers/AssertNoFailure.swift index efff4cbcd..086f69db3 100644 --- a/Sources/Afluent/Workers/AssertNoFailure.swift +++ b/Sources/Afluent/Workers/AssertNoFailure.swift @@ -8,7 +8,9 @@ import Foundation extension Workers { - struct AssertNoFailure: AsynchronousUnitOfWork where Upstream.Success == Success { + struct AssertNoFailure: + AsynchronousUnitOfWork + where Upstream.Success == Success { let state = TaskState() let upstream: Upstream @@ -18,7 +20,8 @@ extension Workers { return try await upstream.operation() } catch { if !(error is CancellationError) { - assertionFailure("Expected no error in asynchronous unit of work, but got: \(error)") + assertionFailure( + "Expected no error in asynchronous unit of work, but got: \(error)") } throw error } diff --git a/Sources/Afluent/Workers/Assign.swift b/Sources/Afluent/Workers/Assign.swift index 4e8336096..6fc528547 100644 --- a/Sources/Afluent/Workers/Assign.swift +++ b/Sources/Afluent/Workers/Assign.swift @@ -12,7 +12,9 @@ extension AsynchronousUnitOfWork { /// - Parameter keyPath: The key path to assign the output to. /// - Parameter object: The object to assign the output to. /// - Note: This method will not retain the object passed in. If the object is deallocated the assignment will stop. - public func assign(to keyPath: ReferenceWritableKeyPath, on object: Root) async throws { + public func assign( + to keyPath: ReferenceWritableKeyPath, on object: Root + ) async throws { _ = try await handleEvents(receiveOutput: { [weak object] in object?[keyPath: keyPath] = $0 }).execute() diff --git a/Sources/Afluent/Workers/Breakpoint.swift b/Sources/Afluent/Workers/Breakpoint.swift index 2eab741f1..2fd7f8de3 100644 --- a/Sources/Afluent/Workers/Breakpoint.swift +++ b/Sources/Afluent/Workers/Breakpoint.swift @@ -18,16 +18,21 @@ extension AsynchronousUnitOfWork { /// - receiveError: A closure that takes any error produced by the operation. If this closure returns `true`, a breakpoint is triggered. Default is `nil`. /// /// - Returns: An asynchronous unit of work with the breakpoint conditions applied. - @_transparent @_alwaysEmitIntoClient @inlinable public func breakpoint(receiveOutput: (@Sendable (Success) async throws -> Bool)? = nil, receiveError: (@Sendable (Error) async throws -> Bool)? = nil) -> some AsynchronousUnitOfWork { - handleEvents(receiveOutput: { output in - if try await receiveOutput?(output) == true { - raise(SIGTRAP) - } - }, receiveError: { error in - if try await receiveError?(error) == true { - raise(SIGTRAP) - } - }) + @_transparent @_alwaysEmitIntoClient @inlinable public func breakpoint( + receiveOutput: (@Sendable (Success) async throws -> Bool)? = nil, + receiveError: (@Sendable (Error) async throws -> Bool)? = nil + ) -> some AsynchronousUnitOfWork { + handleEvents( + receiveOutput: { output in + if try await receiveOutput?(output) == true { + raise(SIGTRAP) + } + }, + receiveError: { error in + if try await receiveError?(error) == true { + raise(SIGTRAP) + } + }) } /// Introduces a breakpoint into the asynchronous unit of work when an error occurs. @@ -35,7 +40,9 @@ extension AsynchronousUnitOfWork { /// This function triggers a `SIGTRAP` signal, pausing execution in a debugger, whenever the asynchronous operation produces an error. /// /// - Returns: An asynchronous unit of work with the breakpoint-on-error condition applied. - @_transparent @_alwaysEmitIntoClient @inlinable public func breakpointOnError() -> some AsynchronousUnitOfWork { + @_transparent @_alwaysEmitIntoClient @inlinable public func breakpointOnError() + -> some AsynchronousUnitOfWork + { breakpoint(receiveError: { _ in true }) } } diff --git a/Sources/Afluent/Workers/Catch.swift b/Sources/Afluent/Workers/Catch.swift index 85c29f466..84893c44f 100644 --- a/Sources/Afluent/Workers/Catch.swift +++ b/Sources/Afluent/Workers/Catch.swift @@ -8,12 +8,19 @@ import Foundation extension Workers { - struct Catch: AsynchronousUnitOfWork where Success == Upstream.Success, Upstream.Success == Downstream.Success { + struct Catch< + Upstream: AsynchronousUnitOfWork, Downstream: AsynchronousUnitOfWork, Success: Sendable + >: AsynchronousUnitOfWork + where Success == Upstream.Success, Upstream.Success == Downstream.Success { let state = TaskState() let upstream: Upstream let handler: @Sendable (Error) async throws -> Downstream - init(upstream: Upstream, @_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (Error) async throws -> Downstream) { + init( + upstream: Upstream, + @_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (Error) + async throws -> Downstream + ) { self.upstream = upstream self.handler = handler } @@ -39,7 +46,10 @@ extension AsynchronousUnitOfWork { /// - handler: A closure that takes an `Error` and returns an `AsynchronousUnitOfWork`. /// /// - Returns: An `AsynchronousUnitOfWork` that will catch and handle any errors emitted by the upstream unit of work. - public func `catch`(@_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (Error) async -> D) -> some AsynchronousUnitOfWork where Success == D.Success { + public func `catch`( + @_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (Error) async -> + D + ) -> some AsynchronousUnitOfWork where Success == D.Success { Workers.Catch(upstream: self, handler) } @@ -50,10 +60,14 @@ extension AsynchronousUnitOfWork { /// - handler: A closure that takes an `Error` and returns an `AsynchronousUnitOfWork`. /// /// - Returns: An `AsynchronousUnitOfWork` that will catch and handle the specific error. - public func `catch`(_ error: E, @_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (E) async -> D) -> some AsynchronousUnitOfWork where Success == D.Success { + public func `catch`( + _ error: E, + @_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (E) async -> D + ) -> some AsynchronousUnitOfWork where Success == D.Success { tryCatch { err in guard let unwrappedError = (err as? E), - unwrappedError == error else { throw err } + unwrappedError == error + else { throw err } return await handler(unwrappedError) } } @@ -64,7 +78,10 @@ extension AsynchronousUnitOfWork { /// - handler: A closure that takes an `Error` and returns an `AsynchronousUnitOfWork`, potentially throwing an error. /// /// - Returns: An `AsynchronousUnitOfWork` that will try to catch and handle any errors emitted by the upstream unit of work. - public func tryCatch(@_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (Error) async throws -> D) -> some AsynchronousUnitOfWork where Success == D.Success { + public func tryCatch( + @_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (Error) + async throws -> D + ) -> some AsynchronousUnitOfWork where Success == D.Success { Workers.Catch(upstream: self, handler) } @@ -75,10 +92,15 @@ extension AsynchronousUnitOfWork { /// - handler: A closure that takes an `Error` and returns an `AsynchronousUnitOfWork`, potentially throwing an error. /// /// - Returns: An `AsynchronousUnitOfWork` that will try to catch and handle the specific error. - public func tryCatch(_ error: E, @_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (E) async throws -> D) -> some AsynchronousUnitOfWork where Success == D.Success { + public func tryCatch( + _ error: E, + @_inheritActorContext @_implicitSelfCapture _ handler: @Sendable @escaping (E) async throws + -> D + ) -> some AsynchronousUnitOfWork where Success == D.Success { tryCatch { err in guard let unwrappedError = (err as? E), - unwrappedError == error else { throw err } + unwrappedError == error + else { throw err } return try await handler(unwrappedError) } } diff --git a/Sources/Afluent/Workers/Decode.swift b/Sources/Afluent/Workers/Decode.swift index a71fa8d6b..67cbde414 100644 --- a/Sources/Afluent/Workers/Decode.swift +++ b/Sources/Afluent/Workers/Decode.swift @@ -12,10 +12,12 @@ public protocol TopLevelDecoder { func decode(_ type: T.Type, from: Self.Input) throws -> T } -extension JSONDecoder: TopLevelDecoder { } +extension JSONDecoder: TopLevelDecoder {} extension Workers { - struct Decode: AsynchronousUnitOfWork where Upstream.Success == Decoder.Input { + struct Decode< + Upstream: AsynchronousUnitOfWork, Decoder: TopLevelDecoder, Success: Sendable & Decodable + >: AsynchronousUnitOfWork where Upstream.Success == Decoder.Input { let state = TaskState() final class State: @unchecked Sendable { let lock = NSRecursiveLock() @@ -57,7 +59,9 @@ extension AsynchronousUnitOfWork { /// - Returns: An `AsynchronousUnitOfWork` whose output is the decoded `T` type object. /// /// - Note: The generic constraint `Success == D.Input` ensures that the upstream unit of work emits a compatible type for the decoder. - public func decode(type _: T.Type, decoder: D) -> some AsynchronousUnitOfWork where Success == D.Input { + public func decode(type _: T.Type, decoder: D) + -> some AsynchronousUnitOfWork where Success == D.Input + { Workers.Decode(upstream: self, decoder: decoder) } } diff --git a/Sources/Afluent/Workers/Delay.swift b/Sources/Afluent/Workers/Delay.swift index 3b781f78e..59b4cf560 100644 --- a/Sources/Afluent/Workers/Delay.swift +++ b/Sources/Afluent/Workers/Delay.swift @@ -9,7 +9,9 @@ import Foundation extension Workers { @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, *) - struct Delay: AsynchronousUnitOfWork where Upstream.Success == Success { + struct Delay: + AsynchronousUnitOfWork + where Upstream.Success == Success { let state = TaskState() let upstream: Upstream let clock: C @@ -51,7 +53,9 @@ extension AsynchronousUnitOfWork { /// /// - Availability: macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0 and above. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, *) - public func delay(for duration: C.Duration, clock: C, tolerance: C.Duration? = nil) -> some AsynchronousUnitOfWork { + public func delay(for duration: C.Duration, clock: C, tolerance: C.Duration? = nil) + -> some AsynchronousUnitOfWork + { Workers.Delay(upstream: self, clock: clock, duration: duration, tolerance: tolerance) } } diff --git a/Sources/Afluent/Workers/Dematerialize.swift b/Sources/Afluent/Workers/Dematerialize.swift index 919c34dd0..742062d4e 100644 --- a/Sources/Afluent/Workers/Dematerialize.swift +++ b/Sources/Afluent/Workers/Dematerialize.swift @@ -27,7 +27,8 @@ extension AsynchronousUnitOfWork { /// /// - Returns: An `AsynchronousUnitOfWork` that emits the success value or throws the error contained within the `Result`. /// - Throws: The error contained within the `Result` if it's a failure. - public func dematerialize() -> some AsynchronousUnitOfWork where Success == Result { + public func dematerialize() -> some AsynchronousUnitOfWork + where Success == Result { tryMap { try $0.get() } } } diff --git a/Sources/Afluent/Workers/Encode.swift b/Sources/Afluent/Workers/Encode.swift index 07fe49356..557be2014 100644 --- a/Sources/Afluent/Workers/Encode.swift +++ b/Sources/Afluent/Workers/Encode.swift @@ -12,10 +12,12 @@ public protocol TopLevelEncoder { func encode(_ value: T) throws -> Output } -extension JSONEncoder: TopLevelEncoder { } +extension JSONEncoder: TopLevelEncoder {} extension Workers { - struct Encode: AsynchronousUnitOfWork where Success == Encoder.Output, Upstream.Success: Encodable { + struct Encode: + AsynchronousUnitOfWork + where Success == Encoder.Output, Upstream.Success: Encodable { final class State: @unchecked Sendable { let lock = NSRecursiveLock() private let encoder: Encoder @@ -55,7 +57,8 @@ extension AsynchronousUnitOfWork { /// - Returns: An `AsynchronousUnitOfWork` emitting the encoded values as output of type `E.Output`. /// /// - Note: The returned `AsynchronousUnitOfWork` will fail if the encoding process fails. - public func encode(encoder: E) -> some AsynchronousUnitOfWork where Success: Encodable, E.Output: Sendable { + public func encode(encoder: E) -> some AsynchronousUnitOfWork + where Success: Encodable, E.Output: Sendable { Workers.Encode(upstream: self, encoder: encoder) } } diff --git a/Sources/Afluent/Workers/FlatMap.swift b/Sources/Afluent/Workers/FlatMap.swift index dc77f8737..3d696c3a0 100644 --- a/Sources/Afluent/Workers/FlatMap.swift +++ b/Sources/Afluent/Workers/FlatMap.swift @@ -8,12 +8,19 @@ import Foundation extension Workers { - struct FlatMap: AsynchronousUnitOfWork where Success == Downstream.Success { + struct FlatMap< + Upstream: AsynchronousUnitOfWork, Downstream: AsynchronousUnitOfWork, Success: Sendable + >: AsynchronousUnitOfWork where Success == Downstream.Success { let state = TaskState() let upstream: Upstream let transform: @Sendable (Upstream.Success) async throws -> Downstream - init(upstream: Upstream, @_inheritActorContext @_implicitSelfCapture transform: @Sendable @escaping (Upstream.Success) async throws -> Downstream) { + init( + upstream: Upstream, + @_inheritActorContext @_implicitSelfCapture transform: @Sendable @escaping ( + Upstream.Success + ) async throws -> Downstream + ) { self.upstream = upstream self.transform = transform } @@ -34,7 +41,10 @@ extension AsynchronousUnitOfWork { /// - Returns: An `AsynchronousUnitOfWork` emitting the successful output values from the new `AsynchronousUnitOfWork` created by the transformation. /// /// - Note: The returned `AsynchronousUnitOfWork` will fail if either the upstream unit of work or the transformation closure fails. - public func flatMap(@_inheritActorContext @_implicitSelfCapture _ transform: @Sendable @escaping (Success) async throws -> D) -> some AsynchronousUnitOfWork { + public func flatMap( + @_inheritActorContext @_implicitSelfCapture _ transform: @Sendable @escaping (Success) + async throws -> D + ) -> some AsynchronousUnitOfWork { Workers.FlatMap(upstream: self, transform: transform) } @@ -46,7 +56,10 @@ extension AsynchronousUnitOfWork { /// - Returns: An `AsynchronousUnitOfWork` emitting the successful output values from the new `AsynchronousUnitOfWork` created by the transformation. /// /// - Note: The returned `AsynchronousUnitOfWork` will fail if either the upstream unit of work or the transformation closure fails. - public func flatMap(@_inheritActorContext @_implicitSelfCapture _ transform: @Sendable @escaping () async throws -> D) -> some AsynchronousUnitOfWork where Success == Void { + public func flatMap( + @_inheritActorContext @_implicitSelfCapture _ transform: @Sendable @escaping () async throws + -> D + ) -> some AsynchronousUnitOfWork where Success == Void { Workers.FlatMap(upstream: self, transform: { _ in try await transform() }) } } diff --git a/Sources/Afluent/Workers/HandleEvents.swift b/Sources/Afluent/Workers/HandleEvents.swift index e5998b3f9..1d9202314 100644 --- a/Sources/Afluent/Workers/HandleEvents.swift +++ b/Sources/Afluent/Workers/HandleEvents.swift @@ -8,7 +8,8 @@ import Foundation extension Workers { - actor HandleEvents: AsynchronousUnitOfWork where Upstream.Success == Success { + actor HandleEvents: AsynchronousUnitOfWork + where Upstream.Success == Success { let state = TaskState() let upstream: Upstream let receiveOperation: (@Sendable () async throws -> Void)? @@ -16,7 +17,21 @@ extension Workers { let receiveError: (@Sendable (Error) async throws -> Void)? let receiveCancel: (@Sendable () async throws -> Void)? - init(upstream: Upstream, @_inheritActorContext @_implicitSelfCapture receiveOperation: (@Sendable () async throws -> Void)?, @_inheritActorContext @_implicitSelfCapture receiveOutput: (@Sendable (Success) async throws -> Void)?, @_inheritActorContext @_implicitSelfCapture receiveError: (@Sendable (Error) async throws -> Void)?, @_inheritActorContext @_implicitSelfCapture receiveCancel: (@Sendable () async throws -> Void)?) { + init( + upstream: Upstream, + @_inheritActorContext @_implicitSelfCapture receiveOperation: ( + @Sendable () async throws -> Void + )?, + @_inheritActorContext @_implicitSelfCapture receiveOutput: ( + @Sendable (Success) async throws -> Void + )?, + @_inheritActorContext @_implicitSelfCapture receiveError: ( + @Sendable (Error) async throws -> Void + )?, + @_inheritActorContext @_implicitSelfCapture receiveCancel: ( + @Sendable () async throws -> Void + )? + ) { self.upstream = upstream self.receiveOperation = receiveOperation self.receiveOutput = receiveOutput @@ -59,7 +74,22 @@ extension AsynchronousUnitOfWork { /// - Returns: An `AsynchronousUnitOfWork` that performs the side-effects for the specified receiving events. /// /// - Note: The returned `AsynchronousUnitOfWork` forwards all receiving events from the upstream unit of work. - public func handleEvents(@_inheritActorContext @_implicitSelfCapture receiveOperation: (@Sendable () async throws -> Void)? = nil, @_inheritActorContext @_implicitSelfCapture receiveOutput: (@Sendable (Success) async throws -> Void)? = nil, @_inheritActorContext @_implicitSelfCapture receiveError: (@Sendable (Error) async throws -> Void)? = nil, @_inheritActorContext @_implicitSelfCapture receiveCancel: (@Sendable () async throws -> Void)? = nil) -> some AsynchronousUnitOfWork { - Workers.HandleEvents(upstream: self, receiveOperation: receiveOperation, receiveOutput: receiveOutput, receiveError: receiveError, receiveCancel: receiveCancel) + public func handleEvents( + @_inheritActorContext @_implicitSelfCapture receiveOperation: ( + @Sendable () async throws -> Void + )? = nil, + @_inheritActorContext @_implicitSelfCapture receiveOutput: ( + @Sendable (Success) async throws -> Void + )? = nil, + @_inheritActorContext @_implicitSelfCapture receiveError: ( + @Sendable (Error) async throws -> Void + )? = nil, + @_inheritActorContext @_implicitSelfCapture receiveCancel: ( + @Sendable () async throws -> Void + )? = nil + ) -> some AsynchronousUnitOfWork { + Workers.HandleEvents( + upstream: self, receiveOperation: receiveOperation, receiveOutput: receiveOutput, + receiveError: receiveError, receiveCancel: receiveCancel) } } diff --git a/Sources/Afluent/Workers/Map.swift b/Sources/Afluent/Workers/Map.swift index ee9305d5e..78acdd514 100644 --- a/Sources/Afluent/Workers/Map.swift +++ b/Sources/Afluent/Workers/Map.swift @@ -13,7 +13,12 @@ extension Workers { let upstream: Upstream let transform: @Sendable (Upstream.Success) async -> Success - init(upstream: Upstream, @_inheritActorContext @_implicitSelfCapture transform: @Sendable @escaping (Upstream.Success) async -> Success) { + init( + upstream: Upstream, + @_inheritActorContext @_implicitSelfCapture transform: @Sendable @escaping ( + Upstream.Success + ) async -> Success + ) { self.upstream = upstream self.transform = transform } @@ -30,7 +35,12 @@ extension Workers { let upstream: Upstream let transform: @Sendable (Upstream.Success) async throws -> Success - init(upstream: Upstream, @_inheritActorContext @_implicitSelfCapture transform: @Sendable @escaping (Upstream.Success) async throws -> Success) { + init( + upstream: Upstream, + @_inheritActorContext @_implicitSelfCapture transform: @Sendable @escaping ( + Upstream.Success + ) async throws -> Success + ) { self.upstream = upstream self.transform = transform } @@ -50,7 +60,10 @@ extension AsynchronousUnitOfWork { /// - transform: A closure that takes the successful output of the upstream and returns a transformed value. /// /// - Returns: An `AsynchronousUnitOfWork` that emits the transformed value. - public func map(@_inheritActorContext @_implicitSelfCapture _ transform: @Sendable @escaping (Success) async -> S) -> some AsynchronousUnitOfWork { + public func map( + @_inheritActorContext @_implicitSelfCapture _ transform: @Sendable @escaping (Success) async + -> S + ) -> some AsynchronousUnitOfWork { Workers.Map(upstream: self, transform: transform) } @@ -72,7 +85,10 @@ extension AsynchronousUnitOfWork { /// - transform: A closure that takes the successful output of the upstream and returns a transformed value. The closure can throw errors. /// /// - Returns: An `AsynchronousUnitOfWork` that emits the transformed value or an error if the transformation fails. - public func tryMap(@_inheritActorContext @_implicitSelfCapture _ transform: @Sendable @escaping (Success) async throws -> S) -> some AsynchronousUnitOfWork { + public func tryMap( + @_inheritActorContext @_implicitSelfCapture _ transform: @Sendable @escaping (Success) + async throws -> S + ) -> some AsynchronousUnitOfWork { Workers.TryMap(upstream: self, transform: transform) } } diff --git a/Sources/Afluent/Workers/MapError.swift b/Sources/Afluent/Workers/MapError.swift index 6cf152318..f004bd6c4 100644 --- a/Sources/Afluent/Workers/MapError.swift +++ b/Sources/Afluent/Workers/MapError.swift @@ -8,7 +8,8 @@ import Foundation extension Workers { - actor MapError: AsynchronousUnitOfWork where Success == Upstream.Success { + actor MapError: AsynchronousUnitOfWork + where Success == Upstream.Success { let state = TaskState() let upstream: Upstream let transform: @Sendable (Error) -> Error @@ -42,7 +43,9 @@ extension AsynchronousUnitOfWork { /// - Parameter transform: A closure that takes the original error and returns a transformed error. /// /// - Returns: An asynchronous unit of work that produces the transformed error. - public func mapError(_ transform: @Sendable @escaping (Error) -> Error) -> some AsynchronousUnitOfWork { + public func mapError(_ transform: @Sendable @escaping (Error) -> Error) + -> some AsynchronousUnitOfWork + { Workers.MapError(upstream: self, transform: transform) } @@ -55,7 +58,9 @@ extension AsynchronousUnitOfWork { /// - transform: A closure that takes the matched error and returns a transformed error. /// /// - Returns: An asynchronous unit of work that produces either the transformed error (if a match was found) or the original error. - public func mapError(_ error: E, _ transform: @Sendable @escaping (Error) -> Error) -> some AsynchronousUnitOfWork { + public func mapError( + _ error: E, _ transform: @Sendable @escaping (Error) -> Error + ) -> some AsynchronousUnitOfWork { mapError { if let e = $0 as? E, e == error { return transform(e) } return $0 diff --git a/Sources/Afluent/Workers/ReplaceError.swift b/Sources/Afluent/Workers/ReplaceError.swift index de0dc8290..c6569fa36 100644 --- a/Sources/Afluent/Workers/ReplaceError.swift +++ b/Sources/Afluent/Workers/ReplaceError.swift @@ -8,7 +8,8 @@ import Foundation extension Workers { - struct ReplaceError: AsynchronousUnitOfWork where Upstream.Success == Success { + struct ReplaceError: AsynchronousUnitOfWork + where Upstream.Success == Success { let state = TaskState() let upstream: Upstream let newValue: Success diff --git a/Sources/Afluent/Workers/ReplaceNil.swift b/Sources/Afluent/Workers/ReplaceNil.swift index affa79370..4b08e349d 100644 --- a/Sources/Afluent/Workers/ReplaceNil.swift +++ b/Sources/Afluent/Workers/ReplaceNil.swift @@ -8,7 +8,8 @@ import Foundation extension Workers { - struct ReplaceNil: AsynchronousUnitOfWork where Upstream.Success == Success? { + struct ReplaceNil: AsynchronousUnitOfWork + where Upstream.Success == Success? { let state = TaskState() let upstream: Upstream let newValue: Success @@ -36,7 +37,8 @@ extension AsynchronousUnitOfWork { /// - Parameter value: The value to emit when the upstream emits `nil`. /// /// - Returns: An `AsynchronousUnitOfWork` that emits the specified value instead of `nil` when the upstream emits `nil`. - public func replaceNil(with value: S) -> some AsynchronousUnitOfWork where Success == S? { + public func replaceNil(with value: S) -> some AsynchronousUnitOfWork + where Success == S? { Workers.ReplaceNil(upstream: self, newValue: value) } } diff --git a/Sources/Afluent/Workers/Retain.swift b/Sources/Afluent/Workers/Retain.swift index 6cfd5695b..94d03215e 100644 --- a/Sources/Afluent/Workers/Retain.swift +++ b/Sources/Afluent/Workers/Retain.swift @@ -8,7 +8,8 @@ import Foundation extension Workers { - actor Retain: AsynchronousUnitOfWork where Success == Upstream.Success { + actor Retain: AsynchronousUnitOfWork + where Success == Upstream.Success { let state = TaskState() let upstream: Upstream var cachedSuccess: Success? diff --git a/Sources/Afluent/Workers/Retry.swift b/Sources/Afluent/Workers/Retry.swift index c98c9b057..9e7cd1f27 100644 --- a/Sources/Afluent/Workers/Retry.swift +++ b/Sources/Afluent/Workers/Retry.swift @@ -8,7 +8,9 @@ import Foundation extension Workers { - actor Retry: AsynchronousUnitOfWork where Upstream.Success == Success { + actor Retry: + AsynchronousUnitOfWork + where Upstream.Success == Success { let state = TaskState() let upstream: Upstream var strategy: Strategy @@ -38,19 +40,22 @@ extension Workers { } } } - - actor RetryOn: AsynchronousUnitOfWork where Upstream.Success == Success { + + actor RetryOn< + Upstream: AsynchronousUnitOfWork, Failure: Error & Equatable, Success, + Strategy: RetryStrategy + >: AsynchronousUnitOfWork where Upstream.Success == Success { let state = TaskState() let upstream: Upstream let strategy: Strategy let error: Failure - + init(upstream: Upstream, strategy: Strategy, error: Failure) { self.upstream = upstream self.strategy = strategy self.error = error } - + func _operation() async throws -> AsynchronousOperation { AsynchronousOperation { [weak self] in guard let self else { throw CancellationError() } @@ -84,7 +89,7 @@ extension AsynchronousUnitOfWork { public func retry(_ strategy: some RetryStrategy) -> some AsynchronousUnitOfWork { Workers.Retry(upstream: self, strategy: strategy) } - + /// Retries the upstream `AsynchronousUnitOfWork` up to a specified number of times. /// /// - Parameter retries: The maximum number of times to retry the upstream, defaulting to 1. @@ -101,7 +106,9 @@ extension AsynchronousUnitOfWork { /// - error: The specific error that should trigger a retry. /// /// - Returns: An `AsynchronousUnitOfWork` that emits the same output as the upstream but retries on the specified error up to the specified number of times. - public func retry(_ retries: UInt = 1, on error: E) -> some AsynchronousUnitOfWork { + public func retry(_ retries: UInt = 1, on error: E) + -> some AsynchronousUnitOfWork + { Workers.RetryOn(upstream: self, strategy: .byCount(retries), error: error) } } diff --git a/Sources/Afluent/Workers/RetryAfterFlatMapping.swift b/Sources/Afluent/Workers/RetryAfterFlatMapping.swift index 063412878..84b629bc7 100644 --- a/Sources/Afluent/Workers/RetryAfterFlatMapping.swift +++ b/Sources/Afluent/Workers/RetryAfterFlatMapping.swift @@ -8,13 +8,20 @@ import Foundation extension Workers { - actor RetryAfterFlatMapping: AsynchronousUnitOfWork where Upstream.Success == Success { + actor RetryAfterFlatMapping< + Upstream: AsynchronousUnitOfWork, Downstream: AsynchronousUnitOfWork, Success, + Strategy: RetryStrategy + >: AsynchronousUnitOfWork where Upstream.Success == Success { let state = TaskState() let upstream: Upstream let strategy: Strategy let transform: @Sendable (Error) async throws -> Downstream - init(upstream: Upstream, strategy: Strategy, @_inheritActorContext @_implicitSelfCapture transform: @Sendable @escaping (Error) async throws -> Downstream) { + init( + upstream: Upstream, strategy: Strategy, + @_inheritActorContext @_implicitSelfCapture transform: @Sendable @escaping (Error) + async throws -> Downstream + ) { self.upstream = upstream self.strategy = strategy self.transform = transform @@ -28,7 +35,9 @@ extension Workers { return try await self.upstream._operation()() } catch { var err = error - while try await strategy.handle(error: err, beforeRetry: { _ = try await self.transform($0).operation() }) { + while try await strategy.handle( + error: err, beforeRetry: { _ = try await self.transform($0).operation() }) + { do { return try await self.upstream._operation()() } catch { @@ -41,14 +50,21 @@ extension Workers { } } - actor RetryOnAfterFlatMapping: AsynchronousUnitOfWork where Upstream.Success == Success { + actor RetryOnAfterFlatMapping< + Upstream: AsynchronousUnitOfWork, Downstream: AsynchronousUnitOfWork, + Failure: Error & Equatable, Success, Strategy: RetryStrategy + >: AsynchronousUnitOfWork where Upstream.Success == Success { let state = TaskState() let upstream: Upstream let strategy: Strategy let error: Failure let transform: @Sendable (Failure) async throws -> Downstream - init(upstream: Upstream, strategy: Strategy, error: Failure, @_inheritActorContext @_implicitSelfCapture transform: @Sendable @escaping (Failure) async throws -> Downstream) { + init( + upstream: Upstream, strategy: Strategy, error: Failure, + @_inheritActorContext @_implicitSelfCapture transform: @Sendable @escaping (Failure) + async throws -> Downstream + ) { self.upstream = upstream self.strategy = strategy self.error = error @@ -63,10 +79,13 @@ extension Workers { return try await self.upstream._operation()() } catch { var err = error - while try await strategy.handle(error: err, beforeRetry: { err in - guard let e = err as? Failure, e == self.error else { return } - _ = try await self.transform(e).operation() - }) { + while try await strategy.handle( + error: err, + beforeRetry: { err in + guard let e = err as? Failure, e == self.error else { return } + _ = try await self.transform(e).operation() + }) + { try err.throwIf(CancellationError.self) .throwIf(not: self.error) @@ -91,10 +110,14 @@ extension AsynchronousUnitOfWork { /// - transform: An async closure that takes the error from the upstream and returns a new `AsynchronousUnitOfWork`. /// /// - Returns: An `AsynchronousUnitOfWork` that emits the same output as the upstream but retries on failure up to the specified number of times, with the applied transformation. - public func retry(_ strategy: some RetryStrategy, @_inheritActorContext @_implicitSelfCapture _ transform: @Sendable @escaping (Error) async throws -> D) -> some AsynchronousUnitOfWork { + public func retry( + _ strategy: some RetryStrategy, + @_inheritActorContext @_implicitSelfCapture _ transform: @Sendable @escaping (Error) + async throws -> D + ) -> some AsynchronousUnitOfWork { Workers.RetryAfterFlatMapping(upstream: self, strategy: strategy, transform: transform) } - + /// Retries the upstream `AsynchronousUnitOfWork` up to a specified number of times while applying a transformation on error. /// /// - Parameters: @@ -102,8 +125,13 @@ extension AsynchronousUnitOfWork { /// - transform: An async closure that takes the error from the upstream and returns a new `AsynchronousUnitOfWork`. /// /// - Returns: An `AsynchronousUnitOfWork` that emits the same output as the upstream but retries on failure up to the specified number of times, with the applied transformation. - public func retry(_ retries: UInt = 1, @_inheritActorContext @_implicitSelfCapture _ transform: @Sendable @escaping (Error) async throws -> D) -> some AsynchronousUnitOfWork { - Workers.RetryAfterFlatMapping(upstream: self, strategy: .byCount(retries), transform: transform) + public func retry( + _ retries: UInt = 1, + @_inheritActorContext @_implicitSelfCapture _ transform: @Sendable @escaping (Error) + async throws -> D + ) -> some AsynchronousUnitOfWork { + Workers.RetryAfterFlatMapping( + upstream: self, strategy: .byCount(retries), transform: transform) } /// Retries the upstream `AsynchronousUnitOfWork` up to a specified number of times only when a specific error occurs, while applying a transformation on error. @@ -114,10 +142,15 @@ extension AsynchronousUnitOfWork { /// - transform: An async closure that takes the error from the upstream and returns a new `AsynchronousUnitOfWork`. /// /// - Returns: An `AsynchronousUnitOfWork` that emits the same output as the upstream but retries on the specified error up to the specified number of times, with the applied transformation. - public func retry(_ retries: UInt = 1, on error: E, @_inheritActorContext @_implicitSelfCapture _ transform: @Sendable @escaping (E) async throws -> D) -> some AsynchronousUnitOfWork { - Workers.RetryOnAfterFlatMapping(upstream: self, strategy: .byCount(retries), error: error, transform: transform) + public func retry( + _ retries: UInt = 1, on error: E, + @_inheritActorContext @_implicitSelfCapture _ transform: @Sendable @escaping (E) + async throws -> D + ) -> some AsynchronousUnitOfWork { + Workers.RetryOnAfterFlatMapping( + upstream: self, strategy: .byCount(retries), error: error, transform: transform) } - + /// Retries the upstream `AsynchronousUnitOfWork` up to a specified number of times only when a specific error occurs, while applying a transformation on error. /// /// - Parameters: @@ -126,7 +159,12 @@ extension AsynchronousUnitOfWork { /// - transform: An async closure that takes the error from the upstream and returns a new `AsynchronousUnitOfWork`. /// /// - Returns: An `AsynchronousUnitOfWork` that emits the same output as the upstream but retries on the specified error up to the specified number of times, with the applied transformation. - public func retry(_ strategy: S, on error: E, @_inheritActorContext @_implicitSelfCapture _ transform: @Sendable @escaping (E) async throws -> D) -> some AsynchronousUnitOfWork { - Workers.RetryOnAfterFlatMapping(upstream: self, strategy: strategy, error: error, transform: transform) + public func retry( + _ strategy: S, on error: E, + @_inheritActorContext @_implicitSelfCapture _ transform: @Sendable @escaping (E) + async throws -> D + ) -> some AsynchronousUnitOfWork { + Workers.RetryOnAfterFlatMapping( + upstream: self, strategy: strategy, error: error, transform: transform) } } diff --git a/Sources/Afluent/Workers/Share.swift b/Sources/Afluent/Workers/Share.swift index 997272f2e..201bca3be 100644 --- a/Sources/Afluent/Workers/Share.swift +++ b/Sources/Afluent/Workers/Share.swift @@ -8,7 +8,8 @@ import Foundation extension Workers { - actor Share: AsynchronousUnitOfWork where Upstream.Success == Success { + actor Share: AsynchronousUnitOfWork + where Upstream.Success == Success { let state = TaskState() let upstream: Upstream private lazy var task = Task { try await upstream.operation() } @@ -44,5 +45,7 @@ extension AsynchronousUnitOfWork { /// Shares the upstream `AsynchronousUnitOfWork` among multiple downstream subscribers. /// /// - Returns: An `AsynchronousUnitOfWork` that shares a single subscription to the upstream, allowing multiple downstream subscribers to receive the same values. - public func share() -> some AsynchronousUnitOfWork & Actor { Workers.Share(upstream: self) } + public func share() -> some AsynchronousUnitOfWork & Actor { + Workers.Share(upstream: self) + } } diff --git a/Sources/Afluent/Workers/ShareFromCache.swift b/Sources/Afluent/Workers/ShareFromCache.swift index 7d08c95a2..9985b3c30 100644 --- a/Sources/Afluent/Workers/ShareFromCache.swift +++ b/Sources/Afluent/Workers/ShareFromCache.swift @@ -8,22 +8,30 @@ import Foundation extension AsynchronousUnitOfWork { - func _shareFromCache(_ cache: AUOWCache, strategy: AUOWCache.Strategy, hasher: inout Hasher, fileId: String = "", function: String = "", line: UInt = 0, column: UInt = 0) -> some AsynchronousUnitOfWork { + func _shareFromCache( + _ cache: AUOWCache, strategy: AUOWCache.Strategy, hasher: inout Hasher, fileId: String = "", + function: String = "", line: UInt = 0, column: UInt = 0 + ) -> some AsynchronousUnitOfWork { hasher.combine(fileId) hasher.combine(function) hasher.combine(line) hasher.combine(column) let key = hasher.finalize() switch strategy { - case .cacheUntilCompletionOrCancellation: - return cache.retrieveOrCreate(unitOfWork: handleEvents(receiveOutput: { [weak cache] _ in - cache?.clearAsynchronousUnitOfWork(withKey: key) - }, receiveError: { [weak cache] _ in - cache?.clearAsynchronousUnitOfWork(withKey: key) - }, receiveCancel: { [weak cache] in - cache?.clearAsynchronousUnitOfWork(withKey: key) - }).share(), - keyedBy: key) + case .cacheUntilCompletionOrCancellation: + return cache.retrieveOrCreate( + unitOfWork: handleEvents( + receiveOutput: { [weak cache] _ in + cache?.clearAsynchronousUnitOfWork(withKey: key) + }, + receiveError: { [weak cache] _ in + cache?.clearAsynchronousUnitOfWork(withKey: key) + }, + receiveCancel: { [weak cache] in + cache?.clearAsynchronousUnitOfWork(withKey: key) + } + ).share(), + keyedBy: key) } } @@ -37,9 +45,14 @@ extension AsynchronousUnitOfWork { /// - line: The line number where this function is called. Defaults to `#line`. /// - column: The column where this function is called. Defaults to `#column`. /// - Returns: An asynchronous unit of work encapsulating the operation's success or failure. - public func shareFromCache(_ cache: AUOWCache, strategy: AUOWCache.Strategy, fileId: String = #fileID, function: String = #function, line: UInt = #line, column: UInt = #column) -> some AsynchronousUnitOfWork { + public func shareFromCache( + _ cache: AUOWCache, strategy: AUOWCache.Strategy, fileId: String = #fileID, + function: String = #function, line: UInt = #line, column: UInt = #column + ) -> some AsynchronousUnitOfWork { var hasher = Hasher() - return _shareFromCache(cache, strategy: strategy, hasher: &hasher, fileId: fileId, function: function, line: line, column: column) + return _shareFromCache( + cache, strategy: strategy, hasher: &hasher, fileId: fileId, function: function, + line: line, column: column) } /// Shares data from the given cache based on a specified caching strategy and hashable keys. @@ -49,7 +62,9 @@ extension AsynchronousUnitOfWork { /// - strategy: The caching strategy to use. /// - keys: One or more hashable keys used to look up the data in the cache. /// - Returns: An asynchronous unit of work encapsulating the operation's success or failure. - public func shareFromCache(_ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0) -> some AsynchronousUnitOfWork { + public func shareFromCache( + _ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0 + ) -> some AsynchronousUnitOfWork { var hasher = Hasher() hasher.combine(k0) return _shareFromCache(cache, strategy: strategy, hasher: &hasher) @@ -62,7 +77,9 @@ extension AsynchronousUnitOfWork { /// - strategy: The caching strategy to use. /// - keys: One or more hashable keys used to look up the data in the cache. /// - Returns: An asynchronous unit of work encapsulating the operation's success or failure. - public func shareFromCache(_ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1) -> some AsynchronousUnitOfWork { + public func shareFromCache( + _ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1 + ) -> some AsynchronousUnitOfWork { var hasher = Hasher() hasher.combine(k0) hasher.combine(k1) @@ -76,7 +93,9 @@ extension AsynchronousUnitOfWork { /// - strategy: The caching strategy to use. /// - keys: One or more hashable keys used to look up the data in the cache. /// - Returns: An asynchronous unit of work encapsulating the operation's success or failure. - public func shareFromCache(_ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1, _ k2: H2) -> some AsynchronousUnitOfWork { + public func shareFromCache( + _ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1, _ k2: H2 + ) -> some AsynchronousUnitOfWork { var hasher = Hasher() hasher.combine(k0) hasher.combine(k1) @@ -91,7 +110,9 @@ extension AsynchronousUnitOfWork { /// - strategy: The caching strategy to use. /// - keys: One or more hashable keys used to look up the data in the cache. /// - Returns: An asynchronous unit of work encapsulating the operation's success or failure. - public func shareFromCache(_ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1, _ k2: H2, _ k3: H3) -> some AsynchronousUnitOfWork { + public func shareFromCache( + _ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1, _ k2: H2, _ k3: H3 + ) -> some AsynchronousUnitOfWork { var hasher = Hasher() hasher.combine(k0) hasher.combine(k1) @@ -107,7 +128,12 @@ extension AsynchronousUnitOfWork { /// - strategy: The caching strategy to use. /// - keys: One or more hashable keys used to look up the data in the cache. /// - Returns: An asynchronous unit of work encapsulating the operation's success or failure. - public func shareFromCache(_ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1, _ k2: H2, _ k3: H3, _ k4: H4) -> some AsynchronousUnitOfWork { + public func shareFromCache< + H0: Hashable, H1: Hashable, H2: Hashable, H3: Hashable, H4: Hashable + >( + _ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1, _ k2: H2, _ k3: H3, + _ k4: H4 + ) -> some AsynchronousUnitOfWork { var hasher = Hasher() hasher.combine(k0) hasher.combine(k1) @@ -124,7 +150,12 @@ extension AsynchronousUnitOfWork { /// - strategy: The caching strategy to use. /// - keys: One or more hashable keys used to look up the data in the cache. /// - Returns: An asynchronous unit of work encapsulating the operation's success or failure. - public func shareFromCache(_ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1, _ k2: H2, _ k3: H3, _ k4: H4, _ k5: H5) -> some AsynchronousUnitOfWork { + public func shareFromCache< + H0: Hashable, H1: Hashable, H2: Hashable, H3: Hashable, H4: Hashable, H5: Hashable + >( + _ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1, _ k2: H2, _ k3: H3, + _ k4: H4, _ k5: H5 + ) -> some AsynchronousUnitOfWork { var hasher = Hasher() hasher.combine(k0) hasher.combine(k1) @@ -142,7 +173,13 @@ extension AsynchronousUnitOfWork { /// - strategy: The caching strategy to use. /// - keys: One or more hashable keys used to look up the data in the cache. /// - Returns: An asynchronous unit of work encapsulating the operation's success or failure. - public func shareFromCache(_ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1, _ k2: H2, _ k3: H3, _ k4: H4, _ k5: H5, _ k6: H6) -> some AsynchronousUnitOfWork { + public func shareFromCache< + H0: Hashable, H1: Hashable, H2: Hashable, H3: Hashable, H4: Hashable, H5: Hashable, + H6: Hashable + >( + _ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1, _ k2: H2, _ k3: H3, + _ k4: H4, _ k5: H5, _ k6: H6 + ) -> some AsynchronousUnitOfWork { var hasher = Hasher() hasher.combine(k0) hasher.combine(k1) @@ -161,7 +198,13 @@ extension AsynchronousUnitOfWork { /// - strategy: The caching strategy to use. /// - keys: One or more hashable keys used to look up the data in the cache. /// - Returns: An asynchronous unit of work encapsulating the operation's success or failure. - public func shareFromCache(_ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1, _ k2: H2, _ k3: H3, _ k4: H4, _ k5: H5, _ k6: H6, _ k7: H7) -> some AsynchronousUnitOfWork { + public func shareFromCache< + H0: Hashable, H1: Hashable, H2: Hashable, H3: Hashable, H4: Hashable, H5: Hashable, + H6: Hashable, H7: Hashable + >( + _ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1, _ k2: H2, _ k3: H3, + _ k4: H4, _ k5: H5, _ k6: H6, _ k7: H7 + ) -> some AsynchronousUnitOfWork { var hasher = Hasher() hasher.combine(k0) hasher.combine(k1) @@ -181,7 +224,13 @@ extension AsynchronousUnitOfWork { /// - strategy: The caching strategy to use. /// - keys: One or more hashable keys used to look up the data in the cache. /// - Returns: An asynchronous unit of work encapsulating the operation's success or failure. - public func shareFromCache(_ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1, _ k2: H2, _ k3: H3, _ k4: H4, _ k5: H5, _ k6: H6, _ k7: H7, _ k8: H8) -> some AsynchronousUnitOfWork { + public func shareFromCache< + H0: Hashable, H1: Hashable, H2: Hashable, H3: Hashable, H4: Hashable, H5: Hashable, + H6: Hashable, H7: Hashable, H8: Hashable + >( + _ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1, _ k2: H2, _ k3: H3, + _ k4: H4, _ k5: H5, _ k6: H6, _ k7: H7, _ k8: H8 + ) -> some AsynchronousUnitOfWork { var hasher = Hasher() hasher.combine(k0) hasher.combine(k1) @@ -202,7 +251,13 @@ extension AsynchronousUnitOfWork { /// - strategy: The caching strategy to use. /// - keys: One or more hashable keys used to look up the data in the cache. /// - Returns: An asynchronous unit of work encapsulating the operation's success or failure. - public func shareFromCache(_ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1, _ k2: H2, _ k3: H3, _ k4: H4, _ k5: H5, _ k6: H6, _ k7: H7, _ k8: H8, _ 🐶: H9) -> some AsynchronousUnitOfWork { + public func shareFromCache< + H0: Hashable, H1: Hashable, H2: Hashable, H3: Hashable, H4: Hashable, H5: Hashable, + H6: Hashable, H7: Hashable, H8: Hashable, H9: Hashable + >( + _ cache: AUOWCache, strategy: AUOWCache.Strategy, keys k0: H0, _ k1: H1, _ k2: H2, _ k3: H3, + _ k4: H4, _ k5: H5, _ k6: H6, _ k7: H7, _ k8: H8, _ 🐶: H9 + ) -> some AsynchronousUnitOfWork { var hasher = Hasher() hasher.combine(k0) hasher.combine(k1) diff --git a/Sources/Afluent/Workers/SingleValueChannel.swift b/Sources/Afluent/Workers/SingleValueChannel.swift index 4f27d3b0f..fcf2b92e5 100644 --- a/Sources/Afluent/Workers/SingleValueChannel.swift +++ b/Sources/Afluent/Workers/SingleValueChannel.swift @@ -24,7 +24,7 @@ public actor SingleValueChannel: AsynchronousUnitOfWork { private var channelState = State.noValue /// Creates a new `SingleValueChannel`. - public init() { } + public init() {} public func _operation() async throws -> AsynchronousOperation { AsynchronousOperation { [weak self] in diff --git a/Sources/Afluent/Workers/SingleValueSubject.swift b/Sources/Afluent/Workers/SingleValueSubject.swift index 42d3508ed..c4caa8ecd 100644 --- a/Sources/Afluent/Workers/SingleValueSubject.swift +++ b/Sources/Afluent/Workers/SingleValueSubject.swift @@ -12,7 +12,9 @@ import Foundation /// `SingleValueSubject` is an `AsynchronousUnitOfWork` that can be manually completed with either a success value or an error. It's useful for scenarios where you need to bridge callback-based APIs into the world of `async/await`. /// /// - Note: Once completed, any further attempts to send a value or an error will result in a `SubjectError.alreadyCompleted`. -public final class SingleValueSubject: AsynchronousUnitOfWork, @unchecked Sendable { +public final class SingleValueSubject: AsynchronousUnitOfWork, @unchecked + Sendable +{ /// Errors specific to `SingleValueSubject`. public enum SubjectError: Error { /// Indicates that the subject has already been completed and cannot accept further values or errors. @@ -24,7 +26,7 @@ public final class SingleValueSubject: AsynchronousUnitOfWork private var subjectState = State.noValue /// Creates a new `SingleValueSubject`. - public init() { } + public init() {} public func _operation() async throws -> AsynchronousOperation { AsynchronousOperation { [weak self] in diff --git a/Sources/Afluent/Workers/Timeout.swift b/Sources/Afluent/Workers/Timeout.swift index 4f7e24fbb..9cd7aae11 100644 --- a/Sources/Afluent/Workers/Timeout.swift +++ b/Sources/Afluent/Workers/Timeout.swift @@ -9,7 +9,9 @@ import Foundation extension Workers { @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, *) - struct Timeout: AsynchronousUnitOfWork where Upstream.Success == Success { + struct Timeout: + AsynchronousUnitOfWork + where Upstream.Success == Success { let state = TaskState() let upstream: Upstream let customError: Error? @@ -17,7 +19,10 @@ extension Workers { let duration: C.Duration let tolerance: C.Duration? - init(upstream: Upstream, customError: Error?, clock: C, duration: C.Duration, tolerance: C.Duration?) { + init( + upstream: Upstream, customError: Error?, clock: C, duration: C.Duration, + tolerance: C.Duration? + ) { self.upstream = upstream self.customError = customError self.clock = clock @@ -28,7 +33,8 @@ extension Workers { func _operation() async throws -> AsynchronousOperation { AsynchronousOperation { try await Race { - try await clock.sleep(until: clock.now.advanced(by: duration), tolerance: tolerance) + try await clock.sleep( + until: clock.now.advanced(by: duration), tolerance: tolerance) throw customError ?? CancellationError() } against: { try await upstream.execute() @@ -47,8 +53,12 @@ extension AsynchronousUnitOfWork { /// - Parameter customError: A custom error to throw if timeout occurs. If no value is supplied a `CancellationError` is thrown. /// - Returns: An asynchronous unit of work that includes the timeout behavior, encapsulating the operation's success or failure. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, *) - public func timeout(_ duration: Duration, customError: Error? = nil) -> some AsynchronousUnitOfWork { - Workers.Timeout(upstream: self, customError: customError, clock: SuspendingClock(), duration: duration, tolerance: nil) + public func timeout(_ duration: Duration, customError: Error? = nil) + -> some AsynchronousUnitOfWork + { + Workers.Timeout( + upstream: self, customError: customError, clock: SuspendingClock(), duration: duration, + tolerance: nil) } /// Adds a timeout to the current asynchronous unit of work. @@ -61,7 +71,11 @@ extension AsynchronousUnitOfWork { /// - Parameter customError: A custom error to throw if timeout occurs. If no value is supplied a `CancellationError` is thrown. /// - Returns: An asynchronous unit of work that includes the timeout behavior, encapsulating the operation's success or failure. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, *) - public func timeout(_ duration: C.Duration, clock: C, tolerance: C.Duration? = nil, customError: Error? = nil) -> some AsynchronousUnitOfWork { - Workers.Timeout(upstream: self, customError: customError, clock: clock, duration: duration, tolerance: tolerance) + public func timeout( + _ duration: C.Duration, clock: C, tolerance: C.Duration? = nil, customError: Error? = nil + ) -> some AsynchronousUnitOfWork { + Workers.Timeout( + upstream: self, customError: customError, clock: clock, duration: duration, + tolerance: tolerance) } } diff --git a/Sources/Afluent/Workers/ToAsyncSequence.swift b/Sources/Afluent/Workers/ToAsyncSequence.swift index f6ea3fd0f..6fe675fae 100644 --- a/Sources/Afluent/Workers/ToAsyncSequence.swift +++ b/Sources/Afluent/Workers/ToAsyncSequence.swift @@ -12,7 +12,9 @@ import Foundation /// `AsynchronousUnitOfWorkSequence` is an `AsyncSequence` that wraps an `AsynchronousUnitOfWork`. It provides a way to use asynchronous units of work in contexts where an `AsyncSequence` is expected. This sequence emits a single value and then completes. /// /// - Note: The sequence will only execute the unit of work once. Subsequent iterations will receive `nil`, indicating the end of the sequence. -public struct AsynchronousUnitOfWorkSequence: AsyncSequence, Sendable { +public struct AsynchronousUnitOfWorkSequence: AsyncSequence, + Sendable +{ public typealias Element = UnitOfWork.Success let unitOfWork: UnitOfWork @@ -39,7 +41,10 @@ extension AsynchronousUnitOfWork { /// Converts the asynchronous unit of work into an `AsyncThrowingStream`. /// /// - Deprecated: This stream was replaced with a custom `AsyncSequence` which behaves better. - @available(*, deprecated, renamed: "toAsyncSequence()", message: "This stream was replaced with a custom AsyncSequence which behaves better") + @available( + *, deprecated, renamed: "toAsyncSequence()", + message: "This stream was replaced with a custom AsyncSequence which behaves better" + ) public func toStream() -> AsyncThrowingStream { .init { continuation in Task { diff --git a/Sources/Afluent/Workers/UnwrapOrThrow.swift b/Sources/Afluent/Workers/UnwrapOrThrow.swift index cb19620b6..6f0ab9150 100644 --- a/Sources/Afluent/Workers/UnwrapOrThrow.swift +++ b/Sources/Afluent/Workers/UnwrapOrThrow.swift @@ -9,7 +9,9 @@ import Foundation extension AsynchronousUnitOfWork { /// Unwraps the optional value if present, or throws an error. - public func unwrap(orThrow error: @Sendable @escaping @autoclosure () -> Error) -> some AsynchronousUnitOfWork where Success == T? { + public func unwrap(orThrow error: @Sendable @escaping @autoclosure () -> Error) + -> some AsynchronousUnitOfWork where Success == T? + { tryMap { output in switch output { case .some(let value): diff --git a/Sources/Afluent/Workers/WithUnretained.swift b/Sources/Afluent/Workers/WithUnretained.swift index 5179f28b5..58eed75f6 100644 --- a/Sources/Afluent/Workers/WithUnretained.swift +++ b/Sources/Afluent/Workers/WithUnretained.swift @@ -20,7 +20,9 @@ extension AsynchronousUnitOfWork { /// - obj: The object to provide an unretained reference on. /// - resultSelector: A function to combine the unretained referenced on `obj` and the value of the observable sequence. /// - Returns: An AsynchronousUnitOfWork that contains the result of `resultSelector` being called with an unretained reference on `obj` and the values of the original sequence. - public func withUnretained(_ obj: Object, resultSelector: @Sendable @escaping (Object, Success) -> Out) -> some AsynchronousUnitOfWork { + public func withUnretained( + _ obj: Object, resultSelector: @Sendable @escaping (Object, Success) -> Out + ) -> some AsynchronousUnitOfWork { tryMap { [weak obj] element -> Out in guard let obj = obj else { throw UnretainedError.failedRetaining } return resultSelector(obj, element) diff --git a/Sources/Afluent/Workers/Workers.swift b/Sources/Afluent/Workers/Workers.swift index 98d7eb55b..88fd77cf4 100644 --- a/Sources/Afluent/Workers/Workers.swift +++ b/Sources/Afluent/Workers/Workers.swift @@ -9,4 +9,4 @@ /// /// The `Workers` enum itself doesn't contain values, serving solely as a container for nested types and functionalities to keep them organized. /// For example, it might contain static methods, nested types, or enums that deal with specific aspects of asynchronous work. -public enum Workers { } +public enum Workers {} diff --git a/Sources/Afluent/Workers/Zip.swift b/Sources/Afluent/Workers/Zip.swift index e402730c6..fe7602512 100644 --- a/Sources/Afluent/Workers/Zip.swift +++ b/Sources/Afluent/Workers/Zip.swift @@ -8,7 +8,9 @@ import Foundation extension Workers { - struct Zip: AsynchronousUnitOfWork { + struct Zip: + AsynchronousUnitOfWork + { typealias Success = (Upstream.Success, Downstream.Success) let state = TaskState() @@ -29,7 +31,10 @@ extension Workers { } } - struct Zip3: AsynchronousUnitOfWork { + struct Zip3< + Upstream: AsynchronousUnitOfWork, Downstream0: AsynchronousUnitOfWork, + Downstream1: AsynchronousUnitOfWork + >: AsynchronousUnitOfWork { typealias Success = (Upstream.Success, Downstream0.Success, Downstream1.Success) let state = TaskState() @@ -54,8 +59,13 @@ extension Workers { } } - struct Zip4: AsynchronousUnitOfWork { - typealias Success = (Upstream.Success, Downstream0.Success, Downstream1.Success, Downstream2.Success) + struct Zip4< + Upstream: AsynchronousUnitOfWork, Downstream0: AsynchronousUnitOfWork, + Downstream1: AsynchronousUnitOfWork, Downstream2: AsynchronousUnitOfWork + >: AsynchronousUnitOfWork { + typealias Success = ( + Upstream.Success, Downstream0.Success, Downstream1.Success, Downstream2.Success + ) let state = TaskState() let upstream: Upstream @@ -89,7 +99,9 @@ extension AsynchronousUnitOfWork { /// - Parameters: /// - downstream: The second asynchronous unit of work to zip with. /// - Returns: A new asynchronous unit of work that produces a tuple containing results from both upstream and downstream when completed. - public func zip(_ downstream: D) -> some AsynchronousUnitOfWork<(Success, D.Success)> { + public func zip(_ downstream: D) -> some AsynchronousUnitOfWork< + (Success, D.Success) + > { Workers.Zip(upstream: self, downstream: downstream) } @@ -99,8 +111,15 @@ extension AsynchronousUnitOfWork { /// - downstream: The second asynchronous unit of work to zip with. /// - transform: A function that takes a tuple of results from both units of work and returns a transformed result. /// - Returns: A new asynchronous unit of work that produces the transformed result when completed. - public func zip(_ downstream: D, @_inheritActorContext @_implicitSelfCapture transform: @Sendable @escaping ((Success, D.Success)) async throws -> T) -> some AsynchronousUnitOfWork { - Workers.TryMap, T>(upstream: Workers.Zip(upstream: self, downstream: downstream), transform: transform) + public func zip( + _ downstream: D, + @_inheritActorContext @_implicitSelfCapture transform: @Sendable @escaping ( + (Success, D.Success) + ) async throws -> T + ) -> some AsynchronousUnitOfWork { + Workers.TryMap, T>( + upstream: Workers.Zip(upstream: self, downstream: downstream), + transform: transform) } // zip3 @@ -110,7 +129,9 @@ extension AsynchronousUnitOfWork { /// - d0: The first additional asynchronous unit of work to zip with. /// - d1: The second additional asynchronous unit of work to zip with. /// - Returns: A new asynchronous unit of work that produces a tuple containing results from the upstream and both downstreams when completed. - public func zip(_ d0: D0, _ d1: D1) -> some AsynchronousUnitOfWork<(Success, D0.Success, D1.Success)> { + public func zip(_ d0: D0, _ d1: D1) + -> some AsynchronousUnitOfWork<(Success, D0.Success, D1.Success)> + { Workers.Zip3(upstream: self, d0: d0, d1: d1) } @@ -121,8 +142,15 @@ extension AsynchronousUnitOfWork { /// - d1: The second additional asynchronous unit of work to zip with. /// - transform: A function that takes a tuple of results from all units of work and returns a transformed result. /// - Returns: A new asynchronous unit of work that produces the transformed result when completed. - public func zip(_ d0: D0, _ d1: D1, @_inheritActorContext @_implicitSelfCapture transform: @Sendable @escaping ((Success, D0.Success, D1.Success)) async throws -> T) -> some AsynchronousUnitOfWork { - Workers.TryMap, T>(upstream: Workers.Zip3(upstream: self, d0: d0, d1: d1), transform: transform) + public func zip( + _ d0: D0, _ d1: D1, + @_inheritActorContext @_implicitSelfCapture transform: @Sendable @escaping ( + (Success, D0.Success, D1.Success) + ) async throws -> T + ) -> some AsynchronousUnitOfWork { + Workers.TryMap, T>( + upstream: Workers.Zip3(upstream: self, d0: d0, d1: d1), + transform: transform) } // zip4 @@ -133,7 +161,11 @@ extension AsynchronousUnitOfWork { /// - d1: The second additional asynchronous unit of work to zip with. /// - d2: The third additional asynchronous unit of work to zip with. /// - Returns: A new asynchronous unit of work that produces a tuple containing results from the upstream and all three downstreams when completed. - public func zip(_ d0: D0, _ d1: D1, _ d2: D2) -> some AsynchronousUnitOfWork<(Success, D0.Success, D1.Success, D2.Success)> { + public func zip< + D0: AsynchronousUnitOfWork, D1: AsynchronousUnitOfWork, D2: AsynchronousUnitOfWork + >(_ d0: D0, _ d1: D1, _ d2: D2) -> some AsynchronousUnitOfWork< + (Success, D0.Success, D1.Success, D2.Success) + > { Workers.Zip4(upstream: self, d0: d0, d1: d1, d2: d2) } @@ -145,7 +177,17 @@ extension AsynchronousUnitOfWork { /// - d2: The third additional asynchronous unit of work to zip with. /// - transform: A function that takes a tuple of results from all units of work and returns a transformed result. /// - Returns: A new asynchronous unit of work that produces the transformed result when completed. - public func zip(_ d0: D0, _ d1: D1, _ d2: D2, @_inheritActorContext @_implicitSelfCapture transform: @Sendable @escaping ((Success, D0.Success, D1.Success, D2.Success)) async throws -> T) -> some AsynchronousUnitOfWork { - Workers.TryMap, T>(upstream: Workers.Zip4(upstream: self, d0: d0, d1: d1, d2: d2), transform: transform) + public func zip< + D0: AsynchronousUnitOfWork, D1: AsynchronousUnitOfWork, D2: AsynchronousUnitOfWork, + T: Sendable + >( + _ d0: D0, _ d1: D1, _ d2: D2, + @_inheritActorContext @_implicitSelfCapture transform: @Sendable @escaping ( + (Success, D0.Success, D1.Success, D2.Success) + ) async throws -> T + ) -> some AsynchronousUnitOfWork { + Workers.TryMap, T>( + upstream: Workers.Zip4(upstream: self, d0: d0, d1: d1, d2: d2), + transform: transform) } } diff --git a/Tests/AfluentTests/CurrentValueSubjectTests.swift b/Tests/AfluentTests/CurrentValueSubjectTests.swift index 3d4ebfef9..839f03178 100644 --- a/Tests/AfluentTests/CurrentValueSubjectTests.swift +++ b/Tests/AfluentTests/CurrentValueSubjectTests.swift @@ -5,8 +5,8 @@ // Created by Tyler Thompson on 10/12/24. // -import Testing @_spi(Experimental) import Afluent +import Testing struct CurrentValueSubjectTests { @Test func currentValueSubjectCanSendValuesAndFinish() async throws { @@ -19,7 +19,7 @@ struct CurrentValueSubjectTests { let test = Test() let subject = CurrentValueSubject(1) let taskStartedSubject = SingleValueSubject() - + let task = Task { for try await value in subject.handleEvents(receiveMakeIterator: { try? taskStartedSubject.send() @@ -27,20 +27,20 @@ struct CurrentValueSubjectTests { await test.appendValue(value) } } - + try await taskStartedSubject.execute() - + #expect(await test.values.count < 2) - + subject.send(2) subject.send(3) subject.send(completion: .finished) - + _ = try await task.value #expect(await test.values == [1, 2, 3]) } - + @Test func currentValueSubjectUpdatesValueOnSend() async throws { actor Test { var values = [Int]() @@ -52,7 +52,7 @@ struct CurrentValueSubjectTests { let subject = CurrentValueSubject(1) #expect(subject.value == 1) let taskStartedSubject = SingleValueSubject() - + let task = Task { for try await value in subject.handleEvents(receiveMakeIterator: { try? taskStartedSubject.send() @@ -60,22 +60,22 @@ struct CurrentValueSubjectTests { await test.appendValue(value) } } - + try await taskStartedSubject.execute() - + #expect(await test.values.count < 2) - + subject.send(2) #expect(subject.value == 2) subject.send(3) #expect(subject.value == 3) subject.send(completion: .finished) - + _ = try await task.value #expect(await test.values == [1, 2, 3]) } - + @Test func currentValueSubjectSendsWhenValueUpdated() async throws { actor Test { var values = [Int]() @@ -87,7 +87,7 @@ struct CurrentValueSubjectTests { let subject = CurrentValueSubject(1) #expect(subject.value == 1) let taskStartedSubject = SingleValueSubject() - + let task = Task { for try await value in subject.handleEvents(receiveMakeIterator: { try? taskStartedSubject.send() @@ -95,22 +95,22 @@ struct CurrentValueSubjectTests { await test.appendValue(value) } } - + try await taskStartedSubject.execute() - + #expect(await test.values.count < 2) - + subject.value = 2 #expect(subject.value == 2) subject.value = 3 #expect(subject.value == 3) subject.send(completion: .finished) - + _ = try await task.value #expect(await test.values == [1, 2, 3]) } - + @Test func currentValueSubjectCanSendValues_ToMultipleConsumers_AndFinish() async throws { actor Test { var values = [Int]() @@ -121,7 +121,7 @@ struct CurrentValueSubjectTests { let test = Test() let test2 = Test() let subject = CurrentValueSubject(1) - + let taskStartedSubject = SingleValueSubject() let task = Task { for try await value in subject.handleEvents(receiveMakeIterator: { @@ -130,7 +130,7 @@ struct CurrentValueSubjectTests { await test.appendValue(value) } } - + let task2StartedSubject = SingleValueSubject() let task2 = Task { for try await value in subject.handleEvents(receiveMakeIterator: { @@ -139,24 +139,24 @@ struct CurrentValueSubjectTests { await test2.appendValue(value) } } - + try await taskStartedSubject.execute() try await task2StartedSubject.execute() - + #expect(await test.values.count < 2) #expect(await test2.values.count < 2) subject.send(2) subject.send(3) subject.send(completion: .finished) - + _ = try await task.value _ = try await task2.value #expect(await test.values == [1, 2, 3]) #expect(await test2.values == [1, 2, 3]) } - + @Test func currentValueSubjectStopsSendingValuesUponFinish() async throws { actor Test { var values = [Int]() @@ -175,7 +175,7 @@ struct CurrentValueSubjectTests { await test.appendValue(value) } } - + try await taskStartedSubject.execute() #expect(await test.values.count < 2) @@ -183,9 +183,9 @@ struct CurrentValueSubjectTests { subject.send(2) subject.send(3) subject.send(completion: .finished) - + _ = try await task.value - + let task2StartedSubject = SingleValueSubject() let task2 = Task { for try await value in subject.handleEvents(receiveMakeIterator: { @@ -198,19 +198,19 @@ struct CurrentValueSubjectTests { try await task2StartedSubject.execute() subject.send(4) - + _ = try await task2.value let values = await test.values print(values) #expect(await test.values == [1, 2, 3]) } - + @Test func currentValueSubjectCanCompleteWithError() async throws { enum Err: Error, Equatable { case e1 } - + actor Test { var values = [Int]() func appendValue(_ value: Int) { @@ -227,17 +227,17 @@ struct CurrentValueSubjectTests { await test.appendValue(value) } } - + #expect(await test.values.count < 2) try await taskStartedSubject.execute() subject.send(completion: .failure(Err.e1)) subject.send(3) subject.send(completion: .finished) - + let task1Result = await task.result try #require(throws: Err.e1, performing: { try task1Result.get() }) - + let task2StartedSubject = SingleValueSubject() let task2 = Task { for try await value in subject.handleEvents(receiveMakeIterator: { @@ -250,13 +250,13 @@ struct CurrentValueSubjectTests { try await task2StartedSubject.execute() subject.send(4) - + let task2Result = await task2.result try #require(throws: Err.e1, performing: { try task2Result.get() }) #expect(await test.values == [1]) } - + @Test func currentValueSubjectCanSendVoidValues() async throws { actor Test { var count = 0 @@ -275,7 +275,7 @@ struct CurrentValueSubjectTests { await test.appendValue() } } - + try await taskStartedSubject.execute() #expect(await test.count < 2) @@ -283,9 +283,9 @@ struct CurrentValueSubjectTests { subject.send() subject.send() subject.send(completion: .finished) - + _ = try await task.value - + let task2StartedSubject = SingleValueSubject() let task2 = Task { for try await _ in subject.handleEvents(receiveMakeIterator: { @@ -298,7 +298,7 @@ struct CurrentValueSubjectTests { try await task2StartedSubject.execute() subject.send() - + _ = try await task2.value #expect(await test.count == 3) diff --git a/Tests/AfluentTests/DeferredTaskTests.swift b/Tests/AfluentTests/DeferredTaskTests.swift index bfdc9b905..8b0e3411f 100644 --- a/Tests/AfluentTests/DeferredTaskTests.swift +++ b/Tests/AfluentTests/DeferredTaskTests.swift @@ -1,6 +1,7 @@ +import ConcurrencyExtras import Testing + @testable import Afluent -import ConcurrencyExtras struct AfluentTests { @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) diff --git a/Tests/AfluentTests/ExponentialBackoffTests.swift b/Tests/AfluentTests/ExponentialBackoffTests.swift index aac9e1070..a1475cda2 100644 --- a/Tests/AfluentTests/ExponentialBackoffTests.swift +++ b/Tests/AfluentTests/ExponentialBackoffTests.swift @@ -5,11 +5,10 @@ // Created by Tyler Thompson on 9/19/24. // -import Testing -import Foundation -import Clocks - import Afluent +import Clocks +import Foundation +import Testing struct ExponentialbackoffTests { @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) @@ -31,7 +30,11 @@ struct ExponentialbackoffTests { continuation.yield() } .tryMap { _ in throw GeneralError.e1 } - .retry(RetryByBackoffStrategy(ExponentialBackoffStrategy(base: 2, maxCount: 4), clock: clock, durationUnit: { .seconds($0) })) + .retry( + RetryByBackoffStrategy( + ExponentialBackoffStrategy(base: 2, maxCount: 4), clock: clock, + durationUnit: { .seconds($0) }) + ) .catch { err in DeferredTask { continuation.finish() @@ -39,7 +42,7 @@ struct ExponentialbackoffTests { } } .run() - + #expect(await test.arr.count == 0) await clock.advance(by: .seconds(2)) await iterator.next() @@ -56,7 +59,7 @@ struct ExponentialbackoffTests { await clock.advance(by: .seconds(16)) #expect(await iterator.next() == nil) } - + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) @Test func taskCanRetryADefinedNumberOfTimes_WithMaxDelay() async throws { actor Test { @@ -76,9 +79,13 @@ struct ExponentialbackoffTests { continuation.yield() } .tryMap { _ in throw GeneralError.e1 } - .retry(RetryByBackoffStrategy(ExponentialBackoffStrategy(base: 2, maxCount: 4, maxDelay: .seconds(1)), clock: clock, durationUnit: { .seconds($0) })) + .retry( + RetryByBackoffStrategy( + ExponentialBackoffStrategy(base: 2, maxCount: 4, maxDelay: .seconds(1)), + clock: clock, durationUnit: { .seconds($0) }) + ) .run() - + #expect(await test.arr.count == 0) await clock.advance(by: .seconds(1)) await iterator.next() diff --git a/Tests/AfluentTests/PassthroughSubjectTests.swift b/Tests/AfluentTests/PassthroughSubjectTests.swift index 79aca6ad5..2a884805c 100644 --- a/Tests/AfluentTests/PassthroughSubjectTests.swift +++ b/Tests/AfluentTests/PassthroughSubjectTests.swift @@ -5,8 +5,8 @@ // Created by Tyler Thompson on 10/12/24. // -import Testing @_spi(Experimental) import Afluent +import Testing struct PassthroughSubjectTests { @Test func passthroughSubjectCanSendValuesAndFinish() async throws { @@ -19,7 +19,7 @@ struct PassthroughSubjectTests { let test = Test() let subject = PassthroughSubject() let taskStartedSubject = SingleValueSubject() - + let task = Task { for try await value in subject.handleEvents(receiveMakeIterator: { try? taskStartedSubject.send() @@ -27,16 +27,16 @@ struct PassthroughSubjectTests { await test.appendValue(value) } } - + try await taskStartedSubject.execute() - + #expect(await test.values.isEmpty) - + subject.send(1) subject.send(2) subject.send(3) subject.send(completion: .finished) - + _ = try await task.value let val = await test.values @@ -44,7 +44,7 @@ struct PassthroughSubjectTests { #expect(await test.values == [1, 2, 3]) } - + @Test func passthroughSubjectCanSendValues_ToMultipleConsumers_AndFinish() async throws { actor Test { var values = [Int]() @@ -55,7 +55,7 @@ struct PassthroughSubjectTests { let test = Test() let test2 = Test() let subject = PassthroughSubject() - + let taskStartedSubject = SingleValueSubject() let task = Task { for try await value in subject.handleEvents(receiveMakeIterator: { @@ -64,7 +64,7 @@ struct PassthroughSubjectTests { await test.appendValue(value) } } - + let task2StartedSubject = SingleValueSubject() let task2 = Task { for try await value in subject.handleEvents(receiveMakeIterator: { @@ -73,10 +73,10 @@ struct PassthroughSubjectTests { await test2.appendValue(value) } } - + try await taskStartedSubject.execute() try await task2StartedSubject.execute() - + #expect(await test.values.isEmpty) #expect(await test2.values.isEmpty) @@ -84,14 +84,14 @@ struct PassthroughSubjectTests { subject.send(2) subject.send(3) subject.send(completion: .finished) - + _ = try await task.value _ = try await task2.value #expect(await test.values == [1, 2, 3]) #expect(await test2.values == [1, 2, 3]) } - + @Test func passthroughSubjectStopsSendingValuesUponFinish() async throws { actor Test { var values = [Int]() @@ -110,18 +110,18 @@ struct PassthroughSubjectTests { await test.appendValue(value) } } - + try await taskStartedSubject.execute() #expect(await test.values.isEmpty) - + subject.send(1) subject.send(2) subject.send(3) subject.send(completion: .finished) - + _ = try await task.value - + let task2StartedSubject = SingleValueSubject() let task2 = Task { for try await value in subject.handleEvents(receiveMakeIterator: { @@ -134,17 +134,17 @@ struct PassthroughSubjectTests { try await task2StartedSubject.execute() subject.send(4) - + _ = try await task2.value #expect(await test.values == [1, 2, 3]) } - + @Test func passthroughSubjectCanCompleteWithError() async throws { enum Err: Error, Equatable { case e1 } - + actor Test { var values = [Int]() func appendValue(_ value: Int) { @@ -161,18 +161,18 @@ struct PassthroughSubjectTests { await test.appendValue(value) } } - + #expect(await test.values.isEmpty) - + subject.send(1) try await taskStartedSubject.execute() subject.send(completion: .failure(Err.e1)) subject.send(3) subject.send(completion: .finished) - + let task1Result = await task.result try #require(throws: Err.e1, performing: { try task1Result.get() }) - + let task2StartedSubject = SingleValueSubject() let task2 = Task { for try await value in subject.handleEvents(receiveMakeIterator: { @@ -185,13 +185,13 @@ struct PassthroughSubjectTests { try await task2StartedSubject.execute() subject.send(4) - + let task2Result = await task2.result try #require(throws: Err.e1, performing: { try task2Result.get() }) #expect(await test.values == [1]) } - + @Test func passthroughSubjectCanSendVoidValues() async throws { actor Test { var count = 0 @@ -210,18 +210,18 @@ struct PassthroughSubjectTests { await test.appendValue() } } - + try await taskStartedSubject.execute() #expect(await test.count == 0) - + subject.send() subject.send() subject.send() subject.send(completion: .finished) - + _ = try await task.value - + let task2StartedSubject = SingleValueSubject() let task2 = Task { for try await _ in subject.handleEvents(receiveMakeIterator: { @@ -234,7 +234,7 @@ struct PassthroughSubjectTests { try await task2StartedSubject.execute() subject.send() - + _ = try await task2.value #expect(await test.count == 3) diff --git a/Tests/AfluentTests/QueueExecutorTests.swift b/Tests/AfluentTests/QueueExecutorTests.swift index f69ee251f..ed0e60954 100644 --- a/Tests/AfluentTests/QueueExecutorTests.swift +++ b/Tests/AfluentTests/QueueExecutorTests.swift @@ -5,80 +5,82 @@ // Created by Annalise Mariottini on 10/10/24. // -@testable import Afluent import Foundation import Testing -#if swift(>=6) -struct QueueExecutorTests { - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - @Test func runsOnExpectedQueue() async throws { - await Task.detached(executorPreference: .mainQueue) { - dispatchPrecondition(condition: .onQueue(.main)) - }.value +@testable import Afluent - await Task.detached(executorPreference: .globalQueue(qos: .background)) { - dispatchPrecondition(condition: .onQueue(.global(qos: .background))) - }.value +#if swift(>=6) + struct QueueExecutorTests { + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + @Test func runsOnExpectedQueue() async throws { + await Task.detached(executorPreference: .mainQueue) { + dispatchPrecondition(condition: .onQueue(.main)) + }.value - let queue = DispatchQueue(label: "\(String(describing: Self.self))\(UUID().uuidString)") - await Task.detached(executorPreference: .queue(queue)) { - dispatchPrecondition(condition: .onQueue(queue)) - }.value - } + await Task.detached(executorPreference: .globalQueue(qos: .background)) { + dispatchPrecondition(condition: .onQueue(.global(qos: .background))) + }.value - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - @Test func hasSharedMainQueueInstance() { - let executor1 = QueueExecutor.mainQueue - let executor2 = QueueExecutor.mainQueue - #expect(executor1 === executor2) - } + let queue = DispatchQueue(label: "\(String(describing: Self.self))\(UUID().uuidString)") + await Task.detached(executorPreference: .queue(queue)) { + dispatchPrecondition(condition: .onQueue(queue)) + }.value + } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - @Test(arguments: [ - DispatchQoS.QoSClass.background, .default, .unspecified, .userInitiated, .userInteractive, - ]) func hasSharedGlobalQOSInstances(qos: DispatchQoS.QoSClass) { - let executor1 = QueueExecutor.globalQueue(qos: qos) - let executor2 = QueueExecutor.globalQueue(qos: qos) - #expect(executor1 === executor2) - } + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + @Test func hasSharedMainQueueInstance() { + let executor1 = QueueExecutor.mainQueue + let executor2 = QueueExecutor.mainQueue + #expect(executor1 === executor2) + } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - @Test func forwardsTaskLocalsWhenUsed() async throws { - enum Context { - @TaskLocal static var value: String? + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + @Test(arguments: [ + DispatchQoS.QoSClass.background, .default, .unspecified, .userInitiated, + .userInteractive, + ]) func hasSharedGlobalQOSInstances(qos: DispatchQoS.QoSClass) { + let executor1 = QueueExecutor.globalQueue(qos: qos) + let executor2 = QueueExecutor.globalQueue(qos: qos) + #expect(executor1 === executor2) } - let expectedValue = UUID().uuidString + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + @Test func forwardsTaskLocalsWhenUsed() async throws { + enum Context { + @TaskLocal static var value: String? + } - #expect(Context.value == nil) + let expectedValue = UUID().uuidString - await Context.$value.withValue(expectedValue) { - await Task(executorPreference: .mainQueue) { - dispatchPrecondition(condition: .onQueue(.main)) - #expect(Context.value == expectedValue) - }.value - } + #expect(Context.value == nil) - await Context.$value.withValue(expectedValue) { - await Task(executorPreference: .globalQueue(qos: .background)) { - dispatchPrecondition(condition: .onQueue(.global(qos: .background))) - #expect(Context.value == expectedValue) - }.value - } + await Context.$value.withValue(expectedValue) { + await Task(executorPreference: .mainQueue) { + dispatchPrecondition(condition: .onQueue(.main)) + #expect(Context.value == expectedValue) + }.value + } - let queue = DispatchQueue(label: "\(String(describing: Self.self))\(UUID().uuidString)") - await Context.$value.withValue(expectedValue) { - await Task(executorPreference: .queue(queue)) { - dispatchPrecondition(condition: .onQueue(queue)) - #expect(Context.value == expectedValue) - }.value + await Context.$value.withValue(expectedValue) { + await Task(executorPreference: .globalQueue(qos: .background)) { + dispatchPrecondition(condition: .onQueue(.global(qos: .background))) + #expect(Context.value == expectedValue) + }.value + } + + let queue = DispatchQueue(label: "\(String(describing: Self.self))\(UUID().uuidString)") + await Context.$value.withValue(expectedValue) { + await Task(executorPreference: .queue(queue)) { + dispatchPrecondition(condition: .onQueue(queue)) + #expect(Context.value == expectedValue) + }.value + } } } -} -#if os(Linux) -extension DispatchQoS.QoSClass: @unchecked Sendable { } -#endif + #if os(Linux) + extension DispatchQoS.QoSClass: @unchecked Sendable {} + #endif #endif diff --git a/Tests/AfluentTests/SequenceTests/CollectSequenceTests.swift b/Tests/AfluentTests/SequenceTests/CollectSequenceTests.swift index bed4d2193..b1fad2568 100644 --- a/Tests/AfluentTests/SequenceTests/CollectSequenceTests.swift +++ b/Tests/AfluentTests/SequenceTests/CollectSequenceTests.swift @@ -13,7 +13,9 @@ struct CollectSequenceTests { @Test func testCollectWithEmptySequence() async throws { let emptySequence = [Int]().async let collected = try await emptySequence.collect().first() - try #expect(#require(collected).isEmpty, "Collect should return an empty array for an empty sequence") + try #expect( + #require(collected).isEmpty, + "Collect should return an empty array for an empty sequence") } @Test func testCollectWithNonEmptySequence() async throws { diff --git a/Tests/AfluentTests/SequenceTests/DeferredSequenceTests.swift b/Tests/AfluentTests/SequenceTests/DeferredSequenceTests.swift index a56e160c1..cd95a4b92 100644 --- a/Tests/AfluentTests/SequenceTests/DeferredSequenceTests.swift +++ b/Tests/AfluentTests/SequenceTests/DeferredSequenceTests.swift @@ -12,9 +12,13 @@ import Foundation import Testing struct DeferredTests { - @Test(.disabled(if: SwiftVersion.isSwift6, "There's some kind of Xcode 16 bug where this crashes intermittently")) func upstreamSequenceDefersExecutionUntilIteration() async throws { + @Test( + .disabled( + if: SwiftVersion.isSwift6, + "There's some kind of Xcode 16 bug where this crashes intermittently")) + func upstreamSequenceDefersExecutionUntilIteration() async throws { let started = ManagedAtomic(false) - let sent = Array(0 ... 9) + let sent = Array(0...9) let sequence = Deferred { defer { @@ -44,7 +48,7 @@ struct DeferredTests { } @Test func returnsANewIteratorEachTime() async throws { - let sent = Array(0 ... 9) + let sent = Array(0...9) let sequence = Deferred { AsyncStream(Int.self) { continuation in @@ -62,13 +66,13 @@ struct DeferredTests { } try await withThrowingTaskGroup(of: Void.self) { group in - for _ in 0 ... 9 { + for _ in 0...9 { group.addTask { try await iterate() } } - for try await _ in group { } + for try await _ in group {} } } @@ -100,7 +104,7 @@ struct DeferredTests { } } - for try await _ in sequence.retry() { } + for try await _ in sequence.retry() {} #expect(await test.upstreamCount == 2) } @@ -110,7 +114,7 @@ struct DeferredTests { let sequence = Deferred> { AsyncStream { _ in } } let task = Task { - for try await _ in sequence { } + for try await _ in sequence {} } task.cancel() diff --git a/Tests/AfluentTests/SequenceTests/DelaySequenceTests.swift b/Tests/AfluentTests/SequenceTests/DelaySequenceTests.swift index 2e5260117..6fb7121a9 100644 --- a/Tests/AfluentTests/SequenceTests/DelaySequenceTests.swift +++ b/Tests/AfluentTests/SequenceTests/DelaySequenceTests.swift @@ -68,9 +68,12 @@ struct DelaySequenceTests { let elapsedTime = startTime.duration(to: currentTime) if count == 1 { - #expect(elapsedTime >= delayDuration, "Element \(count) was not delayed correctly.") + #expect( + elapsedTime >= delayDuration, "Element \(count) was not delayed correctly.") } else { - #expect(elapsedTime < delayDuration * count, "Element \(count) was not delayed correctly.") + #expect( + elapsedTime < delayDuration * count, + "Element \(count) was not delayed correctly.") } } return count @@ -105,12 +108,16 @@ struct DelaySequenceTests { let elapsedTime = startTime.duration(to: currentTime) if count == 1 { - #expect(elapsedTime >= delayDuration, "Element \(count) was not delayed correctly.") + #expect( + elapsedTime >= delayDuration, + "Element \(count) was not delayed correctly.") await clock.advance(by: .milliseconds(15)) continuation.yield(2) Task { await clock.advance(by: delayDuration) } } else { - #expect(elapsedTime >= delayDuration + .milliseconds(15), "Element \(count) was not delayed correctly.") + #expect( + elapsedTime >= delayDuration + .milliseconds(15), + "Element \(count) was not delayed correctly.") continuation.finish() } } diff --git a/Tests/AfluentTests/SequenceTests/GroupBySequenceTests.swift b/Tests/AfluentTests/SequenceTests/GroupBySequenceTests.swift index 3f2f79ca9..841367efe 100644 --- a/Tests/AfluentTests/SequenceTests/GroupBySequenceTests.swift +++ b/Tests/AfluentTests/SequenceTests/GroupBySequenceTests.swift @@ -9,6 +9,7 @@ import Clocks import ConcurrencyExtras import Foundation import Testing + @testable import Afluent struct GroupBySequenceTests { @@ -21,7 +22,7 @@ struct GroupBySequenceTests { let stream = AsyncStream { _ in }.groupBy { $0 } let task = Task { - for try await _ in stream { } + for try await _ in stream {} } task.cancel() @@ -191,7 +192,9 @@ struct GroupBySequenceTests { } } - @Test func groupByWithPopulatedSequenceGroupsByKeysWithSequences_completingWithError() async throws { + @Test func groupByWithPopulatedSequenceGroupsByKeysWithSequences_completingWithError() + async throws + { let stream = AsyncThrowingStream { continuation in continuation.yield("a") continuation.yield("b") diff --git a/Tests/AfluentTests/SequenceTests/HandleEventsSequenceTests.swift b/Tests/AfluentTests/SequenceTests/HandleEventsSequenceTests.swift index 1a9bbe3b7..48aab614b 100644 --- a/Tests/AfluentTests/SequenceTests/HandleEventsSequenceTests.swift +++ b/Tests/AfluentTests/SequenceTests/HandleEventsSequenceTests.swift @@ -17,7 +17,7 @@ struct HandleEventsSequenceTests { let iteratorMade = ManagedAtomic(false) _ = await Task { - _ = DeferredTask { } + _ = DeferredTask {} .toAsyncSequence() .handleEvents(receiveMakeIterator: { iteratorMade.store(true, ordering: .sequentiallyConsistent) @@ -38,21 +38,21 @@ struct HandleEventsSequenceTests { } let test = Test() - let values = Array(0 ... 9) + let values = Array(0...9) let task = Task { let sequence = values.async.handleEvents(receiveNext: { await test.next() }) - for try await _ in sequence { } + for try await _ in sequence {} } try await task.value let nextCalled = await test.nextCalled - #expect(nextCalled == values.count + 1) // values + finish + #expect(nextCalled == values.count + 1) // values + finish } @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, *) @@ -91,7 +91,7 @@ struct HandleEventsSequenceTests { .handleEvents(receiveComplete: { exp() }) - for try await _ in sequence { } + for try await _ in sequence {} }.result } } @@ -140,15 +140,16 @@ struct HandleEventsSequenceTests { await withCheckedContinuation { continuation in Task { - await test.setTask(DeferredTask { - await test.task?.cancel() - } - .toAsyncSequence() - .handleEvents(receiveCancel: { - await test.cancel() - continuation.resume() - }) - .sink()) + await test.setTask( + DeferredTask { + await test.task?.cancel() + } + .toAsyncSequence() + .handleEvents(receiveCancel: { + await test.cancel() + continuation.resume() + }) + .sink()) } } diff --git a/Tests/AfluentTests/SequenceTests/MaterializeSequenceTests.swift b/Tests/AfluentTests/SequenceTests/MaterializeSequenceTests.swift index 451d3bb32..80d524d13 100644 --- a/Tests/AfluentTests/SequenceTests/MaterializeSequenceTests.swift +++ b/Tests/AfluentTests/SequenceTests/MaterializeSequenceTests.swift @@ -97,21 +97,22 @@ struct MaterializeSequenceTests { await withCheckedContinuation { continuation in Task { - await test.setTask(Task { - _ = try await DeferredTask { - await test.start() - await test.task?.cancel() - } - .handleEvents(receiveCancel: { - continuation.resume() + await test.setTask( + Task { + _ = try await DeferredTask { + await test.start() + await test.task?.cancel() + } + .handleEvents(receiveCancel: { + continuation.resume() + }) + .map { + await test.end() + } + .toAsyncSequence() + .materialize() + .first() }) - .map { - await test.end() - } - .toAsyncSequence() - .materialize() - .first() - }) } } diff --git a/Tests/AfluentTests/SequenceTests/RetryAfterFlatMappingSequenceTests.swift b/Tests/AfluentTests/SequenceTests/RetryAfterFlatMappingSequenceTests.swift index 0c5bd5009..4c9a30941 100644 --- a/Tests/AfluentTests/SequenceTests/RetryAfterFlatMappingSequenceTests.swift +++ b/Tests/AfluentTests/SequenceTests/RetryAfterFlatMappingSequenceTests.swift @@ -19,7 +19,7 @@ struct RetryAfterFlatMappingSequenceTests { } let test = Test() - let retryCount = UInt.random(in: 2 ... 10) + let retryCount = UInt.random(in: 2...10) let t = Task { try await DeferredTask { @@ -133,7 +133,7 @@ struct RetryAfterFlatMappingSequenceTests { let copy = await test.arr #expect(UInt(copy.count) == 1) } - + @Test func taskCanRetryADefinedNumberOfTimes_WithStrategy() async throws { actor Test { var arr = [String]() @@ -143,7 +143,7 @@ struct RetryAfterFlatMappingSequenceTests { } let test = Test() - let retryCount = UInt.random(in: 2 ... 10) + let retryCount = UInt.random(in: 2...10) let t = Task { try await DeferredTask { diff --git a/Tests/AfluentTests/SequenceTests/RetryOnAfterFlatMappingSequenceTests.swift b/Tests/AfluentTests/SequenceTests/RetryOnAfterFlatMappingSequenceTests.swift index c3cc6ab18..a93d2f988 100644 --- a/Tests/AfluentTests/SequenceTests/RetryOnAfterFlatMappingSequenceTests.swift +++ b/Tests/AfluentTests/SequenceTests/RetryOnAfterFlatMappingSequenceTests.swift @@ -22,7 +22,7 @@ struct RetryOnAfterFlatMappingSequenceTests { } let test = Test() - let retryCount = UInt.random(in: 2 ... 10) + let retryCount = UInt.random(in: 2...10) let t = Task { try await DeferredTask { @@ -145,7 +145,7 @@ struct RetryOnAfterFlatMappingSequenceTests { let copy = await test.arr #expect(UInt(copy.count) == 1) } - + @Test func taskCanRetryADefinedNumberOfTimes_WithStrategy() async throws { enum Err: Error, Equatable { case e1 @@ -158,7 +158,7 @@ struct RetryOnAfterFlatMappingSequenceTests { } let test = Test() - let retryCount = UInt.random(in: 2 ... 10) + let retryCount = UInt.random(in: 2...10) let t = Task { try await DeferredTask { diff --git a/Tests/AfluentTests/SequenceTests/RetryOnSequenceTests.swift b/Tests/AfluentTests/SequenceTests/RetryOnSequenceTests.swift index 6fb29c26e..5281fb73e 100644 --- a/Tests/AfluentTests/SequenceTests/RetryOnSequenceTests.swift +++ b/Tests/AfluentTests/SequenceTests/RetryOnSequenceTests.swift @@ -22,7 +22,7 @@ struct RetryOnSequenceTests { } let test = Test() - let retryCount = UInt.random(in: 2 ... 10) + let retryCount = UInt.random(in: 2...10) let t = Task { try await DeferredTask { diff --git a/Tests/AfluentTests/SequenceTests/RetrySequenceTests.swift b/Tests/AfluentTests/SequenceTests/RetrySequenceTests.swift index 929d5a519..1829d2b90 100644 --- a/Tests/AfluentTests/SequenceTests/RetrySequenceTests.swift +++ b/Tests/AfluentTests/SequenceTests/RetrySequenceTests.swift @@ -19,7 +19,7 @@ struct RetrySequenceTests { } let test = Test() - let retryCount = UInt.random(in: 2 ... 10) + let retryCount = UInt.random(in: 2...10) let t = Task { try await DeferredTask { @@ -135,7 +135,7 @@ struct RetrySequenceTests { let copy = await test.arr #expect(UInt(copy.count) == 1) } - + @Test func taskCanRetryADefinedNumberOfTimes_WithStrategy() async throws { actor Test { var arr = [String]() @@ -145,7 +145,7 @@ struct RetrySequenceTests { } let test = Test() - let retryCount = UInt.random(in: 2 ... 10) + let retryCount = UInt.random(in: 2...10) let t = Task { try await DeferredTask { @@ -188,7 +188,9 @@ struct RetrySequenceTests { #expect(UInt(copy.count) == 1) } - @Test func taskWithMultipleRetries_OnlyRetriesTheSpecifiedNumberOfTimes_WithStrategy() async throws { + @Test func taskWithMultipleRetries_OnlyRetriesTheSpecifiedNumberOfTimes_WithStrategy() + async throws + { actor Test { var arr = [String]() func append(_ str: String) { diff --git a/Tests/AfluentTests/SequenceTests/ShareSequenceTests.swift b/Tests/AfluentTests/SequenceTests/ShareSequenceTests.swift index c97f1ea1e..fc3c41235 100644 --- a/Tests/AfluentTests/SequenceTests/ShareSequenceTests.swift +++ b/Tests/AfluentTests/SequenceTests/ShareSequenceTests.swift @@ -16,10 +16,8 @@ // Modified by Tyler Thompson on 9/29/24. // -import Testing -import AsyncAlgorithms - @_spi(Experimental) import Afluent +import Testing @Suite struct ShareSequenceTests { @Test func BasicBroadcasting() async { @@ -37,9 +35,9 @@ import AsyncAlgorithms } #expect(results[0] == results[1]) } - + @Test func BasicBroadcastingFromChannel() async { - let base = AsyncChannel() + let (base, continuation) = AsyncStream.makeStream() let a = base.broadcast() let b = a let results = await withTaskGroup(of: [Int].self) { group in @@ -47,9 +45,9 @@ import AsyncAlgorithms var sent = [Int]() for i in 0..<10 { sent.append(i) - await base.send(i) + continuation.yield(i) } - base.finish() + continuation.finish() return sent } group.addTask { @@ -62,14 +60,14 @@ import AsyncAlgorithms } #expect(results[0] == results[1]) } - + @Test func ABaseSequence_BroadcastingToTwoTasks_TheBaseSequenceIsIteratedOnce() async { // Given let elements = (0..<10).map { $0 } let base = ReportingAsyncSequence(elements) - + let expectedNexts = elements.map { _ in ReportingAsyncSequence.Event.next } - + // When let broadcasted = base.broadcast() await withTaskGroup(of: Void.self) { group in @@ -81,16 +79,19 @@ import AsyncAlgorithms } await group.waitForAll() } - + // Then - #expect(base.events == [ReportingAsyncSequence.Event.makeAsyncIterator] + expectedNexts + [ReportingAsyncSequence.Event.next]) + #expect( + base.events == [ReportingAsyncSequence.Event.makeAsyncIterator] + expectedNexts + [ + ReportingAsyncSequence.Event.next + ]) } - + @Test func ABaseSequence_BroadcastingToTwoTasks_TheyReceiveTheBaseElements() async { // Given let base = (0..<10).map { $0 } let expected = (0...4).map { $0 } - + // When let broadcasted = base.async.map { try throwOn(5, $0) }.broadcast() let results = await withTaskGroup(of: [Int].self) { group in @@ -104,7 +105,7 @@ import AsyncAlgorithms } catch { #expect(error is Failure) } - + return received } group.addTask { @@ -117,22 +118,24 @@ import AsyncAlgorithms } catch { #expect(error is Failure) } - + return received } - + return await Array(group) } - + // Then #expect(results[0] == expected) #expect(results[0] == results[1]) } - - @Test func AThrowingBaseSequence_BroadcastingToTwoTasks_TheyReceiveTheBaseElementsAndFailure() async { + + @Test func AThrowingBaseSequence_BroadcastingToTwoTasks_TheyReceiveTheBaseElementsAndFailure() + async + { // Given let base = (0..<10).map { $0 } - + // When let broadcasted = base.async.broadcast() let results = await withTaskGroup(of: [Int].self) { group in @@ -144,46 +147,46 @@ import AsyncAlgorithms } return await Array(group) } - + // Then #expect(results[0] == base) #expect(results[0] == results[1]) } - + @Test func ABaseSequence_BroadcastingToTwoTasks_TheyReceiveFinishAndPastEndIsNil() async { // Given let base = (0..<10).map { $0 } - + // When let broadcasted = base.async.broadcast() await withTaskGroup(of: Void.self) { group in group.addTask { var iterator = broadcasted.makeAsyncIterator() - while let _ = await iterator.next() {} + while await iterator.next() != nil {} let pastEnd = await iterator.next() - + // Then #expect(pastEnd == nil) } group.addTask { var iterator = broadcasted.makeAsyncIterator() - while let _ = await iterator.next() {} + while await iterator.next() != nil {} let pastEnd = await iterator.next() - + // Then #expect(pastEnd == nil) } - + await group.waitForAll() } } - + @Test func ABaseSequence_BroadcastingToTwoTasks_TheBufferIsUsed() async { let task1IsIsFinished = SingleValueSubject() - + // Given let base = (0..<10).map { $0 } - + // When let broadcasted = base.async.broadcast() let results = await withTaskGroup(of: [Int].self) { group in @@ -198,26 +201,26 @@ import AsyncAlgorithms let firstElement = await iterator.next() result.append(firstElement!) try? await task1IsIsFinished.execute() - + while let element = await iterator.next() { result.append(element) } - + return result } return await Array(group) } - + // Then #expect(results[0] == base) #expect(results[0] == results[1]) } - + @Test func AChannel_BroadcastingToTwoTasks_TheyReceivedTheChannelElements() async { // Given let elements = (0..<10).map { $0 } - let base = AsyncChannel() - + let (base, continuation) = AsyncStream.makeStream() + // When let broadcasted = base.broadcast() let results = await withTaskGroup(of: [Int].self) { group in @@ -225,9 +228,9 @@ import AsyncAlgorithms var sent = [Int]() for element in elements { sent.append(element) - await base.send(element) + continuation.yield(element) } - base.finish() + continuation.finish() return sent } group.addTask { @@ -238,7 +241,7 @@ import AsyncAlgorithms } return await Array(group) } - + // Then #expect(results[0] == elements) #expect(results[0] == results[1]) @@ -249,22 +252,22 @@ final class ReportingSequence: Sequence, IteratorProtocol { enum Event: Equatable, CustomStringConvertible { case next case makeIterator - + var description: String { switch self { - case .next: return "next()" - case .makeIterator: return "makeIterator()" + case .next: return "next()" + case .makeIterator: return "makeIterator()" } } } - + var events = [Event]() var elements: [Element] - + init(_ elements: [Element]) { self.elements = elements } - + func next() -> Element? { events.append(.next) guard elements.count > 0 else { @@ -272,33 +275,35 @@ final class ReportingSequence: Sequence, IteratorProtocol { } return elements.removeFirst() } - + func makeIterator() -> ReportingSequence { events.append(.makeIterator) return self } } -final class ReportingAsyncSequence: AsyncSequence, AsyncIteratorProtocol, @unchecked Sendable { +final class ReportingAsyncSequence: AsyncSequence, AsyncIteratorProtocol, + @unchecked Sendable +{ enum Event: Equatable, CustomStringConvertible { case next case makeAsyncIterator - + var description: String { switch self { - case .next: return "next()" - case .makeAsyncIterator: return "makeAsyncIterator()" + case .next: return "next()" + case .makeAsyncIterator: return "makeAsyncIterator()" } } } - + var events = [Event]() var elements: [Element] - + init(_ elements: [Element]) { self.elements = elements } - + func next() async -> Element? { events.append(.next) guard elements.count > 0 else { @@ -306,14 +311,14 @@ final class ReportingAsyncSequence: AsyncSequence, AsyncItera } return elements.removeFirst() } - + func makeAsyncIterator() -> ReportingAsyncSequence { events.append(.makeAsyncIterator) return self } } -struct Failure: Error, Equatable { } +struct Failure: Error, Equatable {} func throwOn(_ toThrowOn: T, _ value: T) throws -> T { if value == toThrowOn { @@ -321,3 +326,24 @@ func throwOn(_ toThrowOn: T, _ value: T) throws -> T { } return value } + +extension Array where Element: Sendable { + fileprivate var async: AsyncStream { + AsyncStream { + for item in self { + $0.yield(item) + } + $0.finish() + } + } +} + +extension RangeReplaceableCollection { + fileprivate init(_ source: Source) async rethrows + where Source.Element == Element { + self.init() + for try await item in source { + append(item) + } + } +} diff --git a/Tests/AfluentTests/SequenceTests/ThrottleSequenceTests.swift b/Tests/AfluentTests/SequenceTests/ThrottleSequenceTests.swift index 861533d4a..8052d5243 100644 --- a/Tests/AfluentTests/SequenceTests/ThrottleSequenceTests.swift +++ b/Tests/AfluentTests/SequenceTests/ThrottleSequenceTests.swift @@ -30,10 +30,11 @@ struct ThrottleSequenceTests { await withMainSerialExecutor { let testClock = TestClock() - let stream = AsyncStream { _ in }.throttle(for: .milliseconds(10), clock: testClock, latest: true) + let stream = AsyncStream { _ in }.throttle( + for: .milliseconds(10), clock: testClock, latest: true) let task = Task { - for try await _ in stream { } + for try await _ in stream {} } task.cancel() @@ -49,10 +50,11 @@ struct ThrottleSequenceTests { await withMainSerialExecutor { let testClock = TestClock() - let stream = AsyncStream { _ in }.throttle(for: .milliseconds(10), clock: testClock, latest: false) + let stream = AsyncStream { _ in }.throttle( + for: .milliseconds(10), clock: testClock, latest: false) let task = Task { - for try await _ in stream { } + for try await _ in stream {} } task.cancel() @@ -91,19 +93,21 @@ struct ThrottleSequenceTests { let (stream, continuation) = AsyncThrowingStream.makeStream() let testClock = TestClock() let test = ElementContainer() - let throttledStream = stream.throttle(for: .milliseconds(10), clock: testClock, latest: true) + let throttledStream = stream.throttle( + for: .milliseconds(10), clock: testClock, latest: true) Task { for try await el in throttledStream { await test.append(el) } } let advancedDuration = ManagedAtomic(0) - await parseThrottleDSL(streamInput: streamInput, - expectedOutput: expectedOutput, - testClock: testClock, - advancedDuration: advancedDuration, - continuation: continuation, - test: test) + await parseThrottleDSL( + streamInput: streamInput, + expectedOutput: expectedOutput, + testClock: testClock, + advancedDuration: advancedDuration, + continuation: continuation, + test: test) } } } @@ -136,25 +140,31 @@ struct ThrottleSequenceTests { let (stream, continuation) = AsyncThrowingStream.makeStream() let testClock = TestClock() let test = ElementContainer() - let throttledStream = stream.throttle(for: .milliseconds(10), clock: testClock, latest: false) + let throttledStream = stream.throttle( + for: .milliseconds(10), clock: testClock, latest: false) Task { for try await el in throttledStream { await test.append(el) } } let advancedDuration = ManagedAtomic(0) - await parseThrottleDSL(streamInput: streamInput, - expectedOutput: expectedOutput, - testClock: testClock, - advancedDuration: advancedDuration, - continuation: continuation, - test: test) + await parseThrottleDSL( + streamInput: streamInput, + expectedOutput: expectedOutput, + testClock: testClock, + advancedDuration: advancedDuration, + continuation: continuation, + test: test) } } } @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, *) - fileprivate func parseThrottleDSL(streamInput: String, expectedOutput: String, testClock: TestClock, advancedDuration: ManagedAtomic, continuation: AsyncThrowingStream.Continuation, test: ElementContainer) async { + fileprivate func parseThrottleDSL( + streamInput: String, expectedOutput: String, testClock: TestClock, + advancedDuration: ManagedAtomic, + continuation: AsyncThrowingStream.Continuation, test: ElementContainer + ) async { for (i, step) in streamInput.enumerated() { if step == "-" { await testClock.advance(by: .milliseconds(10)) @@ -184,7 +194,8 @@ struct ThrottleSequenceTests { var total = 0 // Parse the expected DSL, this is tricky because you have to sort of calculate how far in time to go to understand what the expected result is var expected = expectedOutput.reduce(into: "") { partialResult, character in - guard total < advancedDuration.load(ordering: .sequentiallyConsistent) else { return } + guard total < advancedDuration.load(ordering: .sequentiallyConsistent) + else { return } if character == "-" { total += 10 } else if character == "`" { @@ -193,10 +204,14 @@ struct ThrottleSequenceTests { partialResult.append(character) }.compactMap { Int(String($0)) } // after the first interval you have to wait for the full interval to have elapsed - if !(advancedDuration.load(ordering: .sequentiallyConsistent) % 10 == 0), advancedDuration.load(ordering: .sequentiallyConsistent) > 10, !last { + if !(advancedDuration.load(ordering: .sequentiallyConsistent) % 10 == 0), + advancedDuration.load(ordering: .sequentiallyConsistent) > 10, !last + { expected = Array(expected.dropLast()) } - #expect(elements == expected, "\(i) \(advancedDuration.load(ordering: .sequentiallyConsistent)) \(total)") + #expect( + elements == expected, + "\(i) \(advancedDuration.load(ordering: .sequentiallyConsistent)) \(total)") }.result } } diff --git a/Tests/AfluentTests/SerialTaskQueueTests.swift b/Tests/AfluentTests/SerialTaskQueueTests.swift index 05a195d20..d25c44d19 100644 --- a/Tests/AfluentTests/SerialTaskQueueTests.swift +++ b/Tests/AfluentTests/SerialTaskQueueTests.swift @@ -1,3 +1,4 @@ +import Afluent // // SerialTaskQueueTests.swift // @@ -8,10 +9,9 @@ import Atomics import ConcurrencyExtras import Testing -import Afluent - struct SerialTaskQueueTests { - @Test func serialTaskQueueSchedulesOneTaskAtATime_EvenWhenSchedulingIsConcurrent() async throws { + @Test func serialTaskQueueSchedulesOneTaskAtATime_EvenWhenSchedulingIsConcurrent() async throws + { let queue = SerialTaskQueue() actor Test { var isExecuting = false @@ -21,8 +21,9 @@ struct SerialTaskQueueTests { } } let test = Test() - let results = try await withThrowingTaskGroup(of: Int.self, returning: [Int].self) { group in - for i in 1 ... 100 { + let results = try await withThrowingTaskGroup(of: Int.self, returning: [Int].self) { + group in + for i in 1...100 { group.addTask { try await queue.queue { let executing = await test.isExecuting @@ -42,7 +43,7 @@ struct SerialTaskQueueTests { return results } // Why sort the results? Because task groups don't make any guarantees, just because I asked it to schedule these in order doesn't mean it did. - #expect(results.sorted() == Array(1 ... 100)) + #expect(results.sorted() == Array(1...100)) } @Test func queuingATaskThatThrows_ThrowsToQueue() async throws { @@ -55,9 +56,11 @@ struct SerialTaskQueueTests { throw Err.e1 } }.result - #expect(throws: Err.self, performing: { - try result.get() - }) + #expect( + throws: Err.self, + performing: { + try result.get() + }) } @Test func queuingATaskThatThrows_StillAllowsOthersToBeQueued() async throws { @@ -70,9 +73,11 @@ struct SerialTaskQueueTests { throw Err.e1 } }.result - #expect(throws: Err.self, performing: { - try result.get() - }) + #expect( + throws: Err.self, + performing: { + try result.get() + }) let result2 = try await queue.queue { 2 } @@ -80,7 +85,11 @@ struct SerialTaskQueueTests { } @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - @Test(.disabled(if: SwiftVersion.isSwift6, "There's some kind of Xcode 16 bug where this crashes intermittently")) func queueCanCancelOngoingTasks() async throws { + @Test( + .disabled( + if: SwiftVersion.isSwift6, + "There's some kind of Xcode 16 bug where this crashes intermittently")) + func queueCanCancelOngoingTasks() async throws { try await withMainSerialExecutor { let sub = SingleValueSubject() let queue = SerialTaskQueue() @@ -127,10 +136,10 @@ struct SerialTaskQueueTests { enum SwiftVersion { static var isSwift6: Bool { -#if swift(>=6.0) - return true -#else - return false -#endif + #if swift(>=6.0) + return true + #else + return false + #endif } } diff --git a/Tests/AfluentTests/SubscriptionTests.swift b/Tests/AfluentTests/SubscriptionTests.swift index 9fb34a5d1..270042440 100644 --- a/Tests/AfluentTests/SubscriptionTests.swift +++ b/Tests/AfluentTests/SubscriptionTests.swift @@ -6,10 +6,10 @@ // import Afluent +import Clocks import ConcurrencyExtras import Foundation import Testing -import Clocks struct SubscriptionTests { @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, *) @@ -43,7 +43,7 @@ struct SubscriptionTests { try await testStartSubject.execute() subscription?.cancel() try cancellationSubject.send() - + try await sub.execute() let started = await test.started @@ -84,7 +84,7 @@ struct SubscriptionTests { .subscribe() noop(subscription) - + try await testStartSubject.execute() subscription = nil try cancellationSubject.send() @@ -132,7 +132,7 @@ struct SubscriptionTests { try await testStartSubject.execute() set.removeAll() try cancellationFinishedSubject.send() - + try await sub.execute() let started = await test.started @@ -176,7 +176,7 @@ struct SubscriptionTests { try await testStartSubject.execute() collection.removeAll() try cancellationFinishedSubject.send() - + try await sub.execute() let started = await test.started @@ -199,7 +199,7 @@ struct SubscriptionTests { func start() { started = true } func end() { ended = true } - + func setSubscription(_ subscription: AnyCancellable?) { self.subscription = subscription } @@ -207,22 +207,23 @@ struct SubscriptionTests { let test = Test() let sub = SingleValueSubject() - await test.setSubscription(AsyncStream { continuation in - Task { - await test.start() - continuation.yield(await test.subscription) + await test.setSubscription( + AsyncStream { continuation in + Task { + await test.start() + continuation.yield(await test.subscription) + } } - } - .map { $0?.cancel() } - .handleEvents(receiveCancel: { - try sub.send() - }) - .map { - if !Task.isCancelled { - await test.end() + .map { $0?.cancel() } + .handleEvents(receiveCancel: { + try sub.send() + }) + .map { + if !Task.isCancelled { + await test.end() + } } - } - .sink()) + .sink()) try await sub.execute() @@ -265,7 +266,7 @@ struct SubscriptionTests { try await testStartSubject.execute() subscription = nil - + await clock.advance(by: .milliseconds(10)) let started = await test.started @@ -310,7 +311,7 @@ struct SubscriptionTests { set.removeAll() await clock.advance(by: .milliseconds(10)) - + let started = await test.started let ended = await test.ended @@ -353,7 +354,7 @@ struct SubscriptionTests { collection.removeAll() await clock.advance(by: .milliseconds(10)) - + let started = await test.started let ended = await test.ended @@ -404,7 +405,7 @@ struct SubscriptionTests { try await testStartSubject.execute() subscription.cancel() - + await clock.advance(by: .milliseconds(10)) try await completedChannel.execute() @@ -582,5 +583,5 @@ struct SubscriptionTests { @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, *) extension SubscriptionTests { - func noop(_: Any?) { } + func noop(_: Any?) {} } diff --git a/Tests/AfluentTests/WorkerTests/AssignTests.swift b/Tests/AfluentTests/WorkerTests/AssignTests.swift index bec9bf909..30e700433 100644 --- a/Tests/AfluentTests/WorkerTests/AssignTests.swift +++ b/Tests/AfluentTests/WorkerTests/AssignTests.swift @@ -20,7 +20,8 @@ struct AssignTests { lock.lock() defer { lock.unlock() } return _val - } set { + } + set { lock.lock() defer { lock.unlock() } _val = newValue diff --git a/Tests/AfluentTests/WorkerTests/CancelTests.swift b/Tests/AfluentTests/WorkerTests/CancelTests.swift index 2648c372b..d2db016db 100644 --- a/Tests/AfluentTests/WorkerTests/CancelTests.swift +++ b/Tests/AfluentTests/WorkerTests/CancelTests.swift @@ -13,7 +13,7 @@ import Testing struct CancelTests { @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, *) @Test func deferredTaskCancelledBeforeItStarts() async throws { - let task = DeferredTask { } + let task = DeferredTask {} task.cancel() let res = try await task.result #expect(throws: (any Error).self) { try res.get() } @@ -36,17 +36,18 @@ struct CancelTests { let test = Test() let sub = SingleValueSubject() - await test.setTask(DeferredTask { - await test.start() - await test.task?.cancel() - } - .handleEvents(receiveCancel: { - try? sub.send() - }) - .map { - await test.end() - } - .subscribe()) + await test.setTask( + DeferredTask { + await test.start() + await test.task?.cancel() + } + .handleEvents(receiveCancel: { + try? sub.send() + }) + .map { + await test.end() + } + .subscribe()) try await sub.execute() diff --git a/Tests/AfluentTests/WorkerTests/DelayTests.swift b/Tests/AfluentTests/WorkerTests/DelayTests.swift index c68cd99b2..06ca9c06d 100644 --- a/Tests/AfluentTests/WorkerTests/DelayTests.swift +++ b/Tests/AfluentTests/WorkerTests/DelayTests.swift @@ -21,7 +21,7 @@ struct DelayTests { } let clock = TestClock() let test = Test() - DeferredTask { } + DeferredTask {} .delay(for: .milliseconds(10), clock: clock, tolerance: nil) .handleEvents(receiveOutput: { _ in await test.setFinished(true) }) .run() diff --git a/Tests/AfluentTests/WorkerTests/ExecuteOnTaskExecutorTests.swift b/Tests/AfluentTests/WorkerTests/ExecuteOnTaskExecutorTests.swift index 79b960345..37509f9f0 100644 --- a/Tests/AfluentTests/WorkerTests/ExecuteOnTaskExecutorTests.swift +++ b/Tests/AfluentTests/WorkerTests/ExecuteOnTaskExecutorTests.swift @@ -10,99 +10,99 @@ import Foundation import Testing #if swift(>=6) -struct ExecuteOnTaskExecutorTests { - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - @Test func executesOnExpectedExecutor() async throws { - try await DeferredTask { } - .handleEvents(receiveOutput: { _ in - dispatchPrecondition(condition: .onQueue(.main)) - }) - .execute(executorPreference: .mainQueue) - - try await DeferredTask { } - .handleEvents(receiveOutput: { _ in - dispatchPrecondition(condition: .onQueue(.global(qos: .background))) - }) - .execute(executorPreference: .globalQueue(qos: .background)) - - let queue = DispatchQueue(label: "\(String(describing: Self.self))\(UUID().uuidString)") - try await DeferredTask { } - .handleEvents(receiveOutput: { _ in - dispatchPrecondition(condition: .onQueue(queue)) - }) - .execute(executorPreference: .queue(queue)) + struct ExecuteOnTaskExecutorTests { + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + @Test func executesOnExpectedExecutor() async throws { + try await DeferredTask {} + .handleEvents(receiveOutput: { _ in + dispatchPrecondition(condition: .onQueue(.main)) + }) + .execute(executorPreference: .mainQueue) + + try await DeferredTask {} + .handleEvents(receiveOutput: { _ in + dispatchPrecondition(condition: .onQueue(.global(qos: .background))) + }) + .execute(executorPreference: .globalQueue(qos: .background)) + + let queue = DispatchQueue(label: "\(String(describing: Self.self))\(UUID().uuidString)") + try await DeferredTask {} + .handleEvents(receiveOutput: { _ in + dispatchPrecondition(condition: .onQueue(queue)) + }) + .execute(executorPreference: .queue(queue)) + } + + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + @Test func runsOnExpectedExecutor() async throws { + let completed1 = SingleValueSubject() + let completed2 = SingleValueSubject() + let completed3 = SingleValueSubject() + + DeferredTask {} + .handleEvents(receiveOutput: { _ in + dispatchPrecondition(condition: .onQueue(.main)) + try completed1.send() + }) + .run(executorPreference: .mainQueue) + + DeferredTask {} + .handleEvents(receiveOutput: { _ in + dispatchPrecondition(condition: .onQueue(.global(qos: .background))) + try completed2.send() + }) + .run(executorPreference: .globalQueue(qos: .background)) + + let queue = DispatchQueue(label: "\(String(describing: Self.self))\(UUID().uuidString)") + DeferredTask {} + .handleEvents(receiveOutput: { _ in + dispatchPrecondition(condition: .onQueue(queue)) + try completed3.send() + }) + .run(executorPreference: .queue(queue)) + + try await completed1.execute() + try await completed2.execute() + try await completed3.execute() + } + + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + @Test func subscribesOnExpectedExecutor() async throws { + let completed1 = SingleValueSubject() + let completed2 = SingleValueSubject() + let completed3 = SingleValueSubject() + + let sub1 = DeferredTask {} + .handleEvents(receiveOutput: { _ in + dispatchPrecondition(condition: .onQueue(.main)) + try completed1.send() + }) + .subscribe(executorPreference: .mainQueue) + + let sub2 = DeferredTask {} + .handleEvents(receiveOutput: { _ in + dispatchPrecondition(condition: .onQueue(.global(qos: .background))) + try completed2.send() + }) + .subscribe(executorPreference: .globalQueue(qos: .background)) + + let queue = DispatchQueue(label: "\(String(describing: Self.self))\(UUID().uuidString)") + let sub3 = DeferredTask {} + .handleEvents(receiveOutput: { _ in + dispatchPrecondition(condition: .onQueue(queue)) + try completed3.send() + }) + .subscribe(executorPreference: .queue(queue)) + + noop(sub1) + noop(sub2) + noop(sub3) + + try await completed1.execute() + try await completed2.execute() + try await completed3.execute() + } + + private func noop(_ any: Any) {} } - - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - @Test func runsOnExpectedExecutor() async throws { - let completed1 = SingleValueSubject() - let completed2 = SingleValueSubject() - let completed3 = SingleValueSubject() - - DeferredTask { } - .handleEvents(receiveOutput: { _ in - dispatchPrecondition(condition: .onQueue(.main)) - try completed1.send() - }) - .run(executorPreference: .mainQueue) - - DeferredTask { } - .handleEvents(receiveOutput: { _ in - dispatchPrecondition(condition: .onQueue(.global(qos: .background))) - try completed2.send() - }) - .run(executorPreference: .globalQueue(qos: .background)) - - let queue = DispatchQueue(label: "\(String(describing: Self.self))\(UUID().uuidString)") - DeferredTask { } - .handleEvents(receiveOutput: { _ in - dispatchPrecondition(condition: .onQueue(queue)) - try completed3.send() - }) - .run(executorPreference: .queue(queue)) - - try await completed1.execute() - try await completed2.execute() - try await completed3.execute() - } - - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - @Test func subscribesOnExpectedExecutor() async throws { - let completed1 = SingleValueSubject() - let completed2 = SingleValueSubject() - let completed3 = SingleValueSubject() - - let sub1 = DeferredTask { } - .handleEvents(receiveOutput: { _ in - dispatchPrecondition(condition: .onQueue(.main)) - try completed1.send() - }) - .subscribe(executorPreference: .mainQueue) - - let sub2 = DeferredTask { } - .handleEvents(receiveOutput: { _ in - dispatchPrecondition(condition: .onQueue(.global(qos: .background))) - try completed2.send() - }) - .subscribe(executorPreference: .globalQueue(qos: .background)) - - let queue = DispatchQueue(label: "\(String(describing: Self.self))\(UUID().uuidString)") - let sub3 = DeferredTask { } - .handleEvents(receiveOutput: { _ in - dispatchPrecondition(condition: .onQueue(queue)) - try completed3.send() - }) - .subscribe(executorPreference: .queue(queue)) - - noop(sub1) - noop(sub2) - noop(sub3) - - try await completed1.execute() - try await completed2.execute() - try await completed3.execute() - } - - private func noop(_ any: Any) { } -} #endif diff --git a/Tests/AfluentTests/WorkerTests/ExecutePriorityTests.swift b/Tests/AfluentTests/WorkerTests/ExecutePriorityTests.swift index e4e83bff7..b8e5320b7 100644 --- a/Tests/AfluentTests/WorkerTests/ExecutePriorityTests.swift +++ b/Tests/AfluentTests/WorkerTests/ExecutePriorityTests.swift @@ -29,7 +29,7 @@ struct ExecutePriorityTests { let expectedCurrentPriority = max(Task.currentPriority, priority) - try await DeferredTask { } + try await DeferredTask {} .handleEvents(receiveOutput: { #expect(Task.basePriority == priority) try completed.send() @@ -43,7 +43,7 @@ struct ExecutePriorityTests { func runsWithExpectedPriority(priority: TaskPriority) async throws { let completed = SingleValueSubject() - DeferredTask { } + DeferredTask {} .handleEvents(receiveOutput: { #expect(Task.currentPriority == priority) try completed.send() @@ -57,7 +57,7 @@ struct ExecutePriorityTests { func subscribesWithExpectedPriority(priority: TaskPriority) async throws { let completed = SingleValueSubject() - let subscription = DeferredTask { } + let subscription = DeferredTask {} .handleEvents(receiveOutput: { _ in #expect(Task.currentPriority == priority) try completed.send() @@ -69,5 +69,5 @@ struct ExecutePriorityTests { try await completed.execute() } - private func noop(_ any: Any) { } + private func noop(_ any: Any) {} } diff --git a/Tests/AfluentTests/WorkerTests/HandleEventsTests.swift b/Tests/AfluentTests/WorkerTests/HandleEventsTests.swift index 3fdf6c217..b59ecf66e 100644 --- a/Tests/AfluentTests/WorkerTests/HandleEventsTests.swift +++ b/Tests/AfluentTests/WorkerTests/HandleEventsTests.swift @@ -21,7 +21,7 @@ struct HandleEventsTests { let test = Test() try await confirmation { exp in - try await DeferredTask { } + try await DeferredTask {} .handleEvents(receiveOperation: { await test.operation() exp() @@ -96,11 +96,12 @@ struct HandleEventsTests { await withCheckedContinuation { continuation in Task { - await test.setTask(DeferredTask { await test.task?.cancel() } - .handleEvents(receiveCancel: { - await test.cancel() - continuation.resume() - }) + await test.setTask( + DeferredTask { await test.task?.cancel() } + .handleEvents(receiveCancel: { + await test.cancel() + continuation.resume() + }) .subscribe()) } } diff --git a/Tests/AfluentTests/WorkerTests/MaterializeTests.swift b/Tests/AfluentTests/WorkerTests/MaterializeTests.swift index cd26495c1..16af26d95 100644 --- a/Tests/AfluentTests/WorkerTests/MaterializeTests.swift +++ b/Tests/AfluentTests/WorkerTests/MaterializeTests.swift @@ -78,18 +78,19 @@ struct MaterializeTests { let test = Test() let sub = SingleValueSubject() - await test.setTask(DeferredTask { - await test.start() - await test.task?.cancel() - } - .handleEvents(receiveCancel: { - try? sub.send() - }) - .map { - await test.end() - } - .materialize() - .subscribe()) + await test.setTask( + DeferredTask { + await test.start() + await test.task?.cancel() + } + .handleEvents(receiveCancel: { + try? sub.send() + }) + .map { + await test.end() + } + .materialize() + .subscribe()) try await sub.execute() diff --git a/Tests/AfluentTests/WorkerTests/RetryAfterFlatMappingTests.swift b/Tests/AfluentTests/WorkerTests/RetryAfterFlatMappingTests.swift index 615528d73..3f0c39025 100644 --- a/Tests/AfluentTests/WorkerTests/RetryAfterFlatMappingTests.swift +++ b/Tests/AfluentTests/WorkerTests/RetryAfterFlatMappingTests.swift @@ -19,7 +19,7 @@ struct RetryAfterFlatMappingTests { } let test = Test() - let retryCount = UInt.random(in: 2 ... 10) + let retryCount = UInt.random(in: 2...10) let t = DeferredTask { await test.append("called") @@ -113,7 +113,7 @@ struct RetryAfterFlatMappingTests { let copy = await test.arr #expect(UInt(copy.count) == 1) } - + @Test func taskCanRetryADefinedNumberOfTimes_WithStrategy() async throws { actor Test { var arr = [String]() @@ -123,7 +123,7 @@ struct RetryAfterFlatMappingTests { } let test = Test() - let retryCount = UInt.random(in: 2 ... 10) + let retryCount = UInt.random(in: 2...10) let t = DeferredTask { await test.append("called") diff --git a/Tests/AfluentTests/WorkerTests/RetryOnAfterFlatMappingTests.swift b/Tests/AfluentTests/WorkerTests/RetryOnAfterFlatMappingTests.swift index cf84abd9c..d2b077bbc 100644 --- a/Tests/AfluentTests/WorkerTests/RetryOnAfterFlatMappingTests.swift +++ b/Tests/AfluentTests/WorkerTests/RetryOnAfterFlatMappingTests.swift @@ -22,7 +22,7 @@ struct RetryOnAfterFlatMappingTests { } let test = Test() - let retryCount = UInt.random(in: 2 ... 10) + let retryCount = UInt.random(in: 2...10) let t = DeferredTask { await test.append("called") @@ -125,7 +125,7 @@ struct RetryOnAfterFlatMappingTests { let copy = await test.arr #expect(UInt(copy.count) == 1) } - + @Test func taskCanRetryADefinedNumberOfTimes_WithStrategy() async throws { enum Err: Error, Equatable { case e1 @@ -138,7 +138,7 @@ struct RetryOnAfterFlatMappingTests { } let test = Test() - let retryCount = UInt.random(in: 2 ... 10) + let retryCount = UInt.random(in: 2...10) let t = DeferredTask { await test.append("called") diff --git a/Tests/AfluentTests/WorkerTests/RetryOnTests.swift b/Tests/AfluentTests/WorkerTests/RetryOnTests.swift index e08d74f43..73ee0e67e 100644 --- a/Tests/AfluentTests/WorkerTests/RetryOnTests.swift +++ b/Tests/AfluentTests/WorkerTests/RetryOnTests.swift @@ -22,7 +22,7 @@ struct RetryOnTests { } let test = Test() - let retryCount = UInt.random(in: 2 ... 10) + let retryCount = UInt.random(in: 2...10) let t = DeferredTask { await test.append("called") diff --git a/Tests/AfluentTests/WorkerTests/RetryTests.swift b/Tests/AfluentTests/WorkerTests/RetryTests.swift index 64e501e15..0241123a8 100644 --- a/Tests/AfluentTests/WorkerTests/RetryTests.swift +++ b/Tests/AfluentTests/WorkerTests/RetryTests.swift @@ -19,7 +19,7 @@ struct RetryTests { } let test = Test() - let retryCount = UInt.random(in: 2 ... 10) + let retryCount = UInt.random(in: 2...10) let t = DeferredTask { await test.append("called") @@ -120,7 +120,7 @@ struct RetryTests { let copy = await test.arr #expect(UInt(copy.count) == 1) } - + @Test func taskCanRetryADefinedNumberOfTimes_WithStrategy() async throws { actor Test { var arr = [String]() @@ -130,7 +130,7 @@ struct RetryTests { } let test = Test() - let retryCount = UInt.random(in: 2 ... 10) + let retryCount = UInt.random(in: 2...10) let t = DeferredTask { await test.append("called") @@ -188,7 +188,9 @@ struct RetryTests { #expect(UInt(copy.count) == 2) } - @Test func taskWithMultipleRetries_OnlyRetriesTheSpecifiedNumberOfTimes_WithStrategy() async throws { + @Test func taskWithMultipleRetries_OnlyRetriesTheSpecifiedNumberOfTimes_WithStrategy() + async throws + { actor Test { var arr = [String]() func append(_ str: String) { diff --git a/Tests/AfluentTests/WorkerTests/ShareFromCacheTests.swift b/Tests/AfluentTests/WorkerTests/ShareFromCacheTests.swift index 45cb0a58a..89be50f16 100644 --- a/Tests/AfluentTests/WorkerTests/ShareFromCacheTests.swift +++ b/Tests/AfluentTests/WorkerTests/ShareFromCacheTests.swift @@ -9,6 +9,7 @@ import Clocks import ConcurrencyExtras import Foundation import Testing + @testable import Afluent struct ShareFromCacheTests { @@ -27,15 +28,18 @@ struct ShareFromCacheTests { let cache = AUOWCache() @Sendable func unitOfWork() -> some AsynchronousUnitOfWork { - DeferredTask { await test.increment(); return UUID().uuidString } - .delay(for: .milliseconds(10), clock: clock) - .shareFromCache(cache, strategy: .cacheUntilCompletionOrCancellation) + DeferredTask { + await test.increment() + return UUID().uuidString + } + .delay(for: .milliseconds(10), clock: clock) + .shareFromCache(cache, strategy: .cacheUntilCompletionOrCancellation) } let uow = unitOfWork() async let d1 = uow.execute() #expect(!cache.cache.isEmpty) - async let d2 = DeferredTask { } + async let d2 = DeferredTask {} .delay(for: .milliseconds(5), clock: clock) .flatMap { #expect(!cache.cache.isEmpty) @@ -72,15 +76,18 @@ struct ShareFromCacheTests { let clock = TestClock() @Sendable func unitOfWork() -> some AsynchronousUnitOfWork { - DeferredTask { await test.increment(); return UUID().uuidString } - .delay(for: .milliseconds(10), clock: clock) - .shareFromCache(cache, strategy: .cacheUntilCompletionOrCancellation) + DeferredTask { + await test.increment() + return UUID().uuidString + } + .delay(for: .milliseconds(10), clock: clock) + .shareFromCache(cache, strategy: .cacheUntilCompletionOrCancellation) } let uow = unitOfWork() async let d1 = uow.execute() #expect(!cache.cache.isEmpty) - async let d2 = DeferredTask { } + async let d2 = DeferredTask {} .delay(for: .milliseconds(15), clock: clock) .flatMap { #expect(cache.cache.isEmpty) @@ -114,15 +121,18 @@ struct ShareFromCacheTests { let clock = TestClock() @Sendable func unitOfWork() -> some AsynchronousUnitOfWork { - DeferredTask { await test.increment(); throw Err.e1 } - .delay(for: .milliseconds(10), clock: clock) - .shareFromCache(cache, strategy: .cacheUntilCompletionOrCancellation) + DeferredTask { + await test.increment() + throw Err.e1 + } + .delay(for: .milliseconds(10), clock: clock) + .shareFromCache(cache, strategy: .cacheUntilCompletionOrCancellation) } let uow = unitOfWork() async let d1 = uow.execute() #expect(!cache.cache.isEmpty) - async let d2 = DeferredTask { } + async let d2 = DeferredTask {} .delay(for: .milliseconds(15), clock: clock) .flatMap { #expect(cache.cache.isEmpty) @@ -170,15 +180,18 @@ struct ShareFromCacheTests { let clock = TestClock() @Sendable func unitOfWork() -> some AsynchronousUnitOfWork { - DeferredTask { await test.increment(); return UUID().uuidString } - .delay(for: .milliseconds(10), clock: clock) - .shareFromCache(cache, strategy: .cacheUntilCompletionOrCancellation, keys: 1) + DeferredTask { + await test.increment() + return UUID().uuidString + } + .delay(for: .milliseconds(10), clock: clock) + .shareFromCache(cache, strategy: .cacheUntilCompletionOrCancellation, keys: 1) } let uow = unitOfWork() async let d1 = uow.execute() #expect(!cache.cache.isEmpty) - async let d2 = DeferredTask { } + async let d2 = DeferredTask {} .delay(for: .milliseconds(5), clock: clock) .flatMap { unitOfWork() } .execute() diff --git a/Tests/AfluentTests/WorkerTests/SingleValueChannelTests.swift b/Tests/AfluentTests/WorkerTests/SingleValueChannelTests.swift index a25f01bbe..497a19d33 100644 --- a/Tests/AfluentTests/WorkerTests/SingleValueChannelTests.swift +++ b/Tests/AfluentTests/WorkerTests/SingleValueChannelTests.swift @@ -13,7 +13,7 @@ import Testing struct SingleValueChannelTests { @Test func SingleValueChannelEmittingValueBeforeTaskRuns() async throws { try await confirmation { confirmation in - let expected = Int.random(in: 1 ... 1000) + let expected = Int.random(in: 1...1000) let subject = SingleValueChannel() let unitOfWork = subject.map { confirmation() @@ -28,14 +28,15 @@ struct SingleValueChannelTests { } @Test func SingleValueChannelEmittingValueAfterTaskRuns() async throws { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - let expected = Int.random(in: 1 ... 1000) + try await withCheckedThrowingContinuation { + (continuation: CheckedContinuation) in + let expected = Int.random(in: 1...1000) let subject = SingleValueChannel() subject.map { defer { continuation.resume() } #expect($0 == expected) return $0 - }.run() // task started + }.run() // task started Task { do { @@ -65,7 +66,8 @@ struct SingleValueChannelTests { try await withCheckedThrowingContinuation { continuation in Task { let subject = SingleValueChannel() - let unitOfWork = subject + let unitOfWork = + subject .materialize() .map { continuation.resume() @@ -90,12 +92,12 @@ struct SingleValueChannelTests { } @Test func SingleValueChannelOnlyEmitsValueOnce() async throws { - let expected = Int.random(in: 1 ... 1000) + let expected = Int.random(in: 1...1000) let subject = SingleValueChannel() subject.map { #expect($0 == expected) return $0 - }.run() // task started + }.run() // task started try await subject.send(expected) await #expect(throws: (any Error).self) { try await subject.send(expected) } @@ -106,7 +108,8 @@ struct SingleValueChannelTests { try await confirmation { exp in try await withMainSerialExecutor { let subject = SingleValueChannel() - let unitOfWork = subject + let unitOfWork = + subject .materialize() .map { exp() @@ -146,7 +149,7 @@ struct SingleValueChannelTests { let subject = SingleValueChannel() subject.map { continuation.resume() - }.run() // task started + }.run() // task started Task { do { @@ -159,7 +162,7 @@ struct SingleValueChannelTests { } @Test func SingleValueChannelEmittingValueConcurrentlyWithExecute() async throws { - let expected = Int.random(in: 1 ... 1000) + let expected = Int.random(in: 1...1000) try await confirmation { exp in let subject = SingleValueChannel() let unitOfWork = subject.map { @@ -184,7 +187,8 @@ struct SingleValueChannelTests { enum Err: Error { case e1 } try await confirmation { exp in let subject = SingleValueChannel() - let unitOfWork = subject + let unitOfWork = + subject .materialize() .map { exp() diff --git a/Tests/AfluentTests/WorkerTests/SingleValueSubjectTests.swift b/Tests/AfluentTests/WorkerTests/SingleValueSubjectTests.swift index e46ec01e4..495d47274 100644 --- a/Tests/AfluentTests/WorkerTests/SingleValueSubjectTests.swift +++ b/Tests/AfluentTests/WorkerTests/SingleValueSubjectTests.swift @@ -13,7 +13,7 @@ import Testing struct SingleValueSubjectTests { @Test func singleValueSubjectEmittingValueBeforeTaskRuns() async throws { try await confirmation { confirmation in - let expected = Int.random(in: 1 ... 1000) + let expected = Int.random(in: 1...1000) let subject = SingleValueSubject() let unitOfWork = subject.map { confirmation() @@ -28,14 +28,15 @@ struct SingleValueSubjectTests { } @Test func singleValueSubjectEmittingValueAfterTaskRuns() async throws { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - let expected = Int.random(in: 1 ... 1000) + try await withCheckedThrowingContinuation { + (continuation: CheckedContinuation) in + let expected = Int.random(in: 1...1000) let subject = SingleValueSubject() subject.map { defer { continuation.resume() } #expect($0 == expected) return $0 - }.run() // task started + }.run() // task started do { try subject.send(expected) @@ -63,7 +64,8 @@ struct SingleValueSubjectTests { try await withCheckedThrowingContinuation { continuation in Task { let subject = SingleValueSubject() - let unitOfWork = subject + let unitOfWork = + subject .materialize() .map { continuation.resume() @@ -88,12 +90,12 @@ struct SingleValueSubjectTests { } @Test func singleValueSubjectOnlyEmitsValueOnce() async throws { - let expected = Int.random(in: 1 ... 1000) + let expected = Int.random(in: 1...1000) let subject = SingleValueSubject() subject.map { #expect($0 == expected) return $0 - }.run() // task started + }.run() // task started try subject.send(expected) #expect(throws: (any Error).self) { try subject.send(expected) } @@ -104,7 +106,8 @@ struct SingleValueSubjectTests { enum Err: Error { case e1 } let subject = SingleValueSubject() let exp1 = SingleValueSubject() - let unitOfWork = subject + let unitOfWork = + subject .materialize() .map { try! exp1.send() @@ -145,7 +148,7 @@ struct SingleValueSubjectTests { let subject = SingleValueSubject() subject.map { continuation.resume() - }.run() // task started + }.run() // task started do { try subject.send() @@ -156,7 +159,7 @@ struct SingleValueSubjectTests { } @Test func singleValueSubjectEmittingValueConcurrentlyWithExecute() async throws { - let expected = Int.random(in: 1 ... 1000) + let expected = Int.random(in: 1...1000) try await confirmation { exp in let subject = SingleValueSubject() let unitOfWork = subject.map { @@ -181,7 +184,8 @@ struct SingleValueSubjectTests { enum Err: Error { case e1 } try await confirmation { exp in let subject = SingleValueSubject() - let unitOfWork = subject + let unitOfWork = + subject .materialize() .map { exp() diff --git a/Tests/AfluentTests/WorkerTests/WithUnretainedTests.swift b/Tests/AfluentTests/WorkerTests/WithUnretainedTests.swift index b25ef65d8..8cd2c1902 100644 --- a/Tests/AfluentTests/WorkerTests/WithUnretainedTests.swift +++ b/Tests/AfluentTests/WorkerTests/WithUnretainedTests.swift @@ -5,20 +5,22 @@ // Created by Daniel Bachar on 11/8/23. // import Afluent - import Foundation import Testing struct WithUnretainedTests { - final class MyType: Sendable { } + final class MyType: Sendable {} @Test func withUnretainedHolds() async throws { let myTypeInstance = MyType() try await DeferredTask { 1 } - .withUnretained(myTypeInstance, resultSelector: { myType, _ in - #expect(myType != nil) - }) + .withUnretained( + myTypeInstance, + resultSelector: { myType, _ in + #expect(myType != nil) + } + ) .execute() } diff --git a/Tests/AfluentTests/WorkerTests/ZipTests.swift b/Tests/AfluentTests/WorkerTests/ZipTests.swift index cd998d70f..1cf674450 100644 --- a/Tests/AfluentTests/WorkerTests/ZipTests.swift +++ b/Tests/AfluentTests/WorkerTests/ZipTests.swift @@ -15,7 +15,7 @@ struct ZipTests { let t2 = DeferredTask { "A" } let t3 = t2.zip(t1) - let val = try await t3.execute() // Steak sauce!!! + let val = try await t3.execute() // Steak sauce!!! #expect(val.0 == "A") #expect(val.1 == 1) } @@ -25,7 +25,7 @@ struct ZipTests { let t2 = DeferredTask { "A" } let t3 = t2.zip(t1) - let val = try await t3.execute() // Steak sauce!!! + let val = try await t3.execute() // Steak sauce!!! #expect(val.0 == "A") #expect(val.1 == 1) } @@ -35,7 +35,7 @@ struct ZipTests { let t2 = DeferredTask { "A" } let t3 = t2.zip(t1) { $0 + String(describing: $1) } - let val = try await t3.execute() // Steak sauce!!! + let val = try await t3.execute() // Steak sauce!!! #expect(val == "A1") } @@ -44,7 +44,7 @@ struct ZipTests { let t2 = DeferredTask { "A" } let t3 = t2.zip(t1) { $0 + String(describing: $1) } - let val = try await t3.execute() // Steak sauce!!! + let val = try await t3.execute() // Steak sauce!!! #expect(val == "A1") } @@ -54,7 +54,7 @@ struct ZipTests { let t3 = DeferredTask { true } let t4 = t2.zip(t1, t3) - let val = try await t4.execute() // Steak sauce!!! + let val = try await t4.execute() // Steak sauce!!! #expect(val.0 == "A") #expect(val.1 == 1) #expect(val.2 == true) @@ -66,7 +66,7 @@ struct ZipTests { let t3 = DeferredTask { true } let t4 = t2.zip(t1, t3) - let val = try await t4.execute() // Steak sauce!!! + let val = try await t4.execute() // Steak sauce!!! #expect(val.0 == "A") #expect(val.1 == 1) #expect(val.2 == true) @@ -78,7 +78,7 @@ struct ZipTests { let t3 = DeferredTask { true } let t4 = t2.zip(t1, t3) { $0 + String(describing: $1) + String(describing: $2) } - let val = try await t4.execute() // Steak sauce!!! + let val = try await t4.execute() // Steak sauce!!! #expect(val == "A1true") } @@ -88,7 +88,7 @@ struct ZipTests { let t3 = DeferredTask { true } let t4 = t2.zip(t1, t3) { $0 + String(describing: $1) + String(describing: $2) } - let val = try await t4.execute() // Steak sauce!!! + let val = try await t4.execute() // Steak sauce!!! #expect(val == "A1true") } @@ -99,7 +99,7 @@ struct ZipTests { let t4 = DeferredTask { Character("!") } let t5 = t2.zip(t1, t3, t4) - let val = try await t5.execute() // Steak sauce!!! + let val = try await t5.execute() // Steak sauce!!! #expect(val.0 == "A") #expect(val.1 == 1) #expect(val.2 == true) @@ -113,7 +113,7 @@ struct ZipTests { let t4 = DeferredTask { Character("!") } let t5 = t2.zip(t1, t3, t4) - let val = try await t5.execute() // Steak sauce!!! + let val = try await t5.execute() // Steak sauce!!! #expect(val.0 == "A") #expect(val.1 == 1) #expect(val.2 == true) @@ -126,8 +126,10 @@ struct ZipTests { let t3 = DeferredTask { true } let t4 = DeferredTask { Character("!") } - let t5 = t2.zip(t1, t3, t4) { $0 + String(describing: $1) + String(describing: $2) + String(describing: $3) } - let val = try await t5.execute() // Steak sauce!!! + let t5 = t2.zip(t1, t3, t4) { + $0 + String(describing: $1) + String(describing: $2) + String(describing: $3) + } + let val = try await t5.execute() // Steak sauce!!! #expect(val == "A1true!") } @@ -137,8 +139,10 @@ struct ZipTests { let t3 = DeferredTask { true } let t4 = DeferredTask { Character("!") } - let t5 = t2.zip(t1, t3, t4) { $0 + String(describing: $1) + String(describing: $2) + String(describing: $3) } - let val = try await t5.execute() // Steak sauce!!! + let t5 = t2.zip(t1, t3, t4) { + $0 + String(describing: $1) + String(describing: $2) + String(describing: $3) + } + let val = try await t5.execute() // Steak sauce!!! #expect(val == "A1true!") } }