diff --git a/.swiftlint.yml b/.swiftlint.yml index 127ed03d..292de188 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -114,6 +114,9 @@ custom_rules: newline_before_brace: name: "Closing braces shouldn't have empty lines before them" regex: '\n\n\}' + sendable_order: + name: "@escaping should precede @Sendable when used together" + regex: '@Sendable\s+@escaping' space_before_comma: name: "Commas should never have a space before them" regex: '\s+,' diff --git a/Examples/ElizaSharedSources/GeneratedSources/eliza.connect.swift b/Examples/ElizaSharedSources/GeneratedSources/eliza.connect.swift index cf4b6efe..06b43a2f 100644 --- a/Examples/ElizaSharedSources/GeneratedSources/eliza.connect.swift +++ b/Examples/ElizaSharedSources/GeneratedSources/eliza.connect.swift @@ -13,7 +13,7 @@ import SwiftProtobuf /// superficiality of human-computer communication. DOCTOR simulates a /// psychotherapist, and is commonly found as an Easter egg in emacs /// distributions. -internal protocol Connectrpc_Eliza_V1_ElizaServiceClientInterface { +internal protocol Connectrpc_Eliza_V1_ElizaServiceClientInterface: Sendable { /// Say is a unary RPC. Eliza responds to the prompt with a single sentence. @available(iOS 13, *) @@ -32,7 +32,7 @@ internal protocol Connectrpc_Eliza_V1_ElizaServiceClientInterface { } /// Concrete implementation of `Connectrpc_Eliza_V1_ElizaServiceClientInterface`. -internal final class Connectrpc_Eliza_V1_ElizaServiceClient: Connectrpc_Eliza_V1_ElizaServiceClientInterface { +internal final class Connectrpc_Eliza_V1_ElizaServiceClient: Connectrpc_Eliza_V1_ElizaServiceClientInterface, Sendable { private let client: Connect.ProtocolClientInterface internal init(client: Connect.ProtocolClientInterface) { diff --git a/Libraries/Connect/Implementation/Codecs/JSONCodec.swift b/Libraries/Connect/Implementation/Codecs/JSONCodec.swift index a42bde37..ae7882f2 100644 --- a/Libraries/Connect/Implementation/Codecs/JSONCodec.swift +++ b/Libraries/Connect/Implementation/Codecs/JSONCodec.swift @@ -13,10 +13,11 @@ // limitations under the License. import Foundation -import SwiftProtobuf +// TODO: Remove `@preconcurrency` once `SwiftProtobuf.JSON{Encoding|Decoding}Options` are `Sendable` +@preconcurrency import SwiftProtobuf /// Codec providing functionality for serializing to/from JSON. -public struct JSONCodec { +public struct JSONCodec: Sendable { private let encodingOptions: JSONEncodingOptions private let decodingOptions: JSONDecodingOptions = { var options = JSONDecodingOptions() @@ -43,11 +44,11 @@ extension JSONCodec: Codec { return "json" } - public func serialize(message: Input) throws -> Data { + public func serialize(message: Input) throws -> Data { return try message.jsonUTF8Data(options: self.encodingOptions) } - public func deserialize(source: Data) throws -> Output { + public func deserialize(source: Data) throws -> Output { return try Output(jsonUTF8Data: source, options: self.decodingOptions) } } diff --git a/Libraries/Connect/Implementation/Codecs/ProtoCodec.swift b/Libraries/Connect/Implementation/Codecs/ProtoCodec.swift index 38d86987..068db0ec 100644 --- a/Libraries/Connect/Implementation/Codecs/ProtoCodec.swift +++ b/Libraries/Connect/Implementation/Codecs/ProtoCodec.swift @@ -25,11 +25,11 @@ extension ProtoCodec: Codec { return "proto" } - public func serialize(message: Input) throws -> Data { + public func serialize(message: Input) throws -> Data { return try message.serializedData() } - public func deserialize(source: Data) throws -> Output { + public func deserialize(source: Data) throws -> Output { return try Output(serializedData: source) } } diff --git a/Libraries/Connect/Implementation/Interceptors/ConnectInterceptor.swift b/Libraries/Connect/Implementation/Interceptors/ConnectInterceptor.swift index a965b395..9afecc91 100644 --- a/Libraries/Connect/Implementation/Interceptors/ConnectInterceptor.swift +++ b/Libraries/Connect/Implementation/Interceptors/ConnectInterceptor.swift @@ -101,7 +101,7 @@ extension ConnectInterceptor: Interceptor { } func streamFunction() -> StreamFunction { - var responseHeaders: Headers? + let responseHeaders = Locked(nil) return StreamFunction( requestFunction: { request in var headers = request.headers @@ -124,12 +124,12 @@ extension ConnectInterceptor: Interceptor { streamResultFunction: { result in switch result { case .headers(let headers): - responseHeaders = headers + responseHeaders.value = headers return result case .message(let data): do { - let responseCompressionPool = responseHeaders?[ + let responseCompressionPool = responseHeaders.value?[ HeaderConstants.connectStreamingContentEncoding ]?.first.flatMap { self.config.responseCompressionPool(forName: $0) } let (headerByte, message) = try Envelope.unpackMessage( @@ -161,7 +161,7 @@ extension ConnectInterceptor: Interceptor { code: code, error: ConnectError.from( code: code, - headers: responseHeaders ?? [:], + headers: responseHeaders.value ?? [:], source: nil ), trailers: trailers diff --git a/Libraries/Connect/Implementation/Interceptors/GRPCWebInterceptor.swift b/Libraries/Connect/Implementation/Interceptors/GRPCWebInterceptor.swift index 3d54ab09..511e84ce 100644 --- a/Libraries/Connect/Implementation/Interceptors/GRPCWebInterceptor.swift +++ b/Libraries/Connect/Implementation/Interceptors/GRPCWebInterceptor.swift @@ -111,7 +111,7 @@ extension GRPCWebInterceptor: Interceptor { } func streamFunction() -> StreamFunction { - var responseHeaders: Headers? + let responseHeaders = Locked(nil) return StreamFunction( requestFunction: { request in return HTTPRequest( @@ -137,13 +137,13 @@ extension GRPCWebInterceptor: Interceptor { trailers: headers ) } else { - responseHeaders = headers + responseHeaders.value = headers return result } case .message(let data): do { - let responseCompressionPool = responseHeaders?[ + let responseCompressionPool = responseHeaders.value?[ HeaderConstants.grpcContentEncoding ]?.first.flatMap { self.config.responseCompressionPool(forName: $0) } let (headerByte, unpackedData) = try Envelope.unpackMessage( diff --git a/Libraries/Connect/Implementation/Interceptors/InterceptorChain.swift b/Libraries/Connect/Implementation/Interceptors/InterceptorChain.swift index e92866ad..6e255e82 100644 --- a/Libraries/Connect/Implementation/Interceptors/InterceptorChain.swift +++ b/Libraries/Connect/Implementation/Interceptors/InterceptorChain.swift @@ -14,7 +14,7 @@ /// Represents a chain of interceptors that is used for a single request/stream, /// and orchestrates invoking each of them in the proper order. -struct InterceptorChain { +struct InterceptorChain: Sendable { private let interceptors: [Interceptor] /// Initialize the interceptor chain. @@ -23,9 +23,7 @@ struct InterceptorChain { /// /// - parameter interceptors: Closures that should be called to create interceptors. /// - parameter config: Config to use for setting up interceptors. - init( - interceptors: [(ProtocolClientConfig) -> Interceptor], config: ProtocolClientConfig - ) { + init(interceptors: [InterceptorInitializer], config: ProtocolClientConfig) { self.interceptors = interceptors.map { initialize in initialize(config) } } @@ -85,7 +83,7 @@ struct InterceptorChain { } } -private func executeInterceptors(_ interceptors: [(T) -> T], initial: T) -> T { +private func executeInterceptors(_ interceptors: [@Sendable (T) -> T], initial: T) -> T { var next = initial for interceptor in interceptors { next = interceptor(next) diff --git a/Libraries/Connect/Implementation/Lock.swift b/Libraries/Connect/Implementation/Lock.swift index 74fa3995..9e560ba1 100644 --- a/Libraries/Connect/Implementation/Lock.swift +++ b/Libraries/Connect/Implementation/Lock.swift @@ -15,7 +15,7 @@ import Foundation /// Internal implementation of a lock. Wraps usage of `os_unfair_lock`. -final class Lock { +final class Lock: Sendable { private let underlyingLock: UnsafeMutablePointer init() { @@ -24,6 +24,11 @@ final class Lock { self.underlyingLock.initialize(to: os_unfair_lock()) } + /// Perform an action within the context of the lock. + /// + /// - parameter action: Closure to be executed in the context of the lock. + /// + /// - returns: The result of the closure. func perform(action: @escaping () -> T) -> T { os_unfair_lock_lock(self.underlyingLock) defer { os_unfair_lock_unlock(self.underlyingLock) } diff --git a/Libraries/Connect/Implementation/Locked.swift b/Libraries/Connect/Implementation/Locked.swift new file mode 100644 index 00000000..5212368a --- /dev/null +++ b/Libraries/Connect/Implementation/Locked.swift @@ -0,0 +1,41 @@ +// Copyright 2022-2023 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +/// Class containing an internal lock which can be used to ensure thread-safe access to an +/// underlying value. Conforms to `Sendable`, making it accessible from `@Sendable` closures. +public final class Locked: @unchecked Sendable { + private let lock = Lock() + private var wrappedValue: T + + /// Thread-safe access to the underlying value. + public var value: T { + get { self.lock.perform { self.wrappedValue } } + set { self.lock.perform { self.wrappedValue = newValue } } + } + + /// Perform an action with the underlying value, potentially updating that value. + /// + /// - parameter action: Closure to perform with the underlying value. + public func perform(action: @escaping (inout T) -> Void) { + self.lock.perform { + action(&self.wrappedValue) + } + } + + public init(_ value: T) { + self.wrappedValue = value + } +} diff --git a/Libraries/Connect/Implementation/ProtocolClient.swift b/Libraries/Connect/Implementation/ProtocolClient.swift index 150f0a32..e7360b5b 100644 --- a/Libraries/Connect/Implementation/ProtocolClient.swift +++ b/Libraries/Connect/Implementation/ProtocolClient.swift @@ -17,7 +17,7 @@ import os.log import SwiftProtobuf /// Concrete implementation of the `ProtocolClientInterface`. -public final class ProtocolClient { +public final class ProtocolClient: Sendable { private let config: ProtocolClientConfig private let httpClient: HTTPClientInterface @@ -38,13 +38,11 @@ extension ProtocolClient: ProtocolClientInterface { // MARK: - Callbacks @discardableResult - public func unary< - Input: SwiftProtobuf.Message, Output: SwiftProtobuf.Message - >( + public func unary( path: String, request: Input, headers: Headers, - completion: @escaping (ResponseMessage) -> Void + completion: @escaping @Sendable (ResponseMessage) -> Void ) -> Cancelable { let codec = self.config.codec let data: Data @@ -123,11 +121,11 @@ extension ProtocolClient: ProtocolClientInterface { } public func bidirectionalStream< - Input: SwiftProtobuf.Message, Output: SwiftProtobuf.Message + Input: ProtobufMessage, Output: ProtobufMessage >( path: String, headers: Headers, - onResult: @escaping (StreamResult) -> Void + onResult: @escaping @Sendable (StreamResult) -> Void ) -> any BidirectionalStreamInterface { return BidirectionalStream( requestCallbacks: self.createRequestCallbacks( @@ -138,11 +136,11 @@ extension ProtocolClient: ProtocolClientInterface { } public func clientOnlyStream< - Input: SwiftProtobuf.Message, Output: SwiftProtobuf.Message + Input: ProtobufMessage, Output: ProtobufMessage >( path: String, headers: Headers, - onResult: @escaping (StreamResult) -> Void + onResult: @escaping @Sendable (StreamResult) -> Void ) -> any ClientOnlyStreamInterface { return BidirectionalStream( requestCallbacks: self.createRequestCallbacks( @@ -153,11 +151,11 @@ extension ProtocolClient: ProtocolClientInterface { } public func serverOnlyStream< - Input: SwiftProtobuf.Message, Output: SwiftProtobuf.Message + Input: ProtobufMessage, Output: ProtobufMessage >( path: String, headers: Headers, - onResult: @escaping (StreamResult) -> Void + onResult: @escaping @Sendable (StreamResult) -> Void ) -> any ServerOnlyStreamInterface { return ServerOnlyStream(bidirectionalStream: BidirectionalStream( requestCallbacks: self.createRequestCallbacks( @@ -170,9 +168,7 @@ extension ProtocolClient: ProtocolClientInterface { // MARK: - Async/await @available(iOS 13, *) - public func unary< - Input: SwiftProtobuf.Message, Output: SwiftProtobuf.Message - >( + public func unary( path: String, request: Input, headers: Headers @@ -183,43 +179,37 @@ extension ProtocolClient: ProtocolClientInterface { } @available(iOS 13, *) - public func bidirectionalStream< - Input: SwiftProtobuf.Message, Output: SwiftProtobuf.Message - >( + public func bidirectionalStream( path: String, headers: Headers ) -> any BidirectionalAsyncStreamInterface { let bidirectionalAsync = BidirectionalAsyncStream(codec: self.config.codec) let callbacks = self.createRequestCallbacks( - path: path, headers: headers, onResult: bidirectionalAsync.receive + path: path, headers: headers, onResult: { bidirectionalAsync.receive($0) } ) return bidirectionalAsync.configureForSending(with: callbacks) } @available(iOS 13, *) - public func clientOnlyStream< - Input: SwiftProtobuf.Message, Output: SwiftProtobuf.Message - >( + public func clientOnlyStream( path: String, headers: Headers ) -> any ClientOnlyAsyncStreamInterface { let bidirectionalAsync = BidirectionalAsyncStream(codec: self.config.codec) let callbacks = self.createRequestCallbacks( - path: path, headers: headers, onResult: bidirectionalAsync.receive + path: path, headers: headers, onResult: { bidirectionalAsync.receive($0) } ) return bidirectionalAsync.configureForSending(with: callbacks) } @available(iOS 13, *) - public func serverOnlyStream< - Input: SwiftProtobuf.Message, Output: SwiftProtobuf.Message - >( + public func serverOnlyStream( path: String, headers: Headers ) -> any ServerOnlyAsyncStreamInterface { let bidirectionalAsync = BidirectionalAsyncStream(codec: self.config.codec) let callbacks = self.createRequestCallbacks( - path: path, headers: headers, onResult: bidirectionalAsync.receive + path: path, headers: headers, onResult: { bidirectionalAsync.receive($0) } ) return ServerOnlyAsyncStream( bidirectionalStream: bidirectionalAsync.configureForSending(with: callbacks) @@ -228,10 +218,10 @@ extension ProtocolClient: ProtocolClientInterface { // MARK: - Private - private func createRequestCallbacks( + private func createRequestCallbacks( path: String, headers: Headers, - onResult: @escaping (StreamResult) -> Void + onResult: @escaping @Sendable (StreamResult) -> Void ) -> RequestCallbacks { let codec = self.config.codec let chain = self.config.createInterceptorChain().streamFunction() @@ -244,12 +234,12 @@ extension ProtocolClient: ProtocolClientInterface { trailers: nil )) - var hasCompleted = false - let interceptAndHandleResult: (StreamResult) -> Void = { streamResult in + let hasCompleted = Locked(false) + let interceptAndHandleResult: @Sendable (StreamResult) -> Void = { streamResult in do { let interceptedResult = chain.streamResultFunction(streamResult) if case .complete = interceptedResult { - hasCompleted = true + hasCompleted.value = true } onResult(try interceptedResult.toTypedResult(using: codec)) } catch let error { @@ -261,29 +251,33 @@ extension ProtocolClient: ProtocolClientInterface { ) } } - var responseBuffer = Data() + let responseBuffer = Locked(Data()) let responseCallbacks = ResponseCallbacks( receiveResponseHeaders: { interceptAndHandleResult(.headers($0)) }, receiveResponseData: { responseChunk in // Repeating handles cases where multiple messages are received in a single chunk. - responseBuffer += responseChunk - while true { - let messageLength = Envelope.messageLength(forPackedData: responseBuffer) - if messageLength < 0 { - return - } + responseBuffer.perform { responseBuffer in + responseBuffer += responseChunk + while true { + let messageLength = Envelope.messageLength(forPackedData: responseBuffer) + if messageLength < 0 { + return + } - let prefixedMessageLength = Envelope.prefixLength + messageLength - guard responseBuffer.count >= prefixedMessageLength else { - return - } + let prefixedMessageLength = Envelope.prefixLength + messageLength + guard responseBuffer.count >= prefixedMessageLength else { + return + } - interceptAndHandleResult(.message(responseBuffer.prefix(prefixedMessageLength))) - responseBuffer = Data(responseBuffer.suffix(from: prefixedMessageLength)) + interceptAndHandleResult( + .message(responseBuffer.prefix(prefixedMessageLength)) + ) + responseBuffer = Data(responseBuffer.suffix(from: prefixedMessageLength)) + } } }, receiveClose: { code, trailers, error in - if !hasCompleted { + if !hasCompleted.value { interceptAndHandleResult(.complete( code: code, error: error, @@ -306,7 +300,7 @@ extension ProtocolClient: ProtocolClientInterface { } private extension StreamResult { - func toTypedResult(using codec: Codec) throws -> StreamResult { + func toTypedResult(using codec: Codec) throws -> StreamResult { switch self { case .complete(let code, let error, let trailers): return .complete(code: code, error: error, trailers: trailers) diff --git a/Libraries/Connect/Implementation/ProtocolClientConfig.swift b/Libraries/Connect/Implementation/ProtocolClientConfig.swift index 8955146f..16fe3403 100644 --- a/Libraries/Connect/Implementation/ProtocolClientConfig.swift +++ b/Libraries/Connect/Implementation/ProtocolClientConfig.swift @@ -15,7 +15,7 @@ import Foundation /// Configuration used to set up `ProtocolClientInterface` implementations. -public struct ProtocolClientConfig { +public struct ProtocolClientConfig: Sendable { /// Target host (e.g., `https://buf.build`). public let host: String /// Protocol to use for requests and streams. @@ -28,10 +28,10 @@ public struct ProtocolClientConfig { /// response headers like `content-encoding`. public let responseCompressionPools: [CompressionPool] /// List of interceptors that should be invoked with requests/responses. - public let interceptors: [(ProtocolClientConfig) -> Interceptor] + public let interceptors: [InterceptorInitializer] /// Configuration used to specify if/how requests should be compressed. - public struct RequestCompression { + public struct RequestCompression: Sendable { /// The minimum number of bytes that a request message should be for compression to be used. public let minBytes: Int /// The compression pool that should be used for compressing outbound requests. @@ -53,7 +53,7 @@ public struct ProtocolClientConfig { codec: Codec = JSONCodec(), requestCompression: RequestCompression? = nil, responseCompressionPools: [CompressionPool] = [GzipCompressionPool()], - interceptors: [(ProtocolClientConfig) -> Interceptor] = [] + interceptors: [InterceptorInitializer] = [] ) { self.host = host self.networkProtocol = networkProtocol @@ -63,9 +63,9 @@ public struct ProtocolClientConfig { switch networkProtocol { case .connect: - self.interceptors = interceptors + [ConnectInterceptor.init] + self.interceptors = interceptors + [{ ConnectInterceptor(config: $0) }] case .grpcWeb: - self.interceptors = interceptors + [GRPCWebInterceptor.init] + self.interceptors = interceptors + [{ GRPCWebInterceptor(config: $0) }] case .custom(_, let protocolInterceptor): self.interceptors = interceptors + [protocolInterceptor] } diff --git a/Libraries/Connect/Implementation/Streaming/BidirectionalAsyncStream.swift b/Libraries/Connect/Implementation/Streaming/BidirectionalAsyncStream.swift index 685bab5e..e690f789 100644 --- a/Libraries/Connect/Implementation/Streaming/BidirectionalAsyncStream.swift +++ b/Libraries/Connect/Implementation/Streaming/BidirectionalAsyncStream.swift @@ -18,7 +18,9 @@ import SwiftProtobuf /// Provides the necessary wiring to bridge from closures/callbacks to Swift's `AsyncStream` /// to work with async/await. @available(iOS 13, *) -final class BidirectionalAsyncStream { +final class BidirectionalAsyncStream< + Input: ProtobufMessage, Output: ProtobufMessage +>: @unchecked Sendable { /// The underlying async stream that will be exposed to the consumer. /// Force unwrapped because it captures `self` on `init`. private var asyncStream: AsyncStream>! diff --git a/Libraries/Connect/Implementation/Streaming/BidirectionalStream.swift b/Libraries/Connect/Implementation/Streaming/BidirectionalStream.swift index cac94dca..c112a744 100644 --- a/Libraries/Connect/Implementation/Streaming/BidirectionalStream.swift +++ b/Libraries/Connect/Implementation/Streaming/BidirectionalStream.swift @@ -15,7 +15,7 @@ import SwiftProtobuf /// Concrete implementation of `BidirectionalStreamInterface`. -final class BidirectionalStream { +final class BidirectionalStream: Sendable { private let requestCallbacks: RequestCallbacks private let codec: Codec diff --git a/Libraries/Connect/Implementation/Streaming/ServerOnlyAsyncStream.swift b/Libraries/Connect/Implementation/Streaming/ServerOnlyAsyncStream.swift index f185a81d..af345ac0 100644 --- a/Libraries/Connect/Implementation/Streaming/ServerOnlyAsyncStream.swift +++ b/Libraries/Connect/Implementation/Streaming/ServerOnlyAsyncStream.swift @@ -16,7 +16,7 @@ import SwiftProtobuf /// Concrete implementation of `ServerOnlyAsyncStreamInterface`. @available(iOS 13, *) -final class ServerOnlyAsyncStream { +final class ServerOnlyAsyncStream: Sendable { private let bidirectionalStream: BidirectionalAsyncStream init(bidirectionalStream: BidirectionalAsyncStream) { diff --git a/Libraries/Connect/Implementation/Streaming/ServerOnlyStream.swift b/Libraries/Connect/Implementation/Streaming/ServerOnlyStream.swift index ae159f3a..a54abd5d 100644 --- a/Libraries/Connect/Implementation/Streaming/ServerOnlyStream.swift +++ b/Libraries/Connect/Implementation/Streaming/ServerOnlyStream.swift @@ -15,7 +15,7 @@ import SwiftProtobuf /// Concrete implementation of `ServerOnlyStreamInterface`. -final class ServerOnlyStream { +final class ServerOnlyStream: Sendable { private let bidirectionalStream: BidirectionalStream init(bidirectionalStream: BidirectionalStream) { diff --git a/Libraries/Connect/Implementation/Streaming/URLSessionStream.swift b/Libraries/Connect/Implementation/Streaming/URLSessionStream.swift index 58f30d73..23e1658b 100644 --- a/Libraries/Connect/Implementation/Streaming/URLSessionStream.swift +++ b/Libraries/Connect/Implementation/Streaming/URLSessionStream.swift @@ -15,8 +15,11 @@ import Foundation /// Stream implementation that wraps a `URLSession` stream. -final class URLSessionStream: NSObject { - private var closedByServer = false +/// +/// Note: This class is `@unchecked Sendable` because the `Foundation.{Input|Output}Stream` +/// types do not conform to `Sendable`. +final class URLSessionStream: NSObject, @unchecked Sendable { + private let closedByServer = Locked(false) private let readStream: Foundation.InputStream private let responseCallbacks: ResponseCallbacks private let task: URLSessionUploadTask @@ -93,23 +96,23 @@ final class URLSessionStream: NSObject { let code = Code.fromURLSessionCode(response.statusCode) self.responseCallbacks.receiveResponseHeaders(response.formattedLowercasedHeaders()) if code != .ok { - self.closedByServer = true + self.closedByServer.value = true self.responseCallbacks.receiveClose(code, [:], nil) } } func handleResponseData(_ data: Data) { - if !self.closedByServer { + if !self.closedByServer.value { self.responseCallbacks.receiveResponseData(data) } } func handleCompletion(error: Swift.Error?) { - if self.closedByServer { + if self.closedByServer.value { return } - self.closedByServer = true + self.closedByServer.value = true if let error = error { self.responseCallbacks.receiveClose( Code.fromURLSessionCode((error as NSError).code), diff --git a/Libraries/Connect/Implementation/URLSessionHTTPClient.swift b/Libraries/Connect/Implementation/URLSessionHTTPClient.swift index e1f64c35..1137496b 100644 --- a/Libraries/Connect/Implementation/URLSessionHTTPClient.swift +++ b/Libraries/Connect/Implementation/URLSessionHTTPClient.swift @@ -17,11 +17,15 @@ import Foundation import os.log /// Concrete implementation of `HTTPClientInterface` backed by `URLSession`. -open class URLSessionHTTPClient: NSObject, HTTPClientInterface { +/// +/// This class is thread-safe as-is through the use of an internal lock. It is marked as +/// `open` and `@unchecked Sendable` so that consumers can subclass it if necessary, but +/// subclasses must handle their own thread safety for added functionality. +open class URLSessionHTTPClient: NSObject, HTTPClientInterface, @unchecked Sendable { /// Lock used for safely accessing stream storage. private let lock = Lock() /// Closures stored for notifying when metrics are available. - private var metricsClosures = [Int: (HTTPMetrics) -> Void]() + private var metricsClosures = [Int: @Sendable (HTTPMetrics) -> Void]() /// Force unwrapped to allow using `self` as the delegate. private var session: URLSession! /// List of active streams. @@ -42,8 +46,8 @@ open class URLSessionHTTPClient: NSObject, HTTPClientInterface { @discardableResult open func unary( request: HTTPRequest, - onMetrics: @Sendable @escaping (HTTPMetrics) -> Void, - onResponse: @Sendable @escaping (HTTPResponse) -> Void + onMetrics: @escaping @Sendable (HTTPMetrics) -> Void, + onResponse: @escaping @Sendable (HTTPResponse) -> Void ) -> Cancelable { assert(!request.isGRPC, "URLSessionHTTPClient does not support gRPC, use NIOHTTPClient") let urlRequest = URLRequest(httpRequest: request) @@ -88,7 +92,7 @@ open class URLSessionHTTPClient: NSObject, HTTPClientInterface { } self.lock.perform { self.metricsClosures[task.taskIdentifier] = onMetrics } task.resume() - return Cancelable(cancel: task.cancel) + return Cancelable { task.cancel() } } open func stream( @@ -114,7 +118,7 @@ open class URLSessionHTTPClient: NSObject, HTTPClientInterface { urlSessionStream.close() } }, - sendClose: urlSessionStream.close + sendClose: { urlSessionStream.close() } ) } } diff --git a/Libraries/Connect/Implementation/UnaryAsyncWrapper.swift b/Libraries/Connect/Implementation/UnaryAsyncWrapper.swift index eb0fdc74..600a132b 100644 --- a/Libraries/Connect/Implementation/UnaryAsyncWrapper.swift +++ b/Libraries/Connect/Implementation/UnaryAsyncWrapper.swift @@ -21,14 +21,14 @@ import SwiftProtobuf /// https://forums.swift.org/t/how-to-use-withtaskcancellationhandler-properly/54341/37 /// https://stackoverflow.com/q/71898080 @available(iOS 13, *) -actor UnaryAsyncWrapper { +actor UnaryAsyncWrapper: Sendable { private var cancelable: Cancelable? private let sendUnary: PerformClosure /// Accepts a closure to be called upon completion of a request and returns a cancelable which, /// when invoked, will cancel the underlying request. - typealias PerformClosure = ( - @escaping (ResponseMessage) -> Void + typealias PerformClosure = @Sendable ( + @escaping @Sendable (ResponseMessage) -> Void ) -> Cancelable init(sendUnary: @escaping PerformClosure) { diff --git a/Libraries/Connect/Interfaces/Cancelable.swift b/Libraries/Connect/Interfaces/Cancelable.swift index 33c24b29..575efacc 100644 --- a/Libraries/Connect/Interfaces/Cancelable.swift +++ b/Libraries/Connect/Interfaces/Cancelable.swift @@ -13,11 +13,11 @@ // limitations under the License. /// Type that wraps an action that can be canceled. -public struct Cancelable { +public struct Cancelable: Sendable { /// Cancel the current action. - public let cancel: () -> Void + public let cancel: @Sendable () -> Void - public init(cancel: @escaping () -> Void) { + public init(cancel: @escaping @Sendable () -> Void) { self.cancel = cancel } } diff --git a/Libraries/Connect/Interfaces/Codec.swift b/Libraries/Connect/Interfaces/Codec.swift index 28fcafa3..991f46b6 100644 --- a/Libraries/Connect/Interfaces/Codec.swift +++ b/Libraries/Connect/Interfaces/Codec.swift @@ -16,7 +16,7 @@ import Foundation import SwiftProtobuf /// Defines a type that is capable of encoding and decoding messages using a specific format. -public protocol Codec { +public protocol Codec: Sendable { /// - returns: The name of the codec's format (e.g., "json", "protobuf"). Usually consumed /// in the form of adding the `content-type` header via "application/{name}". func name() -> String @@ -26,12 +26,12 @@ public protocol Codec { /// - parameter message: Typed input message. /// /// - returns: Serialized data that can be transmitted. - func serialize(message: Input) throws -> Data + func serialize(message: Input) throws -> Data /// Deserializes data in the codec's format into a typed message. /// /// - parameter source: The source data to deserialize. /// /// - returns: The typed output message. - func deserialize(source: Data) throws -> Output + func deserialize(source: Data) throws -> Output } diff --git a/Libraries/Connect/Interfaces/CompressionPool.swift b/Libraries/Connect/Interfaces/CompressionPool.swift index 8e0445d5..67fd4d00 100644 --- a/Libraries/Connect/Interfaces/CompressionPool.swift +++ b/Libraries/Connect/Interfaces/CompressionPool.swift @@ -22,7 +22,7 @@ import Foundation /// /// Outbound request compression can be specified using additional options that specify a /// `compressionName` that matches a compression pool's `name()`. -public protocol CompressionPool { +public protocol CompressionPool: Sendable { /// The name of the compression pool, which corresponds to the `content-encoding` header. /// Example: `gzip`. /// diff --git a/Libraries/Connect/Interfaces/ConnectError.swift b/Libraries/Connect/Interfaces/ConnectError.swift index 92c1ed79..14efaf9a 100644 --- a/Libraries/Connect/Interfaces/ConnectError.swift +++ b/Libraries/Connect/Interfaces/ConnectError.swift @@ -37,7 +37,7 @@ public struct ConnectError: Swift.Error, Sendable { /// `let unpackedError: MyError? = error.unpackedDetails().first` /// /// - returns: The matching unpacked typed error details. - public func unpackedDetails() -> [Output] { + public func unpackedDetails() -> [Output] { return self.details.compactMap { detail -> Output? in guard detail.type == Output.protoMessageName else { return nil diff --git a/Libraries/Connect/Interfaces/HTTPClientInterface.swift b/Libraries/Connect/Interfaces/HTTPClientInterface.swift index 100065a6..a846bb80 100644 --- a/Libraries/Connect/Interfaces/HTTPClientInterface.swift +++ b/Libraries/Connect/Interfaces/HTTPClientInterface.swift @@ -13,7 +13,7 @@ // limitations under the License. /// Interface for a client that performs underlying HTTP requests and streams with primitive types. -public protocol HTTPClientInterface { +public protocol HTTPClientInterface: Sendable { /// Perform a unary HTTP request. /// /// - parameter request: The outbound request headers and data. @@ -25,8 +25,8 @@ public protocol HTTPClientInterface { @discardableResult func unary( request: HTTPRequest, - onMetrics: @Sendable @escaping (HTTPMetrics) -> Void, - onResponse: @Sendable @escaping (HTTPResponse) -> Void + onMetrics: @escaping @Sendable (HTTPMetrics) -> Void, + onResponse: @escaping @Sendable (HTTPResponse) -> Void ) -> Cancelable /// Initialize a new HTTP stream. diff --git a/Libraries/Connect/Interfaces/Interceptor.swift b/Libraries/Connect/Interfaces/Interceptor.swift index 5db787d9..a1d12eb9 100644 --- a/Libraries/Connect/Interfaces/Interceptor.swift +++ b/Libraries/Connect/Interfaces/Interceptor.swift @@ -18,7 +18,7 @@ import Foundation /// and inbound responses. /// /// Interceptors are expected to be instantiated once per request/stream. -public protocol Interceptor { +public protocol Interceptor: Sendable { /// Invoked when a unary call is started. Provides a set of closures that will be called /// as the request progresses, allowing the interceptor to alter request/response data. /// @@ -37,15 +37,17 @@ public protocol Interceptor { func streamFunction() -> StreamFunction } -public struct UnaryFunction { - public let requestFunction: (HTTPRequest) -> HTTPRequest - public let responseFunction: (HTTPResponse) -> HTTPResponse - public let responseMetricsFunction: (HTTPMetrics) -> HTTPMetrics +public typealias InterceptorInitializer = @Sendable (ProtocolClientConfig) -> Interceptor + +public struct UnaryFunction: Sendable { + public let requestFunction: @Sendable (HTTPRequest) -> HTTPRequest + public let responseFunction: @Sendable (HTTPResponse) -> HTTPResponse + public let responseMetricsFunction: @Sendable (HTTPMetrics) -> HTTPMetrics public init( - requestFunction: @escaping (HTTPRequest) -> HTTPRequest, - responseFunction: @escaping (HTTPResponse) -> HTTPResponse, - responseMetricsFunction: @escaping (HTTPMetrics) -> HTTPMetrics = { $0 } + requestFunction: @escaping @Sendable (HTTPRequest) -> HTTPRequest, + responseFunction: @escaping @Sendable (HTTPResponse) -> HTTPResponse, + responseMetricsFunction: @escaping @Sendable (HTTPMetrics) -> HTTPMetrics = { $0 } ) { self.requestFunction = requestFunction self.responseFunction = responseFunction @@ -53,15 +55,15 @@ public struct UnaryFunction { } } -public struct StreamFunction { - public let requestFunction: (HTTPRequest) -> HTTPRequest - public let requestDataFunction: (Data) -> Data - public let streamResultFunction: (StreamResult) -> StreamResult +public struct StreamFunction: Sendable { + public let requestFunction: @Sendable (HTTPRequest) -> HTTPRequest + public let requestDataFunction: @Sendable (Data) -> Data + public let streamResultFunction: @Sendable (StreamResult) -> StreamResult public init( - requestFunction: @escaping (HTTPRequest) -> HTTPRequest, - requestDataFunction: @escaping (Data) -> Data, - streamResultFunction: @escaping (StreamResult) -> StreamResult + requestFunction: @escaping @Sendable (HTTPRequest) -> HTTPRequest, + requestDataFunction: @escaping @Sendable (Data) -> Data, + streamResultFunction: @escaping @Sendable (StreamResult) -> StreamResult ) { self.requestFunction = requestFunction self.requestDataFunction = requestDataFunction diff --git a/Libraries/Connect/Interfaces/NetworkProtocol.swift b/Libraries/Connect/Interfaces/NetworkProtocol.swift index 17063b06..6863c2f4 100644 --- a/Libraries/Connect/Interfaces/NetworkProtocol.swift +++ b/Libraries/Connect/Interfaces/NetworkProtocol.swift @@ -13,7 +13,7 @@ // limitations under the License. /// Protocols that are supported by the library. -public enum NetworkProtocol { +public enum NetworkProtocol: Sendable { /// The Connect protocol: /// https://connectrpc.com/docs/protocol case connect @@ -21,7 +21,7 @@ public enum NetworkProtocol { /// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md case grpcWeb /// A custom protocol that is implemented via an interceptor. - case custom(name: String, protocolInterceptor: (ProtocolClientConfig) -> Interceptor) + case custom(name: String, protocolInterceptor: InterceptorInitializer) } extension NetworkProtocol: CustomStringConvertible { diff --git a/Libraries/Connect/Interfaces/ProtobufMessage.swift b/Libraries/Connect/Interfaces/ProtobufMessage.swift new file mode 100644 index 00000000..662db0d9 --- /dev/null +++ b/Libraries/Connect/Interfaces/ProtobufMessage.swift @@ -0,0 +1,17 @@ +// Copyright 2022-2023 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import SwiftProtobuf + +public typealias ProtobufMessage = SwiftProtobuf.Message & Sendable diff --git a/Libraries/Connect/Interfaces/ProtocolClientInterface.swift b/Libraries/Connect/Interfaces/ProtocolClientInterface.swift index 709a615e..0636223e 100644 --- a/Libraries/Connect/Interfaces/ProtocolClientInterface.swift +++ b/Libraries/Connect/Interfaces/ProtocolClientInterface.swift @@ -18,7 +18,7 @@ import SwiftProtobuf /// Primary interface consumed by generated RPCs to perform requests and streams. /// The client itself is protocol-agnostic, but can be configured during initialization /// (see `ProtocolClientConfig` and `ProtocolClientOption`). -public protocol ProtocolClientInterface { +public protocol ProtocolClientInterface: Sendable { // MARK: - Callbacks /// Perform a unary (non-streaming) request. @@ -30,13 +30,11 @@ public protocol ProtocolClientInterface { /// /// - returns: A `Cancelable` which provides the ability to cancel the outbound request. @discardableResult - func unary< - Input: SwiftProtobuf.Message, Output: SwiftProtobuf.Message - >( + func unary( path: String, request: Input, headers: Headers, - completion: @escaping (ResponseMessage) -> Void + completion: @escaping @Sendable (ResponseMessage) -> Void ) -> Cancelable /// Start a new bidirectional stream. @@ -53,12 +51,10 @@ public protocol ProtocolClientInterface { /// (response headers, messages, trailers, etc.). /// /// - returns: An interface for interacting with and sending data over the bidirectional stream. - func bidirectionalStream< - Input: SwiftProtobuf.Message, Output: SwiftProtobuf.Message - >( + func bidirectionalStream( path: String, headers: Headers, - onResult: @escaping (StreamResult) -> Void + onResult: @escaping @Sendable (StreamResult) -> Void ) -> any BidirectionalStreamInterface /// Start a new client-only stream. @@ -75,12 +71,10 @@ public protocol ProtocolClientInterface { /// (response headers, messages, trailers, etc.). /// /// - returns: An interface for interacting with and sending data over the client-only stream. - func clientOnlyStream< - Input: SwiftProtobuf.Message, Output: SwiftProtobuf.Message - >( + func clientOnlyStream( path: String, headers: Headers, - onResult: @escaping (StreamResult) -> Void + onResult: @escaping @Sendable (StreamResult) -> Void ) -> any ClientOnlyStreamInterface /// Start a new server-only stream. @@ -97,12 +91,10 @@ public protocol ProtocolClientInterface { /// (response headers, messages, trailers, etc.). /// /// - returns: An interface for interacting with and sending data over the server-only stream. - func serverOnlyStream< - Input: SwiftProtobuf.Message, Output: SwiftProtobuf.Message - >( + func serverOnlyStream( path: String, headers: Headers, - onResult: @escaping (StreamResult) -> Void + onResult: @escaping @Sendable (StreamResult) -> Void ) -> any ServerOnlyStreamInterface // MARK: - Async/await @@ -115,9 +107,7 @@ public protocol ProtocolClientInterface { /// /// - returns: The response which is returned asynchronously. @available(iOS 13, *) - func unary< - Input: SwiftProtobuf.Message, Output: SwiftProtobuf.Message - >( + func unary( path: String, request: Input, headers: Headers @@ -136,9 +126,7 @@ public protocol ProtocolClientInterface { /// /// - returns: An interface for sending and receiving data over the stream using async/await. @available(iOS 13, *) - func bidirectionalStream< - Input: SwiftProtobuf.Message, Output: SwiftProtobuf.Message - >( + func bidirectionalStream( path: String, headers: Headers ) -> any BidirectionalAsyncStreamInterface @@ -156,9 +144,7 @@ public protocol ProtocolClientInterface { /// /// - returns: An interface for sending and receiving data over the stream using async/await. @available(iOS 13, *) - func clientOnlyStream< - Input: SwiftProtobuf.Message, Output: SwiftProtobuf.Message - >( + func clientOnlyStream( path: String, headers: Headers ) -> any ClientOnlyAsyncStreamInterface @@ -176,9 +162,7 @@ public protocol ProtocolClientInterface { /// /// - returns: An interface for sending and receiving data over the stream using async/await. @available(iOS 13, *) - func serverOnlyStream< - Input: SwiftProtobuf.Message, Output: SwiftProtobuf.Message - >( + func serverOnlyStream( path: String, headers: Headers ) -> any ServerOnlyAsyncStreamInterface diff --git a/Libraries/Connect/Interfaces/ResponseMessage.swift b/Libraries/Connect/Interfaces/ResponseMessage.swift index a7b209b2..a7dc7a86 100644 --- a/Libraries/Connect/Interfaces/ResponseMessage.swift +++ b/Libraries/Connect/Interfaces/ResponseMessage.swift @@ -15,7 +15,7 @@ import SwiftProtobuf /// Typed unary response from an RPC. -public struct ResponseMessage: Sendable { +public struct ResponseMessage: Sendable { /// The status code of the response. public let code: Code /// Response headers specified by the server. diff --git a/Libraries/Connect/Interfaces/Streams/BidirectionalAsyncStreamInterface.swift b/Libraries/Connect/Interfaces/Streams/BidirectionalAsyncStreamInterface.swift index 4d7eb894..d868c12a 100644 --- a/Libraries/Connect/Interfaces/Streams/BidirectionalAsyncStreamInterface.swift +++ b/Libraries/Connect/Interfaces/Streams/BidirectionalAsyncStreamInterface.swift @@ -18,10 +18,10 @@ import SwiftProtobuf @available(iOS 13, *) public protocol BidirectionalAsyncStreamInterface { /// The input (request) message type. - associatedtype Input: SwiftProtobuf.Message + associatedtype Input: ProtobufMessage /// The output (response) message type. - associatedtype Output: SwiftProtobuf.Message + associatedtype Output: ProtobufMessage /// Send a request to the server over the stream. /// diff --git a/Libraries/Connect/Interfaces/Streams/BidirectionalStreamInterface.swift b/Libraries/Connect/Interfaces/Streams/BidirectionalStreamInterface.swift index fc161a22..f334dad8 100644 --- a/Libraries/Connect/Interfaces/Streams/BidirectionalStreamInterface.swift +++ b/Libraries/Connect/Interfaces/Streams/BidirectionalStreamInterface.swift @@ -17,7 +17,7 @@ import SwiftProtobuf /// Represents a bidirectional stream that can send request messages and initiate closes. public protocol BidirectionalStreamInterface { /// The input (request) message type. - associatedtype Input: SwiftProtobuf.Message + associatedtype Input: ProtobufMessage /// Send a request to the server over the stream. /// diff --git a/Libraries/Connect/Interfaces/Streams/ClientOnlyAsyncStreamInterface.swift b/Libraries/Connect/Interfaces/Streams/ClientOnlyAsyncStreamInterface.swift index 20440978..de4334fd 100644 --- a/Libraries/Connect/Interfaces/Streams/ClientOnlyAsyncStreamInterface.swift +++ b/Libraries/Connect/Interfaces/Streams/ClientOnlyAsyncStreamInterface.swift @@ -19,10 +19,10 @@ import SwiftProtobuf @available(iOS 13, *) public protocol ClientOnlyAsyncStreamInterface { /// The input (request) message type. - associatedtype Input: SwiftProtobuf.Message + associatedtype Input: ProtobufMessage /// The output (response) message type. - associatedtype Output: SwiftProtobuf.Message + associatedtype Output: ProtobufMessage /// Send a request to the server over the stream. /// diff --git a/Libraries/Connect/Interfaces/Streams/ClientOnlyStreamInterface.swift b/Libraries/Connect/Interfaces/Streams/ClientOnlyStreamInterface.swift index f066d5d1..629881a5 100644 --- a/Libraries/Connect/Interfaces/Streams/ClientOnlyStreamInterface.swift +++ b/Libraries/Connect/Interfaces/Streams/ClientOnlyStreamInterface.swift @@ -18,7 +18,7 @@ import SwiftProtobuf /// eventually receives a response) that can send request messages and initiate closes. public protocol ClientOnlyStreamInterface { /// The input (request) message type. - associatedtype Input: SwiftProtobuf.Message + associatedtype Input: ProtobufMessage /// Send a request to the server over the stream. /// diff --git a/Libraries/Connect/Interfaces/Streams/RequestCallbacks.swift b/Libraries/Connect/Interfaces/Streams/RequestCallbacks.swift index c31958fa..aa986250 100644 --- a/Libraries/Connect/Interfaces/Streams/RequestCallbacks.swift +++ b/Libraries/Connect/Interfaces/Streams/RequestCallbacks.swift @@ -15,13 +15,15 @@ import Foundation /// Set of closures that are used for wiring outbound request data through to HTTP clients. -public final class RequestCallbacks { +public final class RequestCallbacks: Sendable { /// Closure to send data through to the server. - public let sendData: (Data) -> Void + public let sendData: @Sendable (Data) -> Void /// Closure to initiate a close for a stream. - public let sendClose: () -> Void + public let sendClose: @Sendable () -> Void - public init(sendData: @escaping (Data) -> Void, sendClose: @escaping () -> Void) { + public init( + sendData: @escaping @Sendable (Data) -> Void, sendClose: @escaping @Sendable () -> Void + ) { self.sendData = sendData self.sendClose = sendClose } diff --git a/Libraries/Connect/Interfaces/Streams/ResponseCallbacks.swift b/Libraries/Connect/Interfaces/Streams/ResponseCallbacks.swift index 17523466..441f38c7 100644 --- a/Libraries/Connect/Interfaces/Streams/ResponseCallbacks.swift +++ b/Libraries/Connect/Interfaces/Streams/ResponseCallbacks.swift @@ -15,19 +15,19 @@ import Foundation /// Set of closures that are used for wiring inbound response data through from HTTP clients. -public final class ResponseCallbacks { +public final class ResponseCallbacks: Sendable { /// Closure to call when response headers are available. - public let receiveResponseHeaders: (Headers) -> Void + public let receiveResponseHeaders: @Sendable (Headers) -> Void /// Closure to call when response data is available. - public let receiveResponseData: (Data) -> Void + public let receiveResponseData: @Sendable (Data) -> Void /// Closure to call when the stream is closed. /// Includes the status code, trailers, and potentially an error. - public let receiveClose: (Code, Trailers, Swift.Error?) -> Void + public let receiveClose: @Sendable (Code, Trailers, Swift.Error?) -> Void public init( - receiveResponseHeaders: @escaping (Headers) -> Void, - receiveResponseData: @escaping (Data) -> Void, - receiveClose: @escaping (Code, Trailers, Swift.Error?) -> Void + receiveResponseHeaders: @escaping @Sendable (Headers) -> Void, + receiveResponseData: @escaping @Sendable (Data) -> Void, + receiveClose: @escaping @Sendable (Code, Trailers, Swift.Error?) -> Void ) { self.receiveResponseHeaders = receiveResponseHeaders self.receiveResponseData = receiveResponseData diff --git a/Libraries/Connect/Interfaces/Streams/ServerOnlyAsyncStreamInterface.swift b/Libraries/Connect/Interfaces/Streams/ServerOnlyAsyncStreamInterface.swift index d36829b0..0424fcae 100644 --- a/Libraries/Connect/Interfaces/Streams/ServerOnlyAsyncStreamInterface.swift +++ b/Libraries/Connect/Interfaces/Streams/ServerOnlyAsyncStreamInterface.swift @@ -19,10 +19,10 @@ import SwiftProtobuf @available(iOS 13, *) public protocol ServerOnlyAsyncStreamInterface { /// The input (request) message type. - associatedtype Input: SwiftProtobuf.Message + associatedtype Input: ProtobufMessage /// The output (response) message type. - associatedtype Output: SwiftProtobuf.Message + associatedtype Output: ProtobufMessage /// Send a request to the server over the stream. /// diff --git a/Libraries/Connect/Interfaces/Streams/ServerOnlyStreamInterface.swift b/Libraries/Connect/Interfaces/Streams/ServerOnlyStreamInterface.swift index ca367bcc..23c88d99 100644 --- a/Libraries/Connect/Interfaces/Streams/ServerOnlyStreamInterface.swift +++ b/Libraries/Connect/Interfaces/Streams/ServerOnlyStreamInterface.swift @@ -18,7 +18,7 @@ import SwiftProtobuf /// receiving an initial request) that can send request messages. public protocol ServerOnlyStreamInterface { /// The input (request) message type. - associatedtype Input: SwiftProtobuf.Message + associatedtype Input: ProtobufMessage /// Send a request to the server over the stream. /// diff --git a/Libraries/ConnectMocks/MockBidirectionalAsyncStream.swift b/Libraries/ConnectMocks/MockBidirectionalAsyncStream.swift index 1ba02e56..2d0170e1 100644 --- a/Libraries/ConnectMocks/MockBidirectionalAsyncStream.swift +++ b/Libraries/ConnectMocks/MockBidirectionalAsyncStream.swift @@ -26,8 +26,8 @@ import SwiftProtobuf /// subclassing and overriding `results()`. @available(iOS 13, *) open class MockBidirectionalAsyncStream< - Input: SwiftProtobuf.Message, - Output: SwiftProtobuf.Message + Input: ProtobufMessage, + Output: ProtobufMessage >: BidirectionalAsyncStreamInterface { private var cancellables = [AnyCancellable]() diff --git a/Libraries/ConnectMocks/MockBidirectionalStream.swift b/Libraries/ConnectMocks/MockBidirectionalStream.swift index f13d8299..66866233 100644 --- a/Libraries/ConnectMocks/MockBidirectionalStream.swift +++ b/Libraries/ConnectMocks/MockBidirectionalStream.swift @@ -25,8 +25,8 @@ import SwiftProtobuf /// To return data over the stream, outputs can be specified using `init(outputs: ...)`. @available(iOS 13.0, *) open class MockBidirectionalStream< - Input: SwiftProtobuf.Message, - Output: SwiftProtobuf.Message + Input: ProtobufMessage, + Output: ProtobufMessage >: BidirectionalStreamInterface { /// Closure that is called when `close()` is invoked. public var onClose: (() -> Void)? diff --git a/Libraries/ConnectMocks/MockClientOnlyAsyncStream.swift b/Libraries/ConnectMocks/MockClientOnlyAsyncStream.swift index c3a374d2..1c04a196 100644 --- a/Libraries/ConnectMocks/MockClientOnlyAsyncStream.swift +++ b/Libraries/ConnectMocks/MockClientOnlyAsyncStream.swift @@ -25,6 +25,6 @@ import SwiftProtobuf /// subclassing and overriding `results()`. @available(iOS 13, *) open class MockClientOnlyAsyncStream< - Input: SwiftProtobuf.Message, - Output: SwiftProtobuf.Message + Input: ProtobufMessage, + Output: ProtobufMessage >: MockBidirectionalAsyncStream, ClientOnlyAsyncStreamInterface {} diff --git a/Libraries/ConnectMocks/MockClientOnlyStream.swift b/Libraries/ConnectMocks/MockClientOnlyStream.swift index 48a9d447..63920e34 100644 --- a/Libraries/ConnectMocks/MockClientOnlyStream.swift +++ b/Libraries/ConnectMocks/MockClientOnlyStream.swift @@ -24,6 +24,6 @@ import SwiftProtobuf /// To return data over the stream, outputs can be specified using `init(outputs: ...)`. @available(iOS 13.0, *) open class MockClientOnlyStream< - Input: SwiftProtobuf.Message, - Output: SwiftProtobuf.Message + Input: ProtobufMessage, + Output: ProtobufMessage >: MockBidirectionalStream, ClientOnlyStreamInterface {} diff --git a/Libraries/ConnectMocks/MockServerOnlyAsyncStream.swift b/Libraries/ConnectMocks/MockServerOnlyAsyncStream.swift index e18065f0..4bc5695b 100644 --- a/Libraries/ConnectMocks/MockServerOnlyAsyncStream.swift +++ b/Libraries/ConnectMocks/MockServerOnlyAsyncStream.swift @@ -26,8 +26,8 @@ import SwiftProtobuf /// subclassing and overriding `results()`. @available(iOS 13, *) open class MockServerOnlyAsyncStream< - Input: SwiftProtobuf.Message, - Output: SwiftProtobuf.Message + Input: ProtobufMessage, + Output: ProtobufMessage >: ServerOnlyAsyncStreamInterface { private var cancellables = [AnyCancellable]() diff --git a/Libraries/ConnectMocks/MockServerOnlyStream.swift b/Libraries/ConnectMocks/MockServerOnlyStream.swift index 054490e4..9ace2b30 100644 --- a/Libraries/ConnectMocks/MockServerOnlyStream.swift +++ b/Libraries/ConnectMocks/MockServerOnlyStream.swift @@ -25,8 +25,8 @@ import SwiftProtobuf /// To return data over the stream, outputs can be specified using `init(outputs: ...)`. @available(iOS 13.0, *) open class MockServerOnlyStream< - Input: SwiftProtobuf.Message, - Output: SwiftProtobuf.Message + Input: ProtobufMessage, + Output: ProtobufMessage >: ServerOnlyStreamInterface { /// Closure that is called when `send()` is invoked. public var onSend: ((Input) -> Void)? diff --git a/Libraries/ConnectNIO/Internal/ConnectStreamChannelHandler.swift b/Libraries/ConnectNIO/Internal/ConnectStreamChannelHandler.swift index a072175f..7115fc63 100644 --- a/Libraries/ConnectNIO/Internal/ConnectStreamChannelHandler.swift +++ b/Libraries/ConnectNIO/Internal/ConnectStreamChannelHandler.swift @@ -19,7 +19,7 @@ import NIOFoundationCompat import NIOHTTP1 /// NIO-based channel handler for streams made through the Connect library. -final class ConnectStreamChannelHandler: NIOCore.ChannelInboundHandler { +final class ConnectStreamChannelHandler: NIOCore.ChannelInboundHandler, @unchecked Sendable { private let eventLoop: NIOCore.EventLoop private let request: Connect.HTTPRequest private let responseCallbacks: Connect.ResponseCallbacks @@ -94,7 +94,7 @@ final class ConnectStreamChannelHandler: NIOCore.ChannelInboundHandler { .cascade(to: nil) } - private func runOnEventLoop(action: @escaping () -> Void) { + private func runOnEventLoop(action: @escaping @Sendable () -> Void) { if self.eventLoop.inEventLoop { action() } else { diff --git a/Libraries/ConnectNIO/Internal/ConnectUnaryChannelHandler.swift b/Libraries/ConnectNIO/Internal/ConnectUnaryChannelHandler.swift index 6bd9854c..104c13d6 100644 --- a/Libraries/ConnectNIO/Internal/ConnectUnaryChannelHandler.swift +++ b/Libraries/ConnectNIO/Internal/ConnectUnaryChannelHandler.swift @@ -19,7 +19,7 @@ import NIOFoundationCompat import NIOHTTP1 /// NIO-based channel handler for unary requests made through the Connect library. -final class ConnectUnaryChannelHandler: NIOCore.ChannelInboundHandler { +final class ConnectUnaryChannelHandler: NIOCore.ChannelInboundHandler, @unchecked Sendable { private let eventLoop: NIOCore.EventLoop private let request: Connect.HTTPRequest private let onMetrics: (Connect.HTTPMetrics) -> Void @@ -63,7 +63,7 @@ final class ConnectUnaryChannelHandler: NIOCore.ChannelInboundHandler { } } - private func runOnEventLoop(action: @escaping () -> Void) { + private func runOnEventLoop(action: @escaping @Sendable () -> Void) { if self.eventLoop.inEventLoop { action() } else { diff --git a/Libraries/ConnectNIO/Internal/GRPCInterceptor.swift b/Libraries/ConnectNIO/Internal/GRPCInterceptor.swift index 81ba3f0b..81d3cbb6 100644 --- a/Libraries/ConnectNIO/Internal/GRPCInterceptor.swift +++ b/Libraries/ConnectNIO/Internal/GRPCInterceptor.swift @@ -99,7 +99,7 @@ extension GRPCInterceptor: Connect.Interceptor { } func streamFunction() -> Connect.StreamFunction { - var responseHeaders: Connect.Headers? + let responseHeaders = Locked(nil) return Connect.StreamFunction( requestFunction: { request in return Connect.HTTPRequest( @@ -117,12 +117,12 @@ extension GRPCInterceptor: Connect.Interceptor { streamResultFunction: { result in switch result { case .headers(let headers): - responseHeaders = headers + responseHeaders.value = headers return result case .message(let data): do { - let responseCompressionPool = responseHeaders?[ + let responseCompressionPool = responseHeaders.value?[ Connect.HeaderConstants.grpcContentEncoding ]?.first.flatMap { self.config.responseCompressionPool(forName: $0) } return .message(try Connect.Envelope.unpackMessage( diff --git a/Libraries/ConnectNIO/Public/NIOHTTPClient.swift b/Libraries/ConnectNIO/Public/NIOHTTPClient.swift index 88393107..a1def07d 100644 --- a/Libraries/ConnectNIO/Public/NIOHTTPClient.swift +++ b/Libraries/ConnectNIO/Public/NIOHTTPClient.swift @@ -23,7 +23,7 @@ import NIOSSL import os.log /// HTTP client powered by Swift NIO which supports trailers (unlike URLSession). -open class NIOHTTPClient: Connect.HTTPClientInterface { +open class NIOHTTPClient: Connect.HTTPClientInterface, @unchecked Sendable { private lazy var bootstrap = self.createBootstrap() private let host: String private let lock = NIOConcurrencyHelpers.NIOLock() @@ -146,7 +146,7 @@ open class NIOHTTPClient: Connect.HTTPClientInterface { )) } } - return .init(cancel: handler.cancel) + return .init(cancel: { handler.cancel() }) } open func stream( @@ -169,7 +169,7 @@ open class NIOHTTPClient: Connect.HTTPClientInterface { } } return .init( - sendData: handler.sendData, + sendData: { handler.sendData($0) }, sendClose: { handler.close(trailers: nil) } ) } @@ -204,7 +204,7 @@ open class NIOHTTPClient: Connect.HTTPClientInterface { .whenComplete { [weak self] result in switch result { case .success((let channel, let multiplexer)): - channel.closeFuture.whenComplete { _ in + channel.closeFuture.whenComplete { [weak self] _ in self?.lock.withLock { self?.state = .disconnected } } self?.lock.withLock { diff --git a/Libraries/ConnectNIO/Public/NetworkProtocol+Extensions.swift b/Libraries/ConnectNIO/Public/NetworkProtocol+Extensions.swift index d22f423a..8ab0f5b6 100644 --- a/Libraries/ConnectNIO/Public/NetworkProtocol+Extensions.swift +++ b/Libraries/ConnectNIO/Public/NetworkProtocol+Extensions.swift @@ -21,6 +21,6 @@ extension NetworkProtocol { /// IMPORTANT: This protocol must be used in conjunction with an HTTP client that supports /// trailers, such as the `NIOHTTPClient` included in this library. public static var grpc: Self { - return .custom(name: "gRPC", protocolInterceptor: GRPCInterceptor.init) + return .custom(name: "gRPC", protocolInterceptor: { GRPCInterceptor(config: $0) }) } } diff --git a/Plugins/ConnectMocksPlugin/ConnectMockGenerator.swift b/Plugins/ConnectMocksPlugin/ConnectMockGenerator.swift index 73bc7489..3e791043 100644 --- a/Plugins/ConnectMocksPlugin/ConnectMockGenerator.swift +++ b/Plugins/ConnectMocksPlugin/ConnectMockGenerator.swift @@ -54,12 +54,18 @@ final class ConnectMockGenerator: Generator { self.printLine("/// Mock implementation of `\(protocolName)`.") self.printLine("///") self.printLine("/// Production implementations can be substituted with instances of this") - self.printLine("/// class, allowing for mocking RPC calls. Behavior can be customized") + self.printLine("/// class to mock RPC calls. Behavior can be customized") self.printLine("/// either through the properties on this class or by") - self.printLine("/// subclassing the class and overriding its methods.") + self.printLine("/// subclassing the mock and overriding its methods.") + self.printLine("///") + self.printLine("/// Note: This class does not handle thread-safe locking, but provides") + self.printLine("/// `@unchecked Sendable` conformance to simplify testing and mocking.") self.printLine("@available(iOS 13, *)") self.printLine( - "\(self.typeVisibility) class \(service.mockName(using: self.namer)): \(protocolName) {" + """ + \(self.typeVisibility) class \(service.mockName(using: self.namer)): \ + \(protocolName), @unchecked Sendable { + """ ) self.indent { if self.options.generateCallbackMethods { diff --git a/Plugins/ConnectPluginUtilities/MethodDescriptor+Extensions.swift b/Plugins/ConnectPluginUtilities/MethodDescriptor+Extensions.swift index 8a47ebbe..dc681ebd 100644 --- a/Plugins/ConnectPluginUtilities/MethodDescriptor+Extensions.swift +++ b/Plugins/ConnectPluginUtilities/MethodDescriptor+Extensions.swift @@ -37,28 +37,28 @@ extension MethodDescriptor { return """ func `\(methodName)`\ (headers: Connect.Headers\(includeDefaults ? " = [:]" : ""), \ - onResult: @escaping (Connect.StreamResult<\(outputName)>) -> Void) \ + onResult: @escaping @Sendable (Connect.StreamResult<\(outputName)>) -> Void) \ -> any Connect.BidirectionalStreamInterface<\(inputName)> """ } else if self.serverStreaming { return """ func `\(methodName)`\ (headers: Connect.Headers\(includeDefaults ? " = [:]" : ""), \ - onResult: @escaping (Connect.StreamResult<\(outputName)>) -> Void) \ + onResult: @escaping @Sendable (Connect.StreamResult<\(outputName)>) -> Void) \ -> any Connect.ServerOnlyStreamInterface<\(inputName)> """ } else if self.clientStreaming { return """ func `\(methodName)`\ (headers: Connect.Headers\(includeDefaults ? " = [:]" : ""), \ - onResult: @escaping (Connect.StreamResult<\(outputName)>) -> Void) \ + onResult: @escaping @Sendable (Connect.StreamResult<\(outputName)>) -> Void) \ -> any Connect.ClientOnlyStreamInterface<\(inputName)> """ } else { return """ func `\(methodName)`\ (request: \(inputName), headers: Connect.Headers\(includeDefaults ? " = [:]" : ""), \ - completion: @escaping (ResponseMessage<\(outputName)>) -> Void) \ + completion: @escaping @Sendable (ResponseMessage<\(outputName)>) -> Void) \ -> Connect.Cancelable """ } diff --git a/Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift b/Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift index d42920e5..cf8afb88 100644 --- a/Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift +++ b/Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift @@ -28,7 +28,6 @@ final class ConnectClientGenerator: Generator { self.visibility = "public" } super.init(descriptor, options: options) - self.printContent() } @@ -47,7 +46,7 @@ final class ConnectClientGenerator: Generator { self.printCommentsIfNeeded(for: service) let protocolName = service.protocolName(using: self.namer) - self.printLine("\(self.visibility) protocol \(protocolName) {") + self.printLine("\(self.visibility) protocol \(protocolName): Sendable {") self.indent { for method in service.methods { if self.options.generateCallbackMethods { @@ -64,7 +63,9 @@ final class ConnectClientGenerator: Generator { let className = service.implementationName(using: self.namer) self.printLine("/// Concrete implementation of `\(protocolName)`.") - self.printLine("\(self.visibility) final class \(className): \(protocolName) {") + self.printLine( + "\(self.visibility) final class \(className): \(protocolName), Sendable {" + ) self.indent { self.printLine("private let client: Connect.ProtocolClientInterface") self.printLine() diff --git a/README.md b/README.md index dd001863..5174249a 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,12 @@ protocol interfaces and client implementations:
Click to expand eliza.connect.swift ```swift -public protocol Eliza_V1_ChatServiceClientInterface { +public protocol Eliza_V1_ChatServiceClientInterface: Sendable { func say(request: Eliza_V1_SayRequest, headers: Headers) async -> ResponseMessage } -public final class Eliza_V1_ChatServiceClient: Eliza_V1_ChatServiceClientInterface { +public final class Eliza_V1_ChatServiceClient: Eliza_V1_ChatServiceClientInterface, Sendable { private let client: ProtocolClientInterface public init(client: ProtocolClientInterface) { @@ -85,7 +85,7 @@ protocol interfaces as the production clients:
Click to expand eliza.mock.swift ```swift -open class Eliza_V1_ChatServiceClientMock: Eliza_V1_ChatServiceClientInterface { +open class Eliza_V1_ChatServiceClientMock: Eliza_V1_ChatServiceClientInterface, @unchecked Sendable { public var mockAsyncSay = { (_: Eliza_V1_SayRequest) -> ResponseMessage in .init(message: .init()) } open func say(request: Eliza_V1_SayRequest, headers: Headers = [:]) @@ -103,7 +103,7 @@ func testMessagingViewModel() async { let client = Eliza_V1_ChatServiceClientMock() client.mockAsyncSay = { request in XCTAssertEqual(request.sentence, "hello!") - return ResponseMessage(message: .with { $0.sentence = "hi, i'm eliza!" }) + return ResponseMessage(result: .success(.with { $0.sentence = "hi, i'm eliza!" })) } let viewModel = MessagingViewModel(elizaClient: client) diff --git a/Tests/ConnectLibraryTests/ConnectConformance/CallbackConformance.swift b/Tests/ConnectLibraryTests/ConnectConformance/CallbackConformance.swift index 70a48878..423b700c 100644 --- a/Tests/ConnectLibraryTests/ConnectConformance/CallbackConformance.swift +++ b/Tests/ConnectLibraryTests/ConnectConformance/CallbackConformance.swift @@ -87,15 +87,17 @@ final class CallbackConformance: XCTestCase { try self.executeTestWithClients { client in let sizes = [31_415, 9, 2_653, 58_979] let expectation = self.expectation(description: "Stream completes") - var responseCount = 0 + let responseCount = Locked(0) let stream = client.streamingOutputCall { result in switch result { case .headers: break case .message(let output): - XCTAssertEqual(output.payload.body.count, sizes[responseCount]) - responseCount += 1 + responseCount.perform { responseCount in + XCTAssertEqual(output.payload.body.count, sizes[responseCount]) + responseCount += 1 + } case .complete(let code, let error, _): XCTAssertEqual(code, .ok) @@ -113,7 +115,7 @@ final class CallbackConformance: XCTestCase { }) XCTAssertEqual(XCTWaiter().wait(for: [expectation], timeout: kTimeout), .completed) - XCTAssertEqual(responseCount, 4) + XCTAssertEqual(responseCount.value, 4) } } @@ -433,7 +435,7 @@ final class CallbackConformance: XCTestCase { proto.domain = "connect-conformance" } let expectation = self.expectation(description: "Stream completes") - var responseCount = 0 + let responseCount = Locked(0) let sizes = [31_415, 9, 2_653, 58_979] let stream = client.failStreamingOutputCall { result in switch result { @@ -441,8 +443,10 @@ final class CallbackConformance: XCTestCase { break case .message(let output): - XCTAssertEqual(output.payload.body.count, sizes[responseCount]) - responseCount += 1 + responseCount.perform { responseCount in + XCTAssertEqual(output.payload.body.count, sizes[responseCount]) + responseCount += 1 + } case .complete(_, let error, _): guard let connectError = error as? ConnectError else { @@ -466,7 +470,7 @@ final class CallbackConformance: XCTestCase { }) XCTAssertEqual(XCTWaiter().wait(for: [expectation], timeout: kTimeout), .completed) - XCTAssertEqual(responseCount, 4) + XCTAssertEqual(responseCount.value, 4) } } @@ -478,9 +482,9 @@ final class CallbackConformance: XCTestCase { let cancelable = client.emptyCall( request: SwiftProtobuf.Google_Protobuf_Empty() ) { response in - XCTAssertEqual(response.code, .canceled) - XCTAssertEqual(response.error?.code, .canceled) - expectation.fulfill() + XCTAssertEqual(response.code, .canceled) + XCTAssertEqual(response.error?.code, .canceled) + expectation.fulfill() } cancelable.cancel() XCTAssertEqual(XCTWaiter().wait(for: [expectation], timeout: kTimeout), .completed) diff --git a/Tests/ConnectLibraryTests/ConnectMocksTests/ConnectMocksTests.swift b/Tests/ConnectLibraryTests/ConnectMocksTests/ConnectMocksTests.swift index b2605045..8cd6271f 100644 --- a/Tests/ConnectLibraryTests/ConnectMocksTests/ConnectMocksTests.swift +++ b/Tests/ConnectLibraryTests/ConnectMocksTests/ConnectMocksTests.swift @@ -29,11 +29,11 @@ final class ConnectMocksTests: XCTestCase { return ResponseMessage(result: .success(.with { $0.hostname = "pong" })) } - var receivedMessage: Connectrpc_Conformance_V1_SimpleResponse? + let receivedMessage = Locked(nil) client.unaryCall(request: .with { $0.fillUsername = true }) { response in - receivedMessage = response.message + receivedMessage.value = response.message } - XCTAssertEqual(receivedMessage?.hostname, "pong") + XCTAssertEqual(receivedMessage.value?.hostname, "pong") } func testMockUnaryAsyncAwait() async { @@ -70,17 +70,19 @@ final class ConnectMocksTests: XCTestCase { client.mockFullDuplexCall.onClose = { closeCalled = true } client.mockFullDuplexCall.outputs = Array(expectedResults) - var receivedResults = [ - StreamResult - ]() - let stream = client.fullDuplexCall { receivedResults.append($0) } + let receivedResults = Locked( + [StreamResult]() + ) + let stream = client.fullDuplexCall { result in + receivedResults.perform { $0.append(result) } + } try stream.send(expectedInputs[0]) try stream.send(expectedInputs[1]) stream.close() XCTAssertEqual(sentInputs, expectedInputs) XCTAssertEqual(client.mockFullDuplexCall.inputs, expectedInputs) - XCTAssertEqual(receivedResults, expectedResults) + XCTAssertEqual(receivedResults.value, expectedResults) XCTAssertTrue(closeCalled) XCTAssertTrue(client.mockFullDuplexCall.isClosed) } @@ -138,13 +140,15 @@ final class ConnectMocksTests: XCTestCase { client.mockUnimplementedStreamingOutputCall.onSend = { sentInputs.append($0) } client.mockUnimplementedStreamingOutputCall.outputs = Array(expectedResults) - var receivedResults = [StreamResult]() - let stream = client.unimplementedStreamingOutputCall { receivedResults.append($0) } + let receivedResults = Locked([StreamResult]()) + let stream = client.unimplementedStreamingOutputCall { result in + receivedResults.perform { $0.append(result) } + } try stream.send(expectedInput) XCTAssertEqual(sentInputs, [expectedInput]) XCTAssertEqual(client.mockUnimplementedStreamingOutputCall.inputs, [expectedInput]) - XCTAssertEqual(receivedResults, expectedResults) + XCTAssertEqual(receivedResults.value, expectedResults) } func testMockServerOnlyStreamAsyncAwait() async throws { diff --git a/Tests/ConnectLibraryTests/ConnectTests/ConnectErrorTests.swift b/Tests/ConnectLibraryTests/ConnectTests/ConnectErrorTests.swift index d6dc7a47..91b4d939 100644 --- a/Tests/ConnectLibraryTests/ConnectTests/ConnectErrorTests.swift +++ b/Tests/ConnectLibraryTests/ConnectTests/ConnectErrorTests.swift @@ -87,7 +87,7 @@ final class ConnectErrorTests: XCTestCase { // MARK: - Private - private func errorData(expectedDetails: [SwiftProtobuf.Message]) throws -> Data { + private func errorData(expectedDetails: [ProtobufMessage]) throws -> Data { // Example error from https://connectrpc.com/docs/protocol/#error-end-stream let dictionary: [String: Any] = [ "code": "unavailable", diff --git a/Tests/ConnectLibraryTests/ConnectTests/ProtocolClientConfigTests.swift b/Tests/ConnectLibraryTests/ConnectTests/ProtocolClientConfigTests.swift index 09b47260..bbacb7fa 100644 --- a/Tests/ConnectLibraryTests/ConnectTests/ProtocolClientConfigTests.swift +++ b/Tests/ConnectLibraryTests/ConnectTests/ProtocolClientConfigTests.swift @@ -61,7 +61,7 @@ final class ProtocolClientConfigTests: XCTestCase { let config = ProtocolClientConfig( host: "https://buf.build", networkProtocol: .connect, - interceptors: [NoopInterceptor.init] + interceptors: [{ NoopInterceptor(config: $0) }] ) XCTAssertTrue(config.interceptors[0](config) is NoopInterceptor) XCTAssertTrue(config.interceptors[1](config) is ConnectInterceptor) @@ -71,7 +71,7 @@ final class ProtocolClientConfigTests: XCTestCase { let config = ProtocolClientConfig( host: "https://buf.build", networkProtocol: .grpcWeb, - interceptors: [NoopInterceptor.init] + interceptors: [{ NoopInterceptor(config: $0) }] ) XCTAssertTrue(config.interceptors[0](config) is NoopInterceptor) XCTAssertTrue(config.interceptors[1](config) is GRPCWebInterceptor) diff --git a/Tests/ConnectLibraryTests/Generated/connectrpc/conformance/v1/test.connect.swift b/Tests/ConnectLibraryTests/Generated/connectrpc/conformance/v1/test.connect.swift index 841b9765..5f5a8e8f 100644 --- a/Tests/ConnectLibraryTests/Generated/connectrpc/conformance/v1/test.connect.swift +++ b/Tests/ConnectLibraryTests/Generated/connectrpc/conformance/v1/test.connect.swift @@ -9,11 +9,11 @@ import SwiftProtobuf /// A simple service to test the various types of RPCs and experiment with /// performance with various types of payload. -internal protocol Connectrpc_Conformance_V1_TestServiceClientInterface { +internal protocol Connectrpc_Conformance_V1_TestServiceClientInterface: Sendable { /// One empty request followed by one empty response. @discardableResult - func `emptyCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers, completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable + func `emptyCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers, completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable /// One empty request followed by one empty response. @available(iOS 13, *) @@ -21,7 +21,7 @@ internal protocol Connectrpc_Conformance_V1_TestServiceClientInterface { /// One request followed by one response. @discardableResult - func `unaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers, completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable + func `unaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers, completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable /// One request followed by one response. @available(iOS 13, *) @@ -29,7 +29,7 @@ internal protocol Connectrpc_Conformance_V1_TestServiceClientInterface { /// One request followed by one response. This RPC always fails. @discardableResult - func `failUnaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers, completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable + func `failUnaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers, completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable /// One request followed by one response. This RPC always fails. @available(iOS 13, *) @@ -39,7 +39,7 @@ internal protocol Connectrpc_Conformance_V1_TestServiceClientInterface { /// headers set such that a caching HTTP proxy (such as GFE) can /// satisfy subsequent requests. @discardableResult - func `cacheableUnaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers, completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable + func `cacheableUnaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers, completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable /// One request followed by one response. Response has cache control /// headers set such that a caching HTTP proxy (such as GFE) can @@ -49,7 +49,7 @@ internal protocol Connectrpc_Conformance_V1_TestServiceClientInterface { /// One request followed by a sequence of responses (streamed download). /// The server returns the payload with client desired type and sizes. - func `streamingOutputCall`(headers: Connect.Headers, onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface + func `streamingOutputCall`(headers: Connect.Headers, onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface /// One request followed by a sequence of responses (streamed download). /// The server returns the payload with client desired type and sizes. @@ -59,7 +59,7 @@ internal protocol Connectrpc_Conformance_V1_TestServiceClientInterface { /// One request followed by a sequence of responses (streamed download). /// The server returns the payload with client desired type and sizes. /// This RPC always responds with an error status. - func `failStreamingOutputCall`(headers: Connect.Headers, onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface + func `failStreamingOutputCall`(headers: Connect.Headers, onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface /// One request followed by a sequence of responses (streamed download). /// The server returns the payload with client desired type and sizes. @@ -69,7 +69,7 @@ internal protocol Connectrpc_Conformance_V1_TestServiceClientInterface { /// A sequence of requests followed by one response (streamed upload). /// The server returns the aggregated size of client payload as the result. - func `streamingInputCall`(headers: Connect.Headers, onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.ClientOnlyStreamInterface + func `streamingInputCall`(headers: Connect.Headers, onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.ClientOnlyStreamInterface /// A sequence of requests followed by one response (streamed upload). /// The server returns the aggregated size of client payload as the result. @@ -79,7 +79,7 @@ internal protocol Connectrpc_Conformance_V1_TestServiceClientInterface { /// A sequence of requests with each request served by the server immediately. /// As one request could lead to multiple responses, this interface /// demonstrates the idea of full duplexing. - func `fullDuplexCall`(headers: Connect.Headers, onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.BidirectionalStreamInterface + func `fullDuplexCall`(headers: Connect.Headers, onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.BidirectionalStreamInterface /// A sequence of requests with each request served by the server immediately. /// As one request could lead to multiple responses, this interface @@ -91,7 +91,7 @@ internal protocol Connectrpc_Conformance_V1_TestServiceClientInterface { /// The server buffers all the client requests and then serves them in order. A /// stream of responses are returned to the client when the server starts with /// first request. - func `halfDuplexCall`(headers: Connect.Headers, onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.BidirectionalStreamInterface + func `halfDuplexCall`(headers: Connect.Headers, onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.BidirectionalStreamInterface /// A sequence of requests followed by a sequence of responses. /// The server buffers all the client requests and then serves them in order. A @@ -103,7 +103,7 @@ internal protocol Connectrpc_Conformance_V1_TestServiceClientInterface { /// The test server will not implement this method. It will be used /// to test the behavior when clients call unimplemented methods. @discardableResult - func `unimplementedCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers, completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable + func `unimplementedCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers, completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable /// The test server will not implement this method. It will be used /// to test the behavior when clients call unimplemented methods. @@ -112,7 +112,7 @@ internal protocol Connectrpc_Conformance_V1_TestServiceClientInterface { /// The test server will not implement this method. It will be used /// to test the behavior when clients call unimplemented streaming output methods. - func `unimplementedStreamingOutputCall`(headers: Connect.Headers, onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface + func `unimplementedStreamingOutputCall`(headers: Connect.Headers, onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface /// The test server will not implement this method. It will be used /// to test the behavior when clients call unimplemented streaming output methods. @@ -121,7 +121,7 @@ internal protocol Connectrpc_Conformance_V1_TestServiceClientInterface { } /// Concrete implementation of `Connectrpc_Conformance_V1_TestServiceClientInterface`. -internal final class Connectrpc_Conformance_V1_TestServiceClient: Connectrpc_Conformance_V1_TestServiceClientInterface { +internal final class Connectrpc_Conformance_V1_TestServiceClient: Connectrpc_Conformance_V1_TestServiceClientInterface, Sendable { private let client: Connect.ProtocolClientInterface internal init(client: Connect.ProtocolClientInterface) { @@ -129,7 +129,7 @@ internal final class Connectrpc_Conformance_V1_TestServiceClient: Connectrpc_Con } @discardableResult - internal func `emptyCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `emptyCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { return self.client.unary(path: "/connectrpc.conformance.v1.TestService/EmptyCall", request: request, headers: headers, completion: completion) } @@ -139,7 +139,7 @@ internal final class Connectrpc_Conformance_V1_TestServiceClient: Connectrpc_Con } @discardableResult - internal func `unaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `unaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { return self.client.unary(path: "/connectrpc.conformance.v1.TestService/UnaryCall", request: request, headers: headers, completion: completion) } @@ -149,7 +149,7 @@ internal final class Connectrpc_Conformance_V1_TestServiceClient: Connectrpc_Con } @discardableResult - internal func `failUnaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `failUnaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { return self.client.unary(path: "/connectrpc.conformance.v1.TestService/FailUnaryCall", request: request, headers: headers, completion: completion) } @@ -159,7 +159,7 @@ internal final class Connectrpc_Conformance_V1_TestServiceClient: Connectrpc_Con } @discardableResult - internal func `cacheableUnaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `cacheableUnaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { return self.client.unary(path: "/connectrpc.conformance.v1.TestService/CacheableUnaryCall", request: request, headers: headers, completion: completion) } @@ -168,7 +168,7 @@ internal final class Connectrpc_Conformance_V1_TestServiceClient: Connectrpc_Con return await self.client.unary(path: "/connectrpc.conformance.v1.TestService/CacheableUnaryCall", request: request, headers: headers) } - internal func `streamingOutputCall`(headers: Connect.Headers = [:], onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface { + internal func `streamingOutputCall`(headers: Connect.Headers = [:], onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface { return self.client.serverOnlyStream(path: "/connectrpc.conformance.v1.TestService/StreamingOutputCall", headers: headers, onResult: onResult) } @@ -177,7 +177,7 @@ internal final class Connectrpc_Conformance_V1_TestServiceClient: Connectrpc_Con return self.client.serverOnlyStream(path: "/connectrpc.conformance.v1.TestService/StreamingOutputCall", headers: headers) } - internal func `failStreamingOutputCall`(headers: Connect.Headers = [:], onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface { + internal func `failStreamingOutputCall`(headers: Connect.Headers = [:], onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface { return self.client.serverOnlyStream(path: "/connectrpc.conformance.v1.TestService/FailStreamingOutputCall", headers: headers, onResult: onResult) } @@ -186,7 +186,7 @@ internal final class Connectrpc_Conformance_V1_TestServiceClient: Connectrpc_Con return self.client.serverOnlyStream(path: "/connectrpc.conformance.v1.TestService/FailStreamingOutputCall", headers: headers) } - internal func `streamingInputCall`(headers: Connect.Headers = [:], onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.ClientOnlyStreamInterface { + internal func `streamingInputCall`(headers: Connect.Headers = [:], onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.ClientOnlyStreamInterface { return self.client.clientOnlyStream(path: "/connectrpc.conformance.v1.TestService/StreamingInputCall", headers: headers, onResult: onResult) } @@ -195,7 +195,7 @@ internal final class Connectrpc_Conformance_V1_TestServiceClient: Connectrpc_Con return self.client.clientOnlyStream(path: "/connectrpc.conformance.v1.TestService/StreamingInputCall", headers: headers) } - internal func `fullDuplexCall`(headers: Connect.Headers = [:], onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.BidirectionalStreamInterface { + internal func `fullDuplexCall`(headers: Connect.Headers = [:], onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.BidirectionalStreamInterface { return self.client.bidirectionalStream(path: "/connectrpc.conformance.v1.TestService/FullDuplexCall", headers: headers, onResult: onResult) } @@ -204,7 +204,7 @@ internal final class Connectrpc_Conformance_V1_TestServiceClient: Connectrpc_Con return self.client.bidirectionalStream(path: "/connectrpc.conformance.v1.TestService/FullDuplexCall", headers: headers) } - internal func `halfDuplexCall`(headers: Connect.Headers = [:], onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.BidirectionalStreamInterface { + internal func `halfDuplexCall`(headers: Connect.Headers = [:], onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.BidirectionalStreamInterface { return self.client.bidirectionalStream(path: "/connectrpc.conformance.v1.TestService/HalfDuplexCall", headers: headers, onResult: onResult) } @@ -214,7 +214,7 @@ internal final class Connectrpc_Conformance_V1_TestServiceClient: Connectrpc_Con } @discardableResult - internal func `unimplementedCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `unimplementedCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { return self.client.unary(path: "/connectrpc.conformance.v1.TestService/UnimplementedCall", request: request, headers: headers, completion: completion) } @@ -223,7 +223,7 @@ internal final class Connectrpc_Conformance_V1_TestServiceClient: Connectrpc_Con return await self.client.unary(path: "/connectrpc.conformance.v1.TestService/UnimplementedCall", request: request, headers: headers) } - internal func `unimplementedStreamingOutputCall`(headers: Connect.Headers = [:], onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface { + internal func `unimplementedStreamingOutputCall`(headers: Connect.Headers = [:], onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface { return self.client.serverOnlyStream(path: "/connectrpc.conformance.v1.TestService/UnimplementedStreamingOutputCall", headers: headers, onResult: onResult) } @@ -251,18 +251,18 @@ internal final class Connectrpc_Conformance_V1_TestServiceClient: Connectrpc_Con /// A simple service NOT implemented at servers so clients can test for /// that case. -internal protocol Connectrpc_Conformance_V1_UnimplementedServiceClientInterface { +internal protocol Connectrpc_Conformance_V1_UnimplementedServiceClientInterface: Sendable { /// A call that no server should implement @discardableResult - func `unimplementedCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers, completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable + func `unimplementedCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers, completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable /// A call that no server should implement @available(iOS 13, *) func `unimplementedCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers) async -> ResponseMessage /// A call that no server should implement - func `unimplementedStreamingOutputCall`(headers: Connect.Headers, onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface + func `unimplementedStreamingOutputCall`(headers: Connect.Headers, onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface /// A call that no server should implement @available(iOS 13, *) @@ -270,7 +270,7 @@ internal protocol Connectrpc_Conformance_V1_UnimplementedServiceClientInterface } /// Concrete implementation of `Connectrpc_Conformance_V1_UnimplementedServiceClientInterface`. -internal final class Connectrpc_Conformance_V1_UnimplementedServiceClient: Connectrpc_Conformance_V1_UnimplementedServiceClientInterface { +internal final class Connectrpc_Conformance_V1_UnimplementedServiceClient: Connectrpc_Conformance_V1_UnimplementedServiceClientInterface, Sendable { private let client: Connect.ProtocolClientInterface internal init(client: Connect.ProtocolClientInterface) { @@ -278,7 +278,7 @@ internal final class Connectrpc_Conformance_V1_UnimplementedServiceClient: Conne } @discardableResult - internal func `unimplementedCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `unimplementedCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { return self.client.unary(path: "/connectrpc.conformance.v1.UnimplementedService/UnimplementedCall", request: request, headers: headers, completion: completion) } @@ -287,7 +287,7 @@ internal final class Connectrpc_Conformance_V1_UnimplementedServiceClient: Conne return await self.client.unary(path: "/connectrpc.conformance.v1.UnimplementedService/UnimplementedCall", request: request, headers: headers) } - internal func `unimplementedStreamingOutputCall`(headers: Connect.Headers = [:], onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface { + internal func `unimplementedStreamingOutputCall`(headers: Connect.Headers = [:], onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface { return self.client.serverOnlyStream(path: "/connectrpc.conformance.v1.UnimplementedService/UnimplementedStreamingOutputCall", headers: headers, onResult: onResult) } @@ -305,23 +305,23 @@ internal final class Connectrpc_Conformance_V1_UnimplementedServiceClient: Conne } /// A service used to control reconnect server. -internal protocol Connectrpc_Conformance_V1_ReconnectServiceClientInterface { +internal protocol Connectrpc_Conformance_V1_ReconnectServiceClientInterface: Sendable { @discardableResult - func `start`(request: Connectrpc_Conformance_V1_ReconnectParams, headers: Connect.Headers, completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable + func `start`(request: Connectrpc_Conformance_V1_ReconnectParams, headers: Connect.Headers, completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable @available(iOS 13, *) func `start`(request: Connectrpc_Conformance_V1_ReconnectParams, headers: Connect.Headers) async -> ResponseMessage @discardableResult - func `stop`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers, completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable + func `stop`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers, completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable @available(iOS 13, *) func `stop`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers) async -> ResponseMessage } /// Concrete implementation of `Connectrpc_Conformance_V1_ReconnectServiceClientInterface`. -internal final class Connectrpc_Conformance_V1_ReconnectServiceClient: Connectrpc_Conformance_V1_ReconnectServiceClientInterface { +internal final class Connectrpc_Conformance_V1_ReconnectServiceClient: Connectrpc_Conformance_V1_ReconnectServiceClientInterface, Sendable { private let client: Connect.ProtocolClientInterface internal init(client: Connect.ProtocolClientInterface) { @@ -329,7 +329,7 @@ internal final class Connectrpc_Conformance_V1_ReconnectServiceClient: Connectrp } @discardableResult - internal func `start`(request: Connectrpc_Conformance_V1_ReconnectParams, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `start`(request: Connectrpc_Conformance_V1_ReconnectParams, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { return self.client.unary(path: "/connectrpc.conformance.v1.ReconnectService/Start", request: request, headers: headers, completion: completion) } @@ -339,7 +339,7 @@ internal final class Connectrpc_Conformance_V1_ReconnectServiceClient: Connectrp } @discardableResult - internal func `stop`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `stop`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { return self.client.unary(path: "/connectrpc.conformance.v1.ReconnectService/Stop", request: request, headers: headers, completion: completion) } @@ -357,11 +357,11 @@ internal final class Connectrpc_Conformance_V1_ReconnectServiceClient: Connectrp } /// A service used to obtain stats for verifying LB behavior. -internal protocol Connectrpc_Conformance_V1_LoadBalancerStatsServiceClientInterface { +internal protocol Connectrpc_Conformance_V1_LoadBalancerStatsServiceClientInterface: Sendable { /// Gets the backend distribution for RPCs sent by a test client. @discardableResult - func `getClientStats`(request: Connectrpc_Conformance_V1_LoadBalancerStatsRequest, headers: Connect.Headers, completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable + func `getClientStats`(request: Connectrpc_Conformance_V1_LoadBalancerStatsRequest, headers: Connect.Headers, completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable /// Gets the backend distribution for RPCs sent by a test client. @available(iOS 13, *) @@ -369,7 +369,7 @@ internal protocol Connectrpc_Conformance_V1_LoadBalancerStatsServiceClientInterf /// Gets the accumulated stats for RPCs sent by a test client. @discardableResult - func `getClientAccumulatedStats`(request: Connectrpc_Conformance_V1_LoadBalancerAccumulatedStatsRequest, headers: Connect.Headers, completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable + func `getClientAccumulatedStats`(request: Connectrpc_Conformance_V1_LoadBalancerAccumulatedStatsRequest, headers: Connect.Headers, completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable /// Gets the accumulated stats for RPCs sent by a test client. @available(iOS 13, *) @@ -377,7 +377,7 @@ internal protocol Connectrpc_Conformance_V1_LoadBalancerStatsServiceClientInterf } /// Concrete implementation of `Connectrpc_Conformance_V1_LoadBalancerStatsServiceClientInterface`. -internal final class Connectrpc_Conformance_V1_LoadBalancerStatsServiceClient: Connectrpc_Conformance_V1_LoadBalancerStatsServiceClientInterface { +internal final class Connectrpc_Conformance_V1_LoadBalancerStatsServiceClient: Connectrpc_Conformance_V1_LoadBalancerStatsServiceClientInterface, Sendable { private let client: Connect.ProtocolClientInterface internal init(client: Connect.ProtocolClientInterface) { @@ -385,7 +385,7 @@ internal final class Connectrpc_Conformance_V1_LoadBalancerStatsServiceClient: C } @discardableResult - internal func `getClientStats`(request: Connectrpc_Conformance_V1_LoadBalancerStatsRequest, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `getClientStats`(request: Connectrpc_Conformance_V1_LoadBalancerStatsRequest, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { return self.client.unary(path: "/connectrpc.conformance.v1.LoadBalancerStatsService/GetClientStats", request: request, headers: headers, completion: completion) } @@ -395,7 +395,7 @@ internal final class Connectrpc_Conformance_V1_LoadBalancerStatsServiceClient: C } @discardableResult - internal func `getClientAccumulatedStats`(request: Connectrpc_Conformance_V1_LoadBalancerAccumulatedStatsRequest, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `getClientAccumulatedStats`(request: Connectrpc_Conformance_V1_LoadBalancerAccumulatedStatsRequest, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { return self.client.unary(path: "/connectrpc.conformance.v1.LoadBalancerStatsService/GetClientAccumulatedStats", request: request, headers: headers, completion: completion) } @@ -413,23 +413,23 @@ internal final class Connectrpc_Conformance_V1_LoadBalancerStatsServiceClient: C } /// A service to remotely control health status of an xDS test server. -internal protocol Connectrpc_Conformance_V1_XdsUpdateHealthServiceClientInterface { +internal protocol Connectrpc_Conformance_V1_XdsUpdateHealthServiceClientInterface: Sendable { @discardableResult - func `setServing`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers, completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable + func `setServing`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers, completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable @available(iOS 13, *) func `setServing`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers) async -> ResponseMessage @discardableResult - func `setNotServing`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers, completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable + func `setNotServing`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers, completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable @available(iOS 13, *) func `setNotServing`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers) async -> ResponseMessage } /// Concrete implementation of `Connectrpc_Conformance_V1_XdsUpdateHealthServiceClientInterface`. -internal final class Connectrpc_Conformance_V1_XdsUpdateHealthServiceClient: Connectrpc_Conformance_V1_XdsUpdateHealthServiceClientInterface { +internal final class Connectrpc_Conformance_V1_XdsUpdateHealthServiceClient: Connectrpc_Conformance_V1_XdsUpdateHealthServiceClientInterface, Sendable { private let client: Connect.ProtocolClientInterface internal init(client: Connect.ProtocolClientInterface) { @@ -437,7 +437,7 @@ internal final class Connectrpc_Conformance_V1_XdsUpdateHealthServiceClient: Con } @discardableResult - internal func `setServing`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `setServing`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { return self.client.unary(path: "/connectrpc.conformance.v1.XdsUpdateHealthService/SetServing", request: request, headers: headers, completion: completion) } @@ -447,7 +447,7 @@ internal final class Connectrpc_Conformance_V1_XdsUpdateHealthServiceClient: Con } @discardableResult - internal func `setNotServing`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `setNotServing`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { return self.client.unary(path: "/connectrpc.conformance.v1.XdsUpdateHealthService/SetNotServing", request: request, headers: headers, completion: completion) } @@ -465,11 +465,11 @@ internal final class Connectrpc_Conformance_V1_XdsUpdateHealthServiceClient: Con } /// A service to dynamically update the configuration of an xDS test client. -internal protocol Connectrpc_Conformance_V1_XdsUpdateClientConfigureServiceClientInterface { +internal protocol Connectrpc_Conformance_V1_XdsUpdateClientConfigureServiceClientInterface: Sendable { /// Update the tes client's configuration. @discardableResult - func `configure`(request: Connectrpc_Conformance_V1_ClientConfigureRequest, headers: Connect.Headers, completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable + func `configure`(request: Connectrpc_Conformance_V1_ClientConfigureRequest, headers: Connect.Headers, completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable /// Update the tes client's configuration. @available(iOS 13, *) @@ -477,7 +477,7 @@ internal protocol Connectrpc_Conformance_V1_XdsUpdateClientConfigureServiceClien } /// Concrete implementation of `Connectrpc_Conformance_V1_XdsUpdateClientConfigureServiceClientInterface`. -internal final class Connectrpc_Conformance_V1_XdsUpdateClientConfigureServiceClient: Connectrpc_Conformance_V1_XdsUpdateClientConfigureServiceClientInterface { +internal final class Connectrpc_Conformance_V1_XdsUpdateClientConfigureServiceClient: Connectrpc_Conformance_V1_XdsUpdateClientConfigureServiceClientInterface, Sendable { private let client: Connect.ProtocolClientInterface internal init(client: Connect.ProtocolClientInterface) { @@ -485,7 +485,7 @@ internal final class Connectrpc_Conformance_V1_XdsUpdateClientConfigureServiceCl } @discardableResult - internal func `configure`(request: Connectrpc_Conformance_V1_ClientConfigureRequest, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `configure`(request: Connectrpc_Conformance_V1_ClientConfigureRequest, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { return self.client.unary(path: "/connectrpc.conformance.v1.XdsUpdateClientConfigureService/Configure", request: request, headers: headers, completion: completion) } diff --git a/Tests/ConnectLibraryTests/Generated/connectrpc/conformance/v1/test.mock.swift b/Tests/ConnectLibraryTests/Generated/connectrpc/conformance/v1/test.mock.swift index bf22c7dd..d9152833 100644 --- a/Tests/ConnectLibraryTests/Generated/connectrpc/conformance/v1/test.mock.swift +++ b/Tests/ConnectLibraryTests/Generated/connectrpc/conformance/v1/test.mock.swift @@ -12,11 +12,14 @@ import SwiftProtobuf /// Mock implementation of `Connectrpc_Conformance_V1_TestServiceClientInterface`. /// /// Production implementations can be substituted with instances of this -/// class, allowing for mocking RPC calls. Behavior can be customized +/// class to mock RPC calls. Behavior can be customized /// either through the properties on this class or by -/// subclassing the class and overriding its methods. +/// subclassing the mock and overriding its methods. +/// +/// Note: This class does not handle thread-safe locking, but provides +/// `@unchecked Sendable` conformance to simplify testing and mocking. @available(iOS 13, *) -internal class Connectrpc_Conformance_V1_TestServiceClientMock: Connectrpc_Conformance_V1_TestServiceClientInterface { +internal class Connectrpc_Conformance_V1_TestServiceClientMock: Connectrpc_Conformance_V1_TestServiceClientInterface, @unchecked Sendable { private var cancellables = [Combine.AnyCancellable]() /// Mocked for calls to `emptyCall()`. @@ -67,7 +70,7 @@ internal class Connectrpc_Conformance_V1_TestServiceClientMock: Connectrpc_Confo internal init() {} @discardableResult - internal func `emptyCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `emptyCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { completion(self.mockEmptyCall(request)) return Connect.Cancelable {} } @@ -77,7 +80,7 @@ internal class Connectrpc_Conformance_V1_TestServiceClientMock: Connectrpc_Confo } @discardableResult - internal func `unaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `unaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { completion(self.mockUnaryCall(request)) return Connect.Cancelable {} } @@ -87,7 +90,7 @@ internal class Connectrpc_Conformance_V1_TestServiceClientMock: Connectrpc_Confo } @discardableResult - internal func `failUnaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `failUnaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { completion(self.mockFailUnaryCall(request)) return Connect.Cancelable {} } @@ -97,7 +100,7 @@ internal class Connectrpc_Conformance_V1_TestServiceClientMock: Connectrpc_Confo } @discardableResult - internal func `cacheableUnaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `cacheableUnaryCall`(request: Connectrpc_Conformance_V1_SimpleRequest, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { completion(self.mockCacheableUnaryCall(request)) return Connect.Cancelable {} } @@ -106,7 +109,7 @@ internal class Connectrpc_Conformance_V1_TestServiceClientMock: Connectrpc_Confo return self.mockAsyncCacheableUnaryCall(request) } - internal func `streamingOutputCall`(headers: Connect.Headers = [:], onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface { + internal func `streamingOutputCall`(headers: Connect.Headers = [:], onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface { self.mockStreamingOutputCall.$inputs.first { !$0.isEmpty }.sink { _ in self.mockStreamingOutputCall.outputs.forEach(onResult) }.store(in: &self.cancellables) return self.mockStreamingOutputCall } @@ -115,7 +118,7 @@ internal class Connectrpc_Conformance_V1_TestServiceClientMock: Connectrpc_Confo return self.mockAsyncStreamingOutputCall } - internal func `failStreamingOutputCall`(headers: Connect.Headers = [:], onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface { + internal func `failStreamingOutputCall`(headers: Connect.Headers = [:], onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface { self.mockFailStreamingOutputCall.$inputs.first { !$0.isEmpty }.sink { _ in self.mockFailStreamingOutputCall.outputs.forEach(onResult) }.store(in: &self.cancellables) return self.mockFailStreamingOutputCall } @@ -124,7 +127,7 @@ internal class Connectrpc_Conformance_V1_TestServiceClientMock: Connectrpc_Confo return self.mockAsyncFailStreamingOutputCall } - internal func `streamingInputCall`(headers: Connect.Headers = [:], onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.ClientOnlyStreamInterface { + internal func `streamingInputCall`(headers: Connect.Headers = [:], onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.ClientOnlyStreamInterface { self.mockStreamingInputCall.$inputs.first { !$0.isEmpty }.sink { _ in self.mockStreamingInputCall.outputs.forEach(onResult) }.store(in: &self.cancellables) return self.mockStreamingInputCall } @@ -133,7 +136,7 @@ internal class Connectrpc_Conformance_V1_TestServiceClientMock: Connectrpc_Confo return self.mockAsyncStreamingInputCall } - internal func `fullDuplexCall`(headers: Connect.Headers = [:], onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.BidirectionalStreamInterface { + internal func `fullDuplexCall`(headers: Connect.Headers = [:], onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.BidirectionalStreamInterface { self.mockFullDuplexCall.$inputs.first { !$0.isEmpty }.sink { _ in self.mockFullDuplexCall.outputs.forEach(onResult) }.store(in: &self.cancellables) return self.mockFullDuplexCall } @@ -142,7 +145,7 @@ internal class Connectrpc_Conformance_V1_TestServiceClientMock: Connectrpc_Confo return self.mockAsyncFullDuplexCall } - internal func `halfDuplexCall`(headers: Connect.Headers = [:], onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.BidirectionalStreamInterface { + internal func `halfDuplexCall`(headers: Connect.Headers = [:], onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.BidirectionalStreamInterface { self.mockHalfDuplexCall.$inputs.first { !$0.isEmpty }.sink { _ in self.mockHalfDuplexCall.outputs.forEach(onResult) }.store(in: &self.cancellables) return self.mockHalfDuplexCall } @@ -152,7 +155,7 @@ internal class Connectrpc_Conformance_V1_TestServiceClientMock: Connectrpc_Confo } @discardableResult - internal func `unimplementedCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `unimplementedCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { completion(self.mockUnimplementedCall(request)) return Connect.Cancelable {} } @@ -161,7 +164,7 @@ internal class Connectrpc_Conformance_V1_TestServiceClientMock: Connectrpc_Confo return self.mockAsyncUnimplementedCall(request) } - internal func `unimplementedStreamingOutputCall`(headers: Connect.Headers = [:], onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface { + internal func `unimplementedStreamingOutputCall`(headers: Connect.Headers = [:], onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface { self.mockUnimplementedStreamingOutputCall.$inputs.first { !$0.isEmpty }.sink { _ in self.mockUnimplementedStreamingOutputCall.outputs.forEach(onResult) }.store(in: &self.cancellables) return self.mockUnimplementedStreamingOutputCall } @@ -174,11 +177,14 @@ internal class Connectrpc_Conformance_V1_TestServiceClientMock: Connectrpc_Confo /// Mock implementation of `Connectrpc_Conformance_V1_UnimplementedServiceClientInterface`. /// /// Production implementations can be substituted with instances of this -/// class, allowing for mocking RPC calls. Behavior can be customized +/// class to mock RPC calls. Behavior can be customized /// either through the properties on this class or by -/// subclassing the class and overriding its methods. +/// subclassing the mock and overriding its methods. +/// +/// Note: This class does not handle thread-safe locking, but provides +/// `@unchecked Sendable` conformance to simplify testing and mocking. @available(iOS 13, *) -internal class Connectrpc_Conformance_V1_UnimplementedServiceClientMock: Connectrpc_Conformance_V1_UnimplementedServiceClientInterface { +internal class Connectrpc_Conformance_V1_UnimplementedServiceClientMock: Connectrpc_Conformance_V1_UnimplementedServiceClientInterface, @unchecked Sendable { private var cancellables = [Combine.AnyCancellable]() /// Mocked for calls to `unimplementedCall()`. @@ -193,7 +199,7 @@ internal class Connectrpc_Conformance_V1_UnimplementedServiceClientMock: Connect internal init() {} @discardableResult - internal func `unimplementedCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `unimplementedCall`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { completion(self.mockUnimplementedCall(request)) return Connect.Cancelable {} } @@ -202,7 +208,7 @@ internal class Connectrpc_Conformance_V1_UnimplementedServiceClientMock: Connect return self.mockAsyncUnimplementedCall(request) } - internal func `unimplementedStreamingOutputCall`(headers: Connect.Headers = [:], onResult: @escaping (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface { + internal func `unimplementedStreamingOutputCall`(headers: Connect.Headers = [:], onResult: @escaping @Sendable (Connect.StreamResult) -> Void) -> any Connect.ServerOnlyStreamInterface { self.mockUnimplementedStreamingOutputCall.$inputs.first { !$0.isEmpty }.sink { _ in self.mockUnimplementedStreamingOutputCall.outputs.forEach(onResult) }.store(in: &self.cancellables) return self.mockUnimplementedStreamingOutputCall } @@ -215,11 +221,14 @@ internal class Connectrpc_Conformance_V1_UnimplementedServiceClientMock: Connect /// Mock implementation of `Connectrpc_Conformance_V1_ReconnectServiceClientInterface`. /// /// Production implementations can be substituted with instances of this -/// class, allowing for mocking RPC calls. Behavior can be customized +/// class to mock RPC calls. Behavior can be customized /// either through the properties on this class or by -/// subclassing the class and overriding its methods. +/// subclassing the mock and overriding its methods. +/// +/// Note: This class does not handle thread-safe locking, but provides +/// `@unchecked Sendable` conformance to simplify testing and mocking. @available(iOS 13, *) -internal class Connectrpc_Conformance_V1_ReconnectServiceClientMock: Connectrpc_Conformance_V1_ReconnectServiceClientInterface { +internal class Connectrpc_Conformance_V1_ReconnectServiceClientMock: Connectrpc_Conformance_V1_ReconnectServiceClientInterface, @unchecked Sendable { private var cancellables = [Combine.AnyCancellable]() /// Mocked for calls to `start()`. @@ -234,7 +243,7 @@ internal class Connectrpc_Conformance_V1_ReconnectServiceClientMock: Connectrpc_ internal init() {} @discardableResult - internal func `start`(request: Connectrpc_Conformance_V1_ReconnectParams, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `start`(request: Connectrpc_Conformance_V1_ReconnectParams, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { completion(self.mockStart(request)) return Connect.Cancelable {} } @@ -244,7 +253,7 @@ internal class Connectrpc_Conformance_V1_ReconnectServiceClientMock: Connectrpc_ } @discardableResult - internal func `stop`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `stop`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { completion(self.mockStop(request)) return Connect.Cancelable {} } @@ -257,11 +266,14 @@ internal class Connectrpc_Conformance_V1_ReconnectServiceClientMock: Connectrpc_ /// Mock implementation of `Connectrpc_Conformance_V1_LoadBalancerStatsServiceClientInterface`. /// /// Production implementations can be substituted with instances of this -/// class, allowing for mocking RPC calls. Behavior can be customized +/// class to mock RPC calls. Behavior can be customized /// either through the properties on this class or by -/// subclassing the class and overriding its methods. +/// subclassing the mock and overriding its methods. +/// +/// Note: This class does not handle thread-safe locking, but provides +/// `@unchecked Sendable` conformance to simplify testing and mocking. @available(iOS 13, *) -internal class Connectrpc_Conformance_V1_LoadBalancerStatsServiceClientMock: Connectrpc_Conformance_V1_LoadBalancerStatsServiceClientInterface { +internal class Connectrpc_Conformance_V1_LoadBalancerStatsServiceClientMock: Connectrpc_Conformance_V1_LoadBalancerStatsServiceClientInterface, @unchecked Sendable { private var cancellables = [Combine.AnyCancellable]() /// Mocked for calls to `getClientStats()`. @@ -276,7 +288,7 @@ internal class Connectrpc_Conformance_V1_LoadBalancerStatsServiceClientMock: Con internal init() {} @discardableResult - internal func `getClientStats`(request: Connectrpc_Conformance_V1_LoadBalancerStatsRequest, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `getClientStats`(request: Connectrpc_Conformance_V1_LoadBalancerStatsRequest, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { completion(self.mockGetClientStats(request)) return Connect.Cancelable {} } @@ -286,7 +298,7 @@ internal class Connectrpc_Conformance_V1_LoadBalancerStatsServiceClientMock: Con } @discardableResult - internal func `getClientAccumulatedStats`(request: Connectrpc_Conformance_V1_LoadBalancerAccumulatedStatsRequest, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `getClientAccumulatedStats`(request: Connectrpc_Conformance_V1_LoadBalancerAccumulatedStatsRequest, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { completion(self.mockGetClientAccumulatedStats(request)) return Connect.Cancelable {} } @@ -299,11 +311,14 @@ internal class Connectrpc_Conformance_V1_LoadBalancerStatsServiceClientMock: Con /// Mock implementation of `Connectrpc_Conformance_V1_XdsUpdateHealthServiceClientInterface`. /// /// Production implementations can be substituted with instances of this -/// class, allowing for mocking RPC calls. Behavior can be customized +/// class to mock RPC calls. Behavior can be customized /// either through the properties on this class or by -/// subclassing the class and overriding its methods. +/// subclassing the mock and overriding its methods. +/// +/// Note: This class does not handle thread-safe locking, but provides +/// `@unchecked Sendable` conformance to simplify testing and mocking. @available(iOS 13, *) -internal class Connectrpc_Conformance_V1_XdsUpdateHealthServiceClientMock: Connectrpc_Conformance_V1_XdsUpdateHealthServiceClientInterface { +internal class Connectrpc_Conformance_V1_XdsUpdateHealthServiceClientMock: Connectrpc_Conformance_V1_XdsUpdateHealthServiceClientInterface, @unchecked Sendable { private var cancellables = [Combine.AnyCancellable]() /// Mocked for calls to `setServing()`. @@ -318,7 +333,7 @@ internal class Connectrpc_Conformance_V1_XdsUpdateHealthServiceClientMock: Conne internal init() {} @discardableResult - internal func `setServing`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `setServing`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { completion(self.mockSetServing(request)) return Connect.Cancelable {} } @@ -328,7 +343,7 @@ internal class Connectrpc_Conformance_V1_XdsUpdateHealthServiceClientMock: Conne } @discardableResult - internal func `setNotServing`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `setNotServing`(request: SwiftProtobuf.Google_Protobuf_Empty, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { completion(self.mockSetNotServing(request)) return Connect.Cancelable {} } @@ -341,11 +356,14 @@ internal class Connectrpc_Conformance_V1_XdsUpdateHealthServiceClientMock: Conne /// Mock implementation of `Connectrpc_Conformance_V1_XdsUpdateClientConfigureServiceClientInterface`. /// /// Production implementations can be substituted with instances of this -/// class, allowing for mocking RPC calls. Behavior can be customized +/// class to mock RPC calls. Behavior can be customized /// either through the properties on this class or by -/// subclassing the class and overriding its methods. +/// subclassing the mock and overriding its methods. +/// +/// Note: This class does not handle thread-safe locking, but provides +/// `@unchecked Sendable` conformance to simplify testing and mocking. @available(iOS 13, *) -internal class Connectrpc_Conformance_V1_XdsUpdateClientConfigureServiceClientMock: Connectrpc_Conformance_V1_XdsUpdateClientConfigureServiceClientInterface { +internal class Connectrpc_Conformance_V1_XdsUpdateClientConfigureServiceClientMock: Connectrpc_Conformance_V1_XdsUpdateClientConfigureServiceClientInterface, @unchecked Sendable { private var cancellables = [Combine.AnyCancellable]() /// Mocked for calls to `configure()`. @@ -356,7 +374,7 @@ internal class Connectrpc_Conformance_V1_XdsUpdateClientConfigureServiceClientMo internal init() {} @discardableResult - internal func `configure`(request: Connectrpc_Conformance_V1_ClientConfigureRequest, headers: Connect.Headers = [:], completion: @escaping (ResponseMessage) -> Void) -> Connect.Cancelable { + internal func `configure`(request: Connectrpc_Conformance_V1_ClientConfigureRequest, headers: Connect.Headers = [:], completion: @escaping @Sendable (ResponseMessage) -> Void) -> Connect.Cancelable { completion(self.mockConfigure(request)) return Connect.Cancelable {} }